Support SkiaSharp Blazor views on Server and Hybrid hosts (#1194) - #4363
Support SkiaSharp Blazor views on Server and Hybrid hosts (#1194)#4363mattleibow wants to merge 12 commits into
Conversation
Adds documentation/dev/blazor-server-hybrid-rendering.md describing the architecture for supporting SkiaSharp Blazor views across all host models (WebAssembly, Server, Hybrid, static SSR and Interactive Auto). Links it from AGENTS.md Further Reading and the dev docs index. Design for #1194. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Adds a bridged rendering path so SKCanvasView/SKGLView work outside Blazor WebAssembly. The components now detect their host at runtime (RendererInfo, net9+) and either draw directly in the browser (WebAssembly, unchanged) or render on the .NET side and stream frames to the browser (Server, Hybrid, static SSR). New pieces: - SKBlazorTransferFormat, SKBlazorOptions, AddSkiaSharpViewsBlazor (public API) - Internal SKBlazorHost (host detection + transfer-format resolution), SKBlazorFrameProducer (pure, testable Png/Jpeg/raw payload producer), SKHtmlCanvasBridgeInterop (IJSObjectReference bridge), SKBlazorBridgedRenderer (backpressured render loop, identical-frame suppression) - wwwroot/SKCanvasPresenter (shared, pure-browser paint primitives: Canvas2D + WebGL) and wwwroot/SKHtmlCanvasBridge (host-agnostic present module) net6/net8 remain WebAssembly-only and behaviourally unchanged. All new API is additive. Design: documentation/dev/blazor-server-hybrid-rendering.md Contributes to #1194 Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Adds SkiaSharp.Tests.Blazor with 22 tests covering the frame producer (PNG/JPEG signatures, raw RGBA layout, transparency, quality clamping), host detection + transfer-format resolution precedence, and the DI options registration. Uses InternalsVisibleTo to reach the internal helpers. Contributes to #1194 Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
…mple The bridged renderer marked itself initialized only after the JS initialize() call returned, but that call reports the initial size/DPI synchronously and the OnMetricsChanged callback can race back before initialization completes, which dropped the first (and only) render. Mark initialized before invoking the JS initializer. Adds samples/Basic/BlazorServer: a runnable Interactive Server web app that renders SKCanvasView on the server and streams frames to the browser. Verified end to end (canvas paints at cssSize x DPR and animates via the render loop). Contributes to #1194 Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Adds an implementation-status note to the design doc: Blazor Server rendering is implemented and verified end to end; lists the delivered pieces and the remaining follow-ups (Hybrid sample + device test, full test web app, packaging pack-verification, static-SSR poster, WASM presenter fold-in, API docs). Contributes to #1194 Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
📦 Try the packages from this PRWarning Do not run these scripts without first reviewing the code in this PR. Step 1 — Download the packages bash / macOS / Linux: curl -fsSL https://raw.githubusercontent.com/mono/SkiaSharp/main/scripts/get-skiasharp-pr.sh | bash -s -- 4363PowerShell / Windows: iex "& { $(irm https://raw.githubusercontent.com/mono/SkiaSharp/main/scripts/get-skiasharp-pr.ps1) } 4363"Step 2 — Add the local NuGet source dotnet nuget add source ~/.skiasharp/hives/pr-4363/packages --name skiasharp-pr-4363More options
Or download manually from Azure Pipelines — look for the Remove the source when you're done: dotnet nuget remove source skiasharp-pr-4363 |
Rewrites documentation/dev/blazor-server-hybrid-rendering.md as a normative, host-independent specification of the whole system rather than a delta of what changed. WebAssembly (Direct) behaviour is now specified in the absolute alongside the Server/Hybrid/Static (Bridged) behaviour, covering the public surface, host detection, sizing/DPI, both rendering strategies, the render loop, transfer formats, the JavaScript modules, Auto and static SSR, framework support and packaging. Adds a §14 conformance map linking every rule to the code that implements it. Contributes to #1194 Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
|
📖 Documentation Preview The documentation for this PR has been deployed and is available at: 🔗 View Staging Site This preview will be updated automatically when you push new commits to this PR. This comment is automatically updated by the documentation staging workflow. |
…cher starvation A continuous render loop (EnableRenderLoop) whose frames are byte-identical hit the identical-frame suppression and returned before the awaited present, so the loop iterated with no yield point. Over a momentarily static scene this spun synchronously and starved the Blazor circuit dispatcher, preventing input, metrics and even disposal from being processed (so the loop could not stop). Yield to the host dispatcher every iteration so the loop cooperates and stays cancellable regardless of frame suppression. Spec §7.5 updated to require this. Found by an independent spec-conformance review. Contributes to #1194 Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
…le 1:1 Replaces the minimal Server sample with a faithful copy of the Blazor WebAssembly sample: the same MudBlazor layout, theme, and CPU/GPU/Drawing pages, scoped CSS, font and helpers are byte-identical (verified via diff). Only the host wiring differs for the WebAssembly->Server switch: Web SDK csproj (no WebAssembly packages/props), Program.cs (AddInteractiveServerComponents + MudServices + app-root HttpClient), App.razor host document + Routes.razor router, and _Imports without the WebAssembly.Http using. Verified end to end in a browser: all three pages render identically via the bridged path, including the SKGLView SkSL shader page on a WebGL-backed canvas. Contributes to #1194 Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
…tection Adds samples/Basic/BlazorHybrid: a .NET MAUI Blazor Hybrid app that mirrors the WebAssembly and Server samples 1:1 (same MudBlazor layout, theme and CPU/GPU/ Drawing pages, byte-identical shared files) but runs as a mobile app via BlazorWebView. Scaffolded from the maui-blazor template so the platform projects are standard; only the host wiring differs. The font is a raw app-package asset loaded via FileSystem.OpenAppPackageFileAsync (no HttpClient) - the only line that differs from the shared Home.razor. Program.DefaultPage is a partial that merges with the iOS/MacCatalyst entry points. Builds for Android and MacCatalyst (ValidateXcodeVersion=false); MacCatalyst app launches and runs. Hardens SKBlazorHost.Resolve so any interactive non-browser host that does not report a recognized RendererInfo.Name is treated as Hybrid (bridged) - a native WebView - rather than Unknown, so Blazor Hybrid renders regardless of the exact name reported. Updates the spec (section 4) and the unit test. Contributes to #1194 Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Switches the Hybrid sample's start-page selection from a Program partial to App.DefaultPage, matching the idiomatic MAUI approach used by the native samples/Basic/Maui sample. Removes the Program.DefaultPage partial and reverts the iOS/MacCatalyst platform Program classes to non-partial. The shared MainLayout.razor now reads App.DefaultPage (the one line that differs from the WebAssembly/Server samples). Contributes to #1194 Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
…read Blazor Hybrid was janky because it defaulted to raw-pixel transfer (Put), which marshals a full-resolution buffer (~10 MB/frame) across the WebView bridge, and because frame production ran on the dispatcher - which is the UI thread on Hybrid (on Server the dispatcher is a threadpool thread, so it was smooth). - Default every bridged host to JPEG (small payload). Put/Png remain opt-in. - Produce the transfer payload on a background thread (Task.Run); backpressure keeps the frame buffer stable while encoding. Contributes to #1194 Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
… from internals Encapsulate the per-host 'if bridged else wasm' branching that lived in SKCanvasView/SKGLView behind an internal IRenderer abstraction with three implementations: - CanvasDirectRenderer / GLDirectRenderer (Blazor WebAssembly, browser-only) - BridgedRenderer (Blazor Server / Hybrid / static SSR) The components now hold a single IRenderer, pick the right one in OnAfterRenderAsync, and delegate Invalidate/Dispose/Dpi and parameter forwarding through the interface. A unified RenderPaintHandler delegate lets the bridged renderer own the SKGLView placeholder GL render-target. Rename internals to drop the redundant SKBlazor prefix (SKBlazorHost -> Host, SKBlazorHostKind -> HostKind, SKBlazorFrameProducer -> FrameProducer, SKBlazorBridgedRenderer -> BridgedRenderer) and use file-scoped namespaces for all new/updated internal and public-API files. Public API names keep the SKBlazor prefix. Tests and the conformance map are updated to match. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
|
Hey looks great!
|
Summary
Extends
SkiaSharp.Views.Blazorso the sameSKCanvasView/SKGLViewthat run in Blazor WebAssembly also work under Blazor Server, Blazor Hybrid (BlazorWebView), static SSR, and Interactive Auto — closing the long-standing ask in #1194. Existing WebAssembly consumers are unaffected (API and behaviour identical; all new API is additive).The components detect their host at runtime (
RendererInfo, .NET 9+) and pick one of two present strategies:<canvas>.Server and Hybrid both marshal through
IJSRuntime(SignalR vs in-process WebView), so one code path covers both.Verified end-to-end
The Blazor Server sample was driven in a real browser (Playwright): all three pages render identically to the WebAssembly sample — CPU (
SKCanvasView), GPU (SKGLViewSkSL shader on a WebGL-backed canvas), and Drawing (pointer/wheel input) — with the full MudBlazor chrome, painting atcssSize × DPR. 22 unit tests pass; the library builds for net6.0/net9.0/net10.0.What's included
SKBlazorTransferFormat(Png/Jpeg/Put),SKBlazorOptions,AddSkiaSharpViewsBlazor(...), per-controlTransferFormat/Quality. Configurable globally and per control, host defaults (Hybrid→Put, Server→Jpeg), orthogonal to the host so every path is testable from one host.IJSObjectReferencebridge interop, and a backpressured bridged renderer (matches WASMEnableRenderLoop/Invalidate, awaits the previous frame, suppresses byte-identical frames, yields each loop iteration).SKCanvasPresenter(Canvas2D + WebGL) and a thin host-agnosticSKHtmlCanvasBridge. The bridged path never imports the emscripten-coupledSKHtmlCanvas.js.SKGLViewkeeps a WebGL-backed canvas under a bridged host (CPU raster server-side, presented viatexImage2D; real server GPU is a future extension).tests/SkiaSharp.Tests.Blazor(frame producer, host detection, DI).samples/Basic/BlazorServer— a 1:1 copy of the WebAssembly sample, Server-hosted (shared pages/layout/styles are byte-identical; only the host wiring differs).documentation/dev/blazor-server-hybrid-rendering.md— a normative, host-independent behaviour specification (WebAssembly + Server/Hybrid/Static/Auto) with a §14 conformance map, linked fromAGENTS.mdand the dev docs index.Framework support
Server/Hybrid/Auto require .NET 9.0+ (uses the official
RendererInfoAPI). net6.0/net8.0 remain WebAssembly-only and behaviourally unchanged.Follow-ups
Tracked in #4367 — including partial (damage-based) frame transfer for the bridged path, a Blazor Hybrid sample + device test, the full test web-app pages + CI, native-asset packaging pack-verification, a static-SSR poster frame, folding the WASM paint onto the shared presenter, and API XML docs.
Closes #1194
Co-authored-by: Copilot App 223556219+Copilot@users.noreply.github.com