diff --git a/binding/HarfBuzzSharp/nuget/build/wasm/HarfBuzzSharp.props b/binding/HarfBuzzSharp/nuget/build/wasm/HarfBuzzSharp.props index 013ef410d2c..7f90974e527 100644 --- a/binding/HarfBuzzSharp/nuget/build/wasm/HarfBuzzSharp.props +++ b/binding/HarfBuzzSharp/nuget/build/wasm/HarfBuzzSharp.props @@ -12,7 +12,7 @@ - + \ No newline at end of file diff --git a/binding/SkiaSharp/nuget/build/wasm/SkiaSharp.targets b/binding/SkiaSharp/nuget/build/wasm/SkiaSharp.targets index b4715389656..4583939e167 100644 --- a/binding/SkiaSharp/nuget/build/wasm/SkiaSharp.targets +++ b/binding/SkiaSharp/nuget/build/wasm/SkiaSharp.targets @@ -1,8 +1,21 @@ - + + + + - + + + + + + + + + + + \ No newline at end of file diff --git a/native/wasm/build.cake b/native/wasm/build.cake index 2c54effcc51..e480f9a19a6 100644 --- a/native/wasm/build.cake +++ b/native/wasm/build.cake @@ -24,6 +24,7 @@ Task("libSkiaSharp") .Does(() => { bool hasSimdEnabled = EMSCRIPTEN_FEATURES.Contains("simd"); + bool hasThreadingEnabled = EMSCRIPTEN_FEATURES.Contains("mt"); GnNinja($"wasm", "SkiaSharp", $"target_os='linux' " + @@ -60,9 +61,11 @@ Task("libSkiaSharp") $" '-DSKIA_C_DLL', '-DXML_POOR_ENTROPY', " + $" {(!hasSimdEnabled ? "'-DSKNX_NO_SIMD', " : "")} '-DSK_DISABLE_AAA', '-DGR_GL_CHECK_ALLOC_WITH_GET_ERROR=0', " + $" '-s', 'WARN_UNALIGNED=1' " + // '-s', 'USE_WEBGL2=1' (experimental) + $" { (hasSimdEnabled ? ", '-msimd128'" : "") } " + + $" { (hasThreadingEnabled ? ", '-pthread'" : "") } " + $"] " + // SIMD support is based on https://github.com/google/skia/blob/1f193df9b393d50da39570dab77a0bb5d28ec8ef/modules/canvaskit/compile.sh#L57 - $"extra_cflags_cc=[ '-frtti' { (hasSimdEnabled ? ", '-msimd128'" : "") } ] " + + $"extra_cflags_cc=[ '-frtti' { (hasSimdEnabled ? ", '-msimd128'" : "") } { (hasThreadingEnabled ? ", '-pthread'" : "") } ] " + COMPILERS + ADDITIONAL_GN_ARGS); @@ -106,17 +109,24 @@ Task("libHarfBuzzSharp") .WithCriteria(IsRunningOnLinux()) .Does(() => { + bool hasSimdEnabled = EMSCRIPTEN_FEATURES.Contains("simd"); + bool hasThreadingEnabled = EMSCRIPTEN_FEATURES.Contains("mt"); + GnNinja($"wasm", "HarfBuzzSharp", $"target_os='linux' " + $"target_cpu='wasm' " + $"is_static_skiasharp=true " + $"visibility_hidden=false " + + $"extra_cflags=[ { (hasSimdEnabled ? "'-msimd128', " : "") } { (hasThreadingEnabled ? "'-pthread'" : "") } ] " + + $"extra_cflags_cc=[ '-frtti' { (hasSimdEnabled ? ", '-msimd128'" : "") } { (hasThreadingEnabled ? ", '-pthread'" : "") } ] " + COMPILERS + ADDITIONAL_GN_ARGS); var outDir = OUTPUT_PATH.Combine($"wasm"); if (!string.IsNullOrEmpty(EMSCRIPTEN_VERSION)) outDir = outDir.Combine("libHarfBuzzSharp.a").Combine(EMSCRIPTEN_VERSION); + if (EMSCRIPTEN_FEATURES.Length != 0) + outDir = outDir.Combine(string.Join(",", EMSCRIPTEN_FEATURES)); EnsureDirectoryExists(outDir); var so = SKIA_PATH.CombineWithFilePath($"out/wasm/libHarfBuzzSharp.a"); CopyFileToDirectory(so, outDir); diff --git a/scripts/azure-pipelines-variables.yml b/scripts/azure-pipelines-variables.yml index e9a92cc5ba4..5f25da6d4cc 100644 --- a/scripts/azure-pipelines-variables.yml +++ b/scripts/azure-pipelines-variables.yml @@ -15,7 +15,7 @@ variables: MONO_VERSION_LINUX: 'stable-focal/snapshots/6.12.0.182' XCODE_VERSION: 13.2.1 VISUAL_STUDIO_VERSION: '17/pre' - DOTNET_VERSION_PREVIEW: '6.0.401' + DOTNET_VERSION_PREVIEW: '6.0.402' DOTNET_WORKLOAD_SOURCE: 'https://maui.blob.core.windows.net/metadata/rollbacks/6.0.540.json' CONFIGURATION: 'Release' DOTNET_SKIP_FIRST_TIME_EXPERIENCE: true diff --git a/scripts/azure-templates-stages.yml b/scripts/azure-templates-stages.yml index 277b4ad1645..42937cd2613 100644 --- a/scripts/azure-templates-stages.yml +++ b/scripts/azure-templates-stages.yml @@ -388,6 +388,14 @@ stages: displayName: '3.1.12_SIMD' version: 3.1.12 features: simd + - 3.1.12: + displayName: '3.1.12_Threading' + version: 3.1.12 + features: mt + - 3.1.12: + displayName: '3.1.12_Threading_SIMD' + version: 3.1.12 + features: mt,simd - ${{ if ne(parameters.buildPipelineType, 'tests') }}: - stage: managed diff --git a/source/SkiaSharp.Views.Uno/SkiaSharp.Views.Uno.Wasm/SKSwapChainPanel.Wasm.cs b/source/SkiaSharp.Views.Uno/SkiaSharp.Views.Uno.Wasm/SKSwapChainPanel.Wasm.cs index a454f0962ce..ea5fda39caf 100644 --- a/source/SkiaSharp.Views.Uno/SkiaSharp.Views.Uno.Wasm/SKSwapChainPanel.Wasm.cs +++ b/source/SkiaSharp.Views.Uno/SkiaSharp.Views.Uno.Wasm/SKSwapChainPanel.Wasm.cs @@ -21,7 +21,7 @@ public partial class SKSwapChainPanel : FrameworkElement #if HAS_UNO_WINUI const string SKSwapChainPanelTypeFullName = "SkiaSharp.Views.Windows." + nameof(SKSwapChainPanel); #else - const string SKSwapChainPanelTypeFullName = "SkiaSharp.Views.UWP" + nameof(SKSwapChainPanel); + const string SKSwapChainPanelTypeFullName = "SkiaSharp.Views.UWP." + nameof(SKSwapChainPanel); #endif private const int ResourceCacheBytes = 256 * 1024 * 1024; // 256 MB diff --git a/source/SkiaSharp.Views.Uno/SkiaSharp.Views.Uno.Wasm/SKXamlCanvas.Wasm.cs b/source/SkiaSharp.Views.Uno/SkiaSharp.Views.Uno.Wasm/SKXamlCanvas.Wasm.cs index 7d596cf1993..1141534c526 100644 --- a/source/SkiaSharp.Views.Uno/SkiaSharp.Views.Uno.Wasm/SKXamlCanvas.Wasm.cs +++ b/source/SkiaSharp.Views.Uno/SkiaSharp.Views.Uno.Wasm/SKXamlCanvas.Wasm.cs @@ -90,6 +90,8 @@ private SKImageInfo CreateBitmap(out SKSizeI unscaledSize, out float dpi) private void FreeBitmap() { + WebAssemblyRuntime.InvokeJS(SKXamlCanvasFullTypeName + $".clearCanvas(\"{this.GetHtmlId()}\");"); + if (pixels != null) { pixelsHandle.Free(); diff --git a/source/SkiaSharp.Views.Uno/SkiaSharp.Views.Uno.Wasm/WasmScripts/SkiaSharp.Views.Uno.Wasm.js b/source/SkiaSharp.Views.Uno/SkiaSharp.Views.Uno.Wasm/WasmScripts/SkiaSharp.Views.Uno.Wasm.js index de4ad9f683a..cdf2c016a97 100644 --- a/source/SkiaSharp.Views.Uno/SkiaSharp.Views.Uno.Wasm/WasmScripts/SkiaSharp.Views.Uno.Wasm.js +++ b/source/SkiaSharp.Views.Uno/SkiaSharp.Views.Uno.Wasm/WasmScripts/SkiaSharp.Views.Uno.Wasm.js @@ -6,6 +6,8 @@ (function (UWP) { class SKXamlCanvas { + static buffers = []; + static invalidateCanvas(pData, canvasId, width, height) { var htmlCanvas = document.getElementById(canvasId); htmlCanvas.width = width; @@ -15,12 +17,37 @@ if (!ctx) return false; - var buffer = new Uint8ClampedArray(Module.HEAPU8.buffer, pData, width * height * 4); - var imageData = new ImageData(buffer, width, height); - ctx.putImageData(imageData, 0, 0); + var byteLength = width * height * 4; + + if (isSecureContext) { + // In a secure context (e.g. with threading enabled), creating a view + // from Module.HEAPU8.buffer is not supported, so we're making an + // explicit copy of the wasm memory. + var buffer = SKXamlCanvas.buffers[canvasId]; + + if (!buffer || buffer.length != byteLength) { + SKXamlCanvas.buffers[canvasId] = buffer = new Uint8ClampedArray(new ArrayBuffer(byteLength)); + } + + var slice = Module.HEAPU8.buffer.slice(pData, pData + byteLength); + buffer.set(new Uint8ClampedArray(slice), 0); + var imageData = new ImageData(buffer, width, height); + ctx.putImageData(imageData, 0, 0); + } + else { + var buffer = new Uint8ClampedArray(Module.HEAPU8.buffer, byteLength); + var imageData = new ImageData(buffer, width, height); + ctx.putImageData(imageData, 0, 0); + } return true; } + + static clearCanvas(canvasId) { + if (isSecureContext) { + delete SKXamlCanvas.buffers[canvasId]; + } + } } class SKSwapChainPanel { @@ -113,14 +140,22 @@ // make current GL.makeContextCurrent(ctx); + // Starting from .NET 7 the GLctx is defined in an inaccessible scope + // when the current GL context changes. We need to pick it up from the + // GL.currentContext instead. + let currentGLctx = GL.currentContext && GL.currentContext.GLctx; + + if (!currentGLctx) + throw `Failed to get current WebGL context`; + // read values this.canvas = canvas; var info = { ctx: ctx, - fbo: GLctx.getParameter(GLctx.FRAMEBUFFER_BINDING), - stencil: GLctx.getParameter(GLctx.STENCIL_BITS), - sample: 0, // TODO: GLctx.getParameter(GLctx.SAMPLES) - depth: GLctx.getParameter(GLctx.DEPTH_BITS), + fbo: currentGLctx.getParameter(currentGLctx.FRAMEBUFFER_BINDING), + stencil: currentGLctx.getParameter(currentGLctx.STENCIL_BITS), + sample: 0, // TODO: currentGLctx.getParameter(GLctx.SAMPLES) + depth: currentGLctx.getParameter(currentGLctx.DEPTH_BITS), }; // format as array for nicer parsing