From f9753532a48e847f4bfd13f6738512726dc0d324 Mon Sep 17 00:00:00 2001 From: scawful Date: Thu, 9 Oct 2025 22:14:33 -0400 Subject: [PATCH] feat: Enhance Dungeon Editor with Advanced Room Properties and Layout Visualization - Introduced advanced room properties UI in the Dungeon Editor, allowing users to set effects and tags for rooms through dropdown menus. - Updated the rendering logic to visualize room layouts, including walls, floors, pits, water, and doors, with appropriate color coding for clarity. - Improved texture management by processing texture updates immediately to ensure objects appear correctly in the editor. - Organized ROM address constants into a new header file for better maintainability and clarity in the codebase. - Refactored existing code to utilize the new constants, ensuring a cleaner and more consistent naming convention. --- .../editor/dungeon/dungeon_canvas_viewer.cc | 126 +++++- .../editor/dungeon/dungeon_canvas_viewer.h | 2 +- src/app/gfx/graphics_optimizer.cc | 393 ++++++++++-------- src/app/gfx/graphics_optimizer.h | 3 - .../zelda3/dungeon/dungeon_rom_addresses.h | 100 +++++ src/app/zelda3/dungeon/room.cc | 7 + src/app/zelda3/dungeon/room.h | 106 ++--- 7 files changed, 485 insertions(+), 252 deletions(-) create mode 100644 src/app/zelda3/dungeon/dungeon_rom_addresses.h diff --git a/src/app/editor/dungeon/dungeon_canvas_viewer.cc b/src/app/editor/dungeon/dungeon_canvas_viewer.cc index ce45a272..e51d644e 100644 --- a/src/app/editor/dungeon/dungeon_canvas_viewer.cc +++ b/src/app/editor/dungeon/dungeon_canvas_viewer.cc @@ -85,23 +85,69 @@ void DungeonCanvasViewer::DrawDungeonCanvas(int room_id) { ImGui::EndTable(); } - // Layer visibility controls in compact table + // Advanced room properties (Effect, Tags, Layer Merge) ImGui::Separator(); - if (ImGui::BeginTable("##LayerControls", 3, ImGuiTableFlags_SizingStretchSame)) { + if (ImGui::BeginTable("##AdvancedProperties", 3, ImGuiTableFlags_SizingStretchSame | ImGuiTableFlags_Borders)) { + ImGui::TableSetupColumn("Effect"); + ImGui::TableSetupColumn("Tag 1"); + ImGui::TableSetupColumn("Tag 2"); + ImGui::TableHeadersRow(); + + ImGui::TableNextRow(); + + // Effect dropdown + ImGui::TableNextColumn(); + const char* effect_names[] = {"Nothing", "One", "Moving Floor", "Moving Water", "Four", "Red Flashes", "Torch Show Floor", "Ganon Room"}; + int effect_val = static_cast(room.effect()); + if (ImGui::Combo("##Effect", &effect_val, effect_names, 8)) { + room.SetEffect(static_cast(effect_val)); + } + + // Tag 1 dropdown (abbreviated for space) + ImGui::TableNextColumn(); + const char* tag_names[] = {"Nothing", "NW Kill", "NE Kill", "SW Kill", "SE Kill", "W Kill", "E Kill", "N Kill", "S Kill", + "Clear Quad", "Clear Room", "NW Push", "NE Push", "SW Push", "SE Push", "W Push", "E Push", + "N Push", "S Push", "Push Block", "Pull Lever", "Clear Level", "Switch Hold", "Switch Toggle"}; + int tag1_val = static_cast(room.tag1()); + if (ImGui::Combo("##Tag1", &tag1_val, tag_names, 24)) { + room.SetTag1(static_cast(tag1_val)); + } + + // Tag 2 dropdown + ImGui::TableNextColumn(); + int tag2_val = static_cast(room.tag2()); + if (ImGui::Combo("##Tag2", &tag2_val, tag_names, 24)) { + room.SetTag2(static_cast(tag2_val)); + } + + ImGui::EndTable(); + } + + // Layer visibility and merge controls + ImGui::Separator(); + if (ImGui::BeginTable("##LayerControls", 4, ImGuiTableFlags_SizingStretchSame)) { ImGui::TableNextRow(); ImGui::TableNextColumn(); auto& layer_settings = GetRoomLayerSettings(room_id); - ImGui::Checkbox("Show BG1", &layer_settings.bg1_visible); + ImGui::Checkbox("BG1", &layer_settings.bg1_visible); ImGui::TableNextColumn(); - ImGui::Checkbox("Show BG2", &layer_settings.bg2_visible); + ImGui::Checkbox("BG2", &layer_settings.bg2_visible); ImGui::TableNextColumn(); - // BG2 layer type dropdown - const char* bg2_layer_types[] = {"Normal", "Trans", "Add", "Dark", "Off"}; + // BG2 layer type + const char* bg2_types[] = {"Norm", "Trans", "Add", "Dark", "Off"}; ImGui::SetNextItemWidth(-FLT_MIN); - ImGui::Combo("##BG2Type", &layer_settings.bg2_layer_type, bg2_layer_types, 5); + ImGui::Combo("##BG2Type", &layer_settings.bg2_layer_type, bg2_types, 5); + + ImGui::TableNextColumn(); + // Layer merge type + const char* merge_types[] = {"Off", "Parallax", "Dark", "On top", "Translucent", "Addition", "Normal", "Transparent", "Dark room"}; + int merge_val = room.layer_merging().ID; + if (ImGui::Combo("##Merge", &merge_val, merge_types, 9)) { + room.SetLayerMerging(zelda3::kLayerMergeTypeList[merge_val]); + } ImGui::EndTable(); } @@ -391,20 +437,61 @@ void DungeonCanvasViewer::CalculateWallDimensions(const zelda3::RoomObject& obje } // Room layout visualization -void DungeonCanvasViewer::DrawRoomLayout(const zelda3::Room& room) { +void DungeonCanvasViewer::DrawRoomLayout(zelda3::Room& room) { // Draw room layout structural elements (walls, floors, pits) - // This provides visual context for where objects should be placed + // This is the immovable BASE LAYER that defines room structure - const auto& layout = room.GetLayout(); + auto& layout = room.GetLayout(); - // Get dimensions (64x64 tiles = 512x512 pixels) - auto [width_tiles, height_tiles] = layout.GetDimensions(); + // Ensure layout is loaded (critical!) + if (layout.GetObjects().empty()) { + auto status = layout.LoadLayout(room.id()); + if (!status.ok()) { + LOG_DEBUG("[DrawRoomLayout]", "Failed to load layout: %s", status.message().data()); + return; + } + } - // TODO: Get layout objects by type - // For now, draw a grid overlay to show the room structure - // Future: Implement GetObjectsByType() in RoomLayout + // Get structural elements by type + auto walls = layout.GetObjectsByType(zelda3::RoomLayoutObject::Type::kWall); + auto floors = layout.GetObjectsByType(zelda3::RoomLayoutObject::Type::kFloor); + auto pits = layout.GetObjectsByType(zelda3::RoomLayoutObject::Type::kPit); + auto water = layout.GetObjectsByType(zelda3::RoomLayoutObject::Type::kWater); + auto doors = layout.GetObjectsByType(zelda3::RoomLayoutObject::Type::kDoor); - LOG_DEBUG("[DrawRoomLayout]", "Room layout: %dx%d tiles", width_tiles, height_tiles); + LOG_DEBUG("[DrawRoomLayout]", "Layout elements: %zu walls, %zu floors, %zu pits, %zu water, %zu doors", + walls.size(), floors.size(), pits.size(), water.size(), doors.size()); + + // Get canvas scale for proper sizing + float scale = canvas_.global_scale(); + int tile_size = static_cast(8 * scale); // 8x8 pixels scaled + + // Draw walls (dark gray, semi-transparent) - immovable structure + for (const auto& wall : walls) { + auto [x, y] = RoomToCanvasCoordinates(wall.x(), wall.y()); + canvas_.DrawRect(x, y, tile_size, tile_size, ImVec4(0.2f, 0.2f, 0.2f, 0.5f)); + } + + // Draw pits (orange warning) - damage zones + for (const auto& pit : pits) { + auto [x, y] = RoomToCanvasCoordinates(pit.x(), pit.y()); + canvas_.DrawRect(x, y, tile_size, tile_size, ImVec4(1.0f, 0.4f, 0.0f, 0.6f)); + } + + // Draw water (blue, semi-transparent) + for (const auto& water_tile : water) { + auto [x, y] = RoomToCanvasCoordinates(water_tile.x(), water_tile.y()); + canvas_.DrawRect(x, y, tile_size, tile_size, ImVec4(0.2f, 0.4f, 0.8f, 0.5f)); + } + + // Draw doors (purple) - connection points + for (const auto& door : doors) { + auto [x, y] = RoomToCanvasCoordinates(door.x(), door.y()); + canvas_.DrawRect(x, y, tile_size, tile_size, ImVec4(0.8f, 0.2f, 0.8f, 0.7f)); + if (scale >= 1.0f) { // Only show label if zoomed in enough + canvas_.DrawText("DOOR", x + 2, y + 2); + } + } } // Object visualization methods @@ -414,6 +501,9 @@ void DungeonCanvasViewer::DrawObjectPositionOutlines(const zelda3::Room& room) { const auto& objects = room.GetTileObjects(); + // Get canvas scale for proper sizing + float scale = canvas_.global_scale(); + for (const auto& obj : objects) { // Convert object position (tile coordinates) to canvas pixel coordinates auto [canvas_x, canvas_y] = RoomToCanvasCoordinates(obj.x(), obj.y()); @@ -431,6 +521,10 @@ void DungeonCanvasViewer::DrawObjectPositionOutlines(const zelda3::Room& room) { width = (size_h + 1) * 8; height = (size_v + 1) * 8; + // Apply canvas scale + width = static_cast(width * scale); + height = static_cast(height * scale); + // Clamp to reasonable sizes width = std::min(width, 512); height = std::min(height, 512); diff --git a/src/app/editor/dungeon/dungeon_canvas_viewer.h b/src/app/editor/dungeon/dungeon_canvas_viewer.h index 3d68801f..df1fbf5c 100644 --- a/src/app/editor/dungeon/dungeon_canvas_viewer.h +++ b/src/app/editor/dungeon/dungeon_canvas_viewer.h @@ -105,7 +105,7 @@ class DungeonCanvasViewer { void CalculateWallDimensions(const zelda3::RoomObject& object, int& width, int& height); // Visualization - void DrawRoomLayout(const zelda3::Room& room); + void DrawRoomLayout(zelda3::Room& room); // Non-const to call LoadLayout void DrawObjectPositionOutlines(const zelda3::Room& room); // Room graphics management diff --git a/src/app/gfx/graphics_optimizer.cc b/src/app/gfx/graphics_optimizer.cc index 33e8ef3b..847591e1 100644 --- a/src/app/gfx/graphics_optimizer.cc +++ b/src/app/gfx/graphics_optimizer.cc @@ -1,12 +1,11 @@ #include "app/gfx/graphics_optimizer.h" #include -#include +#include +#include #include #include "app/gfx/bpp_format_manager.h" -#include "app/gfx/atlas_renderer.h" -#include "util/log.h" namespace yaze { namespace gfx { @@ -20,181 +19,201 @@ void GraphicsOptimizer::Initialize() { max_quality_loss_ = 0.1f; min_memory_savings_ = 1024; performance_threshold_ = 0.05f; - + optimization_stats_.clear(); optimization_cache_.clear(); } -OptimizationResult GraphicsOptimizer::OptimizeSheet(const std::vector& sheet_data, - int sheet_id, - const SnesPalette& palette, - OptimizationStrategy strategy) { +OptimizationResult GraphicsOptimizer::OptimizeSheet( + const std::vector& sheet_data, int sheet_id, + const SnesPalette& palette, OptimizationStrategy strategy) { ScopedTimer timer("graphics_optimize_sheet"); - + OptimizationResult result; - + try { // Analyze the sheet SheetOptimizationData data = AnalyzeSheet(sheet_data, sheet_id, palette); - + if (!data.is_convertible) { result.success = false; result.message = "Sheet is not suitable for optimization"; return result; } - + // Check if optimization meets criteria if (!ShouldOptimize(data, strategy)) { result.success = false; result.message = "Optimization does not meet criteria"; return result; } - + // Calculate potential savings result.memory_saved = data.current_size - data.optimized_size; - result.performance_gain = CalculatePerformanceGain(data.current_format, data.recommended_format); - result.quality_loss = CalculateQualityLoss(data.current_format, data.recommended_format, sheet_data); - + result.performance_gain = + CalculatePerformanceGain(data.current_format, data.recommended_format); + result.quality_loss = CalculateQualityLoss( + data.current_format, data.recommended_format, sheet_data); + // Check if optimization is worthwhile - if (result.memory_saved < min_memory_savings_ && + if (result.memory_saved < min_memory_savings_ && result.performance_gain < performance_threshold_ && result.quality_loss > max_quality_loss_) { result.success = false; result.message = "Optimization benefits do not justify quality loss"; return result; } - + result.success = true; result.message = "Optimization recommended"; result.recommended_formats.push_back(data.recommended_format); result.sheet_recommendations[sheet_id] = data.recommended_format; - + UpdateOptimizationStats("sheets_optimized", 1.0); - UpdateOptimizationStats("memory_saved", static_cast(result.memory_saved)); - UpdateOptimizationStats("performance_gain", static_cast(result.performance_gain)); - + UpdateOptimizationStats("memory_saved", + static_cast(result.memory_saved)); + UpdateOptimizationStats("performance_gain", + static_cast(result.performance_gain)); + } catch (const std::exception& e) { result.success = false; result.message = "Optimization failed: " + std::string(e.what()); SDL_Log("GraphicsOptimizer::OptimizeSheet failed: %s", e.what()); } - + return result; } -OptimizationResult GraphicsOptimizer::OptimizeSheets(const std::unordered_map>& sheets, - const std::unordered_map& palettes, - OptimizationStrategy strategy) { +OptimizationResult GraphicsOptimizer::OptimizeSheets( + const std::unordered_map>& sheets, + const std::unordered_map& palettes, + OptimizationStrategy strategy) { ScopedTimer timer("graphics_optimize_sheets"); - + OptimizationResult result; result.success = true; - + size_t total_memory_saved = 0; float total_performance_gain = 0.0f; float total_quality_loss = 0.0f; int optimized_sheets = 0; - + for (const auto& [sheet_id, sheet_data] : sheets) { auto palette_it = palettes.find(sheet_id); if (palette_it == palettes.end()) { - continue; // Skip sheets without palettes + continue; // Skip sheets without palettes } - - auto sheet_result = OptimizeSheet(sheet_data, sheet_id, palette_it->second, strategy); - + + auto sheet_result = + OptimizeSheet(sheet_data, sheet_id, palette_it->second, strategy); + if (sheet_result.success) { total_memory_saved += sheet_result.memory_saved; total_performance_gain += sheet_result.performance_gain; total_quality_loss += sheet_result.quality_loss; optimized_sheets++; - + // Merge recommendations - result.recommended_formats.insert(result.recommended_formats.end(), - sheet_result.recommended_formats.begin(), - sheet_result.recommended_formats.end()); - result.sheet_recommendations.insert(sheet_result.sheet_recommendations.begin(), - sheet_result.sheet_recommendations.end()); + result.recommended_formats.insert( + result.recommended_formats.end(), + sheet_result.recommended_formats.begin(), + sheet_result.recommended_formats.end()); + result.sheet_recommendations.insert( + sheet_result.sheet_recommendations.begin(), + sheet_result.sheet_recommendations.end()); } } - + result.memory_saved = total_memory_saved; - result.performance_gain = optimized_sheets > 0 ? total_performance_gain / optimized_sheets : 0.0f; - result.quality_loss = optimized_sheets > 0 ? total_quality_loss / optimized_sheets : 0.0f; - + result.performance_gain = + optimized_sheets > 0 ? total_performance_gain / optimized_sheets : 0.0f; + result.quality_loss = + optimized_sheets > 0 ? total_quality_loss / optimized_sheets : 0.0f; + if (optimized_sheets > 0) { - result.message = "Optimized " + std::to_string(optimized_sheets) + " sheets"; + result.message = + "Optimized " + std::to_string(optimized_sheets) + " sheets"; } else { result.success = false; result.message = "No sheets could be optimized"; } - + UpdateOptimizationStats("batch_optimizations", 1.0); - UpdateOptimizationStats("total_sheets_processed", static_cast(sheets.size())); - + UpdateOptimizationStats("total_sheets_processed", + static_cast(sheets.size())); + return result; } -SheetOptimizationData GraphicsOptimizer::AnalyzeSheet(const std::vector& sheet_data, - int sheet_id, - const SnesPalette& palette) { +SheetOptimizationData GraphicsOptimizer::AnalyzeSheet( + const std::vector& sheet_data, int sheet_id, + const SnesPalette& palette) { // Check cache first std::string cache_key = GenerateCacheKey(sheet_data, sheet_id); auto cache_it = optimization_cache_.find(cache_key); if (cache_it != optimization_cache_.end()) { return cache_it->second; } - + ScopedTimer timer("graphics_analyze_sheet"); - + SheetOptimizationData data; data.sheet_id = sheet_id; data.current_size = sheet_data.size(); - + // Detect current format - data.current_format = BppFormatManager::Get().DetectFormat(sheet_data, 128, 32); // Standard sheet size - + data.current_format = BppFormatManager::Get().DetectFormat( + sheet_data, 128, 32); // Standard sheet size + // Analyze color usage data.colors_used = CountUsedColors(sheet_data, palette); - + // Determine optimal format - data.recommended_format = DetermineOptimalFormat(sheet_data, palette, OptimizationStrategy::kBalanced); - + data.recommended_format = DetermineOptimalFormat( + sheet_data, palette, OptimizationStrategy::kBalanced); + // Calculate potential savings - const auto& current_info = BppFormatManager::Get().GetFormatInfo(data.current_format); - const auto& recommended_info = BppFormatManager::Get().GetFormatInfo(data.recommended_format); - - data.optimized_size = (sheet_data.size() * recommended_info.bits_per_pixel) / current_info.bits_per_pixel; - data.compression_ratio = static_cast(data.current_size) / data.optimized_size; - + const auto& current_info = + BppFormatManager::Get().GetFormatInfo(data.current_format); + const auto& recommended_info = + BppFormatManager::Get().GetFormatInfo(data.recommended_format); + + data.optimized_size = (sheet_data.size() * recommended_info.bits_per_pixel) / + current_info.bits_per_pixel; + data.compression_ratio = + static_cast(data.current_size) / data.optimized_size; + // Determine if conversion is beneficial - data.is_convertible = (data.current_format != data.recommended_format) && - (data.colors_used <= recommended_info.max_colors) && - (data.compression_ratio > 1.1f); // At least 10% savings - + data.is_convertible = + (data.current_format != data.recommended_format) && + (data.colors_used <= recommended_info.max_colors) && + (data.compression_ratio > 1.1f); // At least 10% savings + data.optimization_reason = GenerateOptimizationReason(data); - + // Cache the result optimization_cache_[cache_key] = data; - + return data; } -std::unordered_map GraphicsOptimizer::GetOptimizationRecommendations( +std::unordered_map +GraphicsOptimizer::GetOptimizationRecommendations( const std::unordered_map>& sheets, const std::unordered_map& palettes) { - + std::unordered_map recommendations; - + for (const auto& [sheet_id, sheet_data] : sheets) { auto palette_it = palettes.find(sheet_id); if (palette_it == palettes.end()) { continue; } - - recommendations[sheet_id] = AnalyzeSheet(sheet_data, sheet_id, palette_it->second); + + recommendations[sheet_id] = + AnalyzeSheet(sheet_data, sheet_id, palette_it->second); } - + return recommendations; } @@ -202,33 +221,34 @@ OptimizationResult GraphicsOptimizer::ApplyOptimizations( const std::unordered_map& recommendations, std::unordered_map>& sheets, std::unordered_map& palettes) { - + ScopedTimer timer("graphics_apply_optimizations"); - + OptimizationResult result; result.success = true; - + size_t total_memory_saved = 0; int optimized_sheets = 0; - + for (const auto& [sheet_id, data] : recommendations) { if (!data.is_convertible) { continue; } - + auto sheet_it = sheets.find(sheet_id); if (sheet_it == sheets.end()) { continue; } - + try { // Convert the sheet data auto converted_data = BppFormatManager::Get().ConvertFormat( - sheet_it->second, data.current_format, data.recommended_format, 128, 32); - + sheet_it->second, data.current_format, data.recommended_format, 128, + 32); + // Update the sheet sheet_it->second = converted_data; - + // Optimize palette if needed auto palette_it = palettes.find(sheet_id); if (palette_it != palettes.end()) { @@ -236,31 +256,34 @@ OptimizationResult GraphicsOptimizer::ApplyOptimizations( for (int i = 0; i < data.colors_used; ++i) { used_colors.push_back(i); } - + palette_it->second = BppFormatManager::Get().OptimizePaletteForFormat( - palette_it->second, data.recommended_format, used_colors); + palette_it->second, data.recommended_format, used_colors); } - + total_memory_saved += data.current_size - data.optimized_size; optimized_sheets++; - + result.sheet_recommendations[sheet_id] = data.recommended_format; - + } catch (const std::exception& e) { SDL_Log("Failed to optimize sheet %d: %s", sheet_id, e.what()); } } - + result.memory_saved = total_memory_saved; result.message = "Optimized " + std::to_string(optimized_sheets) + " sheets"; - - UpdateOptimizationStats("optimizations_applied", static_cast(optimized_sheets)); - UpdateOptimizationStats("total_memory_saved", static_cast(total_memory_saved)); - + + UpdateOptimizationStats("optimizations_applied", + static_cast(optimized_sheets)); + UpdateOptimizationStats("total_memory_saved", + static_cast(total_memory_saved)); + return result; } -std::unordered_map GraphicsOptimizer::GetOptimizationStats() const { +std::unordered_map +GraphicsOptimizer::GetOptimizationStats() const { return optimization_stats_; } @@ -270,8 +293,8 @@ void GraphicsOptimizer::ClearCache() { } void GraphicsOptimizer::SetOptimizationParameters(float max_quality_loss, - size_t min_memory_savings, - float performance_threshold) { + size_t min_memory_savings, + float performance_threshold) { max_quality_loss_ = max_quality_loss; min_memory_savings_ = min_memory_savings; performance_threshold_ = performance_threshold; @@ -279,166 +302,197 @@ void GraphicsOptimizer::SetOptimizationParameters(float max_quality_loss, // Helper method implementations -BppFormat GraphicsOptimizer::DetermineOptimalFormat(const std::vector& data, - const SnesPalette& palette, - OptimizationStrategy strategy) { +BppFormat GraphicsOptimizer::DetermineOptimalFormat( + const std::vector& data, const SnesPalette& palette, + OptimizationStrategy strategy) { int colors_used = CountUsedColors(data, palette); - + // Determine optimal format based on color usage and strategy switch (strategy) { case OptimizationStrategy::kMemoryOptimized: - if (colors_used <= 4) return BppFormat::kBpp2; - if (colors_used <= 8) return BppFormat::kBpp3; - if (colors_used <= 16) return BppFormat::kBpp4; + if (colors_used <= 4) + return BppFormat::kBpp2; + if (colors_used <= 8) + return BppFormat::kBpp3; + if (colors_used <= 16) + return BppFormat::kBpp4; break; - + case OptimizationStrategy::kPerformanceOptimized: // Prefer formats that work well with atlas rendering - if (colors_used <= 16) return BppFormat::kBpp4; + if (colors_used <= 16) + return BppFormat::kBpp4; break; - + case OptimizationStrategy::kQualityOptimized: // Only optimize if significant memory savings - if (colors_used <= 4) return BppFormat::kBpp2; + if (colors_used <= 4) + return BppFormat::kBpp2; break; - + case OptimizationStrategy::kBalanced: - if (colors_used <= 4) return BppFormat::kBpp2; - if (colors_used <= 8) return BppFormat::kBpp3; - if (colors_used <= 16) return BppFormat::kBpp4; + if (colors_used <= 4) + return BppFormat::kBpp2; + if (colors_used <= 8) + return BppFormat::kBpp3; + if (colors_used <= 16) + return BppFormat::kBpp4; break; } - - return BppFormat::kBpp8; // Default to 8BPP + + return BppFormat::kBpp8; // Default to 8BPP } -float GraphicsOptimizer::CalculateQualityLoss(BppFormat from_format, BppFormat to_format, - const std::vector& data) { - if (from_format == to_format) return 0.0f; - +float GraphicsOptimizer::CalculateQualityLoss( + BppFormat from_format, BppFormat to_format, + const std::vector& data) { + if (from_format == to_format) + return 0.0f; + // Higher BPP to lower BPP conversions may lose quality if (static_cast(from_format) > static_cast(to_format)) { int bpp_diff = static_cast(from_format) - static_cast(to_format); - return std::min(1.0f, static_cast(bpp_diff) * 0.1f); // 10% loss per BPP level + return std::min( + 1.0f, static_cast(bpp_diff) * 0.1f); // 10% loss per BPP level } - - return 0.0f; // Lower to higher BPP is lossless + + return 0.0f; // Lower to higher BPP is lossless } -size_t GraphicsOptimizer::CalculateMemorySavings(BppFormat from_format, BppFormat to_format, - const std::vector& data) { - if (from_format == to_format) return 0; - +size_t GraphicsOptimizer::CalculateMemorySavings( + BppFormat from_format, BppFormat to_format, + const std::vector& data) { + if (from_format == to_format) + return 0; + const auto& from_info = BppFormatManager::Get().GetFormatInfo(from_format); const auto& to_info = BppFormatManager::Get().GetFormatInfo(to_format); - + size_t from_size = data.size(); - size_t to_size = (from_size * to_info.bits_per_pixel) / from_info.bits_per_pixel; - + size_t to_size = + (from_size * to_info.bits_per_pixel) / from_info.bits_per_pixel; + return from_size - to_size; } -float GraphicsOptimizer::CalculatePerformanceGain(BppFormat from_format, BppFormat to_format) { - if (from_format == to_format) return 0.0f; - +float GraphicsOptimizer::CalculatePerformanceGain(BppFormat from_format, + BppFormat to_format) { + if (from_format == to_format) + return 0.0f; + // Lower BPP formats generally render faster if (static_cast(from_format) > static_cast(to_format)) { int bpp_diff = static_cast(from_format) - static_cast(to_format); - return std::min(0.5f, static_cast(bpp_diff) * 0.1f); // 10% gain per BPP level + return std::min( + 0.5f, static_cast(bpp_diff) * 0.1f); // 10% gain per BPP level } - + return 0.0f; } -bool GraphicsOptimizer::ShouldOptimize(const SheetOptimizationData& data, OptimizationStrategy strategy) { - if (!data.is_convertible) return false; - +bool GraphicsOptimizer::ShouldOptimize(const SheetOptimizationData& data, + OptimizationStrategy strategy) { + if (!data.is_convertible) + return false; + switch (strategy) { case OptimizationStrategy::kMemoryOptimized: - return data.compression_ratio > 1.2f; // At least 20% savings - + return data.compression_ratio > 1.2f; // At least 20% savings + case OptimizationStrategy::kPerformanceOptimized: - return data.compression_ratio > 1.1f; // At least 10% savings - + return data.compression_ratio > 1.1f; // At least 10% savings + case OptimizationStrategy::kQualityOptimized: - return data.compression_ratio > 1.5f; // At least 50% savings - + return data.compression_ratio > 1.5f; // At least 50% savings + case OptimizationStrategy::kBalanced: - return data.compression_ratio > 1.15f; // At least 15% savings + return data.compression_ratio > 1.15f; // At least 15% savings } - + return false; } -std::string GraphicsOptimizer::GenerateOptimizationReason(const SheetOptimizationData& data) { +std::string GraphicsOptimizer::GenerateOptimizationReason( + const SheetOptimizationData& data) { std::ostringstream reason; - - reason << "Convert from " << BppFormatManager::Get().GetFormatInfo(data.current_format).name - << " to " << BppFormatManager::Get().GetFormatInfo(data.recommended_format).name - << " (uses " << data.colors_used << " colors, " - << std::fixed << std::setprecision(1) << (data.compression_ratio - 1.0f) * 100.0f + + reason << "Convert from " + << BppFormatManager::Get().GetFormatInfo(data.current_format).name + << " to " + << BppFormatManager::Get().GetFormatInfo(data.recommended_format).name + << " (uses " << data.colors_used << " colors, " << std::fixed + << std::setprecision(1) << (data.compression_ratio - 1.0f) * 100.0f << "% memory savings)"; - + return reason.str(); } -int GraphicsOptimizer::CountUsedColors(const std::vector& data, const SnesPalette& palette) { +int GraphicsOptimizer::CountUsedColors(const std::vector& data, + const SnesPalette& palette) { std::vector used_colors(palette.size(), false); - + for (uint8_t pixel : data) { if (pixel < palette.size()) { used_colors[pixel] = true; } } - + int count = 0; for (bool used : used_colors) { - if (used) count++; + if (used) + count++; } - + return count; } -float GraphicsOptimizer::CalculateColorEfficiency(const std::vector& data, const SnesPalette& palette) { +float GraphicsOptimizer::CalculateColorEfficiency( + const std::vector& data, const SnesPalette& palette) { int used_colors = CountUsedColors(data, palette); return static_cast(used_colors) / palette.size(); } -std::vector GraphicsOptimizer::AnalyzeColorDistribution(const std::vector& data) { +std::vector GraphicsOptimizer::AnalyzeColorDistribution( + const std::vector& data) { std::vector distribution(256, 0); - + for (uint8_t pixel : data) { distribution[pixel]++; } - + return distribution; } -std::string GraphicsOptimizer::GenerateCacheKey(const std::vector& data, int sheet_id) { +std::string GraphicsOptimizer::GenerateCacheKey( + const std::vector& data, int sheet_id) { std::ostringstream key; key << "sheet_" << sheet_id << "_" << data.size(); - + // Add hash of data for uniqueness size_t hash = 0; for (size_t i = 0; i < std::min(data.size(), size_t(1024)); ++i) { hash = hash * 31 + data[i]; } key << "_" << hash; - + return key.str(); } -void GraphicsOptimizer::UpdateOptimizationStats(const std::string& operation, double value) { +void GraphicsOptimizer::UpdateOptimizationStats(const std::string& operation, + double value) { optimization_stats_[operation] += value; } // GraphicsOptimizationScope implementation -GraphicsOptimizationScope::GraphicsOptimizationScope(OptimizationStrategy strategy, int sheet_count) - : strategy_(strategy), sheet_count_(sheet_count), +GraphicsOptimizationScope::GraphicsOptimizationScope( + OptimizationStrategy strategy, int sheet_count) + : strategy_(strategy), + sheet_count_(sheet_count), timer_("graphics_optimize_scope") { std::ostringstream op_name; - op_name << "graphics_optimize_" << static_cast(strategy) << "_" << sheet_count; + op_name << "graphics_optimize_" << static_cast(strategy) << "_" + << sheet_count; operation_name_ = op_name.str(); } @@ -446,7 +500,8 @@ GraphicsOptimizationScope::~GraphicsOptimizationScope() { // Timer automatically ends in destructor } -void GraphicsOptimizationScope::AddSheet(int sheet_id, size_t original_size, size_t optimized_size) { +void GraphicsOptimizationScope::AddSheet(int sheet_id, size_t original_size, + size_t optimized_size) { result_.memory_saved += (original_size - optimized_size); } diff --git a/src/app/gfx/graphics_optimizer.h b/src/app/gfx/graphics_optimizer.h index 4bfc545b..18b4c8b6 100644 --- a/src/app/gfx/graphics_optimizer.h +++ b/src/app/gfx/graphics_optimizer.h @@ -3,12 +3,9 @@ #include #include -#include #include -#include "app/gfx/bitmap.h" #include "app/gfx/bpp_format_manager.h" -#include "app/gfx/atlas_renderer.h" #include "app/gfx/performance_profiler.h" namespace yaze { diff --git a/src/app/zelda3/dungeon/dungeon_rom_addresses.h b/src/app/zelda3/dungeon/dungeon_rom_addresses.h new file mode 100644 index 00000000..ab079da3 --- /dev/null +++ b/src/app/zelda3/dungeon/dungeon_rom_addresses.h @@ -0,0 +1,100 @@ +#ifndef YAZE_APP_ZELDA3_DUNGEON_ROM_ADDRESSES_H +#define YAZE_APP_ZELDA3_DUNGEON_ROM_ADDRESSES_H + +#include + +namespace yaze { +namespace zelda3 { + +// ============================================================================ +// Dungeon ROM Address Constants +// ============================================================================ +// This file contains all ROM addresses for dungeon-related data in ALTTP. +// Organized by category for readability and maintainability. + +// === Room Data Pointers === +constexpr int kRoomObjectLayoutPointer = 0x882D; // Layout pointer table +constexpr int kRoomObjectPointer = 0x874C; // Object data pointer (Long) +constexpr int kRoomHeaderPointer = 0xB5DD; // Room header pointer (LONG) +constexpr int kRoomHeaderPointerBank = 0xB5E7; // Room header bank byte + +// === Palette Data === +constexpr int kDungeonsMainBgPalettePointers = 0xDEC4B; // JP Same +constexpr int kDungeonsPalettes = 0xDD734; // Dungeon palette data + +// === Item & Sprite Data === +constexpr int kRoomItemsPointers = 0xDB69; // JP 0xDB67 +constexpr int kRoomsSpritePointer = 0x4C298; // JP Same (2-byte bank 09D62E) +constexpr int kSpriteBlocksetPointer = 0x5B57; // Sprite graphics pointer + +// === Graphics Data === +constexpr int kGfxGroupsPointer = 0x6237; // Graphics group table +constexpr int kTileAddress = 0x001B52; // Main tile graphics +constexpr int kTileAddressFloor = 0x001B5A; // Floor tile graphics + +// === Block Data === +constexpr int kBlocksLength = 0x8896; // Word value +constexpr int kBlocksPointer1 = 0x15AFA; // Block data pointer 1 +constexpr int kBlocksPointer2 = 0x15B01; // Block data pointer 2 +constexpr int kBlocksPointer3 = 0x15B08; // Block data pointer 3 +constexpr int kBlocksPointer4 = 0x15B0F; // Block data pointer 4 + +// === Chests === +constexpr int kChestsLengthPointer = 0xEBF6; // Chest count pointer +constexpr int kChestsDataPointer1 = 0xEBFB; // Chest data start + +// === Torches === +constexpr int kTorchData = 0x2736A; // JP 0x2704A +constexpr int kTorchesLengthPointer = 0x88C1; // Torch count pointer + +// === Pits & Warps === +constexpr int kPitPointer = 0x394AB; // Pit/hole data +constexpr int kPitCount = 0x394A6; // Number of pits + +// === Doors === +constexpr int kDoorPointers = 0xF83C0; // Door data table +constexpr int kDoorGfxUp = 0x4D9E; // Door graphics (up) +constexpr int kDoorGfxDown = 0x4E06; // Door graphics (down) +constexpr int kDoorGfxCaveExitDown = 0x4E06; // Cave exit door +constexpr int kDoorGfxLeft = 0x4E66; // Door graphics (left) +constexpr int kDoorGfxRight = 0x4EC6; // Door graphics (right) +constexpr int kDoorPosUp = 0x197E; // Door position (up) +constexpr int kDoorPosDown = 0x1996; // Door position (down) +constexpr int kDoorPosLeft = 0x19AE; // Door position (left) +constexpr int kDoorPosRight = 0x19C6; // Door position (right) + +// === Sprites === +constexpr int kSpritesData = 0x4D8B0; // Sprite data start +constexpr int kSpritesDataEmptyRoom = 0x4D8AE; // Empty room sprite marker +constexpr int kSpritesEndData = 0x4EC9E; // Sprite data end +constexpr int kDungeonSpritePointers = 0x090000; // Dungeon sprite pointer table + +// === Messages === +constexpr int kMessagesIdDungeon = 0x3F61D; // Dungeon message IDs + +// === Room Metadata === +constexpr int kNumberOfRooms = 296; // Total dungeon rooms (0x00-0x127) + +// Stair objects (special handling) +constexpr uint16_t kStairsObjects[] = {0x139, 0x138, 0x13B, 0x12E, 0x12D}; + +// === Layout Pointers (referenced in comments) === +// Layout00 ptr: 0x47EF04 +// Layout01 ptr: 0xAFEF04 +// Layout02 ptr: 0xF0EF04 +// Layout03 ptr: 0x4CF004 +// Layout04 ptr: 0xA8F004 +// Layout05 ptr: 0xECF004 +// Layout06 ptr: 0x48F104 +// Layout07 ptr: 0xA4F104 + +// === Notes === +// - Layout arrays are NOT exactly the same as rooms +// - Object array is terminated by 0xFFFF (no layers) +// - In normal room, 0xFFFF goes to next layer (layers 0, 1, 2) + +} // namespace zelda3 +} // namespace yaze + +#endif // YAZE_APP_ZELDA3_DUNGEON_ROM_ADDRESSES_H + diff --git a/src/app/zelda3/dungeon/room.cc b/src/app/zelda3/dungeon/room.cc index 98eeb737..c45dad29 100644 --- a/src/app/zelda3/dungeon/room.cc +++ b/src/app/zelda3/dungeon/room.cc @@ -331,17 +331,24 @@ void Room::RenderRoomGraphics() { // Update textures with all the data (floor + background + objects + palette) if (bg1_bmp.texture()) { // Texture exists - UPDATE it with new object data + LOG_DEBUG("[RenderRoomGraphics]", "Queueing UPDATE for existing textures"); gfx::Arena::Get().QueueTextureCommand( gfx::Arena::TextureCommandType::UPDATE, &bg1_bmp); gfx::Arena::Get().QueueTextureCommand( gfx::Arena::TextureCommandType::UPDATE, &bg2_bmp); } else { // No texture yet - CREATE it + LOG_DEBUG("[RenderRoomGraphics]", "Queueing CREATE for new textures"); gfx::Arena::Get().QueueTextureCommand( gfx::Arena::TextureCommandType::CREATE, &bg1_bmp); gfx::Arena::Get().QueueTextureCommand( gfx::Arena::TextureCommandType::CREATE, &bg2_bmp); } + + // CRITICAL: Process texture queue immediately so objects appear! + // Don't wait for next frame - update NOW! + gfx::Arena::Get().ProcessTextureQueue(nullptr); + LOG_DEBUG("[RenderRoomGraphics]", "Processed texture queue immediately"); } void Room::RenderObjectsToBackground() { diff --git a/src/app/zelda3/dungeon/room.h b/src/app/zelda3/dungeon/room.h index 008b66f2..ef65cf0a 100644 --- a/src/app/zelda3/dungeon/room.h +++ b/src/app/zelda3/dungeon/room.h @@ -9,6 +9,7 @@ #include "app/rom.h" #include "app/gfx/background_buffer.h" +#include "app/zelda3/dungeon/dungeon_rom_addresses.h" #include "app/zelda3/dungeon/room_layout.h" #include "app/zelda3/dungeon/room_object.h" #include "app/zelda3/sprite/sprite.h" @@ -16,72 +17,51 @@ namespace yaze { namespace zelda3 { -// room_object_layout_pointer 0x882D -// room_object_pointer 0x874C -// 0x882D -> readlong() -> 2FEF04 (04EF2F -> toPC->026F2F) -> - -// 47EF04 ; layout00 ptr -// AFEF04 ; layout01 ptr -// F0EF04 ; layout02 ptr -// 4CF004 ; layout03 ptr -// A8F004 ; layout04 ptr -// ECF004 ; layout05 ptr -// 48F104 ; layout06 ptr -// A4F104 ; layout07 ptr -// also they are not exactly the same as rooms -// the object array is terminated by a 0xFFFF there's no layers -// in normal room when you encounter a 0xFFFF it goes to the next layer - -constexpr int room_object_layout_pointer = 0x882D; -constexpr int room_object_pointer = 0x874C; // Long pointer - -constexpr int dungeons_main_bg_palette_pointers = 0xDEC4B; // JP Same -constexpr int dungeons_palettes = 0xDD734; -constexpr int room_items_pointers = 0xDB69; // JP 0xDB67 -constexpr int rooms_sprite_pointer = 0x4C298; // JP Same //2byte bank 09D62E -constexpr int kRoomHeaderPointer = 0xB5DD; // LONG -constexpr int kRoomHeaderPointerBank = 0xB5E7; // JP Same -constexpr int gfx_groups_pointer = 0x6237; -constexpr int chests_length_pointer = 0xEBF6; -constexpr int chests_data_pointer1 = 0xEBFB; - -constexpr int messages_id_dungeon = 0x3F61D; - -constexpr int blocks_length = 0x8896; // Word value -constexpr int blocks_pointer1 = 0x15AFA; -constexpr int blocks_pointer2 = 0x15B01; -constexpr int blocks_pointer3 = 0x15B08; -constexpr int blocks_pointer4 = 0x15B0F; -constexpr int torch_data = 0x2736A; // JP 0x2704A -constexpr int torches_length_pointer = 0x88C1; -constexpr int sprite_blockset_pointer = 0x5B57; - -constexpr int sprites_data = 0x4D8B0; -constexpr int sprites_data_empty_room = 0x4D8AE; -constexpr int sprites_end_data = 0x4EC9E; -constexpr int pit_pointer = 0x394AB; -constexpr int pit_count = 0x394A6; -constexpr int doorPointers = 0xF83C0; - -// doors -constexpr int door_gfx_up = 0x4D9E; -constexpr int door_gfx_down = 0x4E06; -constexpr int door_gfx_cavexit_down = 0x4E06; -constexpr int door_gfx_left = 0x4E66; -constexpr int door_gfx_right = 0x4EC6; -constexpr int door_pos_up = 0x197E; -constexpr int door_pos_down = 0x1996; -constexpr int door_pos_left = 0x19AE; -constexpr int door_pos_right = 0x19C6; - -constexpr int dungeon_spr_ptrs = 0x090000; - -constexpr int NumberOfRooms = 296; +// ROM addresses moved to dungeon_rom_addresses.h for better organization +// Use kPrefixedNames for new code (clean naming convention) +// Legacy aliases for backward compatibility (gradual migration) +constexpr int room_object_layout_pointer = kRoomObjectLayoutPointer; +constexpr int room_object_pointer = kRoomObjectPointer; +constexpr int dungeons_main_bg_palette_pointers = kDungeonsMainBgPalettePointers; +constexpr int dungeons_palettes = kDungeonsPalettes; +constexpr int room_items_pointers = kRoomItemsPointers; +constexpr int rooms_sprite_pointer = kRoomsSpritePointer; +constexpr int gfx_groups_pointer = kGfxGroupsPointer; +constexpr int chests_length_pointer = kChestsLengthPointer; +constexpr int chests_data_pointer1 = kChestsDataPointer1; +constexpr int messages_id_dungeon = kMessagesIdDungeon; +constexpr int blocks_length = kBlocksLength; +constexpr int blocks_pointer1 = kBlocksPointer1; +constexpr int blocks_pointer2 = kBlocksPointer2; +constexpr int blocks_pointer3 = kBlocksPointer3; +constexpr int blocks_pointer4 = kBlocksPointer4; +constexpr int torch_data = kTorchData; +constexpr int torches_length_pointer = kTorchesLengthPointer; +constexpr int sprite_blockset_pointer = kSpriteBlocksetPointer; +constexpr int sprites_data = kSpritesData; +constexpr int sprites_data_empty_room = kSpritesDataEmptyRoom; +constexpr int sprites_end_data = kSpritesEndData; +constexpr int pit_pointer = kPitPointer; +constexpr int pit_count = kPitCount; +constexpr int doorPointers = kDoorPointers; +constexpr int door_gfx_up = kDoorGfxUp; +constexpr int door_gfx_down = kDoorGfxDown; +constexpr int door_gfx_cavexit_down = kDoorGfxCaveExitDown; +constexpr int door_gfx_left = kDoorGfxLeft; +constexpr int door_gfx_right = kDoorGfxRight; +constexpr int door_pos_up = kDoorPosUp; +constexpr int door_pos_down = kDoorPosDown; +constexpr int door_pos_left = kDoorPosLeft; +constexpr int door_pos_right = kDoorPosRight; +constexpr int dungeon_spr_ptrs = kDungeonSpritePointers; +constexpr int tile_address = kTileAddress; +constexpr int tile_address_floor = kTileAddressFloor; +constexpr int NumberOfRooms = kNumberOfRooms; constexpr uint16_t stairsObjects[] = {0x139, 0x138, 0x13B, 0x12E, 0x12D}; -constexpr int tile_address = 0x001B52; -constexpr int tile_address_floor = 0x001B5A; +// TODO: Gradually migrate all code to use kPrefixedNames directly +// Then remove these legacy aliases struct LayerMergeType { uint8_t ID;