8#include "absl/strings/str_format.h"
22 static Arena instance;
38 uint32_t gen = bitmap ? bitmap->
generation() : 0;
53 const auto& command = *it;
54 bool processed =
false;
57 if (command.bitmap && command.bitmap->generation() != command.generation) {
58 LOG_DEBUG(
"Arena",
"Skipping stale texture command (gen %u != %u)",
59 command.generation, command.bitmap->generation());
64 switch (command.type) {
66 if (command.bitmap && command.bitmap->surface() &&
67 command.bitmap->surface()->format && command.bitmap->is_active() &&
68 command.bitmap->width() > 0 && command.bitmap->height() > 0) {
71 command.bitmap->width(), command.bitmap->height());
73 command.bitmap->set_texture(texture);
78 LOG_ERROR(
"Arena",
"Exception during single texture creation");
84 if (command.bitmap && command.bitmap->texture() &&
85 command.bitmap->surface() && command.bitmap->surface()->format &&
86 command.bitmap->is_active()) {
92 LOG_ERROR(
"Arena",
"Exception during single texture update");
98 if (command.bitmap && command.bitmap->texture()) {
101 command.bitmap->set_texture(
nullptr);
104 LOG_ERROR(
"Arena",
"Exception during single texture destruction");
120 if (!active_renderer) {
131 constexpr size_t kMaxTexturesPerFrame = 8;
132 size_t processed = 0;
136 processed < kMaxTexturesPerFrame) {
137 const auto& command = *it;
138 bool should_remove =
true;
141 if (command.bitmap && command.bitmap->generation() != command.generation) {
142 LOG_DEBUG(
"Arena",
"Skipping stale texture command (gen %u != %u)",
143 command.generation, command.bitmap->generation());
152 switch (command.type) {
157 if (command.bitmap && command.bitmap->surface() &&
158 command.bitmap->is_active() && command.bitmap->width() > 0 &&
159 command.bitmap->height() > 0) {
162 auto* surf = command.bitmap->surface();
164 bool has_palette = palette !=
nullptr;
165 int color_count = has_palette ? palette->ncolors : 0;
169 "Arena::ProcessTextureQueue (CREATE)", surf);
171 "Arena::ProcessTextureQueue", has_palette, color_count);
177 "Creating texture from surface WITHOUT palette - "
178 "colors will be incorrect!");
180 "Arena::ProcessTextureQueue", 0,
false,
181 "Surface has NO palette");
182 }
else if (color_count < 90) {
184 "Creating texture with only %d palette colors (expected "
188 "Arena::ProcessTextureQueue", 0,
false,
189 absl::StrFormat(
"Low color count: %d", color_count));
194 "Arena::ProcessTextureQueue", 0,
true,
195 "Calling CreateTexture...");
198 command.bitmap->width(), command.bitmap->height());
202 "Arena::ProcessTextureQueue", 0,
true,
203 "CreateTexture SUCCESS");
205 command.bitmap->set_texture(texture);
210 "Arena::ProcessTextureQueue", 0,
false,
211 "CreateTexture returned NULL");
212 should_remove =
false;
215 LOG_ERROR(
"Arena",
"Exception during texture creation");
217 "Arena::ProcessTextureQueue", 0,
false,
218 "EXCEPTION during texture creation");
219 should_remove =
true;
226 if (command.bitmap && command.bitmap->texture() &&
227 command.bitmap->surface() && command.bitmap->surface()->format &&
228 command.bitmap->is_active()) {
234 LOG_ERROR(
"Arena",
"Exception during texture update");
240 if (command.bitmap && command.bitmap->texture()) {
243 command.bitmap->set_texture(
nullptr);
246 LOG_ERROR(
"Arena",
"Exception during texture destruction");
263 using Clock = std::chrono::high_resolution_clock;
264 using Microseconds = std::chrono::microseconds;
267 if (!active_renderer) {
276 const auto budget_us =
static_cast<long long>(budget_ms * 1000.0f);
277 const auto start_time = Clock::now();
279 size_t textures_this_call = 0;
284 if (textures_this_call > 0) {
285 auto elapsed = std::chrono::duration_cast<Microseconds>(
286 Clock::now() - start_time);
287 if (elapsed.count() >= budget_us) {
289 "Budget exhausted: processed %zu textures in %.2fms, "
291 textures_this_call, elapsed.count() / 1000.0f,
299 textures_this_call++;
304 if (textures_this_call > 0) {
305 auto total_elapsed = std::chrono::duration_cast<Microseconds>(
306 Clock::now() - start_time);
307 float elapsed_ms = total_elapsed.count() / 1000.0f;
333 if (std::get<0>(info) == width && std::get<1>(info) == height &&
334 std::get<2>(info) == depth && std::get<3>(info) == format) {
335 SDL_Surface* surface = *it;
343 SDL_Surface* surface =
348 std::unique_ptr<SDL_Surface, util::SDL_Surface_Deleter>(surface);
349 surfaces_[surface] = std::move(surface_ptr);
351 std::make_tuple(width, height, depth, format);
394 if (sheet_index < 0 || sheet_index >= 223) {
395 LOG_WARN(
"Arena",
"Invalid sheet index %d, ignoring notification",
401 if (!sheet.is_active() || !sheet.surface()) {
403 "Sheet %d not active or no surface, skipping notification",
409 if (sheet.texture()) {
411 LOG_DEBUG(
"Arena",
"Queued texture update for modified sheet %d",
416 LOG_DEBUG(
"Arena",
"Queued texture creation for modified sheet %d",
425 LOG_DEBUG(
"Arena",
"Palette modified: group='%s', palette=%d",
426 group_name.c_str(), palette_index);
431 callback(group_name, palette_index);
432 }
catch (
const std::exception& e) {
433 LOG_ERROR(
"Arena",
"Exception in palette listener %d: %s",
id, e.what());
437 LOG_DEBUG(
"Arena",
"Notified %zu palette listeners",
444 LOG_DEBUG(
"Arena",
"Registered palette listener with ID %d",
id);
452 LOG_DEBUG(
"Arena",
"Unregistered palette listener with ID %d", listener_id);
459 if (sheet_index < 0 || sheet_index >= 223) {
477 if (sheet_index < 0 || sheet_index >= 223) {
484 bool had_texture = sheet.
texture() !=
nullptr;
495 if (sheet.is_active() && sheet.surface()) {
523 size_t target_evictions = count;
536 if (sheet.texture()) {
538 LOG_DEBUG(
"Arena",
"Evicted LRU sheet %d texture", sheet_index);
551 LOG_DEBUG(
"Arena",
"Evicted %zu LRU sheet textures, %zu remaining",
562 LOG_DEBUG(
"Arena",
"Cleared sheet cache tracking");
Resource management arena for efficient graphics memory handling.
size_t sheet_cache_max_size_
std::unordered_map< SDL_Surface *, std::unique_ptr< SDL_Surface, util::SDL_Surface_Deleter > > surfaces_
void Initialize(IRenderer *renderer)
struct yaze::gfx::Arena::TexturePool texture_pool_
bool ProcessTextureQueueWithBudget(IRenderer *renderer, float budget_ms)
Process texture queue with a time budget.
SDL_Surface * AllocateSurface(int width, int height, int depth, int format)
std::unordered_map< TextureHandle, std::unique_ptr< SDL_Texture, util::SDL_Texture_Deleter > > textures_
Bitmap * GetSheetWithCache(int sheet_index)
Get a sheet with automatic LRU tracking and texture creation.
void QueueTextureCommand(TextureCommandType type, Bitmap *bitmap)
int RegisterPaletteListener(PaletteChangeCallback callback)
Register a callback for palette change notifications.
std::array< uint16_t, kTotalTiles > layer2_buffer_
SheetCacheStats sheet_cache_stats_
std::function< void(const std::string &group_name, int palette_index)> PaletteChangeCallback
void FreeSurface(SDL_Surface *surface)
void ProcessTextureQueue(IRenderer *renderer)
std::array< gfx::Bitmap, 223 > gfx_sheets_
bool ProcessSingleTexture(IRenderer *renderer)
Process a single texture command for frame-budget-aware loading.
std::unordered_map< int, std::list< int >::iterator > sheet_lru_map_
std::unordered_map< int, PaletteChangeCallback > palette_listeners_
std::vector< TextureCommand > texture_command_queue_
std::array< uint16_t, kTotalTiles > layer1_buffer_
void NotifySheetModified(int sheet_index)
Notify Arena that a graphics sheet has been modified.
struct yaze::gfx::Arena::SurfacePool surface_pool_
void UnregisterPaletteListener(int listener_id)
Unregister a palette change listener.
TextureQueueStats texture_queue_stats_
void SetSheetCacheSize(size_t max_size)
Set the maximum number of sheet textures to keep cached.
size_t EvictLRUSheets(size_t count=0)
Evict least recently used sheet textures.
std::list< int > sheet_lru_list_
int next_palette_listener_id_
void NotifyPaletteModified(const std::string &group_name, int palette_index=-1)
Notify all listeners that a palette has been modified.
void ClearSheetCache()
Clear all sheet texture cache tracking.
void TouchSheet(int sheet_index)
Mark a graphics sheet as recently accessed.
Represents a bitmap image optimized for SNES ROM hacking.
TextureHandle texture() const
uint32_t generation() const
Defines an abstract interface for all rendering operations.
virtual TextureHandle CreateTexture(int width, int height)=0
Creates a new, empty texture.
virtual void UpdateTexture(TextureHandle texture, const Bitmap &bitmap)=0
Updates a texture with the pixel data from a Bitmap.
virtual void DestroyTexture(TextureHandle texture)=0
Destroys a texture and frees its associated resources.
void LogTextureCreation(const std::string &location, bool has_palette, int color_count)
static PaletteDebugger & Get()
void LogPaletteApplication(const std::string &location, int palette_id, bool success, const std::string &reason="")
void LogSurfaceState(const std::string &location, SDL_Surface *surface)
#define LOG_DEBUG(category, format,...)
#define LOG_ERROR(category, format,...)
#define LOG_WARN(category, format,...)
#define LOG_INFO(category, format,...)
Uint32 GetSnesPixelFormat(int format)
Convert bitmap format enum to SDL pixel format.
SDL2/SDL3 compatibility layer.
static constexpr size_t MAX_POOL_SIZE
std::vector< SDL_Surface * > available_surfaces_
std::unordered_map< SDL_Surface *, std::tuple< int, int, int, int > > surface_info_
std::vector< TextureHandle > available_textures_
std::unordered_map< TextureHandle, std::pair< int, int > > texture_sizes_
float avg_texture_time_ms
size_t textures_processed