add image codec.
This commit is contained in:
Родитель
d2992d5f9d
Коммит
f5232e4046
|
@ -17,19 +17,19 @@ namespace Unity.UIWidgets.async2 {
|
|||
}
|
||||
|
||||
public static readonly FutureOr nil = value(null);
|
||||
|
||||
|
||||
public static implicit operator FutureOr(Future f) => future(f);
|
||||
|
||||
|
||||
public static implicit operator FutureOr(bool v) => value(v);
|
||||
|
||||
|
||||
public static implicit operator FutureOr(int v) => value(v);
|
||||
|
||||
|
||||
public static implicit operator FutureOr(long v) => value(v);
|
||||
|
||||
|
||||
public static implicit operator FutureOr(float v) => value(v);
|
||||
|
||||
|
||||
public static implicit operator FutureOr(double v) => value(v);
|
||||
|
||||
|
||||
public static implicit operator FutureOr(string v) => value(v);
|
||||
|
||||
public static implicit operator FutureOr(byte[] v) => value(v);
|
||||
|
@ -309,10 +309,31 @@ namespace Unity.UIWidgets.async2 {
|
|||
|
||||
public abstract Future then(Func<object, FutureOr> onValue, Func<Exception, FutureOr> onError = null);
|
||||
|
||||
public Future then(Action<object> onValue, Func<Exception, FutureOr> onError = null) {
|
||||
return then(v => {
|
||||
onValue(v);
|
||||
return FutureOr.nil;
|
||||
}, onError);
|
||||
}
|
||||
|
||||
public abstract Future catchError(Func<Exception, FutureOr> onError, Func<Exception, bool> test = null);
|
||||
|
||||
public Future catchError(Action<Exception> onError, Func<Exception, bool> test = null) {
|
||||
return catchError(e => {
|
||||
onError(e);
|
||||
return FutureOr.nil;
|
||||
}, test);
|
||||
}
|
||||
|
||||
public abstract Future whenComplete(Func<FutureOr> action);
|
||||
|
||||
public Future whenComplete(Action action) {
|
||||
return whenComplete(() => {
|
||||
action();
|
||||
return FutureOr.nil;
|
||||
});
|
||||
}
|
||||
|
||||
// public abstract Stream asStream();
|
||||
|
||||
public abstract Future timeout(TimeSpan timeLimit, Func<FutureOr> onTimeout = null);
|
||||
|
@ -337,10 +358,18 @@ namespace Unity.UIWidgets.async2 {
|
|||
return _future.then(onValue, onError);
|
||||
}
|
||||
|
||||
public Future<R> then<R>(Func<T, FutureOr> onValue, Func<Exception, FutureOr> onError = null) {
|
||||
public Future then_(Func<T, FutureOr> onValue, Func<Exception, FutureOr> onError = null) {
|
||||
return _future.then(obj => onValue((T) obj), onError);
|
||||
}
|
||||
|
||||
public Future<R> then_<R>(Func<T, FutureOr> onValue, Func<Exception, FutureOr> onError = null) {
|
||||
return _future.then(obj => onValue((T) obj), onError).to<R>();
|
||||
}
|
||||
|
||||
public Future then_(Action<T> onValue, Func<Exception, FutureOr> onError = null) {
|
||||
return _future.then(obj => onValue((T) obj), onError);
|
||||
}
|
||||
|
||||
public override Future catchError(Func<Exception, FutureOr> onError, Func<Exception, bool> test = null) {
|
||||
return _future.catchError(onError, test);
|
||||
}
|
||||
|
@ -375,7 +404,7 @@ namespace Unity.UIWidgets.async2 {
|
|||
public static Completer sync() => new _SyncCompleter();
|
||||
|
||||
public abstract Future future { get; }
|
||||
|
||||
|
||||
public abstract void complete(FutureOr value = default);
|
||||
|
||||
public abstract void completeError(Exception error);
|
||||
|
|
|
@ -87,8 +87,7 @@ namespace Unity.UIWidgets.engine2 {
|
|||
_handle.Free();
|
||||
_handle = default;
|
||||
|
||||
if (isolate != null)
|
||||
Isolate.remove(isolate);
|
||||
D.assert(!isolate.isValid);
|
||||
|
||||
base.OnDisable();
|
||||
}
|
||||
|
|
|
@ -13,7 +13,7 @@ namespace Unity.UIWidgets.services {
|
|||
public abstract Future<byte[]> load(string key);
|
||||
|
||||
public virtual Future<string> loadString(string key, bool cache = true) {
|
||||
return load(key).then<string>(data => {
|
||||
return load(key).then_<string>(data => {
|
||||
if (data == null)
|
||||
throw new UIWidgetsError($"Unable to load asset: {key}");
|
||||
|
||||
|
@ -82,7 +82,7 @@ namespace Unity.UIWidgets.services {
|
|||
public override Future<T> loadStructuredData<T>(string key, Func<string, Future<T>> parser) {
|
||||
D.assert(key != null);
|
||||
D.assert(parser != null);
|
||||
return loadString(key).then<T>(value => parser(value));
|
||||
return loadString(key).then_<T>(value => parser(value));
|
||||
}
|
||||
|
||||
public override string ToString() => $"{foundation_.describeIdentity(this)}({_baseUrl})";
|
||||
|
@ -107,7 +107,7 @@ namespace Unity.UIWidgets.services {
|
|||
|
||||
Completer completer = null;
|
||||
Future<T> result = null;
|
||||
loadString(key, cache: false).then<T>(value => parser(value)).then<object>((T value) => {
|
||||
loadString(key, cache: false).then_<T>(value => parser(value)).then_<object>((T value) => {
|
||||
result = new SynchronousFuture<T>(value);
|
||||
_structuredDataCache[key] = result;
|
||||
if (completer != null) {
|
||||
|
@ -143,7 +143,7 @@ namespace Unity.UIWidgets.services {
|
|||
public override Future<byte[]> load(string key) {
|
||||
byte[] encoded = Encoding.UTF8.GetBytes(key);
|
||||
return ServicesBinding.instance.defaultBinaryMessenger.send(
|
||||
"uiwidgets/assets", encoded).then<byte[]>(asset => {
|
||||
"uiwidgets/assets", encoded).then_<byte[]>(asset => {
|
||||
if (asset == null)
|
||||
throw new UIWidgetsError($"Unable to load asset: {key}");
|
||||
return asset;
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
using System.Collections.Generic;
|
||||
using System.Runtime.InteropServices;
|
||||
using AOT;
|
||||
using RSG;
|
||||
using Unity.UIWidgets.async2;
|
||||
using Unity.UIWidgets.foundation;
|
||||
using Unity.UIWidgets.ui;
|
||||
using UnityEngine;
|
||||
|
@ -16,39 +16,39 @@ namespace Unity.UIWidgets.ui2 {
|
|||
Scene_dispose(ptr);
|
||||
}
|
||||
|
||||
public Promise<Image> toImage(int width, int height) {
|
||||
public Future<Image> toImage(int width, int height) {
|
||||
if (width <= 0 || height <= 0) {
|
||||
throw new Exception("Invalid image dimensions.");
|
||||
}
|
||||
|
||||
var completer = new Promise<Image>(true);
|
||||
GCHandle completerHandle = GCHandle.Alloc(completer);
|
||||
return ui_._futurize(
|
||||
(_Callback<Image> callback) => {
|
||||
GCHandle callbackHandle = GCHandle.Alloc(callback);
|
||||
IntPtr error =
|
||||
Scene_toImage(_ptr, width, height, _toImageCallback,
|
||||
(IntPtr) callbackHandle);
|
||||
|
||||
IntPtr error =
|
||||
Scene_toImage(_ptr, width, height, _toImageCallback,
|
||||
(IntPtr) completerHandle);
|
||||
if (error != null) {
|
||||
completerHandle.Free();
|
||||
throw new Exception(Marshal.PtrToStringAnsi(error));
|
||||
}
|
||||
if (error != IntPtr.Zero) {
|
||||
callbackHandle.Free();
|
||||
return Marshal.PtrToStringAnsi(error);
|
||||
}
|
||||
|
||||
return completer;
|
||||
return null;
|
||||
});
|
||||
}
|
||||
|
||||
[MonoPInvokeCallback(typeof(Scene_toImageCallback))]
|
||||
static void _toImageCallback(IntPtr callbackHandle, IntPtr result) {
|
||||
GCHandle completerHandle = (GCHandle) callbackHandle;
|
||||
var completer = (Promise<Image>) completerHandle.Target;
|
||||
completerHandle.Free();
|
||||
GCHandle handle = (GCHandle) callbackHandle;
|
||||
var callback = (_Callback<Image>) handle.Target;
|
||||
handle.Free();
|
||||
|
||||
if (!Isolate.checkExists()) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
if (result == IntPtr.Zero) {
|
||||
completer.Reject(new Exception("operation failed"));
|
||||
}
|
||||
else {
|
||||
var image = new Image(result);
|
||||
completer.Resolve(image);
|
||||
}
|
||||
callback(result == IntPtr.Zero ? null : new Image(result));
|
||||
}
|
||||
catch (Exception ex) {
|
||||
Debug.LogException(ex);
|
||||
|
|
|
@ -15,7 +15,8 @@ namespace Unity.UIWidgets.ui2 {
|
|||
#endif
|
||||
static unsafe void hook() {
|
||||
Mono_hook(
|
||||
Mono_throwException);
|
||||
Mono_throwException,
|
||||
Mono_shutdown);
|
||||
|
||||
Window_hook(
|
||||
Window_constructor,
|
||||
|
@ -33,8 +34,20 @@ namespace Unity.UIWidgets.ui2 {
|
|||
throw new Exception(Marshal.PtrToStringAnsi(exception));
|
||||
}
|
||||
|
||||
delegate void Mono_ShutdownCallback(IntPtr isolate);
|
||||
|
||||
[MonoPInvokeCallback(typeof(Mono_ShutdownCallback))]
|
||||
static void Mono_shutdown(IntPtr isolate) {
|
||||
try {
|
||||
Isolate.shutdown(isolate);
|
||||
}
|
||||
catch (Exception ex) {
|
||||
Debug.LogException(ex);
|
||||
}
|
||||
}
|
||||
|
||||
[DllImport(NativeBindings.dllName)]
|
||||
static extern void Mono_hook(Mono_ThrowExceptionCallback throwException);
|
||||
static extern void Mono_hook(Mono_ThrowExceptionCallback throwException, Mono_ShutdownCallback shutdown);
|
||||
|
||||
delegate IntPtr Window_constructorCallback(IntPtr ptr);
|
||||
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
using System.Collections.Generic;
|
||||
using System.Runtime.InteropServices;
|
||||
using Unity.UIWidgets.foundation;
|
||||
using UnityEngine;
|
||||
|
||||
namespace Unity.UIWidgets.ui2 {
|
||||
public class Isolate {
|
||||
|
@ -13,6 +14,28 @@ namespace Unity.UIWidgets.ui2 {
|
|||
|
||||
IntPtr _ptr;
|
||||
|
||||
readonly Dictionary<IntPtr, WeakReference<NativeWrapper>> _nativeWrappers =
|
||||
new Dictionary<IntPtr, WeakReference<NativeWrapper>>();
|
||||
|
||||
bool _inShutdown = false;
|
||||
|
||||
internal void addNativeWrapper(NativeWrapper wrapper) {
|
||||
lock (_nativeWrappers) {
|
||||
_nativeWrappers.Add(wrapper._ptr, new WeakReference<NativeWrapper>(wrapper));
|
||||
}
|
||||
}
|
||||
|
||||
internal void removeNativeWrapper(IntPtr ptr) {
|
||||
lock (_nativeWrappers) {
|
||||
if (_inShutdown) {
|
||||
return;
|
||||
}
|
||||
|
||||
_nativeWrappers.Remove(ptr);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public bool isValid => _ptr != IntPtr.Zero;
|
||||
|
||||
public static Isolate current {
|
||||
|
@ -22,7 +45,7 @@ namespace Unity.UIWidgets.ui2 {
|
|||
D.assert(value.isValid);
|
||||
return value;
|
||||
}
|
||||
|
||||
|
||||
var isolate = new Isolate(ptr);
|
||||
_isolates.Add(ptr, isolate);
|
||||
return isolate;
|
||||
|
@ -40,9 +63,28 @@ namespace Unity.UIWidgets.ui2 {
|
|||
return ptr;
|
||||
}
|
||||
|
||||
internal static void remove(Isolate isolate) {
|
||||
D.assert(isolate != null && isolate.isValid);
|
||||
_isolates.Remove(isolate._ptr);
|
||||
public static bool checkExists() {
|
||||
IntPtr ptr = Isolate_current();
|
||||
return ptr != IntPtr.Zero;
|
||||
}
|
||||
|
||||
internal static void shutdown(IntPtr ptr) {
|
||||
var isolate = _isolates[ptr];
|
||||
D.assert(isolate._ptr == ptr);
|
||||
|
||||
lock (isolate._nativeWrappers) {
|
||||
isolate._inShutdown = true;
|
||||
foreach (var entry in isolate._nativeWrappers) {
|
||||
if (entry.Value.TryGetTarget(out NativeWrapper target)) {
|
||||
target._dispose();
|
||||
}
|
||||
}
|
||||
|
||||
isolate._nativeWrappers.Clear();
|
||||
isolate._inShutdown = false;
|
||||
}
|
||||
|
||||
_isolates.Remove(ptr);
|
||||
isolate._ptr = IntPtr.Zero;
|
||||
}
|
||||
|
||||
|
|
|
@ -8,57 +8,60 @@ namespace Unity.UIWidgets.ui2 {
|
|||
#else
|
||||
internal const string dllName = "libUIWidgets_d";
|
||||
#endif
|
||||
|
||||
}
|
||||
|
||||
public abstract class NativeWrapper {
|
||||
protected internal IntPtr _ptr { get; protected set; }
|
||||
protected internal IntPtr _ptr { get; private set; }
|
||||
|
||||
protected NativeWrapper() {
|
||||
}
|
||||
|
||||
Isolate _isolate;
|
||||
|
||||
protected NativeWrapper(IntPtr ptr) {
|
||||
_setPtr(ptr);
|
||||
}
|
||||
|
||||
protected void _setPtr(IntPtr ptr) {
|
||||
D.assert(ptr != IntPtr.Zero);
|
||||
_ptr = ptr;
|
||||
|
||||
_isolate = Isolate.current;
|
||||
_isolate.addNativeWrapper(this);
|
||||
}
|
||||
|
||||
internal void _dispose(bool finalizer = false) {
|
||||
if (_ptr != IntPtr.Zero) {
|
||||
|
||||
D.assert(_isolate.isValid);
|
||||
_isolate.removeNativeWrapper(_ptr);
|
||||
|
||||
DisposePtr(_ptr);
|
||||
|
||||
_ptr = IntPtr.Zero;
|
||||
|
||||
if (!finalizer) {
|
||||
GC.SuppressFinalize(this);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
~NativeWrapper() {
|
||||
if (_ptr != IntPtr.Zero) {
|
||||
DisposePtr(_ptr);
|
||||
_ptr = IntPtr.Zero;
|
||||
}
|
||||
_dispose(true);
|
||||
}
|
||||
|
||||
protected abstract void DisposePtr(IntPtr ptr);
|
||||
}
|
||||
|
||||
public abstract class NativeWrapperDisposable : IDisposable {
|
||||
protected internal IntPtr _ptr { get; protected set; }
|
||||
|
||||
public abstract class NativeWrapperDisposable : NativeWrapper, IDisposable {
|
||||
protected NativeWrapperDisposable() {
|
||||
}
|
||||
|
||||
protected NativeWrapperDisposable(IntPtr ptr) {
|
||||
D.assert(ptr != IntPtr.Zero);
|
||||
_ptr = ptr;
|
||||
protected NativeWrapperDisposable(IntPtr ptr) : base(ptr) {
|
||||
}
|
||||
|
||||
~NativeWrapperDisposable() {
|
||||
if (_ptr != IntPtr.Zero) {
|
||||
DisposePtr(_ptr);
|
||||
_ptr = IntPtr.Zero;
|
||||
}
|
||||
}
|
||||
|
||||
protected abstract void DisposePtr(IntPtr ptr);
|
||||
|
||||
public void Dispose() {
|
||||
if (_ptr != IntPtr.Zero) {
|
||||
DisposePtr(_ptr);
|
||||
_ptr = IntPtr.Zero;
|
||||
}
|
||||
|
||||
GC.SuppressFinalize(this);
|
||||
_dispose();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -4,7 +4,6 @@ using System.Linq;
|
|||
using System.Runtime.InteropServices;
|
||||
using System.Text;
|
||||
using AOT;
|
||||
using RSG;
|
||||
using Unity.UIWidgets.async2;
|
||||
using Unity.UIWidgets.foundation;
|
||||
using UnityEngine;
|
||||
|
@ -392,6 +391,10 @@ namespace Unity.UIWidgets.ui2 {
|
|||
antiAliasWithSaveLayer,
|
||||
}
|
||||
|
||||
public static partial class ui_ {
|
||||
const int _kDoNotResizeDimension = -1;
|
||||
}
|
||||
|
||||
public class Paint {
|
||||
internal readonly byte[] _data = new byte[_kDataByteCount];
|
||||
|
||||
|
@ -730,57 +733,74 @@ namespace Unity.UIWidgets.ui2 {
|
|||
png,
|
||||
}
|
||||
|
||||
enum PixelFormat {
|
||||
public enum PixelFormat {
|
||||
rgba8888,
|
||||
bgra8888,
|
||||
}
|
||||
|
||||
/**
|
||||
* TODO: _ImageInfo
|
||||
*/
|
||||
[StructLayout(LayoutKind.Sequential)]
|
||||
struct _ImageInfo {
|
||||
internal _ImageInfo(int width, int height, int format, int? rowBytes = null) {
|
||||
this.width = width;
|
||||
this.height = height;
|
||||
this.format = format;
|
||||
this.rowBytes = rowBytes ?? width * 4;
|
||||
}
|
||||
|
||||
public int width;
|
||||
public int height;
|
||||
public int format;
|
||||
public int rowBytes;
|
||||
}
|
||||
|
||||
public class Image : NativeWrapperDisposable {
|
||||
internal Image(IntPtr ptr) : base(ptr) {
|
||||
}
|
||||
|
||||
protected override void DisposePtr(IntPtr ptr) {
|
||||
Image_dispose(_ptr);
|
||||
Image_dispose(ptr);
|
||||
}
|
||||
|
||||
public int width => Image_width(_ptr);
|
||||
|
||||
public int height => Image_height(_ptr);
|
||||
|
||||
public Promise<byte[]> toByteData(
|
||||
public Future<byte[]> toByteData(
|
||||
ImageByteFormat format = ImageByteFormat.rawRgba
|
||||
) {
|
||||
var completer = new Promise<byte[]>(true);
|
||||
GCHandle completerHandle = GCHandle.Alloc(completer);
|
||||
return ui_._futurize(
|
||||
(_Callback<byte[]> callback) => {
|
||||
GCHandle callbackHandle = GCHandle.Alloc(callback);
|
||||
|
||||
IntPtr error =
|
||||
Image_toByteData(_ptr, (int) format, _toByteDataCallback,
|
||||
(IntPtr) completerHandle);
|
||||
if (error != null) {
|
||||
completerHandle.Free();
|
||||
throw new Exception(Marshal.PtrToStringAnsi(error));
|
||||
}
|
||||
IntPtr error = Image_toByteData(_ptr,
|
||||
(int) format, _toByteDataCallback, (IntPtr) callbackHandle);
|
||||
if (error != IntPtr.Zero) {
|
||||
callbackHandle.Free();
|
||||
return Marshal.PtrToStringAnsi(error);
|
||||
}
|
||||
|
||||
return completer;
|
||||
return null;
|
||||
});
|
||||
}
|
||||
|
||||
[MonoPInvokeCallback(typeof(Image_toByteDataCallback))]
|
||||
static void _toByteDataCallback(IntPtr callbackHandle, IntPtr data, int length) {
|
||||
GCHandle completerHandle = (GCHandle) callbackHandle;
|
||||
var completer = (Promise<byte[]>) completerHandle.Target;
|
||||
completerHandle.Free();
|
||||
GCHandle handle = (GCHandle) callbackHandle;
|
||||
var callback = (_Callback<byte[]>) handle.Target;
|
||||
handle.Free();
|
||||
|
||||
if (!Isolate.checkExists()) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
if (data == IntPtr.Zero || length == 0) {
|
||||
completer.Resolve(new byte[0]);
|
||||
callback(new byte[0]);
|
||||
}
|
||||
else {
|
||||
var bytes = new byte[length];
|
||||
Marshal.Copy(data, bytes, 0, length);
|
||||
completer.Resolve(bytes);
|
||||
callback(bytes);
|
||||
}
|
||||
}
|
||||
catch (Exception ex) {
|
||||
|
@ -809,6 +829,171 @@ namespace Unity.UIWidgets.ui2 {
|
|||
/*
|
||||
* TODO: FrameInfo, Codec
|
||||
*/
|
||||
|
||||
public delegate void ImageDecoderCallback(Image result);
|
||||
|
||||
public class FrameInfo : NativeWrapper {
|
||||
internal FrameInfo(IntPtr ptr) : base(ptr) {
|
||||
}
|
||||
|
||||
protected override void DisposePtr(IntPtr ptr) {
|
||||
FrameInfo_dispose(ptr);
|
||||
}
|
||||
|
||||
public TimeSpan duration => TimeSpan.FromMilliseconds(_durationMillis);
|
||||
int _durationMillis => FrameInfo_durationMillis(_ptr);
|
||||
|
||||
public Image image => new Image(FrameInfo_image(_ptr));
|
||||
|
||||
[DllImport(NativeBindings.dllName)]
|
||||
static extern void FrameInfo_dispose(IntPtr ptr);
|
||||
|
||||
[DllImport(NativeBindings.dllName)]
|
||||
static extern int FrameInfo_durationMillis(IntPtr ptr);
|
||||
|
||||
[DllImport(NativeBindings.dllName)]
|
||||
static extern IntPtr FrameInfo_image(IntPtr ptr);
|
||||
}
|
||||
|
||||
public class Codec : NativeWrapperDisposable {
|
||||
internal Codec(IntPtr ptr) : base(ptr) {
|
||||
}
|
||||
|
||||
protected override void DisposePtr(IntPtr ptr) {
|
||||
Codec_dispose(ptr);
|
||||
}
|
||||
|
||||
public int frameCount => Codec_frameCount(_ptr);
|
||||
|
||||
public int repetitionCount => Codec_repetitionCount(_ptr);
|
||||
|
||||
public Future<FrameInfo> getNextFrame() {
|
||||
return ui_._futurize<FrameInfo>(_getNextFrame);
|
||||
}
|
||||
|
||||
string _getNextFrame(_Callback<FrameInfo> callback) {
|
||||
GCHandle callbackHandle = GCHandle.Alloc(callback);
|
||||
|
||||
IntPtr error = Codec_getNextFrame(_ptr, _getNextFrameCallback, (IntPtr) callbackHandle);
|
||||
if (error != IntPtr.Zero) {
|
||||
callbackHandle.Free();
|
||||
return Marshal.PtrToStringAnsi(error);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
[MonoPInvokeCallback(typeof(Codec_getNextFrameCallback))]
|
||||
static void _getNextFrameCallback(IntPtr callbackHandle, IntPtr ptr) {
|
||||
GCHandle handle = (GCHandle) callbackHandle;
|
||||
var callback = (_Callback<FrameInfo>) handle.Target;
|
||||
handle.Free();
|
||||
|
||||
if (!Isolate.checkExists()) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
callback(ptr == IntPtr.Zero ? null : new FrameInfo(ptr));
|
||||
}
|
||||
catch (Exception ex) {
|
||||
Debug.LogException(ex);
|
||||
}
|
||||
}
|
||||
|
||||
internal static unsafe string _instantiateImageCodec(byte[] list, _Callback<Codec> callback,
|
||||
_ImageInfo? imageInfo, int targetWidth, int targetHeight) {
|
||||
GCHandle callbackHandle = GCHandle.Alloc(callback);
|
||||
|
||||
fixed (byte* bytes = list) {
|
||||
IntPtr error = Codec_instantiateImageCodec(bytes, list?.Length ?? 0,
|
||||
_instantiateImageCodecCallback, (IntPtr) callbackHandle,
|
||||
imageInfo ?? default, imageInfo.HasValue, targetWidth, targetHeight);
|
||||
if (error != IntPtr.Zero) {
|
||||
callbackHandle.Free();
|
||||
return Marshal.PtrToStringAnsi(error);
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
[MonoPInvokeCallback(typeof(Codec_instantiateImageCodecCallback))]
|
||||
static void _instantiateImageCodecCallback(IntPtr callbackHandle, IntPtr ptr) {
|
||||
GCHandle handle = (GCHandle) callbackHandle;
|
||||
var callback = (_Callback<Codec>) handle.Target;
|
||||
handle.Free();
|
||||
|
||||
try {
|
||||
D.assert(ptr != IntPtr.Zero);
|
||||
callback(new Codec(ptr));
|
||||
}
|
||||
catch (Exception ex) {
|
||||
Debug.LogException(ex);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
[DllImport(NativeBindings.dllName)]
|
||||
static extern void Codec_dispose(IntPtr ptr);
|
||||
|
||||
[DllImport(NativeBindings.dllName)]
|
||||
static extern int Codec_frameCount(IntPtr ptr);
|
||||
|
||||
[DllImport(NativeBindings.dllName)]
|
||||
static extern int Codec_repetitionCount(IntPtr ptr);
|
||||
|
||||
delegate void Codec_getNextFrameCallback(IntPtr callbackHandle, IntPtr ptr);
|
||||
|
||||
[DllImport(NativeBindings.dllName)]
|
||||
static extern IntPtr Codec_getNextFrame(IntPtr ptr, Codec_getNextFrameCallback callback, IntPtr callbackHandle);
|
||||
|
||||
delegate void Codec_instantiateImageCodecCallback(IntPtr callbackHandle, IntPtr ptr);
|
||||
|
||||
[DllImport(NativeBindings.dllName)]
|
||||
static extern unsafe IntPtr Codec_instantiateImageCodec(byte* list, int listLength,
|
||||
Codec_instantiateImageCodecCallback callback,
|
||||
IntPtr callbackHandle, _ImageInfo imageInfo, bool hasImageInfo, int targetWidth, int targetHeight);
|
||||
}
|
||||
|
||||
public static partial class ui_ {
|
||||
public static Future<Codec> instantiateImageCodec(byte[] list, int? targetWidth = null,
|
||||
int? targetHeight = null) {
|
||||
return _futurize(
|
||||
(_Callback<Codec> callback) => Codec._instantiateImageCodec(list, callback, null,
|
||||
targetWidth ?? _kDoNotResizeDimension, targetHeight ?? _kDoNotResizeDimension)
|
||||
);
|
||||
}
|
||||
|
||||
public static void decodeImageFromList(byte[] list, ImageDecoderCallback callback) {
|
||||
_decodeImageFromListAsync(list, callback);
|
||||
}
|
||||
|
||||
static Future _decodeImageFromListAsync(byte[] list, ImageDecoderCallback callback) {
|
||||
return instantiateImageCodec(list).then_(codec => {
|
||||
return codec.getNextFrame().then_(frameInfo => { callback(frameInfo.image); });
|
||||
});
|
||||
}
|
||||
|
||||
public static void decodeImageFromPixels(
|
||||
byte[] pixels,
|
||||
int width,
|
||||
int height,
|
||||
PixelFormat format,
|
||||
ImageDecoderCallback callback,
|
||||
int? rowBytes = null, int? targetWidth = null, int? targetHeight = null
|
||||
) {
|
||||
_ImageInfo imageInfo = new _ImageInfo(width, height, (int) format, rowBytes);
|
||||
Future<Codec> codecFuture = _futurize(
|
||||
(_Callback<Codec> callback1) => Codec._instantiateImageCodec(pixels, callback1, imageInfo,
|
||||
targetWidth ?? _kDoNotResizeDimension, targetHeight ?? _kDoNotResizeDimension)
|
||||
);
|
||||
codecFuture.then_<FrameInfo>(codec => codec.getNextFrame())
|
||||
.then_(frameInfo => callback(frameInfo.image));
|
||||
}
|
||||
}
|
||||
|
||||
public enum PathFillType {
|
||||
nonZero,
|
||||
evenOdd,
|
||||
|
@ -827,11 +1012,11 @@ namespace Unity.UIWidgets.ui2 {
|
|||
}
|
||||
|
||||
protected override void DisposePtr(IntPtr ptr) {
|
||||
Layer_dispose(ptr);
|
||||
EngineLayer_dispose(ptr);
|
||||
}
|
||||
|
||||
[DllImport(NativeBindings.dllName)]
|
||||
static extern void Layer_dispose(IntPtr ptr);
|
||||
static extern void EngineLayer_dispose(IntPtr ptr);
|
||||
}
|
||||
|
||||
public class Path : NativeWrapper {
|
||||
|
@ -1902,9 +2087,9 @@ namespace Unity.UIWidgets.ui2 {
|
|||
}
|
||||
|
||||
cullRect = cullRect ?? Rect.largest;
|
||||
_ptr = Canvas_constructor(recorder._ptr, cullRect.left, cullRect.top,
|
||||
_setPtr(Canvas_constructor(recorder._ptr, cullRect.left, cullRect.top,
|
||||
cullRect.right,
|
||||
cullRect.bottom);
|
||||
cullRect.bottom));
|
||||
}
|
||||
|
||||
protected override void DisposePtr(IntPtr ptr) {
|
||||
|
@ -2272,137 +2457,137 @@ namespace Unity.UIWidgets.ui2 {
|
|||
}
|
||||
|
||||
[DllImport(NativeBindings.dllName)]
|
||||
public static extern IntPtr Canvas_constructor(IntPtr recorder,
|
||||
static extern IntPtr Canvas_constructor(IntPtr recorder,
|
||||
float left,
|
||||
float top,
|
||||
float right,
|
||||
float bottom);
|
||||
|
||||
[DllImport(NativeBindings.dllName)]
|
||||
public static extern void Canvas_dispose(IntPtr ptr);
|
||||
static extern void Canvas_dispose(IntPtr ptr);
|
||||
|
||||
[DllImport(NativeBindings.dllName)]
|
||||
public static extern void Canvas_save(IntPtr ptr);
|
||||
static extern void Canvas_save(IntPtr ptr);
|
||||
|
||||
[DllImport(NativeBindings.dllName)]
|
||||
public static extern unsafe void
|
||||
static extern unsafe void
|
||||
Canvas_saveLayerWithoutBounds(IntPtr ptr, IntPtr* paintObject, byte* paintData);
|
||||
|
||||
[DllImport(NativeBindings.dllName)]
|
||||
public static extern unsafe void Canvas_saveLayer(IntPtr ptr,
|
||||
static extern unsafe void Canvas_saveLayer(IntPtr ptr,
|
||||
float left, float top, float right, float bottom, IntPtr* paintObject, byte* paintData);
|
||||
|
||||
[DllImport(NativeBindings.dllName)]
|
||||
public static extern void Canvas_restore(IntPtr ptr);
|
||||
static extern void Canvas_restore(IntPtr ptr);
|
||||
|
||||
[DllImport(NativeBindings.dllName)]
|
||||
public static extern int Canvas_getSaveCount(IntPtr ptr);
|
||||
static extern int Canvas_getSaveCount(IntPtr ptr);
|
||||
|
||||
[DllImport(NativeBindings.dllName)]
|
||||
public static extern void Canvas_translate(IntPtr ptr,
|
||||
static extern void Canvas_translate(IntPtr ptr,
|
||||
float x, float y);
|
||||
|
||||
[DllImport(NativeBindings.dllName)]
|
||||
public static extern void Canvas_scale(IntPtr ptr,
|
||||
static extern void Canvas_scale(IntPtr ptr,
|
||||
float sx, float sy);
|
||||
|
||||
[DllImport(NativeBindings.dllName)]
|
||||
public static extern void Canvas_rotate(IntPtr ptr,
|
||||
static extern void Canvas_rotate(IntPtr ptr,
|
||||
float radians);
|
||||
|
||||
[DllImport(NativeBindings.dllName)]
|
||||
public static extern void Canvas_skew(IntPtr ptr,
|
||||
static extern void Canvas_skew(IntPtr ptr,
|
||||
float sx, float sy);
|
||||
|
||||
[DllImport(NativeBindings.dllName)]
|
||||
public static extern unsafe void Canvas_transform(IntPtr ptr,
|
||||
static extern unsafe void Canvas_transform(IntPtr ptr,
|
||||
float* matrix4);
|
||||
|
||||
[DllImport(NativeBindings.dllName)]
|
||||
public static extern void Canvas_clipRect(IntPtr ptr,
|
||||
static extern void Canvas_clipRect(IntPtr ptr,
|
||||
float left, float top, float right, float bottom, int clipOp, bool doAntiAlias);
|
||||
|
||||
[DllImport(NativeBindings.dllName)]
|
||||
public static extern unsafe void Canvas_clipRRect(IntPtr ptr,
|
||||
static extern unsafe void Canvas_clipRRect(IntPtr ptr,
|
||||
float* rrect, bool doAntiAlias);
|
||||
|
||||
[DllImport(NativeBindings.dllName)]
|
||||
public static extern void Canvas_clipPath(IntPtr ptr,
|
||||
static extern void Canvas_clipPath(IntPtr ptr,
|
||||
IntPtr path, bool doAntiAlias);
|
||||
|
||||
[DllImport(NativeBindings.dllName)]
|
||||
public static extern void Canvas_drawColor(IntPtr ptr,
|
||||
static extern void Canvas_drawColor(IntPtr ptr,
|
||||
uint color, int blendMode);
|
||||
|
||||
[DllImport(NativeBindings.dllName)]
|
||||
public static extern unsafe void Canvas_drawLine(IntPtr ptr,
|
||||
static extern unsafe void Canvas_drawLine(IntPtr ptr,
|
||||
float x1, float y1, float x2, float y2, IntPtr* paintObject, byte* paintData);
|
||||
|
||||
[DllImport(NativeBindings.dllName)]
|
||||
public static extern unsafe void Canvas_drawPaint(IntPtr ptr,
|
||||
static extern unsafe void Canvas_drawPaint(IntPtr ptr,
|
||||
IntPtr* paintObject, byte* paintData);
|
||||
|
||||
[DllImport(NativeBindings.dllName)]
|
||||
public static extern unsafe void Canvas_drawRect(IntPtr ptr,
|
||||
static extern unsafe void Canvas_drawRect(IntPtr ptr,
|
||||
float left, float top, float right, float bottom, IntPtr* paintObject, byte* paintData);
|
||||
|
||||
[DllImport(NativeBindings.dllName)]
|
||||
public static extern unsafe void Canvas_drawRRect(IntPtr ptr,
|
||||
static extern unsafe void Canvas_drawRRect(IntPtr ptr,
|
||||
float* rrect, IntPtr* paintObject, byte* paintData);
|
||||
|
||||
[DllImport(NativeBindings.dllName)]
|
||||
public static extern unsafe void Canvas_drawDRRect(IntPtr ptr,
|
||||
static extern unsafe void Canvas_drawDRRect(IntPtr ptr,
|
||||
float* outer, float* inner, IntPtr* paintObject, byte* paintData);
|
||||
|
||||
[DllImport(NativeBindings.dllName)]
|
||||
public static extern unsafe void Canvas_drawOval(IntPtr ptr,
|
||||
static extern unsafe void Canvas_drawOval(IntPtr ptr,
|
||||
float left, float top, float right, float bottom, IntPtr* paintObject, byte* paintData);
|
||||
|
||||
[DllImport(NativeBindings.dllName)]
|
||||
public static extern unsafe void Canvas_drawCircle(IntPtr ptr,
|
||||
static extern unsafe void Canvas_drawCircle(IntPtr ptr,
|
||||
float x, float y, float radius, IntPtr* paintObject, byte* paintData);
|
||||
|
||||
[DllImport(NativeBindings.dllName)]
|
||||
public static extern unsafe void Canvas_drawArc(IntPtr ptr,
|
||||
static extern unsafe void Canvas_drawArc(IntPtr ptr,
|
||||
float left, float top, float right, float bottom,
|
||||
float startAngle, float sweepAngle, bool useCenter, IntPtr* paintObject, byte* paintData);
|
||||
|
||||
[DllImport(NativeBindings.dllName)]
|
||||
public static extern unsafe void Canvas_drawPath(IntPtr ptr,
|
||||
static extern unsafe void Canvas_drawPath(IntPtr ptr,
|
||||
IntPtr path, IntPtr* paintObject, byte* paintData);
|
||||
|
||||
[DllImport(NativeBindings.dllName)]
|
||||
public static extern unsafe void Canvas_drawImage(IntPtr ptr,
|
||||
static extern unsafe void Canvas_drawImage(IntPtr ptr,
|
||||
IntPtr image, float x, float y, IntPtr* paintObject, byte* paintData);
|
||||
|
||||
[DllImport(NativeBindings.dllName)]
|
||||
public static extern unsafe void Canvas_drawImageRect(IntPtr ptr,
|
||||
static extern unsafe void Canvas_drawImageRect(IntPtr ptr,
|
||||
IntPtr image, float srcLeft, float srcTop, float srcRight, float srcBottom,
|
||||
float dstLeft, float dstTop, float dstRight, float dstBottom, IntPtr* paintObject, byte* paintData);
|
||||
|
||||
[DllImport(NativeBindings.dllName)]
|
||||
public static extern unsafe void Canvas_drawImageNine(IntPtr ptr,
|
||||
static extern unsafe void Canvas_drawImageNine(IntPtr ptr,
|
||||
IntPtr image, float centerLeft, float centerTop, float centerRight, float centerBottom,
|
||||
float dstLeft, float dstTop, float dstRight, float dstBottom, IntPtr* paintObject, byte* paintData);
|
||||
|
||||
[DllImport(NativeBindings.dllName)]
|
||||
public static extern void Canvas_drawPicture(IntPtr ptr, IntPtr picture);
|
||||
static extern void Canvas_drawPicture(IntPtr ptr, IntPtr picture);
|
||||
|
||||
[DllImport(NativeBindings.dllName)]
|
||||
public static extern unsafe void Canvas_drawPoints(IntPtr ptr,
|
||||
static extern unsafe void Canvas_drawPoints(IntPtr ptr,
|
||||
IntPtr* paintObject, byte* paintData, int pointMode, float* points, int pointsLength);
|
||||
|
||||
[DllImport(NativeBindings.dllName)]
|
||||
public static extern unsafe void Canvas_drawVertices(IntPtr ptr, IntPtr vertices, int blendMode,
|
||||
static extern unsafe void Canvas_drawVertices(IntPtr ptr, IntPtr vertices, int blendMode,
|
||||
IntPtr* paintObject, byte* paintData);
|
||||
|
||||
[DllImport(NativeBindings.dllName)]
|
||||
public static extern unsafe void Canvas_drawAtlas(IntPtr ptr, IntPtr* paintObjects, byte* paintData,
|
||||
static extern unsafe void Canvas_drawAtlas(IntPtr ptr, IntPtr* paintObjects, byte* paintData,
|
||||
IntPtr atlas, float* rstTransforms, int rstTransformsLength,
|
||||
float* rects, int rectsLength, uint* colors, int colorsLength, int blendMode, float* cullRect);
|
||||
|
||||
[DllImport(NativeBindings.dllName)]
|
||||
public static extern void Canvas_drawShadow(IntPtr ptr, IntPtr path, uint color, float elevation,
|
||||
static extern void Canvas_drawShadow(IntPtr ptr, IntPtr path, uint color, float elevation,
|
||||
bool transparentOccluder);
|
||||
}
|
||||
|
||||
|
@ -2414,7 +2599,7 @@ namespace Unity.UIWidgets.ui2 {
|
|||
Picture_dispose(ptr);
|
||||
}
|
||||
|
||||
public Future toImage(int width, int height) {
|
||||
public Future<Image> toImage(int width, int height) {
|
||||
if (width <= 0 || height <= 0) {
|
||||
throw new ArgumentException("Invalid image dimensions.");
|
||||
}
|
||||
|
@ -2441,6 +2626,10 @@ namespace Unity.UIWidgets.ui2 {
|
|||
var callback = (_Callback<Image>) handle.Target;
|
||||
handle.Free();
|
||||
|
||||
if (!Isolate.checkExists()) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
callback(result == IntPtr.Zero ? null : new Image(result));
|
||||
}
|
||||
|
@ -2500,7 +2689,7 @@ namespace Unity.UIWidgets.ui2 {
|
|||
delegate string _Callbacker<T>(_Callback<T> callback);
|
||||
|
||||
public static partial class ui_ {
|
||||
internal static Future _futurize<T>(_Callbacker<T> callbacker) {
|
||||
internal static Future<T> _futurize<T>(_Callbacker<T> callbacker) {
|
||||
Completer completer = Completer.sync();
|
||||
string error = callbacker(t => {
|
||||
if (t == null) {
|
||||
|
@ -2512,7 +2701,7 @@ namespace Unity.UIWidgets.ui2 {
|
|||
});
|
||||
if (error != null)
|
||||
throw new Exception(error);
|
||||
return completer.future;
|
||||
return completer.future.to<T>();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -235,6 +235,10 @@ namespace Unity.UIWidgets.ui2 {
|
|||
var callback = (PlatformMessageResponseCallback) handle.Target;
|
||||
handle.Free();
|
||||
|
||||
if (!Isolate.checkExists()) {
|
||||
return;
|
||||
}
|
||||
|
||||
byte[] bytes = null;
|
||||
if (data != null && dataLength != 0) {
|
||||
bytes = new byte[dataLength];
|
||||
|
|
|
@ -122,10 +122,14 @@ class Build
|
|||
|
||||
"src/lib/ui/painting/canvas.cc",
|
||||
"src/lib/ui/painting/canvas.h",
|
||||
"src/lib/ui/painting/codec.cc",
|
||||
"src/lib/ui/painting/codec.h",
|
||||
"src/lib/ui/painting/color_filter.cc",
|
||||
"src/lib/ui/painting/color_filter.h",
|
||||
"src/lib/ui/painting/engine_layer.cc",
|
||||
"src/lib/ui/painting/engine_layer.h",
|
||||
"src/lib/ui/painting/frame_info.cc",
|
||||
"src/lib/ui/painting/frame_info.h",
|
||||
"src/lib/ui/painting/gradient.cc",
|
||||
"src/lib/ui/painting/gradient.h",
|
||||
"src/lib/ui/painting/image.cc",
|
||||
|
@ -140,6 +144,8 @@ class Build
|
|||
"src/lib/ui/painting/image_shader.h",
|
||||
"src/lib/ui/painting/matrix.cc",
|
||||
"src/lib/ui/painting/matrix.h",
|
||||
"src/lib/ui/painting/multi_frame_codec.cc",
|
||||
"src/lib/ui/painting/multi_frame_codec.h",
|
||||
"src/lib/ui/painting/path.cc",
|
||||
"src/lib/ui/painting/path.h",
|
||||
"src/lib/ui/painting/paint.cc",
|
||||
|
@ -152,6 +158,8 @@ class Build
|
|||
"src/lib/ui/painting/rrect.h",
|
||||
"src/lib/ui/painting/shader.cc",
|
||||
"src/lib/ui/painting/shader.h",
|
||||
"src/lib/ui/painting/single_frame_codec.cc",
|
||||
"src/lib/ui/painting/single_frame_codec.h",
|
||||
"src/lib/ui/painting/vertices.cc",
|
||||
"src/lib/ui/painting/vertices.h",
|
||||
|
||||
|
|
|
@ -13,11 +13,7 @@ SkiaUnrefQueue::SkiaUnrefQueue(fml::RefPtr<fml::TaskRunner> task_runner,
|
|||
drain_pending_(false),
|
||||
context_(context) {}
|
||||
|
||||
SkiaUnrefQueue::~SkiaUnrefQueue() {
|
||||
if (!objects_.empty()) {
|
||||
Drain(false);
|
||||
}
|
||||
}
|
||||
SkiaUnrefQueue::~SkiaUnrefQueue() { FML_DCHECK(objects_.empty()); }
|
||||
|
||||
void SkiaUnrefQueue::Unref(SkRefCnt* object) {
|
||||
std::scoped_lock lock(mutex_);
|
||||
|
@ -29,7 +25,7 @@ void SkiaUnrefQueue::Unref(SkRefCnt* object) {
|
|||
}
|
||||
}
|
||||
|
||||
void SkiaUnrefQueue::Drain(bool performDeferredCleanup) {
|
||||
void SkiaUnrefQueue::Drain() {
|
||||
TRACE_EVENT0("uiwidgets", "SkiaUnrefQueue::Drain");
|
||||
std::deque<SkRefCnt*> skia_objects;
|
||||
{
|
||||
|
@ -42,10 +38,8 @@ void SkiaUnrefQueue::Drain(bool performDeferredCleanup) {
|
|||
skia_object->unref();
|
||||
}
|
||||
|
||||
if (performDeferredCleanup) {
|
||||
if (context_ && skia_objects.size() > 0) {
|
||||
context_->performDeferredCleanup(std::chrono::milliseconds(0));
|
||||
}
|
||||
if (context_ && skia_objects.size() > 0) {
|
||||
context_->performDeferredCleanup(std::chrono::milliseconds(0));
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -22,7 +22,7 @@ class SkiaUnrefQueue : public fml::RefCountedThreadSafe<SkiaUnrefQueue> {
|
|||
// to go away), we may need to pre-emptively drain the unref queue. It is the
|
||||
// responsibility of the caller to ensure that no further unrefs are queued
|
||||
// after this call.
|
||||
void Drain(bool performDeferredCleanup = true);
|
||||
void Drain();
|
||||
|
||||
private:
|
||||
const fml::RefPtr<fml::TaskRunner> task_runner_;
|
||||
|
|
|
@ -207,14 +207,18 @@ UIWIDGETS_API(SceneBuilder*) SceneBuilder_constructor() {
|
|||
|
||||
UIWIDGETS_API(void) SceneBuilder_dispose(SceneBuilder* ptr) { ptr->Release(); }
|
||||
|
||||
UIWIDGETS_API(void)
|
||||
UIWIDGETS_API(EngineLayer*)
|
||||
SceneBuilder_pushTransform(SceneBuilder* ptr, const float* matrix4) {
|
||||
ptr->pushTransform(matrix4);
|
||||
const auto layer = ptr->pushTransform(matrix4);
|
||||
layer->AddRef();
|
||||
return layer.get();
|
||||
}
|
||||
|
||||
UIWIDGETS_API(void)
|
||||
UIWIDGETS_API(EngineLayer*)
|
||||
SceneBuilder_pushOffset(SceneBuilder* ptr, float dx, float dy) {
|
||||
ptr->pushOffset(dx, dy);
|
||||
const auto layer = ptr->pushOffset(dx, dy);
|
||||
layer->AddRef();
|
||||
return layer.get();
|
||||
}
|
||||
|
||||
UIWIDGETS_API(void)
|
||||
|
|
|
@ -1,5 +1,7 @@
|
|||
#include "engine_layer.h"
|
||||
|
||||
#include "runtime/mono_api.h"
|
||||
|
||||
namespace uiwidgets {
|
||||
|
||||
EngineLayer::EngineLayer(std::shared_ptr<ContainerLayer> layer)
|
||||
|
@ -7,6 +9,8 @@ EngineLayer::EngineLayer(std::shared_ptr<ContainerLayer> layer)
|
|||
|
||||
EngineLayer::~EngineLayer() = default;
|
||||
|
||||
size_t EngineLayer::GetAllocationSize() { return 3000; };
|
||||
size_t EngineLayer::GetAllocationSize() { return 3000; }
|
||||
|
||||
UIWIDGETS_API(void) EngineLayer_dispose(EngineLayer* ptr) { ptr->Release(); }
|
||||
|
||||
} // namespace uiwidgets
|
||||
|
|
|
@ -10,7 +10,7 @@ CanvasImage::CanvasImage() = default;
|
|||
|
||||
CanvasImage::~CanvasImage() = default;
|
||||
|
||||
const char* CanvasImage::toByteData(int format, EncodeImageCallback callback,
|
||||
const char* CanvasImage::toByteData(int format, RawEncodeImageCallback callback,
|
||||
Mono_Handle callback_handle) {
|
||||
return EncodeImage(this, format, callback, callback_handle);
|
||||
}
|
||||
|
@ -28,4 +28,17 @@ size_t CanvasImage::GetAllocationSize() {
|
|||
}
|
||||
}
|
||||
|
||||
UIWIDGETS_API(void) Image_dispose(CanvasImage* ptr) { ptr->Release(); }
|
||||
|
||||
UIWIDGETS_API(int) Image_width(CanvasImage* ptr) { return ptr->width(); }
|
||||
|
||||
UIWIDGETS_API(int) Image_height(CanvasImage* ptr) { return ptr->height(); }
|
||||
|
||||
UIWIDGETS_API(const char*)
|
||||
Image_toByteData(CanvasImage* ptr, int format,
|
||||
RawEncodeImageCallback encode_image_callback,
|
||||
Mono_Handle callback_handle) {
|
||||
return ptr->toByteData(format, encode_image_callback, callback_handle);
|
||||
}
|
||||
|
||||
} // namespace uiwidgets
|
||||
|
|
|
@ -19,7 +19,7 @@ class CanvasImage final : public fml::RefCountedThreadSafe<CanvasImage> {
|
|||
|
||||
int height() { return image_.get()->height(); }
|
||||
|
||||
const char* toByteData(int format, EncodeImageCallback callback,
|
||||
const char* toByteData(int format, RawEncodeImageCallback callback,
|
||||
Mono_Handle callback_handle);
|
||||
|
||||
void dispose();
|
||||
|
|
|
@ -7,12 +7,385 @@
|
|||
#include "src/codec/SkCodecImageGenerator.h"
|
||||
|
||||
namespace uiwidgets {
|
||||
namespace {
|
||||
|
||||
constexpr double kAspectRatioChangedThreshold = 0.01;
|
||||
|
||||
} // namespace
|
||||
|
||||
ImageDecoder::ImageDecoder(
|
||||
TaskRunners runners,
|
||||
std::shared_ptr<fml::ConcurrentTaskRunner> concurrent_task_runner,
|
||||
fml::WeakPtr<IOManager> io_manager) {}
|
||||
fml::WeakPtr<IOManager> io_manager)
|
||||
: runners_(std::move(runners)),
|
||||
concurrent_task_runner_(std::move(concurrent_task_runner)),
|
||||
io_manager_(std::move(io_manager)),
|
||||
weak_factory_(this) {
|
||||
FML_DCHECK(runners_.IsValid());
|
||||
FML_DCHECK(runners_.GetUITaskRunner()->RunsTasksOnCurrentThread())
|
||||
<< "The image decoder must be created & collected on the UI thread.";
|
||||
}
|
||||
|
||||
ImageDecoder::~ImageDecoder() = default;
|
||||
|
||||
static double AspectRatio(const SkISize& size) {
|
||||
return static_cast<double>(size.width()) / size.height();
|
||||
}
|
||||
|
||||
// Get the updated dimensions of the image. If both dimensions are specified,
|
||||
// use them. If one of them is specified, respect the one that is and use the
|
||||
// aspect ratio to calculate the other. If neither dimension is specified, use
|
||||
// intrinsic dimensions of the image.
|
||||
static SkISize GetResizedDimensions(SkISize current_size,
|
||||
std::optional<uint32_t> target_width,
|
||||
std::optional<uint32_t> target_height) {
|
||||
if (current_size.isEmpty()) {
|
||||
return SkISize::MakeEmpty();
|
||||
}
|
||||
|
||||
if (target_width && target_height) {
|
||||
return SkISize::Make(target_width.value(), target_height.value());
|
||||
}
|
||||
|
||||
const auto aspect_ratio = AspectRatio(current_size);
|
||||
|
||||
if (target_width) {
|
||||
return SkISize::Make(target_width.value(),
|
||||
target_width.value() / aspect_ratio);
|
||||
}
|
||||
|
||||
if (target_height) {
|
||||
return SkISize::Make(target_height.value() * aspect_ratio,
|
||||
target_height.value());
|
||||
}
|
||||
|
||||
return current_size;
|
||||
}
|
||||
|
||||
static sk_sp<SkImage> ResizeRasterImage(sk_sp<SkImage> image,
|
||||
const SkISize& resized_dimensions,
|
||||
const fml::tracing::TraceFlow& flow) {
|
||||
FML_DCHECK(!image->isTextureBacked());
|
||||
|
||||
TRACE_EVENT0("uiwidgets", __FUNCTION__);
|
||||
flow.Step(__FUNCTION__);
|
||||
|
||||
if (resized_dimensions.isEmpty()) {
|
||||
FML_LOG(ERROR) << "Could not resize to empty dimensions.";
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
if (image->dimensions() == resized_dimensions) {
|
||||
return image->makeRasterImage();
|
||||
}
|
||||
|
||||
if (resized_dimensions.width() > image->dimensions().width() ||
|
||||
resized_dimensions.height() > image->dimensions().height()) {
|
||||
FML_LOG(WARNING) << "Image is being upsized from "
|
||||
<< image->dimensions().width() << "x"
|
||||
<< image->dimensions().height() << " to "
|
||||
<< resized_dimensions.width() << "x"
|
||||
<< resized_dimensions.height()
|
||||
<< ". Are cache(Height|Width) used correctly?";
|
||||
// TOOD(48885): consider exiting here, there's no good reason to support
|
||||
// upsampling in a "caching"-optimization context..
|
||||
}
|
||||
|
||||
const bool aspect_ratio_changed =
|
||||
std::abs(AspectRatio(resized_dimensions) -
|
||||
AspectRatio(image->dimensions())) > kAspectRatioChangedThreshold;
|
||||
if (aspect_ratio_changed) {
|
||||
// This is probably a bug. If a user passes dimensions that change the
|
||||
// aspect ratio in a "caching" context that's probably not working as
|
||||
// intended and rather a signal that the API is hard to use.
|
||||
FML_LOG(WARNING)
|
||||
<< "Aspect ratio changes. Are cache(Height|Width) used correctly?";
|
||||
}
|
||||
|
||||
const auto scaled_image_info =
|
||||
image->imageInfo().makeDimensions(resized_dimensions);
|
||||
|
||||
SkBitmap scaled_bitmap;
|
||||
if (!scaled_bitmap.tryAllocPixels(scaled_image_info)) {
|
||||
FML_LOG(ERROR) << "Failed to allocate memory for bitmap of size "
|
||||
<< scaled_image_info.computeMinByteSize() << "B";
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
if (!image->scalePixels(scaled_bitmap.pixmap(), kLow_SkFilterQuality,
|
||||
SkImage::kDisallow_CachingHint)) {
|
||||
FML_LOG(ERROR) << "Could not scale pixels";
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
// Marking this as immutable makes the MakeFromBitmap call share the pixels
|
||||
// instead of copying.
|
||||
scaled_bitmap.setImmutable();
|
||||
|
||||
auto scaled_image = SkImage::MakeFromBitmap(scaled_bitmap);
|
||||
if (!scaled_image) {
|
||||
FML_LOG(ERROR) << "Could not create a scaled image from a scaled bitmap.";
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
return scaled_image;
|
||||
}
|
||||
|
||||
static sk_sp<SkImage> ImageFromDecompressedData(
|
||||
sk_sp<SkData> data, ImageDecoder::ImageInfo info,
|
||||
std::optional<uint32_t> target_width, std::optional<uint32_t> target_height,
|
||||
const fml::tracing::TraceFlow& flow) {
|
||||
TRACE_EVENT0("uiwidgets", __FUNCTION__);
|
||||
flow.Step(__FUNCTION__);
|
||||
auto image = SkImage::MakeRasterData(info.sk_info, data, info.row_bytes);
|
||||
|
||||
if (!image) {
|
||||
FML_LOG(ERROR) << "Could not create image from decompressed bytes.";
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
if (!target_width && !target_height) {
|
||||
// No resizing requested. Just rasterize the image.
|
||||
return image->makeRasterImage();
|
||||
}
|
||||
|
||||
auto resized_dimensions =
|
||||
GetResizedDimensions(image->dimensions(), target_width, target_height);
|
||||
|
||||
return ResizeRasterImage(std::move(image), resized_dimensions, flow);
|
||||
}
|
||||
|
||||
sk_sp<SkImage> ImageFromCompressedData(sk_sp<SkData> data,
|
||||
std::optional<uint32_t> target_width,
|
||||
std::optional<uint32_t> target_height,
|
||||
const fml::tracing::TraceFlow& flow) {
|
||||
TRACE_EVENT0("uiwidgets", __FUNCTION__);
|
||||
flow.Step(__FUNCTION__);
|
||||
|
||||
if (!target_width && !target_height) {
|
||||
// No resizing requested. Just decode & rasterize the image.
|
||||
return SkImage::MakeFromEncoded(data)->makeRasterImage();
|
||||
}
|
||||
|
||||
auto codec = SkCodec::MakeFromData(data);
|
||||
if (codec == nullptr) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
const auto* codec_ptr = codec.get();
|
||||
|
||||
// Note that we cannot read the dimensions from the codec since they don't
|
||||
// respect image orientation provided e.g. in EXIF data.
|
||||
auto image_generator = SkCodecImageGenerator::MakeFromCodec(std::move(codec));
|
||||
const auto& source_dimensions = image_generator->getInfo().dimensions();
|
||||
|
||||
auto resized_dimensions =
|
||||
GetResizedDimensions(source_dimensions, target_width, target_height);
|
||||
|
||||
// No resize needed.
|
||||
if (resized_dimensions == source_dimensions) {
|
||||
return SkImage::MakeFromEncoded(data)->makeRasterImage();
|
||||
}
|
||||
|
||||
auto decode_dimensions = codec_ptr->getScaledDimensions(
|
||||
std::max(static_cast<double>(resized_dimensions.width()) /
|
||||
source_dimensions.width(),
|
||||
static_cast<double>(resized_dimensions.height()) /
|
||||
source_dimensions.height()));
|
||||
|
||||
// If the codec supports efficient sub-pixel decoding, decoded at a resolution
|
||||
// close to the target resolution before resizing.
|
||||
if (decode_dimensions != codec_ptr->dimensions()) {
|
||||
if (source_dimensions != codec_ptr->dimensions()) {
|
||||
decode_dimensions =
|
||||
SkISize::Make(decode_dimensions.height(), decode_dimensions.width());
|
||||
}
|
||||
|
||||
auto scaled_image_info =
|
||||
image_generator->getInfo().makeDimensions(decode_dimensions);
|
||||
|
||||
SkBitmap scaled_bitmap;
|
||||
if (!scaled_bitmap.tryAllocPixels(scaled_image_info)) {
|
||||
FML_LOG(ERROR) << "Failed to allocate memory for bitmap of size "
|
||||
<< scaled_image_info.computeMinByteSize() << "B";
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
const auto& pixmap = scaled_bitmap.pixmap();
|
||||
if (image_generator->getPixels(pixmap.info(), pixmap.writable_addr(),
|
||||
pixmap.rowBytes())) {
|
||||
// Marking this as immutable makes the MakeFromBitmap call share
|
||||
// the pixels instead of copying.
|
||||
scaled_bitmap.setImmutable();
|
||||
|
||||
auto decoded_image = SkImage::MakeFromBitmap(scaled_bitmap);
|
||||
FML_DCHECK(decoded_image);
|
||||
if (!decoded_image) {
|
||||
FML_LOG(ERROR)
|
||||
<< "Could not create a scaled image from a scaled bitmap.";
|
||||
return nullptr;
|
||||
}
|
||||
return ResizeRasterImage(std::move(decoded_image), resized_dimensions,
|
||||
flow);
|
||||
}
|
||||
}
|
||||
|
||||
auto image = SkImage::MakeFromEncoded(data);
|
||||
if (!image) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
return ResizeRasterImage(std::move(image), resized_dimensions, flow);
|
||||
}
|
||||
|
||||
static SkiaGPUObject<SkImage> UploadRasterImage(
|
||||
sk_sp<SkImage> image, fml::WeakPtr<IOManager> io_manager,
|
||||
const fml::tracing::TraceFlow& flow) {
|
||||
TRACE_EVENT0("uiwidgets", __FUNCTION__);
|
||||
flow.Step(__FUNCTION__);
|
||||
|
||||
// Should not already be a texture image because that is the entire point of
|
||||
// the this method.
|
||||
FML_DCHECK(!image->isTextureBacked());
|
||||
|
||||
if (!io_manager->GetResourceContext() || !io_manager->GetSkiaUnrefQueue()) {
|
||||
FML_LOG(ERROR)
|
||||
<< "Could not acquire context of release queue for texture upload.";
|
||||
return {};
|
||||
}
|
||||
|
||||
SkPixmap pixmap;
|
||||
if (!image->peekPixels(&pixmap)) {
|
||||
FML_LOG(ERROR) << "Could not peek pixels of image for texture upload.";
|
||||
return {};
|
||||
}
|
||||
|
||||
SkiaGPUObject<SkImage> result;
|
||||
io_manager->GetIsGpuDisabledSyncSwitch()->Execute(
|
||||
fml::SyncSwitch::Handlers()
|
||||
.SetIfTrue([&result, &pixmap, &image] {
|
||||
SkSafeRef(image.get());
|
||||
sk_sp<SkImage> texture_image = SkImage::MakeFromRaster(
|
||||
pixmap,
|
||||
[](const void* pixels, SkImage::ReleaseContext context) {
|
||||
SkSafeUnref(static_cast<SkImage*>(context));
|
||||
},
|
||||
image.get());
|
||||
result = {texture_image, nullptr};
|
||||
})
|
||||
.SetIfFalse([&result, context = io_manager->GetResourceContext(),
|
||||
&pixmap, queue = io_manager->GetSkiaUnrefQueue()] {
|
||||
TRACE_EVENT0("uiwidgets", "MakeCrossContextImageFromPixmap");
|
||||
sk_sp<SkImage> texture_image = SkImage::MakeCrossContextFromPixmap(
|
||||
context.get(), // context
|
||||
pixmap, // pixmap
|
||||
true, // buildMips,
|
||||
true // limitToMaxTextureSize
|
||||
);
|
||||
if (!texture_image) {
|
||||
FML_LOG(ERROR) << "Could not make x-context image.";
|
||||
result = {};
|
||||
} else {
|
||||
result = {texture_image, queue};
|
||||
}
|
||||
}));
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
void ImageDecoder::Decode(ImageDescriptor descriptor,
|
||||
const ImageResult& callback) {
|
||||
TRACE_EVENT0("uiwidgets", __FUNCTION__);
|
||||
fml::tracing::TraceFlow flow(__FUNCTION__);
|
||||
|
||||
FML_DCHECK(callback);
|
||||
FML_DCHECK(runners_.GetUITaskRunner()->RunsTasksOnCurrentThread());
|
||||
|
||||
// Always service the callback on the UI thread.
|
||||
auto result = [callback, ui_runner = runners_.GetUITaskRunner()](
|
||||
SkiaGPUObject<SkImage> image,
|
||||
fml::tracing::TraceFlow flow) {
|
||||
ui_runner->PostTask(fml::MakeCopyable(
|
||||
[callback, image = std::move(image), flow = std::move(flow)]() mutable {
|
||||
// We are going to terminate the trace flow here. Flows cannot
|
||||
// terminate without a base trace. Add one explicitly.
|
||||
TRACE_EVENT0("uiwidgets", "ImageDecodeCallback");
|
||||
flow.End();
|
||||
callback(std::move(image));
|
||||
}));
|
||||
};
|
||||
|
||||
if (!descriptor.data || descriptor.data->size() == 0) {
|
||||
result({}, std::move(flow));
|
||||
return;
|
||||
}
|
||||
|
||||
concurrent_task_runner_->PostTask(
|
||||
fml::MakeCopyable([descriptor, //
|
||||
io_manager = io_manager_, //
|
||||
io_runner = runners_.GetIOTaskRunner(), //
|
||||
result, //
|
||||
flow = std::move(flow) //
|
||||
]() mutable {
|
||||
// Step 1: Decompress the image.
|
||||
// On Worker.
|
||||
|
||||
auto decompressed =
|
||||
descriptor.decompressed_image_info
|
||||
? ImageFromDecompressedData(
|
||||
std::move(descriptor.data), //
|
||||
descriptor.decompressed_image_info.value(), //
|
||||
descriptor.target_width, //
|
||||
descriptor.target_height, //
|
||||
flow //
|
||||
)
|
||||
: ImageFromCompressedData(std::move(descriptor.data), //
|
||||
descriptor.target_width, //
|
||||
descriptor.target_height, //
|
||||
flow);
|
||||
|
||||
if (!decompressed) {
|
||||
FML_LOG(ERROR) << "Could not decompress image.";
|
||||
result({}, std::move(flow));
|
||||
return;
|
||||
}
|
||||
|
||||
// Step 2: Update the image to the GPU.
|
||||
// On IO Thread.
|
||||
|
||||
io_runner->PostTask(fml::MakeCopyable([io_manager, decompressed, result,
|
||||
flow =
|
||||
std::move(flow)]() mutable {
|
||||
if (!io_manager) {
|
||||
FML_LOG(ERROR) << "Could not acquire IO manager.";
|
||||
return result({}, std::move(flow));
|
||||
}
|
||||
|
||||
// If the IO manager does not have a resource context, the caller
|
||||
// might not have set one or a software backend could be in use.
|
||||
// Either way, just return the image as-is.
|
||||
if (!io_manager->GetResourceContext()) {
|
||||
result({std::move(decompressed), io_manager->GetSkiaUnrefQueue()},
|
||||
std::move(flow));
|
||||
return;
|
||||
}
|
||||
|
||||
auto uploaded =
|
||||
UploadRasterImage(std::move(decompressed), io_manager, flow);
|
||||
|
||||
if (!uploaded.get()) {
|
||||
FML_LOG(ERROR) << "Could not upload image to the GPU.";
|
||||
result({}, std::move(flow));
|
||||
return;
|
||||
}
|
||||
|
||||
// Finally, all done.
|
||||
result(std::move(uploaded), std::move(flow));
|
||||
}));
|
||||
}));
|
||||
}
|
||||
|
||||
fml::WeakPtr<ImageDecoder> ImageDecoder::GetWeakPtr() const {
|
||||
return fml::WeakPtr<ImageDecoder>();
|
||||
return weak_factory_.GetWeakPtr();
|
||||
}
|
||||
} // namespace uiwidgets
|
||||
|
|
|
@ -1,13 +1,28 @@
|
|||
#pragma once
|
||||
|
||||
#include <flutter/fml/concurrent_message_loop.h>
|
||||
#include <flutter/fml/memory/weak_ptr.h>
|
||||
#include <memory>
|
||||
#include <optional>
|
||||
|
||||
#include "common/task_runners.h"
|
||||
#include "flow/skia_gpu_object.h"
|
||||
#include "flutter/fml/concurrent_message_loop.h"
|
||||
#include "flutter/fml/macros.h"
|
||||
#include "flutter/fml/mapping.h"
|
||||
#include "flutter/fml/trace_event.h"
|
||||
#include "include/core/SkData.h"
|
||||
#include "include/core/SkImage.h"
|
||||
#include "include/core/SkImageInfo.h"
|
||||
#include "include/core/SkRefCnt.h"
|
||||
#include "include/core/SkSize.h"
|
||||
#include "lib/ui/io_manager.h"
|
||||
|
||||
namespace uiwidgets {
|
||||
|
||||
// An object that coordinates image decompression and texture upload across
|
||||
// multiple threads/components in the shell. This object must be created,
|
||||
// accessed and collected on the UI thread (typically the engine or its runtime
|
||||
// controller). None of the expensive operations performed by this component
|
||||
// occur in a frame pipeline.
|
||||
class ImageDecoder {
|
||||
public:
|
||||
ImageDecoder(
|
||||
|
@ -15,7 +30,43 @@ class ImageDecoder {
|
|||
std::shared_ptr<fml::ConcurrentTaskRunner> concurrent_task_runner,
|
||||
fml::WeakPtr<IOManager> io_manager);
|
||||
|
||||
~ImageDecoder();
|
||||
|
||||
struct ImageInfo {
|
||||
SkImageInfo sk_info = {};
|
||||
size_t row_bytes = 0;
|
||||
};
|
||||
|
||||
struct ImageDescriptor {
|
||||
sk_sp<SkData> data;
|
||||
std::optional<ImageInfo> decompressed_image_info;
|
||||
std::optional<uint32_t> target_width;
|
||||
std::optional<uint32_t> target_height;
|
||||
};
|
||||
|
||||
using ImageResult = std::function<void(SkiaGPUObject<SkImage>)>;
|
||||
|
||||
// Takes an image descriptor and returns a handle to a texture resident on the
|
||||
// GPU. All image decompression and resizes are done on a worker thread
|
||||
// concurrently. Texture upload is done on the IO thread and the result
|
||||
// returned back on the UI thread. On error, the texture is null but the
|
||||
// callback is guaranteed to return on the UI thread.
|
||||
void Decode(ImageDescriptor descriptor, const ImageResult& result);
|
||||
|
||||
fml::WeakPtr<ImageDecoder> GetWeakPtr() const;
|
||||
|
||||
private:
|
||||
TaskRunners runners_;
|
||||
std::shared_ptr<fml::ConcurrentTaskRunner> concurrent_task_runner_;
|
||||
fml::WeakPtr<IOManager> io_manager_;
|
||||
fml::WeakPtrFactory<ImageDecoder> weak_factory_;
|
||||
|
||||
FML_DISALLOW_COPY_AND_ASSIGN(ImageDecoder);
|
||||
};
|
||||
|
||||
sk_sp<SkImage> ImageFromCompressedData(sk_sp<SkData> data,
|
||||
std::optional<uint32_t> target_width,
|
||||
std::optional<uint32_t> target_height,
|
||||
const fml::tracing::TraceFlow& flow);
|
||||
|
||||
} // namespace uiwidgets
|
||||
|
|
|
@ -16,6 +16,22 @@
|
|||
namespace uiwidgets {
|
||||
namespace {
|
||||
|
||||
void InvokeDataCallback(std::unique_ptr<EncodeImageCallback> callback,
|
||||
sk_sp<SkData> buffer) {
|
||||
std::shared_ptr<MonoState> mono_state = callback->mono_state.lock();
|
||||
if (!mono_state) {
|
||||
callback->callback(callback->callback_handle, nullptr, 0);
|
||||
return;
|
||||
}
|
||||
MonoState::Scope scope(mono_state);
|
||||
if (!buffer) {
|
||||
callback->callback(callback->callback_handle, nullptr, 0);
|
||||
} else {
|
||||
callback->callback(callback->callback_handle, buffer->bytes(),
|
||||
buffer->size());
|
||||
}
|
||||
}
|
||||
|
||||
// This must be kept in sync with the enum in painting.dart
|
||||
enum ImageByteFormat {
|
||||
kRawRGBA,
|
||||
|
@ -161,7 +177,7 @@ sk_sp<SkData> EncodeImage(sk_sp<SkImage> raster_image, ImageByteFormat format) {
|
|||
if (png_image == nullptr) {
|
||||
FML_LOG(ERROR) << "Could not convert raster image to PNG.";
|
||||
return nullptr;
|
||||
};
|
||||
}
|
||||
return png_image;
|
||||
} break;
|
||||
case kRawRGBA: {
|
||||
|
@ -177,19 +193,18 @@ sk_sp<SkData> EncodeImage(sk_sp<SkImage> raster_image, ImageByteFormat format) {
|
|||
}
|
||||
|
||||
void EncodeImageAndInvokeDataCallback(
|
||||
sk_sp<SkImage> image, EncodeImageCallback callback,
|
||||
Mono_Handle callback_handle, ImageByteFormat format,
|
||||
fml::RefPtr<fml::TaskRunner> ui_task_runner,
|
||||
sk_sp<SkImage> image, std::unique_ptr<EncodeImageCallback> callback,
|
||||
ImageByteFormat format, fml::RefPtr<fml::TaskRunner> ui_task_runner,
|
||||
fml::RefPtr<fml::TaskRunner> raster_task_runner,
|
||||
fml::RefPtr<fml::TaskRunner> io_task_runner, GrContext* resource_context,
|
||||
fml::WeakPtr<SnapshotDelegate> snapshot_delegate) {
|
||||
auto callback_task = fml::MakeCopyable(
|
||||
[callback, callback_handle](sk_sp<SkData> encoded) mutable {
|
||||
callback(callback_handle, encoded->bytes(), encoded->size());
|
||||
[callback = std::move(callback)](sk_sp<SkData> encoded) mutable {
|
||||
InvokeDataCallback(std::move(callback), std::move(encoded));
|
||||
});
|
||||
|
||||
auto encode_task = [callback_task = std::move(callback_task), format,
|
||||
ui_task_runner](sk_sp<SkImage> raster_image) {
|
||||
ui_task_runner](sk_sp<SkImage> raster_image) mutable {
|
||||
sk_sp<SkData> encoded = EncodeImage(std::move(raster_image), format);
|
||||
ui_task_runner->PostTask(
|
||||
[callback_task = std::move(callback_task),
|
||||
|
@ -203,26 +218,29 @@ void EncodeImageAndInvokeDataCallback(
|
|||
} // namespace
|
||||
|
||||
const char* EncodeImage(CanvasImage* canvas_image, int format,
|
||||
EncodeImageCallback callback,
|
||||
RawEncodeImageCallback raw_callback,
|
||||
Mono_Handle callback_handle) {
|
||||
if (!canvas_image) return "encode called with non-genuine Image.";
|
||||
|
||||
if (!callback || !callback_handle) return "Callback must be a function.";
|
||||
if (!raw_callback || !callback_handle) return "Callback must be a function.";
|
||||
|
||||
ImageByteFormat image_format = static_cast<ImageByteFormat>(format);
|
||||
|
||||
auto callback = std::make_unique<EncodeImageCallback>(EncodeImageCallback{
|
||||
MonoState::Current()->GetWeakPtr(), raw_callback, callback_handle});
|
||||
|
||||
const auto& task_runners = UIMonoState::Current()->GetTaskRunners();
|
||||
|
||||
task_runners.GetIOTaskRunner()->PostTask(fml::MakeCopyable(
|
||||
[callback, callback_handle, image = canvas_image->image(), image_format,
|
||||
ui_task_runner = task_runners.GetUITaskRunner(),
|
||||
[callback = std::move(callback), image = canvas_image->image(),
|
||||
image_format, ui_task_runner = task_runners.GetUITaskRunner(),
|
||||
raster_task_runner = task_runners.GetRasterTaskRunner(),
|
||||
io_task_runner = task_runners.GetIOTaskRunner(),
|
||||
io_manager = UIMonoState::Current()->GetIOManager(),
|
||||
snapshot_delegate =
|
||||
UIMonoState::Current()->GetSnapshotDelegate()]() mutable {
|
||||
EncodeImageAndInvokeDataCallback(
|
||||
std::move(image), callback, callback_handle, image_format,
|
||||
std::move(image), std::move(callback), image_format,
|
||||
std::move(ui_task_runner), std::move(raster_task_runner),
|
||||
std::move(io_task_runner), io_manager->GetResourceContext().get(),
|
||||
std::move(snapshot_delegate));
|
||||
|
|
|
@ -1,16 +1,22 @@
|
|||
#pragma once
|
||||
|
||||
#include "runtime/mono_api.h"
|
||||
#include "runtime/mono_state.h"
|
||||
|
||||
namespace uiwidgets {
|
||||
|
||||
class CanvasImage;
|
||||
|
||||
typedef void (*EncodeImageCallback)(Mono_Handle callback_handle,
|
||||
const uint8_t* data, size_t length);
|
||||
typedef void (*RawEncodeImageCallback)(Mono_Handle callback_handle,
|
||||
const uint8_t* data, size_t length);
|
||||
|
||||
struct EncodeImageCallback {
|
||||
std::weak_ptr<MonoState> mono_state;
|
||||
RawEncodeImageCallback callback;
|
||||
Mono_Handle callback_handle;
|
||||
};
|
||||
|
||||
const char* EncodeImage(CanvasImage* canvas_image, int format,
|
||||
EncodeImageCallback callback,
|
||||
Mono_Handle callback_handle);
|
||||
RawEncodeImageCallback callback, Mono_Handle callback_handle);
|
||||
|
||||
} // namespace uiwidgets
|
||||
|
|
|
@ -71,8 +71,10 @@ const char* Picture::RasterizeToImage(sk_sp<SkPicture> picture, uint32_t width,
|
|||
auto mono_state = mono_state_weak.lock();
|
||||
if (!mono_state) {
|
||||
// The root isolate could have died in the meantime.
|
||||
raw_image_callback(callback_handle, nullptr);
|
||||
return;
|
||||
}
|
||||
|
||||
MonoState::Scope scope(mono_state);
|
||||
|
||||
if (!raster_image) {
|
||||
|
@ -82,6 +84,7 @@ const char* Picture::RasterizeToImage(sk_sp<SkPicture> picture, uint32_t width,
|
|||
|
||||
auto mono_image = CanvasImage::Create();
|
||||
mono_image->set_image({std::move(raster_image), std::move(unref_queue)});
|
||||
mono_image->AddRef();
|
||||
auto* raw_mono_image = mono_image.get();
|
||||
|
||||
// All done!
|
||||
|
|
|
@ -26,9 +26,12 @@ void PlatformMessageResponseMono::Complete(std::unique_ptr<fml::Mapping> data) {
|
|||
[mono_state_weak = mono_state_weak_, callback = callback_,
|
||||
handle = handle_, data = std::move(data)]() {
|
||||
const std::shared_ptr<MonoState> mono_state = mono_state_weak.lock();
|
||||
if (!mono_state) return;
|
||||
if (!mono_state) {
|
||||
callback(handle, nullptr, 0);
|
||||
return;
|
||||
}
|
||||
|
||||
MonoState::Scope scope(mono_state);
|
||||
|
||||
callback(handle, data->GetMapping(), static_cast<int>(data->GetSize()));
|
||||
}));
|
||||
}
|
||||
|
@ -40,9 +43,12 @@ void PlatformMessageResponseMono::CompleteEmpty() {
|
|||
ui_task_runner_->PostTask([mono_state_weak = mono_state_weak_,
|
||||
callback = callback_, handle = handle_]() {
|
||||
const std::shared_ptr<MonoState> mono_state = mono_state_weak.lock();
|
||||
if (!mono_state) return;
|
||||
if (!mono_state) {
|
||||
callback(handle, nullptr, 0);
|
||||
return;
|
||||
}
|
||||
|
||||
MonoState::Scope scope(mono_state);
|
||||
|
||||
callback(handle, nullptr, 0);
|
||||
});
|
||||
}
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
|
||||
#include "platform_base.h"
|
||||
#include "render_api.h"
|
||||
#include "shell/platform/unity/uiwidgets_system.h"
|
||||
|
||||
// Direct3D 11 implementation of RenderAPI.
|
||||
|
||||
|
@ -17,13 +18,13 @@
|
|||
|
||||
#include <vector>
|
||||
|
||||
#include "TestLoadICU.h"
|
||||
#include "Unity/IUnityGraphicsD3D11.h"
|
||||
#include "flutter/fml/message_loop.h"
|
||||
#include "flutter/fml/synchronization/waitable_event.h"
|
||||
#include "flutter/third_party/txt/src/txt/font_collection.h"
|
||||
#include "flutter/third_party/txt/src/txt/paragraph.h"
|
||||
#include "flutter/third_party/txt/src/txt/paragraph_builder.h"
|
||||
#include "flutter/third_party/txt/src/txt/font_collection.h"
|
||||
|
||||
#include "flutter/fml/synchronization/waitable_event.h"
|
||||
#include "include/core/SkCanvas.h"
|
||||
#include "include/core/SkSurface.h"
|
||||
#include "include/core/SkTextBlob.h"
|
||||
|
@ -35,14 +36,9 @@
|
|||
#include "src/gpu/gl/GrGLDefines.h"
|
||||
#include "third_party/dart/runtime/include/dart_tools_api.h"
|
||||
#include "third_party/icu/SkLoadICU.h"
|
||||
#include "TestLoadICU.h"
|
||||
|
||||
static std::shared_ptr<txt::FontCollection> _fontCollection;
|
||||
void Dart_TimelineEvent(const char* label, int64_t timestamp0,
|
||||
int64_t timestamp1_or_async_id,
|
||||
Dart_Timeline_Event_Type type, intptr_t argument_count,
|
||||
const char** argument_names,
|
||||
const char** argument_values) {}
|
||||
|
||||
|
||||
class RenderAPI_D3D11 : public RenderAPI {
|
||||
public:
|
||||
|
@ -416,26 +412,26 @@ void RenderAPI_D3D11::draw(SkCanvas* canvas) {
|
|||
txt::ParagraphStyle style;
|
||||
txt::FontCollection tf;
|
||||
txt::TextStyle ts;
|
||||
//style.
|
||||
// style.
|
||||
style.font_family = "Arial";
|
||||
ts.font_size = 28;
|
||||
ts.height = 4.0;
|
||||
|
||||
std::unique_ptr<txt::ParagraphBuilder> pb = txt::ParagraphBuilder::CreateTxtBuilder(style, _fontCollection);
|
||||
std::unique_ptr<txt::ParagraphBuilder> pb =
|
||||
txt::ParagraphBuilder::CreateTxtBuilder(style, _fontCollection);
|
||||
ts.font_families.clear();
|
||||
ts.font_families.push_back("Arial");
|
||||
ts.color = SK_ColorBLACK;
|
||||
|
||||
|
||||
|
||||
pb->PushStyle(ts);
|
||||
std::u16string s16 = u"Hello, some text.你好!";
|
||||
pb->AddText(s16);
|
||||
testParagraph = pb->Build();
|
||||
testParagraph->Layout(500);
|
||||
}
|
||||
|
||||
// canvas->drawColor(SK_ColorWHITE);
|
||||
testParagraph->Paint(canvas, 10, 200);
|
||||
|
||||
// canvas->drawColor(SK_ColorWHITE);
|
||||
testParagraph->Paint(canvas, 10, 200);
|
||||
}
|
||||
|
||||
void draw1(SkCanvas* canvas) {
|
||||
|
@ -448,18 +444,12 @@ void draw1(SkCanvas* canvas) {
|
|||
|
||||
double t = 0;
|
||||
|
||||
|
||||
|
||||
void RenderAPI_D3D11::Draw() {
|
||||
// mutex_skia->AcquireSync(0, 5000);
|
||||
|
||||
|
||||
|
||||
SkCanvas* canvas = m_SkSurface->getCanvas();
|
||||
draw(canvas);
|
||||
|
||||
|
||||
|
||||
/* canvas->clear(SK_ColorWHITE);
|
||||
|
||||
t += 1.0 / 60;
|
||||
|
@ -470,8 +460,8 @@ void RenderAPI_D3D11::Draw() {
|
|||
animation_->seekFrameTime(t);
|
||||
animation_->render(canvas);*/
|
||||
|
||||
//SkRect rect = SkRect::MakeLTRB(100, 100, 200, 200);
|
||||
//canvas->drawImageRect(image_, rect, nullptr);
|
||||
// SkRect rect = SkRect::MakeLTRB(100, 100, 200, 200);
|
||||
// canvas->drawImageRect(image_, rect, nullptr);
|
||||
|
||||
// mutex_skia->ReleaseSync(0);
|
||||
canvas->flush();
|
||||
|
@ -491,10 +481,10 @@ void RenderAPI_D3D11::PostDraw() {
|
|||
using namespace txt;
|
||||
|
||||
std::shared_ptr<txt::FontCollection> GetTestFontCollection() {
|
||||
std::shared_ptr<txt::FontCollection> collection =
|
||||
std::make_shared<txt::FontCollection>();
|
||||
collection->SetupDefaultFontManager();
|
||||
return collection;
|
||||
std::shared_ptr<txt::FontCollection> collection =
|
||||
std::make_shared<txt::FontCollection>();
|
||||
collection->SetupDefaultFontManager();
|
||||
return collection;
|
||||
}
|
||||
|
||||
#endif // #if SUPPORT_D3D11
|
||||
|
|
|
@ -5,6 +5,7 @@
|
|||
|
||||
#include "mono_isolate.h"
|
||||
#include "mono_state.h"
|
||||
#include "shell/platform/unity/uiwidgets_system.h"
|
||||
|
||||
namespace uiwidgets {
|
||||
|
||||
|
@ -50,6 +51,7 @@ void Mono_ShutdownIsolate() {
|
|||
|
||||
const Mono_Isolate current = *ptr;
|
||||
FML_DCHECK(current != nullptr);
|
||||
Mono_Shutdown(current);
|
||||
|
||||
*ptr = nullptr;
|
||||
delete current;
|
||||
|
@ -68,10 +70,16 @@ typedef void (*Mono_ThrowExceptionCallback)(const char* exception);
|
|||
|
||||
static Mono_ThrowExceptionCallback Mono_ThrowExceptionCallback_;
|
||||
|
||||
typedef void (*Mono_ShutdownCallback)(Mono_Isolate isolate);
|
||||
|
||||
static Mono_ShutdownCallback Mono_ShutdownCallback_;
|
||||
|
||||
void Mono_ThrowException(const char* exception) {
|
||||
Mono_ThrowExceptionCallback_(exception);
|
||||
}
|
||||
|
||||
void Mono_Shutdown(Mono_Isolate isolate) { Mono_ShutdownCallback_(isolate); }
|
||||
|
||||
int64_t Mono_TimelineGetMicros() {
|
||||
return fml::TimePoint::Now().ToEpochDelta().ToMicroseconds();
|
||||
}
|
||||
|
@ -79,8 +87,10 @@ int64_t Mono_TimelineGetMicros() {
|
|||
void Mono_NotifyIdle(int64_t deadline) {}
|
||||
|
||||
UIWIDGETS_API(void)
|
||||
Mono_hook(Mono_ThrowExceptionCallback throwException) {
|
||||
Mono_hook(Mono_ThrowExceptionCallback throwException,
|
||||
Mono_ShutdownCallback shutdown) {
|
||||
Mono_ThrowExceptionCallback_ = throwException;
|
||||
Mono_ShutdownCallback_ = shutdown;
|
||||
}
|
||||
|
||||
UIWIDGETS_API(Mono_Isolate)
|
||||
|
@ -97,3 +107,51 @@ Isolate_exit() { Mono_ExitIsolate(); }
|
|||
extern "C" int64_t Dart_TimelineGetMicros() {
|
||||
return uiwidgets::Mono_TimelineGetMicros();
|
||||
}
|
||||
|
||||
inline const char* TimelineEventToString(Dart_Timeline_Event_Type type) {
|
||||
switch (type) {
|
||||
case Dart_Timeline_Event_Begin:
|
||||
return "Begin";
|
||||
case Dart_Timeline_Event_End:
|
||||
return "End";
|
||||
case Dart_Timeline_Event_Instant:
|
||||
return "Instant";
|
||||
case Dart_Timeline_Event_Async_Begin:
|
||||
return "AsyncBegin";
|
||||
case Dart_Timeline_Event_Async_End:
|
||||
return "AsyncEnd";
|
||||
case Dart_Timeline_Event_Async_Instant:
|
||||
return "AsyncInstant";
|
||||
case Dart_Timeline_Event_Counter:
|
||||
return "Counter";
|
||||
case Dart_Timeline_Event_Flow_Begin:
|
||||
return "FlowBegin";
|
||||
case Dart_Timeline_Event_Flow_Step:
|
||||
return "FlowStep";
|
||||
case Dart_Timeline_Event_Flow_End:
|
||||
return "FlowEnd";
|
||||
default:
|
||||
return "";
|
||||
}
|
||||
}
|
||||
|
||||
extern "C" void Dart_TimelineEvent(const char* label, int64_t timestamp0,
|
||||
int64_t timestamp1_or_async_id,
|
||||
Dart_Timeline_Event_Type type,
|
||||
intptr_t argument_count,
|
||||
const char** argument_names,
|
||||
const char** argument_values) {
|
||||
static int64_t timestamp_begin = timestamp0;
|
||||
|
||||
if (timestamp1_or_async_id) {
|
||||
uiwidgets::UIWidgetsSystem::GetInstancePtr()->printf_console(
|
||||
"uiwidgets Timeline [Thread:%d] [%lld ms] [%lld] [%s]: %s\n",
|
||||
GetCurrentThreadId(), (timestamp0 - timestamp_begin) / 1000,
|
||||
timestamp1_or_async_id, TimelineEventToString(type), label);
|
||||
} else {
|
||||
uiwidgets::UIWidgetsSystem::GetInstancePtr()->printf_console(
|
||||
"uiwidgets Timeline [Thread:%d] [%d ms] [%s]: %s\n",
|
||||
GetCurrentThreadId(), (timestamp0 - timestamp_begin) / 1000,
|
||||
TimelineEventToString(type), label);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -36,6 +36,7 @@ void* Mono_IsolateData(Mono_Isolate isolate);
|
|||
void* Mono_CurrentIsolateData();
|
||||
|
||||
void Mono_ThrowException(const char* exception);
|
||||
void Mono_Shutdown(Mono_Isolate isolate);
|
||||
int64_t Mono_TimelineGetMicros();
|
||||
|
||||
void Mono_NotifyIdle(int64_t deadline);
|
||||
|
|
|
@ -87,7 +87,7 @@ bool MonoIsolate::Shutdown() {
|
|||
FML_DCHECK(Mono_CurrentIsolate() == nullptr);
|
||||
}
|
||||
|
||||
return true;
|
||||
return true;
|
||||
}
|
||||
|
||||
MonoIsolate::AutoFireClosure::AutoFireClosure(const fml::closure& closure)
|
||||
|
|
|
@ -26,6 +26,7 @@
|
|||
//#include "shell/common/skia_event_tracer_impl.h"
|
||||
//#include "shell/common/switches.h"
|
||||
#include "shell/common/vsync_waiter.h"
|
||||
#include "shell/platform/embedder/embedder_task_runner.h"
|
||||
|
||||
namespace uiwidgets {
|
||||
|
||||
|
@ -303,21 +304,30 @@ Shell::~Shell() {
|
|||
|
||||
fml::TaskRunner::RunNowOrPostTask(
|
||||
task_runners_.GetUITaskRunner(),
|
||||
fml::MakeCopyable([engine = std::move(engine_), &ui_latch]() mutable {
|
||||
engine.reset();
|
||||
ui_latch.Signal();
|
||||
}));
|
||||
fml::MakeCopyable(
|
||||
[engine = std::move(engine_), &ui_latch,
|
||||
task_runner = task_runners_.GetUITaskRunner()]() mutable {
|
||||
engine.reset();
|
||||
ui_latch.Signal();
|
||||
|
||||
// assume it's always EmbedderTaskRunner
|
||||
static_cast<EmbedderTaskRunner*>(task_runner.get())->Terminate();
|
||||
}));
|
||||
ui_latch.Wait();
|
||||
|
||||
fml::TaskRunner::RunNowOrPostTask(
|
||||
task_runners_.GetRasterTaskRunner(),
|
||||
fml::MakeCopyable([rasterizer = std::move(rasterizer_),
|
||||
weak_factory_gpu = std::move(weak_factory_gpu_),
|
||||
&gpu_latch]() mutable {
|
||||
rasterizer.reset();
|
||||
weak_factory_gpu.reset();
|
||||
gpu_latch.Signal();
|
||||
}));
|
||||
fml::MakeCopyable(
|
||||
[rasterizer = std::move(rasterizer_),
|
||||
weak_factory_gpu = std::move(weak_factory_gpu_), &gpu_latch,
|
||||
task_runner = task_runners_.GetRasterTaskRunner()]() mutable {
|
||||
rasterizer.reset();
|
||||
weak_factory_gpu.reset();
|
||||
gpu_latch.Signal();
|
||||
|
||||
// assume it's always EmbedderTaskRunner
|
||||
static_cast<EmbedderTaskRunner*>(task_runner.get())->Terminate();
|
||||
}));
|
||||
gpu_latch.Wait();
|
||||
|
||||
fml::TaskRunner::RunNowOrPostTask(
|
||||
|
@ -599,6 +609,7 @@ void Shell::OnPlatformViewDestroyed() {
|
|||
if (rasterizer) {
|
||||
rasterizer->Teardown();
|
||||
}
|
||||
|
||||
// Step 2: Next, tell the IO thread to complete its remaining work.
|
||||
fml::TaskRunner::RunNowOrPostTask(io_task_runner, io_task);
|
||||
};
|
||||
|
@ -617,11 +628,13 @@ void Shell::OnPlatformViewDestroyed() {
|
|||
task_runners_.GetPlatformTaskRunner();
|
||||
|
||||
auto ui_task = [engine = engine_->GetWeakPtr(),
|
||||
ui_task_runner = task_runners_.GetUITaskRunner(),
|
||||
raster_task_runner = task_runners_.GetRasterTaskRunner(),
|
||||
raster_task, should_post_raster_task, &latch]() {
|
||||
if (engine) {
|
||||
engine->OnOutputSurfaceDestroyed();
|
||||
}
|
||||
|
||||
// Step 1: Next, tell the raster thread that its rasterizer should suspend
|
||||
// access to the underlying surface.
|
||||
if (should_post_raster_task) {
|
||||
|
@ -637,6 +650,7 @@ void Shell::OnPlatformViewDestroyed() {
|
|||
// surface is about to go away.
|
||||
fml::TaskRunner::RunNowOrPostTask(task_runners_.GetUITaskRunner(), ui_task);
|
||||
latch.Wait();
|
||||
|
||||
if (!should_post_raster_task) {
|
||||
// See comment on should_post_raster_task, in this case the raster_task
|
||||
// wasn't executed, and we just run it here as the platform thread
|
||||
|
|
|
@ -37,6 +37,10 @@ void EmbedderTaskRunner::PostTaskForTime(const fml::closure& task,
|
|||
{
|
||||
// Release the lock before the jump via the dispatch table.
|
||||
std::scoped_lock lock(tasks_mutex_);
|
||||
if (terminated_) {
|
||||
return;
|
||||
}
|
||||
|
||||
baton = ++last_baton_;
|
||||
pending_tasks_[baton] = task;
|
||||
}
|
||||
|
@ -74,6 +78,18 @@ bool EmbedderTaskRunner::PostTask(uint64_t baton) {
|
|||
return true;
|
||||
}
|
||||
|
||||
void EmbedderTaskRunner::Terminate() {
|
||||
FML_DCHECK(RunsTasksOnCurrentThread());
|
||||
|
||||
std::unordered_map<uint64_t, fml::closure> local_tasks;
|
||||
|
||||
{
|
||||
std::scoped_lock lock(tasks_mutex_);
|
||||
terminated_ = true;
|
||||
pending_tasks_.swap(local_tasks);
|
||||
}
|
||||
}
|
||||
|
||||
// |fml::TaskRunner|
|
||||
fml::TaskQueueId EmbedderTaskRunner::GetTaskQueueId() {
|
||||
return placeholder_id_;
|
||||
|
|
|
@ -26,6 +26,8 @@ class EmbedderTaskRunner final : public fml::TaskRunner {
|
|||
|
||||
bool PostTask(uint64_t baton);
|
||||
|
||||
void Terminate();
|
||||
|
||||
private:
|
||||
const size_t embedder_identifier_;
|
||||
DispatchTable dispatch_table_;
|
||||
|
@ -33,6 +35,7 @@ class EmbedderTaskRunner final : public fml::TaskRunner {
|
|||
uint64_t last_baton_;
|
||||
std::unordered_map<uint64_t, fml::closure> pending_tasks_;
|
||||
fml::TaskQueueId placeholder_id_;
|
||||
bool terminated_ = false;
|
||||
|
||||
void PostTask(const fml::closure& task) override;
|
||||
|
||||
|
|
|
@ -3,6 +3,7 @@
|
|||
#include <flutter/fml/closure.h>
|
||||
|
||||
#include <chrono>
|
||||
#include <cstdarg>
|
||||
#include <set>
|
||||
#include <unordered_map>
|
||||
|
||||
|
@ -26,6 +27,12 @@ class UIWidgetsSystem {
|
|||
void UnregisterPanel(UIWidgetsPanel* panel);
|
||||
|
||||
void PostTaskToGfxWorker(const fml::closure& task);
|
||||
void printf_console(const char* log, ...) {
|
||||
va_list vl;
|
||||
va_start(vl, log);
|
||||
unity_uiwidgets_->printf_consolev(log, vl);
|
||||
va_end(vl);
|
||||
}
|
||||
|
||||
void BindUnityInterfaces(IUnityInterfaces* unity_interfaces);
|
||||
void UnBindUnityInterfaces();
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
#pragma once
|
||||
|
||||
#include "IUnityInterface.h"
|
||||
#include "IUnityGraphics.h"
|
||||
#include "IUnityInterface.h"
|
||||
|
||||
namespace UnityUIWidgets {
|
||||
typedef void (*VoidCallback)();
|
||||
|
@ -16,6 +16,7 @@ UNITY_DECLARE_INTERFACE(IUnityUIWidgets) {
|
|||
virtual void SetWakeUpCallback(VoidCallback callback) = 0;
|
||||
virtual void IssuePluginEventAndData(UnityRenderingEventAndData callback,
|
||||
int eventId, void* data) = 0;
|
||||
virtual void printf_consolev(const char* log, va_list alist) = 0;
|
||||
};
|
||||
} // namespace UnityUIWidgets
|
||||
|
||||
|
|
Загрузка…
Ссылка в новой задаче