I was working on a cpu only rendering project with SDL in C. I implemented very good error handling and I got this error when I try to resize the window, "ERROR: SDL Error in render thread: Window surface is invalid, please call SDL_GetWindowSurface() (which is a internal function called automatically when window is resized for rendering) to get a new surface.", I want to fix the root of this problem without removing the error handling.
I think I am drawing to quickly for the SDL_GetWindowSurface() to be called internally while resizing, as resizing invalidates the surface of the window.
THE CODE:
#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include <math.h>
#include <SDL.h>
#include "windpiaware.h"
#pragma warning(disable: 4100) // Unreferenced formal parameter
#pragma warning(disable: 4026) // Function declaration mismatch
#pragma warning(disable: 4189) // Unreferenced local variable
#pragma warning(disable: 4820) // Padding added to struct
void HandleSDLErrorExit(int log_category, const char* error_title, const char* error_detail, SDL_Window* sdl_window){
char* error_message = NULL;
SDL_asprintf(&error_message, "%s: %s", error_detail, SDL_GetError());
SDL_LogError(log_category, error_message);
SetThreadDpiUnaware(); // To automatically adjust Message Box size according to the DPI
SDL_ShowSimpleMessageBox(SDL_MESSAGEBOX_ERROR, error_title, error_message, sdl_window);
SetThreadDpiAware();
SDL_free(error_message);
SDL_Quit();
SDL_Log("SDL quit");
#ifdef DEBUG
exit(EXIT_FAILURE); // used to counter debugger issues
#endif
}
void DisplaySDLError(int log_category, const char* error_title, const char* error_detail, SDL_Window* sdl_window){
char* error_message = NULL;
SDL_asprintf(&error_message, "%s: %s", error_detail, SDL_GetError());
SDL_LogError(log_category, error_message);
SetThreadDpiUnaware(); // To automatically adjust Message Box size according to the DPI
SDL_ShowSimpleMessageBox(SDL_MESSAGEBOX_ERROR, error_title, error_message, sdl_window);
SetThreadDpiAware();
SDL_free(error_message);
}
typedef struct
{
SDL_Window* sdl;
unsigned int id;
bool close;
} Window;
bool dpi_aware = false;
SDL_cond* show_window_cond;
SDL_mutex* show_window_mtx;
SDL_mutex* window_close_mtx;
SDL_mutex* sdl_window_mtx;
SDL_cond* render_cond;
SDL_mutex* render_mtx;
bool resized = false;
SDL_mutex* resized_mtx;
int event_handler(void *data, SDL_Event *event) {
Window* window = data;
if (event->type == SDL_WINDOWEVENT && event->window.windowID == window->id){
if (event->window.event == SDL_WINDOWEVENT_RESIZED){
SDL_LockMutex(resized_mtx);
resized = true;
SDL_UnlockMutex(resized_mtx);
}
else if (event->window.event == SDL_WINDOWEVENT_CLOSE){
SDL_LockMutex(window_close_mtx);
window->close = true;
SDL_UnlockMutex(window_close_mtx);
}
}
return 1;
}
int render_t(void* data){
Window* window = data;
SDL_Renderer* renderer = SDL_CreateRenderer(window->sdl, -1, SDL_RENDERER_SOFTWARE);
if (renderer == NULL) {
SDL_LockMutex(window_close_mtx);
window->close = true;
SDL_UnlockMutex(window_close_mtx);
DisplaySDLError(
SDL_LOG_CATEGORY_RENDER,
"Could'nt create SDL renderer",
"Failed to create SDL renderer",
window->sdl
);
return 1;
}
SDL_Log("SDL created renderer");
SDL_LockMutex(show_window_mtx);
SDL_CondSignal(show_window_cond);
SDL_UnlockMutex(show_window_mtx);
while (true) {
SDL_LockMutex(window_close_mtx);
if (window->close){
SDL_UnlockMutex(window_close_mtx);
break;
}
else {
SDL_UnlockMutex(window_close_mtx);
}
if (SDL_GetError()[0]){
DisplaySDLError(SDL_LOG_CATEGORY_ERROR, "SDL ERROR", "SDL Error in render thread", window->sdl);
SDL_ClearError();
SDL_Event window_close_event;
window_close_event.type = SDL_WINDOWEVENT;
window_close_event.window.event = SDL_WINDOWEVENT_CLOSE;
window_close_event.window.windowID = SDL_GetWindowID(window->sdl);
SDL_PushEvent(&window_close_event);
break;
}
SDL_SetRenderDrawColor(renderer, 255, 0, 128, 255);
SDL_RenderClear(renderer);
SDL_RenderPresent(renderer);
SDL_Delay(8);
}
SDL_DestroyRenderer(renderer);
SDL_Log("SDL destroyed renderer");
return 0;
}
int main(void) {
// Log compiled and runtime SDL versions
SDL_Log("Compiled SDL version: %u.%u.%u", SDL_MAJOR_VERSION, SDL_MINOR_VERSION, SDL_PATCHLEVEL);
SDL_version sdl_version;
SDL_GetVersion(&sdl_version);
SDL_Log("Current SDL version: %u.%u.%u", sdl_version.major, sdl_version.minor, sdl_version.patch);
// Ends
// Configures DPI awareness
#ifdef __ANDROID__
dpi_aware = true; // Android apps are DPI-aware by default
#else
#ifdef _WIN32
dpi_aware = SetProcessDpiAware(); // for Windows
#else
dpi_aware = SDL_SetHint(SDL_HINT_VIDEO_HIGHDPI_DISABLED, "0"); // for Linux, MacOS and others
#endif
#endif
SDL_Log(dpi_aware ? "DPI aware" : "DPI unaware");
// Ends
// Configures linux compositor settings (if applicable)
#if defined(__linux__) && SDL_VERSION_ATLEAST(2, 0, 8)
// "1" for low latency and "0" for better compatibility
#define BYPASS_COMPOSITOR 1
#if BYPASS_COMPOSITOR == 1
if (SDL_SetHint(SDL_HINT_VIDEO_X11_NET_WM_BYPASS_COMPOSITOR, "1")) {
SDL_Log("SDL bypassed X11 NET Window Manager Compositor");
}
else {
SDL_LogWarn(SDL_LOG_CATEGORY_VIDEO, "SDL could'nt bypass X11 NET Window Manager Compositor which could increase latency");
}
#else
if (SDL_SetHint(SDL_HINT_VIDEO_X11_NET_WM_BYPASS_COMPOSITOR, "0")) {
SDL_Log("SDL disabled X11 NET Window Manager Compositor bypass");
}
else {
SDL_LogWarn(SDL_LOG_CATEGORY_VIDEO, "SDL could'nt disable X11 NET Window Manager Compositor bypass which could cause compatibility issues");
}
#endif
#endif
// Ends
// Initializes SDL subsystems
// trys to disables potential for SDL_Surface (frame buffer / pixel data) GPU Acceleration
SDL_SetHint("SDL_HINT_FRAMEBUFFER_ACCELERATION", "0");
if (SDL_InitSubSystem(SDL_INIT_EVENTS | SDL_INIT_VIDEO) < 0){
HandleSDLErrorExit(
SDL_LOG_CATEGORY_VIDEO,
"Failed to initialize SDL",
"SDL failed to initialize events or/and video subsystem(s)",
NULL
);
return EXIT_FAILURE;
}
SDL_Log("SDL initialized events and video subsystems");
// Ends
// Detects video displays
if (SDL_GetNumVideoDisplays() < 1){
HandleSDLErrorExit(
SDL_LOG_CATEGORY_VIDEO,
"No video displays detected",
"SDL failed to detect any video display",
NULL
);
return EXIT_FAILURE;
}
SDL_Log("Primary display detected: %s", SDL_GetDisplayName(0));
// Ends
// Retrieves primary display modes
// Highest quality mode
SDL_DisplayMode primary_display;
SDL_GetDisplayMode(0, 0, &primary_display);
SDL_Log(
"Best primary display mode:"
"\n width: %dpx, height: %dpx"
"\n max refresh rate: %dHz"
"\n format: %s",
primary_display.w, primary_display.h,
primary_display.refresh_rate,
SDL_GetPixelFormatName(primary_display.format)
);
// Current desktop mode
SDL_DisplayMode primary_desktop_display;
SDL_GetDesktopDisplayMode(0, &primary_desktop_display);
// dpi = {horizontal, vertical, diagonal};
float dpi[3];
if (SDL_GetDisplayDPI(0, &dpi[2], &dpi[0], &dpi[1]) < 0){
SDL_LogWarn(SDL_LOG_CATEGORY_VIDEO, "DPI not found, defaulting to 96: %s", SDL_GetError());
dpi[0] = 96.0f;
dpi[1] = 96.0f;
dpi[2] = 96.0f;
}
// Calculates DSF (Display Scaling Factor)
// dsf = {horizontal, vertical, diagonal};
float dsf[] = {dpi[0] / 96.0f, dpi[1] / 96.0f, dpi[2] / 96.0f};
SDL_Log(
"Primary desktop display mode:"
"\n width: %dpx, height: %dpx"
"\n refresh rate: %dHz"
"\n format: %s"
"\n DPI:"
"\n horizontal: %.2f"
"\n vertical: %.2f"
"\n diagonal: %.2f"
"\n Display Scaling Factor:"
"\n horizontal: %.2f"
"\n vertical: %.2f"
"\n diagonal: %.2f",
primary_desktop_display.w, primary_desktop_display.h,
primary_desktop_display.refresh_rate,
SDL_GetPixelFormatName(primary_desktop_display.format),
dpi[0], dpi[1], dpi[2],
dsf[0], dsf[1], dsf[2]
);
// Ends
// Produces Window
Window window = {NULL, 0, false};
const float initial_window_size_scale = 0.3125f; // scaling factor for window size, range [0, 1]
window.sdl = SDL_CreateWindow(
"Window",
SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED,
// Calculate window size using the scaling factor, desktop display mode.
(int)(primary_desktop_display.w * initial_window_size_scale * dsf[0]),
(int)(primary_desktop_display.h * initial_window_size_scale * dsf[1]),
// ends
SDL_WINDOW_HIDDEN | SDL_WINDOW_RESIZABLE | SDL_WINDOW_ALLOW_HIGHDPI
);
if (window.sdl == NULL) {
HandleSDLErrorExit(
SDL_LOG_CATEGORY_VIDEO,
"Failed to create window",
"SDL failed to create window",
NULL
);
return EXIT_FAILURE;
}
SDL_Log("SDL created window");
// Ends
// Initialises render thread
show_window_cond = SDL_CreateCond();
show_window_mtx = SDL_CreateMutex();
window_close_mtx = SDL_CreateMutex();
sdl_window_mtx = SDL_CreateMutex();
render_cond = SDL_CreateCond();
render_mtx = SDL_CreateMutex();
resized_mtx = SDL_CreateMutex();
SDL_Thread* render_thread = SDL_CreateThread(render_t, "render", &window);
SDL_LockMutex(show_window_mtx);
SDL_CondWait(show_window_cond, show_window_mtx);
SDL_UnlockMutex(show_window_mtx);
// Ends
SDL_ShowWindow(window.sdl);
window.id = SDL_GetWindowID(window.sdl);
SDL_AddEventWatch(event_handler, &window);
SDL_Event event;
while (true)
{
SDL_LockMutex(window_close_mtx);
if (window.close){
SDL_UnlockMutex(window_close_mtx);
break;
}
else {
SDL_UnlockMutex(window_close_mtx);
}
if (SDL_GetError()[0]){
DisplaySDLError(SDL_LOG_CATEGORY_ERROR, "SDL ERROR", "SDL Error in main thread (Window Manager thread)", window.sdl);
SDL_ClearError();
SDL_LockMutex(window_close_mtx);
window.close = true;
SDL_UnlockMutex(window_close_mtx);
break;
}
SDL_WaitEvent(&event);
}
int exit_code = EXIT_SUCCESS;
// Cleans up
int render_thread_status;
SDL_WaitThread(render_thread, &render_thread_status);
exit_code = render_thread_status;
SDL_DelEventWatch(event_handler, &window);
// destroy mutexes and conditions
SDL_DestroyCond(show_window_cond);
SDL_DestroyMutex(show_window_mtx);
SDL_DestroyMutex(window_close_mtx);
SDL_DestroyMutex(sdl_window_mtx);
SDL_DestroyCond(render_cond);
SDL_DestroyMutex(render_mtx);
SDL_DestroyMutex(resized_mtx);
SDL_DestroyWindow(window.sdl);
SDL_Log("SDL destroyed window");
SDL_Quit();
SDL_Log("SDL quit");
// Ends
#ifdef DEBUG
exit(exit_code); // used to counter debugger issues
#else
return exit_code;
#endif
}
LOGS:
INFO: Compiled SDL version: 2.30.11
INFO: Current SDL version: 2.30.11
INFO: DPI aware
INFO: SDL initialized events and video subsystems
INFO: Primary display detected: Generic PnP Monitor
INFO: Best primary display mode:
width: 2880px, height: 1800px
max refresh rate: 120Hz
format: SDL_PIXELFORMAT_RGB888
INFO: Primary desktop display mode:
width: 2880px, height: 1800px
refresh rate: 120Hz
format: SDL_PIXELFORMAT_RGB888
DPI:
horizontal: 192.00
vertical: 192.00
diagonal: 192.00
Display Scaling Factor:
horizontal: 2.00
vertical: 2.00
diagonal: 2.00
INFO: SDL created window
INFO: SDL created renderer
ERROR: SDL Error in render thread: Window surface is invalid, please call SDL_GetWindowSurface() to get a new surface
INFO: SDL destroyed renderer
INFO: SDL destroyed window
INFO: SDL quit
I tried removing error handling, resizing and rendering worked in parallel with no issues and I also tried GPU acceleration with error handling, and it worked even better.
I want the window to keep rendering whilst I resize without compromising the error handling and the goal of the project (CPU rendering).
SDL_CondWaitlike in C++ or pthreads. This is certainly the case since IFAIK the SDL internally uses pthreads on Linux. This means the condition variable is not protected enough and so the application can fail in this case. You should add a condition (hence the name "condition variable").