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 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.jetbrains.skiko.wasm.onWasmReady
import org.w3c.dom.HTMLCanvasElement
fun main(args: Array<String>) {
fun main() {
onWasmReady { 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()
} }
} }
\ No newline at end of file
...@@ -8,5 +8,6 @@ ...@@ -8,5 +8,6 @@
</head> </head>
<body> <body>
<h1>Skiko For Web</h1> <h1>Skiko For Web</h1>
<canvas id="c" width="300" height="300" style="outline: 2px solid red"></canvas>
</body> </body>
</html> </html>
...@@ -363,7 +363,10 @@ val wasmCompile = project.tasks.register<Exec>("wasmCompile") { ...@@ -363,7 +363,10 @@ val wasmCompile = project.tasks.register<Exec>("wasmCompile") {
*Arch.Wasm.clangFlags, *Arch.Wasm.clangFlags,
"-I$projectDir/src/commonMain/cpp", "-I$projectDir/src/commonMain/cpp",
*skiaPreprocessorFlags(skiaDir), *skiaPreprocessorFlags(skiaDir),
"-s", "USE_WEBGL2", "-l", "GL",
"-s", "USE_WEBGL2=1",
"-s", "OFFSCREEN_FRAMEBUFFER=1",
"-DSK_SUPPORT_GPU=1",
"-o", outJs, "-o", outJs,
"--extern-post-js", skikoJsPrefix, "--extern-post-js", skikoJsPrefix,
*srcs *srcs
......
...@@ -21,7 +21,3 @@ config.files = [ ...@@ -21,7 +21,3 @@ config.files = [
path.resolve(wasmPath, "skiko.js"), path.resolve(wasmPath, "skiko.js"),
{pattern: path.resolve(wasmPath, "skiko.wasm"), included: false, served: true, watched: false}, {pattern: path.resolve(wasmPath, "skiko.wasm"), included: false, served: true, watched: false},
].concat(config.files); ].concat(config.files);
const fs = require("fs");
fs.writeFileSync("KARMA.txt", JSON.stringify(config, null, 4));
...@@ -6,11 +6,11 @@ ...@@ -6,11 +6,11 @@
static void deleteBackendRenderTarget(GrBackendRenderTarget* rt) { static void deleteBackendRenderTarget(GrBackendRenderTarget* rt) {
delete rt; delete rt;
} }
SKIKO_EXPORT KNativePointer BackendRenderTarget_nGetFinalizer() { SKIKO_EXPORT KNativePointer org_jetbrains_skia_BackendRenderTarget__1nGetFinalizer() {
return reinterpret_cast<KNativePointer>(&deleteBackendRenderTarget); 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) { (KInt width, KInt height, KInt sampleCnt, KInt stencilBits, KInt fbId, KInt fbFormat) {
GrGLFramebufferInfo glInfo = { static_cast<unsigned int>(fbId), static_cast<unsigned int>(fbFormat) }; GrGLFramebufferInfo glInfo = { static_cast<unsigned int>(fbId), static_cast<unsigned int>(fbFormat) };
GrBackendRenderTarget* instance = new GrBackendRenderTarget(width, height, sampleCnt, stencilBits, glInfo); GrBackendRenderTarget* instance = new GrBackendRenderTarget(width, height, sampleCnt, stencilBits, glInfo);
......
// This file has been auto generated. // This file has been auto generated.
#ifndef SKIKO_WASM
#include <iostream>
#include "GrDirectContext.h" #include "GrDirectContext.h"
#include "mtl/GrMtlBackendContext.h" #include "include/gpu/gl/GrGLInterface.h"
#include "common.h" #include "common.h"
#ifdef SK_METAL
#include "mtl/GrMtlBackendContext.h"
#endif
SKIKO_EXPORT KNativePointer org_jetbrains_skia_DirectContext__1nMakeGL SKIKO_EXPORT KNativePointer org_jetbrains_skia_DirectContext__1nMakeGL
() { () {
return reinterpret_cast<KNativePointer>(GrDirectContext::MakeGL().release()); return reinterpret_cast<KNativePointer>(GrDirectContext::MakeGL().release());
} }
#ifdef SK_METAL
SKIKO_EXPORT KNativePointer org_jetbrains_skia_DirectContext__1nMakeMetal SKIKO_EXPORT KNativePointer org_jetbrains_skia_DirectContext__1nMakeMetal
(long devicePtr, long queuePtr) { (long devicePtr, long queuePtr) {
GrMtlBackendContext backendContext = {}; GrMtlBackendContext backendContext = {};
...@@ -23,7 +25,7 @@ SKIKO_EXPORT KNativePointer org_jetbrains_skia_DirectContext__1nMakeMetal ...@@ -23,7 +25,7 @@ SKIKO_EXPORT KNativePointer org_jetbrains_skia_DirectContext__1nMakeMetal
sk_sp<GrDirectContext> instance = GrDirectContext::MakeMetal(backendContext); sk_sp<GrDirectContext> instance = GrDirectContext::MakeMetal(backendContext);
return reinterpret_cast<KNativePointer>(instance.release()); return reinterpret_cast<KNativePointer>(instance.release());
} }
#endif
SKIKO_EXPORT KNativePointer org_jetbrains_skia_DirectContext__1nMakeDirect3D SKIKO_EXPORT KNativePointer org_jetbrains_skia_DirectContext__1nMakeDirect3D
(KNativePointer adapterPtr, KNativePointer devicePtr, KNativePointer queuePtr) { (KNativePointer adapterPtr, KNativePointer devicePtr, KNativePointer queuePtr) {
...@@ -54,4 +56,3 @@ SKIKO_EXPORT void org_jetbrains_skia_DirectContext__1nAbandon ...@@ -54,4 +56,3 @@ SKIKO_EXPORT void org_jetbrains_skia_DirectContext__1nAbandon
context->abandonContext(); context->abandonContext();
} }
#endif
\ No newline at end of file
...@@ -85,8 +85,8 @@ SKIKO_EXPORT KInteropPointer org_jetbrains_skia_Paint__1nGetColor4f ...@@ -85,8 +85,8 @@ SKIKO_EXPORT KInteropPointer org_jetbrains_skia_Paint__1nGetColor4f
(KNativePointer ptr) { (KNativePointer ptr) {
TODO("implement org_jetbrains_skia_Paint__1nGetColor4f"); TODO("implement org_jetbrains_skia_Paint__1nGetColor4f");
} }
#if 0 #if 0
SKIKO_EXPORT KInteropPointer org_jetbrains_skia_Paint__1nGetColor4f SKIKO_EXPORT KInteropPointer org_jetbrains_skia_Paint__1nGetColor4f
(KNativePointer ptr) { (KNativePointer ptr) {
SkPaint* instance = reinterpret_cast<SkPaint*>((ptr)); SkPaint* instance = reinterpret_cast<SkPaint*>((ptr));
......
...@@ -70,10 +70,10 @@ class BackendRenderTarget internal constructor(ptr: NativePointer) : Managed(ptr ...@@ -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 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 private external fun _nMakeGL(width: Int, height: Int, sampleCnt: Int, stencilBits: Int, fbId: Int, fbFormat: Int): NativePointer
@ExternalSymbolName("BackendRenderTarget_nMakeMetal") @ExternalSymbolName("BackendRenderTarget_nMakeMetal")
......
#include "GrBackendSurface.h" #include <emscripten.h>
#include "GrDirectContext.h" #include <emscripten/html5.h>
#include <emscripten/bind.h> #include "webgl/webgl1.h"
using namespace emscripten; // 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) {
extern "C" void* init_surface() { EmscriptenWebGLContextAttributes attr;
return nullptr; emscripten_webgl_init_context_attributes(&attr);
} attr.majorVersion = 2;
EMSCRIPTEN_WEBGL_CONTEXT_HANDLE ctx = emscripten_webgl_create_context(id, &attr);
bool ping() { emscripten_webgl_make_context_current(ctx);
return true; 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) { ...@@ -21,7 +21,7 @@ actual abstract class Native actual constructor(ptr: NativePointer) {
} }
actual fun reachabilityBarrier(obj: Any?) { actual fun reachabilityBarrier(obj: Any?) {
TODO() // TODO: impl later
} }
actual typealias NativePointer = Int actual typealias NativePointer = Int
......
package org.jetbrains.skia.impl package org.jetbrains.skia.impl
import org.jetbrains.skia.ExternalSymbolName
actual abstract class RefCnt : Managed { actual abstract class RefCnt : Managed {
actual protected constructor(ptr: NativePointer): super(ptr, _FinalizerHolder.PTR, false) actual protected constructor(ptr: NativePointer): super(ptr, _FinalizerHolder.PTR, false)
actual protected constructor(ptr: NativePointer, allowClose: Boolean): super(ptr, _FinalizerHolder.PTR, allowClose) actual protected constructor(ptr: NativePointer, allowClose: Boolean): super(ptr, _FinalizerHolder.PTR, allowClose)
...@@ -20,7 +22,7 @@ private object _FinalizerHolder { ...@@ -20,7 +22,7 @@ private object _FinalizerHolder {
val PTR = _nGetFinalizer() val PTR = _nGetFinalizer()
} }
@JsName("org_jetbrains_skia_impl_RefCnt__getFinalizer") @ExternalSymbolName("org_jetbrains_skia_impl_RefCnt__getFinalizer")
private external fun _nGetFinalizer(): NativePointer 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 private external fun _nGetRefCount(ptr: NativePointer): Int
...@@ -2,13 +2,74 @@ package org.jetbrains.skiko.wasm ...@@ -2,13 +2,74 @@ package org.jetbrains.skiko.wasm
import kotlinx.dom.createElement import kotlinx.dom.createElement
import kotlinx.browser.document import kotlinx.browser.document
import org.jetbrains.skia.impl.NativePointer
import org.khronos.webgl.WebGLRenderingContext
import org.w3c.dom.HTMLCanvasElement import org.w3c.dom.HTMLCanvasElement
import org.w3c.dom.RenderingContext
import kotlin.js.Promise import kotlin.js.Promise
external val wasmSetup: Promise<Boolean> external val wasmSetup: Promise<Boolean>
external fun onWasmReady(onReady: () -> Unit) external fun onWasmReady(onReady: () -> Unit)
@JsExport external interface GLInterface {
fun createSkikoCanvas(): HTMLCanvasElement { fun createContext(context: HTMLCanvasElement, contextAttributes: dynamic): NativePointer;
return document.createElement("canvas") {} as HTMLCanvasElement 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
...@@ -4,4 +4,4 @@ var wasmSetup = new Promise(function(resolve, reject) { ...@@ -4,4 +4,4 @@ var wasmSetup = new Promise(function(resolve, reject) {
}; };
}); });
function onWasmReady(onReady) { wasmSetup.then(onReady); } function onWasmReady(onReady) { wasmSetup.then(onReady); }
\ No newline at end of file
...@@ -8,10 +8,7 @@ import kotlin.test.Test ...@@ -8,10 +8,7 @@ import kotlin.test.Test
import kotlin.test.assertEquals import kotlin.test.assertEquals
import kotlin.test.assertTrue import kotlin.test.assertTrue
external interface ModuleInterface { external interface ModuleInterface
fun ping(): Boolean
}
external val Module: ModuleInterface external val Module: ModuleInterface
suspend fun <T> Promise<T>.await(): T = suspendCoroutine { cont -> suspend fun <T> Promise<T>.await(): T = suspendCoroutine { cont ->
...@@ -23,9 +20,4 @@ private fun wasmTest(block: () -> Unit) = GlobalScope.promise { ...@@ -23,9 +20,4 @@ private fun wasmTest(block: () -> Unit) = GlobalScope.promise {
block.invoke() block.invoke()
} }
class WasmTests { class WasmTests
@Test \ No newline at end of file
fun pingTest() = wasmTest {
assertTrue(Module.ping())
}
}
\ 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