yaze 0.3.2
Link to the Past ROM Editor
 
Loading...
Searching...
No Matches
arena.cc
Go to the documentation of this file.
2
4
5#include <algorithm>
6
8#include "util/log.h"
9#include "util/sdl_deleter.h"
11
12namespace yaze {
13namespace gfx {
14
16 renderer_ = renderer;
17}
18
20 static Arena instance;
21 return instance;
22}
23
24Arena::Arena() : bg1_(512, 512), bg2_(512, 512) {
25 layer1_buffer_.fill(0);
26 layer2_buffer_.fill(0);
27}
28
30 // Use the safe shutdown method that handles cleanup properly
31 Shutdown();
32}
33
35 // Store generation at queue time for staleness detection
36 uint32_t gen = bitmap ? bitmap->generation() : 0;
37 texture_command_queue_.push_back({type, bitmap, gen});
38}
39
41 IRenderer* active_renderer = renderer ? renderer : renderer_;
42 if (!active_renderer || texture_command_queue_.empty()) {
43 return false;
44 }
45
46 auto it = texture_command_queue_.begin();
47 const auto& command = *it;
48 bool processed = false;
49
50 // Skip stale commands where bitmap was reallocated since queuing
51 if (command.bitmap && command.bitmap->generation() != command.generation) {
52 LOG_DEBUG("Arena", "Skipping stale texture command (gen %u != %u)",
53 command.generation, command.bitmap->generation());
54 texture_command_queue_.erase(it);
55 return false;
56 }
57
58 switch (command.type) {
60 if (command.bitmap && command.bitmap->surface() &&
61 command.bitmap->surface()->format && command.bitmap->is_active() &&
62 command.bitmap->width() > 0 && command.bitmap->height() > 0) {
63 try {
64 auto texture = active_renderer->CreateTexture(
65 command.bitmap->width(), command.bitmap->height());
66 if (texture) {
67 command.bitmap->set_texture(texture);
68 active_renderer->UpdateTexture(texture, *command.bitmap);
69 processed = true;
70 }
71 } catch (...) {
72 LOG_ERROR("Arena", "Exception during single texture creation");
73 }
74 }
75 break;
76 }
78 if (command.bitmap && command.bitmap->texture() &&
79 command.bitmap->surface() && command.bitmap->surface()->format &&
80 command.bitmap->is_active()) {
81 try {
82 active_renderer->UpdateTexture(command.bitmap->texture(),
83 *command.bitmap);
84 processed = true;
85 } catch (...) {
86 LOG_ERROR("Arena", "Exception during single texture update");
87 }
88 }
89 break;
90 }
92 if (command.bitmap && command.bitmap->texture()) {
93 try {
94 active_renderer->DestroyTexture(command.bitmap->texture());
95 command.bitmap->set_texture(nullptr);
96 processed = true;
97 } catch (...) {
98 LOG_ERROR("Arena", "Exception during single texture destruction");
99 }
100 }
101 break;
102 }
103 }
104
105 // Always remove the command after attempting (whether successful or not)
106 texture_command_queue_.erase(it);
107 return processed;
108}
109
111 // Use provided renderer if available, otherwise use stored renderer
112 IRenderer* active_renderer = renderer ? renderer : renderer_;
113
114 if (!active_renderer) {
115 // Arena not initialized yet - defer processing
116 return;
117 }
118
119 if (texture_command_queue_.empty()) {
120 return;
121 }
122
123 // Performance optimization: Batch process textures with limits
124 // Process up to 8 texture operations per frame to avoid frame drops
125 constexpr size_t kMaxTexturesPerFrame = 8;
126 size_t processed = 0;
127
128 auto it = texture_command_queue_.begin();
129 while (it != texture_command_queue_.end() &&
130 processed < kMaxTexturesPerFrame) {
131 const auto& command = *it;
132 bool should_remove = true;
133
134 // Skip stale commands where bitmap was reallocated since queuing
135 if (command.bitmap && command.bitmap->generation() != command.generation) {
136 LOG_DEBUG("Arena", "Skipping stale texture command (gen %u != %u)",
137 command.generation, command.bitmap->generation());
138 it = texture_command_queue_.erase(it);
139 continue;
140 }
141
142 // CRITICAL: Replicate the exact short-circuit evaluation from working code
143 // We MUST check command.bitmap AND command.bitmap->surface() in one
144 // expression to avoid dereferencing invalid pointers
145
146 switch (command.type) {
148 // Create a new texture and update it with bitmap data
149 // Use short-circuit evaluation - if bitmap is invalid, never call
150 // ->surface()
151 if (command.bitmap && command.bitmap->surface() &&
152 command.bitmap->is_active() && command.bitmap->width() > 0 &&
153 command.bitmap->height() > 0) {
154
155 // DEBUG: Log texture creation with palette validation
156 auto* surf = command.bitmap->surface();
157 SDL_Palette* palette = platform::GetSurfacePalette(surf);
158 bool has_palette = palette != nullptr;
159 int color_count = has_palette ? palette->ncolors : 0;
160
161 // Log detailed surface state for debugging
163 "Arena::ProcessTextureQueue (CREATE)", surf);
165 "Arena::ProcessTextureQueue", has_palette, color_count);
166
167 // WARNING: Creating texture without proper palette will produce wrong
168 // colors
169 if (!has_palette) {
170 LOG_WARN("Arena",
171 "Creating texture from surface WITHOUT palette - "
172 "colors will be incorrect!");
174 "Arena::ProcessTextureQueue", 0, false, "Surface has NO palette");
175 } else if (color_count < 90) {
176 LOG_WARN("Arena",
177 "Creating texture with only %d palette colors (expected "
178 "90 for dungeon)",
179 color_count);
181 "Arena::ProcessTextureQueue", 0, false,
182 absl::StrFormat("Low color count: %d", color_count));
183 }
184
185 try {
187 "Arena::ProcessTextureQueue", 0, true, "Calling CreateTexture...");
188
189 auto texture = active_renderer->CreateTexture(
190 command.bitmap->width(), command.bitmap->height());
191
192 if (texture) {
194 "Arena::ProcessTextureQueue", 0, true, "CreateTexture SUCCESS");
195
196 command.bitmap->set_texture(texture);
197 active_renderer->UpdateTexture(texture, *command.bitmap);
198 processed++;
199 } else {
201 "Arena::ProcessTextureQueue", 0, false, "CreateTexture returned NULL");
202 should_remove = false; // Retry next frame
203 }
204 } catch (...) {
205 LOG_ERROR("Arena", "Exception during texture creation");
207 "Arena::ProcessTextureQueue", 0, false, "EXCEPTION during texture creation");
208 should_remove = true; // Remove bad command
209 }
210 }
211 break;
212 }
214 // Update existing texture with current bitmap data
215 if (command.bitmap && command.bitmap->texture() &&
216 command.bitmap->surface() && command.bitmap->surface()->format &&
217 command.bitmap->is_active()) {
218 try {
219 active_renderer->UpdateTexture(command.bitmap->texture(),
220 *command.bitmap);
221 processed++;
222 } catch (...) {
223 LOG_ERROR("Arena", "Exception during texture update");
224 }
225 }
226 break;
227 }
229 if (command.bitmap && command.bitmap->texture()) {
230 try {
231 active_renderer->DestroyTexture(command.bitmap->texture());
232 command.bitmap->set_texture(nullptr);
233 processed++;
234 } catch (...) {
235 LOG_ERROR("Arena", "Exception during texture destruction");
236 }
237 }
238 break;
239 }
240 }
241
242 if (should_remove) {
243 it = texture_command_queue_.erase(it);
244 } else {
245 ++it;
246 }
247 }
248}
249
250SDL_Surface* Arena::AllocateSurface(int width, int height, int depth,
251 int format) {
252 // Try to get a surface from the pool first
253 for (auto it = surface_pool_.available_surfaces_.begin();
254 it != surface_pool_.available_surfaces_.end(); ++it) {
255 auto& info = surface_pool_.surface_info_[*it];
256 if (std::get<0>(info) == width && std::get<1>(info) == height &&
257 std::get<2>(info) == depth && std::get<3>(info) == format) {
258 SDL_Surface* surface = *it;
260 return surface;
261 }
262 }
263
264 // Create new surface if none available in pool
265 Uint32 sdl_format = GetSnesPixelFormat(format);
266 SDL_Surface* surface =
267 platform::CreateSurface(width, height, depth, sdl_format);
268
269 if (surface) {
270 auto surface_ptr =
271 std::unique_ptr<SDL_Surface, util::SDL_Surface_Deleter>(surface);
272 surfaces_[surface] = std::move(surface_ptr);
274 std::make_tuple(width, height, depth, format);
275 }
276
277 return surface;
278}
279
280void Arena::FreeSurface(SDL_Surface* surface) {
281 if (!surface)
282 return;
283
284 // Return surface to pool if space available
286 surface_pool_.available_surfaces_.push_back(surface);
287 } else {
288 // Remove from tracking maps
289 surface_pool_.surface_info_.erase(surface);
290 surfaces_.erase(surface);
291 }
292}
293
295 // Process any remaining batch updates before shutdown
297
298 // Clear pool references first to prevent reuse during shutdown
303
304 // CRITICAL FIX: Clear containers in reverse order to prevent cleanup issues
305 // This ensures that dependent resources are freed before their dependencies
306 textures_.clear();
307 surfaces_.clear();
308
309 // Clear any remaining queue items
311}
312
313void Arena::NotifySheetModified(int sheet_index) {
314 if (sheet_index < 0 || sheet_index >= 223) {
315 LOG_WARN("Arena", "Invalid sheet index %d, ignoring notification",
316 sheet_index);
317 return;
318 }
319
320 auto& sheet = gfx_sheets_[sheet_index];
321 if (!sheet.is_active() || !sheet.surface()) {
322 LOG_DEBUG("Arena",
323 "Sheet %d not active or no surface, skipping notification",
324 sheet_index);
325 return;
326 }
327
328 // Queue texture update so changes are visible in all editors
329 if (sheet.texture()) {
331 LOG_DEBUG("Arena", "Queued texture update for modified sheet %d",
332 sheet_index);
333 } else {
334 // Create texture if it doesn't exist
336 LOG_DEBUG("Arena", "Queued texture creation for modified sheet %d",
337 sheet_index);
338 }
339}
340
341// ========== Palette Change Notification System ==========
342
343void Arena::NotifyPaletteModified(const std::string& group_name,
344 int palette_index) {
345 LOG_DEBUG("Arena", "Palette modified: group='%s', palette=%d",
346 group_name.c_str(), palette_index);
347
348 // Notify all registered listeners
349 for (const auto& [id, callback] : palette_listeners_) {
350 try {
351 callback(group_name, palette_index);
352 } catch (const std::exception& e) {
353 LOG_ERROR("Arena", "Exception in palette listener %d: %s", id, e.what());
354 }
355 }
356
357 LOG_DEBUG("Arena", "Notified %zu palette listeners", palette_listeners_.size());
358}
359
361 int id = next_palette_listener_id_++;
362 palette_listeners_[id] = std::move(callback);
363 LOG_DEBUG("Arena", "Registered palette listener with ID %d", id);
364 return id;
365}
366
367void Arena::UnregisterPaletteListener(int listener_id) {
368 auto it = palette_listeners_.find(listener_id);
369 if (it != palette_listeners_.end()) {
370 palette_listeners_.erase(it);
371 LOG_DEBUG("Arena", "Unregistered palette listener with ID %d", listener_id);
372 }
373}
374
375} // namespace gfx
376} // namespace yaze
Resource management arena for efficient graphics memory handling.
Definition arena.h:46
IRenderer * renderer_
Definition arena.h:217
std::unordered_map< SDL_Surface *, std::unique_ptr< SDL_Surface, util::SDL_Surface_Deleter > > surfaces_
Definition arena.h:200
void Initialize(IRenderer *renderer)
Definition arena.cc:15
struct yaze::gfx::Arena::TexturePool texture_pool_
SDL_Surface * AllocateSurface(int width, int height, int depth, int format)
Definition arena.cc:250
std::unordered_map< TextureHandle, std::unique_ptr< SDL_Texture, util::SDL_Texture_Deleter > > textures_
Definition arena.h:196
void QueueTextureCommand(TextureCommandType type, Bitmap *bitmap)
Definition arena.cc:34
int RegisterPaletteListener(PaletteChangeCallback callback)
Register a callback for palette change notifications.
Definition arena.cc:360
std::array< uint16_t, kTotalTiles > layer2_buffer_
Definition arena.h:190
std::function< void(const std::string &group_name, int palette_index)> PaletteChangeCallback
Definition arena.h:141
void FreeSurface(SDL_Surface *surface)
Definition arena.cc:280
void ProcessTextureQueue(IRenderer *renderer)
Definition arena.cc:110
std::array< gfx::Bitmap, 223 > gfx_sheets_
Definition arena.h:192
bool ProcessSingleTexture(IRenderer *renderer)
Process a single texture command for frame-budget-aware loading.
Definition arena.cc:40
std::unordered_map< int, PaletteChangeCallback > palette_listeners_
Definition arena.h:220
std::vector< TextureCommand > texture_command_queue_
Definition arena.h:216
std::array< uint16_t, kTotalTiles > layer1_buffer_
Definition arena.h:189
void NotifySheetModified(int sheet_index)
Notify Arena that a graphics sheet has been modified.
Definition arena.cc:313
struct yaze::gfx::Arena::SurfacePool surface_pool_
void UnregisterPaletteListener(int listener_id)
Unregister a palette change listener.
Definition arena.cc:367
void Shutdown()
Definition arena.cc:294
static Arena & Get()
Definition arena.cc:19
int next_palette_listener_id_
Definition arena.h:221
void NotifyPaletteModified(const std::string &group_name, int palette_index=-1)
Notify all listeners that a palette has been modified.
Definition arena.cc:343
Represents a bitmap image optimized for SNES ROM hacking.
Definition bitmap.h:67
uint32_t generation() const
Definition bitmap.h:385
Defines an abstract interface for all rendering operations.
Definition irenderer.h:40
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,...)
Definition log.h:103
#define LOG_ERROR(category, format,...)
Definition log.h:109
#define LOG_WARN(category, format,...)
Definition log.h:107
Uint32 GetSnesPixelFormat(int format)
Convert bitmap format enum to SDL pixel format.
Definition bitmap.cc:33
SDL_Surface * CreateSurface(int width, int height, int depth, uint32_t format)
Create a surface using the appropriate API.
Definition sdl_compat.h:418
SDL_Palette * GetSurfacePalette(SDL_Surface *surface)
Get the palette attached to a surface.
Definition sdl_compat.h:375
SDL2/SDL3 compatibility layer.
static constexpr size_t MAX_POOL_SIZE
Definition arena.h:213
std::vector< SDL_Surface * > available_surfaces_
Definition arena.h:210
std::unordered_map< SDL_Surface *, std::tuple< int, int, int, int > > surface_info_
Definition arena.h:212
std::vector< TextureHandle > available_textures_
Definition arena.h:204
std::unordered_map< TextureHandle, std::pair< int, int > > texture_sizes_
Definition arena.h:205