Search code examples
c#opengltexturesopentk

Bindless texture generated on worker thread not 'seen' on main thread/shader(?) despite shared contexts


My program needs to load images very frequently, so in order to get it not to block, I've put the texture loading logic onto a separate thread.

The texture loading thread maintains an infinite loop and a queue. When the main thread adds a file to that queue, the texture loading thread loads that file and then moves on to any others.

The texture loading works fine. As far as I can tell there are no issues with the loading of the image, nor the generation of regular and bindless texture handles.

The problem comes when I create the bindless texture handle. While the handle is created, once I write it to the shader storage buffer object that I use to tell the fragment shader which textures to draw, nothing renders.


This is not an issue with writing to the shader, the correct data definitely gets written there. It is also not an issue with the texture loading. I can generate a regular texture handle on the texture loading thread, then turn it into bindless texture on the main thread and it will render. As far as I can tell there is a specific issue with how I'm generating the bindless texture on the texture loading thread.

public void Load( int FileIndex )
{
    string Path = Library.Paths[FileIndex];

    BindlessTexture Texture = new();

    Texture.Handle = GL.GenTexture();
    GL.BindTexture(TextureTarget.Texture2D, Texture.Handle);

    Image<Rgba32> ImageData = Image.Load<Rgba32>(Path); //Loaded with SixLabors.ImageSharp
    ImageData.Mutate(X => X.Flip(FlipMode.Vertical));

    byte[] PixelData = new byte[4 * ImageData.Width * ImageData.Height];
    ImageData.CopyPixelDataTo(PixelData);

    GL.TexParameter(TextureTarget.Texture2D, TextureParameterName.TextureMinFilter, (int)TextureMagFilter.Linear);
    GL.TexParameter(TextureTarget.Texture2D, TextureParameterName.TextureMagFilter, (int)TextureMagFilter.Linear);
    GL.TexParameter(TextureTarget.Texture2D, TextureParameterName.TextureWrapS, (int)TextureWrapMode.ClampToBorderArb);
    GL.TexParameter(TextureTarget.Texture2D, TextureParameterName.TextureWrapT, (int)TextureWrapMode.ClampToBorderArb);
    GL.TexParameter(TextureTarget.Texture2D, TextureParameterName.TextureBorderColor, new float[4]);

    GL.TexImage2D(
        TextureTarget.Texture2D,
        0,
        PixelInternalFormat.Rgba,
        ImageData.Width,
        ImageData.Height,
        0,
        PixelFormat.Rgba,
        PixelType.UnsignedByte,
        PixelData
    );

    PixelData = null;
    ImageData.Dispose();
    GL.BindTexture(TextureTarget.Texture2D, 0);

    Texture.BindlessHandle = GL.Arb.GetTextureHandle(Texture.Handle);
    Texture.MakeResident();

    Library.BindlessTextures[FileIndex] = Texture;
    Library.BindlessTextureHandles[FileIndex] = Texture.BindlessHandle;
    Marshal.WriteInt64(Library.BindlessTextureHandleBufferPointer, FileIndex * 8, Texture.BindlessHandle); 

}

Above is the code to load images, and

public void InitMainContext()
{
    GLFW.DefaultWindowHints();

    #region OpenGL Version
    GLFW.WindowHint(WindowHintInt.ContextVersionMajor, 4);
    GLFW.WindowHint(WindowHintInt.ContextVersionMinor, 6);
    #endregion

    #region Window Hints
    GLFW.WindowHint(WindowHintBool.Visible, true);
    GLFW.WindowHint(WindowHintBool.Resizable, true);
    GLFW.WindowHint(WindowHintBool.OpenGLDebugContext, true);
    #endregion

    #region Antialiasing
    GLFW.WindowHint(WindowHintInt.Samples, 4);
    #endregion

    #region Window Initialization
    MainContext = GLFW.CreateWindow(Width, Height, "Qib", null, null);
    if ( MainContext is null ) throw new Exception("Could not create Window!");

    GLFW.MakeContextCurrent(MainContext);
    GLFW.SwapInterval(0);
    GLFW.ShowWindow(MainContext);
    #endregion

    #region Input
    Input.Init();
    GLFW.SetKeyCallback(MainContext, Input.Keyboard);
    GLFW.SetCursorPosCallback(MainContext, Input.Cursor);
    GLFW.SetMouseButtonCallback(MainContext, Input.Buttons);
    GLFW.SetScrollCallback(MainContext, Input.Scroll);
    #endregion

    #region OpenGL Initialization
    GL.LoadBindings(new GLFWBindingsContext());
    GL.ClearColor(0.6f, 0.6f, 1f, 1f);

    GL.Enable(EnableCap.DepthTest);
    GL.Enable(EnableCap.Texture2D);
    GL.Enable(EnableCap.Multisample);
    GL.Enable(EnableCap.DebugOutput);
    GL.Enable(EnableCap.DebugOutputSynchronous);
    #endregion

    #region Debugger
    MainDebugger = new DebugProc(MainThreadDebugCallback);
    GL.DebugMessageCallback(MainDebugger, IntPtr.Zero);
    GL.DebugMessageControl(DebugSourceControl.DontCare, DebugTypeControl.DontCare, DebugSeverityControl.DontCare, 0, new int[0], true);
    GL.DebugMessageInsert(DebugSourceExternal.DebugSourceApplication, DebugType.DebugTypeMarker, 0, DebugSeverity.DebugSeverityNotification, -1, "Debug output enabled");
    #endregion
}

public void InitTextureContext()
{
    GLFW.DefaultWindowHints();

    #region OpenGL Version
    GLFW.WindowHint(WindowHintInt.ContextVersionMajor, 4);
    GLFW.WindowHint(WindowHintInt.ContextVersionMinor, 6);
    #endregion

    #region Window Hints
    GLFW.WindowHint(WindowHintBool.Visible, false);
    GLFW.WindowHint(WindowHintBool.OpenGLDebugContext, true);
    #endregion

    #region Window Initialization
    TextureContext = GLFW.CreateWindow(1, 1, "", null, MainContext);
    if ( TextureContext is null ) throw new Exception("Could not create Texture Context!");
    #endregion

    #region Debugger
    TextureDebugger = new DebugProc(TextureThreadDebugCallback);
    GLFW.MakeContextCurrent(TextureContext);
    GL.DebugMessageCallback(TextureDebugger, IntPtr.Zero);
    GL.DebugMessageControl(DebugSourceControl.DontCare, DebugTypeControl.DontCare, DebugSeverityControl.DontCare, 0, new int[0], true);
    GL.DebugMessageInsert(DebugSourceExternal.DebugSourceApplication, DebugType.DebugTypeMarker, 0, DebugSeverity.DebugSeverityNotification, -1, "Debug output enabled");
    GLFW.MakeContextCurrent(MainContext);
    #endregion
}

Here are the functions called when creating both contexts.

I'm really not sure what I'm missing here. The data is perfectly as expected but its as if the shader can't see the handles created in the texture loader thread. Can a bindless handle created on one thread not be referenced by a shader program compiled on the main thread?


Solution

  • Aight so as far as I can tell, the problem was with the residency of the handles. I needed to make them resident on the main context.

    To do so I set up a second queue which I'd add the loaded textures to. The main thread would go through this queue and make every new texture resident on the main thread.