Skip to content

Add generic external GPU interop for MewVG#117

Draft
wieslawsoltes wants to merge 8 commits intoaprillz:mainfrom
wieslawsoltes:feature/mewvg-skia-platform-packages
Draft

Add generic external GPU interop for MewVG#117
wieslawsoltes wants to merge 8 commits intoaprillz:mainfrom
wieslawsoltes:feature/mewvg-skia-platform-packages

Conversation

@wieslawsoltes
Copy link
Copy Markdown

@wieslawsoltes wieslawsoltes commented Apr 17, 2026

PR Summary: Add generic external GPU interop for MewVG

Overview

This PR reduces the MewUI side of the Skia work to the generic pieces only.

It does not add:

  • SkiaSharp package references
  • public Skia control API
  • retained Skia surface implementations
  • platform-specific Skia backend registration packages
  • a Skia sample application

Those parts were moved into the separate public repository:

This PR keeps only the backend and factory changes that make fast external GPU interop possible.

Why this split

The Skia integration needed two different layers:

  1. generic backend interop hooks that belong in MewUI
  2. renderer-specific SkiaSharp code that should live outside the base framework

Keeping the generic layer in MewUI makes the backends reusable for future external renderers, while the Skia-specific packages can evolve independently in Skia.MewUI.

What this PR adds

1. Graphics service resolution

This PR adds a lightweight graphics-service extension model:

  • IGraphicsServiceProvider
  • GraphicsServiceRegistry
  • TryGetGraphicsService<T>()

That allows external packages to attach services to backend factories without modifying the backend’s public type or introducing a direct dependency from the base backend assembly.

2. Generic MewVG window-resource interop

This PR adds backend-neutral contracts for external GPU renderers:

  • IMewVGWindowResourceResolver
  • IMewVGExternalImageInterop
  • IMewVGGlWindowInterop
  • IMewVGMetalWindowInterop

These expose only the native interop needed to share textures and execute work on the correct GL or Metal device/context.

3. Generic frame-composition interop

This PR adds a generic active-context contract:

  • IMewVGExternalImageContext

The GL and Metal backends now both expose external GPU content as normal backend images. That keeps the drawing model consistent across platforms and avoids the earlier Metal-only suspend/restart composition path.

This is the final shape of the branch. Earlier iterations used a Metal-specific TryBeginExternalComposite(...) fallback, but that has been removed in favor of the same external-image model used on GL.

4. Backend implementation changes

The Win32, X11, and macOS MewVG backends now implement those generic interop contracts directly.

That includes:

  • OpenGL external image creation and disposal
  • context switching for GL interop work
  • Metal shared texture creation
  • Metal external image creation and disposal
  • shared state restoration before drawing externally managed images

On Metal, there is still one backend-private implementation detail: a small internal bridge that registers the shared MTLTexture with the current MewVG Metal image model. That detail stays inside the backend and does not leak into the public interop surface.

5. macOS handle-resolution support

The existing macOS resource-lookup behavior is preserved so that the framework can resolve the live Metal window resources when the visual root handle and the actual CAMetalLayer handle differ.

What this PR intentionally removes

This branch originally contained the SkiaSharp projects and sample app, but they were moved out before finalizing the PR.

The following are now external to MewUI:

  • MewUI.SkiaSharp
  • MewUI.Backend.MewVG.Skia
  • MewUI.Backend.MewVG.Win32.Skia
  • MewUI.Backend.MewVG.X11.Skia
  • MewUI.Backend.MewVG.MacOS.Skia
  • MewUI.SkiaSharp.Sample

The implementation of those projects now lives in:

That repo consumes this PR’s generic interop APIs through a git submodule pinned to this branch.

Files of interest

Generic service model

  • src/MewUI/Rendering/IGraphicsServiceProvider.cs

Generic MewVG interop contracts

  • src/MewUI/Rendering/MewVG/IMewVGGraphicsInterop.cs

Factory-side resource resolution

  • src/MewUI/Shared/Rendering/MewVG/MewVGGraphicsFactory.cs
  • src/MewUI/Shared/Rendering/MewVG/MewVGGraphicsFactory.Metal.MacOS.cs

Generic context/resource implementations

  • src/MewUI/Shared/Rendering/MewVG/MewVGGraphicsContext.Common.cs
  • src/MewUI/Shared/Rendering/MewVG/Win32/MewVGWindowResources.cs
  • src/MewUI/Shared/Rendering/MewVG/Win32/MewVGWin32GraphicsContext.cs
  • src/MewUI/Shared/Rendering/MewVG/X11/MewVGX11WindowResources.cs
  • src/MewUI/Shared/Rendering/MewVG/X11/MewVGX11GraphicsContext.cs
  • src/MewUI/Shared/Rendering/MewVG/Metal/MewVGMetalExternalImageBridge.cs
  • src/MewUI/Shared/Rendering/MewVG/Metal/MewVGMetalWindowResources.cs
  • src/MewUI/Shared/Rendering/MewVG/Metal/MewVGMetalGraphicsContext.cs

Result

After this PR:

  • MewUI exposes a clean, generic external GPU interop surface
  • MewUI remains free of SkiaSharp dependencies and public Skia API
  • renderer-specific integration can live in separate repositories and packages
  • Skia.MewUI can provide the full SkiaSharp experience while consuming the same external-image path on GL and Metal

Validation

Built successfully on this branch:

  • dotnet build src/MewUI/MewUI.csproj -f net8.0
  • dotnet build src/MewUI.Backend.MewVG.Win32/MewUI.Backend.MewVG.Win32.csproj -f net8.0
  • dotnet build src/MewUI.Backend.MewVG.X11/MewUI.Backend.MewVG.X11.csproj -f net8.0
  • dotnet build src/MewUI.Backend.MewVG.MacOS/MewUI.Backend.MewVG.MacOS.csproj -f net8.0

The extracted Skia integration was also validated in the external repo:

  • dotnet build src/MewUI.Backend.MewVG.Skia/MewUI.Backend.MewVG.Skia.csproj -f net8.0
  • dotnet build samples/MewUI.SkiaSharp.Sample/MewUI.SkiaSharp.Sample.csproj -f net10.0

External companion repo

@wieslawsoltes wieslawsoltes marked this pull request as draft April 17, 2026 22:04
@wieslawsoltes
Copy link
Copy Markdown
Author

wieslawsoltes commented Apr 18, 2026

This PR is experimental work, main problem to solve here is to enable easy integration of third party libraries like SkiaSharp into rendering pipeline in the UI, all SkiaSharp parts can be removed from this PR and just parts refactoring rendering api. But for sake of discussion and testing SkiaSharp bits are here for now. Here is my reasoning for this work #116 (comment)

@wieslawsoltes wieslawsoltes changed the title Decouple MewVG Skia hosting into platform-specific packages Add generic external GPU interop for MewVG Apr 18, 2026
@wieslawsoltes
Copy link
Copy Markdown
Author

I splitted original code into separate repo https://github.com/wieslawsoltes/Skia.MewUI and now its only GPU interop.

@wieslawsoltes
Copy link
Copy Markdown
Author

@al6uiz Those changes are not set in stone, I am open for any way this can be done to enable GPU interop work third party libraries, this way it makes easy to build on top on MewUI while making core follow its goals.

@shonescript
Copy link
Copy Markdown

Hello @wieslawsoltes ,
Your Skia.MewUI project is a highly professional extension, that bridges the underlying graphics layers of MewVG and Skia, with some extended modifications to the main library.
According to the author’s roadmap, the goal is to build a minimal and fast lightweight cross-platform UI framework for .NET, which imposes certain constraints on low-level graphics design.
From my own testing, platform-specific native graphics backends deliver the best performance. For example, on Win32, Direct2D is the fastest, and even GDI outperforms bridged implementations such as my Skia-based one.
From the author’s perspective, additional backends for Win32, X11, macOS, and other platforms may be unnecessary, as their performance is unlikely to surpass the existing implementation. Other developers can later pursue their own ports or extended backends to other platforms without affecting the main library.
A SkiaSharp-based backend for MewUI maybe still necessary (@al6uiz ), as it enables support for platforms including Android, iPhone, and WebAssembly (which MewUI does not yet support now). Both Avalonia and Uno use this technology for cross-platform rendering, proving that SkiaSharp is a viable and effective solution.
I have also implemented a Skia backend for Win32 (similar logic for other platforms), directly implementing GraphicsContextBase using SkCanvas, primarily handling the mapping to MewUI’s core graphics interfaces.
The main library’s sample projects can now run directly. Font rendering and line drawing have been implemented, while text wrapping and other features need further work. A major bug has been identified: after an image is drawn, all subsequent drawing commands cease to work in SkCanvas.
I do not use Skia frequently but I want to port to WebAssembly. You seem very proficient with it. I would appreciate any feedback or corrections regarding my implementation. Thanks :-)
https://github.com/shonescript/MewUI

@al6uiz
Copy link
Copy Markdown
Member

al6uiz commented Apr 19, 2026

Thank you for the PR. I also appreciate that you already addressed some of the points I was planning to comment on in the earlier version.

The direction I have in mind is also closer to exposing backend-native resource implementations through common interfaces, rather than letting a specific graphics library penetrate into the backend itself, and then having those resources consumed externally in a way that avoids CPU copies as much as possible.

From the core side, I see this PR as being about the changes needed to provide those kinds of interfaces. That is also something I had in mind within the same broader direction.

I do have one question related to this.

In the Metal-side code, I am curious about the reasoning behind introducing the TryBeginExternalComposite pattern for compositing the shared texture. From my current understanding, it seems like the backend render pass would only need to consume the surface texture bound to the SkElement. Could you explain the technical background that made it necessary to introduce that pattern?

@wieslawsoltes
Copy link
Copy Markdown
Author

wieslawsoltes commented Apr 20, 2026

In the Metal-side code, I am curious about the reasoning behind introducing the TryBeginExternalComposite pattern for compositing the shared texture. From my current understanding, it seems like the backend render pass would only need to consume the surface texture bound to the SkElement. Could you explain the technical background that made it necessary to introduce that pattern?

Full disclosure the metal integration is not by expertise so here is summary for my work with Codex that did heavy lifting integration, the ideas is basically to have fastest path to rendering into GPU texture via metal when using SkiaSharp (or other third party graphics library):


The short version is that TryBeginExternalComposite was introduced because, on the Metal side, I did not yet have the equivalent of the GL path where the backend can treat the externally rendered result as a normal backend image and draw it inside the existing frame.

On GL, the flow is relatively straightforward:

  • the external renderer owns a GPU texture
  • the backend wraps that texture as an image handle
  • the active MewVG context draws that image in the current pass

That is why the GL-side interop can stay at the level of DrawExternalImage(...).

On Metal, I was missing that same capability boundary. The retained SkElement content is rendered into a separate shared MTLTexture, but the current MewVG Metal path does not expose a backend-native mechanism to wrap an external MTLTexture and sample it as a normal image inside the active pass, analogous to what the GL backend can do with CreateImageFromHandle.

Given that constraint, the zero-copy option available to me was:

  1. finish the current MewVG Metal segment
  2. expose the live drawable texture and the state needed to continue composition
  3. let the external renderer composite directly into that drawable
  4. restart the MewVG segment with Load and restore clip / alpha / transform state

That is what TryBeginExternalComposite(...) / EndExternalComposite() is modeling.

So the reason for the pattern is not that direct drawable composition is inherently the ideal final API. It is more that, with the current backend surface model, I wanted to satisfy these constraints at the same time:

  • avoid CPU copies
  • avoid pushing Skia-specific types into the backend
  • keep the interop generic enough to be reusable
  • still support Metal without an existing “wrap external texture as backend image” path

If the preferred long-term direction is to let the backend consume the SkElement surface texture as a normal backend resource, I agree that is a cleaner shape. In that world, I would expect the Metal side to move toward something closer to:

  • create or wrap an external Metal texture as a backend image/resource
  • draw that resource inside the active render pass

In other words, I see TryBeginExternalComposite as the

@wieslawsoltes
Copy link
Copy Markdown
Author

@al6uiz I am continuing to simplify and improve the GPU interop, here is the refactor from last version 135118a

…platform-packages

# Conflicts:
#	src/MewUI.Backend.MewVG.MacOS/MewUI.Backend.MewVG.MacOS.csproj
#	src/MewUI/Shared/Rendering/MewVG/MewVGGraphicsFactory.Metal.MacOS.cs
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants