diff --git a/assets/Aller_Rg.ttf b/assets/Aller_Rg.ttf new file mode 100644 index 0000000..40e9c69 Binary files /dev/null and b/assets/Aller_Rg.ttf differ diff --git a/assets/background.png b/assets/background.png index aaf3a0c..f3b08d2 100644 Binary files a/assets/background.png and b/assets/background.png differ diff --git a/assets/maksw2.png b/assets/maksw2.png new file mode 100644 index 0000000..80ed6bb Binary files /dev/null and b/assets/maksw2.png differ diff --git a/include/compositor.h b/include/compositor.h index 71b2227..8bfac3b 100644 --- a/include/compositor.h +++ b/include/compositor.h @@ -1,7 +1,6 @@ // compositor.h #ifndef COMPOSITOR_H #define COMPOSITOR_H - #include #include @@ -26,7 +25,6 @@ enum easing_t { }; extern "C" { - typedef struct compositor_t compositor_t; // Initialize compositor with given dimensions @@ -37,33 +35,30 @@ void compositor_destroy(compositor_t* comp); // Insert element, returns unique ID uint32_t compositor_insert(compositor_t* comp, - const uint32_t* fb, - uint32_t fb_width, - uint32_t fb_height, - int32_t x, int32_t y, - int32_t z_order, float scale = 1.0f); + const uint32_t* fb, + uint32_t fb_width, + uint32_t fb_height, + int32_t x, int32_t y, + int32_t z_order, float scale = 1.0f); // Remove element by ID void compositor_remove(compositor_t* comp, uint32_t id); // AIO Update element properties void compositor_update(compositor_t* comp, uint32_t id, - int32_t x, int32_t y, - int32_t z_order, float scale, - bool visible); // holy clusterfuck + int32_t x, int32_t y, + int32_t z_order, float scale, + bool visible); // Individual setters void 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, - int32_t z_order); - + int32_t z_order); void compositor_set_visible(compositor_t* comp, uint32_t id, - bool visible); - + bool visible); void compositor_set_scale(compositor_t* comp, uint32_t id, - float scale); + float scale); // Advances all animations by a given time delta. void compositor_tick(compositor_t* comp, float delta_time_sec); @@ -71,20 +66,29 @@ void compositor_tick(compositor_t* comp, float delta_time_sec); // Instantly sets the element-wide alpha. void compositor_set_alpha(compositor_t* comp, uint32_t id, float alpha); -// Starts a "slide" animation on an element. +// Animation functions with queue parameter void compositor_animate_move(compositor_t* comp, uint32_t id, int32_t to_x, int32_t to_y, - float duration_sec, easing_t easing); + float duration_sec, easing_t easing, bool queue = false); -// Starts a "fade" or "dissolve" animation on an element. void compositor_animate_fade(compositor_t* comp, uint32_t id, float to_alpha, - float duration_sec, easing_t easing); + float duration_sec, easing_t easing, bool queue = false); -// Starts a "scale" animation on an element. void compositor_animate_scale(compositor_t* comp, uint32_t id, float to_scale, - float duration_sec, easing_t easing); + float duration_sec, easing_t easing, bool queue = false); + +// Immediately complete all animations +void compositor_animation_complete(compositor_t* comp, uint32_t id); + +void compositor_animation_complete_all(compositor_t* comp); + +// Returns true if any element is animating or has queued animations. +bool compositor_is_animating(compositor_t * comp); + +// Returns true if the specified element is animating or has queued animations. +bool compositor_is_element_animating(compositor_t* comp, uint32_t id); // Compose all visible elements and return framebuffer pointer const uint32_t* compositor_compose(compositor_t* comp); @@ -93,9 +97,6 @@ void compositor_mark_content_dirty(compositor_t* comp, uint32_t id); void compositor_mark_all_dirty(compositor_t* comp); // Returns the last error message set for this compositor instance. -// Returns nullptr if no error has occurred. const char* compositor_get_last_error(compositor_t* comp); - } - -#endif // COMPOSITOR_H +#endif // COMPOSITOR_H \ No newline at end of file diff --git a/include/image.h b/include/image.h index 08eaba0..293bfc4 100644 --- a/include/image.h +++ b/include/image.h @@ -5,10 +5,11 @@ #include extern "C" { -// Loads an image file as ARGB8888. Returns pointer to pixel data (free with image_free), 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); -// Loads and resizes an image file to desired dimensions as ARGB8888. Returns pointer to pixel data (free with image_free). +// Loads and resizes an image file to desired dimensions as ARGB8888. Returns pointer to pixel data. uint32_t* image_load_resized(const char* filename, int desired_width, int desired_height); +// Frees image data. void image_free(uint32_t* data); } diff --git a/include/text.h b/include/text.h index a45cfda..e671003 100644 --- a/include/text.h +++ b/include/text.h @@ -7,22 +7,28 @@ struct text_t; extern "C" { +// initializes a new text renderer with given width and height text_t* text_init(int width, int height); // shares cache, prefer when using the same font to save memory text_t* text_init_shared(text_t* source, int width, int height); +// destroys the text renderer and frees resources void text_destroy(text_t* text); +// pushes new text buffer to be rendered, can be stacked void text_push(text_t* text, const char* str, - float size, float speed, - const char* font_path, bool outline); -// finish current text immediately +// load font and set as current font +void text_set_font(text_t* text, const char* font_path, float size); +// finish whole current text buffer immediately void text_show_all_current(text_t* text); +// returns true if text is still animating bool text_is_animating(text_t* text); -// advance, call every frame, returns false when done +// advance, call every frame, returns true when it touched the framebuffer bool text_nextframe(text_t* text, float deltatime); -uint32_t* text_getfb(text_t* text); +// gets the framebuffer of the text renderer +const uint32_t* text_getfb(text_t* text); +// clears the text buffer and the frame buffer void text_clear(text_t* text); } diff --git a/include/window.h b/include/window.h index 6a72cca..cba0465 100644 --- a/include/window.h +++ b/include/window.h @@ -35,12 +35,19 @@ typedef struct window_t window_t; #define KEY_0 39 extern "C" { +// creates a window with given width, height and title, title can be utf8 window_t* window_create(int width, int height, const char* title); +// destroys the window void window_destroy(window_t* s); +// polls window events, returns true if the window should close bool window_pollevent(window_t* s); +// blits the given framebuffer to the window. must be the same size as the window and ARGB8888 format void window_blit(window_t* s, const uint32_t* framebuffer); +// returns true if the given key is currently held down bool window_is_key_down(window_t* s, int key_scancode); +// returns true if the given mouse button is currently held down bool window_is_mouse_button_down(window_t* s, int button_index); +// gets the current mouse position in window coordinates void window_get_mouse_position(window_t* s, int* x, int* y); } diff --git a/src/compositor.cpp b/src/compositor.cpp index 834db0c..f59a865 100644 --- a/src/compositor.cpp +++ b/src/compositor.cpp @@ -12,13 +12,14 @@ // Define the tile size #define TILE_SIZE 64 +struct property_anim_t { + float progress_sec; + float duration_sec; + easing_t easing; +}; + struct animation_t { animation_type_t type = ANIM_NONE; - easing_t easing = EASE_LINEAR; - - // Time - float progress_sec = 0.0f; - float duration_sec = 0.0f; // Start/End state int32_t start_x = 0, start_y = 0; @@ -30,8 +31,34 @@ struct animation_t { float start_alpha = 1.0f; float end_alpha = 1.0f; - // Combined animation (ANIM_MOVE_FADE) - bool is_move_fade = false; + // Per-property animation state + property_anim_t position_anim; + property_anim_t scale_anim; + property_anim_t alpha_anim; + + // Track which properties are being animated + bool animating_position = false; + bool animating_scale = false; + bool animating_alpha = false; +}; + +// Queued animation structure +struct queued_animation_t { + animation_type_t type; + easing_t easing; + float duration_sec; + + // Target values + int32_t to_x, to_y; + float to_scale; + float to_alpha; + + bool is_move_fade; + + // Which properties this animation affects + bool affects_position; + bool affects_scale; + bool affects_alpha; }; struct element_t { @@ -46,6 +73,7 @@ struct element_t { uint32_t id; // Unique identifier std::vector occupied_tiles; // List of occupied tiles animation_t anim; + std::vector anim_queue; }; struct compositor_t { @@ -268,83 +296,115 @@ extern "C" { void compositor_tick(compositor_t* comp, float delta_time_sec) { if (!comp) return; - // Loop over all elements in the map for (auto& pair : comp->elements_map) { element_t* elem = &pair.second; animation_t* anim = &elem->anim; - if (anim->type == ANIM_NONE) { - continue; // Not animating + // Check if we need to start a queued animation + if (anim->type == ANIM_NONE && !elem->anim_queue.empty()) { + queued_animation_t& next = elem->anim_queue.front(); + + anim->type = next.type; + anim->animating_position = next.affects_position; + anim->animating_scale = next.affects_scale; + anim->animating_alpha = next.affects_alpha; + + if (next.affects_position) { + anim->start_x = elem->x; + anim->start_y = elem->y; + anim->end_x = next.to_x; + anim->end_y = next.to_y; + anim->position_anim.progress_sec = 0.0f; + anim->position_anim.duration_sec = next.duration_sec; + anim->position_anim.easing = next.easing; + } + if (next.affects_alpha) { + anim->start_alpha = elem->alpha; + anim->end_alpha = next.to_alpha; + anim->alpha_anim.progress_sec = 0.0f; + anim->alpha_anim.duration_sec = next.duration_sec; + anim->alpha_anim.easing = next.easing; + } + if (next.affects_scale) { + anim->start_scale = elem->scale; + anim->end_scale = next.to_scale; + anim->scale_anim.progress_sec = 0.0f; + anim->scale_anim.duration_sec = next.duration_sec; + anim->scale_anim.easing = next.easing; + } + + elem->anim_queue.erase(elem->anim_queue.begin()); } - // --- 1. Update Progress --- - anim->progress_sec += delta_time_sec; - float t_raw = anim->progress_sec / anim->duration_sec; + // Skip if nothing is animating + if (anim->type == ANIM_NONE) continue; - // Check for animation end - if (t_raw >= 1.0f) { - t_raw = 1.0f; // Clamp to 1.0 - anim->type = ANIM_NONE; // Animation is done - } - - // --- 2. Apply Easing --- - float t_eased = apply_easing(anim->easing, t_raw); - - // --- 3. Update Properties --- - - // Mark old position as dirty before we change it - // We only need to do this *once* at the start of the tick mark_element_dirty(comp, elem); - bool position_changed = false; + bool all_done = true; - // Animate Move - if (anim->type == ANIM_MOVE || anim->is_move_fade) { - elem->x = (int32_t)std::round(lerp(static_cast(anim->start_x), - static_cast(anim->end_x), t_eased)); - elem->y = (int32_t)std::round(lerp(static_cast(anim->start_y), - static_cast(anim->end_y), t_eased)); + // Update position animation with its own timing + if (anim->animating_position) { + anim->position_anim.progress_sec += delta_time_sec; + float t_raw = anim->position_anim.progress_sec / anim->position_anim.duration_sec; + + if (t_raw >= 1.0f) { + elem->x = anim->end_x; + elem->y = anim->end_y; + anim->animating_position = false; + } else { + float t_eased = apply_easing(anim->position_anim.easing, t_raw); + elem->x = (int32_t)std::round(lerp(static_cast(anim->start_x), + static_cast(anim->end_x), t_eased)); + elem->y = (int32_t)std::round(lerp(static_cast(anim->start_y), + static_cast(anim->end_y), t_eased)); + all_done = false; + } position_changed = true; } - // Animate Fade - if (anim->type == ANIM_FADE || anim->is_move_fade) { - elem->alpha = lerp(anim->start_alpha, anim->end_alpha, t_eased); - } - - // Animate Scale - if (anim->type == ANIM_SCALE) { - elem->scale = lerp(anim->start_scale, anim->end_scale, t_eased); - position_changed = true; // Scale change also affects position/hash - } - - // --- 4. Handle End-of-Animation State --- - if (anim->type == ANIM_NONE) { - // Animation just finished, snap to final values - if (anim->is_move_fade) { - elem->x = anim->end_x; - elem->y = anim->end_y; + // Update alpha animation with its own timing + if (anim->animating_alpha) { + anim->alpha_anim.progress_sec += delta_time_sec; + float t_raw = anim->alpha_anim.progress_sec / anim->alpha_anim.duration_sec; + + if (t_raw >= 1.0f) { elem->alpha = anim->end_alpha; - anim->is_move_fade = false; - } else if (anim->type == ANIM_MOVE) { - elem->x = anim->end_x; - elem->y = anim->end_y; - } else if (anim->type == ANIM_FADE) { - elem->alpha = anim->end_alpha; - // If it faded out, set it to invisible to remove from render if (elem->alpha == 0.0f) elem->visible = false; - } else if (anim->type == ANIM_SCALE) { - elem->scale = anim->end_scale; + anim->animating_alpha = false; + } else { + float t_eased = apply_easing(anim->alpha_anim.easing, t_raw); + elem->alpha = lerp(anim->start_alpha, anim->end_alpha, t_eased); + all_done = false; } } + + // Update scale animation with its own timing + if (anim->animating_scale) { + anim->scale_anim.progress_sec += delta_time_sec; + float t_raw = anim->scale_anim.progress_sec / anim->scale_anim.duration_sec; + + if (t_raw >= 1.0f) { + elem->scale = anim->end_scale; + anim->animating_scale = false; + } else { + float t_eased = apply_easing(anim->scale_anim.easing, t_raw); + elem->scale = lerp(anim->start_scale, anim->end_scale, t_eased); + all_done = false; + } + position_changed = true; + } + + // If all animations are done, mark as complete + if (all_done) { + anim->type = ANIM_NONE; + } - // --- 5. Update Spatial Hash --- - // If position or scale changed, the hash must be updated + // Update spatial hash if (position_changed) { - clear_spatial_hash(comp, elem->id); - update_spatial_hash(comp, elem); + clear_spatial_hash(comp, elem->id); + update_spatial_hash(comp, elem); } else { - // If only alpha changed, we just need to mark it dirty mark_element_dirty(comp, elem); } } @@ -621,10 +681,9 @@ void compositor_set_alpha(compositor_t* comp, uint32_t id, float alpha) { // --- Animation --- -// Starts a "slide" animation on an element. void compositor_animate_move(compositor_t* comp, uint32_t id, int32_t to_x, int32_t to_y, - float duration_sec, easing_t easing) { + float duration_sec, easing_t easing, bool queue) { if (!comp) return; element_t* elem = find_element(comp, id); if (!elem) { @@ -632,50 +691,41 @@ void compositor_animate_move(compositor_t* comp, uint32_t id, return; } - elem->anim.type = ANIM_MOVE; - elem->anim.easing = easing; - elem->anim.progress_sec = 0.0f; - elem->anim.duration_sec = duration_sec; + if (queue) { + queued_animation_t queued; + queued.type = ANIM_MOVE; + queued.easing = easing; + queued.duration_sec = duration_sec; + queued.to_x = to_x; + queued.to_y = to_y; + queued.is_move_fade = false; + queued.affects_position = true; + queued.affects_scale = false; + queued.affects_alpha = false; + elem->anim_queue.push_back(queued); + return; + } + // Parallel execution with independent timing elem->anim.start_x = elem->x; elem->anim.start_y = elem->y; elem->anim.end_x = to_x; elem->anim.end_y = to_y; + elem->anim.animating_position = true; - elem->anim.is_move_fade = false; + // Set position-specific timing + elem->anim.position_anim.progress_sec = 0.0f; + elem->anim.position_anim.duration_sec = duration_sec; + elem->anim.position_anim.easing = easing; + + if (elem->anim.type == ANIM_NONE) { + elem->anim.type = ANIM_MOVE; + } } -// Starts a "fade" or "dissolve" animation on an element. -void compositor_animate_fade(compositor_t* comp, uint32_t id, - float to_alpha, - float duration_sec, easing_t easing) { - if (!comp) return; - element_t* elem = find_element(comp, id); - if (!elem) { - set_error(comp, "Element not found for animate_fade."); - return; - } - - // If we're fading in from 0, we must make it visible first - if (elem->alpha == 0.0f && to_alpha > 0.0f) { - elem->visible = true; - } - - elem->anim.type = ANIM_FADE; - elem->anim.easing = easing; - elem->anim.progress_sec = 0.0f; - elem->anim.duration_sec = duration_sec; - - elem->anim.start_alpha = elem->alpha; - elem->anim.end_alpha = std::max(0.0f, std::min(1.0f, to_alpha)); // Clamp 0-1 - - elem->anim.is_move_fade = false; -} - -// Starts a "scale" animation on an element. void compositor_animate_scale(compositor_t* comp, uint32_t id, float to_scale, - float duration_sec, easing_t easing) { + float duration_sec, easing_t easing, bool queue) { if (!comp) return; element_t* elem = find_element(comp, id); if (!elem) { @@ -683,35 +733,204 @@ void compositor_animate_scale(compositor_t* comp, uint32_t id, return; } - elem->anim.type = ANIM_SCALE; - elem->anim.easing = easing; - elem->anim.progress_sec = 0.0f; - elem->anim.duration_sec = duration_sec; - - elem->anim.start_scale = elem->scale; - elem->anim.end_scale = to_scale; - - elem->anim.is_move_fade = false; -} - -// Example for a combined animation -void compositor_animate_move_fade(compositor_t* comp, uint32_t id, - int32_t to_x, int32_t to_y, float to_alpha, - float duration_sec, easing_t easing) { - if (!comp) return; - element_t* elem = find_element(comp, id); - if (!elem) { - set_error(comp, "Element not found for animate_move_fade."); + if (queue) { + queued_animation_t queued; + queued.type = ANIM_SCALE; + queued.easing = easing; + queued.duration_sec = duration_sec; + queued.to_scale = to_scale; + queued.is_move_fade = false; + queued.affects_position = false; + queued.affects_scale = true; + queued.affects_alpha = false; + elem->anim_queue.push_back(queued); return; } - // Trigger both - compositor_animate_move(comp, id, to_x, to_y, duration_sec, easing); - compositor_animate_fade(comp, id, to_alpha, duration_sec, easing); + // Parallel execution with independent timing + elem->anim.start_scale = elem->scale; + elem->anim.end_scale = to_scale; + elem->anim.animating_scale = true; - // Mark as combined - elem->anim.type = ANIM_MOVE_FADE; // Override type - elem->anim.is_move_fade = true; + // Set scale-specific timing + elem->anim.scale_anim.progress_sec = 0.0f; + elem->anim.scale_anim.duration_sec = duration_sec; + elem->anim.scale_anim.easing = easing; + + if (elem->anim.type == ANIM_NONE) { + elem->anim.type = ANIM_SCALE; + } +} + +void compositor_animate_fade(compositor_t* comp, uint32_t id, + float to_alpha, + float duration_sec, easing_t easing, bool queue) { + if (!comp) return; + element_t* elem = find_element(comp, id); + if (!elem) { + set_error(comp, "Element not found for animate_fade."); + return; + } + + to_alpha = std::max(0.0f, std::min(1.0f, to_alpha)); + + if (queue) { + queued_animation_t queued; + queued.type = ANIM_FADE; + queued.easing = easing; + queued.duration_sec = duration_sec; + queued.to_alpha = to_alpha; + queued.is_move_fade = false; + queued.affects_position = false; + queued.affects_scale = false; + queued.affects_alpha = true; + elem->anim_queue.push_back(queued); + return; + } + + // Parallel execution with independent timing + if (elem->alpha == 0.0f && to_alpha > 0.0f) { + elem->visible = true; + } + + elem->anim.start_alpha = elem->alpha; + elem->anim.end_alpha = to_alpha; + elem->anim.animating_alpha = true; + + // Set alpha-specific timing + elem->anim.alpha_anim.progress_sec = 0.0f; + elem->anim.alpha_anim.duration_sec = duration_sec; + elem->anim.alpha_anim.easing = easing; + + if (elem->anim.type == ANIM_NONE) { + elem->anim.type = ANIM_FADE; + } +} + +// Complete all animations on a specific element +void compositor_animation_complete(compositor_t* comp, uint32_t id) { + if (!comp) return; + element_t* elem = find_element(comp, id); + if (!elem) { + set_error(comp, "Element not found for animation_complete."); + return; + } + + mark_element_dirty(comp, elem); + bool position_changed = false; + + // Complete current animation + if (elem->anim.type != ANIM_NONE) { + if (elem->anim.animating_position) { + elem->x = elem->anim.end_x; + elem->y = elem->anim.end_y; + position_changed = true; + } + if (elem->anim.animating_alpha) { + elem->alpha = elem->anim.end_alpha; + if (elem->alpha == 0.0f) elem->visible = false; + } + if (elem->anim.animating_scale) { + elem->scale = elem->anim.end_scale; + position_changed = true; + } + } + + // Complete all queued animations + for (const auto& queued : elem->anim_queue) { + if (queued.affects_position) { + elem->x = queued.to_x; + elem->y = queued.to_y; + position_changed = true; + } + if (queued.affects_alpha) { + elem->alpha = queued.to_alpha; + if (elem->alpha == 0.0f) elem->visible = false; + } + if (queued.affects_scale) { + elem->scale = queued.to_scale; + position_changed = true; + } + } + + // Clear animations + elem->anim.type = ANIM_NONE; + elem->anim.animating_position = false; + elem->anim.animating_scale = false; + elem->anim.animating_alpha = false; + elem->anim_queue.clear(); + + // Update spatial hash + if (position_changed) { + clear_spatial_hash(comp, elem->id); + update_spatial_hash(comp, elem); + } + else { + mark_element_dirty(comp, elem); + } +} + +// Complete ALL animations on ALL elements +void compositor_animation_complete_all(compositor_t* comp) { + if (!comp) return; + + // Iterate over all elements + for (auto& pair : comp->elements_map) { + element_t* elem = &pair.second; + + mark_element_dirty(comp, elem); + bool position_changed = false; + + // Complete current animation + if (elem->anim.type != ANIM_NONE) { + if (elem->anim.animating_position) { + elem->x = elem->anim.end_x; + elem->y = elem->anim.end_y; + position_changed = true; + } + if (elem->anim.animating_alpha) { + elem->alpha = elem->anim.end_alpha; + if (elem->alpha == 0.0f) elem->visible = false; + } + if (elem->anim.animating_scale) { + elem->scale = elem->anim.end_scale; + position_changed = true; + } + } + + // Complete all queued animations + for (const auto& queued : elem->anim_queue) { + if (queued.affects_position) { + elem->x = queued.to_x; + elem->y = queued.to_y; + position_changed = true; + } + if (queued.affects_alpha) { + elem->alpha = queued.to_alpha; + if (elem->alpha == 0.0f) elem->visible = false; + } + if (queued.affects_scale) { + elem->scale = queued.to_scale; + position_changed = true; + } + } + + // Clear animations + elem->anim.type = ANIM_NONE; + elem->anim.animating_position = false; + elem->anim.animating_scale = false; + elem->anim.animating_alpha = false; + elem->anim_queue.clear(); + + // Update spatial hash + if (position_changed) { + clear_spatial_hash(comp, elem->id); + update_spatial_hash(comp, elem); + } + else { + mark_element_dirty(comp, elem); + } + } } // --- Dirty Marking --- @@ -739,6 +958,40 @@ void compositor_mark_content_dirty(compositor_t* comp, uint32_t id) { comp->last_error.clear(); } +// Returns true if any element has an active animation or queued animations. +bool compositor_is_animating(compositor_t* comp) { + if (!comp) return false; + for (const auto& pair : comp->elements_map) { + const element_t& elem = pair.second; + const animation_t& anim = elem.anim; + + // Active animation (type set) or any per-property animating flags + if (anim.type != ANIM_NONE) return true; + if (anim.animating_position || anim.animating_scale || anim.animating_alpha) return true; + + // Pending queued animations + if (!elem.anim_queue.empty()) return true; + } + return false; +} + +// Returns true if the specified element is animating or has queued animations. +bool compositor_is_element_animating(compositor_t* comp, uint32_t id) { + if (!comp) return false; + element_t* elem = find_element(comp, id); + if (!elem) { + set_error(comp, "Element with ID " + std::to_string(id) + " not found to check animation."); + return false; + } + const animation_t& anim = elem->anim; + // Active animation (type set) or any per-property animating flags + if (anim.type != ANIM_NONE) return true; + if (anim.animating_position || anim.animating_scale || anim.animating_alpha) return true; + // Pending queued animations + if (!elem->anim_queue.empty()) return true; + return false; +} + // --- Rendering --- // Renders all dirty tiles and returns a pointer to the main framebuffer. @@ -807,72 +1060,110 @@ const uint32_t* compositor_compose(compositor_t* comp) { // --- Blending Loop (with SIMD) --- int32_t x = blend_x0; -#if defined(__SSE2__) - // --- SSE2 Path (Processes 4 pixels at a time) --- +#if defined(__SSE2__) || (defined(_M_AMD64) || defined(_M_X64)) + // Pre-calculate fixed values for the SIMD loop + const __m128i v_zero = _mm_setzero_si128(); + const __m128i v_one = _mm_set1_epi16(1); + const __m128i v_255 = _mm_set1_epi16(255); + const __m128i v_elem_a_u8 = _mm_set1_epi16((short)elem_a_u8); + const __m128i v_opaque_mask = _mm_set1_epi32(0xFF000000); // For setting final alpha to 0xFF - // Calculate the end point for the 4-pixel chunks - int32_t simd_end = blend_x0 + ((blend_x1 - blend_x0) / 4) * 4; + // Determine the loop boundary for 4-pixel chunks + int32_t sse_limit = blend_x1 - (blend_x1 - x) % 4; - // Pre-calculate SSE constants - const __m128i zero = _mm_setzero_si128(); - const __m128i c255 = _mm_set1_epi16(255); - // Mask to force output alpha to 255 (0xFFFF in the 16-bit unpacked world) - const __m128i alpha_mask = _mm_set_epi16(0, 0, 0, 0xFFFF, 0, 0, 0, 0xFFFF); - // Element alpha broadcasted to 4 16-bit words - const __m128i elem_a_16 = _mm_set1_epi16(elem_a_u8); + for (; x < sse_limit; x += 4) { + // 1. Map destination x to source x (Need 4 values: x, x+1, x+2, x+3) + // This assumes inv_scale is pre-calculated. We must do the 4 lookups. + int32_t src_x0 = (int32_t)((x - elem_x0) * inv_scale); + int32_t src_x1 = (int32_t)(((x + 1) - elem_x0) * inv_scale); + int32_t src_x2 = (int32_t)(((x + 2) - elem_x0) * inv_scale); + int32_t src_x3 = (int32_t)(((x + 3) - elem_x0) * inv_scale); - for (; x < simd_end; x += 4) { - // 1. GATHER: Map 4 dest x-coords to 4 source x-coords - int32_t src_x0 = (int32_t)((x + 0 - elem_x0) * inv_scale); - int32_t src_x1 = (int32_t)((x + 1 - elem_x0) * inv_scale); - int32_t src_x2 = (int32_t)((x + 2 - elem_x0) * inv_scale); - int32_t src_x3 = (int32_t)((x + 3 - elem_x0) * inv_scale); + // 2. Sample 4 source pixels (ARGB format) + uint32_t src_pixels[4]; + src_pixels[0] = src_row[src_x0]; + src_pixels[1] = src_row[src_x1]; + src_pixels[2] = src_row[src_x2]; + src_pixels[3] = src_row[src_x3]; - // 2. LOAD: Load 4 source pixels and 4 destination pixels - __m128i src_px = _mm_set_epi32(src_row[src_x3], src_row[src_x2], src_row[src_x1], src_row[src_x0]); - __m128i dst_px = _mm_loadu_si128((__m128i*)(dst_row + x)); + __m128i v_src = _mm_loadu_si128((const __m128i*)src_pixels); + __m128i v_dst = _mm_loadu_si128((const __m128i*) & dst_row[x]); - // 3. UNPACK: Unpack 8-bit channels to 16-bit - __m128i src_lo = _mm_unpacklo_epi8(src_px, zero); // Pixels 0, 1 - __m128i src_hi = _mm_unpackhi_epi8(src_px, zero); // Pixels 2, 3 - __m128i dst_lo = _mm_unpacklo_epi8(dst_px, zero); - __m128i dst_hi = _mm_unpackhi_epi8(dst_px, zero); + // --- Unpack 4 pixels from 8-bit (ARGB) to 16-bit (A, R, G, B components) --- + // v_src_16bit_lo: A0 R0 G0 B0 | A1 R1 G1 B1 (packed 16-bit values, high bits zero) + // v_src_16bit_hi: A2 R2 G2 B2 | A3 R3 G3 B3 + __m128i v_src_16bit_lo = _mm_unpacklo_epi8(v_src, v_zero); + __m128i v_src_16bit_hi = _mm_unpackhi_epi8(v_src, v_zero); - // 4. GET ALPHA: Get pixel alpha (A) from [B,G,R,A] - // [A0,A0,A0,A0, A1,A1,A1,A1] - __m128i src_a_lo = _mm_unpacklo_epi64(_mm_shufflelo_epi16(src_lo, _MM_SHUFFLE(3, 3, 3, 3)), _mm_shufflehi_epi16(src_lo, _MM_SHUFFLE(3, 3, 3, 3))); - // [A2,A2,A2,A2, A3,A3,A3,A3] - __m128i src_a_hi = _mm_unpacklo_epi64(_mm_shufflelo_epi16(src_hi, _MM_SHUFFLE(3, 3, 3, 3)), _mm_shufflehi_epi16(src_hi, _MM_SHUFFLE(3, 3, 3, 3))); + __m128i v_dst_16bit_lo = _mm_unpacklo_epi8(v_dst, v_zero); + __m128i v_dst_16bit_hi = _mm_unpackhi_epi8(v_dst, v_zero); - // 5. MODULATE ALPHA: (pixel_alpha * elem_alpha) / 255 - src_a_lo = _mm_srli_epi16(_mm_mullo_epi16(src_a_lo, elem_a_16), 8); - src_a_hi = _mm_srli_epi16(_mm_mullo_epi16(src_a_hi, elem_a_16), 8); + // 3. Modulate alpha (ModAlpha = (SrcAlpha * ElemAlpha) / 255) + // Get Src Alpha + __m128i v_src_a_lo = _mm_shufflelo_epi16(v_src_16bit_lo, _MM_SHUFFLE(3, 3, 3, 3)); // A0 A0 A0 A0 | A1 A1 A1 A1 + v_src_a_lo = _mm_shufflehi_epi16(v_src_a_lo, _MM_SHUFFLE(3, 3, 3, 3)); + __m128i v_src_a_hi = _mm_shufflelo_epi16(v_src_16bit_hi, _MM_SHUFFLE(3, 3, 3, 3)); // A2 A2 A2 A2 | A3 A3 A3 A3 + v_src_a_hi = _mm_shufflehi_epi16(v_src_a_hi, _MM_SHUFFLE(3, 3, 3, 3)); - // 6. BLEND: (src * src_a) + (dst * (255 - src_a)) - __m128i inv_a_lo = _mm_sub_epi16(c255, src_a_lo); - __m128i inv_a_hi = _mm_sub_epi16(c255, src_a_hi); + // ModAlpha = SrcAlpha * ElemAlpha + __m128i v_mod_a_lo = _mm_mullo_epi16(v_src_a_lo, v_elem_a_u8); + __m128i v_mod_a_hi = _mm_mullo_epi16(v_src_a_hi, v_elem_a_u8); - __m128i src_scaled_lo = _mm_mullo_epi16(src_lo, src_a_lo); - __m128i src_scaled_hi = _mm_mullo_epi16(src_hi, src_a_hi); + // ModAlpha = (SrcAlpha * ElemAlpha + 255/2) / 255 (Simple division approximation) + // For / 255, we use a more accurate approximation or just accept the error. + // The scalar path uses (x / 255), we'll approximate with (x + 128) / 256 for a faster result. + // ModAlpha_approx = (v_mod_a + 128) >> 8 + __m128i v_mod_a_lo_div = _mm_add_epi16(v_mod_a_lo, _mm_set1_epi16(128)); + __m128i v_mod_a_hi_div = _mm_add_epi16(v_mod_a_hi, _mm_set1_epi16(128)); + __m128i v_mod_a_approx_lo = _mm_srli_epi16(v_mod_a_lo_div, 8); // Now contains ModAlpha_approx for 4 pixels (A0..A3) + __m128i v_mod_a_approx_hi = _mm_srli_epi16(v_mod_a_hi_div, 8); - __m128i dst_scaled_lo = _mm_mullo_epi16(dst_lo, inv_a_lo); - __m128i dst_scaled_hi = _mm_mullo_epi16(dst_hi, inv_a_hi); + // Replicate ModAlpha_approx for blending (A0 R0 G0 B0, A1 R1 G1 B1) + __m128i v_mod_a_lo_blend = _mm_unpacklo_epi16(v_mod_a_approx_lo, v_mod_a_approx_lo); + v_mod_a_lo_blend = _mm_unpacklo_epi32(v_mod_a_lo_blend, v_mod_a_lo_blend); // ModAlpha_0 for R0 G0 B0 + __m128i v_mod_a_hi_blend = _mm_unpacklo_epi16(v_mod_a_approx_hi, v_mod_a_approx_hi); + v_mod_a_hi_blend = _mm_unpacklo_epi32(v_mod_a_hi_blend, v_mod_a_hi_blend); // ModAlpha_2 for R2 G2 B2 - __m128i sum_lo = _mm_add_epi16(src_scaled_lo, dst_scaled_lo); - __m128i sum_hi = _mm_add_epi16(src_scaled_hi, dst_scaled_hi); + // Also need the alpha for the second pixel in each 64-bit lane + __m128i v_mod_a_lo_1 = _mm_unpackhi_epi16(v_mod_a_approx_lo, v_mod_a_approx_lo); + v_mod_a_lo_blend = _mm_unpackhi_epi64(v_mod_a_lo_blend, v_mod_a_lo_1); // A0 for R0 G0 B0 | A1 for R1 G1 B1 + __m128i v_mod_a_hi_1 = _mm_unpackhi_epi16(v_mod_a_approx_hi, v_mod_a_approx_hi); + v_mod_a_hi_blend = _mm_unpackhi_epi64(v_mod_a_hi_blend, v_mod_a_hi_1); // A2 for R2 G2 B2 | A3 for R3 G3 B3 - // Divide by 255 (by shifting right 8) - __m128i out_lo = _mm_srli_epi16(sum_lo, 8); - __m128i out_hi = _mm_srli_epi16(sum_hi, 8); + // ModAlpha_blend now contains: [A0 B0 G0 R0 | A1 B1 G1 R1] and [A2 B2 G2 R2 | A3 B3 G3 R3] (reversed order for convenience) + // It's crucial: the alpha value is replicated to the R, G, B positions of its corresponding pixel. - // 7. SET ALPHA: Force output alpha to 255 - out_lo = _mm_or_si128(out_lo, alpha_mask); - out_hi = _mm_or_si128(out_hi, alpha_mask); + // Calculate InvAlpha = 255 - ModAlpha + __m128i v_inv_a_lo_blend = _mm_sub_epi16(v_255, v_mod_a_lo_blend); + __m128i v_inv_a_hi_blend = _mm_sub_epi16(v_255, v_mod_a_hi_blend); - // 8. PACK & STORE: Pack 16-bit channels back to 8-bit - __m128i packed_out = _mm_packus_epi16(out_lo, out_hi); - _mm_storeu_si128((__m128i*)(dst_row + x), packed_out); + // 4. Blend: (Src * ModA + Dst * InvA) / 255 + // Src * ModA + __m128i v_src_x_mod_a_lo = _mm_mullo_epi16(_mm_andnot_si128(v_opaque_mask, v_src_16bit_lo), v_mod_a_lo_blend); + __m128i v_src_x_mod_a_hi = _mm_mullo_epi16(_mm_andnot_si128(v_opaque_mask, v_src_16bit_hi), v_mod_a_hi_blend); + + // Dst * InvA + __m128i v_dst_x_inv_a_lo = _mm_mullo_epi16(_mm_andnot_si128(v_opaque_mask, v_dst_16bit_lo), v_inv_a_lo_blend); + __m128i v_dst_x_inv_a_hi = _mm_mullo_epi16(_mm_andnot_si128(v_opaque_mask, v_dst_16bit_hi), v_inv_a_hi_blend); + + // Sum + __m128i v_sum_lo = _mm_add_epi16(v_src_x_mod_a_lo, v_dst_x_inv_a_lo); + __m128i v_sum_hi = _mm_add_epi16(v_src_x_mod_a_hi, v_dst_x_inv_a_hi); + + // Divide by 255: (v_sum + 128) / 256 (Fast approx) + __m128i v_out_16bit_lo = _mm_srli_epi16(_mm_add_epi16(v_sum_lo, _mm_set1_epi16(128)), 8); + __m128i v_out_16bit_hi = _mm_srli_epi16(_mm_add_epi16(v_sum_hi, _mm_set1_epi16(128)), 8); + + // Pack the results back to 8-bit, keeping the alpha from v_opaque_mask (0xFF) + __m128i v_out_8bit_packed = _mm_packus_epi16(v_out_16bit_lo, v_out_16bit_hi); + + // OR with the Opaque Alpha mask + __m128i v_final_out = _mm_or_si128(v_out_8bit_packed, v_opaque_mask); + + // Store the result + _mm_storeu_si128((__m128i*) & dst_row[x], v_final_out); } +#endif // --- Scalar Path (Processes remaining pixels 1 by 1) --- // This loop handles the "tail" on SSE2, or all pixels on ARM. for (; x < blend_x1; x++) { diff --git a/src/image.cpp b/src/image.cpp index d7e52f4..2aa628c 100644 --- a/src/image.cpp +++ b/src/image.cpp @@ -40,7 +40,7 @@ uint32_t* image_load_resized(const char* filename, int desired_width, int desire // Allocate buffer for resized image unsigned char* resized = (unsigned char*)malloc(desired_width * desired_height * 4); if (!resized) { - fprintf(stderr, "image.cpp 41: malloc failed!"); + fprintf(stderr, "image.cpp 43: malloc failed!"); return nullptr; } diff --git a/src/main.cpp b/src/main.cpp index 4fa803e..aa33416 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -1,8 +1,6 @@ #include #include #include -#include -#include #include #include #include @@ -10,527 +8,15 @@ #include #include -// ============================================================================ -// COMMAND SYSTEM -// ============================================================================ - -enum CommandType { - CMD_SHOW, - CMD_HIDE, - CMD_MOVE, - CMD_CHANGE_SPRITE, - CMD_SET_Z, - CMD_SET_SCALE, - CMD_SPEAK, - CMD_CLEAR, - CMD_WAIT, - CMD_BACKGROUND, -}; - -enum TransitionType { - TRANS_NONE, - TRANS_FADE, - TRANS_SLIDE_LEFT, - TRANS_SLIDE_RIGHT, -}; - -struct CmdShow { - const char* character; - const char* sprite; - int x, y, z; - TransitionType transition; - float duration_ms; -}; - -struct CmdHide { - const char* character; - TransitionType transition; - float duration_ms; -}; - -struct CmdMove { - const char* character; - int x, y; - TransitionType transition; - float duration_ms; -}; - -struct CmdChangeSprite { - const char* character; - const char* sprite; -}; - -struct CmdSetZ { - const char* character; - int z; -}; - -struct CmdSetScale { - const char* character; - float scale; - TransitionType transition; - float duration_ms; -}; - -struct CmdSpeak { - const char* speaker; - const char* text; - float text_speed; -}; - -struct CmdWait {}; -struct CmdClear {}; -struct CmdBackground { - const char* filename; - TransitionType transition; - float duration_ms; -}; - -struct Command { - CommandType type; - bool blocking; - - union { - CmdShow show; - CmdHide hide; - CmdMove move; - CmdChangeSprite change_sprite; - CmdSetZ set_z; - CmdSetScale set_scale; - CmdSpeak speak; - CmdWait wait; - CmdClear clear; - CmdBackground background; - }; -}; - -struct Scene { - const Command* commands; - int total_commands; - int current_command; - - void reset() { current_command = 0; } - bool has_next() const { return current_command < total_commands; } - const Command* current() const { - if (current_command < total_commands) return &commands[current_command]; - return nullptr; - } - void next() { - if (current_command < total_commands) current_command++; - } -}; - -constexpr Command Show(const char* character, const char* sprite, int x, int y, int z, - TransitionType trans = TRANS_NONE, float duration = 0.0f, bool blocking = true) -{ - return Command{ - .type = CMD_SHOW, - .blocking = blocking, - .show = CmdShow{character, sprite, x, y, z, trans, duration} - }; -} - -constexpr Command Hide(const char* character, TransitionType trans = TRANS_NONE, float duration = 0.0f, bool blocking = true) -{ - return Command{ - .type = CMD_HIDE, - .blocking = blocking, - .hide = CmdHide{character, trans, duration} - }; -} - -constexpr Command Speak(const char* speaker, const char* text, float text_speed = 30.0f, bool blocking = true) -{ - return Command{ - .type = CMD_SPEAK, - .blocking = blocking, - .speak = CmdSpeak{speaker, text, text_speed} - }; -} - -constexpr Command Wait(bool blocking = true) -{ - return Command{ .type = CMD_WAIT, .blocking = blocking, .wait = {} }; -} - -constexpr Command Clear(bool blocking = true) -{ - return Command{ .type = CMD_CLEAR, .blocking = blocking, .clear = {} }; -} - -constexpr Command SetZ(const char* character, int z, bool blocking = false) -{ - return Command{ .type = CMD_SET_Z, .blocking = blocking, .set_z = CmdSetZ{character, z} }; -} - -constexpr Command SetScale(const char* character, float scale, TransitionType trans = TRANS_NONE, float duration = 0.0f, bool blocking = false) -{ - return Command{ .type = CMD_SET_SCALE, .blocking = blocking, .set_scale = CmdSetScale{character, scale, trans, duration} }; -} - -// ============================================================================ -// ANIMATION TRACKER -// ============================================================================ - -struct ActiveAnimation { - float remaining_time; - - ActiveAnimation(float duration) : remaining_time(duration) {} -}; - -class AnimationTracker { -private: - std::vector active_animations; - -public: - void add_animation(float duration_sec) { - if (duration_sec > 0.0f) { - active_animations.push_back(ActiveAnimation(duration_sec)); - } - } - - void update(float delta_time) { - for (size_t i = 0; i < active_animations.size(); ) { - active_animations[i].remaining_time -= delta_time; - if (active_animations[i].remaining_time <= 0.0f) { - // Animation finished, remove it - active_animations.erase(active_animations.begin() + i); - } - else { - ++i; - } - } - } - - bool has_active_animations() const { - return !active_animations.empty(); - } - - void clear() { - active_animations.clear(); - } - - int count() const { - return (int)active_animations.size(); // it won't overflow.. right? - } -}; - -// ============================================================================ -// CHARACTER MANAGEMENT -// ============================================================================ - -struct SpriteElement { - uint32_t* image_data; - uint32_t element_id; - bool visible; -}; - -struct Character { - std::string name; - std::unordered_map sprites; - int width, height; - int current_x, current_y; - int current_z; - float current_scale; - std::string current_sprite; -}; - -class CharacterManager { -private: - std::unordered_map characters; - compositor_t* compositor; - int screen_width; - -public: - CharacterManager(compositor_t* comp, int screen_w) - : compositor(comp), screen_width(screen_w) { - } - - void add_character(const char* name, int width, int height) { - Character ch; - ch.name = name; - ch.width = width; - ch.height = height; - ch.current_x = 0; - ch.current_y = 0; - ch.current_z = 1; - ch.current_scale = 1.0f; - ch.current_sprite = ""; - characters[name] = ch; - } - - void load_sprite(const char* name, const char* emotion, const char* filename, - int width, int height, int initial_z = 1) { - auto it = characters.find(name); - if (it == characters.end()) return; - - uint32_t* img = image_load_resized(filename, width, height); - if (!img) return; - - SpriteElement elem; - elem.image_data = img; - elem.element_id = compositor_insert(compositor, img, width, height, 0, 0, initial_z); - elem.visible = false; - - compositor_set_alpha(compositor, elem.element_id, 0.0f); - compositor_set_visible(compositor, elem.element_id, false); - - it->second.sprites[emotion] = elem; - } - - Character* get_character(const char* name) { - auto it = characters.find(name); - if (it != characters.end()) return &it->second; - return nullptr; - } - - uint32_t get_active_element_id(Character* ch) { - if (!ch || ch->current_sprite.empty()) return 0; - auto it = ch->sprites.find(ch->current_sprite); - if (it == ch->sprites.end()) return 0; - return it->second.element_id; - } - - void show_character(const char* name, int x, int y, int z, const char* sprite_name, - TransitionType transition, float duration_ms) { - Character* ch = get_character(name); - if (!ch) return; - - for (auto& pair : ch->sprites) { - if (pair.first != sprite_name && pair.second.visible) { - compositor_set_visible(compositor, pair.second.element_id, false); - compositor_set_alpha(compositor, pair.second.element_id, 0.0f); - pair.second.visible = false; - } - } - - auto it = ch->sprites.find(sprite_name); - if (it == ch->sprites.end()) return; - - SpriteElement& elem = it->second; - float duration_sec = duration_ms / 1000.0f; - - ch->current_x = x; - ch->current_y = y; - ch->current_z = z; - ch->current_sprite = sprite_name; - elem.visible = true; - - compositor_set_zorder(compositor, elem.element_id, z); - - if (transition == TRANS_FADE) { - compositor_set_position(compositor, elem.element_id, x, y); - compositor_set_visible(compositor, elem.element_id, true); - compositor_animate_fade(compositor, elem.element_id, 1.0f, duration_sec, EASE_LINEAR); - } - else if (transition == TRANS_SLIDE_LEFT) { - int start_x = -ch->width; - compositor_set_position(compositor, elem.element_id, start_x, y); - compositor_set_alpha(compositor, elem.element_id, 1.0f); - compositor_set_visible(compositor, elem.element_id, true); - compositor_animate_move(compositor, elem.element_id, x, y, duration_sec, EASE_OUT_CUBIC); - } - else if (transition == TRANS_SLIDE_RIGHT) { - int start_x = screen_width; - compositor_set_position(compositor, elem.element_id, start_x, y); - compositor_set_alpha(compositor, elem.element_id, 1.0f); - compositor_set_visible(compositor, elem.element_id, true); - compositor_animate_move(compositor, elem.element_id, x, y, duration_sec, EASE_OUT_CUBIC); - } - else { - compositor_set_position(compositor, elem.element_id, x, y); - compositor_set_alpha(compositor, elem.element_id, 1.0f); - compositor_set_visible(compositor, elem.element_id, true); - } - } - - void hide_character(const char* name, TransitionType transition, float duration_ms) { - Character* ch = get_character(name); - if (!ch) return; - - float duration_sec = duration_ms / 1000.0f; - - for (auto& pair : ch->sprites) { - if (pair.second.visible) { - if (transition == TRANS_FADE) { - compositor_animate_fade(compositor, pair.second.element_id, 0.0f, duration_sec, EASE_LINEAR); - } - else { - compositor_set_visible(compositor, pair.second.element_id, false); - compositor_set_alpha(compositor, pair.second.element_id, 0.0f); - } - pair.second.visible = false; - } - } - ch->current_sprite = ""; - } - - void move_character(const char* name, int x, int y, TransitionType transition, float duration_ms) { - Character* ch = get_character(name); - uint32_t id = get_active_element_id(ch); - if (id == 0) return; - - ch->current_x = x; - ch->current_y = y; - float duration_sec = duration_ms / 1000.0f; - - if (transition == TRANS_SLIDE_LEFT || transition == TRANS_SLIDE_RIGHT) { - compositor_animate_move(compositor, id, x, y, duration_sec, EASE_IN_OUT_QUAD); - } - else { - compositor_set_position(compositor, id, x, y); - } - } - - void change_sprite(const char* name, const char* new_emotion) { - Character* ch = get_character(name); - if (!ch) return; - - if (!ch->current_sprite.empty()) { - auto old_it = ch->sprites.find(ch->current_sprite); - if (old_it != ch->sprites.end()) { - compositor_set_visible(compositor, old_it->second.element_id, false); - compositor_set_alpha(compositor, old_it->second.element_id, 0.0f); - old_it->second.visible = false; - } - } - - auto new_it = ch->sprites.find(new_emotion); - if (new_it != ch->sprites.end()) { - compositor_set_position(compositor, new_it->second.element_id, - ch->current_x, ch->current_y); - compositor_set_zorder(compositor, new_it->second.element_id, ch->current_z); - compositor_set_alpha(compositor, new_it->second.element_id, 1.0f); - compositor_set_visible(compositor, new_it->second.element_id, true); - new_it->second.visible = true; - ch->current_sprite = new_emotion; - } - } - - void set_z(const char* name, int z) { - Character* ch = get_character(name); - uint32_t id = get_active_element_id(ch); - if (id == 0) return; - - ch->current_z = z; - compositor_set_zorder(compositor, id, z); - } - - void set_scale(const char* name, float scale, TransitionType transition, float duration_ms) { - Character* ch = get_character(name); - uint32_t id = get_active_element_id(ch); - if (id == 0) return; - - ch->current_scale = scale; - float duration_sec = duration_ms / 1000.0f; - - if (transition == TRANS_FADE) { - compositor_animate_scale(compositor, id, scale, duration_sec, EASE_IN_OUT_QUAD); - } - else { - compositor_set_scale(compositor, id, scale); - } - } - - ~CharacterManager() { - for (auto& pair : characters) { - for (auto& sprite : pair.second.sprites) { - image_free(sprite.second.image_data); - } - } - } -}; - -// ============================================================================ -// COMMAND EXECUTOR -// ============================================================================ - -class CommandExecutor { -private: - CharacterManager* char_manager; - compositor_t* compositor; - text_t* name_text; - text_t* dialogue_text; - uint32_t name_id; - uint32_t dialogue_id; - -public: - CommandExecutor(CharacterManager* cm, compositor_t* comp, - text_t* nt, text_t* dt, uint32_t nid, uint32_t did) - : char_manager(cm), compositor(comp), name_text(nt), dialogue_text(dt), - name_id(nid), dialogue_id(did) { - } - - float execute(const Command* cmd) { - switch (cmd->type) { - case CMD_SHOW: - char_manager->show_character(cmd->show.character, cmd->show.x, cmd->show.y, - cmd->show.z, cmd->show.sprite, - cmd->show.transition, cmd->show.duration_ms); - return cmd->show.duration_ms / 1000.0f; - - case CMD_HIDE: - char_manager->hide_character(cmd->hide.character, - cmd->hide.transition, cmd->hide.duration_ms); - return cmd->hide.duration_ms / 1000.0f; - - case CMD_MOVE: - char_manager->move_character(cmd->move.character, cmd->move.x, cmd->move.y, - cmd->move.transition, cmd->move.duration_ms); - return cmd->move.duration_ms / 1000.0f; - - case CMD_CHANGE_SPRITE: - char_manager->change_sprite(cmd->change_sprite.character, cmd->change_sprite.sprite); - break; - - case CMD_SET_Z: - char_manager->set_z(cmd->set_z.character, cmd->set_z.z); - break; - - case CMD_SET_SCALE: - char_manager->set_scale(cmd->set_scale.character, cmd->set_scale.scale, - cmd->set_scale.transition, cmd->set_scale.duration_ms); - return cmd->set_scale.duration_ms / 1000.0f; - - case CMD_SPEAK: - if (cmd->speak.speaker) { - text_push(name_text, cmd->speak.speaker, 34.0f, 0.0f, "assets/Vera.ttf", true); - } - if (cmd->speak.text) { - text_push(dialogue_text, cmd->speak.text, 24.0f, cmd->speak.text_speed, "assets/Vera.ttf", true); - } - compositor_mark_content_dirty(compositor, name_id); - compositor_mark_content_dirty(compositor, dialogue_id); - break; - - case CMD_CLEAR: - text_clear(name_text); - text_clear(dialogue_text); - break; - - case CMD_WAIT: - case CMD_BACKGROUND: - break; - } - return 0.0f; - } -}; - -// ============================================================================ -// MAIN PROGRAM -// ============================================================================ - -enum AppState { - STATE_IDLE, - STATE_ANIMATING, - STATE_WAITING_FOR_INPUT -}; - int main() { const int WIDTH = 1280; const int HEIGHT = 720; + const int CHAR_WIDTH = 400; + const int CHAR_HEIGHT = 600; + const int CHAR_Y_OFFSET = 140; + const int POS_Y = HEIGHT - CHAR_HEIGHT - CHAR_Y_OFFSET; - window_t* window = window_create(WIDTH, HEIGHT, "Visual Novel Engine"); + window_t* window = window_create(WIDTH, HEIGHT, u8"The Obwarzanek Narrative Engine"); if (!window) { printf("Failed to create window\n"); return 1; @@ -549,198 +35,56 @@ int main() { compositor_set_alpha(compositor, bg_id, 1.0f); } - const int CHAR_WIDTH = 400; - const int CHAR_HEIGHT = 600; - const int CHAR_Y_OFFSET = 140; - const int POS_Y = HEIGHT - CHAR_HEIGHT - CHAR_Y_OFFSET; - - CharacterManager char_manager(compositor, WIDTH); - - char_manager.add_character("maksw", CHAR_WIDTH, CHAR_HEIGHT); - char_manager.add_character("wikor", CHAR_WIDTH, CHAR_HEIGHT); - - char_manager.load_sprite("maksw", "neutral", "assets/maksw.png", CHAR_WIDTH, CHAR_HEIGHT); - char_manager.load_sprite("wikor", "neutral", "assets/wikor.png", CHAR_WIDTH, CHAR_HEIGHT); - uint32_t* textbox = image_load_resized("assets/textbox.png", WIDTH, 180); if (textbox) { - uint32_t box_id = compositor_insert(compositor, textbox, WIDTH, 180, 0, HEIGHT - 180, 3); - compositor_set_alpha(compositor, box_id, 1.0f); + compositor_insert(compositor, textbox, WIDTH, 180, 0, HEIGHT - 180, 3); } - text_t* name_text = text_init(WIDTH, HEIGHT); + uint32_t* maksw_happy = image_load_resized("assets/maksw.png", CHAR_WIDTH, CHAR_HEIGHT); + uint32_t maksw_happy_id = compositor_insert(compositor, maksw_happy, + CHAR_WIDTH, CHAR_HEIGHT, -600, POS_Y, 1); + + uint32_t* maksw_neutral = image_load_resized("assets/maksw2.png", CHAR_WIDTH, CHAR_HEIGHT); + uint32_t maksw_neutral_id = compositor_insert(compositor, maksw_neutral, + CHAR_WIDTH, CHAR_HEIGHT, 60, POS_Y, 1); + compositor_set_visible(compositor, maksw_neutral_id, false); + + uint32_t* wikor = image_load_resized("assets/wikor.png", CHAR_WIDTH, CHAR_HEIGHT); + uint32_t wikor_id = compositor_insert(compositor, wikor, + CHAR_WIDTH, CHAR_HEIGHT, WIDTH - CHAR_WIDTH - 60, POS_Y, 2); + + text_t* name_text = text_init(300, 100); + text_set_font(name_text, "assets/Aller_Rg.ttf", 40.0f); uint32_t name_id = compositor_insert(compositor, text_getfb(name_text), - WIDTH, HEIGHT, 60, HEIGHT - 170, 4); - compositor_set_alpha(compositor, name_id, 1.0f); + 300, 100, 60, HEIGHT - 180, 4); - text_t* dialogue_text = text_init_shared(name_text, WIDTH - 140, HEIGHT); + text_t* dialogue_text = text_init(WIDTH - 140, 160); + text_set_font(dialogue_text, "assets/Vera.ttf", 30.0f); uint32_t dialogue_id = compositor_insert(compositor, text_getfb(dialogue_text), - WIDTH - 140, HEIGHT, 60, HEIGHT - 130, 4); - compositor_set_alpha(compositor, dialogue_id, 1.0f); + WIDTH - 140, 160, 60, HEIGHT - 130, 4); - // --- Scene Script --- - Command scene_commands[] = { - // entrances - Show("maksw", "neutral", 100, POS_Y, 2, TRANS_SLIDE_LEFT, 500.0f, false), - Show("wikor", "neutral", 800, POS_Y, 1, TRANS_SLIDE_RIGHT, 500.0f, true), + uint32_t* black = (uint32_t*)malloc(WIDTH * HEIGHT * sizeof(uint32_t)); + for (int i = 0; i < WIDTH * HEIGHT; i++) { + black[i] = 0xFF000000; + } + uint32_t black_id = compositor_insert(compositor, black, + WIDTH, HEIGHT, 0, 0, 5); + compositor_set_alpha(compositor, black_id, 0.0f); + compositor_set_visible(compositor, black_id, false); - // wilczur starts (maksw) - SetScale("maksw", 1.05f, TRANS_FADE, 200.0f), - SetZ("maksw", 3), - SetScale("wikor", 1.0f, TRANS_FADE, 200.0f), - SetZ("wikor", 1), - - Speak(u8"wilczur", u8"Long day, huh? Feels like we ran a marathon we didn't sign up for."), - Wait(), - Clear(), - - Speak(u8"wilczur", u8"I thought the report would close this morning. Guess not."), - Wait(), - Clear(), - - // wiktor responds (wikor) - SetScale("wikor", 1.05f, TRANS_FADE, 200.0f), - SetZ("wikor", 3), - SetScale("maksw", 1.0f, TRANS_FADE, 200.0f), - SetZ("maksw", 1), - - Speak(u8"admirał wiktorov", u8"You always underestimate bureaucracy's appetite for time."), - Wait(), - Clear(), - - Speak(u8"admirał wiktorov", u8"It eats minutes like some folks eat sweets."), - Wait(), - Clear(), - - // narrator (reset both scales) - //SetScale("maksw", 1.0f, TRANS_FADE, 150.0f), - //SetZ("maksw", 1), - SetScale("wikor", 1.0f, TRANS_FADE, 150.0f), - SetZ("wikor", 1), - - Speak(u8"", u8"The lights in the corridor hummed, steady and indifferent."), - Wait(), - Clear(), - - // wilczur again - SetScale("maksw", 1.05f, TRANS_FADE, 200.0f), - SetZ("maksw", 3), - //SetScale("wikor", 1.0f, TRANS_FADE, 200.0f), - //SetZ("wikor", 1), - - Speak(u8"wilczur", u8"I keep telling myself 'five minutes' and then three episodes later it's midnight."), - Wait(), - Clear(), - - Speak(u8"wilczur", u8"Maybe I'm cursed to wait in dim rooms forever."), - Wait(), - Clear(), - - // wiktor - SetScale("wikor", 1.05f, TRANS_FADE, 200.0f), - SetZ("wikor", 3), - SetScale("maksw", 1.0f, TRANS_FADE, 200.0f), - SetZ("maksw", 1), - - Speak(u8"admirał wiktorov", u8"Or blessed with patience you haven't earned yet."), - Wait(), - Clear(), - - Speak(u8"admirał wiktorov", u8"Either way, keep the coffee coming and nobody gets dramatic."), - Wait(), - Clear(), - - // narrator - //SetScale("maksw", 1.0f, TRANS_FADE, 150.0f), - //SetZ("maksw", 1), - SetScale("wikor", 1.0f, TRANS_FADE, 150.0f), - SetZ("wikor", 1), - - Speak(u8"", u8"A faint orange band slid behind the clouds like a secret smile."), - Wait(), - Clear(), - - // wilczur - SetScale("maksw", 1.05f, TRANS_FADE, 200.0f), - SetZ("maksw", 3), - //SetScale("wikor", 1.0f, TRANS_FADE, 200.0f), - //SetZ("wikor", 1), - - Speak(u8"wilczur", u8"Did you see that? A flare, or the heavens showing off."), - Wait(), - Clear(), - - Speak(u8"wilczur", u8"If it's a flare, somebody's throwing a party I wasn't invited to."), - Wait(), - Clear(), - - // wiktor - SetScale("wikor", 1.05f, TRANS_FADE, 200.0f), - SetZ("wikor", 3), - SetScale("maksw", 1.0f, TRANS_FADE, 200.0f), - SetZ("maksw", 1), - - Speak(u8"admirał wiktorov", u8"Not every bright thing is celebration. Could be dust, reflection."), - Wait(), - Clear(), - - Speak(u8"admirał wiktorov", u8"But reflection's a good guess — keeps us calmer than panic does."), - Wait(), - Clear(), - - // narrator - //SetScale("maksw", 1.0f, TRANS_FADE, 150.0f), - //SetZ("maksw", 1), - SetScale("wikor", 1.0f, TRANS_FADE, 150.0f), - SetZ("wikor", 1), - - Speak(u8"", u8"The distant rumble that followed was more felt than heard."), - Wait(), - Clear(), - - // final small exchange - SetScale("maksw", 1.05f, TRANS_FADE, 200.0f), - SetZ("maksw", 3), - //SetScale("wikor", 1.0f, TRANS_FADE, 200.0f), - //SetZ("wikor", 1), - - Speak(u8"wilczur", u8"If the sky's putting on a show, at least the scenery's decent."), - Wait(), - Clear(), - - Speak(u8"wilczur", u8"Make a note: next time, bring proper snacks."), - Wait(), - Clear(), - - SetScale("wikor", 1.05f, TRANS_FADE, 200.0f), - SetZ("wikor", 3), - SetScale("maksw", 1.0f, TRANS_FADE, 200.0f), - SetZ("maksw", 1), - - Speak(u8"admirał wiktorov", u8"Noted. I'll smuggle provisions like a cunning admiral."), - Wait(), - Clear(), - - Speak(u8"admirał wiktorov", u8"Till then, keep an eye on the horizon and your soda stash safe."), - Wait(), - Clear(), - - // exits - Hide("maksw", TRANS_FADE, 500.0f, false), - Hide("wikor", TRANS_FADE, 500.0f, true), - }; - - Scene scene = { scene_commands, sizeof(scene_commands) / sizeof(Command), 0 }; - CommandExecutor executor(&char_manager, compositor, name_text, dialogue_text, - name_id, dialogue_id); + compositor_animate_move(compositor, maksw_happy_id, 60, POS_Y, 1.0f, EASE_OUT_CUBIC); + 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, + "Hey there! Welcome to the Obwarzanek Narrative Engine demo. " + "This is a simple visual novel engine showcasing text rendering, " + "image compositing, and basic animation features.", + 30.0f, true); bool running = true; - AppState current_state = STATE_IDLE; - AnimationTracker anim_tracker; // Track multiple animations - 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; float frame_time_sum = 0.0f; @@ -777,91 +121,92 @@ int main() { last_report_time = current_time; // Mark the current time as the new report start } - // Update animation tracker - anim_tracker.update(delta_time_sec); + static bool mouse_was_down = false; + bool mouse_is_down = window_is_mouse_button_down(window, MOUSE_BUTTON_LEFT); + bool mouse_clicked = mouse_is_down && !mouse_was_down; + mouse_was_down = mouse_is_down; - static bool mouse_was_down = false; - bool mouse_is_down = window_is_mouse_button_down(window, MOUSE_BUTTON_LEFT); - bool mouse_clicked = mouse_is_down && !mouse_was_down; - mouse_was_down = mouse_is_down; + static bool key_was_down = false; + bool key_is_down = window_is_key_down(window, KEY_SPACE); + bool key_pressed = key_is_down && !key_was_down; + key_was_down = key_is_down; - static bool key_was_down = false; - bool key_is_down = window_is_key_down(window, KEY_SPACE); - bool key_pressed = key_is_down && !key_was_down; - key_was_down = key_is_down; + static unsigned int lines = 0; + static bool dialog_finished = false; - bool advance_input = mouse_clicked || key_pressed; - - // Check if animations finished - if (current_state == STATE_ANIMATING) { - if (!anim_tracker.has_active_animations()) { - current_state = STATE_IDLE; - } - } - - if (advance_input) { + if (mouse_clicked || key_pressed) { if (text_is_animating(dialogue_text)) { text_show_all_current(dialogue_text); } - else if (current_state == STATE_WAITING_FOR_INPUT) { - current_state = STATE_IDLE; - } - else if (current_state == STATE_ANIMATING) { - // Skip all remaining animations - anim_tracker.clear(); - current_state = STATE_IDLE; - } - } - - // Process commands if idle - while (current_state == STATE_IDLE) { - if (!scene.has_next()) { - running = false; - break; - } - - const Command* cmd = scene.current(); - float duration = executor.execute(cmd); - - // NEW: Add animation to tracker - if (duration > 0.0f) { - anim_tracker.add_animation(duration); - } - - scene.next(); - - // Check blocking flag - if (cmd->blocking) { - if (cmd->type == CMD_WAIT) { - current_state = STATE_WAITING_FOR_INPUT; + else { + if (lines == 0) { + // line 2 + compositor_animation_complete_all(compositor); + compositor_animate_scale(compositor, maksw_happy_id, 1.0f, 0.2f, EASE_IN_OUT_CUBIC); + compositor_animate_scale(compositor, wikor_id, 1.1f, 0.3f, EASE_IN_OUT_CUBIC); + text_clear(dialogue_text); + text_clear(name_text); + text_push(name_text, "wikor", 0.0f, true); + text_push(dialogue_text, "Hello to you too, maksw..", 30.0f, true); + lines++; + } + else if (lines == 1) { + // line 3 + compositor_animation_complete_all(compositor); + compositor_animate_scale(compositor, wikor_id, 1.0f, 0.2f, EASE_IN_OUT_CUBIC); + compositor_set_visible(compositor, maksw_happy_id, false); + compositor_set_visible(compositor, maksw_neutral_id, true); + compositor_animate_scale(compositor, maksw_neutral_id, 1.05f, 0.3f, EASE_IN_OUT_CUBIC); + text_clear(dialogue_text); + text_clear(name_text); + text_push(name_text, "maksw", 0.0f, true); + text_push(dialogue_text, "why so.. serious?", 30.0f, true); + lines++; + } + else if (lines == 2) { + // fade out + compositor_animation_complete_all(compositor); + compositor_animate_scale(compositor, maksw_neutral_id, 1.0f, 0.2f, EASE_IN_OUT_CUBIC); + compositor_set_visible(compositor, black_id, true); + compositor_animate_fade(compositor, black_id, 1.0f, 1.5f, EASE_IN_OUT_CUBIC); + dialog_finished = true; + lines++; + } + else { break; } - else if (anim_tracker.has_active_animations()) { - current_state = STATE_ANIMATING; - break; - } - // If blocking but no animations, continue to next command } - // If non-blocking, loop continues immediately to next command } compositor_tick(compositor, delta_time_sec); - text_nextframe(name_text, delta_time_sec); - text_nextframe(dialogue_text, delta_time_sec); - compositor_mark_content_dirty(compositor, name_id); - compositor_mark_content_dirty(compositor, dialogue_id); + if (dialog_finished) { + compositor_mark_content_dirty(compositor, black_id); + if (compositor_is_element_animating(compositor, black_id) == false) { + running = false; + } + } + if (text_nextframe(name_text, delta_time_sec)) + compositor_mark_content_dirty(compositor, name_id); + if (text_nextframe(dialogue_text, delta_time_sec)) + compositor_mark_content_dirty(compositor, dialogue_id); const uint32_t* fb = compositor_compose(compositor); window_blit(window, fb); } + if (maksw_happy) image_free(maksw_happy); + if (maksw_neutral) image_free(maksw_neutral); + if (wikor) image_free(wikor); + + if (black) free(black); if (bg) image_free(bg); if (textbox) image_free(textbox); + text_destroy(name_text); text_destroy(dialogue_text); compositor_destroy(compositor); window_destroy(window); return 0; -} \ No newline at end of file +} diff --git a/src/text.cpp b/src/text.cpp index ce2d5b3..dd9fbb2 100644 --- a/src/text.cpp +++ b/src/text.cpp @@ -1,6 +1,3 @@ -#include - -// C/C++ Standard Libraries #include #include #include @@ -11,9 +8,17 @@ #include #include #include -#include // For std::clamp +#include +#include + +// SSE2 intrinsics for x86/x64 +#if defined(__x86_64__) || defined(_M_X64) || defined(__i386__) || defined(_M_IX86) +#define USE_SSE2 1 +#include +#else +#define USE_SSE2 0 +#endif -// Embed stb_truetype implementation #define STB_TRUETYPE_IMPLEMENTATION #include "stb_truetype.h" @@ -157,6 +162,10 @@ struct text_t { shared_cache_t* cache; bool owns_cache; // True if this text_t owns the cache and should delete it + // Default font settings + std::string default_font_path; + float default_font_size = 16.0f; + // Rendering State (per-instance) std::vector text_queue; int queue_index = 0; @@ -445,6 +454,61 @@ static uint32_t alpha_blend(uint32_t dest, uint32_t src) { return (out_a << 24) | (out_r << 16) | (out_g << 8) | out_b; } +#if USE_SSE2 +// SSE2-accelerated alpha blending for 4 pixels at once +static inline void alpha_blend_sse2(uint32_t* dest, __m128i src_pixels, __m128i src_rgb_mask) { + // Load 4 destination pixels + __m128i dest_pixels = _mm_loadu_si128((__m128i*)dest); + + // Extract alpha channels + __m128i src_a = _mm_srli_epi32(src_pixels, 24); + __m128i dest_a = _mm_srli_epi32(dest_pixels, 24); + + // Check for trivial cases per pixel would be complex, so we just blend + // For optimization, you could check if all alphas are 0 or 255 + + // Unpack to 16-bit for precision + __m128i zero = _mm_setzero_si128(); + + // Extract RGBA components (low 8 pixels as 16-bit) + __m128i src_lo = _mm_unpacklo_epi8(src_pixels, zero); + __m128i src_hi = _mm_unpackhi_epi8(src_pixels, zero); + __m128i dest_lo = _mm_unpacklo_epi8(dest_pixels, zero); + __m128i dest_hi = _mm_unpackhi_epi8(dest_pixels, zero); + + // Replicate alpha to all channels + __m128i src_a_lo = _mm_shufflelo_epi16(src_lo, _MM_SHUFFLE(3, 3, 3, 3)); + src_a_lo = _mm_shufflehi_epi16(src_a_lo, _MM_SHUFFLE(3, 3, 3, 3)); + __m128i src_a_hi = _mm_shufflelo_epi16(src_hi, _MM_SHUFFLE(3, 3, 3, 3)); + src_a_hi = _mm_shufflehi_epi16(src_a_hi, _MM_SHUFFLE(3, 3, 3, 3)); + + // Compute: src * src_a + __m128i src_mul_lo = _mm_mullo_epi16(src_lo, src_a_lo); + __m128i src_mul_hi = _mm_mullo_epi16(src_hi, src_a_hi); + + // Compute: 255 - src_a + __m128i inv_src_a_lo = _mm_sub_epi16(_mm_set1_epi16(255), src_a_lo); + __m128i inv_src_a_hi = _mm_sub_epi16(_mm_set1_epi16(255), src_a_hi); + + // Compute: dest * (255 - src_a) + __m128i dest_mul_lo = _mm_mullo_epi16(dest_lo, inv_src_a_lo); + __m128i dest_mul_hi = _mm_mullo_epi16(dest_hi, inv_src_a_hi); + + // Add and divide by 255 (approximate with >> 8) + __m128i result_lo = _mm_add_epi16(src_mul_lo, dest_mul_lo); + __m128i result_hi = _mm_add_epi16(src_mul_hi, dest_mul_hi); + + result_lo = _mm_srli_epi16(result_lo, 8); + result_hi = _mm_srli_epi16(result_hi, 8); + + // Pack back to 8-bit + __m128i result = _mm_packus_epi16(result_lo, result_hi); + + // Store result + _mm_storeu_si128((__m128i*)dest, result); +} +#endif + // Draws a glyph using its cached SDF bitmap. static void draw_glyph(text_t* text, int x, int y, @@ -461,16 +525,14 @@ static void draw_glyph(text_t* text, int y1 = y + glyph->h; // --- SDF Parameters --- - // These must match the values used in get_glyph const float padding_percent = 0.15f; - const float onedge_value = 128.0f; // 0-255 range midpoint + const float onedge_value = 128.0f; float padding = std::max(1.0f, size * padding_percent); float pixel_dist_scale = onedge_value / padding; // ---------------------- - // Calculate outline width and anti-alias smoothing float outline_width = std::max(1.0f, size * 0.05f); - float aa_width = 0.75f; // Anti-aliasing width in pixels + float aa_width = 0.75f; // Clip to framebuffer int gx0 = (x0 < 0) ? -x0 : 0; @@ -481,371 +543,399 @@ static void draw_glyph(text_t* text, uint32_t color_rgb = color & 0x00FFFFFF; for (int gy = gy0; gy < gy1; ++gy) { + int fb_y = y0 + gy; + uint32_t* dest_row = &text->framebuffer[fb_y * text->width + x0]; + +#if USE_SSE2 + // Process 4 pixels at a time with SSE2 + int gx = gx0; + __m128i color_rgb_vec = _mm_set1_epi32(color_rgb); + + for (; gx + 3 < gx1; gx += 4) { + // Process 4 pixels + uint8_t sdf_vals[4]; + float alphas[4]; + + for (int i = 0; i < 4; i++) { + sdf_vals[i] = glyph->data[gy * glyph->w + (gx + i)]; + float dist = (sdf_vals[i] - onedge_value) / pixel_dist_scale; + + if (outline) { + float edge_inner_0 = -aa_width; + float edge_inner_1 = +aa_width; + float mask_inner = smoothstep(edge_inner_0, edge_inner_1, dist); + + float edge_outer_0 = -outline_width - aa_width; + float edge_outer_1 = -outline_width + aa_width; + float mask_outer = smoothstep(edge_outer_0, edge_outer_1, dist); + + alphas[i] = std::clamp(mask_outer - mask_inner, 0.0f, 1.0f); + } else { + alphas[i] = smoothstep(-aa_width, aa_width, dist); + } + } + + // Build source pixels + uint32_t src_pixels[4]; + bool any_visible = false; + for (int i = 0; i < 4; i++) { + uint32_t alpha_byte = (uint32_t)(alphas[i] * 255.0f); + src_pixels[i] = (alpha_byte << 24) | color_rgb; + if (alpha_byte > 0) any_visible = true; + } + + if (any_visible) { + __m128i src_vec = _mm_loadu_si128((__m128i*)src_pixels); + alpha_blend_sse2(&dest_row[gx], src_vec, color_rgb_vec); + } + } + + // Handle remaining pixels + for (; gx < gx1; ++gx) { +#else for (int gx = gx0; gx < gx1; ++gx) { - - // 1. Get SDF value (0-255) +#endif uint8_t sdf_raw = glyph->data[gy * glyph->w + gx]; - - // 2. Convert to signed pixel distance (Value > 0 is INSIDE, < 0 is OUTSIDE) float dist = (sdf_raw - onedge_value) / pixel_dist_scale; float alpha = 0.0f; if (outline) { - // Render the outline - // 1. Inner mask (the fill) float edge_inner_0 = -aa_width; float edge_inner_1 = +aa_width; float mask_inner = smoothstep(edge_inner_0, edge_inner_1, dist); - // 2. Outer mask (the fill + outline) 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); alpha = std::clamp(mask_outer - mask_inner, 0.0f, 1.0f); - - } - else { - // Render the fill - // Use smoothstep for anti-aliasing around the 0-edge + } else { alpha = smoothstep(-aa_width, aa_width, dist); } if (alpha == 0.0f) continue; int fb_x = x0 + gx; - int fb_y = y0 + gy; - uint32_t* dest_pixel = &text->framebuffer[fb_y * text->width + fb_x]; - uint32_t src_pixel = ((uint32_t)(alpha * 255.0f) << 24) | color_rgb; + uint32_t src_pixel = ((uint32_t)(alpha * 255.0f) << 24) | color_rgb; *dest_pixel = alpha_blend(*dest_pixel, src_pixel); } } } - // --- Public C-Style API --- extern "C" { - // --- Lifecycle --- +// --- Lifecycle --- - text_t* text_init(int width, int height) { - if (width <= 0 || height <= 0) return nullptr; +text_t* text_init(int width, int height) { + if (width <= 0 || height <= 0) return nullptr; - text_t* text = new (std::nothrow) text_t; - if (!text) return nullptr; + text_t* text = new (std::nothrow) text_t; + if (!text) return nullptr; - text->width = width; - text->height = height; - - text->framebuffer = new (std::nothrow) uint32_t[width * height]; - if (!text->framebuffer) { - delete text; - return nullptr; - } - - // Create new cache that this text_t owns - text->cache = new (std::nothrow) shared_cache_t; - if (!text->cache) { - delete[] text->framebuffer; - delete text; - return nullptr; - } - text->owns_cache = true; - - text->queue_index = 0; - text->char_index = 0; - text->char_progress = 0.0f; - text->current_font = nullptr; - text->last_line_height = 0.0f; - - text->current_x = text->padding_x; - text->current_y = text->padding_y; - - text_clear(text); - return text; - } - - text_t* text_init_shared(text_t* source, int width, int height) { - if (width <= 0 || height <= 0 || !source || !source->cache) return nullptr; - - text_t* text = new (std::nothrow) text_t; - if (!text) return nullptr; - - text->width = width; - text->height = height; - - text->framebuffer = new (std::nothrow) uint32_t[width * height]; - if (!text->framebuffer) { - delete text; - return nullptr; - } - - // Share the cache from source - text->cache = source->cache; - text->cache->refcount++; - text->owns_cache = false; - - text->queue_index = 0; - text->char_index = 0; - text->char_progress = 0.0f; - text->current_font = nullptr; - text->last_line_height = 0.0f; - - text->current_x = text->padding_x; - text->current_y = text->padding_y; - - text_clear(text); - return text; - } - - void text_destroy(text_t* text) { - if (!text) return; - - delete[] text->framebuffer; - - // Handle cache cleanup with reference counting - if (text->cache) { - text->cache->refcount--; - if (text->cache->refcount == 0) { - delete text->cache; - } - } + text->width = width; + text->height = height; + text->framebuffer = new (std::nothrow) uint32_t[width * height]; + if (!text->framebuffer) { delete text; + return nullptr; } - // --- Text Queue & State --- + // Create new cache that this text_t owns + text->cache = new (std::nothrow) shared_cache_t; + if (!text->cache) { + delete[] text->framebuffer; + delete text; + return nullptr; + } + text->owns_cache = true; - // Adds a block of text to the rendering queue. - void text_push(text_t* text, - const char* str, - float size, - float speed, - const char* font_path, - bool outline) - { - if (!text || !str) return; + text->queue_index = 0; + text->char_index = 0; + text->char_progress = 0.0f; + text->current_font = nullptr; + text->last_line_height = 0.0f; - text_params_t p; - p.text = str; - p.size = std::max(1.0f, size); - p.speed = speed; - p.outline = outline; - p.font_path = (font_path ? font_path : ""); - p.lines_computed = false; // Mark for precomputation + text->current_x = text->padding_x; + text->current_y = text->padding_y; - text->text_queue.push_back(std::move(p)); + text_clear(text); + return text; +} + +text_t* text_init_shared(text_t* source, int width, int height) { + if (width <= 0 || height <= 0 || !source || !source->cache) return nullptr; + + text_t* text = new (std::nothrow) text_t; + if (!text) return nullptr; + + text->width = width; + text->height = height; + + text->framebuffer = new (std::nothrow) uint32_t[width * height]; + if (!text->framebuffer) { + delete text; + return nullptr; } - // Clears the framebuffer and resets the text queue and all state. - void text_clear(text_t* text) { - if (!text) return; - memset(text->framebuffer, 0, text->width * text->height * sizeof(uint32_t)); - text->current_x = text->padding_x; - text->current_y = text->padding_y; - text->text_queue.clear(); - text->queue_index = 0; - text->char_index = 0; - text->char_progress = 0.0f; - text->last_line_height = 0.0f; - } + // Share the cache from source + text->cache = source->cache; + text->cache->refcount++; + text->owns_cache = false; - // Immediately sets the character index to the end of the current text block. - void text_show_all_current(text_t* text) { - if (!text || text->queue_index >= text->text_queue.size()) return; + text->queue_index = 0; + text->char_index = 0; + text->char_progress = 0.0f; + text->current_font = nullptr; + text->last_line_height = 0.0f; - text_params_t* params = &text->text_queue[text->queue_index]; + text->current_x = text->padding_x; + text->current_y = text->padding_y; - size_t text_len_cp = utf8_count_codepoints(params->text); + text_clear(text); + return text; +} - // Only update if it's not already complete - if (text->char_index < (int)text_len_cp) { - text->char_index = (int)text_len_cp; - text->char_progress = static_cast(text_len_cp); +void text_destroy(text_t* text) { + if (!text) return; + + delete[] text->framebuffer; + + // Handle cache cleanup with reference counting + if (text->cache) { + text->cache->refcount--; + if (text->cache->refcount == 0) { + delete text->cache; } } - // --- Update & Animation --- + delete text; +} + +// --- Font Configuration --- + +// Sets the default font path and size to be used by subsequent text_push calls. +// Pass NULL for font_path to keep the current default font. +// Pass size <= 0 to keep the current default size. +void text_set_font(text_t* text, const char* font_path, float size) { + if (!text) return; + + if (font_path != nullptr) { + text->default_font_path = font_path; + } + + if (size > 0.0f) { + text->default_font_size = size; + } +} + +// --- Text Queue & State --- + +// Adds a block of text to the rendering queue. +void text_push(text_t* text, + const char* str, + float speed, + bool outline) +{ + if (!text || !str) return; + + text_params_t p; + p.text = str; + + p.size = text->default_font_size; + + p.speed = speed; + p.outline = outline; + + p.font_path = text->default_font_path; + + p.lines_computed = false; // Mark for precomputation + + text->text_queue.push_back(std::move(p)); +} + +// Clears the framebuffer and resets the text queue and all state. +void text_clear(text_t* text) { + if (!text) return; +#if USE_SSE2 + // Clear framebuffer with SSE2 + size_t total_pixels = text->width * text->height; + __m128i zero = _mm_setzero_si128(); + + size_t i = 0; + for (; i + 3 < total_pixels; i += 4) { + _mm_storeu_si128((__m128i*) & text->framebuffer[i], zero); + } + + // Clear remaining pixels + for (; i < total_pixels; i++) { + text->framebuffer[i] = 0; + } +#else + memset(text->framebuffer, 0, text->width * text->height * sizeof(uint32_t)); +#endif + text->current_x = text->padding_x; + text->current_y = text->padding_y; + text->text_queue.clear(); + text->queue_index = 0; + text->char_index = 0; + text->char_progress = 0.0f; + text->last_line_height = 0.0f; +} + +// Immediately sets the character index to the end of the current text block. +void text_show_all_current(text_t* text) { + if (!text || text->queue_index >= text->text_queue.size()) return; + + text_params_t* params = &text->text_queue[text->queue_index]; + + size_t text_len_cp = utf8_count_codepoints(params->text); + + // Only update if it's not already complete + if (text->char_index < (int)text_len_cp) { + text->char_index = (int)text_len_cp; + text->char_progress = static_cast(text_len_cp); + } +} + +// --- Update & Animation --- // Advances the text animation and renders new characters. // deltatime is the time elapsed since the last frame (in seconds). - bool text_nextframe(text_t* text, float deltatime) { - if (!text) return true; - if (text->queue_index >= text->text_queue.size()) { - return true; // Nothing to do +bool text_nextframe(text_t* text, float deltatime) { + if (!text) return false; + if (text->queue_index >= text->text_queue.size()) { + return false; // Nothing to do + } + + text_params_t* params = &text->text_queue[text->queue_index]; + + // Load font if needed + if (!params->font_path.empty()) { + cached_font_t* font = load_font(text, params->font_path); + if (font) text->current_font = font; + } + + if (!text->current_font) { + if (text->cache->font_cache.empty()) return false; + text->current_font = &text->cache->font_cache.begin()->second; + } + + // Precompute layout + precompute_line_breaks(text, params); + + const stbtt_fontinfo* info = &text->current_font->info; + float scale = stbtt_ScaleForPixelHeight(info, params->size); + int ascent, descent, line_gap; + stbtt_GetFontVMetrics(info, &ascent, &descent, &line_gap); + float baseline = ascent * scale; + + const size_t total_cp = utf8_count_codepoints(params->text); + + bool did_draw = false; + + // Update animation progress + if (params->speed > 0.0f && text->char_index < (int)total_cp) { + text->char_progress += params->speed * deltatime; + int new_index = static_cast(text->char_progress); + if (new_index > text->char_index) { + text->char_index = std::min(new_index, (int)total_cp); + did_draw = true; // Will draw new characters } - - text_params_t* params = &text->text_queue[text->queue_index]; - - // Load font - if (!params->font_path.empty()) { - cached_font_t* font = load_font(text, params->font_path); - if (font) { - text->current_font = font; - } - } - - if (!text->current_font) { - if (text->cache->font_cache.empty()) { - fprintf(stderr, "Error: text_nextframe() called with no valid font loaded.\n"); - return false; - } - text->current_font = &text->cache->font_cache.begin()->second; - } - - // Calculate layout on first frame - precompute_line_breaks(text, params); - - const stbtt_fontinfo* info = &text->current_font->info; - float scale = stbtt_ScaleForPixelHeight(info, params->size); - int ascent, descent, line_gap; - stbtt_GetFontVMetrics(info, &ascent, &descent, &line_gap); - float baseline = ascent * scale; - - // --- Update animation progress using deltatime --- - size_t total_cp = utf8_count_codepoints(params->text); - if (params->speed > 0.0f && text->char_index < (int)total_cp) { - // The animation speed (params->speed) is now characters per second. - // Multiply by deltatime to get the progress to advance this frame. - text->char_progress += params->speed * deltatime; - - // Only update char_index if it has advanced past the current index. - // This prevents the animation from advancing multiple characters in a single frame - // if the deltatime is large, but still allows continuous float progress. - int new_char_index = static_cast(text->char_progress); - - // We only want to draw characters *up to* the new index, but we - // still need to check if the new index is greater than the last frame's index. - if (new_char_index > text->char_index) { - text->char_index = new_char_index; - } - } - else if (params->speed <= 0.0f) { + } + else if (params->speed <= 0.0f) { + if (text->char_index < (int)total_cp) { text->char_index = (int)total_cp; + did_draw = true; // Draw all instantly } - // Cap char_index to the total number of codepoints - text->char_index = (int)std::min((size_t)text->char_index, total_cp); + } - // Only draw NEW characters - size_t draw_start = text->last_rendered_char; // cp index - size_t draw_end = text->char_index; // cp index + // Draw only new characters + size_t draw_start = text->last_rendered_char; + size_t draw_end = text->char_index; - // Check if any new characters should be drawn - if (draw_start < draw_end) { - // Drawing loop - only for NEW characters - for (const auto& line : params->lines) { - // Skip lines that are completely rendered - if (line.end_cp_index <= draw_start) { - continue; - } - - // Skip lines that haven't started yet - if (line.start_cp_index >= draw_end) { - break; - } - - text->current_x = text->padding_x; - text->current_y = line.y_offset; - - // Calculate x position up to where we need to draw - // Iterate through the line bytes and sum advances for cps before draw_start - size_t byte_i = line.start_byte_index; - size_t cp_i = line.start_cp_index; - - while (byte_i < line.end_byte_index && cp_i < draw_start) { - uint32_t cp = utf8_next(params->text, byte_i); - const cached_glyph_t* glyph = get_glyph(text, cp, params->size); - if (glyph) { - text->current_x += glyph->advance; - } - ++cp_i; - } - - // Draw only NEW characters on this line - byte_i = line.start_byte_index; - cp_i = line.start_cp_index; - - // advance byte_i to the first drawable codepoint (max(line.start_cp_index, draw_start)) - while (byte_i < line.end_byte_index && cp_i < std::max(line.start_cp_index, draw_start)) { - utf8_next(params->text, byte_i); - ++cp_i; - } - - // Now iterate and draw until cp reaches min(line.end_cp_index, draw_end) - while (byte_i < line.end_byte_index && cp_i < std::min(line.end_cp_index, draw_end)) { - uint32_t cp = utf8_next(params->text, byte_i); - if (cp == (uint32_t)'\n') { ++cp_i; continue; } - - const cached_glyph_t* glyph = get_glyph(text, cp, params->size); - if (!glyph) { - ++cp_i; - continue; - } - - int x_base = static_cast(text->current_x + glyph->xoff); - int y_base = static_cast(text->current_y + baseline + glyph->yoff); - - if (params->outline) { - draw_glyph(text, x_base, y_base, params->color_outline, glyph, params->size, true); - } - draw_glyph(text, x_base, y_base, params->color_main, glyph, params->size, false); - - text->current_x += glyph->advance; - ++cp_i; - } - } - // Update tracking only if we drew something new - text->last_rendered_char = (int)text->char_index; - } - - // Handle block transition - if (text->char_index >= (int)total_cp) { - text->queue_index++; - text->char_index = 0; - text->char_progress = 0.0f; - text->last_rendered_char = 0; // Reset for next block - // Only advance Y if there was a previous block rendered (to prevent double-spacing on first block) - if (text->queue_index > 1) { - text->current_y += text->last_line_height; // Advance y-pos - } - } - - // Check if queue is finished - if (text->queue_index >= text->text_queue.size()) { - text->text_queue.clear(); - text->queue_index = 0; - text->char_index = 0; - text->char_progress = 0.0f; - text->last_rendered_char = 0; + if (draw_start < draw_end) { + for (const auto& line : params->lines) { + if (line.end_cp_index <= draw_start) continue; + if (line.start_cp_index >= draw_end) break; text->current_x = text->padding_x; - text->current_y = text->padding_y; - text->last_line_height = 0.0f; + text->current_y = line.y_offset; - return true; // Finished + // Advance current_x to draw_start + size_t byte_i = line.start_byte_index; + size_t cp_i = line.start_cp_index; + while (byte_i < line.end_byte_index && cp_i < draw_start) { + uint32_t cp = utf8_next(params->text, byte_i); + const cached_glyph_t* glyph = get_glyph(text, cp, params->size); + if (glyph) text->current_x += glyph->advance; + ++cp_i; + } + + // Draw new characters + byte_i = line.start_byte_index; + cp_i = line.start_cp_index; + while (byte_i < line.end_byte_index && cp_i < std::max(line.start_cp_index, draw_start)) { + utf8_next(params->text, byte_i); + ++cp_i; + } + + while (byte_i < line.end_byte_index && cp_i < std::min(line.end_cp_index, draw_end)) { + uint32_t cp = utf8_next(params->text, byte_i); + if (cp == (uint32_t)'\n') { ++cp_i; continue; } + + const cached_glyph_t* glyph = get_glyph(text, cp, params->size); + if (!glyph) { ++cp_i; continue; } + + int x_base = static_cast(text->current_x + glyph->xoff); + int y_base = static_cast(text->current_y + baseline + glyph->yoff); + + if (params->outline) draw_glyph(text, x_base, y_base, params->color_outline, glyph, params->size, true); + draw_glyph(text, x_base, y_base, params->color_main, glyph, params->size, false); + + text->current_x += glyph->advance; + ++cp_i; + } } - return false; // Not finished + text->last_rendered_char = (int)text->char_index; + did_draw = true; } - // Returns true if the currently running text block has not yet displayed all characters. - bool text_is_animating(text_t* text) { - if (!text) return false; - if (text->queue_index >= text->text_queue.size()) return false; - - const text_params_t* params = &text->text_queue[text->queue_index]; - size_t total_cp = utf8_count_codepoints(params->text); - return text->char_index < (int)total_cp; + // Handle block transition + if (text->char_index >= (int)total_cp) { + text->queue_index++; + text->char_index = 0; + text->char_progress = 0.0f; + text->last_rendered_char = 0; + if (text->queue_index > 1) text->current_y += text->last_line_height; } - // --- Accessors --- + // Return true if something was drawn or queue finished + if (did_draw || text->queue_index >= text->text_queue.size()) return true; - // Returns a pointer to the internal framebuffer. - uint32_t* text_getfb(text_t* text) { - return text ? text->framebuffer : nullptr; - } + return false; +} -} // extern "C" +// Returns true if the currently running text block has not yet displayed all characters. +bool text_is_animating(text_t* text) { + if (!text) return false; + if (text->queue_index >= text->text_queue.size()) return false; + + const text_params_t* params = &text->text_queue[text->queue_index]; + size_t total_cp = utf8_count_codepoints(params->text); + return text->char_index < (int)total_cp; +} + +// --- Accessors --- + +// Returns a pointer to the internal framebuffer. +const uint32_t* text_getfb(text_t* text) { + return text ? text->framebuffer : nullptr; +} + +} // extern "C" \ No newline at end of file diff --git a/src/window.cpp b/src/window.cpp index 9e72fa8..2308c37 100644 --- a/src/window.cpp +++ b/src/window.cpp @@ -5,130 +5,134 @@ #include struct window_t { - SDL_Window* window = nullptr; - SDL_Renderer* renderer = nullptr; - SDL_Texture* texture = nullptr; - int width = 0; - int height = 0; - uint32_t last_tick_time; + SDL_Window* window; + SDL_Renderer* renderer; + SDL_Texture* texture; + int width; + int height; - const uint8_t* keyboard_state = nullptr; - int mouse_x = 0; - int mouse_y = 0; - uint32_t mouse_buttons = 0; // Bitmask for L/M/R/X1/X2 buttons + const uint8_t* keyboard_state; + int mouse_x; + int mouse_y; + uint32_t mouse_buttons; }; +// Internal helper +static void window_cleanup(window_t* s) { + if (!s) return; + if (s->texture) SDL_DestroyTexture(s->texture); + if (s->renderer) SDL_DestroyRenderer(s->renderer); + if (s->window) SDL_DestroyWindow(s->window); +} + extern "C" { + window_t* window_create(int width, int height, const char* title) { - if (SDL_Init(SDL_INIT_VIDEO) != 0) return nullptr; + if (SDL_InitSubSystem(SDL_INIT_VIDEO) != 0) { + std::fprintf(stderr, "SDL_InitSubSystem failed: %s\n", SDL_GetError()); + return nullptr; + } - window_t* s = new window_t; + window_t* s = new window_t{}; if (!s) return nullptr; - s->window = SDL_CreateWindow(title, SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED, width, height, 0); - if (!s->window) { delete s; return nullptr; } - - s->renderer = SDL_CreateRenderer(s->window, -1, SDL_RENDERER_ACCELERATED); - if (!s->renderer) { delete s; return nullptr; } - - // Use ARGB8888 format for 32-bit framebuffer - s->texture = SDL_CreateTexture(s->renderer, SDL_PIXELFORMAT_ARGB8888, SDL_TEXTUREACCESS_STREAMING, width, height); - if (!s->texture) { delete s; return nullptr; } - - // --- Input Initialization --- - // Get the pointer to the keyboard state array - s->keyboard_state = SDL_GetKeyboardState(nullptr); - s->width = width; s->height = height; + s->window = SDL_CreateWindow(title, SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED, width, height, 0); + if (!s->window) { + std::fprintf(stderr, "SDL_CreateWindow failed: %s\n", SDL_GetError()); + delete s; + return nullptr; + } + + s->renderer = SDL_CreateRenderer(s->window, -1, SDL_RENDERER_ACCELERATED); + if (!s->renderer) { + std::fprintf(stderr, "SDL_CreateRenderer failed: %s\n", SDL_GetError()); + window_cleanup(s); + delete s; + return nullptr; + } + + s->texture = SDL_CreateTexture(s->renderer, SDL_PIXELFORMAT_ARGB8888, + SDL_TEXTUREACCESS_STREAMING, width, height); + if (!s->texture) { + std::fprintf(stderr, "SDL_CreateTexture failed: %s\n", SDL_GetError()); + window_cleanup(s); + delete s; + return nullptr; + } + + s->keyboard_state = SDL_GetKeyboardState(nullptr); return s; } void window_destroy(window_t* s) { - if (s->texture) SDL_DestroyTexture(s->texture); - if (s->renderer) SDL_DestroyRenderer(s->renderer); - if (s->window) SDL_DestroyWindow(s->window); + if (!s) return; - // Reset state pointers after destruction - s->texture = nullptr; - s->renderer = nullptr; - s->window = nullptr; - - SDL_Quit(); + window_cleanup(s); + SDL_QuitSubSystem(SDL_INIT_VIDEO); + delete s; } bool window_pollevent(window_t* s) { - SDL_Event event; - bool quit_requested = false; - - // Process all events in the queue - while (SDL_PollEvent(&event)) { - if (event.type == SDL_QUIT) { - quit_requested = true; - } + SDL_Event ev; + bool quit = false; + + while (SDL_PollEvent(&ev)) { + if (ev.type == SDL_QUIT) + quit = true; } s->mouse_buttons = SDL_GetMouseState(&s->mouse_x, &s->mouse_y); - - return quit_requested; + return quit; } -// Checks if a key (identified by its Scancode) is currently held down. -// SDL_SCANCODE_* constants should be used by the caller. -bool window_is_key_down(window_t* s, int key_scancode) { - // Check for valid scancode and that the state array is initialized - if (key_scancode >= 0 && key_scancode < SDL_NUM_SCANCODES && s->keyboard_state) { - return s->keyboard_state[key_scancode] == 1; +bool window_is_key_down(window_t* s, int sc) { + if (!s->keyboard_state) return false; + if (sc < 0 || sc >= SDL_NUM_SCANCODES) return false; + return s->keyboard_state[sc] != 0; +} + +bool window_is_mouse_button_down(window_t* s, int button) { + switch (button) { + case MOUSE_BUTTON_LEFT: return s->mouse_buttons & SDL_BUTTON_LMASK; + case MOUSE_BUTTON_MIDDLE: return s->mouse_buttons & SDL_BUTTON_MMASK; + case MOUSE_BUTTON_RIGHT: return s->mouse_buttons & SDL_BUTTON_RMASK; } return false; } -// Checks if a mouse button is currently held down. -// button_index should be one of MOUSE_BUTTON_LEFT, MOUSE_BUTTON_MIDDLE, or MOUSE_BUTTON_RIGHT. -bool window_is_mouse_button_down(window_t* s, int button_index) { - if (button_index == MOUSE_BUTTON_LEFT) { - return s->mouse_buttons & SDL_BUTTON_LMASK; - } - if (button_index == MOUSE_BUTTON_MIDDLE) { - return s->mouse_buttons & SDL_BUTTON_MMASK; - } - if (button_index == MOUSE_BUTTON_RIGHT) { - return s->mouse_buttons & SDL_BUTTON_RMASK; - } - return false; -} - -// Gets the current mouse cursor position relative to the window. void window_get_mouse_position(window_t* s, int* x, int* y) { if (x) *x = s->mouse_x; if (y) *y = s->mouse_y; } -void window_blit(window_t* s, const uint32_t* framebuffer) { - if (!s->texture) return; // Prevent crash if texture wasn't created +void window_blit(window_t* s, const uint32_t* fb) { + if (!s->texture) return; void* pixels; int pitch; - - // Lock and update the texture + SDL_LockTexture(s->texture, nullptr, &pixels, &pitch); - // Copy the framebuffer. Use memcpy for the whole thing if pitch is what's expected (pitch == width * 4) - // Note: The loop-based copy is safer if pitch != width * 4, but let's stick to the row-by-row copy for safety as in the original. - for (int y = 0; y < s->height; ++y) { - const uint32_t* src = framebuffer + y * s->width; - // Calculate destination address for the start of the current row - uint32_t* dst = (uint32_t*)((uint8_t*)pixels + y * pitch); - // Copy one row (s->width * 4 bytes) - memcpy(dst, src, s->width * sizeof(uint32_t)); + const int expected_pitch = s->width * 4; + const size_t total = (size_t)s->width * (size_t)s->height * 4; + + if (pitch == expected_pitch) { + std::memcpy(pixels, fb, total); + } else { + for (int y = 0; y < s->height; ++y) { + uint8_t* row = (uint8_t*)pixels + y * pitch; + std::memcpy(row, fb + y * s->width, expected_pitch); + } } SDL_UnlockTexture(s->texture); - // Rendering SDL_RenderClear(s->renderer); SDL_RenderCopy(s->renderer, s->texture, nullptr, nullptr); SDL_RenderPresent(s->renderer); } + } diff --git a/vn.vcxproj b/vn.vcxproj index 542c1f8..f39bfb6 100644 --- a/vn.vcxproj +++ b/vn.vcxproj @@ -73,18 +73,22 @@ bin\$(Platform)\$(Configuration)\ obj\$(Platform)\$(Configuration)\ + NativeRecommendedRules.ruleset bin\$(Platform)\$(Configuration)\ obj\$(Platform)\$(Configuration)\ + NativeRecommendedRules.ruleset bin\$(Platform)\$(Configuration)\ obj\$(Platform)\$(Configuration)\ + NativeRecommendedRules.ruleset bin\$(Platform)\$(Configuration)\ obj\$(Platform)\$(Configuration)\ + NativeRecommendedRules.ruleset @@ -194,6 +198,7 @@ + diff --git a/vn.vcxproj.filters b/vn.vcxproj.filters index e2c12b0..d696641 100644 --- a/vn.vcxproj.filters +++ b/vn.vcxproj.filters @@ -81,5 +81,8 @@ Assets + + Assets + \ No newline at end of file