Unverified Commit b8a4a399 authored by Shagen Ogandzhanian's avatar Shagen Ogandzhanian Committed by GitHub

Introduce minimal API layer for drawing on Canvas (using WebGL bindings) (#205)

* MakeGrContext borrowed from canvaskit

* Minimal API for webgl interaction

* Minimal API for webgl interaction EXPERIMENTING

* Minimal wasm implementation

* experimenten wasm: draw circle

* update skia to m94

* update external symbol names

* Introduce minimal Kotlin Wrapper API for initializing GL entities on js side

* Clean wrapper.cc

WasmTests should be revisited after we adopt common approach for tests
on all platforms

* Remove obsolete GL initializer from index.html

* Clean up wasmCompile task

* Remove obsolete code from setup

* Rename GetWebGLContextK to just GetWebGLContext

* Remove redundant karma config serialization

looks like a forgotten remnant of debugging to me

* Remove WasmTests::ping empty Test

* Remove obsolete canvas creation code

in Skiko we won't create DOM elements at all
Co-authored-by: 's avatarOleksandr Karpovich <oleksandr.karpovich@jetbrains.com>
parent 6463dfc5
package org.jetbrains.skiko.sample.js
import org.jetbrains.skia.Paint
import kotlinx.browser.document
import kotlinx.browser.window
import org.jetbrains.skia.*
import org.jetbrains.skiko.wasm.GetWebGLContext
import org.jetbrains.skiko.wasm.onWasmReady
import org.w3c.dom.HTMLCanvasElement
fun main(args: Array<String>) {
fun main() {
onWasmReady {
println(Paint())
val paint = Paint()
paint.setColor4f(Color4f(0f, 1f, 0f, 1.0f))
paint.setMode(PaintMode.FILL)
paint.setAntiAlias(true)
val canvas = document.getElementById("c") as HTMLCanvasElement
canvas.getContext("webgl")
GetWebGLContext(canvas)
val grContext = DirectContext.makeGL()
val surface = Surface.makeFromBackendRenderTarget(
grContext,
BackendRenderTarget.makeGL(
canvas.width, canvas.height, 1, 8, 0, 0x8058
),
SurfaceOrigin.BOTTOM_LEFT,
SurfaceColorFormat.RGBA_8888,
ColorSpace.sRGB
)
fun draw() {
surface.canvas.drawCircle(50f, 50f, 25f, paint)
paint.setColor4f(Color4f(1f, 1f, 0f, 1.0f))
surface.canvas.drawCircle(100f, 100f, 10f, paint)
surface.canvas.drawCircle(200f, 200f, 30f, paint)
window.requestAnimationFrame { draw() }
surface.flushAndSubmit()
}
draw()
}
}
......@@ -8,5 +8,6 @@
</head>
<body>
<h1>Skiko For Web</h1>
<canvas id="c" width="300" height="300" style="outline: 2px solid red"></canvas>
</body>
</html>
......@@ -363,7 +363,10 @@ val wasmCompile = project.tasks.register<Exec>("wasmCompile") {
*Arch.Wasm.clangFlags,
"-I$projectDir/src/commonMain/cpp",
*skiaPreprocessorFlags(skiaDir),
"-s", "USE_WEBGL2",
"-l", "GL",
"-s", "USE_WEBGL2=1",
"-s", "OFFSCREEN_FRAMEBUFFER=1",
"-DSK_SUPPORT_GPU=1",
"-o", outJs,
"--extern-post-js", skikoJsPrefix,
*srcs
......
......@@ -21,7 +21,3 @@ config.files = [
path.resolve(wasmPath, "skiko.js"),
{pattern: path.resolve(wasmPath, "skiko.wasm"), included: false, served: true, watched: false},
].concat(config.files);
const fs = require("fs");
fs.writeFileSync("KARMA.txt", JSON.stringify(config, null, 4));
......@@ -6,11 +6,11 @@
static void deleteBackendRenderTarget(GrBackendRenderTarget* rt) {
delete rt;
}
SKIKO_EXPORT KNativePointer BackendRenderTarget_nGetFinalizer() {
SKIKO_EXPORT KNativePointer org_jetbrains_skia_BackendRenderTarget__1nGetFinalizer() {
return reinterpret_cast<KNativePointer>(&deleteBackendRenderTarget);
}
SKIKO_EXPORT KNativePointer BackendRenderTarget_nMakeGL
SKIKO_EXPORT KNativePointer org_jetbrains_skia_BackendRenderTarget__1nMakeGL
(KInt width, KInt height, KInt sampleCnt, KInt stencilBits, KInt fbId, KInt fbFormat) {
GrGLFramebufferInfo glInfo = { static_cast<unsigned int>(fbId), static_cast<unsigned int>(fbFormat) };
GrBackendRenderTarget* instance = new GrBackendRenderTarget(width, height, sampleCnt, stencilBits, glInfo);
......
// This file has been auto generated.
#ifndef SKIKO_WASM
#include <iostream>
#include "GrDirectContext.h"
#include "mtl/GrMtlBackendContext.h"
#include "include/gpu/gl/GrGLInterface.h"
#include "common.h"
#ifdef SK_METAL
#include "mtl/GrMtlBackendContext.h"
#endif
SKIKO_EXPORT KNativePointer org_jetbrains_skia_DirectContext__1nMakeGL
() {
return reinterpret_cast<KNativePointer>(GrDirectContext::MakeGL().release());
}
#ifdef SK_METAL
SKIKO_EXPORT KNativePointer org_jetbrains_skia_DirectContext__1nMakeMetal
(long devicePtr, long queuePtr) {
GrMtlBackendContext backendContext = {};
......@@ -23,7 +25,7 @@ SKIKO_EXPORT KNativePointer org_jetbrains_skia_DirectContext__1nMakeMetal
sk_sp<GrDirectContext> instance = GrDirectContext::MakeMetal(backendContext);
return reinterpret_cast<KNativePointer>(instance.release());
}
#endif
SKIKO_EXPORT KNativePointer org_jetbrains_skia_DirectContext__1nMakeDirect3D
(KNativePointer adapterPtr, KNativePointer devicePtr, KNativePointer queuePtr) {
......@@ -54,4 +56,3 @@ SKIKO_EXPORT void org_jetbrains_skia_DirectContext__1nAbandon
context->abandonContext();
}
#endif
\ No newline at end of file
......@@ -70,10 +70,10 @@ class BackendRenderTarget internal constructor(ptr: NativePointer) : Managed(ptr
}
@ExternalSymbolName("BackendRenderTarget_nGetFinalizer")
@ExternalSymbolName("org_jetbrains_skia_BackendRenderTarget__1nGetFinalizer")
private external fun _nGetFinalizer(): NativePointer
@ExternalSymbolName("BackendRenderTarget_nMakeGL")
@ExternalSymbolName("org_jetbrains_skia_BackendRenderTarget__1nMakeGL")
private external fun _nMakeGL(width: Int, height: Int, sampleCnt: Int, stencilBits: Int, fbId: Int, fbFormat: Int): NativePointer
@ExternalSymbolName("BackendRenderTarget_nMakeMetal")
......
#include "GrBackendSurface.h"
#include "GrDirectContext.h"
#include <emscripten/bind.h>
using namespace emscripten;
extern "C" void* init_surface() {
return nullptr;
}
bool ping() {
return true;
#include <emscripten.h>
#include <emscripten/html5.h>
#include "webgl/webgl1.h"
// We need to have any definition invoking GL-lib on a backend size - otherwise GL is not created on frontend (regardless of -l=GL flag)
EMSCRIPTEN_WEBGL_CONTEXT_HANDLE createContext(char* id) {
EmscriptenWebGLContextAttributes attr;
emscripten_webgl_init_context_attributes(&attr);
attr.majorVersion = 2;
EMSCRIPTEN_WEBGL_CONTEXT_HANDLE ctx = emscripten_webgl_create_context(id, &attr);
emscripten_webgl_make_context_current(ctx);
return ctx;
}
\ No newline at end of file
EMSCRIPTEN_BINDINGS(Skiko) {
function("ping", &ping);
};
......@@ -21,7 +21,7 @@ actual abstract class Native actual constructor(ptr: NativePointer) {
}
actual fun reachabilityBarrier(obj: Any?) {
TODO()
// TODO: impl later
}
actual typealias NativePointer = Int
......
package org.jetbrains.skia.impl
import org.jetbrains.skia.ExternalSymbolName
actual abstract class RefCnt : Managed {
actual protected constructor(ptr: NativePointer): super(ptr, _FinalizerHolder.PTR, false)
actual protected constructor(ptr: NativePointer, allowClose: Boolean): super(ptr, _FinalizerHolder.PTR, allowClose)
......@@ -20,7 +22,7 @@ private object _FinalizerHolder {
val PTR = _nGetFinalizer()
}
@JsName("org_jetbrains_skia_impl_RefCnt__getFinalizer")
@ExternalSymbolName("org_jetbrains_skia_impl_RefCnt__getFinalizer")
private external fun _nGetFinalizer(): NativePointer
@JsName("org_jetbrains_skia_impl_RefCnt__getCount")
@ExternalSymbolName("org_jetbrains_skia_impl_RefCnt__getRefCount")
private external fun _nGetRefCount(ptr: NativePointer): Int
......@@ -2,13 +2,74 @@ package org.jetbrains.skiko.wasm
import kotlinx.dom.createElement
import kotlinx.browser.document
import org.jetbrains.skia.impl.NativePointer
import org.khronos.webgl.WebGLRenderingContext
import org.w3c.dom.HTMLCanvasElement
import org.w3c.dom.RenderingContext
import kotlin.js.Promise
external val wasmSetup: Promise<Boolean>
external fun onWasmReady(onReady: () -> Unit)
@JsExport
fun createSkikoCanvas(): HTMLCanvasElement {
return document.createElement("canvas") {} as HTMLCanvasElement
external interface GLInterface {
fun createContext(context: HTMLCanvasElement, contextAttributes: dynamic): NativePointer;
fun makeContextCurrent(contextPointer: NativePointer): Boolean;
}
external object GL: GLInterface {
override fun createContext(context: HTMLCanvasElement, contextAttributes: dynamic): Int = definedExternally
override fun makeContextCurrent(contextPointer: NativePointer): Boolean = definedExternally
}
external interface ContextAttributes {
val alpha: Int?
val depth: Int?
val stencil: Int?
val antialias: Int?
val premultipliedAlpha: Int?
val preserveDrawingBuffer: Int?
val preferLowPowerToHighPerformance: Int?
val failIfMajorPerformanceCaveat: Int?
val enableExtensionsByDefault: Int?
val explicitSwapControl: Int?
val renderViaOffscreenBackBuffer: Int?
val majorVersion: Int?
}
fun ContextAttributes.asJsObject(): dynamic {
val jsObject = js("{}")
alpha?.let { jsObject.alpha = alpha }
depth?.let { jsObject.depth = depth }
stencil?.let { jsObject.stencil = stencil }
antialias?.let { jsObject.antialias = antialias }
premultipliedAlpha?.let { jsObject.premultipliedAlpha = premultipliedAlpha }
preserveDrawingBuffer?.let { jsObject.preserveDrawingBuffer = preserveDrawingBuffer }
preferLowPowerToHighPerformance?.let { jsObject.preferLowPowerToHighPerformance = preferLowPowerToHighPerformance }
failIfMajorPerformanceCaveat?.let { jsObject.failIfMajorPerformanceCaveat = failIfMajorPerformanceCaveat }
enableExtensionsByDefault?.let { jsObject.enableExtensionsByDefault = enableExtensionsByDefault }
explicitSwapControl?.let { jsObject.explicitSwapControl = explicitSwapControl }
renderViaOffscreenBackBuffer?.let { jsObject.renderViaOffscreenBackBuffer = renderViaOffscreenBackBuffer }
majorVersion?.let { jsObject.majorVersion = majorVersion }
return jsObject
}
fun GetWebGLContext(canvas: HTMLCanvasElement, attr: ContextAttributes? = null): Boolean {
val contextAttributes = object : ContextAttributes {
override val alpha = attr?.alpha ?: 1
override val depth = attr?.depth ?: 1
override val stencil = attr?.stencil ?: 8
override val antialias = attr?.antialias ?: 0
override val premultipliedAlpha = attr?.premultipliedAlpha ?: 1
override val preserveDrawingBuffer = attr?.preserveDrawingBuffer ?: 0
override val preferLowPowerToHighPerformance = attr?.preferLowPowerToHighPerformance ?: 0
override val failIfMajorPerformanceCaveat = attr?.failIfMajorPerformanceCaveat ?: 0
override val enableExtensionsByDefault = attr?.enableExtensionsByDefault ?: 1
override val explicitSwapControl = attr?.explicitSwapControl ?: 0
override val renderViaOffscreenBackBuffer = attr?.renderViaOffscreenBackBuffer ?: 0
override val majorVersion = attr?.majorVersion ?: 1
}
val contextPointer = GL.createContext(canvas, contextAttributes.asJsObject())
return GL.makeContextCurrent(contextPointer)
}
\ No newline at end of file
......@@ -8,10 +8,7 @@ import kotlin.test.Test
import kotlin.test.assertEquals
import kotlin.test.assertTrue
external interface ModuleInterface {
fun ping(): Boolean
}
external interface ModuleInterface
external val Module: ModuleInterface
suspend fun <T> Promise<T>.await(): T = suspendCoroutine { cont ->
......@@ -23,9 +20,4 @@ private fun wasmTest(block: () -> Unit) = GlobalScope.promise {
block.invoke()
}
class WasmTests {
@Test
fun pingTest() = wasmTest {
assertTrue(Module.ping())
}
}
\ No newline at end of file
class WasmTests
\ No newline at end of file
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment