From a2671bed5de42f6867bacda3d60c47249c50d73f Mon Sep 17 00:00:00 2001 From: Maksymilian Wilczynski Date: Wed, 15 Apr 2026 18:23:28 +0200 Subject: [PATCH] image.cpp: remove unused macros, in-place conversion from rgba to argb. main.cpp: change move assignment in generic raii wrapper, change the Interpreter from struct to class to better reflect what it actually is. window.cpp: implement a workaround for the window to not freeze during dragging. fuck you ms. --- include/image.h | 4 +- src/image.cpp | 32 ++++---- src/main.cpp | 13 ++- src/window.cpp | 208 +++++++++++++++++++++++++++++++++--------------- 4 files changed, 164 insertions(+), 93 deletions(-) diff --git a/include/image.h b/include/image.h index d70c3d1..a095920 100644 --- a/include/image.h +++ b/include/image.h @@ -10,9 +10,9 @@ extern "C" { #include #endif -// Loads an image file as ARGB8888. Returns pointer to pixel data, sets width/height. +// Loads an image file as ARGB8888. Returns pointer to pixel data, sets width/height. NULL on failure. uint32_t* image_load(const char* filename, int* width, int* height); -// Loads and resizes an image file to desired dimensions as ARGB8888. Returns pointer to pixel data. +// Loads and resizes an image file to desired dimensions as ARGB8888. Returns pointer to pixel data. NULL on failure. uint32_t* image_load_resized(const char* filename, int desired_width, int desired_height); // Frees image data. void image_free(uint32_t* data); diff --git a/src/image.cpp b/src/image.cpp index 7682214..e26d5bc 100644 --- a/src/image.cpp +++ b/src/image.cpp @@ -9,9 +9,6 @@ #include #include -#define STR2(x) #x -#define STR(x) STR2(x) - extern "C" { uint32_t* image_load(const char* filename, int* width, int* height) { int n; @@ -41,7 +38,7 @@ uint32_t* image_load_resized(const char* filename, int desired_width, int desire if (!data) return nullptr; // Allocate buffer for resized image - unsigned char* resized = (unsigned char*)malloc(desired_width * desired_height * 4); + uint32_t* resized = (uint32_t*)malloc(desired_width * desired_height * 4); if (!resized) { stbi_image_free(data); return nullptr; @@ -49,26 +46,25 @@ uint32_t* image_load_resized(const char* filename, int desired_width, int desire // Resize the image stbir_resize_uint8_linear(data, original_width, original_height, 0, - resized, desired_width, desired_height, 0, + (unsigned char* )resized, desired_width, desired_height, 0, STBIR_RGBA); stbi_image_free(data); - // Convert RGBA to ARGB - uint32_t* out = (uint32_t*)malloc(desired_width * desired_height * sizeof(uint32_t)); - if (!out) { - free(resized); - return nullptr; - } + // In-place conversion from RGBA to ARGB + // We iterate backwards to ensure we don't overwrite bytes before reading them, + // though since we are working within the same 4-byte boundaries, forward works too! for (int i = 0; i < desired_width * desired_height; ++i) { - uint8_t r = resized[4*i + 0]; - uint8_t g = resized[4*i + 1]; - uint8_t b = resized[4*i + 2]; - uint8_t a = resized[4*i + 3]; - out[i] = (a << 24) | (r << 16) | (g << 8) | b; + uint8_t* pixel = (uint8_t*)&resized[i]; + uint8_t r = pixel[0]; + uint8_t g = pixel[1]; + uint8_t b = pixel[2]; + uint8_t a = pixel[3]; + + resized[i] = ((uint32_t)a << 24) | ((uint32_t)r << 16) | ((uint32_t)g << 8) | b; } - free(resized); - return out; + + return resized; } void image_free(uint32_t* data) { diff --git a/src/main.cpp b/src/main.cpp index e15a4df..5249ee7 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -38,13 +38,11 @@ struct EngineHandle { other.ptr = nullptr; // Nullify the old pointer so it doesn't get destroyed } - // ALLOW Move Assignment + // Move Assignment using Swap EngineHandle& operator=(EngineHandle&& other) noexcept { - if (this != &other) { - if (ptr) DestroyFn(ptr); // Clean up our current resource first - ptr = other.ptr; // Steal the resource from the other handle - other.ptr = nullptr; // Nullify the old one - } + T* temp = other.ptr; + other.ptr = ptr; + ptr = temp; return *this; } @@ -84,7 +82,8 @@ enum class WaitKind { END, // script finished, keep rendering until told to quit }; -struct Interpreter { +class Interpreter { +public: std::vector script; size_t pc = 0; diff --git a/src/window.cpp b/src/window.cpp index 0a7e449..134af69 100644 --- a/src/window.cpp +++ b/src/window.cpp @@ -1,9 +1,20 @@ +// window.cpp +// This file implements the windowing and input system using SDL2. +// It provides a simple API for creating a window, blitting pixel data, and handling input events. +// Since Microsoft is retarded and forces us to live in 1985, we use a thread to manage the window +// to avoid freezing the app during window move or resize operations. Of course, normal OSes don't need that. #include #include #include #include #include +#ifdef _WIN32 +#include +#include +#include +#endif + struct window_t { SDL_Window* window; SDL_Renderer* renderer; @@ -15,9 +26,21 @@ struct window_t { int mouse_x; int mouse_y; uint32_t mouse_buttons; + +#ifdef _WIN32 + std::thread worker; + std::atomic running{true}; + std::atomic quit_requested{false}; + + std::mutex buffer_mutex; + uint32_t* shared_fb; + + std::mutex input_mutex; + uint8_t kb_copy[SDL_NUM_SCANCODES]; +#endif }; -// Internal helper +// Internal helper for cleanup static void window_cleanup(window_t* s) { if (!s) return; if (s->texture) SDL_DestroyTexture(s->texture); @@ -25,71 +48,152 @@ static void window_cleanup(window_t* s) { if (s->window) SDL_DestroyWindow(s->window); } +#ifdef _WIN32 +// Internal function to perform the actual blit and present on the worker thread +static void win32_internal_render(window_t* s) { + if (!s->renderer || !s->texture) return; + + void* pixels; + int pitch; + if (SDL_LockTexture(s->texture, nullptr, &pixels, &pitch) == 0) { + { + std::lock_guard lock(s->buffer_mutex); + std::memcpy(pixels, s->shared_fb, s->width * s->height * 4); + } + SDL_UnlockTexture(s->texture); + } + + SDL_RenderClear(s->renderer); + SDL_RenderCopy(s->renderer, s->texture, nullptr, nullptr); + SDL_RenderPresent(s->renderer); +} + +// THE FIX: This callback is triggered by SDL even when Windows is in a modal drag loop. +static int SDLCALL win32_event_watch(void* userdata, SDL_Event* event) { + window_t* s = (window_t*)userdata; + if (event->type == SDL_WINDOWEVENT) { + if (event->window.event == SDL_WINDOWEVENT_MOVED || + event->window.event == SDL_WINDOWEVENT_RESIZED || + event->window.event == SDL_WINDOWEVENT_EXPOSED) { + win32_internal_render(s); + } + } + return 0; +} + +static void win32_window_worker(window_t* s, const char* title) { + SDL_InitSubSystem(SDL_INIT_VIDEO); + + s->window = SDL_CreateWindow(title, SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED, s->width, s->height, 0); + s->renderer = SDL_CreateRenderer(s->window, -1, SDL_RENDERER_ACCELERATED); + s->texture = SDL_CreateTexture(s->renderer, SDL_PIXELFORMAT_ARGB8888, SDL_TEXTUREACCESS_STREAMING, s->width, s->height); + + // Register the watch to handle rendering during the drag-loop + SDL_AddEventWatch(win32_event_watch, s); + + while (s->running) { + SDL_Event ev; + // Use a timeout so we don't hog CPU but stay responsive + if (SDL_WaitEventTimeout(&ev, 8)) { + if (ev.type == SDL_QUIT) s->quit_requested = true; + } + + // Sync input state for the main thread + { + std::lock_guard lock(s->input_mutex); + const uint8_t* state = SDL_GetKeyboardState(nullptr); + std::memcpy(s->kb_copy, state, SDL_NUM_SCANCODES); + s->mouse_buttons = SDL_GetMouseState(&s->mouse_x, &s->mouse_y); + } + + // Normal frame render + win32_internal_render(s); + } + + SDL_DelEventWatch(win32_event_watch, s); + window_cleanup(s); + SDL_QuitSubSystem(SDL_INIT_VIDEO); +} +#endif + extern "C" { window_t* window_create(int width, int height, const char* title) { - if (SDL_InitSubSystem(SDL_INIT_VIDEO) != 0) { - std::fprintf(stderr, "SDL_InitSubSystem failed: %s\n", SDL_GetError()); - return nullptr; - } - +#ifdef _WIN32 + window_t* s = new window_t{}; + s->width = width; + s->height = height; + s->shared_fb = new uint32_t[width * height]; + s->keyboard_state = s->kb_copy; + + // Spawn the worker thread which owns the SDL context + s->worker = std::thread(win32_window_worker, s, title); + + // Wait briefly for the worker to initialize window/renderer pointers + while (s->running && !s->renderer) { std::this_thread::yield(); } + return s; +#else + // Original non-threaded implementation for Linux/others + if (SDL_InitSubSystem(SDL_INIT_VIDEO) != 0) return nullptr; window_t* s = new window_t{}; - if (!s) return nullptr; - s->width = width; s->height = height; - s->window = SDL_CreateWindow(title, SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED, width, height, 0); - if (!s->window) { - std::fprintf(stderr, "SDL_CreateWindow failed: %s\n", SDL_GetError()); - delete s; - return nullptr; - } - s->renderer = SDL_CreateRenderer(s->window, -1, SDL_RENDERER_ACCELERATED); - if (!s->renderer) { - std::fprintf(stderr, "SDL_CreateRenderer failed: %s\n", SDL_GetError()); - window_cleanup(s); - delete s; - return nullptr; - } - - s->texture = SDL_CreateTexture(s->renderer, SDL_PIXELFORMAT_ARGB8888, - SDL_TEXTUREACCESS_STREAMING, width, height); - if (!s->texture) { - std::fprintf(stderr, "SDL_CreateTexture failed: %s\n", SDL_GetError()); - window_cleanup(s); - delete s; - return nullptr; - } - + s->texture = SDL_CreateTexture(s->renderer, SDL_PIXELFORMAT_ARGB8888, SDL_TEXTUREACCESS_STREAMING, width, height); s->keyboard_state = SDL_GetKeyboardState(nullptr); return s; +#endif } void window_destroy(window_t* s) { if (!s) return; - +#ifdef _WIN32 + s->running = false; + if (s->worker.joinable()) s->worker.join(); + delete[] s->shared_fb; +#else window_cleanup(s); SDL_QuitSubSystem(SDL_INIT_VIDEO); +#endif delete s; } bool window_pollevent(window_t* s) { +#ifdef _WIN32 + // Main thread just checks the status reported by the worker + std::lock_guard lock(s->input_mutex); + return s->quit_requested; +#else SDL_Event ev; bool quit = false; - while (SDL_PollEvent(&ev)) { - if (ev.type == SDL_QUIT) - quit = true; + if (ev.type == SDL_QUIT) quit = true; } - s->mouse_buttons = SDL_GetMouseState(&s->mouse_x, &s->mouse_y); return quit; +#endif } +void window_blit(window_t* s, const uint32_t* fb) { +#ifdef _WIN32 + // Just update the shared buffer; the worker thread will pick it up and present it + std::lock_guard lock(s->buffer_mutex); + std::memcpy(s->shared_fb, fb, s->width * s->height * 4); +#else + // Original immediate blit logic + void* pixels; int pitch; + SDL_LockTexture(s->texture, nullptr, &pixels, &pitch); + std::memcpy(pixels, fb, s->width * s->height * 4); + SDL_UnlockTexture(s->texture); + SDL_RenderClear(s->renderer); + SDL_RenderCopy(s->renderer, s->texture, nullptr, nullptr); + SDL_RenderPresent(s->renderer); +#endif +} + +// Input getters remain unchanged as they read from the struct's state fields bool window_is_key_down(window_t* s, int sc) { - if (!s->keyboard_state) return false; if (sc < 0 || sc >= SDL_NUM_SCANCODES) return false; return s->keyboard_state[sc] != 0; } @@ -104,35 +208,7 @@ bool window_is_mouse_button_down(window_t* s, int button) { } mouse_pos_t window_get_mouse_position(window_t* s) { - mouse_pos_t pos = { s->mouse_x, s->mouse_y }; - return pos; -} - -void window_blit(window_t* s, const uint32_t* fb) { - if (!s->texture) return; - - void* pixels; - int pitch; - - SDL_LockTexture(s->texture, nullptr, &pixels, &pitch); - - const int expected_pitch = s->width * 4; - const size_t total = (size_t)s->width * (size_t)s->height * 4; - - if (pitch == expected_pitch) { - std::memcpy(pixels, fb, total); - } else { - for (int y = 0; y < s->height; ++y) { - uint8_t* row = (uint8_t*)pixels + y * pitch; - std::memcpy(row, fb + y * s->width, expected_pitch); - } - } - - SDL_UnlockTexture(s->texture); - - SDL_RenderClear(s->renderer); - SDL_RenderCopy(s->renderer, s->texture, nullptr, nullptr); - SDL_RenderPresent(s->renderer); + return { s->mouse_x, s->mouse_y }; } }