Files
yaze/src/app/gfx/arena.cc
scawful 9e5820a338 feat: Enhance graphics loading and editor synchronization
- Implemented default palette application during graphics data loading to ensure immediate visibility of graphics sheets.
- Refactored GraphicsEditor to queue texture creation without palette management, improving performance and clarity.
- Introduced NotifySheetModified method in Arena to handle texture updates across all editors, ensuring consistency in graphics rendering.
- Enhanced logging for better tracking of graphics sheet modifications and texture operations.
2025-10-09 11:18:43 -04:00

208 lines
6.5 KiB
C++

#include "app/gfx/arena.h"
#include <SDL.h>
#include <algorithm>
#include "app/gfx/backend/irenderer.h"
#include "util/log.h"
#include "util/sdl_deleter.h"
namespace yaze {
namespace gfx {
void Arena::Initialize(IRenderer* renderer) { renderer_ = renderer; }
Arena& Arena::Get() {
static Arena instance;
return instance;
}
Arena::Arena() {
layer1_buffer_.fill(0);
layer2_buffer_.fill(0);
}
Arena::~Arena() {
// Use the safe shutdown method that handles cleanup properly
Shutdown();
}
void Arena::QueueTextureCommand(TextureCommandType type, Bitmap* bitmap) {
texture_command_queue_.push_back({type, bitmap});
}
void Arena::ProcessTextureQueue(IRenderer* renderer) {
// Use provided renderer if available, otherwise use stored renderer
IRenderer* active_renderer = renderer ? renderer : renderer_;
if (!active_renderer) {
// Arena not initialized yet - defer processing
return;
}
if (texture_command_queue_.empty()) {
return;
}
// Performance optimization: Batch process textures with limits
// Process up to 8 texture operations per frame to avoid frame drops
constexpr size_t kMaxTexturesPerFrame = 8;
size_t processed = 0;
auto it = texture_command_queue_.begin();
while (it != texture_command_queue_.end() && processed < kMaxTexturesPerFrame) {
const auto& command = *it;
bool should_remove = true;
// CRITICAL: Replicate the exact short-circuit evaluation from working code
// We MUST check command.bitmap AND command.bitmap->surface() in one expression
// to avoid dereferencing invalid pointers
switch (command.type) {
case TextureCommandType::CREATE: {
// Create a new texture and update it with bitmap data
// Use short-circuit evaluation - if bitmap is invalid, never call ->surface()
if (command.bitmap && command.bitmap->surface() &&
command.bitmap->surface()->format &&
command.bitmap->is_active() &&
command.bitmap->width() > 0 && command.bitmap->height() > 0) {
try {
auto texture = active_renderer->CreateTexture(command.bitmap->width(),
command.bitmap->height());
if (texture) {
command.bitmap->set_texture(texture);
active_renderer->UpdateTexture(texture, *command.bitmap);
processed++;
} else {
should_remove = false; // Retry next frame
}
} catch (...) {
LOG_ERROR("Arena", "Exception during texture creation");
should_remove = true; // Remove bad command
}
}
break;
}
case TextureCommandType::UPDATE: {
// Update existing texture with current bitmap data
if (command.bitmap->texture() &&
command.bitmap->surface() && command.bitmap->surface()->format &&
command.bitmap->is_active()) {
try {
active_renderer->UpdateTexture(command.bitmap->texture(), *command.bitmap);
processed++;
} catch (...) {
LOG_ERROR("Arena", "Exception during texture update");
}
}
break;
}
case TextureCommandType::DESTROY: {
if (command.bitmap->texture()) {
try {
active_renderer->DestroyTexture(command.bitmap->texture());
command.bitmap->set_texture(nullptr);
processed++;
} catch (...) {
LOG_ERROR("Arena", "Exception during texture destruction");
}
}
break;
}
}
if (should_remove) {
it = texture_command_queue_.erase(it);
} else {
++it;
}
}
}
SDL_Surface* Arena::AllocateSurface(int width, int height, int depth, int format) {
// Try to get a surface from the pool first
for (auto it = surface_pool_.available_surfaces_.begin();
it != surface_pool_.available_surfaces_.end(); ++it) {
auto& info = surface_pool_.surface_info_[*it];
if (std::get<0>(info) == width && std::get<1>(info) == height &&
std::get<2>(info) == depth && std::get<3>(info) == format) {
SDL_Surface* surface = *it;
surface_pool_.available_surfaces_.erase(it);
return surface;
}
}
// Create new surface if none available in pool
Uint32 sdl_format = GetSnesPixelFormat(format);
SDL_Surface* surface = SDL_CreateRGBSurfaceWithFormat(0, width, height, depth, sdl_format);
if (surface) {
auto surface_ptr = std::unique_ptr<SDL_Surface, util::SDL_Surface_Deleter>(surface);
surfaces_[surface] = std::move(surface_ptr);
surface_pool_.surface_info_[surface] = std::make_tuple(width, height, depth, format);
}
return surface;
}
void Arena::FreeSurface(SDL_Surface* surface) {
if (!surface) return;
// Return surface to pool if space available
if (surface_pool_.available_surfaces_.size() < surface_pool_.MAX_POOL_SIZE) {
surface_pool_.available_surfaces_.push_back(surface);
} else {
// Remove from tracking maps
surface_pool_.surface_info_.erase(surface);
surfaces_.erase(surface);
}
}
void Arena::Shutdown() {
// Process any remaining batch updates before shutdown
ProcessTextureQueue(renderer_);
// Clear pool references first to prevent reuse during shutdown
surface_pool_.available_surfaces_.clear();
surface_pool_.surface_info_.clear();
texture_pool_.available_textures_.clear();
texture_pool_.texture_sizes_.clear();
// CRITICAL FIX: Clear containers in reverse order to prevent cleanup issues
// This ensures that dependent resources are freed before their dependencies
textures_.clear();
surfaces_.clear();
// Clear any remaining queue items
texture_command_queue_.clear();
}
void Arena::NotifySheetModified(int sheet_index) {
if (sheet_index < 0 || sheet_index >= 223) {
LOG_WARN("Arena", "Invalid sheet index %d, ignoring notification", sheet_index);
return;
}
auto& sheet = gfx_sheets_[sheet_index];
if (!sheet.is_active() || !sheet.surface()) {
LOG_DEBUG("Arena", "Sheet %d not active or no surface, skipping notification", sheet_index);
return;
}
// Queue texture update so changes are visible in all editors
if (sheet.texture()) {
QueueTextureCommand(TextureCommandType::UPDATE, &sheet);
LOG_DEBUG("Arena", "Queued texture update for modified sheet %d", sheet_index);
} else {
// Create texture if it doesn't exist
QueueTextureCommand(TextureCommandType::CREATE, &sheet);
LOG_DEBUG("Arena", "Queued texture creation for modified sheet %d", sheet_index);
}
}
} // namespace gfx
} // namespace yaze