spaces and LF please

This commit is contained in:
maksw committed 2026-04-15 18:27:18 +02:00
1 parent a2671bed5d
commit cd6332fe0a
11 files changed
+3616 -3616

No files matched your search

+79 -79
View File
@@ -1,80 +1,80 @@
// audio.h // audio.h
#pragma once #pragma once
#ifndef AUDIO_H #ifndef AUDIO_H
#define AUDIO_H #define AUDIO_H
#ifdef __cplusplus #ifdef __cplusplus
#include <cstdint> #include <cstdint>
extern "C" { extern "C" {
#else #else
#include <stdint.h> #include <stdint.h>
#include <stdbool.h> #include <stdbool.h>
#endif #endif
typedef struct audio_t audio_t; typedef struct audio_t audio_t;
typedef uint32_t audio_stream_id_t; typedef uint32_t audio_stream_id_t;
// If the return type is bool, false means failure. // If the return type is bool, false means failure.
// Initialize audio system // Initialize audio system
// nullptr means failure // nullptr means failure
audio_t* audio_init(void); audio_t* audio_init(void);
// Destroy audio system and free resources // Destroy audio system and free resources
void audio_destroy(audio_t* audio); void audio_destroy(audio_t* audio);
// Load MADPCM data from a WAV file. // Load MADPCM data from a WAV file.
// Returns a pointer to the allocated audio data, or nullptr on failure. // Returns a pointer to the allocated audio data, or nullptr on failure.
// The returned buffer must be freed with audio_free_data(). // The returned buffer must be freed with audio_free_data().
uint8_t* audio_load_madpcm(const char* filename, int* out_size, int* out_channels, int* out_rate, int* out_total_samples); uint8_t* audio_load_madpcm(const char* filename, int* out_size, int* out_channels, int* out_rate, int* out_total_samples);
// Free data allocated by audio_load_madpcm // Free data allocated by audio_load_madpcm
void audio_free_data(uint8_t* data); void audio_free_data(uint8_t* data);
// Play a MADPCM stream, returns unique ID // Play a MADPCM stream, returns unique ID
// data must remain valid until the stream ends or is stopped // data must remain valid until the stream ends or is stopped
// loop: if true, the stream restarts on EOF // loop: if true, the stream restarts on EOF
// 0 means failure, valid IDs start from 1 // 0 means failure, valid IDs start from 1
audio_stream_id_t audio_play(audio_t* audio, audio_stream_id_t audio_play(audio_t* audio,
const uint8_t* data, const uint8_t* data,
int size, int size,
int channels, int channels,
int rate, int rate,
bool loop); bool loop);
// Stop and remove stream by ID // Stop and remove stream by ID
bool audio_stop(audio_t* audio, audio_stream_id_t id); bool audio_stop(audio_t* audio, audio_stream_id_t id);
bool audio_pause(audio_t* audio, audio_stream_id_t id); bool audio_pause(audio_t* audio, audio_stream_id_t id);
bool audio_resume(audio_t* audio, audio_stream_id_t id); bool audio_resume(audio_t* audio, audio_stream_id_t id);
// Returns true if the stream is active (playing or paused) // Returns true if the stream is active (playing or paused)
bool audio_is_active(audio_t* audio, audio_stream_id_t id); bool audio_is_active(audio_t* audio, audio_stream_id_t id);
// Returns true if the stream is currently playing (not paused, not stopped) // Returns true if the stream is currently playing (not paused, not stopped)
bool audio_is_playing(audio_t* audio, audio_stream_id_t id); bool audio_is_playing(audio_t* audio, audio_stream_id_t id);
// Per-stream volume, immediate effect, range [0.0, 1.0] // Per-stream volume, immediate effect, range [0.0, 1.0]
bool audio_set_volume(audio_t* audio, audio_stream_id_t id, float volume); bool audio_set_volume(audio_t* audio, audio_stream_id_t id, float volume);
// Animate per-stream volume linearly over time, driven by audio_tick() // Animate per-stream volume linearly over time, driven by audio_tick()
bool audio_animate_volume(audio_t* audio, audio_stream_id_t id, bool audio_animate_volume(audio_t* audio, audio_stream_id_t id,
float to_volume, float duration_sec); float to_volume, float duration_sec);
// Global volume, immediate effect, range [0.0, 1.0] // Global volume, immediate effect, range [0.0, 1.0]
// Applied on top of per-stream volume at mix time // Applied on top of per-stream volume at mix time
void audio_set_global_volume(audio_t* audio, float volume); void audio_set_global_volume(audio_t* audio, float volume);
// Animate global volume linearly over time, driven by audio_tick() // Animate global volume linearly over time, driven by audio_tick()
void audio_animate_global_volume(audio_t* audio, void audio_animate_global_volume(audio_t* audio,
float to_volume, float duration_sec); float to_volume, float duration_sec);
// Advances stream decoding and volume animations // Advances stream decoding and volume animations
// Call once per main loop iteration with the same delta as compositor_tick() // Call once per main loop iteration with the same delta as compositor_tick()
void audio_tick(audio_t* audio, float delta_time_sec); void audio_tick(audio_t* audio, float delta_time_sec);
#ifdef __cplusplus #ifdef __cplusplus
} }
#endif #endif
#endif // AUDIO_H #endif // AUDIO_H
+119 -119
View File
@@ -1,120 +1,120 @@
// compositor.h v0.3 // compositor.h v0.3
#pragma once #pragma once
#ifndef COMPOSITOR_H #ifndef COMPOSITOR_H
#define COMPOSITOR_H #define COMPOSITOR_H
// This subsystem is thread-unsafe. // This subsystem is thread-unsafe.
// Caller must serialize all calls. Calls do not need to be made from the main thread. // Caller must serialize all calls. Calls do not need to be made from the main thread.
// Removing an element while ticking/rendering may crash. // Removing an element while ticking/rendering may crash.
// Modifying an element while ticking/rendering may cause visual glitches. // Modifying an element while ticking/rendering may cause visual glitches.
// Easing functions // Easing functions
enum easing_t { enum easing_t {
EASE_LINEAR = 0, EASE_LINEAR = 0,
EASE_IN_QUAD, EASE_IN_QUAD,
EASE_OUT_QUAD, EASE_OUT_QUAD,
EASE_IN_OUT_QUAD, EASE_IN_OUT_QUAD,
EASE_IN_CUBIC, EASE_IN_CUBIC,
EASE_OUT_CUBIC, EASE_OUT_CUBIC,
EASE_IN_OUT_CUBIC EASE_IN_OUT_CUBIC
}; };
// Anchor points // Anchor points
enum anchor_point_t { enum anchor_point_t {
ANCHOR_TOP_LEFT, ANCHOR_TOP_LEFT,
ANCHOR_TOP_CENTER, ANCHOR_TOP_CENTER,
ANCHOR_TOP_RIGHT, ANCHOR_TOP_RIGHT,
ANCHOR_CENTER_LEFT, ANCHOR_CENTER_LEFT,
ANCHOR_CENTER, ANCHOR_CENTER,
ANCHOR_CENTER_RIGHT, ANCHOR_CENTER_RIGHT,
ANCHOR_BOTTOM_LEFT, ANCHOR_BOTTOM_LEFT,
ANCHOR_BOTTOM_CENTER, ANCHOR_BOTTOM_CENTER,
ANCHOR_BOTTOM_RIGHT, ANCHOR_BOTTOM_RIGHT,
}; };
#ifdef __cplusplus #ifdef __cplusplus
#include <cstdint> #include <cstdint>
extern "C" { extern "C" {
#else #else
#include <stdint.h> #include <stdint.h>
#include <stdbool.h> #include <stdbool.h>
#endif #endif
typedef struct compositor_t compositor_t; typedef struct compositor_t compositor_t;
typedef uint32_t element_handle_t; typedef uint32_t element_handle_t;
// If the return type is bool, false means failure. // If the return type is bool, false means failure.
// Initialize compositor with given dimensions // Initialize compositor with given dimensions
// nullptr means failure // nullptr means failure
compositor_t* compositor_init(uint32_t width, uint32_t height); compositor_t* compositor_init(uint32_t width, uint32_t height);
// Destroy compositor and free resources. Everything dies with it. // Destroy compositor and free resources. Everything dies with it.
void compositor_destroy(compositor_t* comp); void compositor_destroy(compositor_t* comp);
// Insert element, returns unique ID // Insert element, returns unique ID
// FB format is ARGB8888 and it always will be // FB format is ARGB8888 and it always will be
// The compositor takes a reference, caller must keep alive until removed // The compositor takes a reference, caller must keep alive until removed
// 0 means failure, valid IDs start from 1 // 0 means failure, valid IDs start from 1
element_handle_t compositor_insert(compositor_t* comp, element_handle_t compositor_insert(compositor_t* comp,
const uint32_t* fb, const uint32_t* fb,
uint32_t fb_width, uint32_t fb_width,
uint32_t fb_height, uint32_t fb_height,
int32_t x, int32_t y, int32_t x, int32_t y,
int32_t z_order, float scale, int32_t z_order, float scale,
anchor_point_t anchor); anchor_point_t anchor);
// Remove element by ID // Remove element by ID
bool compositor_remove(compositor_t* comp, element_handle_t id); bool compositor_remove(compositor_t* comp, element_handle_t id);
// Setters, immediate effect // Setters, immediate effect
bool compositor_set_position(compositor_t* comp, element_handle_t id, bool compositor_set_position(compositor_t* comp, element_handle_t id,
int32_t x, int32_t y); int32_t x, int32_t y);
bool compositor_set_zorder(compositor_t* comp, element_handle_t id, bool compositor_set_zorder(compositor_t* comp, element_handle_t id,
int32_t z_order); int32_t z_order);
bool compositor_set_scale(compositor_t* comp, element_handle_t id, bool compositor_set_scale(compositor_t* comp, element_handle_t id,
float scale); float scale);
bool compositor_set_visible(compositor_t* comp, element_handle_t id, bool compositor_set_visible(compositor_t* comp, element_handle_t id,
bool visible); bool visible);
bool compositor_set_alpha(compositor_t* comp, element_handle_t id, bool compositor_set_alpha(compositor_t* comp, element_handle_t id,
float alpha); float alpha);
// Advances all animations by a given time delta. // Advances all animations by a given time delta.
void compositor_tick(compositor_t* comp, float delta_time_sec); void compositor_tick(compositor_t* comp, float delta_time_sec);
// Sets a target state to advance over time by tick() // Sets a target state to advance over time by tick()
// Queue means the animation will start after the previous animation completed // Queue means the animation will start after the previous animation completed
// Not queueing will play the animation alongside any current animation // Not queueing will play the animation alongside any current animation
// You cannot stack the same type of animation without queueing, the second call will cancel the first call. // You cannot stack the same type of animation without queueing, the second call will cancel the first call.
bool compositor_animate_move(compositor_t* comp, element_handle_t id, bool compositor_animate_move(compositor_t* comp, element_handle_t id,
int32_t to_x, int32_t to_y, int32_t to_x, int32_t to_y,
float duration_sec, easing_t easing, bool queue, bool parallel); float duration_sec, easing_t easing, bool queue, bool parallel);
bool compositor_animate_fade(compositor_t* comp, element_handle_t id, bool compositor_animate_fade(compositor_t* comp, element_handle_t id,
float to_alpha, float to_alpha,
float duration_sec, easing_t easing, bool queue, bool parallel); float duration_sec, easing_t easing, bool queue, bool parallel);
bool compositor_animate_scale(compositor_t* comp, element_handle_t id, bool compositor_animate_scale(compositor_t* comp, element_handle_t id,
float to_scale, float to_scale,
float duration_sec, easing_t easing, bool queue, bool parallel); float duration_sec, easing_t easing, bool queue, bool parallel);
// Immediately finish all animations on a specific object (optionally only N queued, 0 = all), or every object. // Immediately finish all animations on a specific object (optionally only N queued, 0 = all), or every object.
bool compositor_animation_finish_element(compositor_t* comp, element_handle_t id, int num_queued /*0=all*/); bool compositor_animation_finish_element(compositor_t* comp, element_handle_t id, int num_queued /*0=all*/);
void compositor_animation_finish_all(compositor_t* comp); void compositor_animation_finish_all(compositor_t* comp);
// Returns true if any element is animating or has queued animations. // Returns true if any element is animating or has queued animations.
bool compositor_is_animating(compositor_t* comp); bool compositor_is_animating(compositor_t* comp);
// Returns true if the specified element is animating or has queued animations. // Returns true if the specified element is animating or has queued animations.
bool compositor_is_element_animating(compositor_t* comp, element_handle_t id); bool compositor_is_element_animating(compositor_t* comp, element_handle_t id);
// Compose all visible elements and return framebuffer pointer // Compose all visible elements and return framebuffer pointer
const uint32_t* compositor_compose(compositor_t* comp); const uint32_t* compositor_compose(compositor_t* comp);
// Mark an element's content as dirty for redrawing // Mark an element's content as dirty for redrawing
bool compositor_mark_content_dirty(compositor_t* comp, element_handle_t id); bool compositor_mark_content_dirty(compositor_t* comp, element_handle_t id);
void compositor_mark_all_dirty(compositor_t* comp); void compositor_mark_all_dirty(compositor_t* comp);
#ifdef __cplusplus #ifdef __cplusplus
} }
#endif #endif
#endif // COMPOSITOR_H #endif // COMPOSITOR_H
+24 -24
View File
@@ -1,24 +1,24 @@
// image.h // image.h
#pragma once #pragma once
#ifndef IMAGE_H #ifndef IMAGE_H
#define IMAGE_H #define IMAGE_H
#ifdef __cplusplus #ifdef __cplusplus
#include <cstdint> #include <cstdint>
extern "C" { extern "C" {
#else #else
#include <stdint.h> #include <stdint.h>
#endif #endif
// Loads an image file as ARGB8888. Returns pointer to pixel data, sets width/height. NULL on failure. // 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); 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. NULL on failure. // 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); uint32_t* image_load_resized(const char* filename, int desired_width, int desired_height);
// Frees image data. // Frees image data.
void image_free(uint32_t* data); void image_free(uint32_t* data);
#ifdef __cplusplus #ifdef __cplusplus
} }
#endif #endif
#endif // IMAGE_H #endif // IMAGE_H
+44 -44
View File
@@ -1,45 +1,45 @@
// text.h // text.h
#pragma once #pragma once
#ifndef TEXT_RENDERER_H #ifndef TEXT_RENDERER_H
#define TEXT_RENDERER_H #define TEXT_RENDERER_H
#include <stdint.h> #include <stdint.h>
struct text_t; struct text_t;
#ifdef __cplusplus #ifdef __cplusplus
#include <cstdint> #include <cstdint>
extern "C" { extern "C" {
#else #else
#include <stdint.h> #include <stdint.h>
#endif #endif
// initializes a new text renderer with given width and height // initializes a new text renderer with given width and height
text_t* text_init(int width, int height); text_t* text_init(int width, int height);
// shares cache, prefer when using the same font to save memory // shares cache, prefer when using the same font to save memory
text_t* text_init_shared(text_t* source, int width, int height); text_t* text_init_shared(text_t* source, int width, int height);
// destroys the text renderer and frees resources // destroys the text renderer and frees resources
void text_destroy(text_t* text); void text_destroy(text_t* text);
// pushes new text buffer to be rendered, can be stacked // pushes new text buffer to be rendered, can be stacked
void text_push(text_t* text, void text_push(text_t* text,
const char* str, const char* str,
float speed, float speed,
bool outline); bool outline);
// load font and set as current font // load font and set as current font
void text_set_font(text_t* text, const char* font_path, float size); void text_set_font(text_t* text, const char* font_path, float size);
// finish whole current text buffer immediately // finish whole current text buffer immediately
void text_show_all_current(text_t* text); void text_show_all_current(text_t* text);
// returns true if text is still animating // returns true if text is still animating
bool text_is_animating(text_t* text); bool text_is_animating(text_t* text);
// advance, call every frame, returns true when it touched the framebuffer // advance, call every frame, returns true when it touched the framebuffer
bool text_nextframe(text_t* text, float deltatime); bool text_nextframe(text_t* text, float deltatime);
// gets the framebuffer of the text renderer // gets the framebuffer of the text renderer
const uint32_t* text_getfb(text_t* text); const uint32_t* text_getfb(text_t* text);
// clears the text buffer and the frame buffer // clears the text buffer and the frame buffer
void text_clear(text_t* text); void text_clear(text_t* text);
#ifdef __cplusplus #ifdef __cplusplus
} }
#endif #endif
#endif // TEXT_RENDERER_H #endif // TEXT_RENDERER_H
+77 -77
View File
@@ -1,77 +1,77 @@
// window.h // window.h
#pragma once #pragma once
#ifndef WINDOW_H #ifndef WINDOW_H
#define WINDOW_H #define WINDOW_H
typedef struct window_t window_t; typedef struct window_t window_t;
enum KeyCode { enum KeyCode {
// Letters // Letters
KEY_A = 4, KEY_A = 4,
KEY_D = 7, KEY_D = 7,
KEY_S = 22, KEY_S = 22,
KEY_W = 26, KEY_W = 26,
// Numbers // Numbers
KEY_1 = 30, KEY_1 = 30,
KEY_0 = 39, KEY_0 = 39,
// Controls // Controls
KEY_RETURN = 40, KEY_RETURN = 40,
KEY_ESCAPE = 41, KEY_ESCAPE = 41,
KEY_BACKSPACE = 42, KEY_BACKSPACE = 42,
KEY_TAB = 43, KEY_TAB = 43,
KEY_SPACE = 44, KEY_SPACE = 44,
// Modifiers // Modifiers
KEY_LCTRL = 224, KEY_LCTRL = 224,
KEY_LSHIFT = 225, KEY_LSHIFT = 225,
KEY_RCTRL = 228, KEY_RCTRL = 228,
KEY_RSHIFT = 229, KEY_RSHIFT = 229,
// Navigation // Navigation
KEY_RIGHT = 79, KEY_RIGHT = 79,
KEY_LEFT = 80, KEY_LEFT = 80,
KEY_DOWN = 81, KEY_DOWN = 81,
KEY_UP = 82, KEY_UP = 82,
// Function Keys // Function Keys
KEY_F1 = 58, KEY_F1 = 58,
KEY_F2 = 59 KEY_F2 = 59
}; };
enum MouseButton { enum MouseButton {
MOUSE_BUTTON_LEFT = 1, MOUSE_BUTTON_LEFT = 1,
MOUSE_BUTTON_MIDDLE = 2, MOUSE_BUTTON_MIDDLE = 2,
MOUSE_BUTTON_RIGHT = 3 MOUSE_BUTTON_RIGHT = 3
}; };
#ifdef __cplusplus #ifdef __cplusplus
#include <cstdint> #include <cstdint>
extern "C" { extern "C" {
#else #else
#include <stdint.h> #include <stdint.h>
#endif #endif
typedef struct { int x, y; } mouse_pos_t; typedef struct { int x, y; } mouse_pos_t;
// creates a window with given width, height and title, title can be utf8 // creates a window with given width, height and title, title can be utf8
window_t* window_create(int width, int height, const char* title); window_t* window_create(int width, int height, const char* title);
// destroys the window // destroys the window
void window_destroy(window_t* s); void window_destroy(window_t* s);
// polls window events, returns true if the window should close // polls window events, returns true if the window should close
bool window_pollevent(window_t* s); bool window_pollevent(window_t* s);
// blits the given framebuffer to the window. must be the same size as the window and ARGB8888 format // blits the given framebuffer to the window. must be the same size as the window and ARGB8888 format
void window_blit(window_t* s, const uint32_t* framebuffer); void window_blit(window_t* s, const uint32_t* framebuffer);
// returns true if the given key is currently held down // returns true if the given key is currently held down
bool window_is_key_down(window_t* s, int key_scancode); bool window_is_key_down(window_t* s, int key_scancode);
// returns true if the given mouse button is currently held down // returns true if the given mouse button is currently held down
bool window_is_mouse_button_down(window_t* s, int button_index); bool window_is_mouse_button_down(window_t* s, int button_index);
// gets the current mouse position in window coordinates // gets the current mouse position in window coordinates
mouse_pos_t window_get_mouse_position(window_t* s); mouse_pos_t window_get_mouse_position(window_t* s);
#ifdef __cplusplus #ifdef __cplusplus
} }
#endif #endif
#endif // WINDOW_H #endif // WINDOW_H
+343 -343
View File
@@ -1,343 +1,343 @@
// audio.cpp // audio.cpp
#include "audio.h" #include "audio.h"
#include <SDL2/SDL.h> #include <SDL2/SDL.h>
#define MADPCM_IMPLEMENTATION #define MADPCM_IMPLEMENTATION
#include "madpcm.h" #include "madpcm.h"
#include <unordered_map> #include <unordered_map>
#include <vector> #include <vector>
#include <cstring> #include <cstring>
#include <algorithm> #include <algorithm>
#define AHEAD_BLOCKS 10 #define AHEAD_BLOCKS 10
struct AudioDevice { struct AudioDevice {
SDL_AudioDeviceID id = 0; SDL_AudioDeviceID id = 0;
AudioDevice() = default; AudioDevice() = default;
explicit AudioDevice(SDL_AudioDeviceID id) : id(id) {} explicit AudioDevice(SDL_AudioDeviceID id) : id(id) {}
~AudioDevice() { if (id) SDL_CloseAudioDevice(id); } ~AudioDevice() { if (id) SDL_CloseAudioDevice(id); }
AudioDevice(const AudioDevice&) = delete; AudioDevice(const AudioDevice&) = delete;
AudioDevice& operator=(const AudioDevice&) = delete; AudioDevice& operator=(const AudioDevice&) = delete;
AudioDevice(AudioDevice&& o) noexcept : id(o.id) { o.id = 0; } AudioDevice(AudioDevice&& o) noexcept : id(o.id) { o.id = 0; }
AudioDevice& operator=(AudioDevice&& o) noexcept { AudioDevice& operator=(AudioDevice&& o) noexcept {
if (this != &o) { if (id) SDL_CloseAudioDevice(id); id = o.id; o.id = 0; } if (this != &o) { if (id) SDL_CloseAudioDevice(id); id = o.id; o.id = 0; }
return *this; return *this;
} }
operator bool() const { return id != 0; } operator bool() const { return id != 0; }
}; };
struct Stream { struct Stream {
uint32_t id = 0; uint32_t id = 0;
AudioDevice device; AudioDevice device;
const uint8_t* data = nullptr; const uint8_t* data = nullptr;
int total_blocks = 0; int total_blocks = 0;
int current_block = 0; int current_block = 0;
int channels = 0; int channels = 0;
bool loop = false; bool loop = false;
bool paused = false; bool paused = false;
float volume = 1.0f; float volume = 1.0f;
float volume_from = 1.0f; float volume_from = 1.0f;
float volume_to = 1.0f; float volume_to = 1.0f;
float volume_anim_elapsed = 0.0f; float volume_anim_elapsed = 0.0f;
float volume_anim_duration = 0.0f; float volume_anim_duration = 0.0f;
}; };
struct RiffHeader { struct RiffHeader {
char riff[4]; char riff[4];
uint32_t fileSize; uint32_t fileSize;
char wave[4]; char wave[4];
}; };
struct audio_t { struct audio_t {
std::unordered_map<uint32_t, Stream> streams; std::unordered_map<uint32_t, Stream> streams;
uint32_t next_id = 1; uint32_t next_id = 1;
float global_volume = 1.0f; float global_volume = 1.0f;
float global_volume_from = 1.0f; float global_volume_from = 1.0f;
float global_volume_to = 1.0f; float global_volume_to = 1.0f;
float global_volume_anim_elapsed = 0.0f; float global_volume_anim_elapsed = 0.0f;
float global_volume_anim_duration = 0.0f; float global_volume_anim_duration = 0.0f;
}; };
static float clampf(float v, float lo, float hi) { static float clampf(float v, float lo, float hi) {
return v < lo ? lo : v > hi ? hi : v; return v < lo ? lo : v > hi ? hi : v;
} }
// Decode one block, scale by combined volume, queue to device. // Decode one block, scale by combined volume, queue to device.
// Returns false if the stream has ended (non-looping, EOF). // Returns false if the stream has ended (non-looping, EOF).
static bool pump_block(audio_t* audio, Stream& s) { static bool pump_block(audio_t* audio, Stream& s) {
int block_size = (s.channels == 2) ? MADPCM_STEREO_SIZE : MADPCM_MONO_SIZE; int block_size = (s.channels == 2) ? MADPCM_STEREO_SIZE : MADPCM_MONO_SIZE;
if (s.current_block >= s.total_blocks) { if (s.current_block >= s.total_blocks) {
if (s.loop) s.current_block = 0; if (s.loop) s.current_block = 0;
else return false; else return false;
} }
const uint8_t* block = s.data + s.current_block * block_size; const uint8_t* block = s.data + s.current_block * block_size;
int16_t pcm[MADPCM_BLOCK_SAMPLES * 2]; int16_t pcm[MADPCM_BLOCK_SAMPLES * 2];
int sample_count; int sample_count;
if (s.channels == 2) { if (s.channels == 2) {
int16_t L[MADPCM_BLOCK_SAMPLES], R[MADPCM_BLOCK_SAMPLES]; int16_t L[MADPCM_BLOCK_SAMPLES], R[MADPCM_BLOCK_SAMPLES];
madpcm_decode_block_stereo(block, L, R); madpcm_decode_block_stereo(block, L, R);
for (int i = 0; i < MADPCM_BLOCK_SAMPLES; i++) { for (int i = 0; i < MADPCM_BLOCK_SAMPLES; i++) {
pcm[i * 2] = L[i]; pcm[i * 2] = L[i];
pcm[i * 2 + 1] = R[i]; pcm[i * 2 + 1] = R[i];
} }
sample_count = MADPCM_BLOCK_SAMPLES * 2; sample_count = MADPCM_BLOCK_SAMPLES * 2;
} }
else { else {
madpcm_decode_block(block, pcm); madpcm_decode_block(block, pcm);
sample_count = MADPCM_BLOCK_SAMPLES; sample_count = MADPCM_BLOCK_SAMPLES;
} }
float vol = clampf(s.volume * audio->global_volume, 0.0f, 1.0f); float vol = clampf(s.volume * audio->global_volume, 0.0f, 1.0f);
for (int i = 0; i < sample_count; i++) for (int i = 0; i < sample_count; i++)
pcm[i] = static_cast<int16_t>(pcm[i] * vol); pcm[i] = static_cast<int16_t>(pcm[i] * vol);
SDL_QueueAudio(s.device.id, pcm, static_cast<uint32_t>(sample_count * sizeof(int16_t))); SDL_QueueAudio(s.device.id, pcm, static_cast<uint32_t>(sample_count * sizeof(int16_t)));
s.current_block++; s.current_block++;
return true; return true;
} }
extern "C" { extern "C" {
audio_t* audio_init() { audio_t* audio_init() {
if (SDL_InitSubSystem(SDL_INIT_AUDIO) != 0) return nullptr; if (SDL_InitSubSystem(SDL_INIT_AUDIO) != 0) return nullptr;
return new audio_t{}; return new audio_t{};
} }
void audio_destroy(audio_t* audio) { void audio_destroy(audio_t* audio) {
if (!audio) return; if (!audio) return;
// streams destructor closes all SDL devices via RAII // streams destructor closes all SDL devices via RAII
delete audio; delete audio;
SDL_QuitSubSystem(SDL_INIT_AUDIO); SDL_QuitSubSystem(SDL_INIT_AUDIO);
} }
uint8_t* audio_load_madpcm(const char* filename, int* out_size, int* out_channels, int* out_rate, int* out_total_samples) { uint8_t* audio_load_madpcm(const char* filename, int* out_size, int* out_channels, int* out_rate, int* out_total_samples) {
FILE* f = fopen(filename, "rb"); FILE* f = fopen(filename, "rb");
if (!f) return nullptr; if (!f) return nullptr;
RiffHeader h; RiffHeader h;
if (fread(&h, sizeof(h), 1, f) != 1 || if (fread(&h, sizeof(h), 1, f) != 1 ||
memcmp(h.riff, "RIFF", 4) != 0 || memcmp(h.riff, "RIFF", 4) != 0 ||
memcmp(h.wave, "WAVE", 4) != 0) { memcmp(h.wave, "WAVE", 4) != 0) {
fclose(f); fclose(f);
return nullptr; return nullptr;
} }
char chunkId[4]; char chunkId[4];
uint32_t chunkSize; uint32_t chunkSize;
int fmtFound = 0; int fmtFound = 0;
uint16_t audioFmt = 0; uint16_t audioFmt = 0;
uint16_t numChannels = 0; uint16_t numChannels = 0;
uint32_t dataLen = 0; uint32_t dataLen = 0;
int rate = 0; int rate = 0;
uint8_t* data = nullptr; uint8_t* data = nullptr;
while (fread(chunkId, 1, 4, f) == 4) { while (fread(chunkId, 1, 4, f) == 4) {
if (fread(&chunkSize, 4, 1, f) != 1) break; if (fread(&chunkSize, 4, 1, f) != 1) break;
if (memcmp(chunkId, "fmt ", 4) == 0) { if (memcmp(chunkId, "fmt ", 4) == 0) {
if (fread(&audioFmt, 2, 1, f) != 1) break; if (fread(&audioFmt, 2, 1, f) != 1) break;
if (fread(&numChannels, 2, 1, f) != 1) break; if (fread(&numChannels, 2, 1, f) != 1) break;
if (fread(&rate, 4, 1, f) != 1) break; if (fread(&rate, 4, 1, f) != 1) break;
fseek(f, chunkSize - 8, SEEK_CUR); fseek(f, chunkSize - 8, SEEK_CUR);
fmtFound = 1; fmtFound = 1;
} }
else if (memcmp(chunkId, "data", 4) == 0) { else if (memcmp(chunkId, "data", 4) == 0) {
dataLen = chunkSize; dataLen = chunkSize;
data = static_cast<uint8_t*>(malloc(dataLen)); data = static_cast<uint8_t*>(malloc(dataLen));
if (fread(data, 1, dataLen, f) != dataLen) { if (fread(data, 1, dataLen, f) != dataLen) {
free(data); free(data);
fclose(f); fclose(f);
return nullptr; return nullptr;
} }
break; break;
} }
else { else {
fseek(f, chunkSize, SEEK_CUR); fseek(f, chunkSize, SEEK_CUR);
} }
} }
fclose(f); fclose(f);
if (!fmtFound || audioFmt != MADPCM_WAVE_FORMAT || !data) { if (!fmtFound || audioFmt != MADPCM_WAVE_FORMAT || !data) {
if (data) free(data); if (data) free(data);
return nullptr; return nullptr;
} }
if (out_channels) *out_channels = static_cast<int>(numChannels); if (out_channels) *out_channels = static_cast<int>(numChannels);
if (out_size) *out_size = static_cast<int>(dataLen); if (out_size) *out_size = static_cast<int>(dataLen);
if (out_rate) *out_rate = rate; if (out_rate) *out_rate = rate;
if (out_total_samples) { if (out_total_samples) {
int blockSize = (numChannels == 2) ? MADPCM_STEREO_SIZE : MADPCM_MONO_SIZE; int blockSize = (numChannels == 2) ? MADPCM_STEREO_SIZE : MADPCM_MONO_SIZE;
int numBlocks = dataLen / blockSize; int numBlocks = dataLen / blockSize;
*out_total_samples = numBlocks * MADPCM_BLOCK_SAMPLES; *out_total_samples = numBlocks * MADPCM_BLOCK_SAMPLES;
} }
return data; return data;
} }
void audio_free_data(uint8_t* data) { void audio_free_data(uint8_t* data) {
if (data) { if (data) {
free(data); free(data);
} }
} }
audio_stream_id_t audio_play(audio_t* audio, const uint8_t* data, int size, audio_stream_id_t audio_play(audio_t* audio, const uint8_t* data, int size,
int channels, int rate, bool loop) { int channels, int rate, bool loop) {
if (!audio || !data) return 0; if (!audio || !data) return 0;
SDL_AudioSpec spec{}; SDL_AudioSpec spec{};
spec.freq = rate; spec.freq = rate;
spec.format = AUDIO_S16SYS; spec.format = AUDIO_S16SYS;
spec.channels = static_cast<uint8_t>(channels); spec.channels = static_cast<uint8_t>(channels);
spec.samples = MADPCM_BLOCK_SAMPLES; spec.samples = MADPCM_BLOCK_SAMPLES;
SDL_AudioDeviceID dev = SDL_OpenAudioDevice(nullptr, 0, &spec, nullptr, 0); SDL_AudioDeviceID dev = SDL_OpenAudioDevice(nullptr, 0, &spec, nullptr, 0);
if (!dev) return 0; if (!dev) return 0;
int block_size = (channels == 2) ? MADPCM_STEREO_SIZE : MADPCM_MONO_SIZE; int block_size = (channels == 2) ? MADPCM_STEREO_SIZE : MADPCM_MONO_SIZE;
int total_blocks = size / block_size; int total_blocks = size / block_size;
uint32_t id = audio->next_id++; uint32_t id = audio->next_id++;
Stream& s = audio->streams[id]; Stream& s = audio->streams[id];
s.id = id; s.id = id;
s.device = AudioDevice(dev); s.device = AudioDevice(dev);
s.data = data; s.data = data;
s.total_blocks = total_blocks; s.total_blocks = total_blocks;
s.channels = channels; s.channels = channels;
s.loop = loop; s.loop = loop;
for (int i = 0; i < AHEAD_BLOCKS; i++) for (int i = 0; i < AHEAD_BLOCKS; i++)
if (!pump_block(audio, s)) break; if (!pump_block(audio, s)) break;
SDL_PauseAudioDevice(dev, 0); SDL_PauseAudioDevice(dev, 0);
return id; return id;
} }
bool audio_stop(audio_t* audio, audio_stream_id_t id) { bool audio_stop(audio_t* audio, audio_stream_id_t id) {
if (!audio) return false; if (!audio) return false;
return audio->streams.erase(id) > 0; // destructor closes the device return audio->streams.erase(id) > 0; // destructor closes the device
} }
bool audio_pause(audio_t* audio, audio_stream_id_t id) { bool audio_pause(audio_t* audio, audio_stream_id_t id) {
if (!audio) return false; if (!audio) return false;
auto it = audio->streams.find(id); auto it = audio->streams.find(id);
if (it == audio->streams.end()) return false; if (it == audio->streams.end()) return false;
Stream& s = it->second; Stream& s = it->second;
if (!s.paused) { if (!s.paused) {
SDL_PauseAudioDevice(s.device.id, 1); SDL_PauseAudioDevice(s.device.id, 1);
s.paused = true; s.paused = true;
} }
return true; return true;
} }
bool audio_resume(audio_t* audio, audio_stream_id_t id) { bool audio_resume(audio_t* audio, audio_stream_id_t id) {
if (!audio) return false; if (!audio) return false;
auto it = audio->streams.find(id); auto it = audio->streams.find(id);
if (it == audio->streams.end()) return false; if (it == audio->streams.end()) return false;
Stream& s = it->second; Stream& s = it->second;
if (s.paused) { if (s.paused) {
SDL_PauseAudioDevice(s.device.id, 0); SDL_PauseAudioDevice(s.device.id, 0);
s.paused = false; s.paused = false;
} }
return true; return true;
} }
bool audio_is_active(audio_t* audio, audio_stream_id_t id) { bool audio_is_active(audio_t* audio, audio_stream_id_t id) {
if (!audio) return false; if (!audio) return false;
return audio->streams.count(id) > 0; return audio->streams.count(id) > 0;
} }
bool audio_is_playing(audio_t* audio, audio_stream_id_t id) { bool audio_is_playing(audio_t* audio, audio_stream_id_t id) {
if (!audio) return false; if (!audio) return false;
auto it = audio->streams.find(id); auto it = audio->streams.find(id);
if (it == audio->streams.end()) return false; if (it == audio->streams.end()) return false;
return !it->second.paused; return !it->second.paused;
} }
bool audio_set_volume(audio_t* audio, audio_stream_id_t id, float volume) { bool audio_set_volume(audio_t* audio, audio_stream_id_t id, float volume) {
if (!audio) return false; if (!audio) return false;
auto it = audio->streams.find(id); auto it = audio->streams.find(id);
if (it == audio->streams.end()) return false; if (it == audio->streams.end()) return false;
Stream& s = it->second; Stream& s = it->second;
s.volume = clampf(volume, 0.0f, 1.0f); s.volume = clampf(volume, 0.0f, 1.0f);
s.volume_anim_duration = 0.0f; s.volume_anim_duration = 0.0f;
return true; return true;
} }
bool audio_animate_volume(audio_t* audio, audio_stream_id_t id, bool audio_animate_volume(audio_t* audio, audio_stream_id_t id,
float to_volume, float duration_sec) { float to_volume, float duration_sec) {
if (!audio) return false; if (!audio) return false;
auto it = audio->streams.find(id); auto it = audio->streams.find(id);
if (it == audio->streams.end()) return false; if (it == audio->streams.end()) return false;
Stream& s = it->second; Stream& s = it->second;
s.volume_from = s.volume; s.volume_from = s.volume;
s.volume_to = clampf(to_volume, 0.0f, 1.0f); s.volume_to = clampf(to_volume, 0.0f, 1.0f);
s.volume_anim_elapsed = 0.0f; s.volume_anim_elapsed = 0.0f;
s.volume_anim_duration = duration_sec; s.volume_anim_duration = duration_sec;
return true; return true;
} }
void audio_set_global_volume(audio_t* audio, float volume) { void audio_set_global_volume(audio_t* audio, float volume) {
if (!audio) return; if (!audio) return;
audio->global_volume = clampf(volume, 0.0f, 1.0f); audio->global_volume = clampf(volume, 0.0f, 1.0f);
audio->global_volume_anim_duration = 0.0f; audio->global_volume_anim_duration = 0.0f;
} }
void audio_animate_global_volume(audio_t* audio, void audio_animate_global_volume(audio_t* audio,
float to_volume, float duration_sec) { float to_volume, float duration_sec) {
if (!audio) return; if (!audio) return;
audio->global_volume_from = audio->global_volume; audio->global_volume_from = audio->global_volume;
audio->global_volume_to = clampf(to_volume, 0.0f, 1.0f); audio->global_volume_to = clampf(to_volume, 0.0f, 1.0f);
audio->global_volume_anim_elapsed = 0.0f; audio->global_volume_anim_elapsed = 0.0f;
audio->global_volume_anim_duration = duration_sec; audio->global_volume_anim_duration = duration_sec;
} }
void audio_tick(audio_t* audio, float delta_time_sec) { void audio_tick(audio_t* audio, float delta_time_sec) {
if (!audio) return; if (!audio) return;
// Advance global volume animation // Advance global volume animation
if (audio->global_volume_anim_duration > 0.0f) { if (audio->global_volume_anim_duration > 0.0f) {
audio->global_volume_anim_elapsed += delta_time_sec; audio->global_volume_anim_elapsed += delta_time_sec;
float t = audio->global_volume_anim_elapsed / audio->global_volume_anim_duration; float t = audio->global_volume_anim_elapsed / audio->global_volume_anim_duration;
if (t >= 1.0f) { t = 1.0f; audio->global_volume_anim_duration = 0.0f; } if (t >= 1.0f) { t = 1.0f; audio->global_volume_anim_duration = 0.0f; }
audio->global_volume = audio->global_volume_from + audio->global_volume = audio->global_volume_from +
(audio->global_volume_to - audio->global_volume_from) * t; (audio->global_volume_to - audio->global_volume_from) * t;
} }
// Collect IDs of streams to remove after iteration to avoid invalidating the map // Collect IDs of streams to remove after iteration to avoid invalidating the map
std::vector<uint32_t> to_remove; std::vector<uint32_t> to_remove;
for (auto& [id, s] : audio->streams) { for (auto& [id, s] : audio->streams) {
// Advance per-stream volume animation // Advance per-stream volume animation
if (s.volume_anim_duration > 0.0f) { if (s.volume_anim_duration > 0.0f) {
s.volume_anim_elapsed += delta_time_sec; s.volume_anim_elapsed += delta_time_sec;
float t = s.volume_anim_elapsed / s.volume_anim_duration; float t = s.volume_anim_elapsed / s.volume_anim_duration;
if (t >= 1.0f) { t = 1.0f; s.volume_anim_duration = 0.0f; } if (t >= 1.0f) { t = 1.0f; s.volume_anim_duration = 0.0f; }
s.volume = s.volume_from + (s.volume_to - s.volume_from) * t; s.volume = s.volume_from + (s.volume_to - s.volume_from) * t;
} }
if (s.paused) continue; if (s.paused) continue;
size_t target_bytes = AHEAD_BLOCKS * MADPCM_BLOCK_SAMPLES size_t target_bytes = AHEAD_BLOCKS * MADPCM_BLOCK_SAMPLES
* s.channels * sizeof(int16_t); * s.channels * sizeof(int16_t);
while (SDL_GetQueuedAudioSize(s.device.id) < target_bytes) { while (SDL_GetQueuedAudioSize(s.device.id) < target_bytes) {
if (!pump_block(audio, s)) { if (!pump_block(audio, s)) {
// Non-looping EOF — wait for queue to fully drain then remove // Non-looping EOF — wait for queue to fully drain then remove
if (SDL_GetQueuedAudioSize(s.device.id) == 0) if (SDL_GetQueuedAudioSize(s.device.id) == 0)
to_remove.push_back(id); to_remove.push_back(id);
break; break;
} }
} }
} }
for (uint32_t id : to_remove) for (uint32_t id : to_remove)
audio->streams.erase(id); audio->streams.erase(id);
} }
} // extern "C" } // extern "C"
+1185 -1185
View File
File diff suppressed because it is too large. Load diff
+74 -74
View File
@@ -1,74 +1,74 @@
#pragma warning(push, 0) #pragma warning(push, 0)
#define STB_IMAGE_IMPLEMENTATION #define STB_IMAGE_IMPLEMENTATION
#include <stb_image.h> #include <stb_image.h>
#define STB_IMAGE_RESIZE_IMPLEMENTATION #define STB_IMAGE_RESIZE_IMPLEMENTATION
#include <stb_image_resize2.h> #include <stb_image_resize2.h>
#pragma warning(pop) #pragma warning(pop)
#include <cstdlib> #include <cstdlib>
#include <cstdio> #include <cstdio>
#include <cstdint> #include <cstdint>
#include <image.h> #include <image.h>
extern "C" { extern "C" {
uint32_t* image_load(const char* filename, int* width, int* height) { uint32_t* image_load(const char* filename, int* width, int* height) {
int n; int n;
unsigned char* data = stbi_load(filename, width, height, &n, 4); // force 4 channels (RGBA) unsigned char* data = stbi_load(filename, width, height, &n, 4); // force 4 channels (RGBA)
if (!data) return nullptr; if (!data) return nullptr;
// Convert RGBA to ARGB // Convert RGBA to ARGB
uint32_t* out = (uint32_t*)malloc(*width * *height * sizeof(uint32_t)); uint32_t* out = (uint32_t*)malloc(*width * *height * sizeof(uint32_t));
if (!out) { if (!out) {
stbi_image_free(data); stbi_image_free(data);
return nullptr; return nullptr;
} }
for (int i = 0; i < *width * *height; ++i) { for (int i = 0; i < *width * *height; ++i) {
uint8_t r = data[4*i + 0]; uint8_t r = data[4*i + 0];
uint8_t g = data[4*i + 1]; uint8_t g = data[4*i + 1];
uint8_t b = data[4*i + 2]; uint8_t b = data[4*i + 2];
uint8_t a = data[4*i + 3]; uint8_t a = data[4*i + 3];
out[i] = (a << 24) | (r << 16) | (g << 8) | b; out[i] = (a << 24) | (r << 16) | (g << 8) | b;
} }
stbi_image_free(data); stbi_image_free(data);
return out; return out;
} }
uint32_t* image_load_resized(const char* filename, int desired_width, int desired_height) { uint32_t* image_load_resized(const char* filename, int desired_width, int desired_height) {
int original_width, original_height, n; int original_width, original_height, n;
unsigned char* data = stbi_load(filename, &original_width, &original_height, &n, 4); unsigned char* data = stbi_load(filename, &original_width, &original_height, &n, 4);
if (!data) return nullptr; if (!data) return nullptr;
// Allocate buffer for resized image // Allocate buffer for resized image
uint32_t* resized = (uint32_t*)malloc(desired_width * desired_height * 4); uint32_t* resized = (uint32_t*)malloc(desired_width * desired_height * 4);
if (!resized) { if (!resized) {
stbi_image_free(data); stbi_image_free(data);
return nullptr; return nullptr;
} }
// Resize the image // Resize the image
stbir_resize_uint8_linear(data, original_width, original_height, 0, stbir_resize_uint8_linear(data, original_width, original_height, 0,
(unsigned char* )resized, desired_width, desired_height, 0, (unsigned char* )resized, desired_width, desired_height, 0,
STBIR_RGBA); STBIR_RGBA);
stbi_image_free(data); stbi_image_free(data);
// In-place conversion from RGBA to ARGB // In-place conversion from RGBA to ARGB
// We iterate backwards to ensure we don't overwrite bytes before reading them, // 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! // though since we are working within the same 4-byte boundaries, forward works too!
for (int i = 0; i < desired_width * desired_height; ++i) { for (int i = 0; i < desired_width * desired_height; ++i) {
uint8_t* pixel = (uint8_t*)&resized[i]; uint8_t* pixel = (uint8_t*)&resized[i];
uint8_t r = pixel[0]; uint8_t r = pixel[0];
uint8_t g = pixel[1]; uint8_t g = pixel[1];
uint8_t b = pixel[2]; uint8_t b = pixel[2];
uint8_t a = pixel[3]; uint8_t a = pixel[3];
resized[i] = ((uint32_t)a << 24) | ((uint32_t)r << 16) | ((uint32_t)g << 8) | b; resized[i] = ((uint32_t)a << 24) | ((uint32_t)r << 16) | ((uint32_t)g << 8) | b;
} }
return resized; return resized;
} }
void image_free(uint32_t* data) { void image_free(uint32_t* data) {
free(data); free(data);
} }
} // extern "C" } // extern "C"
+659 -659
View File
File diff suppressed because it is too large. Load diff
+798 -798
View File
File diff suppressed because it is too large. Load diff
+214 -214
View File
@@ -1,214 +1,214 @@
// window.cpp // window.cpp
// This file implements the windowing and input system using SDL2. // 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. // 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 // 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. // to avoid freezing the app during window move or resize operations. Of course, normal OSes don't need that.
#include <window.h> #include <window.h>
#include <SDL2/SDL.h> #include <SDL2/SDL.h>
#include <cstring> #include <cstring>
#include <cstdlib> #include <cstdlib>
#include <cstdio> #include <cstdio>
#ifdef _WIN32 #ifdef _WIN32
#include <thread> #include <thread>
#include <mutex> #include <mutex>
#include <atomic> #include <atomic>
#endif #endif
struct window_t { struct window_t {
SDL_Window* window; SDL_Window* window;
SDL_Renderer* renderer; SDL_Renderer* renderer;
SDL_Texture* texture; SDL_Texture* texture;
int width; int width;
int height; int height;
const uint8_t* keyboard_state; const uint8_t* keyboard_state;
int mouse_x; int mouse_x;
int mouse_y; int mouse_y;
uint32_t mouse_buttons; uint32_t mouse_buttons;
#ifdef _WIN32 #ifdef _WIN32
std::thread worker; std::thread worker;
std::atomic<bool> running{true}; std::atomic<bool> running{true};
std::atomic<bool> quit_requested{false}; std::atomic<bool> quit_requested{false};
std::mutex buffer_mutex; std::mutex buffer_mutex;
uint32_t* shared_fb; uint32_t* shared_fb;
std::mutex input_mutex; std::mutex input_mutex;
uint8_t kb_copy[SDL_NUM_SCANCODES]; uint8_t kb_copy[SDL_NUM_SCANCODES];
#endif #endif
}; };
// Internal helper for cleanup // Internal helper for cleanup
static void window_cleanup(window_t* s) { static void window_cleanup(window_t* s) {
if (!s) return; if (!s) return;
if (s->texture) SDL_DestroyTexture(s->texture); if (s->texture) SDL_DestroyTexture(s->texture);
if (s->renderer) SDL_DestroyRenderer(s->renderer); if (s->renderer) SDL_DestroyRenderer(s->renderer);
if (s->window) SDL_DestroyWindow(s->window); if (s->window) SDL_DestroyWindow(s->window);
} }
#ifdef _WIN32 #ifdef _WIN32
// Internal function to perform the actual blit and present on the worker thread // Internal function to perform the actual blit and present on the worker thread
static void win32_internal_render(window_t* s) { static void win32_internal_render(window_t* s) {
if (!s->renderer || !s->texture) return; if (!s->renderer || !s->texture) return;
void* pixels; void* pixels;
int pitch; int pitch;
if (SDL_LockTexture(s->texture, nullptr, &pixels, &pitch) == 0) { if (SDL_LockTexture(s->texture, nullptr, &pixels, &pitch) == 0) {
{ {
std::lock_guard<std::mutex> lock(s->buffer_mutex); std::lock_guard<std::mutex> lock(s->buffer_mutex);
std::memcpy(pixels, s->shared_fb, s->width * s->height * 4); std::memcpy(pixels, s->shared_fb, s->width * s->height * 4);
} }
SDL_UnlockTexture(s->texture); SDL_UnlockTexture(s->texture);
} }
SDL_RenderClear(s->renderer); SDL_RenderClear(s->renderer);
SDL_RenderCopy(s->renderer, s->texture, nullptr, nullptr); SDL_RenderCopy(s->renderer, s->texture, nullptr, nullptr);
SDL_RenderPresent(s->renderer); SDL_RenderPresent(s->renderer);
} }
// THE FIX: This callback is triggered by SDL even when Windows is in a modal drag loop. // 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) { static int SDLCALL win32_event_watch(void* userdata, SDL_Event* event) {
window_t* s = (window_t*)userdata; window_t* s = (window_t*)userdata;
if (event->type == SDL_WINDOWEVENT) { if (event->type == SDL_WINDOWEVENT) {
if (event->window.event == SDL_WINDOWEVENT_MOVED || if (event->window.event == SDL_WINDOWEVENT_MOVED ||
event->window.event == SDL_WINDOWEVENT_RESIZED || event->window.event == SDL_WINDOWEVENT_RESIZED ||
event->window.event == SDL_WINDOWEVENT_EXPOSED) { event->window.event == SDL_WINDOWEVENT_EXPOSED) {
win32_internal_render(s); win32_internal_render(s);
} }
} }
return 0; return 0;
} }
static void win32_window_worker(window_t* s, const char* title) { static void win32_window_worker(window_t* s, const char* title) {
SDL_InitSubSystem(SDL_INIT_VIDEO); SDL_InitSubSystem(SDL_INIT_VIDEO);
s->window = SDL_CreateWindow(title, SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED, s->width, s->height, 0); 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->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); 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 // Register the watch to handle rendering during the drag-loop
SDL_AddEventWatch(win32_event_watch, s); SDL_AddEventWatch(win32_event_watch, s);
while (s->running) { while (s->running) {
SDL_Event ev; SDL_Event ev;
// Use a timeout so we don't hog CPU but stay responsive // Use a timeout so we don't hog CPU but stay responsive
if (SDL_WaitEventTimeout(&ev, 8)) { if (SDL_WaitEventTimeout(&ev, 8)) {
if (ev.type == SDL_QUIT) s->quit_requested = true; if (ev.type == SDL_QUIT) s->quit_requested = true;
} }
// Sync input state for the main thread // Sync input state for the main thread
{ {
std::lock_guard<std::mutex> lock(s->input_mutex); std::lock_guard<std::mutex> lock(s->input_mutex);
const uint8_t* state = SDL_GetKeyboardState(nullptr); const uint8_t* state = SDL_GetKeyboardState(nullptr);
std::memcpy(s->kb_copy, state, SDL_NUM_SCANCODES); std::memcpy(s->kb_copy, state, SDL_NUM_SCANCODES);
s->mouse_buttons = SDL_GetMouseState(&s->mouse_x, &s->mouse_y); s->mouse_buttons = SDL_GetMouseState(&s->mouse_x, &s->mouse_y);
} }
// Normal frame render // Normal frame render
win32_internal_render(s); win32_internal_render(s);
} }
SDL_DelEventWatch(win32_event_watch, s); SDL_DelEventWatch(win32_event_watch, s);
window_cleanup(s); window_cleanup(s);
SDL_QuitSubSystem(SDL_INIT_VIDEO); SDL_QuitSubSystem(SDL_INIT_VIDEO);
} }
#endif #endif
extern "C" { extern "C" {
window_t* window_create(int width, int height, const char* title) { window_t* window_create(int width, int height, const char* title) {
#ifdef _WIN32 #ifdef _WIN32
window_t* s = new window_t{}; window_t* s = new window_t{};
s->width = width; s->width = width;
s->height = height; s->height = height;
s->shared_fb = new uint32_t[width * height]; s->shared_fb = new uint32_t[width * height];
s->keyboard_state = s->kb_copy; s->keyboard_state = s->kb_copy;
// Spawn the worker thread which owns the SDL context // Spawn the worker thread which owns the SDL context
s->worker = std::thread(win32_window_worker, s, title); s->worker = std::thread(win32_window_worker, s, title);
// Wait briefly for the worker to initialize window/renderer pointers // Wait briefly for the worker to initialize window/renderer pointers
while (s->running && !s->renderer) { std::this_thread::yield(); } while (s->running && !s->renderer) { std::this_thread::yield(); }
return s; return s;
#else #else
// Original non-threaded implementation for Linux/others // Original non-threaded implementation for Linux/others
if (SDL_InitSubSystem(SDL_INIT_VIDEO) != 0) return nullptr; if (SDL_InitSubSystem(SDL_INIT_VIDEO) != 0) return nullptr;
window_t* s = new window_t{}; window_t* s = new window_t{};
s->width = width; s->width = width;
s->height = height; s->height = height;
s->window = SDL_CreateWindow(title, SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED, width, height, 0); s->window = SDL_CreateWindow(title, SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED, width, height, 0);
s->renderer = SDL_CreateRenderer(s->window, -1, SDL_RENDERER_ACCELERATED); s->renderer = SDL_CreateRenderer(s->window, -1, SDL_RENDERER_ACCELERATED);
s->texture = SDL_CreateTexture(s->renderer, SDL_PIXELFORMAT_ARGB8888, SDL_TEXTUREACCESS_STREAMING, width, height); s->texture = SDL_CreateTexture(s->renderer, SDL_PIXELFORMAT_ARGB8888, SDL_TEXTUREACCESS_STREAMING, width, height);
s->keyboard_state = SDL_GetKeyboardState(nullptr); s->keyboard_state = SDL_GetKeyboardState(nullptr);
return s; return s;
#endif #endif
} }
void window_destroy(window_t* s) { void window_destroy(window_t* s) {
if (!s) return; if (!s) return;
#ifdef _WIN32 #ifdef _WIN32
s->running = false; s->running = false;
if (s->worker.joinable()) s->worker.join(); if (s->worker.joinable()) s->worker.join();
delete[] s->shared_fb; delete[] s->shared_fb;
#else #else
window_cleanup(s); window_cleanup(s);
SDL_QuitSubSystem(SDL_INIT_VIDEO); SDL_QuitSubSystem(SDL_INIT_VIDEO);
#endif #endif
delete s; delete s;
} }
bool window_pollevent(window_t* s) { bool window_pollevent(window_t* s) {
#ifdef _WIN32 #ifdef _WIN32
// Main thread just checks the status reported by the worker // Main thread just checks the status reported by the worker
std::lock_guard<std::mutex> lock(s->input_mutex); std::lock_guard<std::mutex> lock(s->input_mutex);
return s->quit_requested; return s->quit_requested;
#else #else
SDL_Event ev; SDL_Event ev;
bool quit = false; bool quit = false;
while (SDL_PollEvent(&ev)) { 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); s->mouse_buttons = SDL_GetMouseState(&s->mouse_x, &s->mouse_y);
return quit; return quit;
#endif #endif
} }
void window_blit(window_t* s, const uint32_t* fb) { void window_blit(window_t* s, const uint32_t* fb) {
#ifdef _WIN32 #ifdef _WIN32
// Just update the shared buffer; the worker thread will pick it up and present it // Just update the shared buffer; the worker thread will pick it up and present it
std::lock_guard<std::mutex> lock(s->buffer_mutex); std::lock_guard<std::mutex> lock(s->buffer_mutex);
std::memcpy(s->shared_fb, fb, s->width * s->height * 4); std::memcpy(s->shared_fb, fb, s->width * s->height * 4);
#else #else
// Original immediate blit logic // Original immediate blit logic
void* pixels; int pitch; void* pixels; int pitch;
SDL_LockTexture(s->texture, nullptr, &pixels, &pitch); SDL_LockTexture(s->texture, nullptr, &pixels, &pitch);
std::memcpy(pixels, fb, s->width * s->height * 4); std::memcpy(pixels, fb, s->width * s->height * 4);
SDL_UnlockTexture(s->texture); SDL_UnlockTexture(s->texture);
SDL_RenderClear(s->renderer); SDL_RenderClear(s->renderer);
SDL_RenderCopy(s->renderer, s->texture, nullptr, nullptr); SDL_RenderCopy(s->renderer, s->texture, nullptr, nullptr);
SDL_RenderPresent(s->renderer); SDL_RenderPresent(s->renderer);
#endif #endif
} }
// Input getters remain unchanged as they read from the struct's state fields // Input getters remain unchanged as they read from the struct's state fields
bool window_is_key_down(window_t* s, int sc) { bool window_is_key_down(window_t* s, int sc) {
if (sc < 0 || sc >= SDL_NUM_SCANCODES) return false; if (sc < 0 || sc >= SDL_NUM_SCANCODES) return false;
return s->keyboard_state[sc] != 0; return s->keyboard_state[sc] != 0;
} }
bool window_is_mouse_button_down(window_t* s, int button) { bool window_is_mouse_button_down(window_t* s, int button) {
switch (button) { switch (button) {
case MOUSE_BUTTON_LEFT: return s->mouse_buttons & SDL_BUTTON_LMASK; case MOUSE_BUTTON_LEFT: return s->mouse_buttons & SDL_BUTTON_LMASK;
case MOUSE_BUTTON_MIDDLE: return s->mouse_buttons & SDL_BUTTON_MMASK; case MOUSE_BUTTON_MIDDLE: return s->mouse_buttons & SDL_BUTTON_MMASK;
case MOUSE_BUTTON_RIGHT: return s->mouse_buttons & SDL_BUTTON_RMASK; case MOUSE_BUTTON_RIGHT: return s->mouse_buttons & SDL_BUTTON_RMASK;
} }
return false; return false;
} }
mouse_pos_t window_get_mouse_position(window_t* s) { mouse_pos_t window_get_mouse_position(window_t* s) {
return { s->mouse_x, s->mouse_y }; return { s->mouse_x, s->mouse_y };
} }
} }