feat(skia): GPU-accelerated RenderTargetBitmap by ramezgerges · Pull Request #23637 · unoplatform/uno · GitHub
Skip to content

feat(skia): GPU-accelerated RenderTargetBitmap#23637

Merged
ramezgerges merged 8 commits into
unoplatform:masterfrom
ramezgerges:rtb_gpu_rendering
Jul 4, 2026
Merged

feat(skia): GPU-accelerated RenderTargetBitmap#23637
ramezgerges merged 8 commits into
unoplatform:masterfrom
ramezgerges:rtb_gpu_rendering

Conversation

@ramezgerges

@ramezgerges ramezgerges commented Jul 3, 2026

Copy link
Copy Markdown
Contributor

GitHub Issue: closes #23636, https://github.com/unoplatform/kahua-private/issues/505

PR Type:

✨ Feature

What changed? 🚀

RenderTargetBitmap on Skia targets previously always rasterized in software (SKSurface.Create(info) with Compositor.IsSoftwareRenderer = true), even when the window renders through a hardware GRContext.

With this PR, RenderTargetBitmap renders through the GPU when available, in a fully platform-neutral way:

  • The visual tree is recorded into an SKPicture on the UI thread (mirroring the regular rendering pipeline), then replayed into a GRContext-backed SKSurface during the CompositionTarget's next render pass, and the pixels are read back into the caller's buffer.
  • CompositionTarget gains an internal TryExecuteOnNextRenderAsync(Action<GRContext>): it queues the action, invalidates the host so a render pass happens promptly, and runs the action inside Draw — i.e. on the rendering thread while the native GPU context is current. Since every Skia backend funnels through OnNativePlatformFrameRequestedDraw, no per-platform code is needed.
  • The GRContext is resolved from the draw canvas itself (SKCanvas.Context). A raster canvas (software rendering) yields no context, in which case pending jobs fail fast and RenderTargetBitmap falls back to the previous software path. Pending jobs are also failed when the window is unregistered, so awaiters never hang on a closing window.
  • RenderAsync now completes asynchronously on Skia (one render pass later) instead of synchronously inside the call; on the browser this also avoids blocking the single thread.

Note that GPU rasterization can produce slightly different (equally valid) anti-aliasing than the software rasterizer, so RenderTargetBitmap output is no longer bit-identical to the raster path on GPU-rendered windows — it instead matches what is actually presented on screen.

Tests:

  • New runtime test guarding against vertically-flipped output on GPU backends whose window surfaces have a bottom-left origin (OpenGL).
  • When_CornerRadius_AntiAliasing no longer compares against a software-rendered reference asset (no RMSE threshold separates valid GPU-vs-CPU anti-aliasing variance, measured 0.0325, from a simulated non-anti-aliased render, 0.0349). It now asserts the invariant directly: a meaningful fraction of the pixels in a ring around the circle's edge must blend between the two colors (~34% for the CPU rasterizer, 0% for a jagged render), making it rasterizer-independent.

Validation: Given_RenderTargetBitmap and Given_Border runtime suites were run on Skia Desktop (Win32) against both the Vulkan renderer (via SwiftShader, all renders through the GPU path) and the software renderer (clean fallbacks) — all green. CI agents without GPUs take the software fallback, so screenshot-comparison runs should be unaffected there.

PR Checklist ✅

🤖 Generated with Claude Code

ramezgerges and others added 2 commits July 3, 2026 11:42
RenderTargetBitmap now records the visual tree into an SKPicture on
the UI thread (mirroring the regular rendering pipeline) and replays
it into a GRContext-backed surface during the CompositionTarget's next
render pass, reading the pixels back into the caller's buffer. The
GRContext is resolved from the draw canvas (SKCanvas.Context), so the
mechanism is platform-neutral and applies to every Skia backend; when
the target renders in software (raster canvas), pending render jobs
fail fast and the previous raster path is used as fallback.

Opting out is possible via the new
FeatureConfiguration.Rendering.UseGpuRenderTargetBitmap flag, since
GPU rasterization can produce slightly different anti-aliasing than
the software rasterizer.

RenderAsync now completes asynchronously on Skia (one render pass
later) instead of synchronously inside the call.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Adds an orientation test guarding against vertically-flipped output on
GPU backends with bottom-left-origin surfaces, and pins
When_CornerRadius_AntiAliasing to software RenderTargetBitmap since
its reference asset was rendered by the software rasterizer and GPU
anti-aliasing differs slightly along the rounded edge.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Copilot AI review requested due to automatic review settings July 3, 2026 15:43
@github-actions github-actions Bot added the area/skia ✏️ Categorizes an issue or PR as relevant to Skia label Jul 3, 2026

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds GPU-accelerated RenderTargetBitmap rendering for Skia targets by replaying a recorded SKPicture into a GRContext-backed surface on the next render pass, with a software fallback and a FeatureConfiguration opt-out.

Changes:

  • Implement GPU-backed RenderTargetBitmap rendering on Skia (record on UI thread, replay/readback on render thread) with software fallback.
  • Add CompositionTarget.TryExecuteOnNextRenderAsync(Action<GRContext>) to run work inside Draw while the native GPU context is current.
  • Add runtime tests for vertical-flip regressions and pin an existing anti-aliasing test to software rendering for byte-exact comparisons.

Reviewed changes

Copilot reviewed 6 out of 6 changed files in this pull request and generated 2 comments.

Show a summary per file
File Description
src/Uno.UI/UI/Xaml/Media/Imaging/RenderTargetBitmap.skia.cs Adds async GPU render path via SKPicture + render-thread replay/readback; retains software fallback.
src/Uno.UI/UI/Xaml/Media/Imaging/RenderTargetBitmap.cs Switches RenderAsync to await an async render implementation; adds async wrapper for non-Skia platforms.
src/Uno.UI/UI/Xaml/Media/CompositionTarget.Rendering.skia.cs Introduces render-job queue drained during Draw and a new internal scheduling API.
src/Uno.UI/FeatureConfiguration.cs Adds Rendering.UseGpuRenderTargetBitmap opt-out flag (default true).
src/Uno.UI.RuntimeTests/Tests/Windows_UI_Xaml_Media_Imaging/Given_RenderTargetBitmap.cs Adds runtime test to guard against vertically flipped GPU output.
src/Uno.UI.RuntimeTests/Tests/Windows_UI_Xaml_Controls/Given_Border.cs Pins a byte-exact anti-aliasing comparison test to software RTB rendering.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread src/Uno.UI/UI/Xaml/Media/Imaging/RenderTargetBitmap.skia.cs
Comment thread src/Uno.UI/UI/Xaml/Media/CompositionTarget.Rendering.skia.cs
GPU rendering of RenderTargetBitmap is now always on when the target
renders with a GRContext. When_CornerRadius_AntiAliasing consequently
can no longer pin itself to the software rasterizer, and no RMSE
threshold separates valid GPU-vs-CPU anti-aliasing variance (0.0325)
from a simulated non-anti-aliased render (0.0349) on this image, so
the test now asserts the invariant directly: a meaningful fraction of
the pixels in a ring around the circle's edge must blend between the
two colors (~34% for the CPU rasterizer, 0% for a jagged render). Its
software-rendered reference asset is removed with it.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@github-actions github-actions Bot added the area/automation Categorizes an issue or PR as relevant to project automation label Jul 3, 2026
Both overloads carried identical copy-pasted bodies differing only in
scaledSize and, through historical drift, in the fallback used when
element is null (PublicRootVisual vs RootElement). They now share one
core, unified on RootElement: the window's root visual, i.e. what is
presented on screen including popups.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Copilot AI review requested due to automatic review settings July 3, 2026 16:02

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 5 out of 6 changed files in this pull request and generated 1 comment.

Comment thread src/Uno.UI/UI/Xaml/Media/Imaging/RenderTargetBitmap.skia.cs Outdated
The hardware and software paths shared no code despite doing the same
thing: both now record the SKPicture through one method (owning the
layout-clip removal and the IsSoftwareRenderer record-time flag) and
replay it through one method (surface, optional resampling, pixel
read-back). The only difference between the two paths is the surface
factory: GRContext-backed for hardware, raster for the fallback.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@unodevops

Copy link
Copy Markdown
Contributor

🤖 Your WebAssembly Skia Sample App stage site is ready! Visit it here: https://unowasmprstaging.z20.web.core.windows.net/pr-23637/wasm-skia-net9/index.html

@nventive-devops

Copy link
Copy Markdown
Contributor

The build 220660 found UI Test snapshots differences: skia-linux-screenshots: 57, skia-windows-screenshots: 2348, wasm: 11

Details
  • skia-linux-screenshots: 57 changed over 2348

    🚨🚨 Comparison Details (first 20) 🚨🚨
    • Examples.png-dark
    • Examples.png
    • ExpanderColorValidationPage.png-dark
    • ExpanderColorValidationPage.png
    • Gamepad_CurrentReading.png-dark
    • Gamepad_CurrentReading.png
    • Gamepad_Enumeration.png-dark
    • Gamepad_Enumeration.png
    • ContextRequested.png-dark
    • ContextRequested.png
    • ImageIconPage.png-dark
    • DisplayInformation.png-dark
    • Buttons.png-dark
    • Buttons.png
    • ClipboardTests.png-dark
    • ElementLevelTheme.png-dark
    • ElementLevelTheme.png
    • ButtonClippingTestsControl.png-dark
    • ButtonClippingTestsControl.png
    • CalendarView_Theming.png-dark
  • skia-windows-screenshots: 2348 changed over 2366

    🚨🚨 Comparison Details (first 20) 🚨🚨
    • Accessibility_ScreenReader.png-dark
    • AppBarToggleButtonTest.png-dark
    • AppWindowFeatures.png
    • ArcSegment.png
    • AutomationPropertiesExtensions_Role.png
    • AutoSuggestBox_Icons.png-dark
    • BasicAcrylicBrushTest2.png-dark
    • Border_AntiAlias.png
    • Border_CornerRadius_Clipping.png
    • Border_CornerRadius_Clipping2.png-dark
    • Border_Simple.png
    • Border_Simple_No_Background.png-dark
    • Border_With_ScaleTransform.png-dark
    • Border_With_ScaleTransform.png
    • Button_Opacity_Automated.png-dark
    • Button_Opacity_Automated.png
    • ChatBox.png-dark
    • ChatBox.png
    • ColorPickerSample.png-dark
    • ColorPickerSample.png
  • wasm: 11 changed over 1076

    🚨🚨 Comparison Details (first 20) 🚨🚨
    • UITests.Windows_UI_Xaml_Media_Animation.ColorAnimation_Background
    • SamplesApp.Windows_UI_Xaml_Controls.ListView.ListViewSelectedItems
    • UITests.Microsoft_UI_Xaml_Controls.WebView2Tests.WebView2_NavigationProperties
    • UITests.Windows_UI_Xaml_Controls.CalendarView.CalendarView_Theming
    • UITests.Shared.Microsoft_UI_Xaml_Controls.ExpanderTests.WinUIExpanderPage
    • UITests.Shared.Windows_UI_Xaml_Controls.MediaPlayerElement.MediaPlayerElement_Minimal
    • SamplesApp.Wasm.Windows_UI_Xaml_Controls.ListView.ListView_IsSelected
    • UITests.Uno_Web.Http.CookieManagerTests
    • Uno.UI.Samples.Content.UITests.WebView.WebView_AnchorNavigation
    • SamplesApp.Microsoft_UI_Xaml_Controls.WebView2Tests.WebView2_EnableDevTools
    • UITests.Shared.Windows_UI_Xaml_Controls.MediaPlayerElement.MediaPlayerElement_Ogg_Extension

@unodevops

Copy link
Copy Markdown
Contributor

⚠️⚠️ The build 220660 has failed on Uno.UI - CI.

@ramezgerges ramezgerges marked this pull request as ready for review July 3, 2026 21:51
Copilot AI review requested due to automatic review settings July 3, 2026 21:51

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 5 out of 6 changed files in this pull request and generated 1 comment.

Comment thread src/Uno.UI.RuntimeTests/Tests/Windows_UI_Xaml_Controls/Given_Border.cs Outdated
ramezgerges and others added 3 commits July 3, 2026 18:04
The drag visual was captured with RenderAsync, which now completes
during a later render pass; the resulting dispatcher yield broke the
synchronous DragStarting -> DragStarted -> DragEnter/DragOver sequence
(matching WinUI) that the drag-and-drop runtime tests assert, failing
them on all Skia CI targets. The capture now uses a synchronous
software render (its behavior before the GPU path was introduced),
while the public RenderAsync stays asynchronous.

Also hardens RenderAsync against overlapping calls on the same
instance: calls are serialized with an async gate and the pending GPU
replay roots the pixel buffer it writes to, so a concurrent
EnsureBuffer can neither resize nor free it mid-write.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
RunRenderJobs took the canvas only to reach its GRContext; callers now
resolve the context themselves. Also asserts UI-thread access in
TryExecuteOnNextRenderAsync, matching the rest of the scheduling
surface.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Copilot AI review requested due to automatic review settings July 3, 2026 22:06

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 6 out of 7 changed files in this pull request and generated 1 comment.

Comment thread src/Uno.UI/UI/Xaml/Media/Imaging/RenderTargetBitmap.skia.cs
@unodevops

Copy link
Copy Markdown
Contributor

🤖 Your WebAssembly Skia Sample App stage site is ready! Visit it here: https://unowasmprstaging.z20.web.core.windows.net/pr-23637/wasm-skia-net9/index.html

@nventive-devops

Copy link
Copy Markdown
Contributor

@ramezgerges ramezgerges merged commit e7c552f into unoplatform:master Jul 4, 2026
48 checks passed
@ramezgerges ramezgerges deleted the rtb_gpu_rendering branch July 4, 2026 23:36
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area/automation Categorizes an issue or PR as relevant to project automation area/skia ✏️ Categorizes an issue or PR as relevant to Skia

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Skia] Hardware-accelerate RenderTargetBitmap rendering

5 participants