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).