i'll just say it's better, and i ripped out the frontend for now.

This commit is contained in:
maksw committed 2025-11-23 20:49:24 +01:00
1 parent 97946c613a
commit 51c3f6bacc
14 files changed
+1111 -1358

No files matched your search

Binary file not shown.
Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.3 MiB

After

Width:  |  Height:  |  Size: 1.4 MiB

BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 222 KiB

+28 -27
View File
@@ -1,7 +1,6 @@
// compositor.h
#ifndef COMPOSITOR_H
#define COMPOSITOR_H
#include <stdint.h>
#include <stdbool.h>
@@ -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
+3 -2
View File
@@ -5,10 +5,11 @@
#include <stdint.h>
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);
}
+11 -5
View File
@@ -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);
}
+7
View File
@@ -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);
}
+467 -176
View File
@@ -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<uint32_t> occupied_tiles; // List of occupied tiles
animation_t anim;
std::vector<queued_animation_t> 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<float>(anim->start_x),
static_cast<float>(anim->end_x), t_eased));
elem->y = (int32_t)std::round(lerp(static_cast<float>(anim->start_y),
static_cast<float>(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<float>(anim->start_x),
static_cast<float>(anim->end_x), t_eased));
elem->y = (int32_t)std::round(lerp(static_cast<float>(anim->start_y),
static_cast<float>(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++) {
+1 -1
View File
@@ -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;
}
+105 -760
View File
@@ -1,8 +1,6 @@
#include <cstdio>
#include <cstdint>
#include <cstring>
#include <unordered_map>
#include <string>
#include <chrono>
#include <vector>
#include <window.h>
@@ -10,527 +8,15 @@
#include <compositor.h>
#include <image.h>
// ============================================================================
// 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<ActiveAnimation> 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<std::string, SpriteElement> 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<std::string, Character> 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;
}
}
+397 -307
View File
@@ -1,6 +1,3 @@
#include <text.h>
// C/C++ Standard Libraries
#include <cstdio>
#include <cstdlib>
#include <cstring>
@@ -11,9 +8,17 @@
#include <map>
#include <memory>
#include <algorithm>
#include <iostream> // For std::clamp
#include <iostream>
#include <text.h>
// SSE2 intrinsics for x86/x64
#if defined(__x86_64__) || defined(_M_X64) || defined(__i386__) || defined(_M_IX86)
#define USE_SSE2 1
#include <immintrin.h>
#else
#define USE_SSE2 0
#endif
// 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_params_t> 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<float>(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<float>(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<int>(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<int>(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<int>(text->current_x + glyph->xoff);
int y_base = static_cast<int>(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<int>(text->current_x + glyph->xoff);
int y_base = static_cast<int>(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"
+84 -80
View File
@@ -5,130 +5,134 @@
#include <cstdio>
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);
}
}
+5
View File
@@ -73,18 +73,22 @@
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
<OutDir>bin\$(Platform)\$(Configuration)\</OutDir>
<IntDir>obj\$(Platform)\$(Configuration)\</IntDir>
<CodeAnalysisRuleSet>NativeRecommendedRules.ruleset</CodeAnalysisRuleSet>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
<OutDir>bin\$(Platform)\$(Configuration)\</OutDir>
<IntDir>obj\$(Platform)\$(Configuration)\</IntDir>
<CodeAnalysisRuleSet>NativeRecommendedRules.ruleset</CodeAnalysisRuleSet>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
<OutDir>bin\$(Platform)\$(Configuration)\</OutDir>
<IntDir>obj\$(Platform)\$(Configuration)\</IntDir>
<CodeAnalysisRuleSet>NativeRecommendedRules.ruleset</CodeAnalysisRuleSet>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
<OutDir>bin\$(Platform)\$(Configuration)\</OutDir>
<IntDir>obj\$(Platform)\$(Configuration)\</IntDir>
<CodeAnalysisRuleSet>NativeRecommendedRules.ruleset</CodeAnalysisRuleSet>
</PropertyGroup>
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
<ClCompile>
@@ -194,6 +198,7 @@
<Image Include="assets\wikor.png" />
</ItemGroup>
<ItemGroup>
<Font Include="assets\Aller_Rg.ttf" />
<Font Include="assets\Vera.ttf" />
</ItemGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
+3
View File
@@ -81,5 +81,8 @@
<Font Include="assets\Vera.ttf">
<Filter>Assets</Filter>
</Font>
<Font Include="assets\Aller_Rg.ttf">
<Filter>Assets</Filter>
</Font>
</ItemGroup>
</Project>