#include "app/gfx/atlas_renderer.h" #include #include #include "app/gfx/bpp_format_manager.h" namespace yaze { namespace gfx { AtlasRenderer& AtlasRenderer::Get() { static AtlasRenderer instance; return instance; } void AtlasRenderer::Initialize(SDL_Renderer* renderer, int initial_size) { renderer_ = renderer; next_atlas_id_ = 0; current_atlas_ = 0; // Clear any existing atlases Clear(); // Create initial atlas CreateNewAtlas(); } int AtlasRenderer::AddBitmap(const Bitmap& bitmap) { if (!bitmap.is_active() || !bitmap.texture()) { return -1; // Invalid bitmap } ScopedTimer timer("atlas_add_bitmap"); // Try to pack into current atlas SDL_Rect uv_rect; if (PackBitmap(*atlases_[current_atlas_], bitmap, uv_rect)) { 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); return atlas_id; } // Current atlas is full, create new one CreateNewAtlas(); if (PackBitmap(*atlases_[current_atlas_], bitmap, uv_rect)) { int atlas_id = next_atlas_id_++; auto& atlas = *atlases_[current_atlas_]; 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); return atlas_id; } return -1; // Failed to add } int AtlasRenderer::AddBitmapWithBppOptimization(const Bitmap& bitmap, BppFormat target_bpp) { if (!bitmap.is_active() || !bitmap.texture()) { return -1; // Invalid bitmap } ScopedTimer timer("atlas_add_bitmap_bpp_optimized"); // Detect current BPP format BppFormat current_bpp = BppFormatManager::Get().DetectFormat(bitmap.vector(), bitmap.width(), bitmap.height()); // If formats match, use standard addition if (current_bpp == target_bpp) { return AddBitmap(bitmap); } // Convert bitmap to target BPP format auto converted_data = BppFormatManager::Get().ConvertFormat( bitmap.vector(), current_bpp, target_bpp, bitmap.width(), bitmap.height()); // Create temporary bitmap with converted data Bitmap converted_bitmap(bitmap.width(), bitmap.height(), bitmap.depth(), converted_data, bitmap.palette()); converted_bitmap.CreateTexture(renderer_); // Add converted bitmap to atlas return AddBitmap(converted_bitmap); } void AtlasRenderer::RemoveBitmap(int atlas_id) { auto it = atlas_lookup_.find(atlas_id); if (it == atlas_lookup_.end()) { return; } AtlasEntry* entry = it->second; entry->in_use = false; // Mark region as free for (auto& atlas : atlases_) { for (auto& atlas_entry : atlas->entries) { if (atlas_entry.atlas_id == atlas_id) { MarkRegionUsed(*atlas, atlas_entry.uv_rect, false); break; } } } atlas_lookup_.erase(it); } void AtlasRenderer::UpdateBitmap(int atlas_id, const Bitmap& bitmap) { auto it = atlas_lookup_.find(atlas_id); if (it == atlas_lookup_.end()) { return; } AtlasEntry* entry = it->second; entry->texture = bitmap.texture(); // Update UV coordinates if size changed if (bitmap.width() != entry->uv_rect.w || bitmap.height() != entry->uv_rect.h) { // Remove old entry and add new one RemoveBitmap(atlas_id); AddBitmap(bitmap); } } void AtlasRenderer::RenderBatch(const std::vector& render_commands) { if (render_commands.empty()) { return; } ScopedTimer timer("atlas_batch_render"); // Group commands by atlas for efficient rendering std::unordered_map> atlas_groups; for (const auto& cmd : render_commands) { auto it = atlas_lookup_.find(cmd.atlas_id); if (it != atlas_lookup_.end() && it->second->in_use) { // Find which atlas contains this entry for (size_t i = 0; i < atlases_.size(); ++i) { for (const auto& entry : atlases_[i]->entries) { if (entry.atlas_id == cmd.atlas_id) { atlas_groups[i].push_back(&cmd); break; } } } } } // Render each atlas group for (const auto& [atlas_index, commands] : atlas_groups) { if (commands.empty()) continue; auto& atlas = *atlases_[atlas_index]; // Set atlas texture SDL_SetTextureBlendMode(atlas.texture, SDL_BLENDMODE_BLEND); // Render all commands for this atlas for (const auto* cmd : commands) { auto it = atlas_lookup_.find(cmd->atlas_id); if (it == atlas_lookup_.end()) continue; AtlasEntry* entry = it->second; // Calculate destination rectangle SDL_Rect dest_rect = { static_cast(cmd->x), static_cast(cmd->y), static_cast(entry->uv_rect.w * cmd->scale_x), static_cast(entry->uv_rect.h * cmd->scale_y) }; // Apply rotation if needed 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); } else { SDL_RenderCopy(renderer_, atlas.texture, &entry->uv_rect, &dest_rect); } } } } void AtlasRenderer::RenderBatchWithBppOptimization(const std::vector& render_commands, const std::unordered_map>& bpp_groups) { if (render_commands.empty()) { return; } ScopedTimer timer("atlas_batch_render_bpp_optimized"); // Render each BPP group separately for optimal performance for (const auto& [bpp_format, command_indices] : bpp_groups) { if (command_indices.empty()) continue; // Group commands by atlas for this BPP format std::unordered_map> atlas_groups; for (int cmd_index : command_indices) { if (cmd_index >= 0 && cmd_index < static_cast(render_commands.size())) { const auto& cmd = render_commands[cmd_index]; auto it = atlas_lookup_.find(cmd.atlas_id); if (it != atlas_lookup_.end() && it->second->in_use && it->second->bpp_format == bpp_format) { // Find which atlas contains this entry for (size_t i = 0; i < atlases_.size(); ++i) { for (const auto& entry : atlases_[i]->entries) { if (entry.atlas_id == cmd.atlas_id) { atlas_groups[i].push_back(&cmd); break; } } } } } } // Render each atlas group for this BPP format for (const auto& [atlas_index, commands] : atlas_groups) { if (commands.empty()) continue; auto& atlas = *atlases_[atlas_index]; // Set atlas texture with BPP-specific blend mode SDL_SetTextureBlendMode(atlas.texture, SDL_BLENDMODE_BLEND); // Render all commands for this atlas and BPP format for (const auto* cmd : commands) { auto it = atlas_lookup_.find(cmd->atlas_id); if (it == atlas_lookup_.end()) continue; AtlasEntry* entry = it->second; // Calculate destination rectangle SDL_Rect dest_rect = { static_cast(cmd->x), static_cast(cmd->y), static_cast(entry->uv_rect.w * cmd->scale_x), static_cast(entry->uv_rect.h * cmd->scale_y) }; // Apply rotation if needed if (std::abs(cmd->rotation) > 0.001F) { SDL_RenderCopy(renderer_, atlas.texture, &entry->uv_rect, &dest_rect); } else { SDL_RenderCopy(renderer_, atlas.texture, &entry->uv_rect, &dest_rect); } } } } } AtlasStats AtlasRenderer::GetStats() const { AtlasStats stats; stats.total_atlases = atlases_.size(); for (const auto& atlas : atlases_) { stats.total_entries += atlas->entries.size(); stats.used_entries += std::count_if(atlas->entries.begin(), atlas->entries.end(), [](const AtlasEntry& entry) { return entry.in_use; }); // Calculate memory usage (simplified) stats.total_memory += atlas->size * atlas->size * 4; // RGBA8888 } if (stats.total_entries > 0) { stats.utilization_percent = (static_cast(stats.used_entries) / stats.total_entries) * 100.0F; } return stats; } void AtlasRenderer::Defragment() { ScopedTimer timer("atlas_defragment"); for (auto& atlas : atlases_) { // Remove unused entries atlas->entries.erase( std::remove_if(atlas->entries.begin(), atlas->entries.end(), [](const AtlasEntry& entry) { return !entry.in_use; }), atlas->entries.end()); // Rebuild atlas texture RebuildAtlas(*atlas); } } void AtlasRenderer::Clear() { // Clean up SDL textures for (auto& atlas : atlases_) { if (atlas->texture) { SDL_DestroyTexture(atlas->texture); } } atlases_.clear(); atlas_lookup_.clear(); next_atlas_id_ = 0; current_atlas_ = 0; } AtlasRenderer::~AtlasRenderer() { Clear(); } void AtlasRenderer::RenderBitmap(int atlas_id, float x, float y, float scale_x, float scale_y) { auto it = atlas_lookup_.find(atlas_id); if (it == atlas_lookup_.end() || !it->second->in_use) { return; } AtlasEntry* entry = it->second; // Find which atlas contains this entry for (auto& atlas : atlases_) { for (const auto& atlas_entry : atlas->entries) { if (atlas_entry.atlas_id == atlas_id) { // Calculate destination rectangle SDL_Rect dest_rect = { static_cast(x), static_cast(y), static_cast(entry->uv_rect.w * scale_x), static_cast(entry->uv_rect.h * scale_y) }; // Render using atlas texture SDL_SetTextureBlendMode(atlas->texture, SDL_BLENDMODE_BLEND); SDL_RenderCopy(renderer_, atlas->texture, &entry->uv_rect, &dest_rect); return; } } } } SDL_Rect AtlasRenderer::GetUVCoordinates(int atlas_id) const { auto it = atlas_lookup_.find(atlas_id); if (it == atlas_lookup_.end() || !it->second->in_use) { return {0, 0, 0, 0}; } return it->second->uv_rect; } bool AtlasRenderer::PackBitmap(Atlas& atlas, const Bitmap& bitmap, SDL_Rect& uv_rect) { int width = bitmap.width(); int height = bitmap.height(); // Find free region SDL_Rect free_rect = FindFreeRegion(atlas, width, height); if (free_rect.w == 0 || free_rect.h == 0) { return false; // No space available } // Mark region as used MarkRegionUsed(atlas, free_rect, true); // Set UV coordinates (normalized to 0-1 range) uv_rect = { free_rect.x, free_rect.y, width, height }; return true; } void AtlasRenderer::CreateNewAtlas() { int size = 1024; // Default size if (!atlases_.empty()) { size = atlases_.back()->size * 2; // Double size for new atlas } atlases_.push_back(std::make_unique(size)); current_atlas_ = atlases_.size() - 1; // Create SDL texture for the atlas auto& atlas = *atlases_[current_atlas_]; atlas.texture = SDL_CreateTexture(renderer_, SDL_PIXELFORMAT_RGBA8888, SDL_TEXTUREACCESS_TARGET, size, size); if (!atlas.texture) { SDL_Log("Failed to create atlas texture: %s", SDL_GetError()); } } void AtlasRenderer::RebuildAtlas(Atlas& atlas) { // Clear used regions 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_); for (auto& entry : atlas.entries) { if (entry.in_use && entry.texture) { SDL_RenderCopy(renderer_, entry.texture, nullptr, &entry.uv_rect); MarkRegionUsed(atlas, entry.uv_rect, true); } } SDL_SetRenderTarget(renderer_, nullptr); } SDL_Rect AtlasRenderer::FindFreeRegion(Atlas& atlas, int width, int height) { // Simple first-fit algorithm for (int y = 0; y <= atlas.size - height; ++y) { for (int x = 0; x <= atlas.size - width; ++x) { bool can_fit = true; // Check if region is free for (int dy = 0; dy < height && can_fit; ++dy) { for (int dx = 0; dx < width && can_fit; ++dx) { int index = (y + dy) * atlas.size + (x + dx); if (index >= static_cast(atlas.used_regions.size()) || atlas.used_regions[index]) { can_fit = false; } } } if (can_fit) { return {x, y, width, height}; } } } return {0, 0, 0, 0}; // No space found } void AtlasRenderer::MarkRegionUsed(Atlas& atlas, const SDL_Rect& rect, bool used) { for (int y = rect.y; y < rect.y + rect.h; ++y) { for (int x = rect.x; x < rect.x + rect.w; ++x) { int index = y * atlas.size + x; if (index >= 0 && index < static_cast(atlas.used_regions.size())) { atlas.used_regions[index] = used; } } } } } // namespace gfx } // namespace yaze