intense desloppification, performance improvements, openmp ready, add audio, refactor everything, fix warnings and bad practices
This commit is contained in:
1 parent
0c24781c81
commit
a12c9a3e8f
15 files changed
+1615
-700
No files matched your search
Binary file not shown.
@@ -0,0 +1,79 @@
|
|||||||
|
// audio.h
|
||||||
|
#pragma once
|
||||||
|
#ifndef AUDIO_H
|
||||||
|
#define AUDIO_H
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
#include <cstdint>
|
||||||
|
extern "C" {
|
||||||
|
#else
|
||||||
|
#include <stdint.h>
|
||||||
|
#include <stdbool.h>
|
||||||
|
#endif
|
||||||
|
|
||||||
|
typedef struct audio_t audio_t;
|
||||||
|
|
||||||
|
// 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
|
||||||
+52
-42
@@ -1,17 +1,12 @@
|
|||||||
// compositor.h
|
// compositor.h v0.3
|
||||||
|
#pragma once
|
||||||
#ifndef COMPOSITOR_H
|
#ifndef COMPOSITOR_H
|
||||||
#define COMPOSITOR_H
|
#define COMPOSITOR_H
|
||||||
#include <stdint.h>
|
|
||||||
#include <stdbool.h>
|
|
||||||
|
|
||||||
// Animation types
|
// This subsystem is thread-unsafe.
|
||||||
enum animation_type_t {
|
// Caller must serialize all calls. Calls do not need to be made from the main thread.
|
||||||
ANIM_NONE = 0,
|
// Removing an element while ticking/rendering may crash.
|
||||||
ANIM_MOVE,
|
// Modifying an element while ticking/rendering may cause visual glitches.
|
||||||
ANIM_FADE,
|
|
||||||
ANIM_SCALE,
|
|
||||||
ANIM_MOVE_FADE // Combined animation
|
|
||||||
};
|
|
||||||
|
|
||||||
// Easing functions
|
// Easing functions
|
||||||
enum easing_t {
|
enum easing_t {
|
||||||
@@ -24,6 +19,7 @@ enum easing_t {
|
|||||||
EASE_IN_OUT_CUBIC
|
EASE_IN_OUT_CUBIC
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Anchor points
|
||||||
enum anchor_point_t {
|
enum anchor_point_t {
|
||||||
ANCHOR_TOP_LEFT,
|
ANCHOR_TOP_LEFT,
|
||||||
ANCHOR_TOP_CENTER,
|
ANCHOR_TOP_CENTER,
|
||||||
@@ -36,16 +32,29 @@ enum anchor_point_t {
|
|||||||
ANCHOR_BOTTOM_RIGHT,
|
ANCHOR_BOTTOM_RIGHT,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
#include <cstdint>
|
||||||
extern "C" {
|
extern "C" {
|
||||||
|
#else
|
||||||
|
#include <stdint.h>
|
||||||
|
#include <stdbool.h>
|
||||||
|
#endif
|
||||||
|
|
||||||
typedef struct compositor_t compositor_t;
|
typedef struct compositor_t compositor_t;
|
||||||
|
|
||||||
|
// If the return type is bool, false means failure.
|
||||||
|
|
||||||
// Initialize compositor with given dimensions
|
// Initialize compositor with given dimensions
|
||||||
|
// nullptr means failure
|
||||||
compositor_t* compositor_init(uint32_t width, uint32_t height);
|
compositor_t* compositor_init(uint32_t width, uint32_t height);
|
||||||
|
|
||||||
// Destroy compositor and free resources
|
// Destroy compositor and free resources. Everything dies with it.
|
||||||
void compositor_destroy(compositor_t* comp);
|
void compositor_destroy(compositor_t* comp);
|
||||||
|
|
||||||
// Insert element, returns unique ID
|
// Insert element, returns unique ID
|
||||||
|
// FB format is ARGB8888 and it always will be
|
||||||
|
// 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,
|
uint32_t compositor_insert(compositor_t* comp,
|
||||||
const uint32_t* fb,
|
const uint32_t* fb,
|
||||||
uint32_t fb_width,
|
uint32_t fb_width,
|
||||||
@@ -55,50 +64,49 @@ uint32_t compositor_insert(compositor_t* comp,
|
|||||||
anchor_point_t anchor);
|
anchor_point_t anchor);
|
||||||
|
|
||||||
// Remove element by ID
|
// 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
|
// AIO Update element properties, immediate effect
|
||||||
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 x, int32_t y,
|
||||||
int32_t z_order, float scale,
|
int32_t z_order, float scale,
|
||||||
bool visible);
|
bool visible, float alpha);
|
||||||
|
|
||||||
// Individual setters
|
// Individual setters, immediate effect
|
||||||
void compositor_set_position(compositor_t* comp, uint32_t id,
|
bool compositor_set_position(compositor_t* comp, uint32_t id,
|
||||||
int32_t x, int32_t y);
|
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);
|
int32_t z_order);
|
||||||
void compositor_set_visible(compositor_t* comp, uint32_t id,
|
bool compositor_set_scale(compositor_t* comp, uint32_t id,
|
||||||
bool visible);
|
|
||||||
void compositor_set_scale(compositor_t* comp, uint32_t id,
|
|
||||||
float scale);
|
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.
|
// Advances all animations by a given time delta.
|
||||||
void compositor_tick(compositor_t* comp, float delta_time_sec);
|
void compositor_tick(compositor_t* comp, float delta_time_sec);
|
||||||
|
|
||||||
// Instantly sets the element-wide alpha.
|
// Sets a target state to advance over time by tick()
|
||||||
void compositor_set_alpha(compositor_t* comp, uint32_t id, float alpha);
|
// Queue means the animation will start after the previous animation completed
|
||||||
|
// Not queueing will play the animation alongside any current animation
|
||||||
// Animation functions with queue parameter
|
// You cannot stack the same type of animation without queueing, the second call will cancel the first call.
|
||||||
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,
|
int32_t to_x, int32_t to_y,
|
||||||
float duration_sec, easing_t easing, bool queue = false);
|
float duration_sec, easing_t easing, bool queue);
|
||||||
|
bool compositor_animate_fade(compositor_t* comp, uint32_t id,
|
||||||
void compositor_animate_fade(compositor_t* comp, uint32_t id,
|
|
||||||
float to_alpha,
|
float to_alpha,
|
||||||
float duration_sec, easing_t easing, bool queue = false);
|
float duration_sec, easing_t easing, bool queue);
|
||||||
|
bool compositor_animate_scale(compositor_t* comp, uint32_t id,
|
||||||
void compositor_animate_scale(compositor_t* comp, uint32_t id,
|
|
||||||
float to_scale,
|
float to_scale,
|
||||||
float duration_sec, easing_t easing, bool queue = false);
|
float duration_sec, easing_t easing, bool queue);
|
||||||
|
|
||||||
// Immediately complete all animations
|
// Immediately finish all animations on a specific object (optionally only N queued, 0 = all), or every object.
|
||||||
void compositor_animation_complete(compositor_t* comp, uint32_t id);
|
bool compositor_animation_finish_element(compositor_t* comp, uint32_t id, int num_queued /*0=all*/);
|
||||||
|
void compositor_animation_finish_all(compositor_t* comp);
|
||||||
void compositor_animation_complete_all(compositor_t* comp);
|
|
||||||
|
|
||||||
// Returns true if any element is animating or has queued animations.
|
// Returns true if any element is animating or has queued animations.
|
||||||
bool compositor_is_animating(compositor_t * comp);
|
bool compositor_is_animating(compositor_t* comp);
|
||||||
|
|
||||||
// Returns true if the specified element is animating or has queued animations.
|
// Returns true if the specified element is animating or has queued animations.
|
||||||
bool compositor_is_element_animating(compositor_t* comp, uint32_t id);
|
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
|
// Compose all visible elements and return framebuffer pointer
|
||||||
const uint32_t* compositor_compose(compositor_t* comp);
|
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);
|
void compositor_mark_all_dirty(compositor_t* comp);
|
||||||
|
|
||||||
// Returns the last error message set for this compositor instance.
|
#ifdef __cplusplus
|
||||||
const char* compositor_get_last_error(compositor_t* comp);
|
|
||||||
}
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
#endif // COMPOSITOR_H
|
#endif // COMPOSITOR_H
|
||||||
+9
-1
@@ -2,15 +2,23 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
#ifndef IMAGE_H
|
#ifndef IMAGE_H
|
||||||
#define IMAGE_H
|
#define IMAGE_H
|
||||||
#include <stdint.h>
|
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
#include <cstdint>
|
||||||
extern "C" {
|
extern "C" {
|
||||||
|
#else
|
||||||
|
#include <stdint.h>
|
||||||
|
#endif
|
||||||
|
|
||||||
// Loads an image file as ARGB8888. Returns pointer to pixel data, sets width/height.
|
// Loads an image file as ARGB8888. Returns pointer to pixel data, sets width/height.
|
||||||
uint32_t* image_load(const char* filename, int* width, int* height);
|
uint32_t* image_load(const char* filename, int* width, int* height);
|
||||||
// Loads and resizes an image file to desired dimensions as ARGB8888. Returns pointer to pixel data.
|
// 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);
|
uint32_t* image_load_resized(const char* filename, int desired_width, int desired_height);
|
||||||
// Frees image data.
|
// Frees image data.
|
||||||
void image_free(uint32_t* data);
|
void image_free(uint32_t* data);
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
}
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
#endif // IMAGE_H
|
#endif // IMAGE_H
|
||||||
@@ -0,0 +1,592 @@
|
|||||||
|
// madpcm.h
|
||||||
|
// A more modern approach to ADPCM.
|
||||||
|
#pragma once
|
||||||
|
#ifndef MADPCM_H
|
||||||
|
#define MADPCM_H
|
||||||
|
|
||||||
|
#include <stdbool.h>
|
||||||
|
#include <stddef.h>
|
||||||
|
#include <stdint.h>
|
||||||
|
|
||||||
|
#ifdef __AVX2__
|
||||||
|
#include <immintrin.h>
|
||||||
|
#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 <string.h>
|
||||||
|
#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 <intrin.h>
|
||||||
|
#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
|
||||||
@@ -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_STREAMOUT_PTR( star ) star __restrict__
|
||||||
#define STBIR_NO_UNROLL( ptr ) __asm__ (""::"r"(ptr))
|
#define STBIR_NO_UNROLL( ptr ) __asm__ (""::"r"(ptr))
|
||||||
#if __GNUC__ >= 14
|
#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
|
#else
|
||||||
#define STBIR_NO_UNROLL_LOOP_START
|
#define STBIR_NO_UNROLL_LOOP_START
|
||||||
#endif
|
#endif
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
// text.h
|
// text.h
|
||||||
|
#pragma once
|
||||||
#ifndef TEXT_RENDERER_H
|
#ifndef TEXT_RENDERER_H
|
||||||
#define TEXT_RENDERER_H
|
#define TEXT_RENDERER_H
|
||||||
|
|
||||||
@@ -6,7 +7,13 @@
|
|||||||
|
|
||||||
struct text_t;
|
struct text_t;
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
#include <cstdint>
|
||||||
extern "C" {
|
extern "C" {
|
||||||
|
#else
|
||||||
|
#include <stdint.h>
|
||||||
|
#endif
|
||||||
|
|
||||||
// initializes a new text renderer with given width and height
|
// initializes a new text renderer with given width and height
|
||||||
text_t* text_init(int width, int height);
|
text_t* text_init(int width, int height);
|
||||||
// shares cache, prefer when using the same font to save memory
|
// shares cache, prefer when using the same font to save memory
|
||||||
@@ -30,6 +37,9 @@ bool text_nextframe(text_t* text, float deltatime);
|
|||||||
const uint32_t* text_getfb(text_t* text);
|
const uint32_t* text_getfb(text_t* text);
|
||||||
// clears the text buffer and the frame buffer
|
// clears the text buffer and the frame buffer
|
||||||
void text_clear(text_t* text);
|
void text_clear(text_t* text);
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
}
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
#endif // TEXT_RENDERER_H
|
#endif // TEXT_RENDERER_H
|
||||||
+45
-24
@@ -2,39 +2,57 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
#ifndef WINDOW_H
|
#ifndef WINDOW_H
|
||||||
#define WINDOW_H
|
#define WINDOW_H
|
||||||
#include <cstdint>
|
|
||||||
|
|
||||||
typedef struct window_t window_t;
|
typedef struct window_t window_t;
|
||||||
|
|
||||||
#define KEY_RETURN 40
|
enum KeyCode {
|
||||||
#define KEY_SPACE 44
|
// Letters
|
||||||
#define KEY_ESCAPE 41
|
KEY_A = 4,
|
||||||
#define KEY_LSHIFT 42
|
KEY_D = 7,
|
||||||
#define KEY_RSHIFT 43
|
KEY_S = 22,
|
||||||
#define KEY_LCTRL 29
|
KEY_W = 26,
|
||||||
#define KEY_RCTRL 157
|
|
||||||
|
// Numbers
|
||||||
|
KEY_1 = 30,
|
||||||
|
KEY_0 = 39,
|
||||||
|
|
||||||
#define MOUSE_BUTTON_LEFT 1
|
// Controls
|
||||||
#define MOUSE_BUTTON_MIDDLE 2
|
KEY_RETURN = 40,
|
||||||
#define MOUSE_BUTTON_RIGHT 3
|
KEY_ESCAPE = 41,
|
||||||
|
KEY_BACKSPACE = 42,
|
||||||
|
KEY_TAB = 43,
|
||||||
|
KEY_SPACE = 44,
|
||||||
|
|
||||||
#define KEY_UP 82
|
// Modifiers
|
||||||
#define KEY_DOWN 81
|
KEY_LCTRL = 224,
|
||||||
#define KEY_LEFT 80
|
KEY_LSHIFT = 225,
|
||||||
#define KEY_RIGHT 79
|
KEY_RCTRL = 228,
|
||||||
#define KEY_BACKSPACE 42
|
KEY_RSHIFT = 229,
|
||||||
|
|
||||||
#define KEY_F1 58
|
// Navigation
|
||||||
#define KEY_F2 59
|
KEY_RIGHT = 79,
|
||||||
|
KEY_LEFT = 80,
|
||||||
|
KEY_DOWN = 81,
|
||||||
|
KEY_UP = 82,
|
||||||
|
|
||||||
#define KEY_A 4
|
// Function Keys
|
||||||
#define KEY_D 7
|
KEY_F1 = 58,
|
||||||
#define KEY_W 26
|
KEY_F2 = 59
|
||||||
#define KEY_S 22
|
};
|
||||||
#define KEY_1 30
|
|
||||||
#define KEY_0 39
|
|
||||||
|
|
||||||
|
enum MouseButton {
|
||||||
|
MOUSE_BUTTON_LEFT = 1,
|
||||||
|
MOUSE_BUTTON_MIDDLE = 2,
|
||||||
|
MOUSE_BUTTON_RIGHT = 3
|
||||||
|
};
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
#include <cstdint>
|
||||||
extern "C" {
|
extern "C" {
|
||||||
|
#else
|
||||||
|
#include <stdint.h>
|
||||||
|
#endif
|
||||||
|
|
||||||
// creates a window with given width, height and title, title can be utf8
|
// creates a window with given width, height and title, title can be utf8
|
||||||
window_t* window_create(int width, int height, const char* title);
|
window_t* window_create(int width, int height, const char* title);
|
||||||
// destroys the window
|
// destroys the window
|
||||||
@@ -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);
|
bool window_is_mouse_button_down(window_t* s, int button_index);
|
||||||
// gets the current mouse position in window coordinates
|
// gets the current mouse position in window coordinates
|
||||||
void window_get_mouse_position(window_t* s, int* x, int* y);
|
void window_get_mouse_position(window_t* s, int* x, int* y);
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
}
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
#endif // WINDOW_H
|
#endif // WINDOW_H
|
||||||
+343
@@ -0,0 +1,343 @@
|
|||||||
|
// audio.cpp
|
||||||
|
#include "audio.h"
|
||||||
|
#include <SDL2/SDL.h>
|
||||||
|
#define MADPCM_IMPLEMENTATION
|
||||||
|
#include "madpcm.h"
|
||||||
|
#include <unordered_map>
|
||||||
|
#include <vector>
|
||||||
|
#include <cstring>
|
||||||
|
#include <algorithm>
|
||||||
|
|
||||||
|
#define AHEAD_BLOCKS 10
|
||||||
|
|
||||||
|
struct AudioDevice {
|
||||||
|
SDL_AudioDeviceID id = 0;
|
||||||
|
AudioDevice() = default;
|
||||||
|
explicit AudioDevice(SDL_AudioDeviceID id) : id(id) {}
|
||||||
|
~AudioDevice() { if (id) SDL_CloseAudioDevice(id); }
|
||||||
|
AudioDevice(const AudioDevice&) = delete;
|
||||||
|
AudioDevice& operator=(const AudioDevice&) = delete;
|
||||||
|
AudioDevice(AudioDevice&& o) noexcept : id(o.id) { o.id = 0; }
|
||||||
|
AudioDevice& operator=(AudioDevice&& o) noexcept {
|
||||||
|
if (this != &o) { if (id) SDL_CloseAudioDevice(id); id = o.id; o.id = 0; }
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
operator bool() const { return id != 0; }
|
||||||
|
};
|
||||||
|
|
||||||
|
struct Stream {
|
||||||
|
uint32_t id = 0;
|
||||||
|
AudioDevice device;
|
||||||
|
const uint8_t* data = nullptr;
|
||||||
|
int total_blocks = 0;
|
||||||
|
int current_block = 0;
|
||||||
|
int channels = 0;
|
||||||
|
bool loop = false;
|
||||||
|
bool paused = false;
|
||||||
|
|
||||||
|
float volume = 1.0f;
|
||||||
|
float volume_from = 1.0f;
|
||||||
|
float volume_to = 1.0f;
|
||||||
|
float volume_anim_elapsed = 0.0f;
|
||||||
|
float volume_anim_duration = 0.0f;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct RiffHeader {
|
||||||
|
char riff[4];
|
||||||
|
uint32_t fileSize;
|
||||||
|
char wave[4];
|
||||||
|
};
|
||||||
|
|
||||||
|
struct audio_t {
|
||||||
|
std::unordered_map<uint32_t, Stream> streams;
|
||||||
|
uint32_t next_id = 1;
|
||||||
|
|
||||||
|
float global_volume = 1.0f;
|
||||||
|
float global_volume_from = 1.0f;
|
||||||
|
float global_volume_to = 1.0f;
|
||||||
|
float global_volume_anim_elapsed = 0.0f;
|
||||||
|
float global_volume_anim_duration = 0.0f;
|
||||||
|
};
|
||||||
|
|
||||||
|
static float clampf(float v, float lo, float hi) {
|
||||||
|
return v < lo ? lo : v > hi ? hi : v;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Decode one block, scale by combined volume, queue to device.
|
||||||
|
// Returns false if the stream has ended (non-looping, EOF).
|
||||||
|
static bool pump_block(audio_t* audio, Stream& s) {
|
||||||
|
int block_size = (s.channels == 2) ? MADPCM_STEREO_SIZE : MADPCM_MONO_SIZE;
|
||||||
|
|
||||||
|
if (s.current_block >= s.total_blocks) {
|
||||||
|
if (s.loop) s.current_block = 0;
|
||||||
|
else return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
const uint8_t* block = s.data + s.current_block * block_size;
|
||||||
|
int16_t pcm[MADPCM_BLOCK_SAMPLES * 2];
|
||||||
|
int sample_count;
|
||||||
|
|
||||||
|
if (s.channels == 2) {
|
||||||
|
int16_t L[MADPCM_BLOCK_SAMPLES], R[MADPCM_BLOCK_SAMPLES];
|
||||||
|
madpcm_decode_block_stereo(block, L, R);
|
||||||
|
for (int i = 0; i < MADPCM_BLOCK_SAMPLES; i++) {
|
||||||
|
pcm[i * 2] = L[i];
|
||||||
|
pcm[i * 2 + 1] = R[i];
|
||||||
|
}
|
||||||
|
sample_count = MADPCM_BLOCK_SAMPLES * 2;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
madpcm_decode_block(block, pcm);
|
||||||
|
sample_count = MADPCM_BLOCK_SAMPLES;
|
||||||
|
}
|
||||||
|
|
||||||
|
float vol = clampf(s.volume * audio->global_volume, 0.0f, 1.0f);
|
||||||
|
for (int i = 0; i < sample_count; i++)
|
||||||
|
pcm[i] = static_cast<int16_t>(pcm[i] * vol);
|
||||||
|
|
||||||
|
SDL_QueueAudio(s.device.id, pcm, static_cast<uint32_t>(sample_count * sizeof(int16_t)));
|
||||||
|
s.current_block++;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
extern "C" {
|
||||||
|
|
||||||
|
audio_t* audio_init() {
|
||||||
|
if (SDL_InitSubSystem(SDL_INIT_AUDIO) != 0) return nullptr;
|
||||||
|
return new audio_t{};
|
||||||
|
}
|
||||||
|
|
||||||
|
void audio_destroy(audio_t* audio) {
|
||||||
|
if (!audio) return;
|
||||||
|
// streams destructor closes all SDL devices via RAII
|
||||||
|
delete audio;
|
||||||
|
SDL_QuitSubSystem(SDL_INIT_AUDIO);
|
||||||
|
}
|
||||||
|
|
||||||
|
uint8_t* audio_load_madpcm(const char* filename, int* out_size, int* out_channels, int* out_rate, int* out_total_samples) {
|
||||||
|
FILE* f = fopen(filename, "rb");
|
||||||
|
if (!f) return nullptr;
|
||||||
|
|
||||||
|
RiffHeader h;
|
||||||
|
if (fread(&h, sizeof(h), 1, f) != 1 ||
|
||||||
|
memcmp(h.riff, "RIFF", 4) != 0 ||
|
||||||
|
memcmp(h.wave, "WAVE", 4) != 0) {
|
||||||
|
fclose(f);
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
char chunkId[4];
|
||||||
|
uint32_t chunkSize;
|
||||||
|
int fmtFound = 0;
|
||||||
|
uint16_t audioFmt = 0;
|
||||||
|
uint16_t numChannels = 0;
|
||||||
|
uint32_t dataLen = 0;
|
||||||
|
int rate = 0;
|
||||||
|
uint8_t* data = nullptr;
|
||||||
|
|
||||||
|
while (fread(chunkId, 1, 4, f) == 4) {
|
||||||
|
if (fread(&chunkSize, 4, 1, f) != 1) break;
|
||||||
|
|
||||||
|
if (memcmp(chunkId, "fmt ", 4) == 0) {
|
||||||
|
if (fread(&audioFmt, 2, 1, f) != 1) break;
|
||||||
|
if (fread(&numChannels, 2, 1, f) != 1) break;
|
||||||
|
if (fread(&rate, 4, 1, f) != 1) break;
|
||||||
|
fseek(f, chunkSize - 8, SEEK_CUR);
|
||||||
|
fmtFound = 1;
|
||||||
|
}
|
||||||
|
else if (memcmp(chunkId, "data", 4) == 0) {
|
||||||
|
dataLen = chunkSize;
|
||||||
|
data = static_cast<uint8_t*>(malloc(dataLen));
|
||||||
|
if (fread(data, 1, dataLen, f) != dataLen) {
|
||||||
|
free(data);
|
||||||
|
fclose(f);
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
fseek(f, chunkSize, SEEK_CUR);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fclose(f);
|
||||||
|
|
||||||
|
if (!fmtFound || audioFmt != MADPCM_WAVE_FORMAT || !data) {
|
||||||
|
if (data) free(data);
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (out_channels) *out_channels = static_cast<int>(numChannels);
|
||||||
|
if (out_size) *out_size = static_cast<int>(dataLen);
|
||||||
|
if (out_rate) *out_rate = rate;
|
||||||
|
|
||||||
|
if (out_total_samples) {
|
||||||
|
int blockSize = (numChannels == 2) ? MADPCM_STEREO_SIZE : MADPCM_MONO_SIZE;
|
||||||
|
int numBlocks = dataLen / blockSize;
|
||||||
|
*out_total_samples = numBlocks * MADPCM_BLOCK_SAMPLES;
|
||||||
|
}
|
||||||
|
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
|
||||||
|
void audio_free_data(uint8_t* data) {
|
||||||
|
if (data) {
|
||||||
|
free(data);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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<uint8_t>(channels);
|
||||||
|
spec.samples = MADPCM_BLOCK_SAMPLES;
|
||||||
|
|
||||||
|
SDL_AudioDeviceID dev = SDL_OpenAudioDevice(nullptr, 0, &spec, nullptr, 0);
|
||||||
|
if (!dev) return 0;
|
||||||
|
|
||||||
|
int block_size = (channels == 2) ? MADPCM_STEREO_SIZE : MADPCM_MONO_SIZE;
|
||||||
|
int total_blocks = size / block_size;
|
||||||
|
uint32_t id = audio->next_id++;
|
||||||
|
|
||||||
|
Stream& s = audio->streams[id];
|
||||||
|
s.id = id;
|
||||||
|
s.device = AudioDevice(dev);
|
||||||
|
s.data = data;
|
||||||
|
s.total_blocks = total_blocks;
|
||||||
|
s.channels = channels;
|
||||||
|
s.loop = loop;
|
||||||
|
|
||||||
|
for (int i = 0; i < AHEAD_BLOCKS; i++)
|
||||||
|
if (!pump_block(audio, s)) break;
|
||||||
|
|
||||||
|
SDL_PauseAudioDevice(dev, 0);
|
||||||
|
return id;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool audio_stop(audio_t* audio, 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<uint32_t> to_remove;
|
||||||
|
|
||||||
|
for (auto& [id, s] : audio->streams) {
|
||||||
|
// Advance per-stream volume animation
|
||||||
|
if (s.volume_anim_duration > 0.0f) {
|
||||||
|
s.volume_anim_elapsed += delta_time_sec;
|
||||||
|
float t = s.volume_anim_elapsed / s.volume_anim_duration;
|
||||||
|
if (t >= 1.0f) { t = 1.0f; s.volume_anim_duration = 0.0f; }
|
||||||
|
s.volume = s.volume_from + (s.volume_to - s.volume_from) * t;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (s.paused) continue;
|
||||||
|
|
||||||
|
size_t target_bytes = AHEAD_BLOCKS * MADPCM_BLOCK_SAMPLES
|
||||||
|
* s.channels * sizeof(int16_t);
|
||||||
|
|
||||||
|
while (SDL_GetQueuedAudioSize(s.device.id) < target_bytes) {
|
||||||
|
if (!pump_block(audio, s)) {
|
||||||
|
// Non-looping EOF — wait for queue to fully drain then remove
|
||||||
|
if (SDL_GetQueuedAudioSize(s.device.id) == 0)
|
||||||
|
to_remove.push_back(id);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (uint32_t id : to_remove)
|
||||||
|
audio->streams.erase(id);
|
||||||
|
}
|
||||||
|
|
||||||
|
} // extern "C"
|
||||||
+272
-331
File diff suppressed because it is too large.
Load diff
+7
-4
@@ -9,6 +9,9 @@
|
|||||||
#include <cstdint>
|
#include <cstdint>
|
||||||
#include <image.h>
|
#include <image.h>
|
||||||
|
|
||||||
|
#define STR2(x) #x
|
||||||
|
#define STR(x) STR2(x)
|
||||||
|
|
||||||
extern "C" {
|
extern "C" {
|
||||||
uint32_t* image_load(const char* filename, int* width, int* height) {
|
uint32_t* image_load(const char* filename, int* width, int* height) {
|
||||||
int n;
|
int n;
|
||||||
@@ -18,7 +21,7 @@ uint32_t* image_load(const char* filename, int* width, int* height) {
|
|||||||
// Convert RGBA to ARGB
|
// Convert RGBA to ARGB
|
||||||
uint32_t* out = (uint32_t*)malloc(*width * *height * sizeof(uint32_t));
|
uint32_t* out = (uint32_t*)malloc(*width * *height * sizeof(uint32_t));
|
||||||
if (!out) {
|
if (!out) {
|
||||||
fprintf(stderr, "image.cpp 19: malloc failed!");
|
stbi_image_free(data);
|
||||||
return nullptr;
|
return nullptr;
|
||||||
}
|
}
|
||||||
for (int i = 0; i < *width * *height; ++i) {
|
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
|
// Allocate buffer for resized image
|
||||||
unsigned char* resized = (unsigned char*)malloc(desired_width * desired_height * 4);
|
unsigned char* resized = (unsigned char*)malloc(desired_width * desired_height * 4);
|
||||||
if (!resized) {
|
if (!resized) {
|
||||||
fprintf(stderr, "image.cpp 43: malloc failed!");
|
stbi_image_free(data);
|
||||||
return nullptr;
|
return nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -54,7 +57,6 @@ uint32_t* image_load_resized(const char* filename, int desired_width, int desire
|
|||||||
// Convert RGBA to ARGB
|
// Convert RGBA to ARGB
|
||||||
uint32_t* out = (uint32_t*)malloc(desired_width * desired_height * sizeof(uint32_t));
|
uint32_t* out = (uint32_t*)malloc(desired_width * desired_height * sizeof(uint32_t));
|
||||||
if (!out) {
|
if (!out) {
|
||||||
fprintf(stderr, "image.cpp 55: malloc failed!");
|
|
||||||
free(resized);
|
free(resized);
|
||||||
return nullptr;
|
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) {
|
void image_free(uint32_t* data) {
|
||||||
free(data);
|
free(data);
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
} // extern "C"
|
||||||
+164
-138
@@ -1,7 +1,6 @@
|
|||||||
// this file is an absolute clusterfuck to demo the internals.
|
// this file is a clusterfuck that works as a demo of the internals.
|
||||||
// if you know any better please make a pr. please.
|
// frontend is hard. todo a real state machine.
|
||||||
// i hate lines++; but i don't know any better, do i look like i know frontend?
|
// want branching? if you try really hard.
|
||||||
// branching? use arbitrary lines value, do i look like i know any better?
|
|
||||||
#include <cstdio>
|
#include <cstdio>
|
||||||
#include <cstdint>
|
#include <cstdint>
|
||||||
#include <cstring>
|
#include <cstring>
|
||||||
@@ -12,13 +11,13 @@
|
|||||||
#include <text.h>
|
#include <text.h>
|
||||||
#include <compositor.h>
|
#include <compositor.h>
|
||||||
#include <image.h>
|
#include <image.h>
|
||||||
|
#include <audio.h>
|
||||||
|
|
||||||
// --- RAII wrappers for engine objects ---
|
// smartass approach
|
||||||
struct WindowHandle {
|
struct WindowHandle {
|
||||||
window_t* ptr;
|
window_t* ptr;
|
||||||
WindowHandle(window_t* w) : ptr(w) {}
|
WindowHandle(window_t* w) : ptr(w) {}
|
||||||
~WindowHandle() { if (ptr) window_destroy(ptr); }
|
~WindowHandle() { if (ptr) window_destroy(ptr); }
|
||||||
window_t* get() { return ptr; }
|
|
||||||
operator window_t* () { return ptr; }
|
operator window_t* () { return ptr; }
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -26,7 +25,6 @@ struct CompositorHandle {
|
|||||||
compositor_t* ptr;
|
compositor_t* ptr;
|
||||||
CompositorHandle(compositor_t* c) : ptr(c) {}
|
CompositorHandle(compositor_t* c) : ptr(c) {}
|
||||||
~CompositorHandle() { if (ptr) compositor_destroy(ptr); }
|
~CompositorHandle() { if (ptr) compositor_destroy(ptr); }
|
||||||
compositor_t* get() { return ptr; }
|
|
||||||
operator compositor_t* () { return ptr; }
|
operator compositor_t* () { return ptr; }
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -34,7 +32,6 @@ struct TextHandle {
|
|||||||
text_t* ptr;
|
text_t* ptr;
|
||||||
TextHandle(text_t* t) : ptr(t) {}
|
TextHandle(text_t* t) : ptr(t) {}
|
||||||
~TextHandle() { if (ptr) text_destroy(ptr); }
|
~TextHandle() { if (ptr) text_destroy(ptr); }
|
||||||
text_t* get() { return ptr; }
|
|
||||||
operator text_t* () { return ptr; }
|
operator text_t* () { return ptr; }
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -42,17 +39,31 @@ struct ImageHandle {
|
|||||||
uint32_t* ptr;
|
uint32_t* ptr;
|
||||||
ImageHandle(uint32_t* p) : ptr(p) {}
|
ImageHandle(uint32_t* p) : ptr(p) {}
|
||||||
~ImageHandle() { if (ptr) image_free(ptr); }
|
~ImageHandle() { if (ptr) image_free(ptr); }
|
||||||
uint32_t* get() { return ptr; }
|
|
||||||
operator uint32_t* () { 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() {
|
int main() {
|
||||||
const int WIDTH = 1280;
|
const int WIDTH = 1280;
|
||||||
const int HEIGHT = 720;
|
const int HEIGHT = 720;
|
||||||
const int CHAR_WIDTH = 400;
|
const int CHAR_WIDTH = 400;
|
||||||
const int CHAR_HEIGHT = 600;
|
const int CHAR_HEIGHT = 600;
|
||||||
|
|
||||||
// --- Character Positioning Constants ---
|
// Character Positioning Constants
|
||||||
// The vertical baseline for character sprites, making positioning independent of sprite height.
|
// The vertical baseline for character sprites, making positioning independent of sprite height.
|
||||||
const int CHAR_BASELINE_Y = HEIGHT - 140;
|
const int CHAR_BASELINE_Y = HEIGHT - 140;
|
||||||
// The horizontal center for the character on the left.
|
// 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;
|
const int OFFSCREEN_LEFT_X = LEFT_CHAR_CENTER_X - 660;
|
||||||
|
|
||||||
WindowHandle window(window_create(WIDTH, HEIGHT, u8"The Obwarzanek Narrative Engine"));
|
WindowHandle window(window_create(WIDTH, HEIGHT, u8"The Obwarzanek Narrative Engine"));
|
||||||
if (!window.get()) {
|
if (!window) {
|
||||||
printf("Failed to create window\n");
|
printf("Failed to create window\n");
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
CompositorHandle compositor(compositor_init(WIDTH, HEIGHT));
|
CompositorHandle compositor(compositor_init(WIDTH, HEIGHT));
|
||||||
if (!compositor.get()) {
|
if (!compositor) {
|
||||||
printf("Failed to initialize compositor\n");
|
printf("Failed to initialize compositor\n");
|
||||||
return 1;
|
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));
|
ImageHandle bg(image_load_resized("assets/background.png", WIDTH, HEIGHT));
|
||||||
if (bg.get()) {
|
if (bg) {
|
||||||
uint32_t bg_id = compositor_insert(compositor.get(), bg.get(), WIDTH, HEIGHT, 0, 0, 0, 1.0f, ANCHOR_TOP_LEFT);
|
uint32_t bg_id = compositor_insert(compositor, bg, WIDTH, HEIGHT, 0, 0, 0, 1.0f, ANCHOR_TOP_LEFT);
|
||||||
compositor_set_alpha(compositor.get(), bg_id, 1.0f);
|
compositor_set_alpha(compositor, bg_id, 1.0f);
|
||||||
}
|
}
|
||||||
|
|
||||||
ImageHandle textbox(image_load_resized("assets/textbox.png", WIDTH, 180));
|
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
|
// 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));
|
ImageHandle maksw_happy(image_load_resized("assets/maksw.png", CHAR_WIDTH, CHAR_HEIGHT));
|
||||||
// Insert off-screen left, anchored at bottom-center for natural scaling
|
// Insert off-screen left, anchored at bottom-center for natural scaling
|
||||||
uint32_t maksw_happy_id = compositor_insert(compositor.get(), maksw_happy.get(),
|
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);
|
||||||
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));
|
ImageHandle maksw_neutral(image_load_resized("assets/maksw2.png", CHAR_WIDTH, CHAR_HEIGHT));
|
||||||
// Insert at the final left position, anchored at bottom-center
|
// 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);
|
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));
|
ImageHandle wikor(image_load_resized("assets/wikor.png", CHAR_WIDTH, CHAR_HEIGHT));
|
||||||
// Insert at the final right position, anchored at bottom-center
|
// 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);
|
CHAR_WIDTH, CHAR_HEIGHT, RIGHT_CHAR_CENTER_X, CHAR_BASELINE_Y, 2, 1.0f, ANCHOR_BOTTOM_CENTER);
|
||||||
|
|
||||||
TextHandle name_text(text_init(300, 100));
|
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
|
// 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);
|
300, 100, 60, HEIGHT - 180, 4, 1.0f, ANCHOR_TOP_LEFT);
|
||||||
|
|
||||||
TextHandle dialogue_text(text_init(WIDTH - 140, 160));
|
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
|
// 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);
|
WIDTH - 140, 160, 60, HEIGHT - 130, 4, 1.0f, ANCHOR_TOP_LEFT);
|
||||||
|
|
||||||
ImageHandle mask(image_load_resized("assets/mask.png", 1280, 1080));
|
ImageHandle mask(image_load_resized("assets/mask.png", 1280, 1080));
|
||||||
// Use top-left anchor for full-screen wipes
|
// 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);
|
uint32_t mask_id = compositor_insert(compositor, mask, 1280, 1080, 0, -1080, 5, 1.0f, ANCHOR_TOP_LEFT);
|
||||||
compositor_set_visible(compositor.get(), mask_id, false);
|
compositor_set_visible(compositor, mask_id, false);
|
||||||
|
|
||||||
ImageHandle mask2(image_load_resized("assets/mask2.png", 1280, 1080));
|
ImageHandle mask2(image_load_resized("assets/mask2.png", 1280, 1080));
|
||||||
// Use top-left anchor for full-screen wipes
|
// 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);
|
uint32_t mask2_id = compositor_insert(compositor, mask2, 1280, 1080, 0, 0, 5, 1.0f, ANCHOR_TOP_LEFT);
|
||||||
compositor_set_visible(compositor.get(), mask2_id, false);
|
compositor_set_visible(compositor, mask2_id, false);
|
||||||
|
|
||||||
// yeah! memory savings!
|
|
||||||
//std::unique_ptr<uint32_t[]> 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);
|
|
||||||
|
|
||||||
bool running = true;
|
bool running = true;
|
||||||
|
|
||||||
auto last_frame_time = std::chrono::high_resolution_clock::now();
|
auto last_frame_time = std::chrono::high_resolution_clock::now();
|
||||||
auto last_report_time = last_frame_time;
|
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;
|
float frame_time_sum = 0.0f;
|
||||||
const std::chrono::duration<float> REPORT_INTERVAL = std::chrono::seconds(5);
|
const std::chrono::duration<float> REPORT_INTERVAL = std::chrono::seconds(1);
|
||||||
|
|
||||||
while (running) {
|
while (running) {
|
||||||
if (window_pollevent(window.get())) running = false;
|
if (window_pollevent(window)) running = false;
|
||||||
|
|
||||||
auto current_time = std::chrono::high_resolution_clock::now();
|
auto current_time = std::chrono::high_resolution_clock::now();
|
||||||
std::chrono::duration<float> delta_duration = current_time - last_frame_time;
|
std::chrono::duration<float> delta_duration = current_time - last_frame_time;
|
||||||
@@ -159,6 +171,7 @@ int main() {
|
|||||||
|
|
||||||
frame_count++;
|
frame_count++;
|
||||||
frame_time_sum += delta_time_sec;
|
frame_time_sum += delta_time_sec;
|
||||||
|
if (delta_time_sec > highest_frame_time) highest_frame_time = delta_time_sec;
|
||||||
|
|
||||||
std::chrono::duration<float> time_since_last_report = current_time - last_report_time;
|
std::chrono::duration<float> time_since_last_report = current_time - last_report_time;
|
||||||
|
|
||||||
@@ -168,130 +181,141 @@ int main() {
|
|||||||
float fps = 1.0f / avg_delta_time;
|
float fps = 1.0f / avg_delta_time;
|
||||||
float actual_duration = time_since_last_report.count();
|
float actual_duration = time_since_last_report.count();
|
||||||
|
|
||||||
printf("FPS: %.2f (over %d frames in %.2f seconds)\n",
|
printf("FPS: %.2f (over %llu frames in %.2f seconds), highest frame time: %.2fms\n",
|
||||||
fps, frame_count, actual_duration);
|
fps, frame_count, actual_duration, highest_frame_time * 1000.0f);
|
||||||
}
|
}
|
||||||
frame_count = 0;
|
frame_count = 0;
|
||||||
frame_time_sum = 0.0f;
|
frame_time_sum = 0.0f;
|
||||||
|
highest_frame_time = 0.0f;
|
||||||
last_report_time = current_time;
|
last_report_time = current_time;
|
||||||
}
|
}
|
||||||
|
|
||||||
static bool mouse_was_down = false;
|
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;
|
bool mouse_clicked = mouse_is_down && !mouse_was_down;
|
||||||
mouse_was_down = mouse_is_down;
|
mouse_was_down = mouse_is_down;
|
||||||
|
|
||||||
static bool key_was_down = false;
|
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;
|
bool key_pressed = key_is_down && !key_was_down;
|
||||||
key_was_down = key_is_down;
|
key_was_down = key_is_down;
|
||||||
|
|
||||||
// don't judge me, it will work for now
|
// 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 dialog_finished = false;
|
||||||
static bool transitioning = false;
|
static bool transitioning = false;
|
||||||
|
|
||||||
if (mouse_clicked || key_pressed) {
|
if (mouse_clicked || key_pressed || lines == 1 /* hack */) {
|
||||||
if (text_is_animating(dialogue_text.get())) {
|
if (text_is_animating(dialogue_text)) {
|
||||||
text_show_all_current(dialogue_text.get());
|
text_show_all_current(dialogue_text);
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
switch (lines) {
|
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:
|
case 2:
|
||||||
compositor_animation_complete_all(compositor.get());
|
compositor_finish_animations_all(compositor);
|
||||||
text_clear(dialogue_text.get());
|
text_clear(dialogue_text);
|
||||||
text_push(dialogue_text.get(), "Today I'll present to you-", 30.0f, true);
|
text_push(dialogue_text, "Today I'll present to you-", 30.0f, true);
|
||||||
lines++;
|
lines++;
|
||||||
break;
|
break;
|
||||||
case 3:
|
case 3:
|
||||||
compositor_animation_complete_all(compositor.get());
|
compositor_finish_animations_all(compositor);
|
||||||
compositor_animate_scale(compositor.get(), maksw_happy_id, 1.0f, 0.3f, EASE_IN_OUT_CUBIC);
|
compositor_animate_scale(compositor, maksw_happy_id, 1.0f, 0.3f, EASE_IN_OUT_CUBIC, false);
|
||||||
compositor_animate_scale(compositor.get(), wikor_id, 1.05f, 0.3f, EASE_IN_OUT_CUBIC);
|
compositor_animate_move(compositor, maksw_happy_id, LEFT_CHAR_CENTER_X, CHAR_BASELINE_Y - 20, 0.1f, EASE_OUT_CUBIC, false);
|
||||||
text_clear(dialogue_text.get());
|
compositor_animate_move(compositor, maksw_happy_id, LEFT_CHAR_CENTER_X, CHAR_BASELINE_Y, 0.1f, EASE_IN_CUBIC, true);
|
||||||
text_clear(name_text.get());
|
compositor_animate_scale(compositor, wikor_id, 1.05f, 0.3f, EASE_IN_OUT_CUBIC, false);
|
||||||
text_push(name_text.get(), "wikor", 0.0f, true);
|
text_clear(dialogue_text);
|
||||||
text_push(dialogue_text.get(), "Yeah, hello to you too.", 30.0f, true);
|
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++;
|
lines++;
|
||||||
break;
|
break;
|
||||||
case 4:
|
case 4:
|
||||||
compositor_animation_complete_all(compositor.get());
|
compositor_finish_animations_all(compositor);
|
||||||
compositor_animate_scale(compositor.get(), wikor_id, 1.0f, 0.3f, EASE_IN_OUT_CUBIC);
|
compositor_animate_scale(compositor, wikor_id, 1.0f, 0.3f, EASE_IN_OUT_CUBIC, false);
|
||||||
compositor_animate_scale(compositor.get(), maksw_happy_id, 1.05f, 0.3f, EASE_IN_OUT_CUBIC);
|
compositor_animate_scale(compositor, maksw_happy_id, 1.05f, 0.3f, EASE_IN_OUT_CUBIC, false);
|
||||||
text_clear(dialogue_text.get());
|
text_clear(dialogue_text);
|
||||||
text_clear(name_text.get());
|
text_clear(name_text);
|
||||||
text_push(name_text.get(), "maksw", 0.0f, true);
|
text_push(name_text, "maksw", 0.0f, true);
|
||||||
text_push(dialogue_text.get(), "Oh Wiktor, what a nice suprise!", 30.0f, true);
|
text_push(dialogue_text, "Oh Wiktor, what a nice suprise!", 30.0f, true);
|
||||||
lines++;
|
lines++;
|
||||||
break;
|
break;
|
||||||
case 5:
|
case 5:
|
||||||
compositor_animation_complete_all(compositor.get());
|
compositor_finish_animations_all(compositor);
|
||||||
text_clear(dialogue_text.get());
|
text_clear(dialogue_text);
|
||||||
text_push(dialogue_text.get(), "Do you need something?", 30.0f, true);
|
text_push(dialogue_text, "Do you need anything?", 30.0f, true);
|
||||||
lines++;
|
lines++;
|
||||||
break;
|
break;
|
||||||
case 6:
|
case 6:
|
||||||
compositor_animation_complete_all(compositor.get());
|
compositor_finish_animations_all(compositor);
|
||||||
compositor_animate_scale(compositor.get(), maksw_happy_id, 1.0f, 0.3f, EASE_IN_OUT_CUBIC);
|
compositor_animate_scale(compositor, maksw_happy_id, 1.0f, 0.3f, EASE_IN_OUT_CUBIC, false);
|
||||||
compositor_animate_scale(compositor.get(), wikor_id, 1.05f, 0.3f, EASE_IN_OUT_CUBIC);
|
compositor_animate_scale(compositor, wikor_id, 1.05f, 0.3f, EASE_IN_OUT_CUBIC, false);
|
||||||
text_clear(dialogue_text.get());
|
text_clear(dialogue_text);
|
||||||
text_clear(name_text.get());
|
text_clear(name_text);
|
||||||
text_push(name_text.get(), "wikor", 0.0f, true);
|
text_push(name_text, "wikor", 0.0f, true);
|
||||||
text_push(dialogue_text.get(), "No, i was just walking by.", 30.0f, true);
|
text_push(dialogue_text, "No, i was just walking by.", 30.0f, true);
|
||||||
lines++;
|
lines++;
|
||||||
break;
|
break;
|
||||||
case 7:
|
case 7:
|
||||||
compositor_animation_complete_all(compositor.get());
|
compositor_finish_animations_all(compositor);
|
||||||
text_clear(dialogue_text.get());
|
text_clear(dialogue_text);
|
||||||
text_push(dialogue_text.get(), "Cool engine though.", 30.0f, true);
|
text_push(dialogue_text, "Cool engine though.", 30.0f, true);
|
||||||
lines++;
|
lines++;
|
||||||
break;
|
break;
|
||||||
case 8:
|
case 8:
|
||||||
// fade transition
|
// fade transition
|
||||||
compositor_animation_complete_all(compositor.get());
|
compositor_animation_complete_all(compositor);
|
||||||
compositor_set_visible(compositor.get(), mask_id, true);
|
compositor_set_visible(compositor, mask_id, true);
|
||||||
compositor_animate_move(compositor.get(), mask_id, 0, 0, 0.5f, EASE_OUT_CUBIC, false);
|
compositor_animate_move(compositor, mask_id, 0, 0, 0.5f, EASE_OUT_CUBIC, false);
|
||||||
transitioning = true;
|
transitioning = true;
|
||||||
lines++;
|
lines++;
|
||||||
break;
|
break;
|
||||||
case 9:
|
case 9:
|
||||||
// some fucking how?
|
// transitioning blocks input, empty case not to trigger default and desync lines from actual line
|
||||||
break;
|
break;
|
||||||
case 10:
|
case 10:
|
||||||
compositor_animation_complete_all(compositor.get());
|
compositor_animation_complete_all(compositor);
|
||||||
compositor_animate_scale(compositor.get(), wikor_id, 1.0f, 0.3f, EASE_IN_OUT_CUBIC);
|
compositor_animate_scale(compositor, wikor_id, 1.0f, 0.3f, EASE_IN_OUT_CUBIC, false);
|
||||||
compositor_animate_scale(compositor.get(), maksw_neutral_id, 1.05f, 0.3f, EASE_IN_OUT_CUBIC);
|
compositor_animate_scale(compositor, maksw_neutral_id, 1.05f, 0.3f, EASE_IN_OUT_CUBIC, false);
|
||||||
text_clear(dialogue_text.get());
|
text_clear(dialogue_text);
|
||||||
text_clear(name_text.get());
|
text_clear(name_text);
|
||||||
text_push(name_text.get(), "maksw", 0.0f, true);
|
text_push(name_text, "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);
|
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++;
|
lines++;
|
||||||
break;
|
break;
|
||||||
case 11:
|
case 11:
|
||||||
compositor_animation_complete_all(compositor.get());
|
compositor_animation_complete_all(compositor);
|
||||||
compositor_animate_scale(compositor.get(), maksw_neutral_id, 1.0f, 0.3f, EASE_IN_OUT_CUBIC);
|
compositor_animate_scale(compositor, maksw_neutral_id, 1.0f, 0.3f, EASE_IN_OUT_CUBIC, false);
|
||||||
compositor_animate_scale(compositor.get(), wikor_id, 1.05f, 0.3f, EASE_IN_OUT_CUBIC);
|
compositor_animate_scale(compositor, wikor_id, 1.05f, 0.3f, EASE_IN_OUT_CUBIC, false);
|
||||||
text_clear(dialogue_text.get());
|
text_clear(dialogue_text);
|
||||||
text_clear(name_text.get());
|
text_clear(name_text);
|
||||||
text_push(name_text.get(), "wikor", 0.0f, true);
|
text_push(name_text, "wikor", 0.0f, true);
|
||||||
text_push(dialogue_text.get(), "Sure thing..", 30.0f, true);
|
text_push(dialogue_text, "Sure thing..", 30.0f, true);
|
||||||
lines++;
|
lines++;
|
||||||
break;
|
break;
|
||||||
case 12:
|
case 12:
|
||||||
compositor_animation_complete_all(compositor.get());
|
compositor_animation_complete_all(compositor);
|
||||||
text_clear(dialogue_text.get());
|
text_clear(dialogue_text);
|
||||||
text_push(dialogue_text.get(), "You're talking to me like i know anything about computers.", 30.0f, true);
|
text_push(dialogue_text, "You're talking to me like i know anything about computers.", 30.0f, true);
|
||||||
lines++;
|
lines++;
|
||||||
break;
|
break;
|
||||||
case 13:
|
case 13:
|
||||||
// fade to black and end
|
// fade to black and end
|
||||||
compositor_animation_complete_all(compositor.get());
|
compositor_animation_complete_all(compositor);
|
||||||
compositor_animate_scale(compositor.get(), maksw_neutral_id, 1.0f, 0.3f, EASE_IN_OUT_CUBIC, false);
|
compositor_animate_scale(compositor, maksw_neutral_id, 1.0f, 0.3f, EASE_IN_OUT_CUBIC, false);
|
||||||
//compositor_set_visible(compositor.get(), black_id, true);
|
// reuse mask
|
||||||
//compositor_animate_fade(compositor.get(), black_id, 1.0f, 0.7f, EASE_IN_CUBIC, false);
|
compositor_set_visible(compositor, mask_id, true);
|
||||||
// yeah! memory savings!
|
compositor_set_alpha(compositor, mask_id, 0.0f);
|
||||||
compositor_set_visible(compositor.get(), mask_id, true);
|
compositor_animate_fade(compositor, mask_id, 1.0f, 0.8f, EASE_IN_OUT_CUBIC, false);
|
||||||
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);
|
audio_animate_volume(audio, music_id, 0.0f, 0.6f);
|
||||||
|
|
||||||
dialog_finished = true;
|
dialog_finished = true;
|
||||||
lines++;
|
lines++;
|
||||||
break;
|
break;
|
||||||
@@ -302,43 +326,45 @@ int main() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
compositor_tick(compositor.get(), delta_time_sec);
|
compositor_tick(compositor, delta_time_sec);
|
||||||
|
|
||||||
if (transitioning) {
|
if (transitioning) {
|
||||||
if (!compositor_is_element_animating(compositor.get(), mask_id)) {
|
if (!compositor_is_element_animating(compositor, mask_id)) {
|
||||||
compositor_set_visible(compositor.get(), maksw_happy_id, false);
|
compositor_set_visible(compositor, maksw_happy_id, false);
|
||||||
compositor_set_visible(compositor.get(), maksw_neutral_id, true);
|
compositor_set_visible(compositor, maksw_neutral_id, true);
|
||||||
compositor_set_scale(compositor.get(), maksw_neutral_id, 1.0f);
|
compositor_set_scale(compositor, maksw_neutral_id, 1.0f);
|
||||||
compositor_set_scale(compositor.get(), wikor_id, 1.05f);
|
compositor_set_scale(compositor, wikor_id, 1.05f);
|
||||||
text_clear(dialogue_text.get());
|
text_clear(dialogue_text);
|
||||||
text_clear(name_text.get());
|
text_clear(name_text);
|
||||||
text_push(name_text.get(), "wikor", 0.0f, true);
|
text_push(name_text, "wikor", 0.0f, true);
|
||||||
text_push(dialogue_text.get(), "Anyways, how's your day going?", 30.0f, true);
|
text_push(dialogue_text, "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);
|
|
||||||
|
|
||||||
|
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;
|
transitioning = false;
|
||||||
lines++;
|
lines++;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (dialog_finished) {
|
if (dialog_finished) {
|
||||||
compositor_mark_content_dirty(compositor.get(), mask_id);
|
//compositor_mark_content_dirty(compositor, mask_id);
|
||||||
if (!compositor_is_element_animating(compositor.get(), mask_id)) {
|
if (!compositor_is_element_animating(compositor, mask_id)) {
|
||||||
running = false;
|
running = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (text_nextframe(name_text.get(), delta_time_sec))
|
if (text_nextframe(name_text, delta_time_sec))
|
||||||
compositor_mark_content_dirty(compositor.get(), name_id);
|
compositor_mark_content_dirty(compositor, name_id);
|
||||||
if (text_nextframe(dialogue_text.get(), delta_time_sec))
|
if (text_nextframe(dialogue_text, delta_time_sec))
|
||||||
compositor_mark_content_dirty(compositor.get(), dialogue_id);
|
compositor_mark_content_dirty(compositor, dialogue_id);
|
||||||
|
|
||||||
const uint32_t* fb = compositor_compose(compositor.get());
|
const uint32_t* fb = compositor_compose(compositor);
|
||||||
window_blit(window.get(), fb);
|
window_blit(window, fb);
|
||||||
|
audio_tick(audio, delta_time_sec);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
audio_stop(audio, music_id);
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
+12
-155
@@ -11,18 +11,17 @@
|
|||||||
#include <iostream>
|
#include <iostream>
|
||||||
#include <text.h>
|
#include <text.h>
|
||||||
|
|
||||||
// SSE2 intrinsics for x86/x64
|
|
||||||
#if defined(__x86_64__) || defined(_M_X64) || defined(__i386__) || defined(_M_IX86)
|
|
||||||
#define USE_SSE2 1
|
|
||||||
#include <immintrin.h>
|
|
||||||
#else
|
|
||||||
#define USE_SSE2 0
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#define STB_TRUETYPE_IMPLEMENTATION
|
#define STB_TRUETYPE_IMPLEMENTATION
|
||||||
#include "stb_truetype.h"
|
#ifdef _MSC_VER
|
||||||
|
#pragma warning(push, 0)
|
||||||
// --- UTF-8 Helpers ---
|
#endif
|
||||||
|
#if defined(__GNUC__) || defined(__clang__)
|
||||||
|
#pragma GCC system_header
|
||||||
|
#endif
|
||||||
|
#include <stb_truetype.h>
|
||||||
|
#ifdef _MSC_VER
|
||||||
|
#pragma warning(pop)
|
||||||
|
#endif
|
||||||
|
|
||||||
// Decode the next UTF-8 codepoint from `s` starting at `i` (byte index).
|
// Decode the next UTF-8 codepoint from `s` starting at `i` (byte index).
|
||||||
// Advances `i` to the next byte after the decoded codepoint.
|
// 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;
|
return count;
|
||||||
}
|
}
|
||||||
|
|
||||||
// --- Internal Data Structures ---
|
|
||||||
|
|
||||||
// Holds the raw file data and stbtt_fontinfo for a cached font.
|
// Holds the raw file data and stbtt_fontinfo for a cached font.
|
||||||
struct cached_font_t {
|
struct cached_font_t {
|
||||||
std::vector<uint8_t> file_data;
|
std::vector<uint8_t> file_data;
|
||||||
@@ -183,11 +180,6 @@ struct text_t {
|
|||||||
int last_rendered_char = 0; // codepoint count
|
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.
|
// 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) {
|
static cached_font_t* load_font(text_t* text, const std::string& font_path) {
|
||||||
// Check cache (now in shared_cache_t)
|
// 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;
|
return &map_it->second;
|
||||||
}
|
}
|
||||||
|
|
||||||
// --- Text Layout ---
|
|
||||||
|
|
||||||
// Precomputes line breaks for a text block.
|
// Precomputes line breaks for a text block.
|
||||||
static void precompute_line_breaks(text_t* text, const text_params_t* params) {
|
static void precompute_line_breaks(text_t* text, const text_params_t* params) {
|
||||||
if (params->lines_computed) return;
|
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;
|
params->lines_computed = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
// --- SDF Rendering ---
|
|
||||||
|
|
||||||
// Standard smoothstep function for SDF anti-aliasing.
|
// Standard smoothstep function for SDF anti-aliasing.
|
||||||
static float smoothstep(float edge0, float edge1, float x) {
|
static float smoothstep(float edge0, float edge1, float x) {
|
||||||
float t = std::clamp((x - edge0) / (edge1 - edge0), 0.0f, 1.0f);
|
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;
|
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.
|
// Draws a glyph using its cached SDF bitmap.
|
||||||
static void draw_glyph(text_t* text,
|
static void draw_glyph(text_t* text,
|
||||||
int x, int y,
|
int x, int y,
|
||||||
@@ -544,57 +477,7 @@ static void draw_glyph(text_t* text,
|
|||||||
|
|
||||||
for (int gy = gy0; gy < gy1; ++gy) {
|
for (int gy = gy0; gy < gy1; ++gy) {
|
||||||
int fb_y = y0 + 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) {
|
for (int gx = gx0; gx < gx1; ++gx) {
|
||||||
#endif
|
|
||||||
uint8_t sdf_raw = glyph->data[gy * glyph->w + gx];
|
uint8_t sdf_raw = glyph->data[gy * glyph->w + gx];
|
||||||
float dist = (sdf_raw - onedge_value) / pixel_dist_scale;
|
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" {
|
extern "C" {
|
||||||
|
|
||||||
// --- Lifecycle ---
|
|
||||||
|
|
||||||
text_t* text_init(int width, int height) {
|
text_t* text_init(int width, int height) {
|
||||||
if (width <= 0 || height <= 0) return nullptr;
|
if (width <= 0 || height <= 0) return nullptr;
|
||||||
|
|
||||||
@@ -716,8 +595,6 @@ void text_destroy(text_t* text) {
|
|||||||
delete text;
|
delete text;
|
||||||
}
|
}
|
||||||
|
|
||||||
// --- Font Configuration ---
|
|
||||||
|
|
||||||
// Sets the default font path and size to be used by subsequent text_push calls.
|
// 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 NULL for font_path to keep the current default font.
|
||||||
// Pass size <= 0 to keep the current default size.
|
// 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.
|
// Adds a block of text to the rendering queue.
|
||||||
void text_push(text_t* text,
|
void text_push(text_t* text,
|
||||||
const char* str,
|
const char* str,
|
||||||
@@ -761,23 +636,9 @@ void text_push(text_t* text,
|
|||||||
// Clears the framebuffer and resets the text queue and all state.
|
// Clears the framebuffer and resets the text queue and all state.
|
||||||
void text_clear(text_t* text) {
|
void text_clear(text_t* text) {
|
||||||
if (!text) return;
|
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));
|
memset(text->framebuffer, 0, text->width * text->height * sizeof(uint32_t));
|
||||||
#endif
|
|
||||||
text->current_x = text->padding_x;
|
text->current_x = text->padding_x;
|
||||||
text->current_y = text->padding_y;
|
text->current_y = text->padding_y;
|
||||||
text->text_queue.clear();
|
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.
|
// Advances the text animation and renders new characters.
|
||||||
// deltatime is the time elapsed since the last frame (in seconds).
|
// deltatime is the time elapsed since the last frame (in seconds).
|
||||||
bool text_nextframe(text_t* text, float deltatime) {
|
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;
|
return text->char_index < (int)total_cp;
|
||||||
}
|
}
|
||||||
|
|
||||||
// --- Accessors ---
|
|
||||||
|
|
||||||
// Returns a pointer to the internal framebuffer.
|
// Returns a pointer to the internal framebuffer.
|
||||||
const uint32_t* text_getfb(text_t* text) {
|
const uint32_t* text_getfb(text_t* text) {
|
||||||
return text ? text->framebuffer : nullptr;
|
return text ? text->framebuffer : nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
} // extern "C"
|
} // extern "C"
|
||||||
+23
-4
@@ -101,8 +101,11 @@
|
|||||||
<LanguageStandard>stdcpp20</LanguageStandard>
|
<LanguageStandard>stdcpp20</LanguageStandard>
|
||||||
<RuntimeLibrary>MultiThreadedDebug</RuntimeLibrary>
|
<RuntimeLibrary>MultiThreadedDebug</RuntimeLibrary>
|
||||||
<EnableEnhancedInstructionSet>StreamingSIMDExtensions2</EnableEnhancedInstructionSet>
|
<EnableEnhancedInstructionSet>StreamingSIMDExtensions2</EnableEnhancedInstructionSet>
|
||||||
<AdditionalOptions>/Zc:char8_t- %(AdditionalOptions)</AdditionalOptions>
|
<AdditionalOptions>/Zc:char8_t- /openmp:llvm %(AdditionalOptions)</AdditionalOptions>
|
||||||
<SupportJustMyCode>false</SupportJustMyCode>
|
<SupportJustMyCode>false</SupportJustMyCode>
|
||||||
|
<OpenMPSupport>
|
||||||
|
</OpenMPSupport>
|
||||||
|
<LanguageStandard_C>stdc17</LanguageStandard_C>
|
||||||
</ClCompile>
|
</ClCompile>
|
||||||
<Link>
|
<Link>
|
||||||
<SubSystem>Console</SubSystem>
|
<SubSystem>Console</SubSystem>
|
||||||
@@ -124,7 +127,10 @@
|
|||||||
<LanguageStandard>stdcpp20</LanguageStandard>
|
<LanguageStandard>stdcpp20</LanguageStandard>
|
||||||
<RuntimeLibrary>MultiThreadedDebug</RuntimeLibrary>
|
<RuntimeLibrary>MultiThreadedDebug</RuntimeLibrary>
|
||||||
<EnableEnhancedInstructionSet>StreamingSIMDExtensions2</EnableEnhancedInstructionSet>
|
<EnableEnhancedInstructionSet>StreamingSIMDExtensions2</EnableEnhancedInstructionSet>
|
||||||
<AdditionalOptions>/Zc:char8_t- %(AdditionalOptions)</AdditionalOptions>
|
<OpenMPSupport>
|
||||||
|
</OpenMPSupport>
|
||||||
|
<LanguageStandard_C>stdc17</LanguageStandard_C>
|
||||||
|
<AdditionalOptions>/Zc:char8_t- /openmp:llvm %(AdditionalOptions)</AdditionalOptions>
|
||||||
</ClCompile>
|
</ClCompile>
|
||||||
<Link>
|
<Link>
|
||||||
<SubSystem>Console</SubSystem>
|
<SubSystem>Console</SubSystem>
|
||||||
@@ -144,8 +150,13 @@
|
|||||||
<AdditionalIncludeDirectories>include</AdditionalIncludeDirectories>
|
<AdditionalIncludeDirectories>include</AdditionalIncludeDirectories>
|
||||||
<MultiProcessorCompilation>true</MultiProcessorCompilation>
|
<MultiProcessorCompilation>true</MultiProcessorCompilation>
|
||||||
<EnableEnhancedInstructionSet>StreamingSIMDExtensions2</EnableEnhancedInstructionSet>
|
<EnableEnhancedInstructionSet>StreamingSIMDExtensions2</EnableEnhancedInstructionSet>
|
||||||
<AdditionalOptions>/Zc:char8_t- %(AdditionalOptions)</AdditionalOptions>
|
<AdditionalOptions>/Zc:char8_t- /openmp:llvm %(AdditionalOptions)</AdditionalOptions>
|
||||||
<SupportJustMyCode>false</SupportJustMyCode>
|
<SupportJustMyCode>false</SupportJustMyCode>
|
||||||
|
<OpenMPSupport>
|
||||||
|
</OpenMPSupport>
|
||||||
|
<LanguageStandard_C>stdc17</LanguageStandard_C>
|
||||||
|
<FloatingPointModel>Fast</FloatingPointModel>
|
||||||
|
<BasicRuntimeChecks>EnableFastChecks</BasicRuntimeChecks>
|
||||||
</ClCompile>
|
</ClCompile>
|
||||||
<Link>
|
<Link>
|
||||||
<SubSystem>Console</SubSystem>
|
<SubSystem>Console</SubSystem>
|
||||||
@@ -167,7 +178,13 @@
|
|||||||
<LanguageStandard>stdcpp20</LanguageStandard>
|
<LanguageStandard>stdcpp20</LanguageStandard>
|
||||||
<RuntimeLibrary>MultiThreadedDebug</RuntimeLibrary>
|
<RuntimeLibrary>MultiThreadedDebug</RuntimeLibrary>
|
||||||
<EnableEnhancedInstructionSet>StreamingSIMDExtensions2</EnableEnhancedInstructionSet>
|
<EnableEnhancedInstructionSet>StreamingSIMDExtensions2</EnableEnhancedInstructionSet>
|
||||||
<AdditionalOptions>/Zc:char8_t- %(AdditionalOptions)</AdditionalOptions>
|
<OpenMPSupport>true</OpenMPSupport>
|
||||||
|
<LanguageStandard_C>stdc17</LanguageStandard_C>
|
||||||
|
<AdditionalOptions>/Zc:char8_t- /Zc:__cplusplus %(AdditionalOptions)</AdditionalOptions>
|
||||||
|
<WholeProgramOptimization>true</WholeProgramOptimization>
|
||||||
|
<InlineFunctionExpansion>AnySuitable</InlineFunctionExpansion>
|
||||||
|
<FavorSizeOrSpeed>Speed</FavorSizeOrSpeed>
|
||||||
|
<Optimization>Full</Optimization>
|
||||||
</ClCompile>
|
</ClCompile>
|
||||||
<Link>
|
<Link>
|
||||||
<SubSystem>Console</SubSystem>
|
<SubSystem>Console</SubSystem>
|
||||||
@@ -177,6 +194,7 @@
|
|||||||
</Link>
|
</Link>
|
||||||
</ItemDefinitionGroup>
|
</ItemDefinitionGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
<ClCompile Include="src\audio.cpp" />
|
||||||
<ClCompile Include="src\compositor.cpp" />
|
<ClCompile Include="src\compositor.cpp" />
|
||||||
<ClCompile Include="src\image.cpp" />
|
<ClCompile Include="src\image.cpp" />
|
||||||
<ClCompile Include="src\main.cpp" />
|
<ClCompile Include="src\main.cpp" />
|
||||||
@@ -184,6 +202,7 @@
|
|||||||
<ClCompile Include="src\window.cpp" />
|
<ClCompile Include="src\window.cpp" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
<ClInclude Include="include\audio.h" />
|
||||||
<ClInclude Include="include\compositor.h" />
|
<ClInclude Include="include\compositor.h" />
|
||||||
<ClInclude Include="include\image.h" />
|
<ClInclude Include="include\image.h" />
|
||||||
<ClInclude Include="include\SDL2\SDL.h" />
|
<ClInclude Include="include\SDL2\SDL.h" />
|
||||||
|
|||||||
@@ -36,6 +36,9 @@
|
|||||||
<ClCompile Include="src\window.cpp">
|
<ClCompile Include="src\window.cpp">
|
||||||
<Filter>Source Files</Filter>
|
<Filter>Source Files</Filter>
|
||||||
</ClCompile>
|
</ClCompile>
|
||||||
|
<ClCompile Include="src\audio.cpp">
|
||||||
|
<Filter>Source Files</Filter>
|
||||||
|
</ClCompile>
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<ClInclude Include="include\compositor.h">
|
<ClInclude Include="include\compositor.h">
|
||||||
@@ -62,6 +65,9 @@
|
|||||||
<ClInclude Include="include\SDL2\SDL.h">
|
<ClInclude Include="include\SDL2\SDL.h">
|
||||||
<Filter>Header Files\SDL2</Filter>
|
<Filter>Header Files\SDL2</Filter>
|
||||||
</ClInclude>
|
</ClInclude>
|
||||||
|
<ClInclude Include="include\audio.h">
|
||||||
|
<Filter>Header Files</Filter>
|
||||||
|
</ClInclude>
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<Image Include="assets\background.png">
|
<Image Include="assets\background.png">
|
||||||
|
|||||||
Reference in new issue
Block a user