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;
55 bool should_remove =
true;
58 if (command.bitmap && command.bitmap->generation() != command.generation) {
59 LOG_DEBUG(
"Arena",
"Skipping stale texture command (gen %u != %u)",
60 command.generation, command.bitmap->generation());
65 switch (command.type) {
67 if (command.bitmap && command.bitmap->surface() &&
68 command.bitmap->surface()->format && command.bitmap->is_active() &&
69 command.bitmap->width() > 0 && command.bitmap->height() > 0) {
70 const auto old_texture = command.bitmap->texture();
74 command.bitmap->width(), command.bitmap->height());
76 LOG_ERROR(
"Arena",
"Exception during single texture creation");
77 should_remove =
false;
81 should_remove =
false;
87 LOG_ERROR(
"Arena",
"Exception updating new single texture");
88 if (replacement != old_texture) {
92 LOG_ERROR(
"Arena",
"Exception cleaning up failed single texture");
95 should_remove =
false;
99 command.bitmap->set_texture(replacement);
100 if (old_texture && old_texture != replacement) {
104 LOG_ERROR(
"Arena",
"Exception destroying replaced single texture");
112 if (command.bitmap && command.bitmap->texture() &&
113 command.bitmap->surface() && command.bitmap->surface()->format &&
114 command.bitmap->is_active()) {
120 LOG_ERROR(
"Arena",
"Exception during single texture update");
126 if (command.bitmap && command.bitmap->texture()) {
129 command.bitmap->set_texture(
nullptr);
132 LOG_ERROR(
"Arena",
"Exception during single texture destruction");
149 if (!active_renderer) {
160 constexpr size_t kMaxTexturesPerFrame = 8;
161 size_t processed = 0;
165 processed < kMaxTexturesPerFrame) {
166 const auto& command = *it;
167 bool should_remove =
true;
170 if (command.bitmap && command.bitmap->generation() != command.generation) {
171 LOG_DEBUG(
"Arena",
"Skipping stale texture command (gen %u != %u)",
172 command.generation, command.bitmap->generation());
181 switch (command.type) {
186 if (command.bitmap && command.bitmap->surface() &&
187 command.bitmap->is_active() && command.bitmap->width() > 0 &&
188 command.bitmap->height() > 0) {
191 auto* surf = command.bitmap->surface();
193 bool has_palette = palette !=
nullptr;
194 int color_count = has_palette ? palette->ncolors : 0;
198 "Arena::ProcessTextureQueue (CREATE)", surf);
200 "Arena::ProcessTextureQueue", has_palette, color_count);
206 "Creating texture from surface WITHOUT palette - "
207 "colors will be incorrect!");
209 "Arena::ProcessTextureQueue", 0,
false,
210 "Surface has NO palette");
211 }
else if (color_count < 90) {
213 "Creating texture with only %d palette colors (expected "
217 "Arena::ProcessTextureQueue", 0,
false,
218 absl::StrFormat(
"Low color count: %d", color_count));
222 "Arena::ProcessTextureQueue", 0,
true,
223 "Calling CreateTexture...");
224 const auto old_texture = command.bitmap->texture();
228 command.bitmap->width(), command.bitmap->height());
230 LOG_ERROR(
"Arena",
"Exception during texture creation");
232 "Arena::ProcessTextureQueue", 0,
false,
233 "EXCEPTION during texture creation");
234 should_remove =
false;
240 "Arena::ProcessTextureQueue", 0,
false,
241 "CreateTexture returned NULL");
242 should_remove =
false;
247 active_renderer->
UpdateTexture(replacement, *command.bitmap);
249 LOG_ERROR(
"Arena",
"Exception updating new texture");
250 if (replacement != old_texture) {
254 LOG_ERROR(
"Arena",
"Exception cleaning up failed texture");
257 should_remove =
false;
261 command.bitmap->set_texture(replacement);
262 if (old_texture && old_texture != replacement) {
266 LOG_ERROR(
"Arena",
"Exception destroying replaced texture");
270 "Arena::ProcessTextureQueue", 0,
true,
"CreateTexture SUCCESS");
277 if (command.bitmap && command.bitmap->texture() &&
278 command.bitmap->surface() && command.bitmap->surface()->format &&
279 command.bitmap->is_active()) {
285 LOG_ERROR(
"Arena",
"Exception during texture update");
291 if (command.bitmap && command.bitmap->texture()) {
294 command.bitmap->set_texture(
nullptr);
297 LOG_ERROR(
"Arena",
"Exception during texture destruction");
314 using Clock = std::chrono::high_resolution_clock;
315 using Microseconds = std::chrono::microseconds;
318 if (!active_renderer) {
327 const auto budget_us =
static_cast<long long>(budget_ms * 1000.0f);
328 const auto start_time = Clock::now();
330 size_t textures_this_call = 0;
335 if (textures_this_call > 0) {
337 std::chrono::duration_cast<Microseconds>(Clock::now() - start_time);
338 if (elapsed.count() >= budget_us) {
340 "Budget exhausted: processed %zu textures in %.2fms, "
342 textures_this_call, elapsed.count() / 1000.0f,
353 textures_this_call++;
360 if (textures_this_call > 0) {
362 std::chrono::duration_cast<Microseconds>(Clock::now() - start_time);
363 float elapsed_ms = total_elapsed.count() / 1000.0f;
389 if (std::get<0>(info) == width && std::get<1>(info) == height &&
390 std::get<2>(info) == depth && std::get<3>(info) == format) {
391 SDL_Surface* surface = *it;
399 SDL_Surface* surface =
404 std::unique_ptr<SDL_Surface, util::SDL_Surface_Deleter>(surface);
405 surfaces_[surface] = std::move(surface_ptr);
407 std::make_tuple(width, height, depth, format);
450 if (sheet_index < 0 || sheet_index >= 223) {
451 LOG_WARN(
"Arena",
"Invalid sheet index %d, ignoring notification",
457 if (!sheet.is_active() || !sheet.surface()) {
459 "Sheet %d not active or no surface, skipping notification",
465 if (sheet.texture()) {
467 LOG_DEBUG(
"Arena",
"Queued texture update for modified sheet %d",
472 LOG_DEBUG(
"Arena",
"Queued texture creation for modified sheet %d",
481 LOG_DEBUG(
"Arena",
"Palette modified: group='%s', palette=%d",
482 group_name.c_str(), palette_index);
487 callback(group_name, palette_index);
488 }
catch (
const std::exception& e) {
489 LOG_ERROR(
"Arena",
"Exception in palette listener %d: %s",
id, e.what());
493 LOG_DEBUG(
"Arena",
"Notified %zu palette listeners",
500 LOG_DEBUG(
"Arena",
"Registered palette listener with ID %d",
id);
508 LOG_DEBUG(
"Arena",
"Unregistered palette listener with ID %d", listener_id);
515 if (sheet_index < 0 || sheet_index >= 223) {
533 if (sheet_index < 0 || sheet_index >= 223) {
540 bool had_texture = sheet.
texture() !=
nullptr;
551 if (sheet.is_active() && sheet.surface()) {
579 size_t target_evictions = count;
592 if (sheet.texture()) {
594 LOG_DEBUG(
"Arena",
"Evicted LRU sheet %d texture", sheet_index);
607 LOG_DEBUG(
"Arena",
"Evicted %zu LRU sheet textures, %zu remaining", evicted,
618 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,...)
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