8#include "absl/strings/str_format.h"
19 if (renderer !=
nullptr) {
25 static Arena instance;
41 uint32_t gen = bitmap ? bitmap->
generation() : 0;
46 const Bitmap* bitmap)
const {
47 if (bitmap ==
nullptr) {
50 const uint32_t generation = bitmap->
generation();
54 return command.type == type && command.bitmap == bitmap &&
55 command.generation == generation;
68 return command.
bitmap == &bitmap;
86 if (surface !=
nullptr &&
surfaces_.contains(surface)) {
93 if (active_renderer ==
nullptr) {
105 LOG_ERROR(
"Arena",
"Exception destroying retired bitmap texture");
119 const auto& command = *it;
120 bool processed =
false;
121 bool should_remove =
true;
124 if (command.bitmap && command.bitmap->generation() != command.generation) {
125 LOG_DEBUG(
"Arena",
"Skipping stale texture command (gen %u != %u)",
126 command.generation, command.bitmap->generation());
131 switch (command.type) {
133 if (command.bitmap && command.bitmap->surface() &&
134 command.bitmap->surface()->format && command.bitmap->is_active() &&
135 command.bitmap->width() > 0 && command.bitmap->height() > 0) {
136 const auto old_texture = command.bitmap->texture();
140 command.bitmap->width(), command.bitmap->height());
142 LOG_ERROR(
"Arena",
"Exception during single texture creation");
143 should_remove =
false;
147 should_remove =
false;
151 active_renderer->
UpdateTexture(replacement, *command.bitmap);
153 LOG_ERROR(
"Arena",
"Exception updating new single texture");
154 if (replacement != old_texture) {
158 LOG_ERROR(
"Arena",
"Exception cleaning up failed single texture");
161 should_remove =
false;
165 command.bitmap->set_texture(replacement);
166 if (old_texture && old_texture != replacement) {
170 LOG_ERROR(
"Arena",
"Exception destroying replaced single texture");
178 if (command.bitmap && command.bitmap->texture() &&
179 command.bitmap->surface() && command.bitmap->surface()->format &&
180 command.bitmap->is_active()) {
186 LOG_ERROR(
"Arena",
"Exception during single texture update");
192 if (command.bitmap && command.bitmap->texture()) {
195 command.bitmap->set_texture(
nullptr);
198 LOG_ERROR(
"Arena",
"Exception during single texture destruction");
215 if (!active_renderer) {
226 constexpr size_t kMaxTexturesPerFrame = 8;
227 size_t processed = 0;
231 processed < kMaxTexturesPerFrame) {
232 const auto& command = *it;
233 bool should_remove =
true;
236 if (command.bitmap && command.bitmap->generation() != command.generation) {
237 LOG_DEBUG(
"Arena",
"Skipping stale texture command (gen %u != %u)",
238 command.generation, command.bitmap->generation());
247 switch (command.type) {
252 if (command.bitmap && command.bitmap->surface() &&
253 command.bitmap->is_active() && command.bitmap->width() > 0 &&
254 command.bitmap->height() > 0) {
257 auto* surf = command.bitmap->surface();
259 bool has_palette = palette !=
nullptr;
260 int color_count = has_palette ? palette->ncolors : 0;
264 "Arena::ProcessTextureQueue (CREATE)", surf);
266 "Arena::ProcessTextureQueue", has_palette, color_count);
272 "Creating texture from surface WITHOUT palette - "
273 "colors will be incorrect!");
275 "Arena::ProcessTextureQueue", 0,
false,
276 "Surface has NO palette");
277 }
else if (color_count < 90) {
279 "Creating texture with only %d palette colors (expected "
283 "Arena::ProcessTextureQueue", 0,
false,
284 absl::StrFormat(
"Low color count: %d", color_count));
288 "Arena::ProcessTextureQueue", 0,
true,
289 "Calling CreateTexture...");
290 const auto old_texture = command.bitmap->texture();
294 command.bitmap->width(), command.bitmap->height());
296 LOG_ERROR(
"Arena",
"Exception during texture creation");
298 "Arena::ProcessTextureQueue", 0,
false,
299 "EXCEPTION during texture creation");
300 should_remove =
false;
306 "Arena::ProcessTextureQueue", 0,
false,
307 "CreateTexture returned NULL");
308 should_remove =
false;
313 active_renderer->
UpdateTexture(replacement, *command.bitmap);
315 LOG_ERROR(
"Arena",
"Exception updating new texture");
316 if (replacement != old_texture) {
320 LOG_ERROR(
"Arena",
"Exception cleaning up failed texture");
323 should_remove =
false;
327 command.bitmap->set_texture(replacement);
328 if (old_texture && old_texture != replacement) {
332 LOG_ERROR(
"Arena",
"Exception destroying replaced texture");
336 "Arena::ProcessTextureQueue", 0,
true,
"CreateTexture SUCCESS");
343 if (command.bitmap && command.bitmap->texture() &&
344 command.bitmap->surface() && command.bitmap->surface()->format &&
345 command.bitmap->is_active()) {
351 LOG_ERROR(
"Arena",
"Exception during texture update");
357 if (command.bitmap && command.bitmap->texture()) {
360 command.bitmap->set_texture(
nullptr);
363 LOG_ERROR(
"Arena",
"Exception during texture destruction");
380 using Clock = std::chrono::high_resolution_clock;
381 using Microseconds = std::chrono::microseconds;
384 if (!active_renderer) {
393 const auto budget_us =
static_cast<long long>(budget_ms * 1000.0f);
394 const auto start_time = Clock::now();
396 size_t textures_this_call = 0;
401 if (textures_this_call > 0) {
403 std::chrono::duration_cast<Microseconds>(Clock::now() - start_time);
404 if (elapsed.count() >= budget_us) {
406 "Budget exhausted: processed %zu textures in %.2fms, "
408 textures_this_call, elapsed.count() / 1000.0f,
419 textures_this_call++;
426 if (textures_this_call > 0) {
428 std::chrono::duration_cast<Microseconds>(Clock::now() - start_time);
429 float elapsed_ms = total_elapsed.count() / 1000.0f;
455 if (std::get<0>(info) == width && std::get<1>(info) == height &&
456 std::get<2>(info) == depth && std::get<3>(info) == format) {
457 SDL_Surface* surface = *it;
465 SDL_Surface* surface =
470 std::unique_ptr<SDL_Surface, util::SDL_Surface_Deleter>(surface);
471 surfaces_[surface] = std::move(surface_ptr);
473 std::make_tuple(width, height, depth, format);
527 if (sheet_index < 0 || sheet_index >= 223) {
528 LOG_WARN(
"Arena",
"Invalid sheet index %d, ignoring notification",
534 if (!sheet.is_active() || !sheet.surface()) {
536 "Sheet %d not active or no surface, skipping notification",
542 if (sheet.texture()) {
544 LOG_DEBUG(
"Arena",
"Queued texture update for modified sheet %d",
549 LOG_DEBUG(
"Arena",
"Queued texture creation for modified sheet %d",
558 LOG_DEBUG(
"Arena",
"Palette modified: group='%s', palette=%d",
559 group_name.c_str(), palette_index);
564 callback(group_name, palette_index);
565 }
catch (
const std::exception& e) {
566 LOG_ERROR(
"Arena",
"Exception in palette listener %d: %s",
id, e.what());
570 LOG_DEBUG(
"Arena",
"Notified %zu palette listeners",
577 LOG_DEBUG(
"Arena",
"Registered palette listener with ID %d",
id);
585 LOG_DEBUG(
"Arena",
"Unregistered palette listener with ID %d", listener_id);
592 if (sheet_index < 0 || sheet_index >= 223) {
610 if (sheet_index < 0 || sheet_index >= 223) {
617 bool had_texture = sheet.
texture() !=
nullptr;
628 if (sheet.is_active() && sheet.surface()) {
656 size_t target_evictions = count;
669 if (sheet.texture()) {
671 LOG_DEBUG(
"Arena",
"Evicted LRU sheet %d texture", sheet_index);
684 LOG_DEBUG(
"Arena",
"Evicted %zu LRU sheet textures, %zu remaining", evicted,
695 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_
bool HasPendingTextureCommand(TextureCommandType type, const Bitmap *bitmap) const
void RetireBitmap(Bitmap &bitmap)
Explicitly retire resources owned by a bitmap that is being erased.
size_t DrainRetiredBitmaps(IRenderer *renderer)
Destroy texture handles retired earlier in the frame.
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< TextureHandle > retired_texture_handles_
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.
SDL_Surface * DetachSurfaceForArena() noexcept
TextureHandle texture() const
uint32_t generation() const
TextureHandle DetachTextureForArena() noexcept
void MarkRetiredByArena() noexcept
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,...)
void * TextureHandle
An abstract handle representing a texture.
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