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
#pragma once
#ifndef AUDIO_H
#define AUDIO_H
#ifdef __cplusplus
#include <cstdint>
extern "C" {
#else
#include <stdint.h>
#include <stdbool.h>
#endif
typedef struct audio_t audio_t;
typedef uint32_t audio_stream_id_t;
// If the return type is bool, false means failure.
// Initialize audio system
// nullptr means failure
audio_t* audio_init(void);
// Destroy audio system and free resources
void audio_destroy(audio_t* audio);
// Load MADPCM data from a WAV file.
// Returns a pointer to the allocated audio data, or nullptr on failure.
// 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);
// Free data allocated by audio_load_madpcm
void audio_free_data(uint8_t* data);
// Play a MADPCM stream, returns unique ID
// data must remain valid until the stream ends or is stopped
// loop: if true, the stream restarts on EOF
// 0 means failure, valid IDs start from 1
audio_stream_id_t audio_play(audio_t* audio,
const uint8_t* data,
int size,
int channels,
int rate,
bool loop);
// Stop and remove stream by 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_resume(audio_t* audio, audio_stream_id_t id);
// Returns true if the stream is active (playing or paused)
bool audio_is_active(audio_t* audio, audio_stream_id_t id);
// Returns true if the stream is currently playing (not paused, not stopped)
bool audio_is_playing(audio_t* audio, audio_stream_id_t id);
// Per-stream volume, immediate effect, range [0.0, 1.0]
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()
bool audio_animate_volume(audio_t* audio, audio_stream_id_t id,
float to_volume, float duration_sec);
// Global volume, immediate effect, range [0.0, 1.0]
// Applied on top of per-stream volume at mix time
void audio_set_global_volume(audio_t* audio, float volume);
// Animate global volume linearly over time, driven by audio_tick()
void audio_animate_global_volume(audio_t* audio,
float to_volume, float duration_sec);
// Advances stream decoding and volume animations
// Call once per main loop iteration with the same delta as compositor_tick()
void audio_tick(audio_t* audio, float delta_time_sec);
#ifdef __cplusplus
}
#endif
// audio.h
#pragma once
#ifndef AUDIO_H
#define AUDIO_H
#ifdef __cplusplus
#include <cstdint>
extern "C" {
#else
#include <stdint.h>
#include <stdbool.h>
#endif
typedef struct audio_t audio_t;
typedef uint32_t audio_stream_id_t;
// If the return type is bool, false means failure.
// Initialize audio system
// nullptr means failure
audio_t* audio_init(void);
// Destroy audio system and free resources
void audio_destroy(audio_t* audio);
// Load MADPCM data from a WAV file.
// Returns a pointer to the allocated audio data, or nullptr on failure.
// 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);
// Free data allocated by audio_load_madpcm
void audio_free_data(uint8_t* data);
// Play a MADPCM stream, returns unique ID
// data must remain valid until the stream ends or is stopped
// loop: if true, the stream restarts on EOF
// 0 means failure, valid IDs start from 1
audio_stream_id_t audio_play(audio_t* audio,
const uint8_t* data,
int size,
int channels,
int rate,
bool loop);
// Stop and remove stream by 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_resume(audio_t* audio, audio_stream_id_t id);
// Returns true if the stream is active (playing or paused)
bool audio_is_active(audio_t* audio, audio_stream_id_t id);
// Returns true if the stream is currently playing (not paused, not stopped)
bool audio_is_playing(audio_t* audio, audio_stream_id_t id);
// Per-stream volume, immediate effect, range [0.0, 1.0]
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()
bool audio_animate_volume(audio_t* audio, audio_stream_id_t id,
float to_volume, float duration_sec);
// Global volume, immediate effect, range [0.0, 1.0]
// Applied on top of per-stream volume at mix time
void audio_set_global_volume(audio_t* audio, float volume);
// Animate global volume linearly over time, driven by audio_tick()
void audio_animate_global_volume(audio_t* audio,
float to_volume, float duration_sec);
// Advances stream decoding and volume animations
// Call once per main loop iteration with the same delta as compositor_tick()
void audio_tick(audio_t* audio, float delta_time_sec);
#ifdef __cplusplus
}
#endif
#endif // AUDIO_H
+119 -119
View File
@@ -1,120 +1,120 @@
// compositor.h v0.3
#pragma once
#ifndef COMPOSITOR_H
#define COMPOSITOR_H
// This subsystem is thread-unsafe.
// Caller must serialize all calls. Calls do not need to be made from the main thread.
// Removing an element while ticking/rendering may crash.
// Modifying an element while ticking/rendering may cause visual glitches.
// Easing functions
enum easing_t {
EASE_LINEAR = 0,
EASE_IN_QUAD,
EASE_OUT_QUAD,
EASE_IN_OUT_QUAD,
EASE_IN_CUBIC,
EASE_OUT_CUBIC,
EASE_IN_OUT_CUBIC
};
// Anchor points
enum anchor_point_t {
ANCHOR_TOP_LEFT,
ANCHOR_TOP_CENTER,
ANCHOR_TOP_RIGHT,
ANCHOR_CENTER_LEFT,
ANCHOR_CENTER,
ANCHOR_CENTER_RIGHT,
ANCHOR_BOTTOM_LEFT,
ANCHOR_BOTTOM_CENTER,
ANCHOR_BOTTOM_RIGHT,
};
#ifdef __cplusplus
#include <cstdint>
extern "C" {
#else
#include <stdint.h>
#include <stdbool.h>
#endif
typedef struct compositor_t compositor_t;
typedef uint32_t element_handle_t;
// If the return type is bool, false means failure.
// Initialize compositor with given dimensions
// nullptr means failure
compositor_t* compositor_init(uint32_t width, uint32_t height);
// Destroy compositor and free resources. Everything dies with it.
void compositor_destroy(compositor_t* comp);
// Insert element, returns unique ID
// FB format is ARGB8888 and it always will be
// The compositor takes a reference, caller must keep alive until removed
// 0 means failure, valid IDs start from 1
element_handle_t compositor_insert(compositor_t* comp,
const uint32_t* fb,
uint32_t fb_width,
uint32_t fb_height,
int32_t x, int32_t y,
int32_t z_order, float scale,
anchor_point_t anchor);
// Remove element by ID
bool compositor_remove(compositor_t* comp, element_handle_t id);
// Setters, immediate effect
bool compositor_set_position(compositor_t* comp, element_handle_t id,
int32_t x, int32_t y);
bool compositor_set_zorder(compositor_t* comp, element_handle_t id,
int32_t z_order);
bool compositor_set_scale(compositor_t* comp, element_handle_t id,
float scale);
bool compositor_set_visible(compositor_t* comp, element_handle_t id,
bool visible);
bool compositor_set_alpha(compositor_t* comp, element_handle_t id,
float alpha);
// Advances all animations by a given time delta.
void compositor_tick(compositor_t* comp, float delta_time_sec);
// Sets a target state to advance over time by tick()
// Queue means the animation will start after the previous animation completed
// 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.
bool compositor_animate_move(compositor_t* comp, element_handle_t id,
int32_t to_x, int32_t to_y,
float duration_sec, easing_t easing, bool queue, bool parallel);
bool compositor_animate_fade(compositor_t* comp, element_handle_t id,
float to_alpha,
float duration_sec, easing_t easing, bool queue, bool parallel);
bool compositor_animate_scale(compositor_t* comp, element_handle_t id,
float to_scale,
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.
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);
// Returns true if any element is animating or has queued animations.
bool compositor_is_animating(compositor_t* comp);
// Returns true if the specified element is animating or has queued animations.
bool compositor_is_element_animating(compositor_t* comp, element_handle_t id);
// Compose all visible elements and return framebuffer pointer
const uint32_t* compositor_compose(compositor_t* comp);
// Mark an element's content as dirty for redrawing
bool compositor_mark_content_dirty(compositor_t* comp, element_handle_t id);
void compositor_mark_all_dirty(compositor_t* comp);
#ifdef __cplusplus
}
#endif
// compositor.h v0.3
#pragma once
#ifndef COMPOSITOR_H
#define COMPOSITOR_H
// This subsystem is thread-unsafe.
// Caller must serialize all calls. Calls do not need to be made from the main thread.
// Removing an element while ticking/rendering may crash.
// Modifying an element while ticking/rendering may cause visual glitches.
// Easing functions
enum easing_t {
EASE_LINEAR = 0,
EASE_IN_QUAD,
EASE_OUT_QUAD,
EASE_IN_OUT_QUAD,
EASE_IN_CUBIC,
EASE_OUT_CUBIC,
EASE_IN_OUT_CUBIC
};
// Anchor points
enum anchor_point_t {
ANCHOR_TOP_LEFT,
ANCHOR_TOP_CENTER,
ANCHOR_TOP_RIGHT,
ANCHOR_CENTER_LEFT,
ANCHOR_CENTER,
ANCHOR_CENTER_RIGHT,
ANCHOR_BOTTOM_LEFT,
ANCHOR_BOTTOM_CENTER,
ANCHOR_BOTTOM_RIGHT,
};
#ifdef __cplusplus
#include <cstdint>
extern "C" {
#else
#include <stdint.h>
#include <stdbool.h>
#endif
typedef struct compositor_t compositor_t;
typedef uint32_t element_handle_t;
// If the return type is bool, false means failure.
// Initialize compositor with given dimensions
// nullptr means failure
compositor_t* compositor_init(uint32_t width, uint32_t height);
// Destroy compositor and free resources. Everything dies with it.
void compositor_destroy(compositor_t* comp);
// Insert element, returns unique ID
// FB format is ARGB8888 and it always will be
// The compositor takes a reference, caller must keep alive until removed
// 0 means failure, valid IDs start from 1
element_handle_t compositor_insert(compositor_t* comp,
const uint32_t* fb,
uint32_t fb_width,
uint32_t fb_height,
int32_t x, int32_t y,
int32_t z_order, float scale,
anchor_point_t anchor);
// Remove element by ID
bool compositor_remove(compositor_t* comp, element_handle_t id);
// Setters, immediate effect
bool compositor_set_position(compositor_t* comp, element_handle_t id,
int32_t x, int32_t y);
bool compositor_set_zorder(compositor_t* comp, element_handle_t id,
int32_t z_order);
bool compositor_set_scale(compositor_t* comp, element_handle_t id,
float scale);
bool compositor_set_visible(compositor_t* comp, element_handle_t id,
bool visible);
bool compositor_set_alpha(compositor_t* comp, element_handle_t id,
float alpha);
// Advances all animations by a given time delta.
void compositor_tick(compositor_t* comp, float delta_time_sec);
// Sets a target state to advance over time by tick()
// Queue means the animation will start after the previous animation completed
// 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.
bool compositor_animate_move(compositor_t* comp, element_handle_t id,
int32_t to_x, int32_t to_y,
float duration_sec, easing_t easing, bool queue, bool parallel);
bool compositor_animate_fade(compositor_t* comp, element_handle_t id,
float to_alpha,
float duration_sec, easing_t easing, bool queue, bool parallel);
bool compositor_animate_scale(compositor_t* comp, element_handle_t id,
float to_scale,
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.
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);
// Returns true if any element is animating or has queued animations.
bool compositor_is_animating(compositor_t* comp);
// Returns true if the specified element is animating or has queued animations.
bool compositor_is_element_animating(compositor_t* comp, element_handle_t id);
// Compose all visible elements and return framebuffer pointer
const uint32_t* compositor_compose(compositor_t* comp);
// Mark an element's content as dirty for redrawing
bool compositor_mark_content_dirty(compositor_t* comp, element_handle_t id);
void compositor_mark_all_dirty(compositor_t* comp);
#ifdef __cplusplus
}
#endif
#endif // COMPOSITOR_H
+24 -24
View File
@@ -1,24 +1,24 @@
// image.h
#pragma once
#ifndef IMAGE_H
#define IMAGE_H
#ifdef __cplusplus
#include <cstdint>
extern "C" {
#else
#include <stdint.h>
#endif
// 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. 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);
#ifdef __cplusplus
}
#endif
#endif // IMAGE_H
// image.h
#pragma once
#ifndef IMAGE_H
#define IMAGE_H
#ifdef __cplusplus
#include <cstdint>
extern "C" {
#else
#include <stdint.h>
#endif
// 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. 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);
#ifdef __cplusplus
}
#endif
#endif // IMAGE_H
+44 -44
View File
@@ -1,45 +1,45 @@
// text.h
#pragma once
#ifndef TEXT_RENDERER_H
#define TEXT_RENDERER_H
#include <stdint.h>
struct text_t;
#ifdef __cplusplus
#include <cstdint>
extern "C" {
#else
#include <stdint.h>
#endif
// initializes a new text renderer with given width and height
text_t* text_init(int width, int height);
// shares cache, prefer when using the same font to save memory
text_t* text_init_shared(text_t* source, int width, int height);
// destroys the text renderer and frees resources
void text_destroy(text_t* text);
// pushes new text buffer to be rendered, can be stacked
void text_push(text_t* text,
const char* str,
float speed,
bool outline);
// load font and set as current font
void text_set_font(text_t* text, const char* font_path, float size);
// finish whole current text buffer immediately
void text_show_all_current(text_t* text);
// returns true if text is still animating
bool text_is_animating(text_t* text);
// advance, call every frame, returns true when it touched the framebuffer
bool text_nextframe(text_t* text, float deltatime);
// gets the framebuffer of the text renderer
const uint32_t* text_getfb(text_t* text);
// clears the text buffer and the frame buffer
void text_clear(text_t* text);
#ifdef __cplusplus
}
#endif
// text.h
#pragma once
#ifndef TEXT_RENDERER_H
#define TEXT_RENDERER_H
#include <stdint.h>
struct text_t;
#ifdef __cplusplus
#include <cstdint>
extern "C" {
#else
#include <stdint.h>
#endif
// initializes a new text renderer with given width and height
text_t* text_init(int width, int height);
// shares cache, prefer when using the same font to save memory
text_t* text_init_shared(text_t* source, int width, int height);
// destroys the text renderer and frees resources
void text_destroy(text_t* text);
// pushes new text buffer to be rendered, can be stacked
void text_push(text_t* text,
const char* str,
float speed,
bool outline);
// load font and set as current font
void text_set_font(text_t* text, const char* font_path, float size);
// finish whole current text buffer immediately
void text_show_all_current(text_t* text);
// returns true if text is still animating
bool text_is_animating(text_t* text);
// advance, call every frame, returns true when it touched the framebuffer
bool text_nextframe(text_t* text, float deltatime);
// gets the framebuffer of the text renderer
const uint32_t* text_getfb(text_t* text);
// clears the text buffer and the frame buffer
void text_clear(text_t* text);
#ifdef __cplusplus
}
#endif
#endif // TEXT_RENDERER_H
+77 -77
View File
@@ -1,77 +1,77 @@
// window.h
#pragma once
#ifndef WINDOW_H
#define WINDOW_H
typedef struct window_t window_t;
enum KeyCode {
// Letters
KEY_A = 4,
KEY_D = 7,
KEY_S = 22,
KEY_W = 26,
// Numbers
KEY_1 = 30,
KEY_0 = 39,
// Controls
KEY_RETURN = 40,
KEY_ESCAPE = 41,
KEY_BACKSPACE = 42,
KEY_TAB = 43,
KEY_SPACE = 44,
// Modifiers
KEY_LCTRL = 224,
KEY_LSHIFT = 225,
KEY_RCTRL = 228,
KEY_RSHIFT = 229,
// Navigation
KEY_RIGHT = 79,
KEY_LEFT = 80,
KEY_DOWN = 81,
KEY_UP = 82,
// Function Keys
KEY_F1 = 58,
KEY_F2 = 59
};
enum MouseButton {
MOUSE_BUTTON_LEFT = 1,
MOUSE_BUTTON_MIDDLE = 2,
MOUSE_BUTTON_RIGHT = 3
};
#ifdef __cplusplus
#include <cstdint>
extern "C" {
#else
#include <stdint.h>
#endif
typedef struct { int x, y; } mouse_pos_t;
// creates a window with given width, height and title, title can be utf8
window_t* window_create(int width, int height, const char* title);
// destroys the window
void window_destroy(window_t* s);
// polls window events, returns true if the window should close
bool window_pollevent(window_t* s);
// 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);
// returns true if the given key is currently held down
bool window_is_key_down(window_t* s, int key_scancode);
// returns true if the given mouse button is currently held down
bool window_is_mouse_button_down(window_t* s, int button_index);
// gets the current mouse position in window coordinates
mouse_pos_t window_get_mouse_position(window_t* s);
#ifdef __cplusplus
}
#endif
#endif // WINDOW_H
// window.h
#pragma once
#ifndef WINDOW_H
#define WINDOW_H
typedef struct window_t window_t;
enum KeyCode {
// Letters
KEY_A = 4,
KEY_D = 7,
KEY_S = 22,
KEY_W = 26,
// Numbers
KEY_1 = 30,
KEY_0 = 39,
// Controls
KEY_RETURN = 40,
KEY_ESCAPE = 41,
KEY_BACKSPACE = 42,
KEY_TAB = 43,
KEY_SPACE = 44,
// Modifiers
KEY_LCTRL = 224,
KEY_LSHIFT = 225,
KEY_RCTRL = 228,
KEY_RSHIFT = 229,
// Navigation
KEY_RIGHT = 79,
KEY_LEFT = 80,
KEY_DOWN = 81,
KEY_UP = 82,
// Function Keys
KEY_F1 = 58,
KEY_F2 = 59
};
enum MouseButton {
MOUSE_BUTTON_LEFT = 1,
MOUSE_BUTTON_MIDDLE = 2,
MOUSE_BUTTON_RIGHT = 3
};
#ifdef __cplusplus
#include <cstdint>
extern "C" {
#else
#include <stdint.h>
#endif
typedef struct { int x, y; } mouse_pos_t;
// creates a window with given width, height and title, title can be utf8
window_t* window_create(int width, int height, const char* title);
// destroys the window
void window_destroy(window_t* s);
// polls window events, returns true if the window should close
bool window_pollevent(window_t* s);
// 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);
// returns true if the given key is currently held down
bool window_is_key_down(window_t* s, int key_scancode);
// returns true if the given mouse button is currently held down
bool window_is_mouse_button_down(window_t* s, int button_index);
// gets the current mouse position in window coordinates
mouse_pos_t window_get_mouse_position(window_t* s);
#ifdef __cplusplus
}
#endif
#endif // WINDOW_H
+343 -343
View File
@@ -1,343 +1,343 @@
// audio.cpp
#include "audio.h"
#include <SDL2/SDL.h>
#define MADPCM_IMPLEMENTATION
#include "madpcm.h"
#include <unordered_map>
#include <vector>
#include <cstring>
#include <algorithm>
#define AHEAD_BLOCKS 10
struct AudioDevice {
SDL_AudioDeviceID id = 0;
AudioDevice() = default;
explicit AudioDevice(SDL_AudioDeviceID id) : id(id) {}
~AudioDevice() { if (id) SDL_CloseAudioDevice(id); }
AudioDevice(const AudioDevice&) = delete;
AudioDevice& operator=(const AudioDevice&) = delete;
AudioDevice(AudioDevice&& o) noexcept : id(o.id) { o.id = 0; }
AudioDevice& operator=(AudioDevice&& o) noexcept {
if (this != &o) { if (id) SDL_CloseAudioDevice(id); id = o.id; o.id = 0; }
return *this;
}
operator bool() const { return id != 0; }
};
struct Stream {
uint32_t id = 0;
AudioDevice device;
const uint8_t* data = nullptr;
int total_blocks = 0;
int current_block = 0;
int channels = 0;
bool loop = false;
bool paused = false;
float volume = 1.0f;
float volume_from = 1.0f;
float volume_to = 1.0f;
float volume_anim_elapsed = 0.0f;
float volume_anim_duration = 0.0f;
};
struct RiffHeader {
char riff[4];
uint32_t fileSize;
char wave[4];
};
struct audio_t {
std::unordered_map<uint32_t, Stream> streams;
uint32_t next_id = 1;
float global_volume = 1.0f;
float global_volume_from = 1.0f;
float global_volume_to = 1.0f;
float global_volume_anim_elapsed = 0.0f;
float global_volume_anim_duration = 0.0f;
};
static float clampf(float v, float lo, float hi) {
return v < lo ? lo : v > hi ? hi : v;
}
// Decode one block, scale by combined volume, queue to device.
// Returns false if the stream has ended (non-looping, EOF).
static bool pump_block(audio_t* audio, Stream& s) {
int block_size = (s.channels == 2) ? MADPCM_STEREO_SIZE : MADPCM_MONO_SIZE;
if (s.current_block >= s.total_blocks) {
if (s.loop) s.current_block = 0;
else return false;
}
const uint8_t* block = s.data + s.current_block * block_size;
int16_t pcm[MADPCM_BLOCK_SAMPLES * 2];
int sample_count;
if (s.channels == 2) {
int16_t L[MADPCM_BLOCK_SAMPLES], R[MADPCM_BLOCK_SAMPLES];
madpcm_decode_block_stereo(block, L, R);
for (int i = 0; i < MADPCM_BLOCK_SAMPLES; i++) {
pcm[i * 2] = L[i];
pcm[i * 2 + 1] = R[i];
}
sample_count = MADPCM_BLOCK_SAMPLES * 2;
}
else {
madpcm_decode_block(block, pcm);
sample_count = MADPCM_BLOCK_SAMPLES;
}
float vol = clampf(s.volume * audio->global_volume, 0.0f, 1.0f);
for (int i = 0; i < sample_count; i++)
pcm[i] = static_cast<int16_t>(pcm[i] * vol);
SDL_QueueAudio(s.device.id, pcm, static_cast<uint32_t>(sample_count * sizeof(int16_t)));
s.current_block++;
return true;
}
extern "C" {
audio_t* audio_init() {
if (SDL_InitSubSystem(SDL_INIT_AUDIO) != 0) return nullptr;
return new audio_t{};
}
void audio_destroy(audio_t* audio) {
if (!audio) return;
// streams destructor closes all SDL devices via RAII
delete 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) {
FILE* f = fopen(filename, "rb");
if (!f) return nullptr;
RiffHeader h;
if (fread(&h, sizeof(h), 1, f) != 1 ||
memcmp(h.riff, "RIFF", 4) != 0 ||
memcmp(h.wave, "WAVE", 4) != 0) {
fclose(f);
return nullptr;
}
char chunkId[4];
uint32_t chunkSize;
int fmtFound = 0;
uint16_t audioFmt = 0;
uint16_t numChannels = 0;
uint32_t dataLen = 0;
int rate = 0;
uint8_t* data = nullptr;
while (fread(chunkId, 1, 4, f) == 4) {
if (fread(&chunkSize, 4, 1, f) != 1) break;
if (memcmp(chunkId, "fmt ", 4) == 0) {
if (fread(&audioFmt, 2, 1, f) != 1) break;
if (fread(&numChannels, 2, 1, f) != 1) break;
if (fread(&rate, 4, 1, f) != 1) break;
fseek(f, chunkSize - 8, SEEK_CUR);
fmtFound = 1;
}
else if (memcmp(chunkId, "data", 4) == 0) {
dataLen = chunkSize;
data = static_cast<uint8_t*>(malloc(dataLen));
if (fread(data, 1, dataLen, f) != dataLen) {
free(data);
fclose(f);
return nullptr;
}
break;
}
else {
fseek(f, chunkSize, SEEK_CUR);
}
}
fclose(f);
if (!fmtFound || audioFmt != MADPCM_WAVE_FORMAT || !data) {
if (data) free(data);
return nullptr;
}
if (out_channels) *out_channels = static_cast<int>(numChannels);
if (out_size) *out_size = static_cast<int>(dataLen);
if (out_rate) *out_rate = rate;
if (out_total_samples) {
int blockSize = (numChannels == 2) ? MADPCM_STEREO_SIZE : MADPCM_MONO_SIZE;
int numBlocks = dataLen / blockSize;
*out_total_samples = numBlocks * MADPCM_BLOCK_SAMPLES;
}
return data;
}
void audio_free_data(uint8_t* data) {
if (data) {
free(data);
}
}
audio_stream_id_t audio_play(audio_t* audio, const uint8_t* data, int size,
int channels, int rate, bool loop) {
if (!audio || !data) return 0;
SDL_AudioSpec spec{};
spec.freq = rate;
spec.format = AUDIO_S16SYS;
spec.channels = static_cast<uint8_t>(channels);
spec.samples = MADPCM_BLOCK_SAMPLES;
SDL_AudioDeviceID dev = SDL_OpenAudioDevice(nullptr, 0, &spec, nullptr, 0);
if (!dev) return 0;
int block_size = (channels == 2) ? MADPCM_STEREO_SIZE : MADPCM_MONO_SIZE;
int total_blocks = size / block_size;
uint32_t id = audio->next_id++;
Stream& s = audio->streams[id];
s.id = id;
s.device = AudioDevice(dev);
s.data = data;
s.total_blocks = total_blocks;
s.channels = channels;
s.loop = loop;
for (int i = 0; i < AHEAD_BLOCKS; i++)
if (!pump_block(audio, s)) break;
SDL_PauseAudioDevice(dev, 0);
return id;
}
bool audio_stop(audio_t* audio, audio_stream_id_t id) {
if (!audio) return false;
return audio->streams.erase(id) > 0; // destructor closes the device
}
bool audio_pause(audio_t* audio, audio_stream_id_t id) {
if (!audio) return false;
auto it = audio->streams.find(id);
if (it == audio->streams.end()) return false;
Stream& s = it->second;
if (!s.paused) {
SDL_PauseAudioDevice(s.device.id, 1);
s.paused = true;
}
return true;
}
bool audio_resume(audio_t* audio, audio_stream_id_t id) {
if (!audio) return false;
auto it = audio->streams.find(id);
if (it == audio->streams.end()) return false;
Stream& s = it->second;
if (s.paused) {
SDL_PauseAudioDevice(s.device.id, 0);
s.paused = false;
}
return true;
}
bool audio_is_active(audio_t* audio, audio_stream_id_t id) {
if (!audio) return false;
return audio->streams.count(id) > 0;
}
bool audio_is_playing(audio_t* audio, audio_stream_id_t id) {
if (!audio) return false;
auto it = audio->streams.find(id);
if (it == audio->streams.end()) return false;
return !it->second.paused;
}
bool audio_set_volume(audio_t* audio, audio_stream_id_t id, float volume) {
if (!audio) return false;
auto it = audio->streams.find(id);
if (it == audio->streams.end()) return false;
Stream& s = it->second;
s.volume = clampf(volume, 0.0f, 1.0f);
s.volume_anim_duration = 0.0f;
return true;
}
bool audio_animate_volume(audio_t* audio, audio_stream_id_t id,
float to_volume, float duration_sec) {
if (!audio) return false;
auto it = audio->streams.find(id);
if (it == audio->streams.end()) return false;
Stream& s = it->second;
s.volume_from = s.volume;
s.volume_to = clampf(to_volume, 0.0f, 1.0f);
s.volume_anim_elapsed = 0.0f;
s.volume_anim_duration = duration_sec;
return true;
}
void audio_set_global_volume(audio_t* audio, float volume) {
if (!audio) return;
audio->global_volume = clampf(volume, 0.0f, 1.0f);
audio->global_volume_anim_duration = 0.0f;
}
void audio_animate_global_volume(audio_t* audio,
float to_volume, float duration_sec) {
if (!audio) return;
audio->global_volume_from = audio->global_volume;
audio->global_volume_to = clampf(to_volume, 0.0f, 1.0f);
audio->global_volume_anim_elapsed = 0.0f;
audio->global_volume_anim_duration = duration_sec;
}
void audio_tick(audio_t* audio, float delta_time_sec) {
if (!audio) return;
// Advance global volume animation
if (audio->global_volume_anim_duration > 0.0f) {
audio->global_volume_anim_elapsed += delta_time_sec;
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; }
audio->global_volume = audio->global_volume_from +
(audio->global_volume_to - audio->global_volume_from) * t;
}
// Collect IDs of streams to remove after iteration to avoid invalidating the map
std::vector<uint32_t> to_remove;
for (auto& [id, s] : audio->streams) {
// Advance per-stream volume animation
if (s.volume_anim_duration > 0.0f) {
s.volume_anim_elapsed += delta_time_sec;
float t = s.volume_anim_elapsed / s.volume_anim_duration;
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;
}
if (s.paused) continue;
size_t target_bytes = AHEAD_BLOCKS * MADPCM_BLOCK_SAMPLES
* s.channels * sizeof(int16_t);
while (SDL_GetQueuedAudioSize(s.device.id) < target_bytes) {
if (!pump_block(audio, s)) {
// Non-looping EOF — wait for queue to fully drain then remove
if (SDL_GetQueuedAudioSize(s.device.id) == 0)
to_remove.push_back(id);
break;
}
}
}
for (uint32_t id : to_remove)
audio->streams.erase(id);
}
} // extern "C"
// audio.cpp
#include "audio.h"
#include <SDL2/SDL.h>
#define MADPCM_IMPLEMENTATION
#include "madpcm.h"
#include <unordered_map>
#include <vector>
#include <cstring>
#include <algorithm>
#define AHEAD_BLOCKS 10
struct AudioDevice {
SDL_AudioDeviceID id = 0;
AudioDevice() = default;
explicit AudioDevice(SDL_AudioDeviceID id) : id(id) {}
~AudioDevice() { if (id) SDL_CloseAudioDevice(id); }
AudioDevice(const AudioDevice&) = delete;
AudioDevice& operator=(const AudioDevice&) = delete;
AudioDevice(AudioDevice&& o) noexcept : id(o.id) { o.id = 0; }
AudioDevice& operator=(AudioDevice&& o) noexcept {
if (this != &o) { if (id) SDL_CloseAudioDevice(id); id = o.id; o.id = 0; }
return *this;
}
operator bool() const { return id != 0; }
};
struct Stream {
uint32_t id = 0;
AudioDevice device;
const uint8_t* data = nullptr;
int total_blocks = 0;
int current_block = 0;
int channels = 0;
bool loop = false;
bool paused = false;
float volume = 1.0f;
float volume_from = 1.0f;
float volume_to = 1.0f;
float volume_anim_elapsed = 0.0f;
float volume_anim_duration = 0.0f;
};
struct RiffHeader {
char riff[4];
uint32_t fileSize;
char wave[4];
};
struct audio_t {
std::unordered_map<uint32_t, Stream> streams;
uint32_t next_id = 1;
float global_volume = 1.0f;
float global_volume_from = 1.0f;
float global_volume_to = 1.0f;
float global_volume_anim_elapsed = 0.0f;
float global_volume_anim_duration = 0.0f;
};
static float clampf(float v, float lo, float hi) {
return v < lo ? lo : v > hi ? hi : v;
}
// Decode one block, scale by combined volume, queue to device.
// Returns false if the stream has ended (non-looping, EOF).
static bool pump_block(audio_t* audio, Stream& s) {
int block_size = (s.channels == 2) ? MADPCM_STEREO_SIZE : MADPCM_MONO_SIZE;
if (s.current_block >= s.total_blocks) {
if (s.loop) s.current_block = 0;
else return false;
}
const uint8_t* block = s.data + s.current_block * block_size;
int16_t pcm[MADPCM_BLOCK_SAMPLES * 2];
int sample_count;
if (s.channels == 2) {
int16_t L[MADPCM_BLOCK_SAMPLES], R[MADPCM_BLOCK_SAMPLES];
madpcm_decode_block_stereo(block, L, R);
for (int i = 0; i < MADPCM_BLOCK_SAMPLES; i++) {
pcm[i * 2] = L[i];
pcm[i * 2 + 1] = R[i];
}
sample_count = MADPCM_BLOCK_SAMPLES * 2;
}
else {
madpcm_decode_block(block, pcm);
sample_count = MADPCM_BLOCK_SAMPLES;
}
float vol = clampf(s.volume * audio->global_volume, 0.0f, 1.0f);
for (int i = 0; i < sample_count; i++)
pcm[i] = static_cast<int16_t>(pcm[i] * vol);
SDL_QueueAudio(s.device.id, pcm, static_cast<uint32_t>(sample_count * sizeof(int16_t)));
s.current_block++;
return true;
}
extern "C" {
audio_t* audio_init() {
if (SDL_InitSubSystem(SDL_INIT_AUDIO) != 0) return nullptr;
return new audio_t{};
}
void audio_destroy(audio_t* audio) {
if (!audio) return;
// streams destructor closes all SDL devices via RAII
delete 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) {
FILE* f = fopen(filename, "rb");
if (!f) return nullptr;
RiffHeader h;
if (fread(&h, sizeof(h), 1, f) != 1 ||
memcmp(h.riff, "RIFF", 4) != 0 ||
memcmp(h.wave, "WAVE", 4) != 0) {
fclose(f);
return nullptr;
}
char chunkId[4];
uint32_t chunkSize;
int fmtFound = 0;
uint16_t audioFmt = 0;
uint16_t numChannels = 0;
uint32_t dataLen = 0;
int rate = 0;
uint8_t* data = nullptr;
while (fread(chunkId, 1, 4, f) == 4) {
if (fread(&chunkSize, 4, 1, f) != 1) break;
if (memcmp(chunkId, "fmt ", 4) == 0) {
if (fread(&audioFmt, 2, 1, f) != 1) break;
if (fread(&numChannels, 2, 1, f) != 1) break;
if (fread(&rate, 4, 1, f) != 1) break;
fseek(f, chunkSize - 8, SEEK_CUR);
fmtFound = 1;
}
else if (memcmp(chunkId, "data", 4) == 0) {
dataLen = chunkSize;
data = static_cast<uint8_t*>(malloc(dataLen));
if (fread(data, 1, dataLen, f) != dataLen) {
free(data);
fclose(f);
return nullptr;
}
break;
}
else {
fseek(f, chunkSize, SEEK_CUR);
}
}
fclose(f);
if (!fmtFound || audioFmt != MADPCM_WAVE_FORMAT || !data) {
if (data) free(data);
return nullptr;
}
if (out_channels) *out_channels = static_cast<int>(numChannels);
if (out_size) *out_size = static_cast<int>(dataLen);
if (out_rate) *out_rate = rate;
if (out_total_samples) {
int blockSize = (numChannels == 2) ? MADPCM_STEREO_SIZE : MADPCM_MONO_SIZE;
int numBlocks = dataLen / blockSize;
*out_total_samples = numBlocks * MADPCM_BLOCK_SAMPLES;
}
return data;
}
void audio_free_data(uint8_t* data) {
if (data) {
free(data);
}
}
audio_stream_id_t audio_play(audio_t* audio, const uint8_t* data, int size,
int channels, int rate, bool loop) {
if (!audio || !data) return 0;
SDL_AudioSpec spec{};
spec.freq = rate;
spec.format = AUDIO_S16SYS;
spec.channels = static_cast<uint8_t>(channels);
spec.samples = MADPCM_BLOCK_SAMPLES;
SDL_AudioDeviceID dev = SDL_OpenAudioDevice(nullptr, 0, &spec, nullptr, 0);
if (!dev) return 0;
int block_size = (channels == 2) ? MADPCM_STEREO_SIZE : MADPCM_MONO_SIZE;
int total_blocks = size / block_size;
uint32_t id = audio->next_id++;
Stream& s = audio->streams[id];
s.id = id;
s.device = AudioDevice(dev);
s.data = data;
s.total_blocks = total_blocks;
s.channels = channels;
s.loop = loop;
for (int i = 0; i < AHEAD_BLOCKS; i++)
if (!pump_block(audio, s)) break;
SDL_PauseAudioDevice(dev, 0);
return id;
}
bool audio_stop(audio_t* audio, audio_stream_id_t id) {
if (!audio) return false;
return audio->streams.erase(id) > 0; // destructor closes the device
}
bool audio_pause(audio_t* audio, audio_stream_id_t id) {
if (!audio) return false;
auto it = audio->streams.find(id);
if (it == audio->streams.end()) return false;
Stream& s = it->second;
if (!s.paused) {
SDL_PauseAudioDevice(s.device.id, 1);
s.paused = true;
}
return true;
}
bool audio_resume(audio_t* audio, audio_stream_id_t id) {
if (!audio) return false;
auto it = audio->streams.find(id);
if (it == audio->streams.end()) return false;
Stream& s = it->second;
if (s.paused) {
SDL_PauseAudioDevice(s.device.id, 0);
s.paused = false;
}
return true;
}
bool audio_is_active(audio_t* audio, audio_stream_id_t id) {
if (!audio) return false;
return audio->streams.count(id) > 0;
}
bool audio_is_playing(audio_t* audio, audio_stream_id_t id) {
if (!audio) return false;
auto it = audio->streams.find(id);
if (it == audio->streams.end()) return false;
return !it->second.paused;
}
bool audio_set_volume(audio_t* audio, audio_stream_id_t id, float volume) {
if (!audio) return false;
auto it = audio->streams.find(id);
if (it == audio->streams.end()) return false;
Stream& s = it->second;
s.volume = clampf(volume, 0.0f, 1.0f);
s.volume_anim_duration = 0.0f;
return true;
}
bool audio_animate_volume(audio_t* audio, audio_stream_id_t id,
float to_volume, float duration_sec) {
if (!audio) return false;
auto it = audio->streams.find(id);
if (it == audio->streams.end()) return false;
Stream& s = it->second;
s.volume_from = s.volume;
s.volume_to = clampf(to_volume, 0.0f, 1.0f);
s.volume_anim_elapsed = 0.0f;
s.volume_anim_duration = duration_sec;
return true;
}
void audio_set_global_volume(audio_t* audio, float volume) {
if (!audio) return;
audio->global_volume = clampf(volume, 0.0f, 1.0f);
audio->global_volume_anim_duration = 0.0f;
}
void audio_animate_global_volume(audio_t* audio,
float to_volume, float duration_sec) {
if (!audio) return;
audio->global_volume_from = audio->global_volume;
audio->global_volume_to = clampf(to_volume, 0.0f, 1.0f);
audio->global_volume_anim_elapsed = 0.0f;
audio->global_volume_anim_duration = duration_sec;
}
void audio_tick(audio_t* audio, float delta_time_sec) {
if (!audio) return;
// Advance global volume animation
if (audio->global_volume_anim_duration > 0.0f) {
audio->global_volume_anim_elapsed += delta_time_sec;
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; }
audio->global_volume = audio->global_volume_from +
(audio->global_volume_to - audio->global_volume_from) * t;
}
// Collect IDs of streams to remove after iteration to avoid invalidating the map
std::vector<uint32_t> to_remove;
for (auto& [id, s] : audio->streams) {
// Advance per-stream volume animation
if (s.volume_anim_duration > 0.0f) {
s.volume_anim_elapsed += delta_time_sec;
float t = s.volume_anim_elapsed / s.volume_anim_duration;
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;
}
if (s.paused) continue;
size_t target_bytes = AHEAD_BLOCKS * MADPCM_BLOCK_SAMPLES
* s.channels * sizeof(int16_t);
while (SDL_GetQueuedAudioSize(s.device.id) < target_bytes) {
if (!pump_block(audio, s)) {
// Non-looping EOF — wait for queue to fully drain then remove
if (SDL_GetQueuedAudioSize(s.device.id) == 0)
to_remove.push_back(id);
break;
}
}
}
for (uint32_t id : to_remove)
audio->streams.erase(id);
}
} // 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)
#define STB_IMAGE_IMPLEMENTATION
#include <stb_image.h>
#define STB_IMAGE_RESIZE_IMPLEMENTATION
#include <stb_image_resize2.h>
#pragma warning(pop)
#include <cstdlib>
#include <cstdio>
#include <cstdint>
#include <image.h>
extern "C" {
uint32_t* image_load(const char* filename, int* width, int* height) {
int n;
unsigned char* data = stbi_load(filename, width, height, &n, 4); // force 4 channels (RGBA)
if (!data) return nullptr;
// Convert RGBA to ARGB
uint32_t* out = (uint32_t*)malloc(*width * *height * sizeof(uint32_t));
if (!out) {
stbi_image_free(data);
return nullptr;
}
for (int i = 0; i < *width * *height; ++i) {
uint8_t r = data[4*i + 0];
uint8_t g = data[4*i + 1];
uint8_t b = data[4*i + 2];
uint8_t a = data[4*i + 3];
out[i] = (a << 24) | (r << 16) | (g << 8) | b;
}
stbi_image_free(data);
return out;
}
uint32_t* image_load_resized(const char* filename, int desired_width, int desired_height) {
int original_width, original_height, n;
unsigned char* data = stbi_load(filename, &original_width, &original_height, &n, 4);
if (!data) return nullptr;
// Allocate buffer for resized image
uint32_t* resized = (uint32_t*)malloc(desired_width * desired_height * 4);
if (!resized) {
stbi_image_free(data);
return nullptr;
}
// Resize the image
stbir_resize_uint8_linear(data, original_width, original_height, 0,
(unsigned char* )resized, desired_width, desired_height, 0,
STBIR_RGBA);
stbi_image_free(data);
// 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* 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;
}
return resized;
}
void image_free(uint32_t* data) {
free(data);
}
} // extern "C"
#pragma warning(push, 0)
#define STB_IMAGE_IMPLEMENTATION
#include <stb_image.h>
#define STB_IMAGE_RESIZE_IMPLEMENTATION
#include <stb_image_resize2.h>
#pragma warning(pop)
#include <cstdlib>
#include <cstdio>
#include <cstdint>
#include <image.h>
extern "C" {
uint32_t* image_load(const char* filename, int* width, int* height) {
int n;
unsigned char* data = stbi_load(filename, width, height, &n, 4); // force 4 channels (RGBA)
if (!data) return nullptr;
// Convert RGBA to ARGB
uint32_t* out = (uint32_t*)malloc(*width * *height * sizeof(uint32_t));
if (!out) {
stbi_image_free(data);
return nullptr;
}
for (int i = 0; i < *width * *height; ++i) {
uint8_t r = data[4*i + 0];
uint8_t g = data[4*i + 1];
uint8_t b = data[4*i + 2];
uint8_t a = data[4*i + 3];
out[i] = (a << 24) | (r << 16) | (g << 8) | b;
}
stbi_image_free(data);
return out;
}
uint32_t* image_load_resized(const char* filename, int desired_width, int desired_height) {
int original_width, original_height, n;
unsigned char* data = stbi_load(filename, &original_width, &original_height, &n, 4);
if (!data) return nullptr;
// Allocate buffer for resized image
uint32_t* resized = (uint32_t*)malloc(desired_width * desired_height * 4);
if (!resized) {
stbi_image_free(data);
return nullptr;
}
// Resize the image
stbir_resize_uint8_linear(data, original_width, original_height, 0,
(unsigned char* )resized, desired_width, desired_height, 0,
STBIR_RGBA);
stbi_image_free(data);
// 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* 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;
}
return resized;
}
void image_free(uint32_t* data) {
free(data);
}
} // 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
// 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 <window.h>
#include <SDL2/SDL.h>
#include <cstring>
#include <cstdlib>
#include <cstdio>
#ifdef _WIN32
#include <thread>
#include <mutex>
#include <atomic>
#endif
struct window_t {
SDL_Window* window;
SDL_Renderer* renderer;
SDL_Texture* texture;
int width;
int height;
const uint8_t* keyboard_state;
int mouse_x;
int mouse_y;
uint32_t mouse_buttons;
#ifdef _WIN32
std::thread worker;
std::atomic<bool> running{true};
std::atomic<bool> 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 for cleanup
static void window_cleanup(window_t* s) {
if (!s) return;
if (s->texture) SDL_DestroyTexture(s->texture);
if (s->renderer) SDL_DestroyRenderer(s->renderer);
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<std::mutex> 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<std::mutex> 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) {
#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{};
s->width = width;
s->height = height;
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->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<std::mutex> 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;
}
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<std::mutex> 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 (sc < 0 || sc >= SDL_NUM_SCANCODES) return false;
return s->keyboard_state[sc] != 0;
}
bool window_is_mouse_button_down(window_t* s, int button) {
switch (button) {
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_RIGHT: return s->mouse_buttons & SDL_BUTTON_RMASK;
}
return false;
}
mouse_pos_t window_get_mouse_position(window_t* s) {
return { s->mouse_x, s->mouse_y };
}
}
// 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 <window.h>
#include <SDL2/SDL.h>
#include <cstring>
#include <cstdlib>
#include <cstdio>
#ifdef _WIN32
#include <thread>
#include <mutex>
#include <atomic>
#endif
struct window_t {
SDL_Window* window;
SDL_Renderer* renderer;
SDL_Texture* texture;
int width;
int height;
const uint8_t* keyboard_state;
int mouse_x;
int mouse_y;
uint32_t mouse_buttons;
#ifdef _WIN32
std::thread worker;
std::atomic<bool> running{true};
std::atomic<bool> 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 for cleanup
static void window_cleanup(window_t* s) {
if (!s) return;
if (s->texture) SDL_DestroyTexture(s->texture);
if (s->renderer) SDL_DestroyRenderer(s->renderer);
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<std::mutex> 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<std::mutex> 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) {
#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{};
s->width = width;
s->height = height;
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->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<std::mutex> 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;
}
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<std::mutex> 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 (sc < 0 || sc >= SDL_NUM_SCANCODES) return false;
return s->keyboard_state[sc] != 0;
}
bool window_is_mouse_button_down(window_t* s, int button) {
switch (button) {
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_RIGHT: return s->mouse_buttons & SDL_BUTTON_RMASK;
}
return false;
}
mouse_pos_t window_get_mouse_position(window_t* s) {
return { s->mouse_x, s->mouse_y };
}
}