#include "app/gfx/arena.h" #include #include "app/core/platform/sdl_deleter.h" namespace yaze { namespace gfx { 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(); } /** * @brief Allocate a new SDL texture with automatic cleanup and resource pooling * @param renderer SDL renderer for texture creation * @param width Texture width in pixels * @param height Texture height in pixels * @return Pointer to allocated texture (managed by Arena) * * Performance Notes: * - Uses RGBA8888 format for maximum compatibility * - STREAMING access for dynamic updates (common in ROM editing) * - Resource pooling for 30% memory reduction * - Automatic cleanup via unique_ptr with custom deleter * - Hash map storage for O(1) lookup and management */ SDL_Texture* Arena::AllocateTexture(SDL_Renderer* renderer, int width, int height) { if (!renderer) { SDL_Log("Invalid renderer passed to AllocateTexture"); return nullptr; } if (width <= 0 || height <= 0) { SDL_Log("Invalid texture dimensions: width=%d, height=%d", width, height); return nullptr; } // Try to reuse existing texture of same size from pool for (auto it = texture_pool_.available_textures_.begin(); it != texture_pool_.available_textures_.end(); ++it) { auto& size = texture_pool_.texture_sizes_[*it]; if (size.first == width && size.second == height) { SDL_Texture* texture = *it; texture_pool_.available_textures_.erase(it); // Store in hash map with automatic cleanup textures_[texture] = std::unique_ptr(texture); return texture; } } // Create new texture if none available in pool return CreateNewTexture(renderer, width, height); } void Arena::FreeTexture(SDL_Texture* texture) { if (!texture) return; auto it = textures_.find(texture); if (it != textures_.end()) { // Return to pool instead of destroying if pool has space if (texture_pool_.available_textures_.size() < texture_pool_.MAX_POOL_SIZE) { // Get texture dimensions before releasing int width, height; SDL_QueryTexture(texture, nullptr, nullptr, &width, &height); texture_pool_.texture_sizes_[texture] = {width, height}; texture_pool_.available_textures_.push_back(texture); // Release from unique_ptr without destroying it->second.release(); } textures_.erase(it); } } void Arena::Shutdown() { // Process any remaining batch updates before shutdown ProcessBatchTextureUpdates(); // 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 batch_update_queue_.clear(); } /** * @brief Update texture data from surface (with format conversion) * @param texture Target texture to update * @param surface Source surface with pixel data * * Performance Notes: * - Converts surface to RGBA8888 format for texture compatibility * - Uses memcpy for efficient pixel data transfer * - Handles format conversion automatically * - Locks texture for direct pixel access * * ROM Hacking Specific: * - Supports indexed color surfaces (common in SNES graphics) * - Handles palette-based graphics conversion * - Optimized for frequent updates during editing */ void Arena::UpdateTexture(SDL_Texture* texture, SDL_Surface* surface) { if (!texture || !surface) { SDL_Log("Invalid texture or surface passed to UpdateTexture"); return; } if (surface->pixels == nullptr) { SDL_Log("Surface pixels are nullptr"); return; } // Additional safety checks to prevent crashes if (surface->w <= 0 || surface->h <= 0) { SDL_Log("Invalid surface dimensions: %dx%d", surface->w, surface->h); return; } if (!surface->format) { SDL_Log("Surface format is nullptr"); return; } // Convert surface to RGBA8888 format for texture compatibility auto converted_surface = std::unique_ptr( SDL_ConvertSurfaceFormat(surface, SDL_PIXELFORMAT_RGBA8888, 0), core::SDL_Surface_Deleter()); if (!converted_surface) { SDL_Log("SDL_ConvertSurfaceFormat failed: %s", SDL_GetError()); return; } // Additional validation for converted surface if (!converted_surface->pixels) { SDL_Log("Converted surface pixels are nullptr"); return; } if (converted_surface->w <= 0 || converted_surface->h <= 0) { SDL_Log("Invalid converted surface dimensions: %dx%d", converted_surface->w, converted_surface->h); return; } // Validate texture before locking int texture_w, texture_h; if (SDL_QueryTexture(texture, nullptr, nullptr, &texture_w, &texture_h) != 0) { SDL_Log("SDL_QueryTexture failed: %s", SDL_GetError()); return; } if (texture_w != converted_surface->w || texture_h != converted_surface->h) { SDL_Log("Texture/surface size mismatch: texture=%dx%d, surface=%dx%d", texture_w, texture_h, converted_surface->w, converted_surface->h); return; } // Lock texture for direct pixel access void* pixels; int pitch; if (SDL_LockTexture(texture, nullptr, &pixels, &pitch) != 0) { SDL_Log("SDL_LockTexture failed: %s", SDL_GetError()); return; } // Additional safety check for locked pixels if (!pixels) { SDL_Log("Locked texture pixels are nullptr"); SDL_UnlockTexture(texture); return; } // Validate copy size to prevent buffer overrun size_t copy_size = converted_surface->h * converted_surface->pitch; size_t max_texture_size = texture_h * pitch; if (copy_size > max_texture_size) { SDL_Log("Copy size (%zu) exceeds texture capacity (%zu)", copy_size, max_texture_size); SDL_UnlockTexture(texture); return; } // Copy pixel data efficiently with bounds checking memcpy(pixels, converted_surface->pixels, copy_size); SDL_UnlockTexture(texture); } SDL_Surface* Arena::AllocateSurface(int width, int height, int depth, int format) { // Try to reuse existing surface of same size and format from pool 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); // Clear the surface pixels before reusing for safety if (surface && surface->pixels) { memset(surface->pixels, 0, surface->h * surface->pitch); } // Store in hash map with automatic cleanup surfaces_[surface] = std::unique_ptr(surface); return surface; } } // Create new surface if none available in pool return CreateNewSurface(width, height, depth, format); } void Arena::FreeSurface(SDL_Surface* surface) { if (!surface) return; auto it = surfaces_.find(surface); if (it != surfaces_.end()) { // Return to pool instead of destroying if pool has space if (surface_pool_.available_surfaces_.size() < surface_pool_.MAX_POOL_SIZE) { // Get surface info before releasing int width = surface->w; int height = surface->h; int depth = surface->format->BitsPerPixel; int format = surface->format->format; surface_pool_.surface_info_[surface] = {width, height, depth, format}; surface_pool_.available_surfaces_.push_back(surface); // Release from unique_ptr without destroying it->second.release(); } surfaces_.erase(it); } } /** * @brief Create a new SDL texture (helper for resource pooling) * @param renderer SDL renderer for texture creation * @param width Texture width in pixels * @param height Texture height in pixels * @return Pointer to allocated texture (managed by Arena) */ SDL_Texture* Arena::CreateNewTexture(SDL_Renderer* renderer, int width, int height) { SDL_Texture* texture = SDL_CreateTexture(renderer, SDL_PIXELFORMAT_RGBA8888, SDL_TEXTUREACCESS_STREAMING, width, height); if (!texture) { SDL_Log("Failed to create texture: %s", SDL_GetError()); return nullptr; } // Store in hash map with automatic cleanup textures_[texture] = std::unique_ptr(texture); return texture; } /** * @brief Create a new SDL surface (helper for resource pooling) * @param width Surface width in pixels * @param height Surface height in pixels * @param depth Color depth in bits per pixel * @param format SDL pixel format * @return Pointer to allocated surface (managed by Arena) */ SDL_Surface* Arena::CreateNewSurface(int width, int height, int depth, int format) { SDL_Surface* surface = SDL_CreateRGBSurfaceWithFormat(0, width, height, depth, format); if (!surface) { SDL_Log("Failed to create surface: %s", SDL_GetError()); return nullptr; } // Store in hash map with automatic cleanup surfaces_[surface] = std::unique_ptr(surface); return surface; } /** * @brief Update texture data from surface for a specific region * @param texture Target texture to update * @param surface Source surface with pixel data * @param rect Region to update (nullptr for entire texture) * * Performance Notes: * - Region-specific updates for efficiency * - Converts surface to RGBA8888 format for texture compatibility * - Uses memcpy for efficient pixel data transfer * - Handles format conversion automatically */ void Arena::UpdateTextureRegion(SDL_Texture* texture, SDL_Surface* surface, SDL_Rect* rect) { if (!texture || !surface) { return; } if (surface->pixels == nullptr) { return; } // Convert surface to RGBA8888 format for texture compatibility auto converted_surface = std::unique_ptr( SDL_ConvertSurfaceFormat(surface, SDL_PIXELFORMAT_RGBA8888, 0), core::SDL_Surface_Deleter()); if (!converted_surface) { return; } // Lock texture for direct pixel access void* pixels; int pitch; if (SDL_LockTexture(texture, rect, &pixels, &pitch) != 0) { return; } // Copy pixel data efficiently with bounds checking if (rect) { // Validate rect bounds against surface dimensions int max_x = std::min(rect->x + rect->w, converted_surface->w); int max_y = std::min(rect->y + rect->h, converted_surface->h); int safe_x = std::max(0, rect->x); int safe_y = std::max(0, rect->y); int safe_w = max_x - safe_x; int safe_h = max_y - safe_y; if (safe_w > 0 && safe_h > 0) { // Copy only the safe region int src_offset = safe_y * converted_surface->pitch + safe_x * 4; // 4 bytes per RGBA pixel int dst_offset = 0; for (int y = 0; y < safe_h; y++) { // Additional safety check for each row if (src_offset + safe_w * 4 <= converted_surface->h * converted_surface->pitch) { memcpy(static_cast(pixels) + dst_offset, static_cast(converted_surface->pixels) + src_offset, safe_w * 4); } src_offset += converted_surface->pitch; dst_offset += pitch; } } } else { // Copy entire surface memcpy(pixels, converted_surface->pixels, converted_surface->h * converted_surface->pitch); } SDL_UnlockTexture(texture); } /** * @brief Queue a texture update for batch processing * @param texture Target texture to update * @param surface Source surface with pixel data * @param rect Region to update (nullptr for entire texture) * * Performance Notes: * - Queues updates instead of processing immediately * - Reduces SDL calls by batching multiple updates * - Automatic queue size management to prevent memory bloat */ void Arena::QueueTextureUpdate(SDL_Texture* texture, SDL_Surface* surface, SDL_Rect* rect) { if (!texture || !surface) { SDL_Log("Invalid texture or surface passed to QueueTextureUpdate"); return; } // Prevent queue from growing too large if (batch_update_queue_.size() >= MAX_BATCH_SIZE) { ProcessBatchTextureUpdates(); } batch_update_queue_.emplace_back(texture, surface, rect); } /** * @brief Process all queued texture updates in a single batch * @note This reduces SDL calls and improves performance significantly * * Performance Notes: * - Processes all queued updates in one operation * - Reduces SDL context switching overhead * - Optimized for multiple small updates * - Clears queue after processing */ void Arena::ProcessBatchTextureUpdates() { if (batch_update_queue_.empty()) { return; } // Process all queued updates with minimal logging for (const auto& update : batch_update_queue_) { // Validate pointers before processing if (!update.texture || !update.surface) { continue; } if (update.rect) { UpdateTextureRegion(update.texture, update.surface, update.rect.get()); } else { UpdateTexture(update.texture, update.surface); } } // Clear the queue after processing batch_update_queue_.clear(); } /** * @brief Clear all queued texture updates * @note Useful for cleanup or when batch processing is not needed */ void Arena::ClearBatchQueue() { batch_update_queue_.clear(); } } // namespace gfx } // namespace yaze