Created
May 2, 2025 16:53
-
-
Save vittorioromeo/d58202a947ed0f6dbfca34757b7b7fd2 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// Compile on MSYS2 with: | |
// gcc ./fboBug.c -lSDL3 -lopengl32 | |
#include <SDL3/SDL.h> | |
#include <SDL3/SDL_opengl.h> | |
#include <assert.h> | |
#include <stdio.h> | |
#define glCheck(...) \ | |
do \ | |
{ \ | |
const unsigned int openGlError = glGetError(); \ | |
\ | |
__VA_ARGS__; \ | |
\ | |
while (!glCheckError(openGlError, __FILE__, __LINE__, #__VA_ARGS__)) \ | |
/* no-op */; \ | |
\ | |
} while (false) | |
static bool glCheckError(const unsigned int openGlError, const char* const file, const unsigned int line, const char* const expression) | |
{ | |
if (openGlError == GL_NO_ERROR) | |
return true; | |
printf( | |
"An internal OpenGL call failed in %s(%u).\n" | |
"Expression:\n %s\n" | |
"Error description:\n %s\n %s\n", | |
file, | |
line, | |
expression, | |
"TODO: ERROR", | |
"TODO: DESCRIPTION"); | |
abort(); | |
return false; | |
} | |
PFNGLGENFRAMEBUFFERSPROC pglGenFramebuffers = NULL; | |
PFNGLBINDFRAMEBUFFERPROC pglBindFramebuffer = NULL; | |
PFNGLFRAMEBUFFERTEXTURE2DPROC pglFramebufferTexture2D = NULL; | |
PFNGLCHECKFRAMEBUFFERSTATUSPROC pglCheckFramebufferStatus = NULL; | |
PFNGLBLITFRAMEBUFFERPROC pglBlitFramebuffer = NULL; | |
PFNGLDELETEFRAMEBUFFERSPROC pglDeleteFramebuffers = NULL; | |
PFNGLGETINTEGER64VPROC pglGetIntegerv = NULL; | |
int main(void) | |
{ | |
assert(SDL_InitSubSystem(SDL_INIT_VIDEO)); | |
// SDL window associated with the shared OpenGL context | |
SDL_Window* sharedCtxHiddenWindow = SDL_CreateWindow("", 1, 1, SDL_WINDOW_OPENGL | SDL_WINDOW_HIDDEN); | |
assert(sharedCtxHiddenWindow); | |
// SDL shared OpenGL context handle | |
SDL_GLContext sharedCtx = SDL_GL_CreateContext(sharedCtxHiddenWindow); | |
assert(sharedCtx); | |
// Activate shared context and load GL functions | |
assert(SDL_GL_MakeCurrent(sharedCtxHiddenWindow, sharedCtx)); | |
// clang-format off | |
assert(pglGenFramebuffers = (PFNGLGENFRAMEBUFFERSPROC)SDL_GL_GetProcAddress("glGenFramebuffers")); | |
assert(pglBindFramebuffer = (PFNGLBINDFRAMEBUFFERPROC)SDL_GL_GetProcAddress("glBindFramebuffer")); | |
assert(pglFramebufferTexture2D = (PFNGLFRAMEBUFFERTEXTURE2DPROC)SDL_GL_GetProcAddress("glFramebufferTexture2D")); | |
assert(pglCheckFramebufferStatus = (PFNGLCHECKFRAMEBUFFERSTATUSPROC)SDL_GL_GetProcAddress("glCheckFramebufferStatus")); | |
assert(pglBlitFramebuffer = (PFNGLBLITFRAMEBUFFERPROC)SDL_GL_GetProcAddress("glBlitFramebuffer")); | |
assert(pglDeleteFramebuffers = (PFNGLDELETEFRAMEBUFFERSPROC)SDL_GL_GetProcAddress("glDeleteFramebuffers")); | |
assert(pglGetIntegerv = (PFNGLGETINTEGER64VPROC)SDL_GL_GetProcAddress("glGetIntegerv")); | |
// clang-format on | |
// SDL window that will be cleared to red | |
SDL_Window* window = SDL_CreateWindow("", 256, 256, SDL_WINDOW_OPENGL); | |
assert(window); | |
// OpenGL context associated with the window | |
assert(SDL_GL_SetAttribute(SDL_GL_SHARE_WITH_CURRENT_CONTEXT, 1)); // Share shared context with next created context | |
SDL_GLContext windowCtx = SDL_GL_CreateContext(window); | |
assert(windowCtx); | |
// Activate window context and bind default FBO | |
assert(SDL_GL_MakeCurrent(window, windowCtx)); | |
glCheck(pglBindFramebuffer(GL_FRAMEBUFFER, 0u)); | |
// Create OpenGL texture on the shared context | |
assert(SDL_GL_MakeCurrent(sharedCtxHiddenWindow, sharedCtx)); | |
GLuint glTexture = 0u; | |
glCheck(glGenTextures(1, &glTexture)); | |
assert(glTexture); | |
// Initialize the texture | |
glCheck(glBindTexture(GL_TEXTURE_2D, glTexture)); | |
glCheck(glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, 256, 256, 0, GL_RGBA, GL_UNSIGNED_BYTE, NULL)); | |
// Revert to window context and bind default FBO | |
assert(SDL_GL_MakeCurrent(window, windowCtx)); | |
glCheck(pglBindFramebuffer(GL_FRAMEBUFFER, 0u)); | |
// Bind the texture to the window context and clear to red | |
glCheck(glClearColor(1.f, 0.f, 0.f, 1.f)); | |
glCheck(glClear(GL_COLOR_BUFFER_BIT)); | |
// Intentionally not calling `SDL_GL_SwapWindow` | |
// Create destination framebuffer and bind the previously created texture to it | |
GLuint destFrameBuffer = 0u; | |
glCheck(pglGenFramebuffers(1, &destFrameBuffer)); | |
assert(destFrameBuffer); | |
// Bind FBOs: read from the window FBO, write to the texture FBO | |
glCheck(pglBindFramebuffer(GL_READ_FRAMEBUFFER, 0u /* default FBO */)); | |
glCheck(pglBindFramebuffer(GL_DRAW_FRAMEBUFFER, destFrameBuffer)); | |
glCheck(pglFramebufferTexture2D(GL_DRAW_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, glTexture, 0)); | |
assert(pglCheckFramebufferStatus(GL_READ_FRAMEBUFFER) == GL_FRAMEBUFFER_COMPLETE); | |
assert(pglCheckFramebufferStatus(GL_DRAW_FRAMEBUFFER) == GL_FRAMEBUFFER_COMPLETE); | |
// (!) This context switching should be idempotent, but commenting out `sharedCtxPtr.makeCurrent(true)` FIXES THE ISSUE (!) | |
assert(SDL_GL_MakeCurrent(sharedCtxHiddenWindow, sharedCtx)); | |
assert(SDL_GL_MakeCurrent(window, windowCtx)); | |
// (!) Uncommenting this line fixes the issue even if the context switching or the query is not commented out (!) | |
// glCheck(pglBindFramebuffer(GL_FRAMEBUFFER, 0u)); | |
// (!) This query should be a noop, but commenting out `glGetIntegerv` FIXES THE ISSUE (!) | |
GLint out = 0u; | |
glCheck(glGetIntegerv(GL_READ_FRAMEBUFFER_BINDING, &out)); | |
// (!) EITHER the context switching or the query must be commented out to FIX THE ISSUE (!) | |
// (!) ALTERNATIVELY, the line `glCheck(pglBindFramebuffer(GL_FRAMEBUFFER, 0u))` must be uncommented (!) | |
// Blit the framebuffer from the window FBO to the texture FBO, then delete the framebuffer | |
glCheck(pglBindFramebuffer(GL_READ_FRAMEBUFFER, 0u)); | |
glCheck(pglBindFramebuffer(GL_DRAW_FRAMEBUFFER, destFrameBuffer)); | |
glCheck(pglBlitFramebuffer(0, 0, 256, 256, 0, 0, 256, 256, GL_COLOR_BUFFER_BIT, GL_NEAREST)); | |
glCheck(pglDeleteFramebuffers(1, &destFrameBuffer)); | |
// Write texture data into a pixel array and read it back | |
glCheck(glBindTexture(GL_TEXTURE_2D, glTexture)); | |
unsigned char pixels[256 * 256 * 4] = {0}; | |
GLuint imageFBO = 0u; | |
glCheck(pglGenFramebuffers(1, &imageFBO)); | |
assert(imageFBO); | |
glCheck(pglBindFramebuffer(GL_FRAMEBUFFER, imageFBO)); | |
glCheck(pglFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, glTexture, 0)); | |
glCheck(glReadPixels(0, 0, 256, 256, GL_RGBA, GL_UNSIGNED_BYTE, pixels)); | |
glCheck(pglDeleteFramebuffers(1, &imageFBO)); | |
const int index = (64 + 64 * 256) * 4; // pixel at (64, 64) | |
const unsigned char* pixel = &pixels[index]; | |
// This assertion will fail unless | |
// - EITHER the context switching or the query is commented out | |
// - OR `glBindFramebuffer(GL_FRAMEBUFFER, 0u)` is uncommented after the context switching | |
assert(pixel[0] == 255); | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment