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.
This commit is contained in:
scawful
2025-10-09 22:14:33 -04:00
parent c1e69ce03e
commit f9753532a4
7 changed files with 485 additions and 252 deletions

View File

@@ -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<int>(room.effect());
if (ImGui::Combo("##Effect", &effect_val, effect_names, 8)) {
room.SetEffect(static_cast<zelda3::EffectKey>(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<int>(room.tag1());
if (ImGui::Combo("##Tag1", &tag1_val, tag_names, 24)) {
room.SetTag1(static_cast<zelda3::TagKey>(tag1_val));
}
// Tag 2 dropdown
ImGui::TableNextColumn();
int tag2_val = static_cast<int>(room.tag2());
if (ImGui::Combo("##Tag2", &tag2_val, tag_names, 24)) {
room.SetTag2(static_cast<zelda3::TagKey>(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<int>(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<int>(width * scale);
height = static_cast<int>(height * scale);
// Clamp to reasonable sizes
width = std::min(width, 512);
height = std::min(height, 512);

View File

@@ -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

View File

@@ -1,12 +1,11 @@
#include "app/gfx/graphics_optimizer.h"
#include <algorithm>
#include <cmath>
#include <iomanip>
#include <iostream>
#include <sstream>
#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<uint8_t>& sheet_data,
int sheet_id,
const SnesPalette& palette,
OptimizationStrategy strategy) {
OptimizationResult GraphicsOptimizer::OptimizeSheet(
const std::vector<uint8_t>& 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<double>(result.memory_saved));
UpdateOptimizationStats("performance_gain", static_cast<double>(result.performance_gain));
UpdateOptimizationStats("memory_saved",
static_cast<double>(result.memory_saved));
UpdateOptimizationStats("performance_gain",
static_cast<double>(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<int, std::vector<uint8_t>>& sheets,
const std::unordered_map<int, SnesPalette>& palettes,
OptimizationStrategy strategy) {
OptimizationResult GraphicsOptimizer::OptimizeSheets(
const std::unordered_map<int, std::vector<uint8_t>>& sheets,
const std::unordered_map<int, SnesPalette>& 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<double>(sheets.size()));
UpdateOptimizationStats("total_sheets_processed",
static_cast<double>(sheets.size()));
return result;
}
SheetOptimizationData GraphicsOptimizer::AnalyzeSheet(const std::vector<uint8_t>& sheet_data,
int sheet_id,
const SnesPalette& palette) {
SheetOptimizationData GraphicsOptimizer::AnalyzeSheet(
const std::vector<uint8_t>& 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<float>(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<float>(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<int, SheetOptimizationData> GraphicsOptimizer::GetOptimizationRecommendations(
std::unordered_map<int, SheetOptimizationData>
GraphicsOptimizer::GetOptimizationRecommendations(
const std::unordered_map<int, std::vector<uint8_t>>& sheets,
const std::unordered_map<int, SnesPalette>& palettes) {
std::unordered_map<int, SheetOptimizationData> 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<int, SheetOptimizationData>& recommendations,
std::unordered_map<int, std::vector<uint8_t>>& sheets,
std::unordered_map<int, SnesPalette>& 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<double>(optimized_sheets));
UpdateOptimizationStats("total_memory_saved", static_cast<double>(total_memory_saved));
UpdateOptimizationStats("optimizations_applied",
static_cast<double>(optimized_sheets));
UpdateOptimizationStats("total_memory_saved",
static_cast<double>(total_memory_saved));
return result;
}
std::unordered_map<std::string, double> GraphicsOptimizer::GetOptimizationStats() const {
std::unordered_map<std::string, double>
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<uint8_t>& data,
const SnesPalette& palette,
OptimizationStrategy strategy) {
BppFormat GraphicsOptimizer::DetermineOptimalFormat(
const std::vector<uint8_t>& 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<uint8_t>& data) {
if (from_format == to_format) return 0.0f;
float GraphicsOptimizer::CalculateQualityLoss(
BppFormat from_format, BppFormat to_format,
const std::vector<uint8_t>& data) {
if (from_format == to_format)
return 0.0f;
// Higher BPP to lower BPP conversions may lose quality
if (static_cast<int>(from_format) > static_cast<int>(to_format)) {
int bpp_diff = static_cast<int>(from_format) - static_cast<int>(to_format);
return std::min(1.0f, static_cast<float>(bpp_diff) * 0.1f); // 10% loss per BPP level
return std::min(
1.0f, static_cast<float>(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<uint8_t>& data) {
if (from_format == to_format) return 0;
size_t GraphicsOptimizer::CalculateMemorySavings(
BppFormat from_format, BppFormat to_format,
const std::vector<uint8_t>& 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<int>(from_format) > static_cast<int>(to_format)) {
int bpp_diff = static_cast<int>(from_format) - static_cast<int>(to_format);
return std::min(0.5f, static_cast<float>(bpp_diff) * 0.1f); // 10% gain per BPP level
return std::min(
0.5f, static_cast<float>(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<uint8_t>& data, const SnesPalette& palette) {
int GraphicsOptimizer::CountUsedColors(const std::vector<uint8_t>& data,
const SnesPalette& palette) {
std::vector<bool> 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<uint8_t>& data, const SnesPalette& palette) {
float GraphicsOptimizer::CalculateColorEfficiency(
const std::vector<uint8_t>& data, const SnesPalette& palette) {
int used_colors = CountUsedColors(data, palette);
return static_cast<float>(used_colors) / palette.size();
}
std::vector<int> GraphicsOptimizer::AnalyzeColorDistribution(const std::vector<uint8_t>& data) {
std::vector<int> GraphicsOptimizer::AnalyzeColorDistribution(
const std::vector<uint8_t>& data) {
std::vector<int> distribution(256, 0);
for (uint8_t pixel : data) {
distribution[pixel]++;
}
return distribution;
}
std::string GraphicsOptimizer::GenerateCacheKey(const std::vector<uint8_t>& data, int sheet_id) {
std::string GraphicsOptimizer::GenerateCacheKey(
const std::vector<uint8_t>& 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<int>(strategy) << "_" << sheet_count;
op_name << "graphics_optimize_" << static_cast<int>(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);
}

View File

@@ -3,12 +3,9 @@
#include <vector>
#include <unordered_map>
#include <memory>
#include <string>
#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 {

View File

@@ -0,0 +1,100 @@
#ifndef YAZE_APP_ZELDA3_DUNGEON_ROM_ADDRESSES_H
#define YAZE_APP_ZELDA3_DUNGEON_ROM_ADDRESSES_H
#include <cstdint>
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

View File

@@ -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() {

View File

@@ -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;