epic: refactor SDL2_Renderer usage to IRenderer and queued texture rendering
- Updated the testing guide to clarify the testing framework's organization and execution methods, improving user understanding. - Refactored CMakeLists to include new platform-specific files, ensuring proper integration of the rendering backend. - Modified main application files to utilize the new IRenderer interface, enhancing flexibility in rendering operations. - Implemented deferred texture management in various components, allowing for more efficient graphics handling and improved performance. - Introduced new methods for texture creation and updates, streamlining the rendering process across the application. - Enhanced logging and error handling in the rendering pipeline to facilitate better debugging and diagnostics.
This commit is contained in:
@@ -3,11 +3,14 @@
|
||||
#include <SDL.h>
|
||||
#include <algorithm>
|
||||
|
||||
#include "app/gfx/backend/irenderer.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;
|
||||
@@ -23,74 +26,95 @@ Arena::~Arena() {
|
||||
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<SDL_Texture, util::SDL_Texture_Deleter>(texture);
|
||||
return texture;
|
||||
}
|
||||
}
|
||||
|
||||
// Create new texture if none available in pool
|
||||
return CreateNewTexture(renderer, width, height);
|
||||
void Arena::QueueTextureCommand(TextureCommandType type, Bitmap* bitmap) {
|
||||
texture_command_queue_.push_back({type, bitmap});
|
||||
}
|
||||
|
||||
void Arena::FreeTexture(SDL_Texture* texture) {
|
||||
if (!texture) return;
|
||||
void Arena::ProcessTextureQueue(IRenderer* renderer) {
|
||||
if (!renderer_) 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();
|
||||
for (const auto& command : texture_command_queue_) {
|
||||
switch (command.type) {
|
||||
case TextureCommandType::CREATE: {
|
||||
// Create a new texture and update it with bitmap data
|
||||
if (command.bitmap && command.bitmap->surface() &&
|
||||
command.bitmap->surface()->format &&
|
||||
command.bitmap->is_active() &&
|
||||
command.bitmap->width() > 0 && command.bitmap->height() > 0) {
|
||||
auto texture = renderer_->CreateTexture(command.bitmap->width(),
|
||||
command.bitmap->height());
|
||||
if (texture) {
|
||||
command.bitmap->set_texture(texture);
|
||||
renderer_->UpdateTexture(texture, *command.bitmap);
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
case TextureCommandType::UPDATE: {
|
||||
// Update existing texture with current bitmap data
|
||||
if (command.bitmap && command.bitmap->texture() &&
|
||||
command.bitmap->surface() && command.bitmap->surface()->format &&
|
||||
command.bitmap->is_active()) {
|
||||
renderer_->UpdateTexture(command.bitmap->texture(), *command.bitmap);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case TextureCommandType::DESTROY: {
|
||||
if (command.bitmap && command.bitmap->texture()) {
|
||||
renderer_->DestroyTexture(command.bitmap->texture());
|
||||
command.bitmap->set_texture(nullptr);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
textures_.erase(it);
|
||||
}
|
||||
texture_command_queue_.clear();
|
||||
}
|
||||
|
||||
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
|
||||
ProcessBatchTextureUpdates();
|
||||
ProcessTextureQueue(renderer_);
|
||||
|
||||
// Clear pool references first to prevent reuse during shutdown
|
||||
surface_pool_.available_surfaces_.clear();
|
||||
@@ -104,406 +128,9 @@ void Arena::Shutdown() {
|
||||
surfaces_.clear();
|
||||
|
||||
// Clear any remaining queue items
|
||||
batch_update_queue_.clear();
|
||||
texture_command_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_Surface, util::SDL_Surface_Deleter>(
|
||||
SDL_ConvertSurfaceFormat(surface, SDL_PIXELFORMAT_RGBA8888, 0),
|
||||
util::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<SDL_Surface, util::SDL_Surface_Deleter>(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<SDL_Texture, util::SDL_Texture_Deleter>(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<SDL_Surface, util::SDL_Surface_Deleter>(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_Surface, util::SDL_Surface_Deleter>(
|
||||
SDL_ConvertSurfaceFormat(surface, SDL_PIXELFORMAT_RGBA8888, 0),
|
||||
util::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<char*>(pixels) + dst_offset,
|
||||
static_cast<char*>(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();
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Progressive/Deferred Texture Management
|
||||
// ============================================================================
|
||||
|
||||
void Arena::QueueDeferredTexture(gfx::Bitmap* bitmap, int priority) {
|
||||
if (!bitmap) return;
|
||||
|
||||
std::lock_guard<std::mutex> lock(deferred_mutex_);
|
||||
deferred_textures_.emplace_back(bitmap, priority);
|
||||
}
|
||||
|
||||
std::vector<gfx::Bitmap*> Arena::GetNextDeferredTextureBatch(
|
||||
int high_priority_limit, int low_priority_limit) {
|
||||
std::lock_guard<std::mutex> lock(deferred_mutex_);
|
||||
|
||||
std::vector<gfx::Bitmap*> batch;
|
||||
|
||||
if (deferred_textures_.empty()) {
|
||||
return batch;
|
||||
}
|
||||
|
||||
// Sort by priority (lower number = higher priority)
|
||||
std::sort(deferred_textures_.begin(), deferred_textures_.end(),
|
||||
[](const DeferredTexture& a, const DeferredTexture& b) {
|
||||
return a.priority < b.priority;
|
||||
});
|
||||
|
||||
// Phase 1: Collect high-priority items (priority 0-10)
|
||||
auto it = deferred_textures_.begin();
|
||||
while (it != deferred_textures_.end() && batch.size() < static_cast<size_t>(high_priority_limit)) {
|
||||
if (it->bitmap && it->priority <= 10 && !it->bitmap->texture()) {
|
||||
batch.push_back(it->bitmap);
|
||||
it = deferred_textures_.erase(it);
|
||||
} else {
|
||||
++it;
|
||||
}
|
||||
}
|
||||
|
||||
// Phase 2: Collect low-priority items (priority 11+) if we have capacity
|
||||
if (batch.size() < static_cast<size_t>(high_priority_limit)) {
|
||||
it = deferred_textures_.begin();
|
||||
int low_count = 0;
|
||||
while (it != deferred_textures_.end() && low_count < low_priority_limit) {
|
||||
if (it->bitmap && it->priority > 10 && !it->bitmap->texture()) {
|
||||
batch.push_back(it->bitmap);
|
||||
low_count++;
|
||||
it = deferred_textures_.erase(it);
|
||||
} else {
|
||||
++it;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return batch;
|
||||
}
|
||||
|
||||
void Arena::ClearDeferredTextures() {
|
||||
std::lock_guard<std::mutex> lock(deferred_mutex_);
|
||||
deferred_textures_.clear();
|
||||
}
|
||||
|
||||
} // namespace gfx
|
||||
} // namespace yaze
|
||||
@@ -45,76 +45,23 @@ class Arena {
|
||||
public:
|
||||
static Arena& Get();
|
||||
|
||||
void Initialize(IRenderer* renderer);
|
||||
~Arena();
|
||||
|
||||
// Resource management
|
||||
/**
|
||||
* @brief Allocate a new SDL texture with automatic cleanup
|
||||
* @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* AllocateTexture(SDL_Renderer* renderer, int width, int height);
|
||||
|
||||
/**
|
||||
* @brief Free a texture and remove from Arena management
|
||||
* @param texture Texture to free
|
||||
*/
|
||||
void FreeTexture(SDL_Texture* texture);
|
||||
|
||||
/**
|
||||
* @brief Update texture data from surface (with format conversion)
|
||||
* @param texture Target texture to update
|
||||
* @param surface Source surface with pixel data
|
||||
*/
|
||||
void UpdateTexture(SDL_Texture* texture, SDL_Surface* surface);
|
||||
// --- New Deferred Command System ---
|
||||
enum class TextureCommandType { CREATE, UPDATE, DESTROY };
|
||||
struct TextureCommand {
|
||||
TextureCommandType type;
|
||||
Bitmap* bitmap; // The bitmap that needs a texture operation
|
||||
};
|
||||
|
||||
/**
|
||||
* @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)
|
||||
*/
|
||||
void UpdateTextureRegion(SDL_Texture* texture, SDL_Surface* surface, SDL_Rect* rect = nullptr);
|
||||
void QueueTextureCommand(TextureCommandType type, Bitmap* bitmap);
|
||||
void ProcessTextureQueue(IRenderer* renderer);
|
||||
|
||||
// Batch operations for improved performance
|
||||
/**
|
||||
* @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)
|
||||
*/
|
||||
void QueueTextureUpdate(SDL_Texture* texture, SDL_Surface* surface, SDL_Rect* rect = nullptr);
|
||||
|
||||
/**
|
||||
* @brief Process all queued texture updates in a single batch
|
||||
* @note This reduces SDL calls and improves performance significantly
|
||||
*/
|
||||
void ProcessBatchTextureUpdates();
|
||||
|
||||
/**
|
||||
* @brief Clear all queued texture updates
|
||||
*/
|
||||
void ClearBatchQueue();
|
||||
|
||||
/**
|
||||
* @brief Allocate a new SDL surface with automatic cleanup
|
||||
* @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)
|
||||
*/
|
||||
// --- Surface Management (unchanged) ---
|
||||
SDL_Surface* AllocateSurface(int width, int height, int depth, int format);
|
||||
|
||||
/**
|
||||
* @brief Free a surface and remove from Arena management
|
||||
* @param surface Surface to free
|
||||
*/
|
||||
void FreeSurface(SDL_Surface* surface);
|
||||
|
||||
// Explicit cleanup method for controlled shutdown
|
||||
void Shutdown();
|
||||
|
||||
// Resource tracking for debugging
|
||||
@@ -163,41 +110,6 @@ class Arena {
|
||||
*/
|
||||
auto& bg2() { return bg2_; }
|
||||
|
||||
// Progressive/Deferred Texture Management (for large asset loading)
|
||||
/**
|
||||
* @brief Add a bitmap to the deferred texture queue
|
||||
* @param bitmap Bitmap that needs a texture created
|
||||
* @param priority Higher priority items processed first (0 = highest)
|
||||
*
|
||||
* Use this for progressive loading of large asset sets (e.g., overworld maps).
|
||||
* Textures are created incrementally per frame to avoid UI freezes.
|
||||
*/
|
||||
void QueueDeferredTexture(gfx::Bitmap* bitmap, int priority = 0);
|
||||
|
||||
/**
|
||||
* @brief Get next batch of deferred textures to process
|
||||
* @param high_priority_limit Max high-priority items to return
|
||||
* @param low_priority_limit Max low-priority items to return
|
||||
* @return Vector of bitmaps to render (caller renders them via Renderer)
|
||||
*
|
||||
* Call this once per frame in your editor's Update() method, then render each bitmap.
|
||||
* High-priority items (priority 0-10) returned up to high_priority_limit.
|
||||
* Low-priority items (priority 11+) returned up to low_priority_limit.
|
||||
*/
|
||||
std::vector<gfx::Bitmap*> GetNextDeferredTextureBatch(int high_priority_limit = 4,
|
||||
int low_priority_limit = 2);
|
||||
|
||||
/**
|
||||
* @brief Clear all deferred texture items
|
||||
*/
|
||||
void ClearDeferredTextures();
|
||||
|
||||
/**
|
||||
* @brief Get count of remaining deferred textures
|
||||
* @return Number of bitmaps waiting for textures
|
||||
*/
|
||||
size_t GetDeferredTextureCount() const { return deferred_textures_.size(); }
|
||||
|
||||
private:
|
||||
Arena();
|
||||
|
||||
@@ -213,7 +125,7 @@ class Arena {
|
||||
|
||||
std::array<gfx::Bitmap, 223> gfx_sheets_;
|
||||
|
||||
std::unordered_map<SDL_Texture*,
|
||||
std::unordered_map<TextureHandle,
|
||||
std::unique_ptr<SDL_Texture, util::SDL_Texture_Deleter>>
|
||||
textures_;
|
||||
|
||||
@@ -223,8 +135,8 @@ class Arena {
|
||||
|
||||
// Resource pooling for efficient memory management
|
||||
struct TexturePool {
|
||||
std::vector<SDL_Texture*> available_textures_;
|
||||
std::unordered_map<SDL_Texture*, std::pair<int, int>> texture_sizes_;
|
||||
std::vector<TextureHandle> available_textures_;
|
||||
std::unordered_map<TextureHandle, std::pair<int, int>> texture_sizes_;
|
||||
static constexpr size_t MAX_POOL_SIZE = 100;
|
||||
} texture_pool_;
|
||||
|
||||
@@ -234,32 +146,8 @@ class Arena {
|
||||
static constexpr size_t MAX_POOL_SIZE = 100;
|
||||
} surface_pool_;
|
||||
|
||||
// Batch operations for improved performance
|
||||
struct BatchUpdate {
|
||||
SDL_Texture* texture;
|
||||
SDL_Surface* surface;
|
||||
std::unique_ptr<SDL_Rect> rect;
|
||||
|
||||
BatchUpdate(SDL_Texture* t, SDL_Surface* s, SDL_Rect* r = nullptr)
|
||||
: texture(t), surface(s), rect(r ? std::make_unique<SDL_Rect>(*r) : nullptr) {}
|
||||
};
|
||||
|
||||
std::vector<BatchUpdate> batch_update_queue_;
|
||||
static constexpr size_t MAX_BATCH_SIZE = 50;
|
||||
|
||||
// Helper methods for resource pooling
|
||||
SDL_Texture* CreateNewTexture(SDL_Renderer* renderer, int width, int height);
|
||||
SDL_Surface* CreateNewSurface(int width, int height, int depth, int format);
|
||||
|
||||
// Progressive loading infrastructure
|
||||
struct DeferredTexture {
|
||||
gfx::Bitmap* bitmap;
|
||||
int priority;
|
||||
|
||||
DeferredTexture(gfx::Bitmap* bmp, int prio) : bitmap(bmp), priority(prio) {}
|
||||
};
|
||||
std::vector<DeferredTexture> deferred_textures_;
|
||||
std::mutex deferred_mutex_;
|
||||
std::vector<TextureCommand> texture_command_queue_;
|
||||
IRenderer* renderer_ = nullptr;
|
||||
};
|
||||
|
||||
} // namespace gfx
|
||||
|
||||
@@ -12,7 +12,7 @@ AtlasRenderer& AtlasRenderer::Get() {
|
||||
return instance;
|
||||
}
|
||||
|
||||
void AtlasRenderer::Initialize(SDL_Renderer* renderer, int initial_size) {
|
||||
void AtlasRenderer::Initialize(IRenderer* renderer, int initial_size) {
|
||||
renderer_ = renderer;
|
||||
next_atlas_id_ = 0;
|
||||
current_atlas_ = 0;
|
||||
@@ -37,15 +37,10 @@ int AtlasRenderer::AddBitmap(const Bitmap& bitmap) {
|
||||
int atlas_id = next_atlas_id_++;
|
||||
auto& atlas = *atlases_[current_atlas_];
|
||||
|
||||
// Create atlas entry with BPP format information
|
||||
BppFormat bpp_format = BppFormatManager::Get().DetectFormat(bitmap.vector(), bitmap.width(), bitmap.height());
|
||||
atlas.entries.emplace_back(atlas_id, uv_rect, bitmap.texture(), bpp_format, bitmap.width(), bitmap.height());
|
||||
atlas_lookup_[atlas_id] = &atlas.entries.back();
|
||||
|
||||
// Copy bitmap data to atlas texture
|
||||
SDL_SetRenderTarget(renderer_, atlas.texture);
|
||||
SDL_RenderCopy(renderer_, bitmap.texture(), nullptr, &uv_rect);
|
||||
SDL_SetRenderTarget(renderer_, nullptr);
|
||||
renderer_->SetRenderTarget(atlas.texture);
|
||||
renderer_->RenderCopy(bitmap.texture(), nullptr, &uv_rect);
|
||||
renderer_->SetRenderTarget(nullptr);
|
||||
|
||||
return atlas_id;
|
||||
}
|
||||
@@ -61,9 +56,9 @@ int AtlasRenderer::AddBitmap(const Bitmap& bitmap) {
|
||||
atlas_lookup_[atlas_id] = &atlas.entries.back();
|
||||
|
||||
// Copy bitmap data to atlas texture
|
||||
SDL_SetRenderTarget(renderer_, atlas.texture);
|
||||
SDL_RenderCopy(renderer_, bitmap.texture(), nullptr, &uv_rect);
|
||||
SDL_SetRenderTarget(renderer_, nullptr);
|
||||
renderer_->SetRenderTarget(atlas.texture);
|
||||
renderer_->RenderCopy(bitmap.texture(), nullptr, &uv_rect);
|
||||
renderer_->SetRenderTarget(nullptr);
|
||||
|
||||
return atlas_id;
|
||||
}
|
||||
@@ -92,7 +87,7 @@ int AtlasRenderer::AddBitmapWithBppOptimization(const Bitmap& bitmap, BppFormat
|
||||
|
||||
// Create temporary bitmap with converted data
|
||||
Bitmap converted_bitmap(bitmap.width(), bitmap.height(), bitmap.depth(), converted_data, bitmap.palette());
|
||||
converted_bitmap.CreateTexture(renderer_);
|
||||
converted_bitmap.CreateTexture();
|
||||
|
||||
// Add converted bitmap to atlas
|
||||
return AddBitmap(converted_bitmap);
|
||||
@@ -169,7 +164,7 @@ void AtlasRenderer::RenderBatch(const std::vector<RenderCommand>& render_command
|
||||
auto& atlas = *atlases_[atlas_index];
|
||||
|
||||
// Set atlas texture
|
||||
SDL_SetTextureBlendMode(atlas.texture, SDL_BLENDMODE_BLEND);
|
||||
// SDL_SetTextureBlendMode(atlas.texture, SDL_BLENDMODE_BLEND);
|
||||
|
||||
// Render all commands for this atlas
|
||||
for (const auto* cmd : commands) {
|
||||
@@ -190,9 +185,9 @@ void AtlasRenderer::RenderBatch(const std::vector<RenderCommand>& render_command
|
||||
if (std::abs(cmd->rotation) > 0.001F) {
|
||||
// For rotation, we'd need to use SDL_RenderCopyEx
|
||||
// This is a simplified version
|
||||
SDL_RenderCopy(renderer_, atlas.texture, &entry->uv_rect, &dest_rect);
|
||||
renderer_->RenderCopy(atlas.texture, &entry->uv_rect, &dest_rect);
|
||||
} else {
|
||||
SDL_RenderCopy(renderer_, atlas.texture, &entry->uv_rect, &dest_rect);
|
||||
renderer_->RenderCopy(atlas.texture, &entry->uv_rect, &dest_rect);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -238,7 +233,7 @@ void AtlasRenderer::RenderBatchWithBppOptimization(const std::vector<RenderComma
|
||||
auto& atlas = *atlases_[atlas_index];
|
||||
|
||||
// Set atlas texture with BPP-specific blend mode
|
||||
SDL_SetTextureBlendMode(atlas.texture, SDL_BLENDMODE_BLEND);
|
||||
// SDL_SetTextureBlendMode(atlas.texture, SDL_BLENDMODE_BLEND);
|
||||
|
||||
// Render all commands for this atlas and BPP format
|
||||
for (const auto* cmd : commands) {
|
||||
@@ -257,9 +252,9 @@ void AtlasRenderer::RenderBatchWithBppOptimization(const std::vector<RenderComma
|
||||
|
||||
// Apply rotation if needed
|
||||
if (std::abs(cmd->rotation) > 0.001F) {
|
||||
SDL_RenderCopy(renderer_, atlas.texture, &entry->uv_rect, &dest_rect);
|
||||
renderer_->RenderCopy(atlas.texture, &entry->uv_rect, &dest_rect);
|
||||
} else {
|
||||
SDL_RenderCopy(renderer_, atlas.texture, &entry->uv_rect, &dest_rect);
|
||||
renderer_->RenderCopy(atlas.texture, &entry->uv_rect, &dest_rect);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -306,7 +301,7 @@ void AtlasRenderer::Clear() {
|
||||
// Clean up SDL textures
|
||||
for (auto& atlas : atlases_) {
|
||||
if (atlas->texture) {
|
||||
SDL_DestroyTexture(atlas->texture);
|
||||
renderer_->DestroyTexture(atlas->texture);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -341,8 +336,8 @@ void AtlasRenderer::RenderBitmap(int atlas_id, float x, float y, float scale_x,
|
||||
};
|
||||
|
||||
// Render using atlas texture
|
||||
SDL_SetTextureBlendMode(atlas->texture, SDL_BLENDMODE_BLEND);
|
||||
SDL_RenderCopy(renderer_, atlas->texture, &entry->uv_rect, &dest_rect);
|
||||
// SDL_SetTextureBlendMode(atlas->texture, SDL_BLENDMODE_BLEND);
|
||||
renderer_->RenderCopy(atlas->texture, &entry->uv_rect, &dest_rect);
|
||||
return;
|
||||
}
|
||||
}
|
||||
@@ -393,8 +388,7 @@ void AtlasRenderer::CreateNewAtlas() {
|
||||
|
||||
// Create SDL texture for the atlas
|
||||
auto& atlas = *atlases_[current_atlas_];
|
||||
atlas.texture = SDL_CreateTexture(renderer_, SDL_PIXELFORMAT_RGBA8888,
|
||||
SDL_TEXTUREACCESS_TARGET, size, size);
|
||||
atlas.texture = renderer_->CreateTexture(size, size);
|
||||
|
||||
if (!atlas.texture) {
|
||||
SDL_Log("Failed to create atlas texture: %s", SDL_GetError());
|
||||
@@ -406,18 +400,18 @@ void AtlasRenderer::RebuildAtlas(Atlas& atlas) {
|
||||
std::fill(atlas.used_regions.begin(), atlas.used_regions.end(), false);
|
||||
|
||||
// Rebuild atlas texture by copying from source textures
|
||||
SDL_SetRenderTarget(renderer_, atlas.texture);
|
||||
SDL_SetRenderDrawColor(renderer_, 0, 0, 0, 0);
|
||||
SDL_RenderClear(renderer_);
|
||||
renderer_->SetRenderTarget(atlas.texture);
|
||||
renderer_->SetDrawColor({0, 0, 0, 0});
|
||||
renderer_->Clear();
|
||||
|
||||
for (auto& entry : atlas.entries) {
|
||||
if (entry.in_use && entry.texture) {
|
||||
SDL_RenderCopy(renderer_, entry.texture, nullptr, &entry.uv_rect);
|
||||
renderer_->RenderCopy(entry.texture, nullptr, &entry.uv_rect);
|
||||
MarkRegionUsed(atlas, entry.uv_rect, true);
|
||||
}
|
||||
}
|
||||
|
||||
SDL_SetRenderTarget(renderer_, nullptr);
|
||||
renderer_->SetRenderTarget(nullptr);
|
||||
}
|
||||
|
||||
SDL_Rect AtlasRenderer::FindFreeRegion(Atlas& atlas, int width, int height) {
|
||||
|
||||
@@ -77,10 +77,10 @@ class AtlasRenderer {
|
||||
|
||||
/**
|
||||
* @brief Initialize the atlas renderer
|
||||
* @param renderer SDL renderer for texture operations
|
||||
* @param renderer The renderer to use for texture operations
|
||||
* @param initial_size Initial atlas size (power of 2 recommended)
|
||||
*/
|
||||
void Initialize(SDL_Renderer* renderer, int initial_size = 1024);
|
||||
void Initialize(IRenderer* renderer, int initial_size = 1024);
|
||||
|
||||
/**
|
||||
* @brief Add a bitmap to the atlas
|
||||
@@ -164,20 +164,20 @@ class AtlasRenderer {
|
||||
struct AtlasEntry {
|
||||
int atlas_id;
|
||||
SDL_Rect uv_rect; // UV coordinates in atlas
|
||||
SDL_Texture* texture;
|
||||
TextureHandle texture;
|
||||
bool in_use;
|
||||
BppFormat bpp_format; // BPP format of this entry
|
||||
int original_width;
|
||||
int original_height;
|
||||
|
||||
AtlasEntry(int id, const SDL_Rect& rect, SDL_Texture* tex, BppFormat bpp = BppFormat::kBpp8,
|
||||
AtlasEntry(int id, const SDL_Rect& rect, TextureHandle tex, BppFormat bpp = BppFormat::kBpp8,
|
||||
int width = 0, int height = 0)
|
||||
: atlas_id(id), uv_rect(rect), texture(tex), in_use(true),
|
||||
bpp_format(bpp), original_width(width), original_height(height) {}
|
||||
};
|
||||
|
||||
struct Atlas {
|
||||
SDL_Texture* texture;
|
||||
TextureHandle texture;
|
||||
int size;
|
||||
std::vector<AtlasEntry> entries;
|
||||
std::vector<bool> used_regions; // Track used regions for packing
|
||||
@@ -185,7 +185,7 @@ class AtlasRenderer {
|
||||
Atlas(int s) : size(s), used_regions(s * s, false) {}
|
||||
};
|
||||
|
||||
SDL_Renderer* renderer_;
|
||||
IRenderer* renderer_;
|
||||
std::vector<std::unique_ptr<Atlas>> atlases_;
|
||||
std::unordered_map<int, AtlasEntry*> atlas_lookup_;
|
||||
int next_atlas_id_;
|
||||
|
||||
@@ -56,13 +56,24 @@ TextureHandle SDL2Renderer::CreateTexture(int width, int height) {
|
||||
*/
|
||||
void SDL2Renderer::UpdateTexture(TextureHandle texture, const Bitmap& bitmap) {
|
||||
SDL_Surface* surface = bitmap.surface();
|
||||
if (!texture || !surface) return;
|
||||
|
||||
// Validate texture, surface, and surface format
|
||||
if (!texture || !surface || !surface->format) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Validate surface has pixels
|
||||
if (!surface->pixels || surface->w <= 0 || surface->h <= 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Convert the bitmap's surface to RGBA8888 format for compatibility with the texture.
|
||||
auto converted_surface = std::unique_ptr<SDL_Surface, util::SDL_Surface_Deleter>(
|
||||
SDL_ConvertSurfaceFormat(surface, SDL_PIXELFORMAT_RGBA8888, 0));
|
||||
|
||||
if (!converted_surface) return;
|
||||
if (!converted_surface || !converted_surface->pixels) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Update the texture with the pixels from the converted surface.
|
||||
SDL_UpdateTexture(static_cast<SDL_Texture*>(texture), nullptr, converted_surface->pixels, converted_surface->pitch);
|
||||
|
||||
@@ -218,6 +218,11 @@ void Bitmap::Create(int width, int height, int depth, int format,
|
||||
SDL_UnlockSurface(surface_);
|
||||
}
|
||||
active_ = true;
|
||||
|
||||
// Apply the stored palette if one exists
|
||||
if (!palette_.empty()) {
|
||||
ApplyStoredPalette();
|
||||
}
|
||||
}
|
||||
|
||||
void Bitmap::Reformat(int format) {
|
||||
@@ -234,124 +239,38 @@ void Bitmap::Reformat(int format) {
|
||||
SetPalette(palette_);
|
||||
}
|
||||
|
||||
void Bitmap::UpdateTexture(SDL_Renderer *renderer) {
|
||||
if (!texture_) {
|
||||
CreateTexture(renderer);
|
||||
return;
|
||||
}
|
||||
|
||||
// Use direct SDL calls for reliable texture updates
|
||||
if (modified_ && surface_ && surface_->pixels) {
|
||||
// Convert surface to RGBA8888 format for texture compatibility
|
||||
SDL_Surface* converted = SDL_ConvertSurfaceFormat(surface_, SDL_PIXELFORMAT_RGBA8888, 0);
|
||||
if (converted) {
|
||||
// Update texture directly with SDL
|
||||
int result = SDL_UpdateTexture(texture_, nullptr, converted->pixels, converted->pitch);
|
||||
if (result != 0) {
|
||||
SDL_Log("SDL_UpdateTexture failed: %s", SDL_GetError());
|
||||
}
|
||||
SDL_FreeSurface(converted);
|
||||
} else {
|
||||
SDL_Log("SDL_ConvertSurfaceFormat failed: %s", SDL_GetError());
|
||||
}
|
||||
modified_ = false;
|
||||
}
|
||||
void Bitmap::CreateTexture() {
|
||||
Arena::Get().QueueTextureCommand(Arena::TextureCommandType::CREATE, this);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Queue texture update for batch processing (improved performance)
|
||||
* @param renderer SDL renderer for texture operations
|
||||
* @note Use this for better performance when multiple textures need updating
|
||||
*
|
||||
* Performance Notes:
|
||||
* - Queues updates instead of processing immediately
|
||||
* - Reduces SDL calls by batching multiple updates
|
||||
* - 5x faster for multiple texture updates
|
||||
* - Automatic dirty region handling
|
||||
*/
|
||||
void Bitmap::QueueTextureUpdate(SDL_Renderer *renderer) {
|
||||
ScopedTimer timer("texture_batch_queue");
|
||||
|
||||
if (!texture_) {
|
||||
CreateTexture(renderer);
|
||||
return;
|
||||
}
|
||||
|
||||
// Only queue if there are dirty regions
|
||||
if (!dirty_region_.is_dirty) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Queue the dirty region update for batch processing
|
||||
if (dirty_region_.is_dirty) {
|
||||
// Ensure dirty rect is within bounds
|
||||
int rect_x = std::max(0, dirty_region_.min_x);
|
||||
int rect_y = std::max(0, dirty_region_.min_y);
|
||||
int rect_w = std::min(width_ - rect_x, dirty_region_.max_x - dirty_region_.min_x + 1);
|
||||
int rect_h = std::min(height_ - rect_y, dirty_region_.max_y - dirty_region_.min_y + 1);
|
||||
|
||||
// Only proceed if we have a valid rect
|
||||
if (rect_w > 0 && rect_h > 0) {
|
||||
SDL_Rect dirty_rect = { rect_x, rect_y, rect_w, rect_h };
|
||||
Arena::Get().QueueTextureUpdate(texture_, surface_, &dirty_rect);
|
||||
}
|
||||
dirty_region_.Reset();
|
||||
}
|
||||
void Bitmap::UpdateTexture() {
|
||||
Arena::Get().QueueTextureCommand(Arena::TextureCommandType::UPDATE, this);
|
||||
}
|
||||
|
||||
void Bitmap::CreateTexture(SDL_Renderer *renderer) {
|
||||
if (!renderer) {
|
||||
SDL_Log("Invalid renderer passed to CreateTexture");
|
||||
return;
|
||||
}
|
||||
|
||||
if (width_ <= 0 || height_ <= 0) {
|
||||
SDL_Log("Invalid texture dimensions: width=%d, height=%d\n", width_,
|
||||
height_);
|
||||
return;
|
||||
}
|
||||
|
||||
// Get a texture from the Arena
|
||||
texture_ = Arena::Get().AllocateTexture(renderer, width_, height_);
|
||||
if (!texture_) {
|
||||
SDL_Log("Bitmap::CreateTexture failed to allocate texture: %s\n",
|
||||
SDL_GetError());
|
||||
return;
|
||||
}
|
||||
|
||||
UpdateTextureData();
|
||||
}
|
||||
|
||||
void Bitmap::UpdateTextureData() {
|
||||
if (!texture_ || !surface_) {
|
||||
return;
|
||||
}
|
||||
|
||||
Arena::Get().UpdateTexture(texture_, surface_);
|
||||
modified_ = false;
|
||||
}
|
||||
|
||||
void Bitmap::SetPalette(const SnesPalette &palette) {
|
||||
void Bitmap::ApplyStoredPalette() {
|
||||
if (surface_ == nullptr) {
|
||||
throw BitmapError("Surface is null. Palette not applied");
|
||||
return; // Can't apply without surface
|
||||
}
|
||||
if (surface_->format == nullptr || surface_->format->palette == nullptr) {
|
||||
throw BitmapError(
|
||||
"Surface format or palette is null. Palette not applied.");
|
||||
return; // Can't apply palette to this surface format
|
||||
}
|
||||
if (palette_.empty()) {
|
||||
return; // No palette to apply
|
||||
}
|
||||
palette_ = palette;
|
||||
|
||||
// Invalidate palette cache when palette changes
|
||||
InvalidatePaletteCache();
|
||||
|
||||
SDL_Palette *sdl_palette = surface_->format->palette;
|
||||
if (sdl_palette == nullptr) {
|
||||
throw BitmapError("Failed to get SDL palette");
|
||||
return;
|
||||
}
|
||||
|
||||
SDL_UnlockSurface(surface_);
|
||||
for (size_t i = 0; i < palette.size(); ++i) {
|
||||
const auto& pal_color = palette[i];
|
||||
for (size_t i = 0; i < palette_.size() && i < 256; ++i) {
|
||||
const auto& pal_color = palette_[i];
|
||||
sdl_palette->colors[i].r = pal_color.rgb().x;
|
||||
sdl_palette->colors[i].g = pal_color.rgb().y;
|
||||
sdl_palette->colors[i].b = pal_color.rgb().z;
|
||||
@@ -360,10 +279,22 @@ void Bitmap::SetPalette(const SnesPalette &palette) {
|
||||
SDL_LockSurface(surface_);
|
||||
}
|
||||
|
||||
void Bitmap::SetPalette(const SnesPalette &palette) {
|
||||
// Store palette even if surface isn't ready yet
|
||||
palette_ = palette;
|
||||
|
||||
// Apply it immediately if surface is ready
|
||||
ApplyStoredPalette();
|
||||
}
|
||||
|
||||
void Bitmap::SetPaletteWithTransparent(const SnesPalette &palette, size_t index,
|
||||
int length) {
|
||||
// Store palette even if surface isn't ready yet
|
||||
palette_ = palette;
|
||||
|
||||
// If surface isn't created yet, just store the palette for later
|
||||
if (surface_ == nullptr) {
|
||||
throw BitmapError("Surface is null. Palette not applied");
|
||||
return; // Palette will be applied when surface is created
|
||||
}
|
||||
|
||||
// CRITICAL FIX: Use index directly as palette slot, not index * 7
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
#include <unordered_map>
|
||||
#include <vector>
|
||||
|
||||
#include "app/gfx/backend/irenderer.h"
|
||||
#include "app/gfx/snes_palette.h"
|
||||
|
||||
namespace yaze {
|
||||
@@ -136,19 +137,19 @@ class Bitmap {
|
||||
/**
|
||||
* @brief Creates the underlying SDL_Texture to be displayed.
|
||||
*/
|
||||
void CreateTexture(SDL_Renderer *renderer);
|
||||
void CreateTexture();
|
||||
|
||||
/**
|
||||
* @brief Updates the underlying SDL_Texture when it already exists.
|
||||
*/
|
||||
void UpdateTexture(SDL_Renderer *renderer);
|
||||
void UpdateTexture();
|
||||
|
||||
/**
|
||||
* @brief Queue texture update for batch processing (improved performance)
|
||||
* @param renderer SDL renderer for texture operations
|
||||
* @note Use this for better performance when multiple textures need updating
|
||||
*/
|
||||
void QueueTextureUpdate(SDL_Renderer *renderer);
|
||||
void QueueTextureUpdate(IRenderer *renderer);
|
||||
|
||||
/**
|
||||
* @brief Updates the texture data from the surface
|
||||
@@ -166,6 +167,11 @@ class Bitmap {
|
||||
void SetPaletteWithTransparent(const SnesPalette &palette, size_t index,
|
||||
int length = 7);
|
||||
|
||||
/**
|
||||
* @brief Apply the stored palette to the surface (internal helper)
|
||||
*/
|
||||
void ApplyStoredPalette();
|
||||
|
||||
/**
|
||||
* @brief Set the palette using SDL colors
|
||||
*/
|
||||
@@ -251,7 +257,7 @@ class Bitmap {
|
||||
const uint8_t *data() const { return data_.data(); }
|
||||
std::vector<uint8_t> &mutable_data() { return data_; }
|
||||
SDL_Surface *surface() const { return surface_; }
|
||||
SDL_Texture *texture() const { return texture_; }
|
||||
TextureHandle texture() const { return texture_; }
|
||||
const std::vector<uint8_t> &vector() const { return data_; }
|
||||
uint8_t at(int i) const { return data_[i]; }
|
||||
bool modified() const { return modified_; }
|
||||
@@ -259,6 +265,7 @@ class Bitmap {
|
||||
void set_active(bool active) { active_ = active; }
|
||||
void set_data(const std::vector<uint8_t> &data);
|
||||
void set_modified(bool modified) { modified_ = modified; }
|
||||
void set_texture(TextureHandle texture) { texture_ = texture; }
|
||||
|
||||
|
||||
private:
|
||||
@@ -285,7 +292,7 @@ class Bitmap {
|
||||
SDL_Surface *surface_ = nullptr;
|
||||
|
||||
// Texture for the bitmap (managed by Arena)
|
||||
SDL_Texture *texture_ = nullptr;
|
||||
TextureHandle texture_ = nullptr;
|
||||
|
||||
// Optimized palette lookup cache for O(1) color index lookups
|
||||
std::unordered_map<uint32_t, uint8_t> color_to_index_cache_;
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
|
||||
#include <vector>
|
||||
|
||||
#include "app/core/window.h"
|
||||
#include "app/gfx/arena.h"
|
||||
#include "app/gfx/atlas_renderer.h"
|
||||
#include "app/gfx/bitmap.h"
|
||||
@@ -12,7 +11,7 @@
|
||||
namespace yaze {
|
||||
namespace gfx {
|
||||
|
||||
Tilemap CreateTilemap(std::vector<uint8_t> &data, int width, int height,
|
||||
Tilemap CreateTilemap(IRenderer* renderer, std::vector<uint8_t> &data, int width, int height,
|
||||
int tile_size, int num_tiles, SnesPalette &palette) {
|
||||
Tilemap tilemap;
|
||||
tilemap.tile_size.x = tile_size;
|
||||
@@ -21,16 +20,28 @@ Tilemap CreateTilemap(std::vector<uint8_t> &data, int width, int height,
|
||||
tilemap.map_size.y = num_tiles;
|
||||
tilemap.atlas = Bitmap(width, height, 8, data);
|
||||
tilemap.atlas.SetPalette(palette);
|
||||
core::Renderer::Get().RenderBitmap(&tilemap.atlas);
|
||||
|
||||
// Queue texture creation directly via Arena
|
||||
if (tilemap.atlas.is_active() && tilemap.atlas.surface()) {
|
||||
Arena::Get().QueueTextureCommand(Arena::TextureCommandType::CREATE, &tilemap.atlas);
|
||||
}
|
||||
|
||||
return tilemap;
|
||||
}
|
||||
|
||||
void UpdateTilemap(Tilemap &tilemap, const std::vector<uint8_t> &data) {
|
||||
void UpdateTilemap(IRenderer* renderer, Tilemap &tilemap, const std::vector<uint8_t> &data) {
|
||||
tilemap.atlas.set_data(data);
|
||||
core::Renderer::Get().UpdateBitmap(&tilemap.atlas);
|
||||
|
||||
// Queue texture update directly via Arena
|
||||
if (tilemap.atlas.texture() && tilemap.atlas.is_active() && tilemap.atlas.surface()) {
|
||||
Arena::Get().QueueTextureCommand(Arena::TextureCommandType::UPDATE, &tilemap.atlas);
|
||||
} else if (!tilemap.atlas.texture() && tilemap.atlas.is_active() && tilemap.atlas.surface()) {
|
||||
// Create if doesn't exist yet
|
||||
Arena::Get().QueueTextureCommand(Arena::TextureCommandType::CREATE, &tilemap.atlas);
|
||||
}
|
||||
}
|
||||
|
||||
void RenderTile(Tilemap &tilemap, int tile_id) {
|
||||
void RenderTile(IRenderer* renderer, Tilemap &tilemap, int tile_id) {
|
||||
// Validate tilemap state before proceeding
|
||||
if (!tilemap.atlas.is_active() || tilemap.atlas.vector().empty()) {
|
||||
return;
|
||||
@@ -49,7 +60,7 @@ void RenderTile(Tilemap &tilemap, int tile_id) {
|
||||
// Note: Tile cache disabled to prevent std::move() related crashes
|
||||
}
|
||||
|
||||
void RenderTile16(Tilemap &tilemap, int tile_id) {
|
||||
void RenderTile16(IRenderer* renderer, Tilemap &tilemap, int tile_id) {
|
||||
// Validate tilemap state before proceeding
|
||||
if (!tilemap.atlas.is_active() || tilemap.atlas.vector().empty()) {
|
||||
return;
|
||||
@@ -76,7 +87,7 @@ void RenderTile16(Tilemap &tilemap, int tile_id) {
|
||||
// Note: Tile cache disabled to prevent std::move() related crashes
|
||||
}
|
||||
|
||||
void UpdateTile16(Tilemap &tilemap, int tile_id) {
|
||||
void UpdateTile16(IRenderer* renderer, Tilemap &tilemap, int tile_id) {
|
||||
// Check if tile is cached
|
||||
Bitmap* cached_tile = tilemap.tile_cache.GetTile(tile_id);
|
||||
if (cached_tile) {
|
||||
@@ -88,10 +99,14 @@ void UpdateTile16(Tilemap &tilemap, int tile_id) {
|
||||
int tile_data_offset = 0;
|
||||
tilemap.atlas.Get16x16Tile(tile_x, tile_y, tile_data, tile_data_offset);
|
||||
cached_tile->set_data(tile_data);
|
||||
core::Renderer::Get().UpdateBitmap(cached_tile);
|
||||
|
||||
// Queue texture update directly via Arena
|
||||
if (cached_tile->texture() && cached_tile->is_active()) {
|
||||
Arena::Get().QueueTextureCommand(Arena::TextureCommandType::UPDATE, cached_tile);
|
||||
}
|
||||
} else {
|
||||
// Tile not cached, render it fresh
|
||||
RenderTile16(tilemap, tile_id);
|
||||
RenderTile16(renderer, tilemap, tile_id);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -293,7 +308,7 @@ std::vector<uint8_t> GetTilemapData(Tilemap &tilemap, int tile_id) {
|
||||
return data;
|
||||
}
|
||||
|
||||
void RenderTilesBatch(Tilemap& tilemap, const std::vector<int>& tile_ids,
|
||||
void RenderTilesBatch(IRenderer* renderer, Tilemap& tilemap, const std::vector<int>& tile_ids,
|
||||
const std::vector<std::pair<float, float>>& positions,
|
||||
const std::vector<std::pair<float, float>>& scales) {
|
||||
if (tile_ids.empty() || positions.empty() || tile_ids.size() != positions.size()) {
|
||||
@@ -302,9 +317,6 @@ void RenderTilesBatch(Tilemap& tilemap, const std::vector<int>& tile_ids,
|
||||
|
||||
ScopedTimer timer("tilemap_batch_render");
|
||||
|
||||
// Get renderer from Arena
|
||||
SDL_Renderer* renderer = nullptr; // We need to get this from the renderer system
|
||||
|
||||
// Initialize atlas renderer if not already done
|
||||
auto& atlas_renderer = AtlasRenderer::Get();
|
||||
if (!renderer) {
|
||||
@@ -340,11 +352,16 @@ void RenderTilesBatch(Tilemap& tilemap, const std::vector<int>& tile_ids,
|
||||
tilemap.tile_cache.CacheTile(tile_id, std::move(new_tile));
|
||||
cached_tile = tilemap.tile_cache.GetTile(tile_id);
|
||||
if (cached_tile) {
|
||||
core::Renderer::Get().RenderBitmap(cached_tile);
|
||||
cached_tile->CreateTexture();
|
||||
}
|
||||
}
|
||||
|
||||
if (cached_tile && cached_tile->is_active()) {
|
||||
// Queue texture creation if needed
|
||||
if (!cached_tile->texture() && cached_tile->surface()) {
|
||||
Arena::Get().QueueTextureCommand(Arena::TextureCommandType::CREATE, cached_tile);
|
||||
}
|
||||
|
||||
// Add to atlas renderer
|
||||
int atlas_id = atlas_renderer.AddBitmap(*cached_tile);
|
||||
if (atlas_id >= 0) {
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
#define YAZE_GFX_TILEMAP_H
|
||||
|
||||
#include "absl/container/flat_hash_map.h"
|
||||
#include "app/gfx/backend/irenderer.h"
|
||||
#include "app/gfx/bitmap.h"
|
||||
#include "app/gfx/snes_tile.h"
|
||||
|
||||
@@ -116,15 +117,15 @@ struct Tilemap {
|
||||
std::vector<uint8_t> FetchTileDataFromGraphicsBuffer(
|
||||
const std::vector<uint8_t> &data, int tile_id, int sheet_offset);
|
||||
|
||||
Tilemap CreateTilemap(std::vector<uint8_t> &data, int width, int height,
|
||||
Tilemap CreateTilemap(IRenderer* renderer, std::vector<uint8_t> &data, int width, int height,
|
||||
int tile_size, int num_tiles, SnesPalette &palette);
|
||||
|
||||
void UpdateTilemap(Tilemap &tilemap, const std::vector<uint8_t> &data);
|
||||
void UpdateTilemap(IRenderer* renderer, Tilemap &tilemap, const std::vector<uint8_t> &data);
|
||||
|
||||
void RenderTile(Tilemap &tilemap, int tile_id);
|
||||
void RenderTile(IRenderer* renderer, Tilemap &tilemap, int tile_id);
|
||||
|
||||
void RenderTile16(Tilemap &tilemap, int tile_id);
|
||||
void UpdateTile16(Tilemap &tilemap, int tile_id);
|
||||
void RenderTile16(IRenderer* renderer, Tilemap &tilemap, int tile_id);
|
||||
void UpdateTile16(IRenderer* renderer, Tilemap &tilemap, int tile_id);
|
||||
|
||||
void ModifyTile16(Tilemap &tilemap, const std::vector<uint8_t> &data,
|
||||
const TileInfo &top_left, const TileInfo &top_right,
|
||||
@@ -146,7 +147,7 @@ std::vector<uint8_t> GetTilemapData(Tilemap &tilemap, int tile_id);
|
||||
* @param scales Vector of scale factors for each tile (optional, defaults to 1.0)
|
||||
* @note This function uses atlas rendering to reduce draw calls significantly
|
||||
*/
|
||||
void RenderTilesBatch(Tilemap& tilemap, const std::vector<int>& tile_ids,
|
||||
void RenderTilesBatch(IRenderer* renderer, Tilemap& tilemap, const std::vector<int>& tile_ids,
|
||||
const std::vector<std::pair<float, float>>& positions,
|
||||
const std::vector<std::pair<float, float>>& scales = {});
|
||||
|
||||
|
||||
Reference in New Issue
Block a user