Unverified Commit 79e5d248 authored by Aleksandr Veselov's avatar Aleksandr Veselov Committed by GitHub

Implement Picture for native (#361)

parent e76e4a31
...@@ -21,15 +21,6 @@ expect class Matcher { ...@@ -21,15 +21,6 @@ expect class Matcher {
expect fun compilePattern(regex: String): Pattern expect fun compilePattern(regex: String): Pattern
interface BooleanSupplier {
/**
* Gets a result.
*
* @return a result
*/
val asBoolean: Boolean
}
@OptIn(ExperimentalMultiplatform::class) @OptIn(ExperimentalMultiplatform::class)
@OptionalExpectation @OptionalExpectation
expect annotation class ExternalSymbolName(val name: String) expect annotation class ExternalSymbolName(val name: String)
...@@ -73,10 +73,12 @@ class Picture internal constructor(ptr: NativePointer) : RefCnt(ptr) { ...@@ -73,10 +73,12 @@ class Picture internal constructor(ptr: NativePointer) : RefCnt(ptr) {
* *
* @see [https://fiddle.skia.org/c/@Picture_playback](https://fiddle.skia.org/c/@Picture_playback) * @see [https://fiddle.skia.org/c/@Picture_playback](https://fiddle.skia.org/c/@Picture_playback)
*/ */
fun playback(canvas: Canvas?, abort: BooleanSupplier? = null): Picture { fun playback(canvas: Canvas?, abort: (() -> Boolean)? = null): Picture {
return try { return try {
Stats.onNativeCall() Stats.onNativeCall()
_nPlayback(_ptr, getPtr(canvas), abort) interopScope {
_nPlayback(_ptr, getPtr(canvas), toInterop(abort))
}
this this
} finally { } finally {
reachabilityBarrier(canvas) reachabilityBarrier(canvas)
...@@ -99,7 +101,7 @@ class Picture internal constructor(ptr: NativePointer) : RefCnt(ptr) { ...@@ -99,7 +101,7 @@ class Picture internal constructor(ptr: NativePointer) : RefCnt(ptr) {
val cullRect: Rect val cullRect: Rect
get() = try { get() = try {
Stats.onNativeCall() Stats.onNativeCall()
_nGetCullRect(_ptr) Rect.fromInteropPointer { _nGetCullRect(_ptr, it) }
} finally { } finally {
reachabilityBarrier(this) reachabilityBarrier(this)
} }
...@@ -209,7 +211,18 @@ class Picture internal constructor(ptr: NativePointer) : RefCnt(ptr) { ...@@ -209,7 +211,18 @@ class Picture internal constructor(ptr: NativePointer) : RefCnt(ptr) {
val arr = localMatrix?.mat val arr = localMatrix?.mat
Shader( Shader(
interopScope { interopScope {
_nMakeShader(_ptr, tmx.ordinal, tmy.ordinal, mode.ordinal, toInterop(arr), tileRect) _nMakeShader(
_ptr,
tmx.ordinal,
tmy.ordinal,
mode.ordinal,
toInterop(arr),
tileRect != null,
tileRect?.left ?: 0f,
tileRect?.top ?: 0f,
tileRect?.right ?: 0f,
tileRect?.bottom ?: 0f
)
} }
) )
} finally { } finally {
...@@ -218,15 +231,11 @@ class Picture internal constructor(ptr: NativePointer) : RefCnt(ptr) { ...@@ -218,15 +231,11 @@ class Picture internal constructor(ptr: NativePointer) : RefCnt(ptr) {
} }
} }
@ExternalSymbolName("org_jetbrains_skia_Picture__1nMakeFromData") @ExternalSymbolName("org_jetbrains_skia_Picture__1nMakeFromData")
private external fun Picture_nMakeFromData(dataPtr: NativePointer /*, SkDeserialProcs */): NativePointer private external fun Picture_nMakeFromData(dataPtr: NativePointer /*, SkDeserialProcs */): NativePointer
@ExternalSymbolName("org_jetbrains_skia_Picture__1nPlayback")
private external fun _nPlayback(ptr: NativePointer, canvasPtr: NativePointer, abort: BooleanSupplier?)
@ExternalSymbolName("org_jetbrains_skia_Picture__1nGetCullRect") @ExternalSymbolName("org_jetbrains_skia_Picture__1nGetCullRect")
private external fun _nGetCullRect(ptr: NativePointer): Rect private external fun _nGetCullRect(ptr: NativePointer, ltrb: InteropPointer)
@ExternalSymbolName("org_jetbrains_skia_Picture__1nGetUniqueId") @ExternalSymbolName("org_jetbrains_skia_Picture__1nGetUniqueId")
private external fun _nGetUniqueId(ptr: NativePointer): Int private external fun _nGetUniqueId(ptr: NativePointer): Int
...@@ -250,5 +259,12 @@ private external fun _nMakeShader( ...@@ -250,5 +259,12 @@ private external fun _nMakeShader(
tmy: Int, tmy: Int,
filterMode: Int, filterMode: Int,
localMatrix: InteropPointer, localMatrix: InteropPointer,
tileRect: Rect? hasTile: Boolean,
tileL: Float,
tileT: Float,
tileR: Float,
tileB: Float,
): NativePointer ): NativePointer
@ExternalSymbolName("org_jetbrains_skia_Picture__1nPlayback")
private external fun _nPlayback(ptr: NativePointer, canvasPtr: NativePointer, data: InteropPointer)
\ No newline at end of file
...@@ -42,6 +42,7 @@ expect class InteropScope() { ...@@ -42,6 +42,7 @@ expect class InteropScope() {
fun InteropPointer.fromInteropNativePointerArray(): NativePointerArray fun InteropPointer.fromInteropNativePointerArray(): NativePointerArray
inline fun <reified T> InteropPointer.fromInterop(decoder: ArrayInteropDecoder<T>): Array<T> inline fun <reified T> InteropPointer.fromInterop(decoder: ArrayInteropDecoder<T>): Array<T>
fun toInteropForArraysOfPointers(interopPointers: Array<InteropPointer>): InteropPointer fun toInteropForArraysOfPointers(interopPointers: Array<InteropPointer>): InteropPointer
fun toInterop(callback: (() -> Boolean)?): InteropPointer
fun release() fun release()
} }
......
package org.jetbrains.skia
import org.jetbrains.skia.tests.assertCloseEnough
import kotlin.test.Test
import kotlin.test.assertEquals
class PictureTest {
@Test
fun canMakeShader() {
val pic = Picture.makePlaceholder(Rect(0.0f, 0.0f, 32.0f, 32.0f))
val localMatrix = Matrix33(1.0f, 0.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f, 1.0f)
val tile = Rect(0.0f, 0.0f, 16.0f, 16.0f)
pic.makeShader(FilterTileMode.MIRROR, FilterTileMode.MIRROR, FilterMode.LINEAR)
pic.makeShader(FilterTileMode.MIRROR, FilterTileMode.MIRROR, FilterMode.LINEAR, localMatrix)
pic.makeShader(FilterTileMode.MIRROR, FilterTileMode.MIRROR, FilterMode.LINEAR, localMatrix, tile)
}
@Test
fun canGetCullRect() {
val size = Rect(0.0f, 0.0f, 32.0f, 32.0f)
val pic = Picture.makePlaceholder(size)
val cullRect = pic.cullRect
assertCloseEnough(size, cullRect)
}
@Test
fun canReplay() {
val size = Rect(0.0f, 0.0f, 32.0f, 32.0f)
val recorder = PictureRecorder()
val canvas = recorder.beginRecording(size)
canvas.drawRect(Rect(10.0f, 10.0f, 20.0f, 20.0f), Paint().apply { color = Color.RED })
val pic = recorder.finishRecordingAsPicture()
val surface = Surface.makeRasterN32Premul(32, 32)
pic.playback(surface.canvas)
assertEquals(Color.RED, Bitmap.makeFromImage(surface.makeImageSnapshot()).getColor(15, 15))
}
@Test
fun canReplayWithCallback() {
val size = Rect(0.0f, 0.0f, 32.0f, 32.0f)
val recorder = PictureRecorder()
val canvas = recorder.beginRecording(size)
canvas.drawRect(Rect(10.0f, 10.0f, 20.0f, 20.0f), Paint().apply { color = Color.RED })
canvas.drawRect(Rect(12.0f, 12.0f, 18.0f, 18.0f), Paint().apply { color = Color.WHITE })
val pic = recorder.finishRecordingAsPicture()
val surface = Surface.makeRasterN32Premul(32, 32)
var drawCount = 0
pic.playback(surface.canvas) { drawCount += 1; drawCount > 1 } // Draw only once
assertEquals(2, drawCount)
assertEquals(Color.RED, Bitmap.makeFromImage(surface.makeImageSnapshot()).getColor(15, 15))
}
}
...@@ -51,6 +51,7 @@ actual inline fun <T> interopScope(block: InteropScope.() -> T): T { ...@@ -51,6 +51,7 @@ actual inline fun <T> interopScope(block: InteropScope.() -> T): T {
actual class InteropScope actual constructor() { actual class InteropScope actual constructor() {
private val elements = mutableListOf<NativePointer>() private val elements = mutableListOf<NativePointer>()
private var callbacksInitialized = false
actual fun toInterop(string: String?): InteropPointer { actual fun toInterop(string: String?): InteropPointer {
return if (string != null) { return if (string != null) {
...@@ -194,13 +195,45 @@ actual class InteropScope actual constructor() { ...@@ -194,13 +195,45 @@ actual class InteropScope actual constructor() {
return toInterop(interopPointers.toIntArray()) return toInterop(interopPointers.toIntArray())
} }
actual fun toInterop(callback: (() -> Boolean)?): InteropPointer {
if (callback == null) { return 0 }
initCallbacks()
val data = CallbackData<Boolean>(null)
return _registerCallback({ data.value = callback() }, data)
}
actual fun release() { actual fun release() {
elements.forEach { elements.forEach {
_free(it) _free(it)
} }
releaseCallbacks()
}
private inline fun initCallbacks() {
if (!callbacksInitialized) {
_createLocalCallbackScope()
callbacksInitialized = true
}
}
private inline fun releaseCallbacks() {
if (callbacksInitialized) {
_releaseLocalCallbackScope()
callbacksInitialized = false
}
} }
} }
// Callbacks
private class CallbackData<T>(@JsName("value") var value: T?)
// See `setup.js`
private external fun _registerCallback(cb: () -> Unit, data: Any?): Int
private external fun _createLocalCallbackScope()
private external fun _releaseLocalCallbackScope()
// Those functions are defined by Emscripten. // Those functions are defined by Emscripten.
private external fun _malloc(size: Int): NativePointer private external fun _malloc(size: Int): NativePointer
......
var {_callCallback, _registerCallback, _releaseCallback, _createLocalCallbackScope, _releaseLocalCallbackScope} = (() => {
const CB_NULL = {
callback: () => { throw new RangeError("attempted to call a callback at NULL") },
data: null
};
const CB_UNDEFINED = {
callback: () => { throw new RangeError("attempted to call an uninitialized callback") },
data: null
};
class Scope {
constructor() {
this.callbackMap = [CB_NULL];
}
addCallback(callback, data) {
this.callbackMap.push({callback, data});
return this.callbackMap.length - 1;
}
getCallback(id) {
return this.callbackMap[id] || CB_UNDEFINED
}
deleteCallback(id) {
this.callbackMap[id] = CB_UNDEFINED
}
release() {
this.callbackMap = null;
}
}
const GLOBAL_SCOPE = new Scope();
let scope = GLOBAL_SCOPE;
return {
_callCallback(callbackId) {
let callback = scope.getCallback(callbackId);
try {
callback.callback();
return callback.data;
} catch (e) {
console.error(e)
}
},
_registerCallback(callback, data = null) {
return scope.addCallback(callback, data);
},
_releaseCallback(callbackId) {
scope.deleteCallback(callbackId);
},
_createLocalCallbackScope() {
if (scope !== GLOBAL_SCOPE) {
throw new Error("attempted to overwrite local scope")
}
scope = new Scope()
},
_releaseLocalCallbackScope() {
if (scope === GLOBAL_SCOPE) {
throw new Error("attempted to release global scope")
}
scope.release()
scope = GLOBAL_SCOPE
},
}
})();
var wasmSetup = new Promise(function(resolve, reject) { var wasmSetup = new Promise(function(resolve, reject) {
Module['onRuntimeInitialized'] = _ => { Module['onRuntimeInitialized'] = _ => {
resolve(Module); resolve(Module);
......
...@@ -12,21 +12,18 @@ extern "C" JNIEXPORT jlong JNICALL Java_org_jetbrains_skia_PictureKt_Picture_1nM ...@@ -12,21 +12,18 @@ extern "C" JNIEXPORT jlong JNICALL Java_org_jetbrains_skia_PictureKt_Picture_1nM
return reinterpret_cast<jlong>(instance); return reinterpret_cast<jlong>(instance);
} }
class BooleanSupplierAbort: public SkPicture::AbortCallback { class JAbortCallback: public SkPicture::AbortCallback {
public: public:
BooleanSupplierAbort(JNIEnv* env, jobject supplier) { JAbortCallback(JNIEnv* env, jobject supplier) : callback(env, supplier) {}
this->env = env;
this->supplier = supplier;
}
bool abort() override { bool abort() override {
bool res = env->CallBooleanMethod(supplier, java::util::function::BooleanSupplier::apply); bool res = static_cast<bool>(callback());
if (java::lang::Throwable::exceptionThrown(env)) if (callback.isExceptionThrown())
return false; return false;
return res; return res;
} }
private: private:
JNIEnv* env; JBooleanCallback callback;
jobject supplier;
}; };
extern "C" JNIEXPORT void JNICALL Java_org_jetbrains_skia_PictureKt__1nPlayback extern "C" JNIEXPORT void JNICALL Java_org_jetbrains_skia_PictureKt__1nPlayback
...@@ -36,15 +33,16 @@ extern "C" JNIEXPORT void JNICALL Java_org_jetbrains_skia_PictureKt__1nPlayback ...@@ -36,15 +33,16 @@ extern "C" JNIEXPORT void JNICALL Java_org_jetbrains_skia_PictureKt__1nPlayback
if (abort == nullptr) { if (abort == nullptr) {
instance->playback(canvas, nullptr); instance->playback(canvas, nullptr);
} else { } else {
BooleanSupplierAbort callback(env, abort); JAbortCallback callback(env, abort);
instance->playback(canvas, &callback); instance->playback(canvas, &callback);
} }
} }
extern "C" JNIEXPORT jobject JNICALL Java_org_jetbrains_skia_PictureKt__1nGetCullRect extern "C" JNIEXPORT void JNICALL Java_org_jetbrains_skia_PictureKt__1nGetCullRect
(JNIEnv* env, jclass jclass, jlong ptr) { (JNIEnv* env, jclass jclass, jlong ptr, jfloatArray ltrbArray) {
SkPicture* instance = reinterpret_cast<SkPicture*>(static_cast<uintptr_t>(ptr)); SkPicture* instance = reinterpret_cast<SkPicture*>(static_cast<uintptr_t>(ptr));
return skija::Rect::fromSkRect(env, instance->cullRect()); SkRect cullRect = instance->cullRect();
env->SetFloatArrayRegion(ltrbArray, 0, 4, reinterpret_cast<const jfloat*>(cullRect.asScalars()));
} }
extern "C" JNIEXPORT jint JNICALL Java_org_jetbrains_skia_PictureKt__1nGetUniqueId extern "C" JNIEXPORT jint JNICALL Java_org_jetbrains_skia_PictureKt__1nGetUniqueId
...@@ -80,13 +78,18 @@ extern "C" JNIEXPORT jlong JNICALL Java_org_jetbrains_skia_PictureKt__1nGetAppro ...@@ -80,13 +78,18 @@ extern "C" JNIEXPORT jlong JNICALL Java_org_jetbrains_skia_PictureKt__1nGetAppro
} }
extern "C" JNIEXPORT jlong JNICALL Java_org_jetbrains_skia_PictureKt__1nMakeShader extern "C" JNIEXPORT jlong JNICALL Java_org_jetbrains_skia_PictureKt__1nMakeShader
(JNIEnv* env, jclass jclass, jlong ptr, jint tmxValue, jint tmyValue, jint filterModeValue, jfloatArray localMatrixArr, jobject tileRectObj) { (JNIEnv* env, jclass jclass, jlong ptr, jint tmxValue, jint tmyValue, jint filterModeValue, jfloatArray localMatrixArr, jboolean hasTile, jfloat tileLeft, jfloat tileTop, jfloat tileRight, jfloat tileBottom) {
SkPicture* instance = reinterpret_cast<SkPicture*>(static_cast<uintptr_t>(ptr)); SkPicture* instance = reinterpret_cast<SkPicture*>(static_cast<uintptr_t>(ptr));
SkTileMode tmx = static_cast<SkTileMode>(tmxValue); SkTileMode tmx = static_cast<SkTileMode>(tmxValue);
SkTileMode tmy = static_cast<SkTileMode>(tmyValue); SkTileMode tmy = static_cast<SkTileMode>(tmyValue);
SkFilterMode filterMode = static_cast<SkFilterMode>(filterModeValue); SkFilterMode filterMode = static_cast<SkFilterMode>(filterModeValue);
std::unique_ptr<SkMatrix> localMatrix = skMatrix(env, localMatrixArr); std::unique_ptr<SkMatrix> localMatrix = skMatrix(env, localMatrixArr);
std::unique_ptr<SkRect> tileRect = skija::Rect::toSkRect(env, tileRectObj); SkShader* shader;
SkShader* shader = instance->makeShader(tmx, tmy, filterMode, localMatrix.get(), tileRect.get()).release(); if (hasTile) {
SkRect tileRect = SkRect::MakeLTRB(tileLeft, tileRight, tileBottom, tileTop);
shader = instance->makeShader(tmx, tmy, filterMode, localMatrix.get(), &tileRect).release();
} else {
shader = instance->makeShader(tmx, tmy, filterMode, localMatrix.get(), nullptr).release();
}
return reinterpret_cast<jlong>(shader); return reinterpret_cast<jlong>(shader);
} }
\ No newline at end of file
...@@ -17,6 +17,7 @@ extern "C" JNIEXPORT void JNICALL Java_org_jetbrains_skia_impl_Library__1nAfterL ...@@ -17,6 +17,7 @@ extern "C" JNIEXPORT void JNICALL Java_org_jetbrains_skia_impl_Library__1nAfterL
(JNIEnv* env, jclass jclass) { (JNIEnv* env, jclass jclass) {
env->EnsureLocalCapacity(64); env->EnsureLocalCapacity(64);
java::onLoad(env); java::onLoad(env);
kotlin::onLoad(env);
skija::onLoad(env); skija::onLoad(env);
skija::shaper::onLoad(env); skija::shaper::onLoad(env);
skija::skottie::onLoad(env); skija::skottie::onLoad(env);
...@@ -34,5 +35,6 @@ JNIEXPORT void JNICALL JNI_OnUnload(JavaVM* vm, void* reserved) { ...@@ -34,5 +35,6 @@ JNIEXPORT void JNICALL JNI_OnUnload(JavaVM* vm, void* reserved) {
skija::skottie::onUnload(env); skija::skottie::onUnload(env);
skija::shaper::onUnload(env); skija::shaper::onUnload(env);
skija::onUnload(env); skija::onUnload(env);
kotlin::onUnload(env);
java::onUnload(env); java::onUnload(env);
} }
...@@ -25,6 +25,21 @@ namespace java { ...@@ -25,6 +25,21 @@ namespace java {
} }
namespace lang { namespace lang {
namespace Boolean {
jclass cls;
jmethodID booleanValue;
void onLoad(JNIEnv* env) {
jclass local = env->FindClass("java/lang/Boolean");
cls = static_cast<jclass>(env->NewGlobalRef(local));
booleanValue = env->GetMethodID(cls, "booleanValue", "()Z");
}
void onUnload(JNIEnv* env) {
env->DeleteGlobalRef(cls);
}
}
namespace Float { namespace Float {
jclass cls; jclass cls;
jmethodID ctor; jmethodID ctor;
...@@ -103,41 +118,24 @@ namespace java { ...@@ -103,41 +118,24 @@ namespace java {
env->DeleteGlobalRef(cls); env->DeleteGlobalRef(cls);
} }
} }
namespace function {
namespace BooleanSupplier {
jclass cls;
jmethodID apply;
void onLoad(JNIEnv* env) {
jclass local = env->FindClass("java/util/function/BooleanSupplier");
cls = static_cast<jclass>(env->NewGlobalRef(local));
apply = env->GetMethodID(cls, "getAsBoolean", "()Z");
}
void onUnload(JNIEnv* env) {
env->DeleteGlobalRef(cls);
}
}
}
} }
void onLoad(JNIEnv* env) { void onLoad(JNIEnv* env) {
io::OutputStream::onLoad(env); io::OutputStream::onLoad(env);
lang::Boolean::onLoad(env);
lang::Float::onLoad(env); lang::Float::onLoad(env);
lang::RuntimeException::onLoad(env); lang::RuntimeException::onLoad(env);
lang::String::onLoad(env); lang::String::onLoad(env);
lang::Throwable::onLoad(env); lang::Throwable::onLoad(env);
util::Iterator::onLoad(env); util::Iterator::onLoad(env);
util::function::BooleanSupplier::onLoad(env);
} }
void onUnload(JNIEnv* env) { void onUnload(JNIEnv* env) {
util::function::BooleanSupplier::onUnload(env);
util::Iterator::onUnload(env); util::Iterator::onUnload(env);
lang::String::onUnload(env); lang::String::onUnload(env);
lang::RuntimeException::onUnload(env); lang::RuntimeException::onUnload(env);
lang::Float::onUnload(env); lang::Float::onUnload(env);
lang::Boolean::onUnload(env);
} }
} }
...@@ -1202,3 +1200,36 @@ namespace skija { ...@@ -1202,3 +1200,36 @@ namespace skija {
} }
} }
} }
namespace kotlin {
namespace jvm {
namespace functions {
namespace Function0 {
jclass cls;
jmethodID invoke;
void onLoad(JNIEnv* env) {
jclass local = env->FindClass("kotlin/jvm/functions/Function0");
cls = static_cast<jclass>(env->NewGlobalRef(local));
invoke = env->GetMethodID(cls, "invoke", "()Ljava/lang/Object;");
}
void onUnload(JNIEnv* env) {
env->DeleteGlobalRef(cls);
}
}
}
}
void onLoad(JNIEnv* env) {
jvm::functions::Function0::onLoad(env);
}
void onUnload(JNIEnv* env) {
jvm::functions::Function0::onUnload(env);
}
}
template<>
jboolean jObjectConvert(JNIEnv* env, jobject obj) {
return env->CallBooleanMethod(obj, java::lang::Boolean::booleanValue);
}
...@@ -30,6 +30,13 @@ namespace java { ...@@ -30,6 +30,13 @@ namespace java {
} }
namespace lang { namespace lang {
namespace Boolean {
extern jclass cls;
extern jmethodID booleanValue;
void onLoad(JNIEnv* env);
void onUnload(JNIEnv* env);
}
namespace Float { namespace Float {
extern jclass cls; extern jclass cls;
extern jmethodID ctor; extern jmethodID ctor;
...@@ -64,15 +71,6 @@ namespace java { ...@@ -64,15 +71,6 @@ namespace java {
void onLoad(JNIEnv* env); void onLoad(JNIEnv* env);
void onUnload(JNIEnv* env); void onUnload(JNIEnv* env);
} }
namespace function {
namespace BooleanSupplier {
extern jclass cls;
extern jmethodID apply;
void onLoad(JNIEnv* env);
void onUnload(JNIEnv* env);
}
}
} }
void onLoad(JNIEnv* env); void onLoad(JNIEnv* env);
...@@ -335,6 +333,22 @@ namespace skija { ...@@ -335,6 +333,22 @@ namespace skija {
void onUnload(JNIEnv* env); void onUnload(JNIEnv* env);
} }
namespace kotlin {
namespace jvm {
namespace functions {
namespace Function0 {
extern jclass cls;
extern jmethodID invoke;
void onLoad(JNIEnv* env);
void onUnload(JNIEnv* env);
}
}
}
void onLoad(JNIEnv* env);
void onUnload(JNIEnv* env);
}
std::unique_ptr<SkMatrix> skMatrix(JNIEnv* env, jfloatArray arr); std::unique_ptr<SkMatrix> skMatrix(JNIEnv* env, jfloatArray arr);
std::unique_ptr<SkM44> skM44(JNIEnv* env, jfloatArray arr); std::unique_ptr<SkM44> skM44(JNIEnv* env, jfloatArray arr);
...@@ -394,3 +408,55 @@ static inline jfloat fromBits(jint i) { ...@@ -394,3 +408,55 @@ static inline jfloat fromBits(jint i) {
u.i = i; u.i = i;
return u.f; return u.f;
} }
template<typename T>
T jObjectConvert(JNIEnv* env, jobject obj);
// Callback support
template<typename T>
class JCallback {
public:
JCallback (JNIEnv* env, jobject callback) : env(env), callback(env->NewGlobalRef(callback)) {
env->GetJavaVM(&javaVM);
}
~JCallback() {
if (callback != nullptr) {
JNIEnv* localEnv;
if (javaVM->GetEnv(AS_JNI_ENV_PTR(&localEnv), SKIKO_JNI_VERSION) == JNI_OK) {
localEnv->DeleteGlobalRef(callback);
}
}
}
JCallback(const JCallback&) = delete;
JCallback& operator=(const JCallback&) = delete;
JCallback(JCallback&& other) noexcept {
std::swap(env, other.env);
std::swap(javaVM, other.javaVM);
std::swap(callback, other.callback);
}
JCallback& operator=(JCallback&& other) {
std::swap(env, other.env);
std::swap(javaVM, other.javaVM);
std::swap(callback, other.callback);
return this;
}
T operator()() {
jobject res = env->CallObjectMethod(callback, kotlin::jvm::functions::Function0::invoke);
return jObjectConvert<T>(env, res);
}
bool isExceptionThrown() {
return java::lang::Throwable::exceptionThrown(env);
}
private:
JNIEnv* env;
JavaVM* javaVM;
jobject callback;
};
typedef JCallback<jboolean> JBooleanCallback;
\ No newline at end of file
...@@ -78,6 +78,7 @@ actual open class InteropScope actual constructor() { ...@@ -78,6 +78,7 @@ actual open class InteropScope actual constructor() {
actual inline fun <reified T> InteropPointer.fromInterop(decoder: ArrayInteropDecoder<T>): Array<T> = actual inline fun <reified T> InteropPointer.fromInterop(decoder: ArrayInteropDecoder<T>): Array<T> =
this@fromInterop as Array<T> this@fromInterop as Array<T>
actual fun toInteropForArraysOfPointers(interopPointers: Array<InteropPointer>): InteropPointer = interopPointers actual fun toInteropForArraysOfPointers(interopPointers: Array<InteropPointer>): InteropPointer = interopPointers
actual fun toInterop(callback: (() -> Boolean)?) = callback as Any?
actual fun release() {} actual fun release() {}
} }
......
...@@ -7,6 +7,16 @@ ...@@ -7,6 +7,16 @@
#include "SkShader.h" #include "SkShader.h"
#include "common.h" #include "common.h"
class KotlinAbortCallback: public SkPicture::AbortCallback {
public:
KotlinAbortCallback(KInteropPointer data) : callback(data) {}
bool abort() override {
return static_cast<bool>(callback());
}
private:
KBooleanCallback callback;
};
SKIKO_EXPORT KNativePointer org_jetbrains_skia_Picture__1nMakeFromData SKIKO_EXPORT KNativePointer org_jetbrains_skia_Picture__1nMakeFromData
(KNativePointer dataPtr) { (KNativePointer dataPtr) {
SkData* data = reinterpret_cast<SkData*>((dataPtr)); SkData* data = reinterpret_cast<SkData*>((dataPtr));
...@@ -14,41 +24,28 @@ SKIKO_EXPORT KNativePointer org_jetbrains_skia_Picture__1nMakeFromData ...@@ -14,41 +24,28 @@ SKIKO_EXPORT KNativePointer org_jetbrains_skia_Picture__1nMakeFromData
return reinterpret_cast<KNativePointer>(instance); return reinterpret_cast<KNativePointer>(instance);
} }
SKIKO_EXPORT void org_jetbrains_skia_Picture__1nPlayback
(KNativePointer ptr, KNativePointer canvasPtr, KInteropPointer abort) {
TODO("implement org_jetbrains_skia_Picture__1nPlayback");
}
#if 0
SKIKO_EXPORT void org_jetbrains_skia_Picture__1nPlayback SKIKO_EXPORT void org_jetbrains_skia_Picture__1nPlayback
(KNativePointer ptr, KNativePointer canvasPtr, KInteropPointer abort) { (KNativePointer ptr, KNativePointer canvasPtr, KInteropPointer abort) {
SkPicture* instance = reinterpret_cast<SkPicture*>((ptr)); SkPicture* instance = reinterpret_cast<SkPicture*>((ptr));
SkCanvas* canvas = reinterpret_cast<SkCanvas*>((canvasPtr)); SkCanvas* canvas = reinterpret_cast<SkCanvas*>((canvasPtr));
if (abort == nullptr) { if (abort) {
instance->playback(canvas, nullptr); KotlinAbortCallback abortCallback(abort);
instance->playback(canvas, &abortCallback);
} else { } else {
BooleanSupplierAbort callback(env, abort); instance->playback(canvas, nullptr);
instance->playback(canvas, &callback);
} }
} }
#endif
SKIKO_EXPORT void org_jetbrains_skia_Picture__1nGetCullRect
SKIKO_EXPORT KInteropPointer org_jetbrains_skia_Picture__1nGetCullRect (KNativePointer ptr, KInteropPointer ltrbArray) {
(KNativePointer ptr) {
TODO("implement org_jetbrains_skia_Picture__1nGetCullRect");
}
#if 0
SKIKO_EXPORT KInteropPointer org_jetbrains_skia_Picture__1nGetCullRect
(KNativePointer ptr) {
SkPicture* instance = reinterpret_cast<SkPicture*>((ptr)); SkPicture* instance = reinterpret_cast<SkPicture*>((ptr));
return skija::Rect::fromSkRect(env, instance->cullRect()); SkRect cullRect = instance->cullRect();
float* ltrb = reinterpret_cast<float*>(ltrbArray);
ltrb[0] = cullRect.left();
ltrb[1] = cullRect.top();
ltrb[2] = cullRect.right();
ltrb[3] = cullRect.bottom();
} }
#endif
SKIKO_EXPORT KInt org_jetbrains_skia_Picture__1nGetUniqueId SKIKO_EXPORT KInt org_jetbrains_skia_Picture__1nGetUniqueId
(KNativePointer ptr) { (KNativePointer ptr) {
...@@ -84,21 +81,19 @@ SKIKO_EXPORT KLong org_jetbrains_skia_Picture__1nGetApproximateBytesUsed ...@@ -84,21 +81,19 @@ SKIKO_EXPORT KLong org_jetbrains_skia_Picture__1nGetApproximateBytesUsed
SKIKO_EXPORT KNativePointer org_jetbrains_skia_Picture__1nMakeShader SKIKO_EXPORT KNativePointer org_jetbrains_skia_Picture__1nMakeShader
(KNativePointer ptr, KInt tmxValue, KInt tmyValue, KInt filterModeValue, KFloat* localMatrixArr, KInteropPointer tileRectObj) { (KNativePointer ptr, KInt tmxValue, KInt tmyValue, KInt filterModeValue, KFloat* localMatrixArr, KBoolean hasTile, KFloat tileLeft, KFloat tileTop, KFloat tileRight, KFloat tileBottom) {
TODO("implement org_jetbrains_skia_Picture__1nMakeShader");
}
#if 0
SKIKO_EXPORT KNativePointer org_jetbrains_skia_Picture__1nMakeShader
(KNativePointer ptr, KInt tmxValue, KInt tmyValue, KInt filterModeValue, KFloat* localMatrixArr, KInteropPointer tileRectObj) {
SkPicture* instance = reinterpret_cast<SkPicture*>((ptr)); SkPicture* instance = reinterpret_cast<SkPicture*>((ptr));
SkTileMode tmx = static_cast<SkTileMode>(tmxValue); SkTileMode tmx = static_cast<SkTileMode>(tmxValue);
SkTileMode tmy = static_cast<SkTileMode>(tmyValue); SkTileMode tmy = static_cast<SkTileMode>(tmyValue);
SkFilterMode filterMode = static_cast<SkFilterMode>(filterModeValue); SkFilterMode filterMode = static_cast<SkFilterMode>(filterModeValue);
std::unique_ptr<SkMatrix> localMatrix = skMatrix(env, localMatrixArr); std::unique_ptr<SkMatrix> localMatrix = skMatrix(localMatrixArr);
std::unique_ptr<SkRect> tileRect = skija::Rect::toSkRect(env, tileRectObj); SkShader* shader;
SkShader* shader = instance->makeShader(tmx, tmy, filterMode, localMatrix.get(), tileRect.get()).release(); if (hasTile) {
SkRect tileRect = SkRect::MakeLTRB(tileLeft, tileRight, tileBottom, tileTop);
shader = instance->makeShader(tmx, tmy, filterMode, localMatrix.get(), &tileRect).release();
} else {
shader = instance->makeShader(tmx, tmy, filterMode, localMatrix.get(), nullptr).release();
}
return reinterpret_cast<KNativePointer>(shader); return reinterpret_cast<KNativePointer>(shader);
} }
#endif
...@@ -157,5 +157,33 @@ static inline KFloat fromBits(KInt i) { ...@@ -157,5 +157,33 @@ static inline KFloat fromBits(KInt i) {
u.i = i; u.i = i;
return u.f; return u.f;
} }
// Callback support
typedef void (*SkikoDisposeCallback)(KInteropPointer);
typedef KBoolean (*SkikoCallBooleanCallback)(KInteropPointer);
typedef void* KOpaquePointer;
void disposeCallback(KInteropPointer cb);
KBoolean callBooleanCallback(KInteropPointer cb);
template <typename T, T(*Apply)(KInteropPointer)>
class KCallback {
public:
explicit KCallback(KInteropPointer data) : data(data) {}
virtual ~KCallback() { disposeCallback(data); }
KCallback(const KCallback&) = delete;
KCallback(KCallback&&) = delete;
KCallback& operator=(const KCallback&) = delete;
KCallback& operator=(KCallback&&) = delete;
T operator()() const { return Apply(data); }
private:
KInteropPointer data;
};
typedef KCallback<KBoolean, callBooleanCallback> KBooleanCallback;
#endif /* SKIKO_COMMON_H */ #endif /* SKIKO_COMMON_H */
...@@ -127,9 +127,9 @@ std::unique_ptr<SkMatrix> skMatrix(KFloat* matrixArray) { ...@@ -127,9 +127,9 @@ std::unique_ptr<SkMatrix> skMatrix(KFloat* matrixArray) {
return std::unique_ptr<SkMatrix>(nullptr); return std::unique_ptr<SkMatrix>(nullptr);
else { else {
KFloat* m = matrixArray; KFloat* m = matrixArray;
SkMatrix* ptr = new SkMatrix(); std::unique_ptr<SkMatrix> ptr = std::make_unique<SkMatrix>();
ptr->setAll(m[0], m[1], m[2], m[3], m[4], m[5], m[6], m[7], m[8]); ptr->setAll(m[0], m[1], m[2], m[3], m[4], m[5], m[6], m[7], m[8]);
return std::unique_ptr<SkMatrix>(ptr); return ptr;
} }
} }
...@@ -326,3 +326,39 @@ namespace skija { ...@@ -326,3 +326,39 @@ namespace skija {
} }
} }
} }
// Callback support
#ifdef __EMSCRIPTEN__
void disposeCallback(KInteropPointer cb) {
EM_ASM({ _releaseCallback($0) }, cb);
}
KBoolean callBooleanCallback(KInteropPointer cb) {
int value = EM_ASM_INT({
return _callCallback($0).value ? 1 : 0;
}, cb);
return static_cast<KBoolean>(value);
}
#else // __EMSCRIPTEN__
static SkikoDisposeCallback disposeCallbackImpl = nullptr;
static SkikoCallBooleanCallback callBooleanCallbackImpl = nullptr;
SKIKO_EXPORT void skiko_initCallbacks(KOpaquePointer callBoolean, KOpaquePointer dispose) {
callBooleanCallbackImpl = reinterpret_cast<SkikoCallBooleanCallback>(callBoolean);
disposeCallbackImpl = reinterpret_cast<SkikoDisposeCallback>(dispose);
}
void disposeCallback(KInteropPointer cb) {
disposeCallbackImpl(cb);
}
KBoolean callBooleanCallback(KInteropPointer cb) {
return callBooleanCallbackImpl(cb);
}
#endif // __EMSCRIPTEN__
package org.jetbrains.skia.impl package org.jetbrains.skia.impl
import kotlinx.cinterop.Pinned import kotlinx.cinterop.*
import kotlinx.cinterop.addressOf import org.jetbrains.skia.ExternalSymbolName
import kotlinx.cinterop.pin
import kotlin.native.internal.NativePtr import kotlin.native.internal.NativePtr
actual abstract class Native actual constructor(ptr: NativePointer) { actual abstract class Native actual constructor(ptr: NativePointer) {
...@@ -24,6 +23,13 @@ actual abstract class Native actual constructor(ptr: NativePointer) { ...@@ -24,6 +23,13 @@ actual abstract class Native actual constructor(ptr: NativePointer) {
} }
actual companion object { actual companion object {
init {
initCallbacks(
staticCFunction(::callBooleanCallback),
staticCFunction(::disposeCallback),
)
}
actual val NullPointer: NativePointer actual val NullPointer: NativePointer
get() = NativePtr.NULL get() = NativePtr.NULL
} }
...@@ -194,6 +200,12 @@ actual class InteropScope actual constructor() { ...@@ -194,6 +200,12 @@ actual class InteropScope actual constructor() {
return toInterop(interopPointers.map { it.toLong() }.toLongArray()) return toInterop(interopPointers.map { it.toLong() }.toLongArray())
} }
actual fun toInterop(callback: (() -> Boolean)?): InteropPointer
= callback?.let {
val ptr = StableRef.create(it).asCPointer()
NativePtr.NULL.plus(ptr.toLong())
} ?: NativePtr.NULL
actual fun release() { actual fun release() {
elements.forEach { elements.forEach {
it.unpin() it.unpin()
...@@ -217,3 +229,16 @@ actual class NativePointerArray actual constructor(size: Int) { ...@@ -217,3 +229,16 @@ actual class NativePointerArray actual constructor(size: Int) {
actual val size: Int actual val size: Int
get() = backing.size get() = backing.size
} }
// Callbacks support
private fun callBooleanCallback(ptr: COpaquePointer): Boolean {
return ptr.asStableRef<() -> Boolean>().get().invoke()
}
private fun disposeCallback(ptr: COpaquePointer) {
ptr.asStableRef<Any>().dispose()
}
@ExternalSymbolName("skiko_initCallbacks")
private external fun initCallbacks(callBoolean: COpaquePointer, dispose: COpaquePointer)
\ 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