Search code examples
c#opengl.net-coreopentkskiasharp

"ERROR creating stencil attachment" in SkiaSharp with OpenTK


I tried to get SkiaSharp to work with an OpenGL backend in a Windows App, so I found this example on the internet https://gist.github.com/d-kr/eeced4157bf926accc9c6ad435d37a37.

The problem

On my laptop running a GTX1050 everything works fine, but on my Desktop running a GTX1060 it doesn't. Both systems run Windows. As You can see in the image below an OpenTK GameWindow is created and filled.

enter image description here

On my laptop you can see circles being drawn, but on my desktop it throws an error message saying ERROR creating stencil attachment. Draw skipped. I was able to track down the error to OnRenderFrame(), occurring when canvas.DrawCircle() is called.

First attempt

I know SkiaSharp quite well but I'm quite new to OpenTK. What am I missing here? On Google I only found this post https://groups.google.com/forum/#!topic/skia-discuss/m53JH2scDh4 which suggests that this is some kind of setup or hardware problem. So where do I start debugging this? These are my dependencies:

enter image description here

Thanks in advance!


Necessary code

using OpenTK;
using OpenTK.Graphics;
using OpenTK.Graphics.OpenGL;
using OpenTK.Input;
using SkiaSharp;
using System;
using System.Diagnostics;

// https://gist.github.com/d-kr/eeced4157bf926accc9c6ad435d37a37

namespace NetCoreDesktop
{
    public sealed class MainWindow : GameWindow
    {


    private GRContext context;
    private GRBackendRenderTargetDesc renderTarget;

    public MainWindow()
        : base(1280, // initial width
        720, // initial height
        GraphicsMode.Default,
        "window",  // initial title
        GameWindowFlags.Default,
        DisplayDevice.Default,
        1, // OpenGL major version
        0, // OpenGL minor version
        GraphicsContextFlags.ForwardCompatible) {
            Title += ": OpenGL Version: " + GL.GetString(StringName.Version);
    }

    protected override void OnLoad(EventArgs e) {
        base.OnLoad(e);
        var glInterface = GRGlInterface.CreateNativeGlInterface();
        Debug.Assert(glInterface.Validate());
        this.context = GRContext.Create(GRBackend.OpenGL, glInterface);
        Debug.Assert(this.context.Handle != IntPtr.Zero);
        this.renderTarget = CreateRenderTarget();
        CursorVisible = true;
    }

    protected override void OnUnload(EventArgs e) {
        base.OnUnload(e);
        this.context?.Dispose();
        this.context = null;
    }

    protected override void OnResize(EventArgs e) {
        GL.Viewport(0, 0, Width, Height);
    }


    protected override void OnUpdateFrame(FrameEventArgs e) {
        HandleKeyboard();
    }
    private void HandleKeyboard() {
        var keyState = Keyboard.GetState();
        if (keyState.IsKeyDown(Key.Escape)) {
            Exit();
        }
    }

    public static GRBackendRenderTargetDesc CreateRenderTarget() {
        GL.GetInteger(GetPName.FramebufferBinding, out int framebuffer);
        GL.GetInteger(GetPName.StencilBits, out int stencil);
        GL.GetInteger(GetPName.Samples, out int samples);
        int bufferWidth = 0;
        int bufferHeight = 0;
        GL.GetRenderbufferParameter(RenderbufferTarget.Renderbuffer, RenderbufferParameterName.RenderbufferWidth, out bufferWidth);
        GL.GetRenderbufferParameter(RenderbufferTarget.Renderbuffer, RenderbufferParameterName.RenderbufferHeight, out bufferHeight);

        return new GRBackendRenderTargetDesc {
            Width = bufferWidth,
            Height = bufferHeight,
            Config = GRPixelConfig.Bgra8888, // Question: Is this the right format and how to do it platform independent?
            Origin = GRSurfaceOrigin.TopLeft,
            SampleCount = samples,
            StencilBits = stencil,
            RenderTargetHandle = (IntPtr)framebuffer,
        };
    }


    protected override void OnRenderFrame(FrameEventArgs e) {
        base.OnRenderFrame(e);

        Title = $"(Vsync: {VSync}) FPS: {1f / e.Time:0}";

        Color4 backColor;
        backColor.A = 1.0f;
        backColor.R = 0.1f;
        backColor.G = 0.1f;
        backColor.B = 0.3f;
        GL.ClearColor(backColor);
        GL.Clear(ClearBufferMask.ColorBufferBit | ClearBufferMask.DepthBufferBit);

        this.renderTarget.Width = this.Width;
        this.renderTarget.Height = this.Height;

        using (var surface = SKSurface.Create(this.context, this.renderTarget)) {
            Debug.Assert(surface != null);
            Debug.Assert(surface.Handle != IntPtr.Zero);

            var canvas = surface.Canvas;

            canvas.Flush();

            var info = this.renderTarget;

            //canvas.Clear(SKColors.Beige);

            using (SKPaint paint = new SKPaint {
                Style = SKPaintStyle.Stroke,
                Color = SKColors.Red,
                StrokeWidth = 25
            }) {
                canvas.DrawCircle(info.Width / 2, info.Height / 2, 100, paint);
                paint.Style = SKPaintStyle.Fill;
                paint.Color = SKColors.Blue;
                canvas.DrawCircle(info.Width / 2, info.Height / 2, 100, paint);
            }

            canvas.Flush();
        }
        this.context.Flush();
        SwapBuffers();
    }


}
}

Solution

  • For some reason, StencilBits is set to zero, which is not valid. Setting it to a fixed value (like 1) helps.

    public static GRBackendRenderTargetDesc CreateRenderTarget() {
        ...
        return new GRBackendRenderTargetDesc {
            ...
            StencilBits = 1,
            ...
        };
    }