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