0

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

9
  • 3
    Generally, doing user-interface operations in multiple threads is not possible, or comes with very bad disadvantages. Do all your window, rendering and event-loop operations in a single thread. Commented Jan 20 at 12:32
  • This is what most games does: there is a rendering thread and a game one (plus many others for IOs, external libraries, threads pools for differed parallel operations, etc). Generally, you can move the expensive computational parts in other threads and 1 thread for the rendering is often enough (especially once rendering operations are optimized). Commented Jan 20 at 13:16
  • I know but I will make it work, in future I will not be using SDL_Renderer, I just want the framework to be optimal. Edit: @ Jérôme Richard yeah, I love it. as ive said it works if I don't catch that specific error I think it works in the next iteration of the loop. Commented Jan 20 at 13:16
  • By the way, I wonder if spurious wake up are possible with SDL_CondWait like 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"). Commented Jan 20 at 13:26
  • This (closed) issue is certainly worth reading for you : github.com/libsdl-org/SDL/issues/11159 . They talk about thread safety in the SDL (with SDL developers answers), especially renderer-related functions. The first answer is pretty clear already IMHO. Commented Jan 20 at 13:28

0

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.