diff --git a/assets/Freikugeln.wav b/assets/Freikugeln.wav new file mode 100644 index 0000000..55794cf Binary files /dev/null and b/assets/Freikugeln.wav differ diff --git a/include/audio.h b/include/audio.h new file mode 100644 index 0000000..d7c1ddc --- /dev/null +++ b/include/audio.h @@ -0,0 +1,79 @@ +// audio.h +#pragma once +#ifndef AUDIO_H +#define AUDIO_H + +#ifdef __cplusplus +#include +extern "C" { +#else +#include +#include +#endif + +typedef struct audio_t audio_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 +uint32_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, uint32_t id); + +bool audio_pause(audio_t* audio, uint32_t id); +bool audio_resume(audio_t* audio, uint32_t id); + +// Returns true if the stream is active (playing or paused) +bool audio_is_active(audio_t* audio, uint32_t id); + +// Returns true if the stream is currently playing (not paused, not stopped) +bool audio_is_playing(audio_t* audio, uint32_t id); + +// Per-stream volume, immediate effect, range [0.0, 1.0] +bool audio_set_volume(audio_t* audio, uint32_t id, float volume); + +// Animate per-stream volume linearly over time, driven by audio_tick() +bool audio_animate_volume(audio_t* audio, uint32_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 \ No newline at end of file diff --git a/include/compositor.h b/include/compositor.h index 8ccc833..6a49780 100644 --- a/include/compositor.h +++ b/include/compositor.h @@ -1,17 +1,12 @@ -// compositor.h +// compositor.h v0.3 +#pragma once #ifndef COMPOSITOR_H #define COMPOSITOR_H -#include -#include -// Animation types -enum animation_type_t { - ANIM_NONE = 0, - ANIM_MOVE, - ANIM_FADE, - ANIM_SCALE, - ANIM_MOVE_FADE // Combined animation -}; +// 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 { @@ -24,6 +19,7 @@ enum easing_t { EASE_IN_OUT_CUBIC }; +// Anchor points enum anchor_point_t { ANCHOR_TOP_LEFT, ANCHOR_TOP_CENTER, @@ -36,16 +32,29 @@ enum anchor_point_t { ANCHOR_BOTTOM_RIGHT, }; +#ifdef __cplusplus +#include extern "C" { +#else +#include +#include +#endif + typedef struct compositor_t compositor_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 +// 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 uint32_t compositor_insert(compositor_t* comp, const uint32_t* fb, uint32_t fb_width, @@ -55,50 +64,49 @@ uint32_t compositor_insert(compositor_t* comp, anchor_point_t anchor); // Remove element by ID -void compositor_remove(compositor_t* comp, uint32_t id); +bool compositor_remove(compositor_t* comp, uint32_t id); -// AIO Update element properties -void compositor_update(compositor_t* comp, uint32_t id, +// AIO Update element properties, immediate effect +bool compositor_update(compositor_t* comp, uint32_t id, int32_t x, int32_t y, int32_t z_order, float scale, - bool visible); + bool visible, float alpha); -// Individual setters -void compositor_set_position(compositor_t* comp, uint32_t id, +// Individual setters, immediate effect +bool compositor_set_position(compositor_t* comp, uint32_t id, int32_t x, int32_t y); -void compositor_set_zorder(compositor_t* comp, uint32_t id, +bool compositor_set_zorder(compositor_t* comp, uint32_t id, int32_t z_order); -void compositor_set_visible(compositor_t* comp, uint32_t id, - bool visible); -void compositor_set_scale(compositor_t* comp, uint32_t id, +bool compositor_set_scale(compositor_t* comp, uint32_t id, float scale); +bool compositor_set_visible(compositor_t* comp, uint32_t id, + bool visible); +bool compositor_set_alpha(compositor_t* comp, uint32_t id, + float alpha); // Advances all animations by a given time delta. void compositor_tick(compositor_t* comp, float delta_time_sec); -// Instantly sets the element-wide alpha. -void compositor_set_alpha(compositor_t* comp, uint32_t id, float alpha); - -// Animation functions with queue parameter -void compositor_animate_move(compositor_t* comp, uint32_t id, +// 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, uint32_t id, int32_t to_x, int32_t to_y, - float duration_sec, easing_t easing, bool queue = false); - -void compositor_animate_fade(compositor_t* comp, uint32_t id, + float duration_sec, easing_t easing, bool queue); +bool compositor_animate_fade(compositor_t* comp, uint32_t id, float to_alpha, - float duration_sec, easing_t easing, bool queue = false); - -void compositor_animate_scale(compositor_t* comp, uint32_t id, + float duration_sec, easing_t easing, bool queue); +bool compositor_animate_scale(compositor_t* comp, uint32_t id, float to_scale, - float duration_sec, easing_t easing, bool queue = false); + float duration_sec, easing_t easing, bool queue); -// Immediately complete all animations -void compositor_animation_complete(compositor_t* comp, uint32_t id); - -void compositor_animation_complete_all(compositor_t* comp); +// 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, uint32_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); +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, uint32_t id); @@ -106,10 +114,12 @@ bool compositor_is_element_animating(compositor_t* comp, uint32_t id); // Compose all visible elements and return framebuffer pointer const uint32_t* compositor_compose(compositor_t* comp); -void compositor_mark_content_dirty(compositor_t* comp, uint32_t id); +// Mark an element's content as dirty for redrawing +bool compositor_mark_content_dirty(compositor_t* comp, uint32_t id); void compositor_mark_all_dirty(compositor_t* comp); -// Returns the last error message set for this compositor instance. -const char* compositor_get_last_error(compositor_t* comp); +#ifdef __cplusplus } +#endif + #endif // COMPOSITOR_H \ No newline at end of file diff --git a/include/image.h b/include/image.h index 293bfc4..d70c3d1 100644 --- a/include/image.h +++ b/include/image.h @@ -2,15 +2,23 @@ #pragma once #ifndef IMAGE_H #define IMAGE_H -#include +#ifdef __cplusplus +#include extern "C" { +#else +#include +#endif + // Loads an image file as ARGB8888. Returns pointer to pixel data, sets width/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. 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 diff --git a/include/madpcm.h b/include/madpcm.h new file mode 100644 index 0000000..3952653 --- /dev/null +++ b/include/madpcm.h @@ -0,0 +1,592 @@ +// madpcm.h +// A more modern approach to ADPCM. +#pragma once +#ifndef MADPCM_H +#define MADPCM_H + +#include +#include +#include + +#ifdef __AVX2__ +#include +#endif + +#define MADPCM_LPC_ORDER 4 +#define MADPCM_BLOCK_SAMPLES 1024 +#define MADPCM_HEADER_SIZE 24 +#define MADPCM_PAYLOAD_SIZE 512 +#define MADPCM_MONO_SIZE (MADPCM_HEADER_SIZE + MADPCM_PAYLOAD_SIZE) +#define MADPCM_STEREO_SIZE (MADPCM_MONO_SIZE * 2) +#define MADPCM_WAVE_FORMAT 0x4D41 // 'MA' in hex + +// Core MADPCM functions +int madpcm_encode_block(const int16_t* inSamples, const int16_t* prevSamples, uint8_t* outBuffer, bool fast); +int madpcm_encode_block_stereo(const int16_t* inL, const int16_t* inR, const int16_t* prevL, const int16_t* prevR, uint8_t* outBuffer, bool fast); +void madpcm_decode_block(const uint8_t* inBuffer, int16_t* outSamples); +void madpcm_decode_block_stereo(const uint8_t* inBuffer, int16_t* outL, int16_t* outR); + +#ifdef MADPCM_IMPLEMENTATION + +// prepare for the clusterfuck +#pragma region freestanding +#ifndef MADPCM_FREESTANDING +#include +#define madpcm_memset memset +#define madpcm_memcpy memcpy +#else // freestanding +#if defined(MADPCM_MEMSET) || defined(MADPCM_MEMCPY) +// user provided +#if defined(MADPCM_MEMSET) != defined(MADPCM_MEMCPY) +// user fucked up +#error "Both MADPCM_MEMSET and MADPCM_MEMCPY must be defined together" +#endif +#define madpcm_memset MADPCM_MEMSET +#define madpcm_memcpy MADPCM_MEMCPY +#else +// i am running on a toaster implementation +#ifndef MADPCM_INTERNAL_MEMFUNCS +void* memcpy(void* dest, const void* src, size_t count); +void* memset(void* dest, int val, size_t count); +#define madpcm_memset memset +#define madpcm_memcpy memcpy +#else +// truly freestanding +#ifdef _MSC_VER +#include +#pragma intrinsic(__movsb, __stosb) +#endif +static inline void* madpcm_memcpy(void* dest, const void* src, size_t count) { +#if defined(_MSC_VER) && (defined(_M_IX86) || defined(_M_X64)) + __movsb((unsigned char*)dest, (const unsigned char*)src, count); + return dest; +#elif defined(__GNUC__) + return __builtin_memcpy(dest, src, count); +#else + uint8_t* d = (uint8_t*)dest; const uint8_t* s = (const uint8_t*)src; + while (count--) *d++ = *s++; + return dest; +#endif +} +static inline void* madpcm_memset(void* dest, int val, size_t count) { +#if defined(_MSC_VER) && (defined(_M_IX86) || defined(_M_X64)) + __stosb((unsigned char*)dest, (unsigned char)val, count); + return dest; +#elif defined(__GNUC__) + return __builtin_memset(dest, val, count); +#else + uint8_t* d = (uint8_t*)dest; while (count--) *d++ = (uint8_t)val; + return dest; +#endif +} +#endif +#endif +#endif +#pragma endregion + +#define MADPCM_TRELLIS_K 4 +#define MADPCM_Q15_MAX 32767 +#define MADPCM_Q15_MIN -32768 + +// Gaussian Table (Q11) - Optimized for Laplacian residuals +static const int16_t madpcm__gauss_steps[8] = { 205, 614, 1024, 1433, 1843, 2252, 2662, 3200 }; + +typedef struct madpcm__block_header { + int16_t scales[4]; // LSB of scales[0] is Stereo Flag + int16_t coeffs[MADPCM_LPC_ORDER]; + int16_t seed[MADPCM_LPC_ORDER]; // History samples +} madpcm__block_header; + +// Trellis helper struct +typedef struct madpcm__trellis_candidate { + int64_t err; + int32_t p_idx; + int16_t val; + int8_t nib; +} madpcm__trellis_candidate; + +typedef struct madpcm__trellis_state { + int64_t err; + int16_t h[MADPCM_LPC_ORDER]; +} madpcm__trellis_state; + +// Math Helpers +static inline int16_t madpcm__clamp_q15(int32_t value) { +#ifdef __AVX2__ + __m128i v = _mm_cvtsi32_si128(value); + v = _mm_packs_epi32(v, v); // Saturate 32->16 bit + return (int16_t)_mm_cvtsi128_si32(v); +#else + if (value > MADPCM_Q15_MAX) return MADPCM_Q15_MAX; + if (value < MADPCM_Q15_MIN) return MADPCM_Q15_MIN; + return (int16_t)value; +#endif +} +static inline int32_t madpcm__abs_int32(int32_t value) { return (value < 0) ? -value : value; } +static inline int64_t madpcm__abs_int64(int64_t value) { return (value < 0) ? -value : value; } + +static void madpcm__compute_autocorr(const int16_t* signal, int length, int order, int64_t* r_out) { + for (int i = 0; i <= order; i++) { + int64_t sum = 0; + for (int j = 0; j < length - i; j++) sum += (int64_t)signal[j] * signal[j + i]; + r_out[i] = sum; + } + r_out[0] = (r_out[0] > 256) ? r_out[0] : 256; + r_out[0] += (r_out[0] >> 9); +} + +static bool madpcm__compute_LPC(const int64_t* r, int order, int16_t* outCoeffs) { + int32_t a[MADPCM_LPC_ORDER + 1][MADPCM_LPC_ORDER + 1] = { 0 }; + int64_t error[MADPCM_LPC_ORDER + 1]; + error[0] = r[0]; + + for (int k = 1; k <= order; k++) { + int64_t sum = 0; + for (int j = 1; j < k; j++) { + int64_t product = (int64_t)a[k - 1][j] * r[k - j]; + sum += (product >> 24); + } + int64_t numerator = r[k] - sum; + int64_t denominator = error[k - 1]; + if (denominator == 0) denominator = 1; + if (madpcm__abs_int64(numerator) >= madpcm__abs_int64(denominator)) return false; + + int32_t reflection_coeff = (int32_t)((numerator << 24) / denominator); + a[k][k] = reflection_coeff; + + for (int j = 1; j < k; j++) { + int64_t term = (int64_t)reflection_coeff * a[k - 1][k - j]; + a[k][j] = a[k - 1][j] - (int32_t)(term >> 24); + } + + int64_t reflection_sq = (int64_t)reflection_coeff * reflection_coeff; + error[k] = (error[k - 1] * ((1LL << 30) - (reflection_sq >> 18))) >> 30; + } + + for (int i = 0; i < order; i++) { + outCoeffs[i] = madpcm__clamp_q15(a[order][i + 1] >> 12); + } + return true; +} + +// LUT-based Dequantizer +static inline int16_t madpcm__dequantize_LUT(int8_t nibble, int16_t scale) { + int8_t index = nibble & 0x07; + int8_t sign = (nibble & 0x08) ? -1 : 1; + int32_t value = ((int32_t)scale * madpcm__gauss_steps[index]) >> 11; + return (sign < 0) ? (int16_t)-value : (int16_t)value; +} + +// Finds best nibble in O(1) using inverse quantization estimation +static inline int8_t madpcm__find_best_nibble(int32_t target, int16_t scale) { + if (scale < 1) scale = 1; + + int32_t abs_target = madpcm__abs_int32(target); + // Approximation: index ~ (val * 160) >> 16 + // 1/410 (step size) in Q16 is approx 160 + int32_t normalized_val = (abs_target << 11) / scale; + int32_t index = (normalized_val * 160) >> 16; + + if (index > 7) index = 7; + + // Check neighbor to handle boundary cases + int32_t estimate = (scale * madpcm__gauss_steps[index]) >> 11; + int32_t diff1 = madpcm__abs_int32(abs_target - estimate); + + if (index < 7) { + int32_t estimate_next = (scale * madpcm__gauss_steps[index + 1]) >> 11; + int32_t diff2 = madpcm__abs_int32(abs_target - estimate_next); + if (diff2 < diff1) index++; + } + + if (target < 0) return (int8_t)(index | 0x08); + return (int8_t)index; +} + +static inline void madpcm__stereo_to_mid_side(int16_t* left, int16_t* right, int count) { + for (int i = 0; i < count; i++) { + int32_t l_val = left[i], r_val = right[i]; + left[i] = (int16_t)((l_val + r_val) >> 1); + right[i] = (int16_t)((l_val - r_val) >> 1); + } +} + +static inline void madpcm__mid_side_to_stereo(int16_t* left, int16_t* right, int count) { +#ifdef __AVX2__ + for (int i = 0; i < count; i += 16) { + __m256i M = _mm256_loadu_si256((__m256i*)(left + i)); + __m256i S = _mm256_loadu_si256((__m256i*)(right + i)); + _mm256_storeu_si256((__m256i*)(left + i), _mm256_adds_epi16(M, S)); + _mm256_storeu_si256((__m256i*)(right + i), _mm256_subs_epi16(M, S)); + } +#else + for (int i = 0; i < count; i++) { + int32_t mid = left[i], side = right[i]; + left[i] = madpcm__clamp_q15(mid + side); + right[i] = madpcm__clamp_q15(mid - side); + } +#endif +} + +#ifdef __AVX2__ +// SIMD-accelerated dequantization using PSHUFB for LUT +static inline __m128i madpcm__dequantize_8_sse3( + __m128i nibbles, // 8 nibbles in low bytes + int16_t scale +) { + // Build LUT in XMM register (8 entries, 16-bit each) + __m128i lut = _mm_set_epi16( + madpcm__gauss_steps[7], madpcm__gauss_steps[6], + madpcm__gauss_steps[5], madpcm__gauss_steps[4], + madpcm__gauss_steps[3], madpcm__gauss_steps[2], + madpcm__gauss_steps[1], madpcm__gauss_steps[0] + ); + + // Extract index (lower 3 bits) and sign bit + __m128i indices = _mm_and_si128(nibbles, _mm_set1_epi8(0x07)); + __m128i signs = _mm_and_si128(nibbles, _mm_set1_epi8(0x08)); + + // Shuffle to get LUT values (needs careful byte manipulation) + // Convert indices to word positions + __m128i idx_even = _mm_and_si128(indices, _mm_set1_epi16(0x00FF)); + __m128i idx_even_scaled = _mm_slli_epi16(idx_even, 1); + + // This is getting complex - for production, use scalar loop with + // heavily unrolled and pipelined code instead + + // For now, fall back to optimized scalar in the main loop + return _mm_setzero_si128(); +} + +#endif + +// Encoder +int madpcm_encode_block(const int16_t* inSamples, const int16_t* prevSamples, uint8_t* outBuffer, bool fast) { + madpcm__block_header* header = (madpcm__block_header*)outBuffer; + uint8_t* payload = outBuffer + sizeof(madpcm__block_header); + + // Capture History from Raw PCM + // internal history layout: [0]=t-1, [1]=t-2, [2]=t-3, [3]=t-4 + if (prevSamples) { + header->seed[0] = prevSamples[3]; + header->seed[1] = prevSamples[2]; + header->seed[2] = prevSamples[1]; + header->seed[3] = prevSamples[0]; + } else { + madpcm_memset(header->seed, 0, sizeof(header->seed)); + } + + // High Precision Analysis + int64_t autocorrelation[MADPCM_LPC_ORDER + 1] = { 0 }; + madpcm__compute_autocorr(inSamples, MADPCM_BLOCK_SAMPLES, MADPCM_LPC_ORDER, autocorrelation); + + if (!madpcm__compute_LPC(autocorrelation, MADPCM_LPC_ORDER, header->coeffs)) { + madpcm_memset(header->coeffs, 0, sizeof(header->coeffs)); + } + + // Simulation & Heuristics Loop + int64_t signal_energy = 0, residual_energy = 0; + int zero_crossings = 0; + int32_t max_residual_sub[4] = { 0 }; + int32_t max_signal_sub[4] = { 0 }; + + // Initialize simulation history with perfect seeds + int16_t sim_history[MADPCM_LPC_ORDER]; + madpcm_memcpy(sim_history, header->seed, sizeof(sim_history)); + + int32_t c0 = header->coeffs[0], c1 = header->coeffs[1]; + int32_t c2 = header->coeffs[2], c3 = header->coeffs[3]; + bool bad_model = false; + + for (int i = 0; i < MADPCM_BLOCK_SAMPLES; i++) { + int sub_block_idx = i >> 8; + + // ZCR Check: (current ^ prev) < 0 means sign bit changed + if (i > 0 && ((inSamples[i] ^ inSamples[i - 1]) < 0)) + zero_crossings++; + + int32_t prediction = (c0 * sim_history[0]) + (c1 * sim_history[1]) + (c2 * sim_history[2]) + (c3 * sim_history[3]); + prediction >>= 12; + + int32_t residual = inSamples[i] - prediction; + signal_energy += (int64_t)inSamples[i] * inSamples[i]; + residual_energy += (int64_t)residual * residual; + + if (madpcm__abs_int32(residual) > max_residual_sub[sub_block_idx]) max_residual_sub[sub_block_idx] = madpcm__abs_int32(residual); + if (madpcm__abs_int32(inSamples[i]) > max_signal_sub[sub_block_idx]) max_signal_sub[sub_block_idx] = madpcm__abs_int32(inSamples[i]); + + if (madpcm__abs_int32(residual) > 28000) bad_model = true; + + sim_history[3] = sim_history[2]; sim_history[2] = sim_history[1]; sim_history[1] = sim_history[0]; sim_history[0] = inSamples[i]; + } + + bool high_zcr = zero_crossings > (MADPCM_BLOCK_SAMPLES >> 2); + bool low_gain = signal_energy < (8 * (residual_energy + 1)); + bool use_fallback = bad_model || (high_zcr && low_gain); + + if (use_fallback) { + madpcm_memset(header->coeffs, 0, sizeof(header->coeffs)); + c0 = c1 = c2 = c3 = 0; + } + + // Populate scales with Gaussian Correction (~0.66x) + for (int k = 0; k < 4; k++) { + int32_t target_max = use_fallback ? max_signal_sub[k] : max_residual_sub[k]; + int32_t scale_val = (target_max * 2) / 3; + if (scale_val < 10) scale_val = 10; + header->scales[k] = madpcm__clamp_q15(scale_val); + } + + // Trellis Quantization + int16_t current_history[MADPCM_LPC_ORDER]; + madpcm_memcpy(current_history, header->seed, sizeof(current_history)); + + // Process 4 sub-blocks + for (int sb = 0; sb < 4; sb++) { + int16_t current_scale = header->scales[sb]; + const int16_t* block_ptr = inSamples + (sb * 256); + uint8_t* sub_payload_ptr = payload + (sb * 128); + + // FAST MODE: No Trellis + if (fast) { + for (int i = 0; i < 256; i += 2) { + // Nibble 1 + int32_t p1 = (c0 * current_history[0] + c1 * current_history[1] + c2 * current_history[2] + c3 * current_history[3]) >> 12; + int8_t n1 = madpcm__find_best_nibble(block_ptr[i] - p1, current_scale); + int16_t r1 = madpcm__clamp_q15(p1 + madpcm__dequantize_LUT(n1, current_scale)); + current_history[3] = current_history[2]; current_history[2] = current_history[1]; current_history[1] = current_history[0]; current_history[0] = r1; + + // Nibble 2 + int32_t p2 = (c0 * current_history[0] + c1 * current_history[1] + c2 * current_history[2] + c3 * current_history[3]) >> 12; + int8_t n2 = madpcm__find_best_nibble(block_ptr[i + 1] - p2, current_scale); + int16_t r2 = madpcm__clamp_q15(p2 + madpcm__dequantize_LUT(n2, current_scale)); + current_history[3] = current_history[2]; current_history[2] = current_history[1]; current_history[1] = current_history[0]; current_history[0] = r2; + + sub_payload_ptr[i >> 1] = (n1 & 0x0F) | (n2 << 4); + } + continue; + } + + // QUALITY MODE: Trellis + // Traceback buffers [sample][path] + int8_t trace_nibble[256][MADPCM_TRELLIS_K]; + int8_t trace_index[256][MADPCM_TRELLIS_K]; + + madpcm__trellis_state current_states[MADPCM_TRELLIS_K]; + int active_paths = 1; + current_states[0].err = 0; + madpcm_memcpy(current_states[0].h, current_history, sizeof(current_history)); + + for (int sample_idx = 0; sample_idx < 256; sample_idx++) { + madpcm__trellis_candidate candidates[MADPCM_TRELLIS_K * 3]; + int candidate_count = 0; + + for (int path_idx = 0; path_idx < active_paths; path_idx++) { + int16_t* path_hist = current_states[path_idx].h; + int32_t prediction = (c0 * path_hist[0] + c1 * path_hist[1] + c2 * path_hist[2] + c3 * path_hist[3]) >> 12; + int32_t residual_target = block_ptr[sample_idx] - prediction; + + // Check best greedy + neighbors + int8_t best_nibble = madpcm__find_best_nibble(residual_target, current_scale); + int8_t center_idx = (best_nibble & 0x08) ? (best_nibble & 0x07) + 8 : best_nibble; + + for (int offset = -1; offset <= 1; offset++) { + int search_idx = center_idx + offset; + if (search_idx < 0 || search_idx > 15) continue; + + int8_t current_nib = (search_idx < 8) ? (int8_t)search_idx : (int8_t)((search_idx & 0x07) | 0x08); + int16_t reconstructed = madpcm__clamp_q15(prediction + madpcm__dequantize_LUT(current_nib, current_scale)); + int64_t diff = block_ptr[sample_idx] - reconstructed; + int64_t squared_error = diff * diff; + + candidates[candidate_count].err = current_states[path_idx].err + squared_error; + candidates[candidate_count].p_idx = path_idx; + candidates[candidate_count].nib = current_nib; + candidates[candidate_count].val = reconstructed; + candidate_count++; + } + } + + // Select best K + madpcm__trellis_state next_states[MADPCM_TRELLIS_K]; + int next_count = 0; + + for (int k = 0; k < MADPCM_TRELLIS_K && k < candidate_count; k++) { + int best_cand_idx = -1; + int64_t min_err = -1; + for (int j = 0; j < candidate_count; j++) { + if (candidates[j].err == -1) continue; + if (best_cand_idx == -1 || candidates[j].err < min_err) { min_err = candidates[j].err; best_cand_idx = j; } + } + if (best_cand_idx == -1) break; + + // Save traceback data instead of full path copy + trace_index[sample_idx][k] = (int8_t)candidates[best_cand_idx].p_idx; + trace_nibble[sample_idx][k] = candidates[best_cand_idx].nib; + + next_states[k] = current_states[candidates[best_cand_idx].p_idx]; + next_states[k].err = candidates[best_cand_idx].err; + + int16_t* next_hist = next_states[k].h; + next_hist[3] = next_hist[2]; next_hist[2] = next_hist[1]; next_hist[1] = next_hist[0]; next_hist[0] = candidates[best_cand_idx].val; + + candidates[best_cand_idx].err = -1; // Mark used + next_count++; + } + active_paths = next_count; + madpcm_memcpy(current_states, next_states, sizeof(madpcm__trellis_state) * active_paths); + } + + // Commit best path (Backtracking) + madpcm_memcpy(current_history, current_states[0].h, sizeof(current_history)); + int backtrack_path_idx = 0; // Index 0 is best due to sort + + uint8_t final_nibbles[256]; + for (int i = 255; i >= 0; i--) { + final_nibbles[i] = trace_nibble[i][backtrack_path_idx]; + backtrack_path_idx = trace_index[i][backtrack_path_idx]; + } + + for (int j = 0; j < 128; j++) { + sub_payload_ptr[j] = (final_nibbles[2 * j] & 0x0F) | (final_nibbles[2 * j + 1] << 4); + } + } + + return sizeof(madpcm__block_header) + (MADPCM_BLOCK_SAMPLES / 2); +} + +// Adaptive Stereo Encoder +int madpcm_encode_block_stereo(const int16_t* inL, const int16_t* inR, const int16_t* prevL, const int16_t* prevR, uint8_t* outBuffer, bool fast) { + int16_t buffer_left[MADPCM_BLOCK_SAMPLES], buffer_right[MADPCM_BLOCK_SAMPLES]; + int16_t history_left[4], history_right[4]; + + madpcm_memcpy(buffer_left, inL, sizeof(buffer_left)); + madpcm_memcpy(buffer_right, inR, sizeof(buffer_right)); + + // Prepare local history copies (safe to modify) + if (prevL && prevR) { + madpcm_memcpy(history_left, prevL, 4 * sizeof(int16_t)); + madpcm_memcpy(history_right, prevR, 4 * sizeof(int16_t)); + } else { + madpcm_memset(history_left, 0, sizeof(history_left)); + madpcm_memset(history_right, 0, sizeof(history_right)); + } + + // Adaptive Check: Calculate Energies + int64_t energy_lr = 0, energy_ms = 0; + for (int i = 0; i < MADPCM_BLOCK_SAMPLES; i += 4) { + int32_t mid = (buffer_left[i] + buffer_right[i]) >> 1; + int32_t side = (buffer_left[i] - buffer_right[i]) >> 1; + energy_lr += madpcm__abs_int32(buffer_left[i]) + madpcm__abs_int32(buffer_right[i]); + energy_ms += madpcm__abs_int32(mid) + madpcm__abs_int32(side); + } + + bool use_ms = (energy_ms * 10 < energy_lr * 9); + + // If using MS, we must also convert the HISTORY to MS + if (use_ms) { + madpcm__stereo_to_mid_side(buffer_left, buffer_right, MADPCM_BLOCK_SAMPLES); + madpcm__stereo_to_mid_side(history_left, history_right, 4); + } + + int size_left = madpcm_encode_block(buffer_left, history_left, outBuffer, fast); + int size_right = madpcm_encode_block(buffer_right, history_right, outBuffer + size_left, fast); + + // Flagging: Use LSB of first scale + madpcm__block_header* main_header = (madpcm__block_header*)outBuffer; + main_header->scales[0] = (main_header->scales[0] & ~1) | (use_ms ? 1 : 0); + + return size_left + size_right; +} + +// Decoder +void madpcm_decode_block(const uint8_t* inBuffer, int16_t* outSamples) { + const madpcm__block_header* header = (const madpcm__block_header*)inBuffer; + const uint8_t* payload = inBuffer + sizeof(madpcm__block_header); + + const int32_t c0 = header->coeffs[0]; + const int32_t c1 = header->coeffs[1]; + const int32_t c2 = header->coeffs[2]; + const int32_t c3 = header->coeffs[3]; + + int16_t h0 = header->seed[0]; + int16_t h1 = header->seed[1]; + int16_t h2 = header->seed[2]; + int16_t h3 = header->seed[3]; + + int current_sample_idx = 0; + +#ifdef __AVX2__ + // Process in batches of 32 samples (16 bytes) + for (int s_idx = 0; s_idx < MADPCM_BLOCK_SAMPLES / 256; s_idx++) { + int16_t scale = header->scales[s_idx] & ~1; + + // Unroll by 8 for better pipelining + for (int i = 0; i < 256; i += 8) { + // Load 4 bytes = 8 nibbles + uint32_t packed = *(uint32_t*)(payload + (current_sample_idx >> 1)); + + // Unroll 8 iterations manually + #define DECODE_SAMPLE(shift) \ + { \ + int32_t pred = (c0 * h0 + c1 * h1 + c2 * h2 + c3 * h3) >> 12; \ + int8_t nibble = (packed >> (shift)) & 0x0F; \ + int16_t delta = madpcm__dequantize_LUT(nibble, scale); \ + int32_t val = pred + delta; \ + h3 = h2; h2 = h1; h1 = h0; \ + h0 = madpcm__clamp_q15(val); \ + outSamples[current_sample_idx++] = h0; \ + } + + DECODE_SAMPLE(0); + DECODE_SAMPLE(4); + DECODE_SAMPLE(8); + DECODE_SAMPLE(12); + DECODE_SAMPLE(16); + DECODE_SAMPLE(20); + DECODE_SAMPLE(24); + DECODE_SAMPLE(28); + + #undef DECODE_SAMPLE + } + } +#else + // Each scale applies to 256 samples + for (int s_idx = 0; s_idx < MADPCM_BLOCK_SAMPLES / 256; s_idx++) { + int16_t current_scale = header->scales[s_idx] & ~1; + + for (int i = 0; i < 256; i++, current_sample_idx++) { + int32_t prediction = (c0 * h0 + c1 * h1 + c2 * h2 + c3 * h3) >> 12; + + uint8_t payload_byte = payload[current_sample_idx >> 1]; + int8_t current_nibble = (current_sample_idx & 1) + ? (payload_byte >> 4) + : (payload_byte & 0x0F); + + int16_t delta = madpcm__dequantize_LUT(current_nibble, current_scale); + int32_t final_val = prediction + delta; + + h3 = h2; + h2 = h1; + h1 = h0; + h0 = madpcm__clamp_q15(final_val); + + outSamples[current_sample_idx] = h0; + } + } +#endif +} + +void madpcm_decode_block_stereo(const uint8_t* inBuffer, int16_t* outL, int16_t* outR) { + const madpcm__block_header* header = (const madpcm__block_header*)inBuffer; + bool is_mid_side = (header->scales[0] & 1); + + int mono_block_size = sizeof(madpcm__block_header) + (MADPCM_BLOCK_SAMPLES / 2); + madpcm_decode_block(inBuffer, outL); + madpcm_decode_block(inBuffer + mono_block_size, outR); + + if (is_mid_side) madpcm__mid_side_to_stereo(outL, outR, MADPCM_BLOCK_SAMPLES); +} + +#endif // MADPCM_IMPLEMENTATION +#endif // MADPCM_H \ No newline at end of file diff --git a/include/stb_image_resize2.h b/include/stb_image_resize2.h index 86e66d2..65aa576 100644 --- a/include/stb_image_resize2.h +++ b/include/stb_image_resize2.h @@ -1221,7 +1221,7 @@ static stbir__inline stbir_uint8 stbir__linear_to_srgb_uchar(float in) #define STBIR_STREAMOUT_PTR( star ) star __restrict__ #define STBIR_NO_UNROLL( ptr ) __asm__ (""::"r"(ptr)) #if __GNUC__ >= 14 - #define STBIR_NO_UNROLL_LOOP_START _Pragma("GCC unroll 0") _Pragma("GCC novector") + #define STBIR_NO_UNROLL_LOOP_START _Pragma("GCC novector") #else #define STBIR_NO_UNROLL_LOOP_START #endif diff --git a/include/text.h b/include/text.h index e671003..f7b5f93 100644 --- a/include/text.h +++ b/include/text.h @@ -1,4 +1,5 @@ // text.h +#pragma once #ifndef TEXT_RENDERER_H #define TEXT_RENDERER_H @@ -6,7 +7,13 @@ struct text_t; +#ifdef __cplusplus +#include extern "C" { +#else +#include +#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 @@ -30,6 +37,9 @@ bool text_nextframe(text_t* text, float deltatime); 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 \ No newline at end of file diff --git a/include/window.h b/include/window.h index cba0465..86e0327 100644 --- a/include/window.h +++ b/include/window.h @@ -2,39 +2,57 @@ #pragma once #ifndef WINDOW_H #define WINDOW_H -#include typedef struct window_t window_t; -#define KEY_RETURN 40 -#define KEY_SPACE 44 -#define KEY_ESCAPE 41 -#define KEY_LSHIFT 42 -#define KEY_RSHIFT 43 -#define KEY_LCTRL 29 -#define KEY_RCTRL 157 +enum KeyCode { + // Letters + KEY_A = 4, + KEY_D = 7, + KEY_S = 22, + KEY_W = 26, + + // Numbers + KEY_1 = 30, + KEY_0 = 39, -#define MOUSE_BUTTON_LEFT 1 -#define MOUSE_BUTTON_MIDDLE 2 -#define MOUSE_BUTTON_RIGHT 3 + // Controls + KEY_RETURN = 40, + KEY_ESCAPE = 41, + KEY_BACKSPACE = 42, + KEY_TAB = 43, + KEY_SPACE = 44, -#define KEY_UP 82 -#define KEY_DOWN 81 -#define KEY_LEFT 80 -#define KEY_RIGHT 79 -#define KEY_BACKSPACE 42 + // Modifiers + KEY_LCTRL = 224, + KEY_LSHIFT = 225, + KEY_RCTRL = 228, + KEY_RSHIFT = 229, -#define KEY_F1 58 -#define KEY_F2 59 + // Navigation + KEY_RIGHT = 79, + KEY_LEFT = 80, + KEY_DOWN = 81, + KEY_UP = 82, -#define KEY_A 4 -#define KEY_D 7 -#define KEY_W 26 -#define KEY_S 22 -#define KEY_1 30 -#define KEY_0 39 + // 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 extern "C" { +#else +#include +#endif + // 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 @@ -49,6 +67,9 @@ bool window_is_key_down(window_t* s, int key_scancode); bool window_is_mouse_button_down(window_t* s, int button_index); // gets the current mouse position in window coordinates void window_get_mouse_position(window_t* s, int* x, int* y); + +#ifdef __cplusplus } +#endif #endif // WINDOW_H diff --git a/src/audio.cpp b/src/audio.cpp new file mode 100644 index 0000000..1e61149 --- /dev/null +++ b/src/audio.cpp @@ -0,0 +1,343 @@ +// audio.cpp +#include "audio.h" +#include +#define MADPCM_IMPLEMENTATION +#include "madpcm.h" +#include +#include +#include +#include + +#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 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(pcm[i] * vol); + + SDL_QueueAudio(s.device.id, pcm, static_cast(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(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(numChannels); + if (out_size) *out_size = static_cast(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); + } +} + +uint32_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(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, uint32_t id) { + if (!audio) return false; + return audio->streams.erase(id) > 0; // destructor closes the device +} + +bool audio_pause(audio_t* audio, uint32_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, uint32_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, uint32_t id) { + if (!audio) return false; + return audio->streams.count(id) > 0; +} + +bool audio_is_playing(audio_t* audio, uint32_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, uint32_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, uint32_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 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" diff --git a/src/compositor.cpp b/src/compositor.cpp index f0a483b..e9917ff 100644 --- a/src/compositor.cpp +++ b/src/compositor.cpp @@ -4,9 +4,6 @@ #include #include #include -#if defined(__x86_64__) || defined(__i386__) || defined(_M_X64) || defined(_M_IX86) -#include -#endif #include // Define the tile size @@ -18,6 +15,15 @@ struct property_anim_t { easing_t easing; }; +// Animation types +enum animation_type_t { + ANIM_NONE = 0, + ANIM_MOVE, + ANIM_FADE, + ANIM_SCALE, + ANIM_MOVE_FADE // Combined animation +}; + struct animation_t { animation_type_t type = ANIM_NONE; @@ -62,16 +68,17 @@ struct queued_animation_t { }; struct element_t { - const uint32_t* fb; + const uint32_t* fb; // Format ARGB8888 uint32_t fb_width; uint32_t fb_height; int32_t x, y; int32_t z_order; // Used for draw order bool visible; + bool valid; // Do we still exist? float scale; float alpha = 1.0f; anchor_point_t anchor; // Anchor point for positioning and scaling - uint32_t id; // Unique identifier + uint32_t id; // Unique identifier std::vector occupied_tiles; // List of occupied tiles animation_t anim; std::vector anim_queue; @@ -84,11 +91,11 @@ struct compositor_t { uint32_t height; // Element Management - // 1. Storage: Map unique ID to element data (O(1) lookup) + // Storage: Map unique ID to element data (O(1) lookup) std::unordered_map elements_map; uint32_t next_id = 1; // Unique ID counter (0 is reserved/invalid) - // 2. Draw Order: Vector of IDs, maintained sorted by z_order + // Draw Order: Vector of IDs, maintained sorted by z_order std::vector draw_order; // Tiling System (Spatial Hashing) @@ -98,13 +105,8 @@ struct compositor_t { std::vector dirty_tiles; // tile_map: List of element IDs that overlap each tile std::vector> tile_map; - - // Error Reporting - std::string last_error; }; -// --- Private Helper Functions --- - // Linear interpolation (lerp) static float lerp(float a, float b, float t) { return a + (b - a) * t; @@ -132,29 +134,22 @@ static float apply_easing(easing_t easing, float t) { } } -// --- Core Utilities --- - -// Sets the last error string for the compositor instance. -static void set_error(compositor_t* comp, const std::string& msg) { - if (comp) { - comp->last_error = msg; - } -} - // Finds the element structure corresponding to a given ID, or nullptr if not found. static element_t* find_element(compositor_t* comp, uint32_t id) { auto it = comp->elements_map.find(id); if (it == comp->elements_map.end()) { return nullptr; } + if (!it->second.valid) { + return nullptr; + } return &it->second; } // Calculates the final screen-space bounding box for an element (THE source of truth). static inline void get_element_bounds(const element_t* elem, int32_t& out_x0, int32_t& out_y0, - int32_t& out_x1, int32_t& out_y1) -{ + int32_t& out_x1, int32_t& out_y1) { // Calculate scaled dimensions, rounding to nearest integer to prevent jitter int32_t scaled_width = (int32_t)std::round(elem->fb_width * elem->scale); int32_t scaled_height = (int32_t)std::round(elem->fb_height * elem->scale); @@ -186,8 +181,6 @@ static inline void get_element_bounds(const element_t* elem, out_y1 = out_y0 + scaled_height; } -// --- Spatial Hashing & Dirty Rects --- - // Marks all tiles an element occupies as dirty. static void mark_element_dirty(compositor_t* comp, const element_t* elem) { if (!elem || !elem->visible) return; @@ -268,13 +261,13 @@ static void update_draw_order(compositor_t* comp, uint32_t id) { element_t* elem = find_element(comp, id); if (!elem) return; - // 1. Remove element from its current position in draw_order + // Remove element from its current position in draw_order auto it_to_remove = std::find(comp->draw_order.begin(), comp->draw_order.end(), id); if (it_to_remove != comp->draw_order.end()) { comp->draw_order.erase(it_to_remove); } - // 2. Find the new correct position using binary search logic (lower_bound) + // Find the new correct position using binary search logic (lower_bound) auto it_insert = std::lower_bound( comp->draw_order.begin(), comp->draw_order.end(), @@ -287,7 +280,7 @@ static void update_draw_order(compositor_t* comp, uint32_t id) { } ); - // 3. Insert the ID at the new position + // Insert the ID at the new position comp->draw_order.insert(it_insert, id); } @@ -299,6 +292,7 @@ void compositor_tick(compositor_t* comp, float delta_time_sec) { for (auto& pair : comp->elements_map) { element_t* elem = &pair.second; + if (!elem->valid) continue; animation_t* anim = &elem->anim; // Check if we need to start a queued animation @@ -433,9 +427,8 @@ compositor_t* compositor_init(uint32_t width, uint32_t height) { comp->tile_map.resize(total_tiles); } - catch (const std::bad_alloc& e) { + catch (const std::bad_alloc&) { // Handle allocation failure during initialization of vectors - set_error(comp, "Allocation failed during compositor_init: " + std::string(e.what())); delete comp; return nullptr; } @@ -458,22 +451,21 @@ uint32_t compositor_insert(compositor_t* comp, int32_t z_order, float scale, anchor_point_t anchor) { if (!comp) { - // Cannot set error if comp is NULL, must return 0 + // Cannot set error if comp is NULL return 0; } if (!fb) { - set_error(comp, "Invalid framebuffer pointer (NULL) passed to insert."); return 0; } - // 1. Generate unique ID + // Generate unique ID uint32_t id = comp->next_id++; if (id == 0) { // Wrap around safety comp->next_id = 1; id = comp->next_id++; } - // 2. Create and store the new element in the map + // Create and store the new element in the map element_t new_elem = { .fb = fb, .fb_width = fb_width, @@ -482,101 +474,108 @@ uint32_t compositor_insert(compositor_t* comp, .y = y, .z_order = z_order, .visible = true, + .valid = true, .scale = scale, .anchor = anchor, .id = id }; comp->elements_map[id] = new_elem; - // 3. Update the sorted draw order + // Update the sorted draw order update_draw_order(comp, id); - // 4. Update spatial hash and mark tiles dirty + // Update spatial hash and mark tiles dirty update_spatial_hash(comp, &comp->elements_map[id]); // Pass pointer to map value - - // Clear last error on success - comp->last_error.clear(); return id; } // Removes an element from the compositor. -void compositor_remove(compositor_t* comp, uint32_t id) { - if (!comp) return; +bool compositor_remove(compositor_t* comp, uint32_t id) { + if (!comp) { + return false; + } element_t* elem = find_element(comp, id); if (!elem) { - set_error(comp, "Element with ID " + std::to_string(id) + " not found for removal."); - return; + return false; } - // 1. Mark area dirty before removal (to force background redraw) + // Mark area dirty before removal (to force background redraw) mark_element_dirty(comp, elem); - // 2. Remove from draw order + // Remove from draw order auto it = std::find(comp->draw_order.begin(), comp->draw_order.end(), id); if (it != comp->draw_order.end()) { comp->draw_order.erase(it); } - // 3. Remove from spatial hash + // Remove from spatial hash clear_spatial_hash(comp, id); - // 4. Remove from element map + // Mark as invalid before erasing + elem->valid = false; + + // Remove from element map comp->elements_map.erase(id); - // Clear last error on success - comp->last_error.clear(); + return true; } // Updates all properties of an element in a single "heavy" call. -void compositor_update(compositor_t* comp, uint32_t id, +bool compositor_update(compositor_t* comp, uint32_t id, int32_t x, int32_t y, int32_t z_order, float scale, - bool visible) { - if (!comp) return; + bool visible, float alpha) { + if (!comp) return false; element_t* elem = find_element(comp, id); if (!elem) { - set_error(comp, "Element with ID " + std::to_string(id) + " not found for update."); - return; + return false; } - // 1. Mark old area dirty (based on current position/size) + // Mark old area dirty (based on current position/size) mark_element_dirty(comp, elem); clear_spatial_hash(comp, id); // Clear hash entries for old position/size - // 2. Update properties + // Update properties elem->x = x; elem->y = y; - // 3. Check for z_order change + // Check for z_order change bool z_order_changed = (elem->z_order != z_order); elem->z_order = z_order; elem->scale = scale; elem->visible = visible; - // 4. Update the spatial hash and mark the new area dirty + // Update the spatial hash and mark the new area dirty update_spatial_hash(comp, elem); - // 5. Update draw order if z_order changed + // Update draw order if z_order changed if (z_order_changed) { update_draw_order(comp, id); } - // Clear last error on success - comp->last_error.clear(); + elem->alpha = std::max(0.0f, std::min(1.0f, alpha)); + + if (elem->alpha == 0.0f) { + mark_element_dirty(comp, elem); + } + + return true; } // setter for an element's (x, y) position. -void compositor_set_position(compositor_t* comp, uint32_t id, - int32_t x, int32_t y) { - if (!comp) return; +bool compositor_set_position(compositor_t* comp, uint32_t id, int32_t x, int32_t y) { + if (!comp) { + return false; + } + element_t* elem = find_element(comp, id); if (!elem) { - set_error(comp, "Element with ID " + std::to_string(id) + " not found to set position."); - return; + return false; } + // Mark old area dirty and clear spatial hash mark_element_dirty(comp, elem); clear_spatial_hash(comp, id); @@ -584,35 +583,31 @@ void compositor_set_position(compositor_t* comp, uint32_t id, elem->y = y; // Update spatial hash and mark new area dirty update_spatial_hash(comp, elem); - // Clear last error on success - comp->last_error.clear(); + + return true; } // setter for an element's z-order. -void compositor_set_zorder(compositor_t* comp, uint32_t id, - int32_t z_order) { - if (!comp) return; +bool compositor_set_zorder(compositor_t* comp, uint32_t id, int32_t z_order) { + if (!comp) return false; element_t* elem = find_element(comp, id); if (!elem) { - set_error(comp, "Element with ID " + std::to_string(id) + " not found to set z-order."); - return; + return false; } if (elem->z_order != z_order) { elem->z_order = z_order; update_draw_order(comp, id); } - // Clear last error on success - comp->last_error.clear(); + + return true; } // setter for an element's visibility. -void compositor_set_visible(compositor_t* comp, uint32_t id, - bool visible) { - if (!comp) return; +bool compositor_set_visible(compositor_t* comp, uint32_t id, bool visible) { + if (!comp) return false; element_t* elem = find_element(comp, id); if (!elem) { - set_error(comp, "Element with ID " + std::to_string(id) + " not found to set visibility."); - return; + return false; } if (elem->visible != visible) { elem->visible = visible; @@ -624,42 +619,37 @@ void compositor_set_visible(compositor_t* comp, uint32_t id, clear_spatial_hash(comp, id); } } - // Clear last error on success - comp->last_error.clear(); + + return true; } // setter for an element's scale. -void compositor_set_scale(compositor_t* comp, uint32_t id, - float scale) { - if (!comp) return; +bool compositor_set_scale(compositor_t* comp, uint32_t id, float scale) { + if (!comp) return false; element_t* elem = find_element(comp, id); if (!elem) { - set_error(comp, "Element with ID " + std::to_string(id) + " not found to set scale."); - return; + return false; } if (scale <= 0.0f) { - set_error(comp, "Invalid scale value (must be > 0): " + std::to_string(scale)); - return; + return false; } if (elem->scale == scale) { - comp->last_error.clear(); - return; + return true; } mark_element_dirty(comp, elem); clear_spatial_hash(comp, id); elem->scale = scale; update_spatial_hash(comp, elem); - // Clear last error on success - comp->last_error.clear(); + + return true; } // setter for an element's alpha. -void compositor_set_alpha(compositor_t* comp, uint32_t id, float alpha) { - if (!comp) return; +bool compositor_set_alpha(compositor_t* comp, uint32_t id, float alpha) { + if (!comp) return false; element_t* elem = find_element(comp, id); if (!elem) { - set_error(comp, "Element not found to set alpha."); - return; + return false; } elem->alpha = std::max(0.0f, std::min(1.0f, alpha)); @@ -676,19 +666,22 @@ void compositor_set_alpha(compositor_t* comp, uint32_t id, float alpha) { // will handle setting visible=false on completion. // This setter is for instant changes. } + + return true; } // setter for an element's anchor point. -void compositor_set_anchor(compositor_t* comp, uint32_t id, anchor_point_t anchor) { - if (!comp) return; +bool compositor_set_anchor(compositor_t* comp, uint32_t id, anchor_point_t anchor) { + if (!comp) { + return false; + } + element_t* elem = find_element(comp, id); if (!elem) { - set_error(comp, "Element with ID " + std::to_string(id) + " not found to set anchor."); - return; + return false; } if (elem->anchor == anchor) { - comp->last_error.clear(); - return; + return true; } // Changing anchor changes bounds if scale != 1.0, so treat it like a move/scale. @@ -699,22 +692,24 @@ void compositor_set_anchor(compositor_t* comp, uint32_t id, anchor_point_t ancho update_spatial_hash(comp, elem); - // Clear last error on success - comp->last_error.clear(); + return true; } -void compositor_animate_move(compositor_t* comp, uint32_t id, +bool compositor_animate_move(compositor_t* comp, uint32_t id, int32_t to_x, int32_t to_y, float duration_sec, easing_t easing, bool queue) { - if (!comp) return; + if (!comp) { + return false; + } + element_t* elem = find_element(comp, id); if (!elem) { - set_error(comp, "Element not found for animate_move."); - return; + return false; } if (queue) { queued_animation_t queued; + queued.type = ANIM_MOVE; queued.easing = easing; queued.duration_sec = duration_sec; @@ -725,7 +720,8 @@ void compositor_animate_move(compositor_t* comp, uint32_t id, queued.affects_scale = false; queued.affects_alpha = false; elem->anim_queue.push_back(queued); - return; + + return true; } // Parallel execution with independent timing @@ -743,20 +739,24 @@ void compositor_animate_move(compositor_t* comp, uint32_t id, if (elem->anim.type == ANIM_NONE) { elem->anim.type = ANIM_MOVE; } + + return true; } -void compositor_animate_scale(compositor_t* comp, uint32_t id, - float to_scale, - float duration_sec, easing_t easing, bool queue) { - if (!comp) return; +bool compositor_animate_scale(compositor_t* comp, uint32_t id, + float to_scale, float duration_sec, easing_t easing, bool queue) { + if (!comp) { + return false; + } + element_t* elem = find_element(comp, id); if (!elem) { - set_error(comp, "Element not found for animate_scale."); - return; + return false; } if (queue) { queued_animation_t queued; + queued.type = ANIM_SCALE; queued.easing = easing; queued.duration_sec = duration_sec; @@ -766,7 +766,8 @@ void compositor_animate_scale(compositor_t* comp, uint32_t id, queued.affects_scale = true; queued.affects_alpha = false; elem->anim_queue.push_back(queued); - return; + + return true; } // Parallel execution with independent timing @@ -782,22 +783,26 @@ void compositor_animate_scale(compositor_t* comp, uint32_t id, if (elem->anim.type == ANIM_NONE) { elem->anim.type = ANIM_SCALE; } + + return true; } -void compositor_animate_fade(compositor_t* comp, uint32_t id, - float to_alpha, - float duration_sec, easing_t easing, bool queue) { - if (!comp) return; +bool compositor_animate_fade(compositor_t* comp, uint32_t id, + float to_alpha, float duration_sec, easing_t easing, bool queue) { + if (!comp) { + return false; + } + element_t* elem = find_element(comp, id); if (!elem) { - set_error(comp, "Element not found for animate_fade."); - return; + return false; } to_alpha = std::max(0.0f, std::min(1.0f, to_alpha)); if (queue) { queued_animation_t queued; + queued.type = ANIM_FADE; queued.easing = easing; queued.duration_sec = duration_sec; @@ -807,7 +812,8 @@ void compositor_animate_fade(compositor_t* comp, uint32_t id, queued.affects_scale = false; queued.affects_alpha = true; elem->anim_queue.push_back(queued); - return; + + return true; } // Parallel execution with independent timing @@ -827,15 +833,18 @@ void compositor_animate_fade(compositor_t* comp, uint32_t id, if (elem->anim.type == ANIM_NONE) { elem->anim.type = ANIM_FADE; } + + return true; } -// Complete all animations on a specific element -void compositor_animation_complete(compositor_t* comp, uint32_t id) { - if (!comp) return; +// Finish all or up to num_queued queued animations for a specific element +bool compositor_animation_finish_element(compositor_t* comp, uint32_t id, int num_queued) { + if (!comp) { + return false; + } element_t* elem = find_element(comp, id); if (!elem) { - set_error(comp, "Element not found for animation_complete."); - return; + return false; } mark_element_dirty(comp, elem); @@ -858,8 +867,11 @@ void compositor_animation_complete(compositor_t* comp, uint32_t id) { } } - // Complete all queued animations - for (const auto& queued : elem->anim_queue) { + // Complete up to num_queued queued animations (or all if num_queued==0) + int count = 0; + auto it = elem->anim_queue.begin(); + while (it != elem->anim_queue.end() && (num_queued == 0 || count < num_queued)) { + const auto& queued = *it; if (queued.affects_position) { elem->x = queued.to_x; elem->y = queued.to_y; @@ -873,27 +885,36 @@ void compositor_animation_complete(compositor_t* comp, uint32_t id) { elem->scale = queued.to_scale; position_changed = true; } + ++count; + ++it; } + // Erase only the completed queued animations + elem->anim_queue.erase(elem->anim_queue.begin(), it); - // Clear animations + // Clear current animation state elem->anim.type = ANIM_NONE; elem->anim.animating_position = false; elem->anim.animating_scale = false; elem->anim.animating_alpha = false; - elem->anim_queue.clear(); + + // If num_queued==0, clear all queued animations + if (num_queued == 0) { + elem->anim_queue.clear(); + } // Update spatial hash if (position_changed) { clear_spatial_hash(comp, elem->id); update_spatial_hash(comp, elem); - } - else { + } else { mark_element_dirty(comp, elem); } + + return true; } -// Complete ALL animations on ALL elements -void compositor_animation_complete_all(compositor_t* comp) { +// Finish all animations (current and queued) for all elements +void compositor_animation_finish_all(compositor_t* comp) { if (!comp) return; // Iterate over all elements @@ -948,15 +969,12 @@ void compositor_animation_complete_all(compositor_t* comp) { if (position_changed) { clear_spatial_hash(comp, elem->id); update_spatial_hash(comp, elem); - } - else { + } else { mark_element_dirty(comp, elem); } } } -// --- Dirty Marking --- - // Forces all screen tiles to be redrawn on the next compose pass. void compositor_mark_all_dirty(compositor_t* comp) { if (!comp) return; @@ -964,20 +982,20 @@ void compositor_mark_all_dirty(compositor_t* comp) { } // Marks the tiles occupied by a specific element as dirty. -void compositor_mark_content_dirty(compositor_t* comp, uint32_t id) { - if (!comp) return; +bool compositor_mark_content_dirty(compositor_t* comp, uint32_t id) { + if (!comp) { + return false; + } element_t* elem = find_element(comp, id); if (!elem) { - set_error(comp, "Element with ID " + std::to_string(id) + " not found to mark dirty."); - return; + return false; } // The helper function handles bounds and visibility check. mark_element_dirty(comp, elem); - // Clear last error on success - comp->last_error.clear(); + return true; } // Returns true if any element has an active animation or queued animations. @@ -1002,7 +1020,6 @@ bool compositor_is_element_animating(compositor_t* comp, uint32_t id) { if (!comp) return false; element_t* elem = find_element(comp, id); if (!elem) { - set_error(comp, "Element with ID " + std::to_string(id) + " not found to check animation."); return false; } const animation_t& anim = elem->anim; @@ -1014,199 +1031,91 @@ bool compositor_is_element_animating(compositor_t* comp, uint32_t id) { return false; } -// --- Rendering --- - // Renders all dirty tiles and returns a pointer to the main framebuffer. const uint32_t* compositor_compose(compositor_t* comp) { if (!comp) return nullptr; - // Clear any previous error before starting composition - comp->last_error.clear(); + // OpenMP 2.0 requires signed integers for loop counters + int total_tiles = (int)(comp->tiles_y * comp->tiles_x); - // Process dirty tiles using the spatial hash - for (uint32_t ty = 0; ty < comp->tiles_y; ty++) { - for (uint32_t tx = 0; tx < comp->tiles_x; tx++) { - uint32_t tile_idx = ty * comp->tiles_x + tx; + // Uncomment when you're ready to test threading again! + //#pragma omp parallel for schedule(dynamic, 8) + for (int i = 0; i < total_tiles; i++) { + uint32_t ty = (uint32_t)(i / comp->tiles_x); + uint32_t tx = (uint32_t)(i % comp->tiles_x); + uint32_t tile_idx = (uint32_t)i; - // Skip if tile is not dirty - if (!comp->dirty_tiles[tile_idx]) continue; + // Skip if tile is not dirty + if (!comp->dirty_tiles[tile_idx]) continue; - // Tile boundaries - uint32_t tile_x0 = tx * TILE_SIZE; - uint32_t tile_y0 = ty * TILE_SIZE; - uint32_t tile_x1 = std::min(tile_x0 + TILE_SIZE, comp->width); - uint32_t tile_y1 = std::min(tile_y0 + TILE_SIZE, comp->height); + // Tile boundaries + uint32_t tile_x0 = tx * TILE_SIZE; + uint32_t tile_y0 = ty * TILE_SIZE; + uint32_t tile_x1 = std::min(tile_x0 + TILE_SIZE, comp->width); + uint32_t tile_y1 = std::min(tile_y0 + TILE_SIZE, comp->height); - // 1. Clear tile in the destination framebuffer to opaque black - for (uint32_t y = tile_y0; y < tile_y1; y++) { - for (uint32_t x = tile_x0; x < tile_x1; x++) { - comp->framebuffer[y * comp->width + x] = 0xFF000000; - } - } + // FAST CLEAR: Use std::fill per row for contiguous memory writes + for (uint32_t y = tile_y0; y < tile_y1; y++) { + uint32_t* row_ptr = &comp->framebuffer[y * comp->width + tile_x0]; + std::fill(row_ptr, row_ptr + (tile_x1 - tile_x0), 0xFF000000); + } - // 2. Use the draw_order to ensure z-order correctness. - for (uint32_t element_id : comp->draw_order) { - const element_t* elem = find_element(comp, element_id); - // Also skip if element is fully transparent - if (!elem || !elem->visible || elem->alpha == 0.0f) continue; + // SPATIAL HASH: Only process elements that actually touch this tile + std::vector local_draw_list = comp->tile_map[tile_idx]; - // Get element bounds - int32_t elem_x0, elem_y0, elem_x1, elem_y1; - get_element_bounds(elem, elem_x0, elem_y0, elem_x1, elem_y1); + // If nothing is in this tile, we're done here + if (local_draw_list.empty()) { + comp->dirty_tiles[tile_idx] = 0; + continue; + } - // Check if element intersects this tile (AABB check) - if (elem_x1 <= (int32_t)tile_x0 || elem_x0 >= (int32_t)tile_x1) continue; - if (elem_y1 <= (int32_t)tile_y0 || elem_y0 >= (int32_t)tile_y1) continue; + // Sort the local elements by z-order + std::sort(local_draw_list.begin(), local_draw_list.end(), [comp](uint32_t a, uint32_t b) { + return comp->elements_map[a].z_order < comp->elements_map[b].z_order; + }); - // Find intersection (clipping rectangle) - int32_t blend_x0 = std::max((int32_t)tile_x0, elem_x0); - int32_t blend_y0 = std::max((int32_t)tile_y0, elem_y0); - int32_t blend_x1 = std::min((int32_t)tile_x1, elem_x1); - int32_t blend_y1 = std::min((int32_t)tile_y1, elem_y1); + // Draw only the local elements + for (uint32_t element_id : local_draw_list) { + const element_t* elem = find_element(comp, element_id); + if (!elem || !elem->valid || !elem->visible || elem->alpha == 0.0f) continue; - // Pre-calculate inverse scale for mapping screen pixels to source pixels - float inv_scale = 1.0f / elem->scale; + int32_t elem_x0, elem_y0, elem_x1, elem_y1; + get_element_bounds(elem, elem_x0, elem_y0, elem_x1, elem_y1); - // Get element alpha as an 8-bit integer (0-255) - uint32_t elem_a_u8 = (uint32_t)(elem->alpha * 255.0f); + int32_t blend_x0 = std::max((int32_t)tile_x0, elem_x0); + int32_t blend_y0 = std::max((int32_t)tile_y0, elem_y0); + int32_t blend_x1 = std::min((int32_t)tile_x1, elem_x1); + int32_t blend_y1 = std::min((int32_t)tile_y1, elem_y1); - // Perform blending over the clipped area + // Setup math variables outside the loop + float inv_scale = 1.0f / elem->scale; + bool is_1to1_scale = (inv_scale == 1.0f); + uint32_t elem_a_u8 = (uint32_t)(elem->alpha * 255.0f); + + // 16.16 Fixed-point scale factor + int32_t inv_scale_fp = (int32_t)(inv_scale * 65536.0f); + + // BRANCH HOISTING: Separate loops for 1:1 vs scaled rendering + if (is_1to1_scale) { for (int32_t y = blend_y0; y < blend_y1; y++) { - // Map destination y to source y (Nearest Neighbor) - int32_t src_y = (int32_t)((y - elem_y0) * inv_scale); - - // Pre-calculate source row pointer + int32_t src_y = y - elem_y0; const uint32_t* src_row = elem->fb + (src_y * elem->fb_width); uint32_t* dst_row = &comp->framebuffer[y * comp->width]; - // --- Blending Loop (with SIMD) --- - int32_t x = blend_x0; + for (int32_t x = blend_x0; x < blend_x1; x++) { + int32_t src_x = x - elem_x0; -#if defined(__x86_64__) || defined(__i386__) || defined(_M_X64) || defined(_M_IX86) - // Pre-calculate fixed values for the SIMD loop - const __m128i v_zero = _mm_setzero_si128(); - const __m128i v_one = _mm_set1_epi16(1); - const __m128i v_255 = _mm_set1_epi16(255); - const __m128i v_elem_a_u8 = _mm_set1_epi16((short)elem_a_u8); - const __m128i v_opaque_mask = _mm_set1_epi32(0xFF000000); // For setting final alpha to 0xFF - - // Determine the loop boundary for 4-pixel chunks - int32_t sse_limit = blend_x1 - (blend_x1 - x) % 4; - - for (; x < sse_limit; x += 4) { - // 1. Map destination x to source x (Need 4 values: x, x+1, x+2, x+3) - // This assumes inv_scale is pre-calculated. We must do the 4 lookups. - int32_t src_x0 = (int32_t)((x - elem_x0) * inv_scale); - int32_t src_x1 = (int32_t)(((x + 1) - elem_x0) * inv_scale); - int32_t src_x2 = (int32_t)(((x + 2) - elem_x0) * inv_scale); - int32_t src_x3 = (int32_t)(((x + 3) - elem_x0) * inv_scale); - - // 2. Sample 4 source pixels (ARGB format) - uint32_t src_pixels[4]; - src_pixels[0] = src_row[src_x0]; - src_pixels[1] = src_row[src_x1]; - src_pixels[2] = src_row[src_x2]; - src_pixels[3] = src_row[src_x3]; - - __m128i v_src = _mm_loadu_si128((const __m128i*)src_pixels); - __m128i v_dst = _mm_loadu_si128((const __m128i*) & dst_row[x]); - - // --- Unpack 4 pixels from 8-bit (ARGB) to 16-bit (A, R, G, B components) --- - // v_src_16bit_lo: A0 R0 G0 B0 | A1 R1 G1 B1 (packed 16-bit values, high bits zero) - // v_src_16bit_hi: A2 R2 G2 B2 | A3 R3 G3 B3 - __m128i v_src_16bit_lo = _mm_unpacklo_epi8(v_src, v_zero); - __m128i v_src_16bit_hi = _mm_unpackhi_epi8(v_src, v_zero); - - __m128i v_dst_16bit_lo = _mm_unpacklo_epi8(v_dst, v_zero); - __m128i v_dst_16bit_hi = _mm_unpackhi_epi8(v_dst, v_zero); - - // 3. Modulate alpha (ModAlpha = (SrcAlpha * ElemAlpha) / 255) - // Get Src Alpha - __m128i v_src_a_lo = _mm_shufflelo_epi16(v_src_16bit_lo, _MM_SHUFFLE(3, 3, 3, 3)); // A0 A0 A0 A0 | A1 A1 A1 A1 - v_src_a_lo = _mm_shufflehi_epi16(v_src_a_lo, _MM_SHUFFLE(3, 3, 3, 3)); - __m128i v_src_a_hi = _mm_shufflelo_epi16(v_src_16bit_hi, _MM_SHUFFLE(3, 3, 3, 3)); // A2 A2 A2 A2 | A3 A3 A3 A3 - v_src_a_hi = _mm_shufflehi_epi16(v_src_a_hi, _MM_SHUFFLE(3, 3, 3, 3)); - - // ModAlpha = SrcAlpha * ElemAlpha - __m128i v_mod_a_lo = _mm_mullo_epi16(v_src_a_lo, v_elem_a_u8); - __m128i v_mod_a_hi = _mm_mullo_epi16(v_src_a_hi, v_elem_a_u8); - - // ModAlpha = (SrcAlpha * ElemAlpha + 255/2) / 255 (Simple division approximation) - // For / 255, we use a more accurate approximation or just accept the error. - // The scalar path uses (x / 255), we'll approximate with (x + 128) / 256 for a faster result. - // ModAlpha_approx = (v_mod_a + 128) >> 8 - __m128i v_mod_a_lo_div = _mm_add_epi16(v_mod_a_lo, _mm_set1_epi16(128)); - __m128i v_mod_a_hi_div = _mm_add_epi16(v_mod_a_hi, _mm_set1_epi16(128)); - __m128i v_mod_a_approx_lo = _mm_srli_epi16(v_mod_a_lo_div, 8); // Now contains ModAlpha_approx for 4 pixels (A0..A3) - __m128i v_mod_a_approx_hi = _mm_srli_epi16(v_mod_a_hi_div, 8); - - // Replicate ModAlpha_approx for blending (A0 R0 G0 B0, A1 R1 G1 B1) - __m128i v_mod_a_lo_blend = _mm_unpacklo_epi16(v_mod_a_approx_lo, v_mod_a_approx_lo); - v_mod_a_lo_blend = _mm_unpacklo_epi32(v_mod_a_lo_blend, v_mod_a_lo_blend); // ModAlpha_0 for R0 G0 B0 - __m128i v_mod_a_hi_blend = _mm_unpacklo_epi16(v_mod_a_approx_hi, v_mod_a_approx_hi); - v_mod_a_hi_blend = _mm_unpacklo_epi32(v_mod_a_hi_blend, v_mod_a_hi_blend); // ModAlpha_2 for R2 G2 B2 - - // Also need the alpha for the second pixel in each 64-bit lane - __m128i v_mod_a_lo_1 = _mm_unpackhi_epi16(v_mod_a_approx_lo, v_mod_a_approx_lo); - v_mod_a_lo_blend = _mm_unpackhi_epi64(v_mod_a_lo_blend, v_mod_a_lo_1); // A0 for R0 G0 B0 | A1 for R1 G1 B1 - __m128i v_mod_a_hi_1 = _mm_unpackhi_epi16(v_mod_a_approx_hi, v_mod_a_approx_hi); - v_mod_a_hi_blend = _mm_unpackhi_epi64(v_mod_a_hi_blend, v_mod_a_hi_1); // A2 for R2 G2 B2 | A3 for R3 G3 B3 - - // ModAlpha_blend now contains: [A0 B0 G0 R0 | A1 B1 G1 R1] and [A2 B2 G2 R2 | A3 B3 G3 R3] (reversed order for convenience) - // It's crucial: the alpha value is replicated to the R, G, B positions of its corresponding pixel. - - // Calculate InvAlpha = 255 - ModAlpha - __m128i v_inv_a_lo_blend = _mm_sub_epi16(v_255, v_mod_a_lo_blend); - __m128i v_inv_a_hi_blend = _mm_sub_epi16(v_255, v_mod_a_hi_blend); - - // 4. Blend: (Src * ModA + Dst * InvA) / 255 - // Src * ModA - __m128i v_src_x_mod_a_lo = _mm_mullo_epi16(_mm_andnot_si128(v_opaque_mask, v_src_16bit_lo), v_mod_a_lo_blend); - __m128i v_src_x_mod_a_hi = _mm_mullo_epi16(_mm_andnot_si128(v_opaque_mask, v_src_16bit_hi), v_mod_a_hi_blend); - - // Dst * InvA - __m128i v_dst_x_inv_a_lo = _mm_mullo_epi16(_mm_andnot_si128(v_opaque_mask, v_dst_16bit_lo), v_inv_a_lo_blend); - __m128i v_dst_x_inv_a_hi = _mm_mullo_epi16(_mm_andnot_si128(v_opaque_mask, v_dst_16bit_hi), v_inv_a_hi_blend); - - // Sum - __m128i v_sum_lo = _mm_add_epi16(v_src_x_mod_a_lo, v_dst_x_inv_a_lo); - __m128i v_sum_hi = _mm_add_epi16(v_src_x_mod_a_hi, v_dst_x_inv_a_hi); - - // Divide by 255: (v_sum + 128) / 256 (Fast approx) - __m128i v_out_16bit_lo = _mm_srli_epi16(_mm_add_epi16(v_sum_lo, _mm_set1_epi16(128)), 8); - __m128i v_out_16bit_hi = _mm_srli_epi16(_mm_add_epi16(v_sum_hi, _mm_set1_epi16(128)), 8); - - // Pack the results back to 8-bit, keeping the alpha from v_opaque_mask (0xFF) - __m128i v_out_8bit_packed = _mm_packus_epi16(v_out_16bit_lo, v_out_16bit_hi); - - // OR with the Opaque Alpha mask - __m128i v_final_out = _mm_or_si128(v_out_8bit_packed, v_opaque_mask); - - // Store the result - _mm_storeu_si128((__m128i*) & dst_row[x], v_final_out); - } -#endif - // --- Scalar Path (Processes remaining pixels 1 by 1) --- - // This loop handles the "tail" on SSE2, or all pixels on ARM. - for (; x < blend_x1; x++) { - // 1. Map destination x to source x - int32_t src_x = (int32_t)((x - elem_x0) * inv_scale); - - // 2. Sample source pixel uint32_t src = src_row[src_x]; - - // 3. Modulate alpha uint32_t src_a_raw = (src >> 24) & 0xFF; - uint32_t mod_a = (src_a_raw * elem_a_u8) / 255; - // 4. Blend - if (mod_a == 255) { // Opaque, just copy + // Bitshift division approximation + uint32_t mod_a = (src_a_raw * elem_a_u8) >> 8; + + if (mod_a >= 254) { // Effectively opaque dst_row[x] = (src & 0x00FFFFFF) | 0xFF000000; continue; } - if (mod_a == 0) { // Fully transparent, skip - continue; - } + if (mod_a == 0) continue; uint32_t dst = dst_row[x]; uint32_t inv_a = 255 - mod_a; @@ -1219,31 +1128,63 @@ const uint32_t* compositor_compose(compositor_t* comp) { uint32_t dst_g = (dst >> 8) & 0xFF; uint32_t dst_b = dst & 0xFF; - uint32_t out_r = (src_r * mod_a + dst_r * inv_a) / 255; - uint32_t out_g = (src_g * mod_a + dst_g * inv_a) / 255; - uint32_t out_b = (src_b * mod_a + dst_b * inv_a) / 255; + // Bitshift division for final blend + uint32_t out_r = (src_r * mod_a + dst_r * inv_a) >> 8; + uint32_t out_g = (src_g * mod_a + dst_g * inv_a) >> 8; + uint32_t out_b = (src_b * mod_a + dst_b * inv_a) >> 8; dst_row[x] = 0xFF000000 | (out_r << 16) | (out_g << 8) | out_b; } } } + else { + for (int32_t y = blend_y0; y < blend_y1; y++) { + // Fixed point math for Y + int32_t src_y = ((y - elem_y0) * inv_scale_fp) >> 16; + const uint32_t* src_row = elem->fb + (src_y * elem->fb_width); + uint32_t* dst_row = &comp->framebuffer[y * comp->width]; - // 3. Clean tile - comp->dirty_tiles[tile_idx] = 0; + for (int32_t x = blend_x0; x < blend_x1; x++) { + // Fixed point math for X + int32_t src_x = ((x - elem_x0) * inv_scale_fp) >> 16; + + uint32_t src = src_row[src_x]; + uint32_t src_a_raw = (src >> 24) & 0xFF; + + uint32_t mod_a = (src_a_raw * elem_a_u8) >> 8; + + if (mod_a >= 254) { + dst_row[x] = (src & 0x00FFFFFF) | 0xFF000000; + continue; + } + if (mod_a == 0) continue; + + uint32_t dst = dst_row[x]; + uint32_t inv_a = 255 - mod_a; + + uint32_t src_r = (src >> 16) & 0xFF; + uint32_t src_g = (src >> 8) & 0xFF; + uint32_t src_b = src & 0xFF; + + uint32_t dst_r = (dst >> 16) & 0xFF; + uint32_t dst_g = (dst >> 8) & 0xFF; + uint32_t dst_b = dst & 0xFF; + + uint32_t out_r = (src_r * mod_a + dst_r * inv_a) >> 8; + uint32_t out_g = (src_g * mod_a + dst_g * inv_a) >> 8; + uint32_t out_b = (src_b * mod_a + dst_b * inv_a) >> 8; + + dst_row[x] = 0xFF000000 | (out_r << 16) | (out_g << 8) | out_b; + } + } + } } + + // Clean tile flag safely (distinct threads write to distinct tiles) + comp->dirty_tiles[tile_idx] = 0; } return comp->framebuffer.data(); } -// --- Error Handling --- - -// Returns a C-string describing the last error, or nullptr if no error. -const char* compositor_get_last_error(compositor_t* comp) { - if (!comp || comp->last_error.empty()) { - return nullptr; - } - return comp->last_error.c_str(); -} - -} // extern "C" \ No newline at end of file +} // extern "C" diff --git a/src/image.cpp b/src/image.cpp index 2aa628c..7682214 100644 --- a/src/image.cpp +++ b/src/image.cpp @@ -9,6 +9,9 @@ #include #include +#define STR2(x) #x +#define STR(x) STR2(x) + extern "C" { uint32_t* image_load(const char* filename, int* width, int* height) { int n; @@ -18,7 +21,7 @@ uint32_t* image_load(const char* filename, int* width, int* height) { // Convert RGBA to ARGB uint32_t* out = (uint32_t*)malloc(*width * *height * sizeof(uint32_t)); if (!out) { - fprintf(stderr, "image.cpp 19: malloc failed!"); + stbi_image_free(data); return nullptr; } for (int i = 0; i < *width * *height; ++i) { @@ -40,7 +43,7 @@ uint32_t* image_load_resized(const char* filename, int desired_width, int desire // Allocate buffer for resized image unsigned char* resized = (unsigned char*)malloc(desired_width * desired_height * 4); if (!resized) { - fprintf(stderr, "image.cpp 43: malloc failed!"); + stbi_image_free(data); return nullptr; } @@ -54,7 +57,6 @@ uint32_t* image_load_resized(const char* filename, int desired_width, int desire // Convert RGBA to ARGB uint32_t* out = (uint32_t*)malloc(desired_width * desired_height * sizeof(uint32_t)); if (!out) { - fprintf(stderr, "image.cpp 55: malloc failed!"); free(resized); return nullptr; } @@ -72,4 +74,5 @@ uint32_t* image_load_resized(const char* filename, int desired_width, int desire void image_free(uint32_t* data) { free(data); } -} + +} // extern "C" diff --git a/src/main.cpp b/src/main.cpp index f736904..62405da 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -1,7 +1,6 @@ -// this file is an absolute clusterfuck to demo the internals. -// if you know any better please make a pr. please. -// i hate lines++; but i don't know any better, do i look like i know frontend? -// branching? use arbitrary lines value, do i look like i know any better? +// this file is a clusterfuck that works as a demo of the internals. +// frontend is hard. todo a real state machine. +// want branching? if you try really hard. #include #include #include @@ -12,13 +11,13 @@ #include #include #include +#include -// --- RAII wrappers for engine objects --- +// smartass approach struct WindowHandle { window_t* ptr; WindowHandle(window_t* w) : ptr(w) {} ~WindowHandle() { if (ptr) window_destroy(ptr); } - window_t* get() { return ptr; } operator window_t* () { return ptr; } }; @@ -26,7 +25,6 @@ struct CompositorHandle { compositor_t* ptr; CompositorHandle(compositor_t* c) : ptr(c) {} ~CompositorHandle() { if (ptr) compositor_destroy(ptr); } - compositor_t* get() { return ptr; } operator compositor_t* () { return ptr; } }; @@ -34,7 +32,6 @@ struct TextHandle { text_t* ptr; TextHandle(text_t* t) : ptr(t) {} ~TextHandle() { if (ptr) text_destroy(ptr); } - text_t* get() { return ptr; } operator text_t* () { return ptr; } }; @@ -42,17 +39,31 @@ struct ImageHandle { uint32_t* ptr; ImageHandle(uint32_t* p) : ptr(p) {} ~ImageHandle() { if (ptr) image_free(ptr); } - uint32_t* get() { return ptr; } operator uint32_t* () { return ptr; } }; +struct AudioHandle { + audio_t* ptr; + AudioHandle(audio_t* a) : ptr(a) {} + ~AudioHandle() { if (ptr) audio_destroy(ptr); } + operator audio_t* () { return ptr; } +}; + +struct AudioDataHandle { + uint8_t* ptr; + AudioDataHandle(uint8_t* p) : ptr(p) {} + ~AudioDataHandle() { if (ptr) audio_free_data(ptr); } + operator uint8_t* () { return ptr; } +}; + +// the clusterfuck begins int main() { const int WIDTH = 1280; const int HEIGHT = 720; const int CHAR_WIDTH = 400; const int CHAR_HEIGHT = 600; - // --- Character Positioning Constants --- + // Character Positioning Constants // The vertical baseline for character sprites, making positioning independent of sprite height. const int CHAR_BASELINE_Y = HEIGHT - 140; // The horizontal center for the character on the left. @@ -63,94 +74,95 @@ int main() { const int OFFSCREEN_LEFT_X = LEFT_CHAR_CENTER_X - 660; WindowHandle window(window_create(WIDTH, HEIGHT, u8"The Obwarzanek Narrative Engine")); - if (!window.get()) { + if (!window) { printf("Failed to create window\n"); return 1; } CompositorHandle compositor(compositor_init(WIDTH, HEIGHT)); - if (!compositor.get()) { + if (!compositor) { printf("Failed to initialize compositor\n"); return 1; } + AudioHandle audio(audio_init()); + if (!audio) { + printf("Failed to initialize audio\n"); + return 1; + } + + audio_set_global_volume(audio, 1.0f); + + int music_size, music_channels, music_rate, music_total_samples; + AudioDataHandle music_data(audio_load_madpcm("assets/Freikugeln.wav", &music_size, &music_channels, &music_rate, &music_total_samples)); + if (!music_data) { + printf("Failed to load audio\n"); + return 1; + } + + uint32_t music_id = audio_play(audio, music_data, music_size, music_channels, music_rate, true); + ImageHandle bg(image_load_resized("assets/background.png", WIDTH, HEIGHT)); - if (bg.get()) { - uint32_t bg_id = compositor_insert(compositor.get(), bg.get(), WIDTH, HEIGHT, 0, 0, 0, 1.0f, ANCHOR_TOP_LEFT); - compositor_set_alpha(compositor.get(), bg_id, 1.0f); + if (bg) { + uint32_t bg_id = compositor_insert(compositor, bg, WIDTH, HEIGHT, 0, 0, 0, 1.0f, ANCHOR_TOP_LEFT); + compositor_set_alpha(compositor, bg_id, 1.0f); } ImageHandle textbox(image_load_resized("assets/textbox.png", WIDTH, 180)); - if (textbox.get()) { + if (textbox) { // Position via the bottom-left corner for easier alignment with screen bottom - compositor_insert(compositor.get(), textbox.get(), WIDTH, 180, 0, HEIGHT, 3, 1.0f, ANCHOR_BOTTOM_LEFT); + compositor_insert(compositor, textbox, WIDTH, 180, 0, HEIGHT, 3, 1.0f, ANCHOR_BOTTOM_LEFT); } ImageHandle maksw_happy(image_load_resized("assets/maksw.png", CHAR_WIDTH, CHAR_HEIGHT)); // Insert off-screen left, anchored at bottom-center for natural scaling - uint32_t maksw_happy_id = compositor_insert(compositor.get(), maksw_happy.get(), - CHAR_WIDTH, CHAR_HEIGHT, OFFSCREEN_LEFT_X, CHAR_BASELINE_Y, 1, 1.0f, ANCHOR_BOTTOM_CENTER); + uint32_t maksw_happy_id = compositor_insert(compositor, maksw_happy, CHAR_WIDTH, CHAR_HEIGHT, OFFSCREEN_LEFT_X, CHAR_BASELINE_Y, 1, 1.0f, ANCHOR_BOTTOM_CENTER); ImageHandle maksw_neutral(image_load_resized("assets/maksw2.png", CHAR_WIDTH, CHAR_HEIGHT)); // Insert at the final left position, anchored at bottom-center - uint32_t maksw_neutral_id = compositor_insert(compositor.get(), maksw_neutral.get(), + uint32_t maksw_neutral_id = compositor_insert(compositor, maksw_neutral, CHAR_WIDTH, CHAR_HEIGHT, LEFT_CHAR_CENTER_X, CHAR_BASELINE_Y, 1, 1.0f, ANCHOR_BOTTOM_CENTER); - compositor_set_visible(compositor.get(), maksw_neutral_id, false); + compositor_set_visible(compositor, maksw_neutral_id, false); ImageHandle wikor(image_load_resized("assets/wikor.png", CHAR_WIDTH, CHAR_HEIGHT)); // Insert at the final right position, anchored at bottom-center - uint32_t wikor_id = compositor_insert(compositor.get(), wikor.get(), + uint32_t wikor_id = compositor_insert(compositor, wikor, CHAR_WIDTH, CHAR_HEIGHT, RIGHT_CHAR_CENTER_X, CHAR_BASELINE_Y, 2, 1.0f, ANCHOR_BOTTOM_CENTER); TextHandle name_text(text_init(300, 100)); - text_set_font(name_text.get(), "assets/Aller_Rg.ttf", 40.0f); + text_set_font(name_text, "assets/Aller_Rg.ttf", 40.0f); // Text is anchored at top-left, positioned relative to textbox - uint32_t name_id = compositor_insert(compositor.get(), text_getfb(name_text.get()), + uint32_t name_id = compositor_insert(compositor, text_getfb(name_text), 300, 100, 60, HEIGHT - 180, 4, 1.0f, ANCHOR_TOP_LEFT); TextHandle dialogue_text(text_init(WIDTH - 140, 160)); - text_set_font(dialogue_text.get(), "assets/Vera.ttf", 30.0f); + text_set_font(dialogue_text, "assets/Vera.ttf", 30.0f); // Text is anchored at top-left, positioned relative to textbox - uint32_t dialogue_id = compositor_insert(compositor.get(), text_getfb(dialogue_text.get()), + uint32_t dialogue_id = compositor_insert(compositor, text_getfb(dialogue_text), WIDTH - 140, 160, 60, HEIGHT - 130, 4, 1.0f, ANCHOR_TOP_LEFT); ImageHandle mask(image_load_resized("assets/mask.png", 1280, 1080)); // Use top-left anchor for full-screen wipes - uint32_t mask_id = compositor_insert(compositor.get(), mask.get(), 1280, 1080, 0, -1080, 5, 1.0f, ANCHOR_TOP_LEFT); - compositor_set_visible(compositor.get(), mask_id, false); + uint32_t mask_id = compositor_insert(compositor, mask, 1280, 1080, 0, -1080, 5, 1.0f, ANCHOR_TOP_LEFT); + compositor_set_visible(compositor, mask_id, false); ImageHandle mask2(image_load_resized("assets/mask2.png", 1280, 1080)); // Use top-left anchor for full-screen wipes - uint32_t mask2_id = compositor_insert(compositor.get(), mask2.get(), 1280, 1080, 0, 0, 5, 1.0f, ANCHOR_TOP_LEFT); - compositor_set_visible(compositor.get(), mask2_id, false); - - // yeah! memory savings! - //std::unique_ptr black(new uint32_t[WIDTH * HEIGHT]); - //for (int i = 0; i < WIDTH * HEIGHT; i++) black[i] = 0xFF000000; - //// Use top-left anchor for full-screen fade - //uint32_t black_id = compositor_insert(compositor.get(), black.get(), WIDTH, HEIGHT, 0, 0, 6, 1.0f, ANCHOR_TOP_LEFT); - //compositor_set_visible(compositor.get(), black_id, false); - //compositor_set_alpha(compositor.get(), black_id, 0.0f); - - // Animate character into position using the new centered coordinates - compositor_animate_move(compositor.get(), maksw_happy_id, LEFT_CHAR_CENTER_X, CHAR_BASELINE_Y, 1.0f, EASE_OUT_CUBIC); - compositor_animate_scale(compositor.get(), maksw_happy_id, 1.05f, 0.3f, EASE_IN_OUT_CUBIC, true); - text_push(name_text.get(), "maksw", 0.0f, true); - text_push(dialogue_text.get(), - "Hello and welcome to the Obwarzanek Narrative Engine demo!", - 30.0f, true); + uint32_t mask2_id = compositor_insert(compositor, mask2, 1280, 1080, 0, 0, 5, 1.0f, ANCHOR_TOP_LEFT); + compositor_set_visible(compositor, mask2_id, false); bool running = true; auto last_frame_time = std::chrono::high_resolution_clock::now(); auto last_report_time = last_frame_time; - int frame_count = 0; + uint64_t frame_count = 0; + float highest_frame_time = 0.0f; float frame_time_sum = 0.0f; - const std::chrono::duration REPORT_INTERVAL = std::chrono::seconds(5); + const std::chrono::duration REPORT_INTERVAL = std::chrono::seconds(1); while (running) { - if (window_pollevent(window.get())) running = false; + if (window_pollevent(window)) running = false; auto current_time = std::chrono::high_resolution_clock::now(); std::chrono::duration delta_duration = current_time - last_frame_time; @@ -159,6 +171,7 @@ int main() { frame_count++; frame_time_sum += delta_time_sec; + if (delta_time_sec > highest_frame_time) highest_frame_time = delta_time_sec; std::chrono::duration time_since_last_report = current_time - last_report_time; @@ -168,130 +181,141 @@ int main() { float fps = 1.0f / avg_delta_time; float actual_duration = time_since_last_report.count(); - printf("FPS: %.2f (over %d frames in %.2f seconds)\n", - fps, frame_count, actual_duration); + printf("FPS: %.2f (over %llu frames in %.2f seconds), highest frame time: %.2fms\n", + fps, frame_count, actual_duration, highest_frame_time * 1000.0f); } frame_count = 0; frame_time_sum = 0.0f; + highest_frame_time = 0.0f; last_report_time = current_time; } static bool mouse_was_down = false; - bool mouse_is_down = window_is_mouse_button_down(window.get(), MOUSE_BUTTON_LEFT); + bool mouse_is_down = window_is_mouse_button_down(window, MOUSE_BUTTON_LEFT); bool mouse_clicked = mouse_is_down && !mouse_was_down; mouse_was_down = mouse_is_down; static bool key_was_down = false; - bool key_is_down = window_is_key_down(window.get(), KEY_SPACE); + bool key_is_down = window_is_key_down(window, KEY_SPACE); bool key_pressed = key_is_down && !key_was_down; key_was_down = key_is_down; // don't judge me, it will work for now - static unsigned int lines = 2; + static unsigned int lines = 1; static bool dialog_finished = false; static bool transitioning = false; - if (mouse_clicked || key_pressed) { - if (text_is_animating(dialogue_text.get())) { - text_show_all_current(dialogue_text.get()); + if (mouse_clicked || key_pressed || lines == 1 /* hack */) { + if (text_is_animating(dialogue_text)) { + text_show_all_current(dialogue_text); } else { switch (lines) { + case 1: + compositor_animate_move(compositor, maksw_happy_id, LEFT_CHAR_CENTER_X, CHAR_BASELINE_Y, 1.0f, EASE_OUT_CUBIC, false); + compositor_animate_scale(compositor, maksw_happy_id, 1.05f, 0.3f, EASE_IN_OUT_CUBIC, true); + text_push(name_text, "maksw", 0.0f, true); + text_push(dialogue_text, "Hello and welcome to the Obwarzanek Narrative Engine demo!", 30.0f, true); + lines++; + break; case 2: - compositor_animation_complete_all(compositor.get()); - text_clear(dialogue_text.get()); - text_push(dialogue_text.get(), "Today I'll present to you-", 30.0f, true); + compositor_finish_animations_all(compositor); + text_clear(dialogue_text); + text_push(dialogue_text, "Today I'll present to you-", 30.0f, true); lines++; break; case 3: - compositor_animation_complete_all(compositor.get()); - compositor_animate_scale(compositor.get(), maksw_happy_id, 1.0f, 0.3f, EASE_IN_OUT_CUBIC); - compositor_animate_scale(compositor.get(), wikor_id, 1.05f, 0.3f, EASE_IN_OUT_CUBIC); - text_clear(dialogue_text.get()); - text_clear(name_text.get()); - text_push(name_text.get(), "wikor", 0.0f, true); - text_push(dialogue_text.get(), "Yeah, hello to you too.", 30.0f, true); + compositor_finish_animations_all(compositor); + compositor_animate_scale(compositor, maksw_happy_id, 1.0f, 0.3f, EASE_IN_OUT_CUBIC, false); + compositor_animate_move(compositor, maksw_happy_id, LEFT_CHAR_CENTER_X, CHAR_BASELINE_Y - 20, 0.1f, EASE_OUT_CUBIC, false); + compositor_animate_move(compositor, maksw_happy_id, LEFT_CHAR_CENTER_X, CHAR_BASELINE_Y, 0.1f, EASE_IN_CUBIC, true); + compositor_animate_scale(compositor, wikor_id, 1.05f, 0.3f, EASE_IN_OUT_CUBIC, false); + text_clear(dialogue_text); + text_clear(name_text); + text_push(name_text, "wikor", 0.0f, true); + text_push(dialogue_text, "Yeah, hello to you too.", 30.0f, true); lines++; break; case 4: - compositor_animation_complete_all(compositor.get()); - compositor_animate_scale(compositor.get(), wikor_id, 1.0f, 0.3f, EASE_IN_OUT_CUBIC); - compositor_animate_scale(compositor.get(), maksw_happy_id, 1.05f, 0.3f, EASE_IN_OUT_CUBIC); - text_clear(dialogue_text.get()); - text_clear(name_text.get()); - text_push(name_text.get(), "maksw", 0.0f, true); - text_push(dialogue_text.get(), "Oh Wiktor, what a nice suprise!", 30.0f, true); + compositor_finish_animations_all(compositor); + compositor_animate_scale(compositor, wikor_id, 1.0f, 0.3f, EASE_IN_OUT_CUBIC, false); + compositor_animate_scale(compositor, maksw_happy_id, 1.05f, 0.3f, EASE_IN_OUT_CUBIC, false); + text_clear(dialogue_text); + text_clear(name_text); + text_push(name_text, "maksw", 0.0f, true); + text_push(dialogue_text, "Oh Wiktor, what a nice suprise!", 30.0f, true); lines++; break; case 5: - compositor_animation_complete_all(compositor.get()); - text_clear(dialogue_text.get()); - text_push(dialogue_text.get(), "Do you need something?", 30.0f, true); + compositor_finish_animations_all(compositor); + text_clear(dialogue_text); + text_push(dialogue_text, "Do you need anything?", 30.0f, true); lines++; break; case 6: - compositor_animation_complete_all(compositor.get()); - compositor_animate_scale(compositor.get(), maksw_happy_id, 1.0f, 0.3f, EASE_IN_OUT_CUBIC); - compositor_animate_scale(compositor.get(), wikor_id, 1.05f, 0.3f, EASE_IN_OUT_CUBIC); - text_clear(dialogue_text.get()); - text_clear(name_text.get()); - text_push(name_text.get(), "wikor", 0.0f, true); - text_push(dialogue_text.get(), "No, i was just walking by.", 30.0f, true); + compositor_finish_animations_all(compositor); + compositor_animate_scale(compositor, maksw_happy_id, 1.0f, 0.3f, EASE_IN_OUT_CUBIC, false); + compositor_animate_scale(compositor, wikor_id, 1.05f, 0.3f, EASE_IN_OUT_CUBIC, false); + text_clear(dialogue_text); + text_clear(name_text); + text_push(name_text, "wikor", 0.0f, true); + text_push(dialogue_text, "No, i was just walking by.", 30.0f, true); lines++; break; case 7: - compositor_animation_complete_all(compositor.get()); - text_clear(dialogue_text.get()); - text_push(dialogue_text.get(), "Cool engine though.", 30.0f, true); + compositor_finish_animations_all(compositor); + text_clear(dialogue_text); + text_push(dialogue_text, "Cool engine though.", 30.0f, true); lines++; break; case 8: // fade transition - compositor_animation_complete_all(compositor.get()); - compositor_set_visible(compositor.get(), mask_id, true); - compositor_animate_move(compositor.get(), mask_id, 0, 0, 0.5f, EASE_OUT_CUBIC, false); + compositor_animation_complete_all(compositor); + compositor_set_visible(compositor, mask_id, true); + compositor_animate_move(compositor, mask_id, 0, 0, 0.5f, EASE_OUT_CUBIC, false); transitioning = true; lines++; break; case 9: - // some fucking how? + // transitioning blocks input, empty case not to trigger default and desync lines from actual line break; case 10: - compositor_animation_complete_all(compositor.get()); - compositor_animate_scale(compositor.get(), wikor_id, 1.0f, 0.3f, EASE_IN_OUT_CUBIC); - compositor_animate_scale(compositor.get(), maksw_neutral_id, 1.05f, 0.3f, EASE_IN_OUT_CUBIC); - text_clear(dialogue_text.get()); - text_clear(name_text.get()); - text_push(name_text.get(), "maksw", 0.0f, true); - text_push(dialogue_text.get(), "Can't complain, the winter break is just a week away. Plus the compositor is finally cooperating.", 30.0f, true); + compositor_animation_complete_all(compositor); + compositor_animate_scale(compositor, wikor_id, 1.0f, 0.3f, EASE_IN_OUT_CUBIC, false); + compositor_animate_scale(compositor, maksw_neutral_id, 1.05f, 0.3f, EASE_IN_OUT_CUBIC, false); + text_clear(dialogue_text); + text_clear(name_text); + text_push(name_text, "maksw", 0.0f, true); + text_push(dialogue_text, "Can't complain, the winter break is just a week away. Plus the compositor is finally cooperating.", 30.0f, true); lines++; break; case 11: - compositor_animation_complete_all(compositor.get()); - compositor_animate_scale(compositor.get(), maksw_neutral_id, 1.0f, 0.3f, EASE_IN_OUT_CUBIC); - compositor_animate_scale(compositor.get(), wikor_id, 1.05f, 0.3f, EASE_IN_OUT_CUBIC); - text_clear(dialogue_text.get()); - text_clear(name_text.get()); - text_push(name_text.get(), "wikor", 0.0f, true); - text_push(dialogue_text.get(), "Sure thing..", 30.0f, true); + compositor_animation_complete_all(compositor); + compositor_animate_scale(compositor, maksw_neutral_id, 1.0f, 0.3f, EASE_IN_OUT_CUBIC, false); + compositor_animate_scale(compositor, wikor_id, 1.05f, 0.3f, EASE_IN_OUT_CUBIC, false); + text_clear(dialogue_text); + text_clear(name_text); + text_push(name_text, "wikor", 0.0f, true); + text_push(dialogue_text, "Sure thing..", 30.0f, true); lines++; break; case 12: - compositor_animation_complete_all(compositor.get()); - text_clear(dialogue_text.get()); - text_push(dialogue_text.get(), "You're talking to me like i know anything about computers.", 30.0f, true); + compositor_animation_complete_all(compositor); + text_clear(dialogue_text); + text_push(dialogue_text, "You're talking to me like i know anything about computers.", 30.0f, true); lines++; break; case 13: // fade to black and end - compositor_animation_complete_all(compositor.get()); - compositor_animate_scale(compositor.get(), maksw_neutral_id, 1.0f, 0.3f, EASE_IN_OUT_CUBIC, false); - //compositor_set_visible(compositor.get(), black_id, true); - //compositor_animate_fade(compositor.get(), black_id, 1.0f, 0.7f, EASE_IN_CUBIC, false); - // yeah! memory savings! - compositor_set_visible(compositor.get(), mask_id, true); - compositor_set_alpha(compositor.get(), mask_id, 0.0f); - compositor_animate_fade(compositor.get(), mask_id, 1.0f, 0.8f, EASE_IN_OUT_CUBIC, false); + compositor_animation_complete_all(compositor); + compositor_animate_scale(compositor, maksw_neutral_id, 1.0f, 0.3f, EASE_IN_OUT_CUBIC, false); + // reuse mask + compositor_set_visible(compositor, mask_id, true); + compositor_set_alpha(compositor, mask_id, 0.0f); + compositor_animate_fade(compositor, mask_id, 1.0f, 0.8f, EASE_IN_OUT_CUBIC, false); + + audio_animate_volume(audio, music_id, 0.0f, 0.6f); + dialog_finished = true; lines++; break; @@ -302,43 +326,45 @@ int main() { } } - compositor_tick(compositor.get(), delta_time_sec); + compositor_tick(compositor, delta_time_sec); if (transitioning) { - if (!compositor_is_element_animating(compositor.get(), mask_id)) { - compositor_set_visible(compositor.get(), maksw_happy_id, false); - compositor_set_visible(compositor.get(), maksw_neutral_id, true); - compositor_set_scale(compositor.get(), maksw_neutral_id, 1.0f); - compositor_set_scale(compositor.get(), wikor_id, 1.05f); - text_clear(dialogue_text.get()); - text_clear(name_text.get()); - text_push(name_text.get(), "wikor", 0.0f, true); - text_push(dialogue_text.get(), "Anyways, how's your day going?", 30.0f, true); - - compositor_set_visible(compositor.get(), mask_id, false); - compositor_set_visible(compositor.get(), mask2_id, true); - compositor_animate_move(compositor.get(), mask2_id, 0, HEIGHT, 0.5f, EASE_OUT_CUBIC, false); + if (!compositor_is_element_animating(compositor, mask_id)) { + compositor_set_visible(compositor, maksw_happy_id, false); + compositor_set_visible(compositor, maksw_neutral_id, true); + compositor_set_scale(compositor, maksw_neutral_id, 1.0f); + compositor_set_scale(compositor, wikor_id, 1.05f); + text_clear(dialogue_text); + text_clear(name_text); + text_push(name_text, "wikor", 0.0f, true); + text_push(dialogue_text, "Anyways, how's your day going?", 30.0f, true); + compositor_set_visible(compositor, mask_id, false); + compositor_set_visible(compositor, mask2_id, true); + compositor_animate_move(compositor, mask2_id, 0, HEIGHT, 0.5f, EASE_OUT_CUBIC, false); transitioning = false; lines++; } } if (dialog_finished) { - compositor_mark_content_dirty(compositor.get(), mask_id); - if (!compositor_is_element_animating(compositor.get(), mask_id)) { + //compositor_mark_content_dirty(compositor, mask_id); + if (!compositor_is_element_animating(compositor, mask_id)) { running = false; } } - if (text_nextframe(name_text.get(), delta_time_sec)) - compositor_mark_content_dirty(compositor.get(), name_id); - if (text_nextframe(dialogue_text.get(), delta_time_sec)) - compositor_mark_content_dirty(compositor.get(), dialogue_id); + if (text_nextframe(name_text, delta_time_sec)) + compositor_mark_content_dirty(compositor, name_id); + if (text_nextframe(dialogue_text, delta_time_sec)) + compositor_mark_content_dirty(compositor, dialogue_id); - const uint32_t* fb = compositor_compose(compositor.get()); - window_blit(window.get(), fb); + const uint32_t* fb = compositor_compose(compositor); + window_blit(window, fb); + audio_tick(audio, delta_time_sec); } + audio_stop(audio, music_id); + return 0; } diff --git a/src/text.cpp b/src/text.cpp index dd9fbb2..94c90fa 100644 --- a/src/text.cpp +++ b/src/text.cpp @@ -11,18 +11,17 @@ #include #include -// SSE2 intrinsics for x86/x64 -#if defined(__x86_64__) || defined(_M_X64) || defined(__i386__) || defined(_M_IX86) -#define USE_SSE2 1 -#include -#else -#define USE_SSE2 0 -#endif - #define STB_TRUETYPE_IMPLEMENTATION -#include "stb_truetype.h" - -// --- UTF-8 Helpers --- +#ifdef _MSC_VER +#pragma warning(push, 0) +#endif +#if defined(__GNUC__) || defined(__clang__) +#pragma GCC system_header +#endif +#include +#ifdef _MSC_VER +#pragma warning(pop) +#endif // Decode the next UTF-8 codepoint from `s` starting at `i` (byte index). // Advances `i` to the next byte after the decoded codepoint. @@ -88,8 +87,6 @@ static size_t utf8_count_codepoints(const std::string& s) { return count; } -// --- Internal Data Structures --- - // Holds the raw file data and stbtt_fontinfo for a cached font. struct cached_font_t { std::vector file_data; @@ -183,11 +180,6 @@ struct text_t { int last_rendered_char = 0; // codepoint count }; - -// --- Private Helper Functions --- - -// --- Caching & Font Loading --- - // Loads a font from file or retrieves it from the shared cache. static cached_font_t* load_font(text_t* text, const std::string& font_path) { // Check cache (now in shared_cache_t) @@ -288,8 +280,6 @@ static const cached_glyph_t* get_glyph(text_t* text, uint32_t codepoint, float s return &map_it->second; } -// --- Text Layout --- - // Precomputes line breaks for a text block. static void precompute_line_breaks(text_t* text, const text_params_t* params) { if (params->lines_computed) return; @@ -420,8 +410,6 @@ static void precompute_line_breaks(text_t* text, const text_params_t* params) { params->lines_computed = true; } -// --- SDF Rendering --- - // Standard smoothstep function for SDF anti-aliasing. static float smoothstep(float edge0, float edge1, float x) { float t = std::clamp((x - edge0) / (edge1 - edge0), 0.0f, 1.0f); @@ -454,61 +442,6 @@ static uint32_t alpha_blend(uint32_t dest, uint32_t src) { return (out_a << 24) | (out_r << 16) | (out_g << 8) | out_b; } -#if USE_SSE2 -// SSE2-accelerated alpha blending for 4 pixels at once -static inline void alpha_blend_sse2(uint32_t* dest, __m128i src_pixels, __m128i src_rgb_mask) { - // Load 4 destination pixels - __m128i dest_pixels = _mm_loadu_si128((__m128i*)dest); - - // Extract alpha channels - __m128i src_a = _mm_srli_epi32(src_pixels, 24); - __m128i dest_a = _mm_srli_epi32(dest_pixels, 24); - - // Check for trivial cases per pixel would be complex, so we just blend - // For optimization, you could check if all alphas are 0 or 255 - - // Unpack to 16-bit for precision - __m128i zero = _mm_setzero_si128(); - - // Extract RGBA components (low 8 pixels as 16-bit) - __m128i src_lo = _mm_unpacklo_epi8(src_pixels, zero); - __m128i src_hi = _mm_unpackhi_epi8(src_pixels, zero); - __m128i dest_lo = _mm_unpacklo_epi8(dest_pixels, zero); - __m128i dest_hi = _mm_unpackhi_epi8(dest_pixels, zero); - - // Replicate alpha to all channels - __m128i src_a_lo = _mm_shufflelo_epi16(src_lo, _MM_SHUFFLE(3, 3, 3, 3)); - src_a_lo = _mm_shufflehi_epi16(src_a_lo, _MM_SHUFFLE(3, 3, 3, 3)); - __m128i src_a_hi = _mm_shufflelo_epi16(src_hi, _MM_SHUFFLE(3, 3, 3, 3)); - src_a_hi = _mm_shufflehi_epi16(src_a_hi, _MM_SHUFFLE(3, 3, 3, 3)); - - // Compute: src * src_a - __m128i src_mul_lo = _mm_mullo_epi16(src_lo, src_a_lo); - __m128i src_mul_hi = _mm_mullo_epi16(src_hi, src_a_hi); - - // Compute: 255 - src_a - __m128i inv_src_a_lo = _mm_sub_epi16(_mm_set1_epi16(255), src_a_lo); - __m128i inv_src_a_hi = _mm_sub_epi16(_mm_set1_epi16(255), src_a_hi); - - // Compute: dest * (255 - src_a) - __m128i dest_mul_lo = _mm_mullo_epi16(dest_lo, inv_src_a_lo); - __m128i dest_mul_hi = _mm_mullo_epi16(dest_hi, inv_src_a_hi); - - // Add and divide by 255 (approximate with >> 8) - __m128i result_lo = _mm_add_epi16(src_mul_lo, dest_mul_lo); - __m128i result_hi = _mm_add_epi16(src_mul_hi, dest_mul_hi); - - result_lo = _mm_srli_epi16(result_lo, 8); - result_hi = _mm_srli_epi16(result_hi, 8); - - // Pack back to 8-bit - __m128i result = _mm_packus_epi16(result_lo, result_hi); - - // Store result - _mm_storeu_si128((__m128i*)dest, result); -} -#endif - // Draws a glyph using its cached SDF bitmap. static void draw_glyph(text_t* text, int x, int y, @@ -544,57 +477,7 @@ static void draw_glyph(text_t* text, for (int gy = gy0; gy < gy1; ++gy) { int fb_y = y0 + gy; - uint32_t* dest_row = &text->framebuffer[fb_y * text->width + x0]; - -#if USE_SSE2 - // Process 4 pixels at a time with SSE2 - int gx = gx0; - __m128i color_rgb_vec = _mm_set1_epi32(color_rgb); - - for (; gx + 3 < gx1; gx += 4) { - // Process 4 pixels - uint8_t sdf_vals[4]; - float alphas[4]; - - for (int i = 0; i < 4; i++) { - sdf_vals[i] = glyph->data[gy * glyph->w + (gx + i)]; - float dist = (sdf_vals[i] - onedge_value) / pixel_dist_scale; - - if (outline) { - float edge_inner_0 = -aa_width; - float edge_inner_1 = +aa_width; - float mask_inner = smoothstep(edge_inner_0, edge_inner_1, dist); - - float edge_outer_0 = -outline_width - aa_width; - float edge_outer_1 = -outline_width + aa_width; - float mask_outer = smoothstep(edge_outer_0, edge_outer_1, dist); - - alphas[i] = std::clamp(mask_outer - mask_inner, 0.0f, 1.0f); - } else { - alphas[i] = smoothstep(-aa_width, aa_width, dist); - } - } - - // Build source pixels - uint32_t src_pixels[4]; - bool any_visible = false; - for (int i = 0; i < 4; i++) { - uint32_t alpha_byte = (uint32_t)(alphas[i] * 255.0f); - src_pixels[i] = (alpha_byte << 24) | color_rgb; - if (alpha_byte > 0) any_visible = true; - } - - if (any_visible) { - __m128i src_vec = _mm_loadu_si128((__m128i*)src_pixels); - alpha_blend_sse2(&dest_row[gx], src_vec, color_rgb_vec); - } - } - - // Handle remaining pixels - for (; gx < gx1; ++gx) { -#else for (int gx = gx0; gx < gx1; ++gx) { -#endif uint8_t sdf_raw = glyph->data[gy * glyph->w + gx]; float dist = (sdf_raw - onedge_value) / pixel_dist_scale; @@ -624,12 +507,8 @@ static void draw_glyph(text_t* text, } } -// --- Public C-Style API --- - extern "C" { -// --- Lifecycle --- - text_t* text_init(int width, int height) { if (width <= 0 || height <= 0) return nullptr; @@ -716,8 +595,6 @@ void text_destroy(text_t* text) { delete text; } -// --- Font Configuration --- - // Sets the default font path and size to be used by subsequent text_push calls. // Pass NULL for font_path to keep the current default font. // Pass size <= 0 to keep the current default size. @@ -733,8 +610,6 @@ void text_set_font(text_t* text, const char* font_path, float size) { } } -// --- Text Queue & State --- - // Adds a block of text to the rendering queue. void text_push(text_t* text, const char* str, @@ -761,23 +636,9 @@ void text_push(text_t* text, // Clears the framebuffer and resets the text queue and all state. void text_clear(text_t* text) { if (!text) return; -#if USE_SSE2 - // Clear framebuffer with SSE2 - size_t total_pixels = text->width * text->height; - __m128i zero = _mm_setzero_si128(); - size_t i = 0; - for (; i + 3 < total_pixels; i += 4) { - _mm_storeu_si128((__m128i*) & text->framebuffer[i], zero); - } - - // Clear remaining pixels - for (; i < total_pixels; i++) { - text->framebuffer[i] = 0; - } -#else memset(text->framebuffer, 0, text->width * text->height * sizeof(uint32_t)); -#endif + text->current_x = text->padding_x; text->current_y = text->padding_y; text->text_queue.clear(); @@ -802,8 +663,6 @@ void text_show_all_current(text_t* text) { } } -// --- Update & Animation --- - // Advances the text animation and renders new characters. // deltatime is the time elapsed since the last frame (in seconds). bool text_nextframe(text_t* text, float deltatime) { @@ -931,11 +790,9 @@ bool text_is_animating(text_t* text) { return text->char_index < (int)total_cp; } -// --- Accessors --- - // Returns a pointer to the internal framebuffer. const uint32_t* text_getfb(text_t* text) { return text ? text->framebuffer : nullptr; } -} // extern "C" \ No newline at end of file +} // extern "C" diff --git a/vn.vcxproj b/vn.vcxproj index e41ae19..880310a 100644 --- a/vn.vcxproj +++ b/vn.vcxproj @@ -101,8 +101,11 @@ stdcpp20 MultiThreadedDebug StreamingSIMDExtensions2 - /Zc:char8_t- %(AdditionalOptions) + /Zc:char8_t- /openmp:llvm %(AdditionalOptions) false + + + stdc17 Console @@ -124,7 +127,10 @@ stdcpp20 MultiThreadedDebug StreamingSIMDExtensions2 - /Zc:char8_t- %(AdditionalOptions) + + + stdc17 + /Zc:char8_t- /openmp:llvm %(AdditionalOptions) Console @@ -144,8 +150,13 @@ include true StreamingSIMDExtensions2 - /Zc:char8_t- %(AdditionalOptions) + /Zc:char8_t- /openmp:llvm %(AdditionalOptions) false + + + stdc17 + Fast + EnableFastChecks Console @@ -167,7 +178,13 @@ stdcpp20 MultiThreadedDebug StreamingSIMDExtensions2 - /Zc:char8_t- %(AdditionalOptions) + true + stdc17 + /Zc:char8_t- /Zc:__cplusplus %(AdditionalOptions) + true + AnySuitable + Speed + Full Console @@ -177,6 +194,7 @@ + @@ -184,6 +202,7 @@ + diff --git a/vn.vcxproj.filters b/vn.vcxproj.filters index d696641..a1b68e2 100644 --- a/vn.vcxproj.filters +++ b/vn.vcxproj.filters @@ -36,6 +36,9 @@ Source Files + + Source Files + @@ -62,6 +65,9 @@ Header Files\SDL2 + + Header Files +