Accessing Godot OpenGL/Vulkan context in C# to use for GPU accellerated 2d rendering in SkiaSharp

Godot Version

4.6.x

Question

I’m experimenting with using SkiaSharp to do some 2d vector rendering in a Godot project. The initial setup was surprisingly easy, just adding the nuget package to the project and I was quickly able to setup a CPU-based rendering system where I render Skia stuff into a bitmap pixel buffer and then copy that buffer content into a Godot Image and create an Texture from it I can render in Godot. So far so good.

However, Skia (and the C# SkiaSharp version) has extensive support for GPU accelleration, and ideally I’d like to use this instead of needing to render on the CPU and transfer the data to the GPU for each update.

This involves getting a reference to a valid GPU context to pass to the Skia rendering system (either OpenGL for Compatibility renderer or Vulkan in Mobile/Forward+ seem to be supported) and ideally also getting a GPU backend reference to a texture you’ve set up to use as a rendering target for the Skia rendering. This way I could do all the rendering on the GPU and get the output in a texture I could hopefully use right away to render in Godot for great performance.

However, I haven’t found a way to find this reference to the gpu context Godot is using. At least nothing Skia will accept and be able to use. You can find an example of someone else trying to do something similar and not succeeding here: [BUG] Failed to obtain vulkan context · Issue #3312 · mono/SkiaSharp · GitHub

The SkiaSharp documentation also mentions that you’re responsible to making the context active for it to do its drawing, so I guess I’d also need to hook this into the Godot rendering system somehow to make sure the Skia draw calls are called during the rendering cycle when Godot has the context active (maybe just have it be called from some control _draw method?).

Does anyone know how of a way to get a C# reference to the CPU context (OpenGL/vulkan) used by Godot to use for interfacing with other rendering systems like this? Or better yet, has anyone else managed to get an GPU accelerated rendering with SkiaSharp working in a Godot project?

(Edit: fixed reference link)

Rendering context is platform specific and completely abstracted away in Godot. You can’t do this without source code interventions.

Has a custom pipeline but it doesnt access any godot objects like textures /nodes /meshes or anything.

If it were me, I would create the canvas in skia, grab the surface data, convert surface data to png then render with Godot. Something like:

        SKImageInfo imageInfo = new SKImageInfo(512, 768);
        SKSurface surface = SKSurface.Create(imageInfo);
        SKCanvas canvas = surface.Canvas;
        canvas.Clear(SKColors.Azure);
        canvas.DrawPicture(Grid.Picture);
        canvas.Restore();
        canvas.Save();
        var data = surface.snapshot();
        var img = data.Encode(SKEncodedImageFormat.Png,100)
         //DoSomething with image

The above code creates an image with a grid on it, code for Grid at end of post.

Some things to keep in mind, if you want your Godot project to be cross platform you will have to figure out a way to load the proper native libraries that are required for Skia on different platforms.

using SkiaSharp;

namespace SkiasharpDrawingCoOrdinatesLearn.Pictures;

public class Grid
{
    public static SKPicture Picture { get; }

    static Grid()
    {
        Picture = Record();
    }

    private static SKPicture Record()
    {
        var skPictureRecorder0 = new SKPictureRecorder();
        var skCanvas0 = skPictureRecorder0.BeginRecording(new SKRect(0f, 0f, 512f, 768f));
        skCanvas0.Save();
        //var skPath0 = new SKPath();
        //skPath0.AddRect(new SKRect(0, 0, 256, 256));
        var skPaint0 = new SKPaint();
        skPaint0.IsAntialias = true;
        skPaint0.Color = SKColors.Black;
        skCanvas0.DrawLine(0,256,512,256, skPaint0);
        skCanvas0.DrawLine(0,512,512,512, skPaint0);
        skCanvas0.DrawLine(256,0,256,768, skPaint0);
        skPaint0.Color = SKColors.Gray;
        skCanvas0.DrawLine(0,128,512,128, skPaint0);
        skCanvas0.DrawLine(0,384,512,384, skPaint0);
        skCanvas0.DrawLine(0,640,512,640, skPaint0);
        skCanvas0.DrawLine(128,0,128,768, skPaint0);
        skCanvas0.DrawLine(384,0,384,768, skPaint0);
        //skCanvas0.DrawPath(skPath0, skPaint0);
        skPaint0?.Dispose();
        //skPath0?.Dispose();
        skCanvas0.Save();
        var skPicture0 = skPictureRecorder0.EndRecording();
        skPictureRecorder0.Dispose();
        skCanvas0.Dispose();
        return skPicture0;
    }
    
    public static void Draw(SKCanvas skCanvas)
    {
        skCanvas.DrawPicture(Picture);
    }
}

Well, I was hopeful since for example RenderingServer — Godot Engine (stable) documentation in English seems to allow you to get the underlying IDs/references to textures for this type of integration with other APIs, so I was hoping that the RenderingServer or some underlying component like the RenderingDevices would give you access to the current GPU context as well for the same reason.

Yes, I’ve had no trouble setting up a rendering surface in Skia and render into it. And you don’t need to encode the surface data into PNG and use in Godot. For example you can draw directly into a SKBitmap and then create a Godot Image → Texture from the SKBitmap Bytes buffer without encoding/decoding to png. like:

bitmap = new SKBitmap(size_x, size_y);

SKCanvas canvas = new SKCanvas(bitmap);

// * Skia draw into canvas * 

Image image = Image.CreateFromData(size_x,size_y,false,Image.Format.Rgba8,bitmap.Bytes);

//Create a new texture if needed, 
godotTexture = ImageTexture.CreateFromImage(image);
//or if it exists update with new image
godotTexture.Update(image);

However, this means the rendering is done on the CPU and then the pixel data needs to be transferred to the GPU for each update. So instead I’d like it to be able to render on the GPU directly into a texture to avoid this transfer.

Okay misunderstood what your trying to do.

Note I think the Skia encode method would be slightly faster then reading the bytes in Godot.

Sorry can’t be of more help. Another thing to keep in mind is co-ordinate systems in a lot of cases Godot use the center of a texture as the origin, where as, as you probably know Skia uses the original upper left corner, even after the canvas has been rotated so you’ll have to keep that in mind.

No, problem and thanks anyway. But what makes you think that encoding the image pixel buffer data into PNG and then decoding it back to a pixel buffer would be faster than just copying the buffer bytes?

Basically what (I think) I want to do in Skia is:

// Create a Skia GPU rendering Vulkan context referencing the Godot context (missing the vkBackendContext info properties for this)
var context = GRContext.CreateVulkan(vkBackendContext);

// Create a Skia backendTexture reference to an existing Godot Texture (missing the vkImageInfo info properties for this)
GRBackendTexture backendTexture = new GRBackendTexture(Width, Height, vkImageInfo);

// Then create a Skia Surface to render into base on this context and texture
SKSurface surface = SKSurface.Create(context, backendTexture, GRSurfaceOrigin.TopLeft, SKColorType.Rgba8888);

// And hopefully be able to draw into this canvas and have Skia render directly into the supplied texture so I can draw it directly in Godot.        

It may be that these can be obtained from RenderingDevice.get_driver_resource (RenderingDevice — Godot Engine (stable) documentation in English) and RenderingDevice.texture_get_native_handle (RenderingDevice — Godot Engine (stable) documentation in English) I just need to figure out what is what there.

So you want to make a Godot /Skia interop?

What about a compute shader? If you can get a handle to the GPU screen from Skia then maybe you could compute shade it onto a godot surface …

I think its probably an independant rendering device object and there were typically problems getting things like that to work …

Well, yes. Basically I have a Godot project and want to draw vector graphics using Skia and hope to use the GPU rendering capabilities it offers.

I don’t know anything about compute shaders, and very little about Skia. But as I understand it Skia doesn’t create or manage any GPU context of its own, and instead relies on you creating one somehow and then telling Skia about it so it can use it for rendering. Since Godot is creating a Vulkan (Or Metal, or DirectX etc) context I therefore would try to get a reference to that and tell Skia about it so it can use it as well.

Did you see my first reply? The godot project that just renders a triangle to the screen using native vulkan?

Yes, but looking at it I don’t see how this would relate to setting up a GPU context for SkiaSharp?

You would need to put it together with something like this compute shader sample

The rendering device that uses the compute shader is ‘local’, whatever that means.

If possible you pass the same local rendering device or native vulkan device into skia sharp

Ah, I see. So you mean that if I can’t get access to the original Godot GPU context I might be able to use a similar approach to set up a secondary GPU device I could then share with Skia somehow?

I would take a look at the compute shade example and the vulkan native example and try to research what they actually do and how those relate to what you are doing when you pass a rendering context.

1 Like