diff --git a/src/app/editor/editor_library.cmake b/src/app/editor/editor_library.cmake index cb6b8ec9..95c3884f 100644 --- a/src/app/editor/editor_library.cmake +++ b/src/app/editor/editor_library.cmake @@ -18,7 +18,6 @@ set( app/editor/dungeon/dungeon_room_loader.cc app/editor/dungeon/dungeon_usage_tracker.cc app/editor/overworld/overworld_editor.cc - app/editor/overworld/overworld_editor_manager.cc app/editor/overworld/scratch_space.cc app/editor/sprite/sprite_editor.cc app/editor/music/music_editor.cc @@ -36,7 +35,6 @@ set( app/editor/graphics/gfx_group_editor.cc app/editor/overworld/entity.cc app/editor/overworld/overworld_entity_renderer.cc - app/editor/overworld/overworld_graphics_manager.cc app/editor/system/settings_editor.cc app/editor/system/command_manager.cc app/editor/system/extension_manager.cc diff --git a/src/app/editor/overworld/overworld_editor.cc b/src/app/editor/overworld/overworld_editor.cc index 6af17928..4ed467f3 100644 --- a/src/app/editor/overworld/overworld_editor.cc +++ b/src/app/editor/overworld/overworld_editor.cc @@ -59,10 +59,6 @@ void OverworldEditor::Initialize() { [this](int map_index) { this->ForceRefreshGraphics(map_index); } ); - // Initialize OverworldEditorManager for v3 features - overworld_manager_ = - std::make_unique(&overworld_, rom_, this); - // Initialize OverworldEntityRenderer for entity visualization entity_renderer_ = std::make_unique( &overworld_, &ow_map_canvas_, &sprite_previews_); @@ -220,14 +216,6 @@ absl::Status OverworldEditor::Update() { usage_stats_card.End(); } - // v3 Settings popup - if (show_v3_settings_ && v3_settings_card.Begin(&show_v3_settings_)) { - if (rom_->is_loaded()) { - status_ = overworld_manager_->DrawV3SettingsPanel(); - } - v3_settings_card.End(); - } - // Area Configuration Panel (detailed editing) if (show_map_properties_panel_) { ImGui::SetNextWindowSize(ImVec2(650, 750), ImGuiCond_FirstUseEver); @@ -1397,9 +1385,26 @@ absl::Status OverworldEditor::LoadGraphics() { } } - // Store remaining maps for lazy texture creation - deferred_map_textures_.assign(maps_to_texture.begin() + initial_texture_count, - maps_to_texture.end()); + // Queue remaining maps for progressive loading via Arena + // Priority based on current world (0 = current world, 11+ = other worlds) + for (size_t i = initial_texture_count; i < maps_to_texture.size(); ++i) { + // Determine priority based on which world this map belongs to + int map_index = -1; + for (int j = 0; j < zelda3::kNumOverworldMaps; ++j) { + if (&maps_bmp_[j] == maps_to_texture[i]) { + map_index = j; + break; + } + } + + int priority = 15; // Default low priority + if (map_index >= 0) { + int map_world = map_index / 0x40; + priority = (map_world == current_world_) ? 5 : 15; // Current world = priority 5, others = 15 + } + + gfx::Arena::Get().QueueDeferredTexture(maps_to_texture[i], priority); + } if (core::FeatureFlags::get().overworld.kDrawOverworldSprites) { { @@ -1433,75 +1438,29 @@ absl::Status OverworldEditor::LoadSpriteGraphics() { } void OverworldEditor::ProcessDeferredTextures() { - std::lock_guard lock(deferred_textures_mutex_); - - if (deferred_map_textures_.empty()) { - return; - } - - // Priority-based loading: process more textures for visible maps - const int textures_per_frame = 8; // Increased from 2 to 8 for faster loading - int processed = 0; - - // First pass: prioritize textures for the current world - auto it = deferred_map_textures_.begin(); - while (it != deferred_map_textures_.end() && processed < textures_per_frame) { - if (*it && !(*it)->texture()) { - // Check if this texture belongs to the current world - int map_index = -1; - for (int i = 0; i < zelda3::kNumOverworldMaps; ++i) { - if (&maps_bmp_[i] == *it) { - map_index = i; - break; - } - } - - bool is_current_world = false; - if (map_index >= 0) { - int map_world = map_index / 0x40; // 64 maps per world - is_current_world = (map_world == current_world_); - } - - // Prioritize current world maps, but also process others if we have capacity - if (is_current_world || processed < textures_per_frame / 2) { - Renderer::Get().RenderBitmap(*it); - processed++; - it = deferred_map_textures_.erase( - it); // Remove immediately after processing - } else { - ++it; - } - } else { - ++it; + // Use Arena's centralized progressive loading system + // This makes progressive loading available to all editors + auto batch = gfx::Arena::Get().GetNextDeferredTextureBatch(4, 2); + + for (auto* bitmap : batch) { + if (bitmap && !bitmap->texture()) { + Renderer::Get().RenderBitmap(bitmap); } } - - // Second pass: process remaining textures if we still have capacity - if (processed < textures_per_frame) { - it = deferred_map_textures_.begin(); - while (it != deferred_map_textures_.end() && - processed < textures_per_frame) { - if (*it && !(*it)->texture()) { - Renderer::Get().RenderBitmap(*it); - processed++; - it = deferred_map_textures_.erase(it); - } else { - ++it; - } - } - } - - // Third pass: process deferred map refreshes for visible maps - if (processed < textures_per_frame) { - for (int i = 0; - i < zelda3::kNumOverworldMaps && processed < textures_per_frame; ++i) { - if (maps_bmp_[i].modified() && maps_bmp_[i].is_active()) { - // Check if this map is visible - bool is_visible = (i == current_map_) || (i / 0x40 == current_world_); - if (is_visible) { - RefreshOverworldMapOnDemand(i); - processed++; - } + + // Also process deferred map refreshes for modified maps + int refresh_count = 0; + const int max_refreshes_per_frame = 2; + + for (int i = 0; i < zelda3::kNumOverworldMaps && refresh_count < max_refreshes_per_frame; ++i) { + if (maps_bmp_[i].modified() && maps_bmp_[i].is_active()) { + // Check if this map is in current world (prioritize) + bool is_current_world = (i / 0x40 == current_world_); + bool is_current_map = (i == current_map_); + + if (is_current_map || is_current_world) { + RefreshOverworldMapOnDemand(i); + refresh_count++; } } } @@ -1539,14 +1498,7 @@ void OverworldEditor::EnsureMapTexture(int map_index) { if (!bitmap.texture() && bitmap.is_active()) { Renderer::Get().RenderBitmap(&bitmap); - - // Remove from deferred list if it was there - std::lock_guard lock(deferred_textures_mutex_); - auto it = std::find(deferred_map_textures_.begin(), - deferred_map_textures_.end(), &bitmap); - if (it != deferred_map_textures_.end()) { - deferred_map_textures_.erase(it); - } + // Note: Arena automatically removes from deferred queue when textures are created } } diff --git a/src/app/editor/overworld/overworld_editor.h b/src/app/editor/overworld/overworld_editor.h index 4750c1a9..bd709f42 100644 --- a/src/app/editor/overworld/overworld_editor.h +++ b/src/app/editor/overworld/overworld_editor.h @@ -15,7 +15,6 @@ #include "app/gui/input.h" #include "app/rom.h" #include "app/zelda3/overworld/overworld.h" -#include "app/editor/overworld/overworld_editor_manager.h" #include "imgui/imgui.h" #include @@ -273,7 +272,6 @@ class OverworldEditor : public Editor, public gfx::GfxContext { // Map properties system for UI organization std::unique_ptr map_properties_system_; - std::unique_ptr overworld_manager_; std::unique_ptr entity_renderer_; // Scratch space for large layouts @@ -313,8 +311,7 @@ class OverworldEditor : public Editor, public gfx::GfxContext { std::vector sprite_previews_; // Deferred texture creation for performance optimization - std::vector deferred_map_textures_; - std::mutex deferred_textures_mutex_; + // Deferred texture management now handled by gfx::Arena::Get() zelda3::Overworld overworld_{rom_}; zelda3::OverworldBlockset refresh_blockset_; diff --git a/src/app/editor/overworld/overworld_editor_manager.cc b/src/app/editor/overworld/overworld_editor_manager.cc deleted file mode 100644 index 7feaf14f..00000000 --- a/src/app/editor/overworld/overworld_editor_manager.cc +++ /dev/null @@ -1,423 +0,0 @@ -#include "overworld_editor_manager.h" - -#include "app/gfx/snes_color.h" -#include "app/gui/icons.h" -#include "app/gui/input.h" -#include "app/gui/style.h" -#include "app/zelda3/overworld/overworld_map.h" - -namespace yaze { -namespace editor { - -using namespace ImGui; - -absl::Status OverworldEditorManager::DrawV3SettingsPanel() { - if (BeginTabItem("v3 Settings")) { - Text("ZSCustomOverworld v3 Settings"); - Separator(); - - // Check if custom ASM is applied - uint8_t asm_version = GetCustomASMVersion(); - if (asm_version >= 3 && asm_version != 0xFF) { - TextColored(ImVec4(0, 1, 0, 1), "Custom Overworld ASM v%d Applied", asm_version); - } else if (asm_version == 0x00) { - TextColored(ImVec4(1, 1, 0, 1), "Vanilla ROM - Custom features available via flag"); - } else { - TextColored(ImVec4(1, 0, 0, 1), "Custom ASM v%d - Consider upgrading to v3", asm_version); - } - - Separator(); - - RETURN_IF_ERROR(DrawCustomOverworldSettings()); - RETURN_IF_ERROR(DrawAreaSpecificSettings()); - RETURN_IF_ERROR(DrawTransitionSettings()); - RETURN_IF_ERROR(DrawOverlaySettings()); - - EndTabItem(); - } - return absl::OkStatus(); -} - -absl::Status OverworldEditorManager::DrawCustomOverworldSettings() { - if (TreeNode("Custom Overworld Features")) { - RETURN_IF_ERROR(DrawBooleanSetting("Enable Area-Specific Background Colors", - &enable_area_specific_bg_, - "Allows each overworld area to have its own background color")); - - RETURN_IF_ERROR(DrawBooleanSetting("Enable Main Palette Override", - &enable_main_palette_, - "Allows each area to override the main palette")); - - RETURN_IF_ERROR(DrawBooleanSetting("Enable Mosaic Transitions", - &enable_mosaic_, - "Enables mosaic screen transitions between areas")); - - RETURN_IF_ERROR(DrawBooleanSetting("Enable Custom GFX Groups", - &enable_gfx_groups_, - "Allows each area to have custom tile GFX groups")); - - RETURN_IF_ERROR(DrawBooleanSetting("Enable Subscreen Overlays", - &enable_subscreen_overlay_, - "Enables custom subscreen overlays (fog, sky, etc.)")); - - RETURN_IF_ERROR(DrawBooleanSetting("Enable Animated GFX Override", - &enable_animated_gfx_, - "Allows each area to have custom animated tiles")); - - Separator(); - - if (Button("Apply Custom Overworld ASM")) { - RETURN_IF_ERROR(ApplyCustomOverworldASM()); - } - SameLine(); - HOVER_HINT("Writes the custom overworld settings to ROM"); - - TreePop(); - } - return absl::OkStatus(); -} - -absl::Status OverworldEditorManager::DrawAreaSpecificSettings() { - if (TreeNode("Area-Specific Settings")) { - // Map selection - int map_count = zelda3::kNumOverworldMaps; - SliderInt("Map Index", ¤t_map_index_, 0, map_count - 1); - - auto* current_map = overworld_->mutable_overworld_map(current_map_index_); - - // Area size controls - RETURN_IF_ERROR(DrawAreaSizeControls()); - - // Background color - if (enable_area_specific_bg_) { - uint16_t bg_color = current_map->area_specific_bg_color(); - RETURN_IF_ERROR(DrawColorPicker("Background Color", &bg_color)); - current_map->set_area_specific_bg_color(bg_color); - } - - // Main palette - if (enable_main_palette_) { - uint8_t main_palette = current_map->main_palette(); - SliderInt("Main Palette", (int*)&main_palette, 0, 5); - current_map->set_main_palette(main_palette); - } - - // Mosaic settings - if (enable_mosaic_) { - RETURN_IF_ERROR(DrawMosaicControls()); - } - - // GFX groups - if (enable_gfx_groups_) { - RETURN_IF_ERROR(DrawGfxGroupControls()); - } - - // Subscreen overlay - if (enable_subscreen_overlay_) { - uint16_t overlay = current_map->subscreen_overlay(); - RETURN_IF_ERROR(DrawOverlaySetting("Subscreen Overlay", &overlay)); - current_map->set_subscreen_overlay(overlay); - } - - // Animated GFX - if (enable_animated_gfx_) { - uint8_t animated_gfx = current_map->animated_gfx(); - RETURN_IF_ERROR(DrawGfxGroupSetting("Animated GFX", &animated_gfx)); - current_map->set_animated_gfx(animated_gfx); - } - - TreePop(); - } - return absl::OkStatus(); -} - -absl::Status OverworldEditorManager::DrawAreaSizeControls() { - auto* current_map = overworld_->mutable_overworld_map(current_map_index_); - - const char* area_size_names[] = {"Small", "Large", "Wide", "Tall"}; - int current_size = static_cast(current_map->area_size()); - - if (Combo("Area Size", ¤t_size, area_size_names, 4)) { - current_map->SetAreaSize(static_cast(current_size)); - } - - return absl::OkStatus(); -} - -absl::Status OverworldEditorManager::DrawMosaicControls() { - auto* current_map = overworld_->mutable_overworld_map(current_map_index_); - const auto& mosaic = current_map->mosaic_expanded(); - - bool mosaic_up = mosaic[0]; - bool mosaic_down = mosaic[1]; - bool mosaic_left = mosaic[2]; - bool mosaic_right = mosaic[3]; - - if (Checkbox("Mosaic Up", &mosaic_up)) { - current_map->set_mosaic_expanded(0, mosaic_up); - } - SameLine(); - if (Checkbox("Mosaic Down", &mosaic_down)) { - current_map->set_mosaic_expanded(1, mosaic_down); - } - if (Checkbox("Mosaic Left", &mosaic_left)) { - current_map->set_mosaic_expanded(2, mosaic_left); - } - SameLine(); - if (Checkbox("Mosaic Right", &mosaic_right)) { - current_map->set_mosaic_expanded(3, mosaic_right); - } - - return absl::OkStatus(); -} - -absl::Status OverworldEditorManager::DrawGfxGroupControls() { - auto* current_map = overworld_->mutable_overworld_map(current_map_index_); - - Text("Custom Tile GFX Groups:"); - for (int i = 0; i < 8; i++) { - uint8_t gfx_id = current_map->custom_tileset(i); - std::string label = "GFX " + std::to_string(i); - RETURN_IF_ERROR(DrawGfxGroupSetting(label.c_str(), &gfx_id)); - current_map->set_custom_tileset(i, gfx_id); - if (i < 7) SameLine(); - } - - return absl::OkStatus(); -} - -absl::Status OverworldEditorManager::DrawTransitionSettings() { - if (TreeNode("Transition Settings")) { - Text("Complex area transition calculations are automatically handled"); - Text("based on neighboring area sizes (Large, Wide, Tall, Small)."); - - if (GetCustomASMVersion() >= 3) { - TextColored(ImVec4(0, 1, 0, 1), "Using v3+ enhanced transitions"); - } else { - TextColored(ImVec4(1, 1, 0, 1), "Using vanilla/v2 transitions"); - } - - TreePop(); - } - return absl::OkStatus(); -} - -absl::Status OverworldEditorManager::DrawOverlaySettings() { - if (TreeNode("Interactive Overlay Settings")) { - Text("Interactive overlays reveal holes and change map elements."); - - auto* current_map = overworld_->mutable_overworld_map(current_map_index_); - - Text("Map %d has %s", current_map_index_, - current_map->has_overlay() ? "interactive overlay" : "no overlay"); - - if (current_map->has_overlay()) { - Text("Overlay ID: 0x%04X", current_map->overlay_id()); - Text("Overlay data size: %zu bytes", current_map->overlay_data().size()); - } - - TreePop(); - } - return absl::OkStatus(); -} - -absl::Status OverworldEditorManager::ApplyCustomOverworldASM() { - return overworld_->SaveCustomOverworldASM( - enable_area_specific_bg_, enable_main_palette_, enable_mosaic_, - enable_gfx_groups_, enable_subscreen_overlay_, enable_animated_gfx_); -} - -bool OverworldEditorManager::ValidateV3Compatibility() { - uint8_t asm_version = GetCustomASMVersion(); - return (asm_version >= 3 && asm_version != 0xFF); -} - -bool OverworldEditorManager::CheckCustomASMApplied() { - uint8_t asm_version = GetCustomASMVersion(); - return (asm_version != 0xFF && asm_version != 0x00); -} - -uint8_t OverworldEditorManager::GetCustomASMVersion() { - return (*rom_)[zelda3::OverworldCustomASMHasBeenApplied]; -} - -absl::Status OverworldEditorManager::DrawBooleanSetting(const char* label, bool* setting, - const char* help_text) { - Checkbox(label, setting); - if (help_text && IsItemHovered()) { - SetTooltip("%s", help_text); - } - return absl::OkStatus(); -} - -absl::Status OverworldEditorManager::DrawColorPicker(const char* label, uint16_t* color) { - gfx::SnesColor snes_color(*color); - ImVec4 imgui_color = snes_color.rgb(); - - if (ColorEdit3(label, &imgui_color.x)) { - gfx::SnesColor new_color; - new_color.set_rgb(imgui_color); - *color = new_color.snes(); - } - - return absl::OkStatus(); -} - -absl::Status OverworldEditorManager::DrawOverlaySetting(const char* label, uint16_t* overlay) { - int overlay_int = *overlay; - if (InputInt(label, &overlay_int, 1, 16, ImGuiInputTextFlags_CharsHexadecimal)) { - *overlay = static_cast(overlay_int & 0xFFFF); - } - return absl::OkStatus(); -} - -absl::Status OverworldEditorManager::DrawGfxGroupSetting(const char* label, uint8_t* gfx_id, - int max_value) { - int gfx_int = *gfx_id; - if (SliderInt(label, &gfx_int, 0, max_value)) { - *gfx_id = static_cast(gfx_int); - } - return absl::OkStatus(); -} - -absl::Status OverworldEditorManager::DrawUnifiedSettingsTable() { - // Create a comprehensive settings table that combines toolset and properties - if (BeginTable("##UnifiedOverworldSettings", 6, - ImGuiTableFlags_Resizable | ImGuiTableFlags_BordersOuter | - ImGuiTableFlags_BordersV | ImGuiTableFlags_SizingFixedFit)) { - - // Setup columns with proper widths - TableSetupColumn(ICON_MD_BUILD " Tools", ImGuiTableColumnFlags_WidthFixed, 120); - TableSetupColumn(ICON_MD_MAP " World", ImGuiTableColumnFlags_WidthFixed, 100); - TableSetupColumn(ICON_MD_IMAGE " Graphics", ImGuiTableColumnFlags_WidthFixed, 100); - TableSetupColumn(ICON_MD_PALETTE " Palette", ImGuiTableColumnFlags_WidthFixed, 100); - TableSetupColumn(ICON_MD_SETTINGS " Properties", ImGuiTableColumnFlags_WidthStretch); - TableSetupColumn(ICON_MD_EXTENSION " v3 Features", ImGuiTableColumnFlags_WidthFixed, 120); - TableHeadersRow(); - - TableNextRow(); - - // Tools column - TableNextColumn(); - RETURN_IF_ERROR(DrawToolsetInSettings()); - - // World column - TableNextColumn(); - Text(ICON_MD_PUBLIC " Current World"); - SetNextItemWidth(80.f); - // if (Combo("##world", ¤t_world_, kWorldList.data(), 3)) { - // // World change logic would go here - // } - - // Graphics column - TableNextColumn(); - Text(ICON_MD_IMAGE " Area Graphics"); - // Graphics controls would go here - - // Palette column - TableNextColumn(); - Text(ICON_MD_PALETTE " Area Palette"); - // Palette controls would go here - - // Properties column - TableNextColumn(); - Text(ICON_MD_SETTINGS " Map Properties"); - // Map properties would go here - - // v3 Features column - TableNextColumn(); - uint8_t asm_version = GetCustomASMVersion(); - if (asm_version >= 3 && asm_version != 0xFF) { - TextColored(ImVec4(0, 1, 0, 1), ICON_MD_NEW_RELEASES " v3 Active"); - if (Button(ICON_MD_TUNE " Settings")) { - // Open v3 settings - } - } else { - TextColored(ImVec4(0.7f, 0.7f, 0.7f, 1), ICON_MD_UPGRADE " v3 Available"); - if (Button(ICON_MD_UPGRADE " Upgrade")) { - // Trigger upgrade - } - } - - EndTable(); - } - - return absl::OkStatus(); -} - -absl::Status OverworldEditorManager::DrawToolsetInSettings() { - // Compact toolset layout within the settings table - BeginGroup(); - - // Core editing tools in a compact grid - if (Button(ICON_MD_PAN_TOOL_ALT, ImVec2(25, 25))) { - // Set PAN mode - } - HOVER_HINT("Pan (1)"); - - SameLine(); - if (Button(ICON_MD_DRAW, ImVec2(25, 25))) { - // Set DRAW_TILE mode - } - HOVER_HINT("Draw Tile (2)"); - - SameLine(); - if (Button(ICON_MD_DOOR_FRONT, ImVec2(25, 25))) { - // Set ENTRANCES mode - } - HOVER_HINT("Entrances (3)"); - - SameLine(); - if (Button(ICON_MD_DOOR_BACK, ImVec2(25, 25))) { - // Set EXITS mode - } - HOVER_HINT("Exits (4)"); - - // Second row - if (Button(ICON_MD_GRASS, ImVec2(25, 25))) { - // Set ITEMS mode - } - HOVER_HINT("Items (5)"); - - SameLine(); - if (Button(ICON_MD_PEST_CONTROL_RODENT, ImVec2(25, 25))) { - // Set SPRITES mode - } - HOVER_HINT("Sprites (6)"); - - SameLine(); - if (Button(ICON_MD_ADD_LOCATION, ImVec2(25, 25))) { - // Set TRANSPORTS mode - } - HOVER_HINT("Transports (7)"); - - SameLine(); - if (Button(ICON_MD_MUSIC_NOTE, ImVec2(25, 25))) { - // Set MUSIC mode - } - HOVER_HINT("Music (8)"); - - EndGroup(); - return absl::OkStatus(); -} - -absl::Status OverworldEditorManager::HandleCanvasSelectionTransfer() { - // This could be called to manage bidirectional selection transfer - // For now, it's a placeholder for future canvas interaction management - return absl::OkStatus(); -} - -absl::Status OverworldEditorManager::TransferOverworldSelectionToScratch() { - // Transfer logic would go here to copy selections from overworld to scratch - // This could be integrated with the editor's context system - return absl::OkStatus(); -} - -absl::Status OverworldEditorManager::TransferScratchSelectionToOverworld() { - // Transfer logic would go here to copy selections from scratch to overworld - // This could be integrated with the editor's context system - return absl::OkStatus(); -} - -} // namespace editor -} // namespace yaze diff --git a/src/app/editor/overworld/overworld_editor_manager.h b/src/app/editor/overworld/overworld_editor_manager.h deleted file mode 100644 index 48305bb0..00000000 --- a/src/app/editor/overworld/overworld_editor_manager.h +++ /dev/null @@ -1,108 +0,0 @@ -#ifndef YAZE_APP_EDITOR_OVERWORLD_OVERWORLD_EDITOR_MANAGER_H -#define YAZE_APP_EDITOR_OVERWORLD_OVERWORLD_EDITOR_MANAGER_H - -#include - -#include "absl/status/status.h" -#include "app/rom.h" -#include "app/zelda3/overworld/overworld.h" -#include "app/gui/canvas.h" -#include "app/gui/input.h" - -namespace yaze { -namespace editor { - -// Forward declarations -enum class EditingMode; -class OverworldEditor; - -/** - * @class OverworldEditorManager - * @brief Manages the complex overworld editor functionality and v3 features - * - * This class separates the complex overworld editing functionality from the main - * OverworldEditor class to improve maintainability and organization, especially - * for ZSCustomOverworld v3 features. - */ -class OverworldEditorManager { - public: - OverworldEditorManager(zelda3::Overworld* overworld, Rom* rom, - OverworldEditor* editor = nullptr) - : overworld_(overworld), rom_(rom), editor_(editor) {} - - // Set editor context for mode changes - void SetEditorContext(OverworldEditor* editor) { editor_ = editor; } - - // v3 Feature Management - absl::Status DrawV3SettingsPanel(); - absl::Status DrawCustomOverworldSettings(); - absl::Status DrawAreaSpecificSettings(); - absl::Status DrawTransitionSettings(); - absl::Status DrawOverlaySettings(); - - // Map Properties Management - absl::Status DrawMapPropertiesTable(); - absl::Status DrawUnifiedSettingsTable(); - absl::Status DrawToolsetInSettings(); - absl::Status DrawAreaSizeControls(); - absl::Status DrawMosaicControls(); - absl::Status DrawPaletteControls(); - absl::Status DrawGfxGroupControls(); - - // Save/Load Operations for v3 - absl::Status SaveCustomOverworldData(); - absl::Status LoadCustomOverworldData(); - absl::Status ApplyCustomOverworldASM(); - - // Canvas Interaction Management - absl::Status HandleCanvasSelectionTransfer(); - absl::Status TransferOverworldSelectionToScratch(); - absl::Status TransferScratchSelectionToOverworld(); - - // Validation and Checks - bool ValidateV3Compatibility(); - bool CheckCustomASMApplied(); - uint8_t GetCustomASMVersion(); - - // Getters/Setters for v3 settings - auto enable_area_specific_bg() const { return enable_area_specific_bg_; } - auto enable_main_palette() const { return enable_main_palette_; } - auto enable_mosaic() const { return enable_mosaic_; } - auto enable_gfx_groups() const { return enable_gfx_groups_; } - auto enable_subscreen_overlay() const { return enable_subscreen_overlay_; } - auto enable_animated_gfx() const { return enable_animated_gfx_; } - - void set_enable_area_specific_bg(bool value) { enable_area_specific_bg_ = value; } - void set_enable_main_palette(bool value) { enable_main_palette_ = value; } - void set_enable_mosaic(bool value) { enable_mosaic_ = value; } - void set_enable_gfx_groups(bool value) { enable_gfx_groups_ = value; } - void set_enable_subscreen_overlay(bool value) { enable_subscreen_overlay_ = value; } - void set_enable_animated_gfx(bool value) { enable_animated_gfx_ = value; } - - private: - zelda3::Overworld* overworld_; - Rom* rom_; - OverworldEditor* editor_; - - // v3 Feature flags - bool enable_area_specific_bg_ = false; - bool enable_main_palette_ = false; - bool enable_mosaic_ = false; - bool enable_gfx_groups_ = false; - bool enable_subscreen_overlay_ = false; - bool enable_animated_gfx_ = false; - - // Current editing state - int current_map_index_ = 0; - - // Helper methods - absl::Status DrawBooleanSetting(const char* label, bool* setting, const char* help_text = nullptr); - absl::Status DrawColorPicker(const char* label, uint16_t* color); - absl::Status DrawOverlaySetting(const char* label, uint16_t* overlay); - absl::Status DrawGfxGroupSetting(const char* label, uint8_t* gfx_id, int max_value = 255); -}; - -} // namespace editor -} // namespace yaze - -#endif // YAZE_APP_EDITOR_OVERWORLD_OVERWORLD_EDITOR_MANAGER_H diff --git a/src/app/editor/overworld/overworld_graphics_manager.cc b/src/app/editor/overworld/overworld_graphics_manager.cc deleted file mode 100644 index 9cba6963..00000000 --- a/src/app/editor/overworld/overworld_graphics_manager.cc +++ /dev/null @@ -1,714 +0,0 @@ -#include "overworld_graphics_manager.h" - -#include - -#include "app/core/features.h" -#include "app/core/window.h" -#include "app/gfx/performance_profiler.h" -#include "util/log.h" -#include "util/macro.h" - -namespace yaze { -namespace editor { - -using core::Renderer; -using zelda3::kNumOverworldMaps; -using zelda3::kOverworldMapSize; - -constexpr int kTile16Size = 16; - -// ============================================================================ -// Loading Operations -// ============================================================================ - -absl::Status OverworldGraphicsManager::LoadGraphics() { - gfx::ScopedTimer timer("LoadGraphics"); - - LOG_INFO("OverworldGraphicsManager", "Loading overworld."); - // Load the Link to the Past overworld. - { - gfx::ScopedTimer load_timer("Overworld::Load"); - RETURN_IF_ERROR(overworld_->Load(rom_)); - } - *palette_ = overworld_->current_area_palette(); - - LOG_INFO("OverworldGraphicsManager", "Loading overworld graphics (optimized)."); - - // Phase 1: Create bitmaps without textures for faster loading - // This avoids blocking the main thread with GPU texture creation - { - gfx::ScopedTimer gfx_timer("CreateBitmapWithoutTexture_Graphics"); - Renderer::Get().CreateBitmapWithoutTexture(0x80, kOverworldMapSize, 0x40, - overworld_->current_graphics(), - *current_gfx_bmp_, *palette_); - } - - LOG_INFO("OverworldGraphicsManager", "Loading overworld tileset (deferred textures)."); - { - gfx::ScopedTimer tileset_timer("CreateBitmapWithoutTexture_Tileset"); - Renderer::Get().CreateBitmapWithoutTexture( - 0x80, 0x2000, 0x08, overworld_->tile16_blockset_data(), - *tile16_blockset_bmp_, *palette_); - } - map_blockset_loaded_ = true; - - // Copy the tile16 data into individual tiles. - auto tile16_blockset_data = overworld_->tile16_blockset_data(); - LOG_INFO("OverworldGraphicsManager", "Loading overworld tile16 graphics."); - - { - gfx::ScopedTimer tilemap_timer("CreateTilemap"); - *tile16_blockset_ = - gfx::CreateTilemap(tile16_blockset_data, 0x80, 0x2000, kTile16Size, - zelda3::kNumTile16Individual, *palette_); - } - - // Phase 2: Create bitmaps only for essential maps initially - // Non-essential maps will be created on-demand when accessed - constexpr int kEssentialMapsPerWorld = 8; - constexpr int kLightWorldEssential = kEssentialMapsPerWorld; - constexpr int kDarkWorldEssential = - zelda3::kDarkWorldMapIdStart + kEssentialMapsPerWorld; - constexpr int kSpecialWorldEssential = - zelda3::kSpecialWorldMapIdStart + kEssentialMapsPerWorld; - - LOG_INFO("OverworldGraphicsManager", - "Creating bitmaps for essential maps only (first %d maps per world)", - kEssentialMapsPerWorld); - - std::vector maps_to_texture; - maps_to_texture.reserve(kEssentialMapsPerWorld * - 3); // 8 maps per world * 3 worlds - - { - gfx::ScopedTimer maps_timer("CreateEssentialOverworldMaps"); - for (int i = 0; i < zelda3::kNumOverworldMaps; ++i) { - bool is_essential = false; - - // Check if this is an essential map - if (i < kLightWorldEssential) { - is_essential = true; - } else if (i >= zelda3::kDarkWorldMapIdStart && i < kDarkWorldEssential) { - is_essential = true; - } else if (i >= zelda3::kSpecialWorldMapIdStart && - i < kSpecialWorldEssential) { - is_essential = true; - } - - if (is_essential) { - overworld_->set_current_map(i); - auto palette = overworld_->current_area_palette(); - try { - // Create bitmap data and surface but defer texture creation - (*maps_bmp_)[i].Create(kOverworldMapSize, kOverworldMapSize, 0x80, - overworld_->current_map_bitmap_data()); - (*maps_bmp_)[i].SetPalette(palette); - maps_to_texture.push_back(&(*maps_bmp_)[i]); - } catch (const std::bad_alloc& e) { - LOG_ERROR("OverworldGraphicsManager", "Error allocating map %d: %s", - i, e.what()); - continue; - } - } - // Non-essential maps will be created on-demand when accessed - } - } - - // Phase 3: Create textures only for currently visible maps - // Only create textures for the first few maps initially - const int initial_texture_count = - std::min(4, static_cast(maps_to_texture.size())); - { - gfx::ScopedTimer initial_textures_timer("CreateInitialTextures"); - for (int i = 0; i < initial_texture_count; ++i) { - Renderer::Get().RenderBitmap(maps_to_texture[i]); - } - } - - // Store remaining maps for lazy texture creation - deferred_map_textures_.assign(maps_to_texture.begin() + initial_texture_count, - maps_to_texture.end()); - - if (core::FeatureFlags::get().overworld.kDrawOverworldSprites) { - { - gfx::ScopedTimer sprites_timer("LoadSpriteGraphics"); - RETURN_IF_ERROR(LoadSpriteGraphics()); - } - } - - all_gfx_loaded_ = true; - return absl::OkStatus(); -} - -absl::Status OverworldGraphicsManager::LoadSpriteGraphics() { - // Render the sprites for each Overworld map - const int depth = 0x10; - for (int i = 0; i < 3; i++) - for (auto const& sprite : *overworld_->mutable_sprites(i)) { - int width = sprite.width(); - int height = sprite.height(); - if (width == 0 || height == 0) { - continue; - } - if (sprite_previews_->size() < sprite.id()) { - sprite_previews_->resize(sprite.id() + 1); - } - (*sprite_previews_)[sprite.id()].Create(width, height, depth, - *sprite.preview_graphics()); - (*sprite_previews_)[sprite.id()].SetPalette(*palette_); - Renderer::Get().RenderBitmap(&(*sprite_previews_)[sprite.id()]); - } - return absl::OkStatus(); -} - -// ============================================================================ -// Texture Management -// ============================================================================ - -void OverworldGraphicsManager::ProcessDeferredTextures() { - std::lock_guard lock(deferred_textures_mutex_); - - // Always process deferred textures progressively, even if the list is "empty" - // This allows for continuous background loading - - // PHASE 1: Priority loading for current world - const int high_priority_per_frame = 4; // Current world maps - const int low_priority_per_frame = 2; // Other world maps - const int refresh_per_frame = 2; // Modified map refreshes - int processed = 0; - - // Process high-priority deferred textures (current world) - if (!deferred_map_textures_.empty()) { - auto it = deferred_map_textures_.begin(); - while (it != deferred_map_textures_.end() && processed < high_priority_per_frame) { - if (*it && !(*it)->texture()) { - // Find map index for priority check - int map_index = -1; - for (int i = 0; i < zelda3::kNumOverworldMaps; ++i) { - if (&(*maps_bmp_)[i] == *it) { - map_index = i; - break; - } - } - - bool is_current_world = false; - if (map_index >= 0) { - int map_world = map_index / 0x40; // 64 maps per world - is_current_world = (map_world == current_world_); - } - - if (is_current_world) { - Renderer::Get().RenderBitmap(*it); - processed++; - it = deferred_map_textures_.erase(it); - } else { - ++it; - } - } else { - ++it; - } - } - } - - // PHASE 2: Background loading for other worlds (lower priority) - if (!deferred_map_textures_.empty() && processed < high_priority_per_frame) { - auto it = deferred_map_textures_.begin(); - int low_priority_processed = 0; - while (it != deferred_map_textures_.end() && low_priority_processed < low_priority_per_frame) { - if (*it && !(*it)->texture()) { - Renderer::Get().RenderBitmap(*it); - low_priority_processed++; - processed++; - it = deferred_map_textures_.erase(it); - } else { - ++it; - } - } - } - - // PHASE 3: Process modified maps that need refresh (highest priority) - int refresh_processed = 0; - for (int i = 0; i < zelda3::kNumOverworldMaps && refresh_processed < refresh_per_frame; ++i) { - if ((*maps_bmp_)[i].modified() && (*maps_bmp_)[i].is_active()) { - // Check if this map is in current world (high priority) or visible - bool is_current_world = (i / 0x40 == current_world_); - bool is_current_map = (i == current_map_); - - if (is_current_map || is_current_world) { - RefreshOverworldMapOnDemand(i); - refresh_processed++; - } - } - } - - // PHASE 4: Background refresh for modified maps in other worlds (very low priority) - if (refresh_processed == 0) { - for (int i = 0; i < zelda3::kNumOverworldMaps; ++i) { - if ((*maps_bmp_)[i].modified() && (*maps_bmp_)[i].is_active()) { - bool is_current_world = (i / 0x40 == current_world_); - if (!is_current_world) { - // Just mark for later, don't refresh now to avoid lag - // These will be refreshed when the world is switched - break; - } - } - } - } -} - -void OverworldGraphicsManager::EnsureMapTexture(int map_index) { - if (map_index < 0 || map_index >= zelda3::kNumOverworldMaps) { - return; - } - - // Ensure the map is built first (on-demand loading) - auto status = overworld_->EnsureMapBuilt(map_index); - if (!status.ok()) { - LOG_ERROR("OverworldGraphicsManager", "Failed to build map %d: %s", map_index, - status.message().data()); - return; - } - - auto& bitmap = (*maps_bmp_)[map_index]; - - // If bitmap doesn't exist yet (non-essential map), create it now - if (!bitmap.is_active()) { - overworld_->set_current_map(map_index); - auto palette = overworld_->current_area_palette(); - try { - bitmap.Create(kOverworldMapSize, kOverworldMapSize, 0x80, - overworld_->current_map_bitmap_data()); - bitmap.SetPalette(palette); - } catch (const std::bad_alloc& e) { - LOG_ERROR("OverworldGraphicsManager", "Error allocating bitmap for map %d: %s", - map_index, e.what()); - return; - } - } - - if (!bitmap.texture() && bitmap.is_active()) { - Renderer::Get().RenderBitmap(&bitmap); - - // Remove from deferred list if it was there - std::lock_guard lock(deferred_textures_mutex_); - auto it = std::find(deferred_map_textures_.begin(), - deferred_map_textures_.end(), &bitmap); - if (it != deferred_map_textures_.end()) { - deferred_map_textures_.erase(it); - } - } -} - -// ============================================================================ -// Refresh Operations -// ============================================================================ - -void OverworldGraphicsManager::RefreshOverworldMap() { - // Use the new on-demand refresh system - RefreshOverworldMapOnDemand(current_map_); -} - -void OverworldGraphicsManager::RefreshOverworldMapOnDemand(int map_index) { - if (map_index < 0 || map_index >= zelda3::kNumOverworldMaps) { - return; - } - - // Check if the map is actually visible or being edited - bool is_current_map = (map_index == current_map_); - bool is_current_world = (map_index / 0x40 == current_world_); - - // For non-current maps in non-current worlds, defer the refresh - if (!is_current_map && !is_current_world) { - // Mark for deferred refresh - will be processed when the map becomes visible - (*maps_bmp_)[map_index].set_modified(true); - return; - } - - // For visible maps, do immediate refresh - RefreshChildMapOnDemand(map_index); -} - -void OverworldGraphicsManager::RefreshChildMap(int map_index) { - overworld_->mutable_overworld_map(map_index)->LoadAreaGraphics(); - auto status = overworld_->mutable_overworld_map(map_index)->BuildTileset(); - PRINT_IF_ERROR(status); - status = overworld_->mutable_overworld_map(map_index)->BuildTiles16Gfx( - *overworld_->mutable_tiles16(), overworld_->tiles16().size()); - PRINT_IF_ERROR(status); - status = overworld_->mutable_overworld_map(map_index)->BuildBitmap( - overworld_->GetMapTiles(current_world_)); - (*maps_bmp_)[map_index].set_data( - overworld_->mutable_overworld_map(map_index)->bitmap_data()); - (*maps_bmp_)[map_index].set_modified(true); - PRINT_IF_ERROR(status); -} - -void OverworldGraphicsManager::RefreshChildMapOnDemand(int map_index) { - auto* map = overworld_->mutable_overworld_map(map_index); - - // Check what actually needs to be refreshed - bool needs_graphics_rebuild = (*maps_bmp_)[map_index].modified(); - - if (needs_graphics_rebuild) { - // Only rebuild what's actually changed - map->LoadAreaGraphics(); - - // Rebuild tileset only if graphics changed - auto status = map->BuildTileset(); - if (!status.ok()) { - LOG_ERROR("OverworldGraphicsManager", "Failed to build tileset for map %d: %s", - map_index, status.message().data()); - return; - } - - // Rebuild tiles16 graphics - status = map->BuildTiles16Gfx(*overworld_->mutable_tiles16(), - overworld_->tiles16().size()); - if (!status.ok()) { - LOG_ERROR("OverworldGraphicsManager", "Failed to build tiles16 graphics for map %d: %s", - map_index, status.message().data()); - return; - } - - // Rebuild bitmap - status = map->BuildBitmap(overworld_->GetMapTiles(current_world_)); - if (!status.ok()) { - LOG_ERROR("OverworldGraphicsManager", "Failed to build bitmap for map %d: %s", - map_index, status.message().data()); - return; - } - - // Update bitmap data - (*maps_bmp_)[map_index].set_data(map->bitmap_data()); - (*maps_bmp_)[map_index].set_modified(false); - - // Validate surface synchronization to help debug crashes - if (!(*maps_bmp_)[map_index].ValidateDataSurfaceSync()) { - LOG_WARN("OverworldGraphicsManager", "Warning: Surface synchronization issue detected for map %d", - map_index); - } - - // Update texture on main thread - if ((*maps_bmp_)[map_index].texture()) { - Renderer::Get().UpdateBitmap(&(*maps_bmp_)[map_index]); - } else { - // Create texture if it doesn't exist - EnsureMapTexture(map_index); - } - } - - // Handle multi-area maps (large, wide, tall) with safe coordination - // Check if ZSCustomOverworld v3 is present - uint8_t asm_version = (*rom_)[zelda3::OverworldCustomASMHasBeenApplied]; - bool use_v3_area_sizes = (asm_version >= 3 && asm_version != 0xFF); - - if (use_v3_area_sizes) { - // Use v3 multi-area coordination - RefreshMultiAreaMapsSafely(map_index, map); - } else { - // Legacy logic: only handle large maps for vanilla/v2 - if (map->is_large_map()) { - RefreshMultiAreaMapsSafely(map_index, map); - } - } -} - -void OverworldGraphicsManager::RefreshMultiAreaMapsSafely( - int map_index, zelda3::OverworldMap* map) { - using zelda3::AreaSizeEnum; - - // Skip if this is already a processed sibling to avoid double-processing - static std::set currently_processing; - if (currently_processing.count(map_index)) { - return; - } - - auto area_size = map->area_size(); - if (area_size == AreaSizeEnum::SmallArea) { - return; // No siblings to coordinate - } - - LOG_DEBUG("OverworldGraphicsManager", - "RefreshMultiAreaMapsSafely: Processing %s area map %d (parent: %d)", - (area_size == AreaSizeEnum::LargeArea) ? "large" - : (area_size == AreaSizeEnum::WideArea) ? "wide" - : "tall", - map_index, map->parent()); - - // Determine all maps that are part of this multi-area structure - std::vector sibling_maps; - int parent_id = map->parent(); - - // Use the same logic as ZScream for area coordination - switch (area_size) { - case AreaSizeEnum::LargeArea: { - // Large Area: 2x2 grid (4 maps total) - sibling_maps = {parent_id, parent_id + 1, parent_id + 8, parent_id + 9}; - LOG_DEBUG("OverworldGraphicsManager", - "RefreshMultiAreaMapsSafely: Large area siblings: %d, %d, %d, %d", - parent_id, parent_id + 1, parent_id + 8, parent_id + 9); - break; - } - - case AreaSizeEnum::WideArea: { - // Wide Area: 2x1 grid (2 maps total, horizontally adjacent) - sibling_maps = {parent_id, parent_id + 1}; - LOG_DEBUG("OverworldGraphicsManager", - "RefreshMultiAreaMapsSafely: Wide area siblings: %d, %d", - parent_id, parent_id + 1); - break; - } - - case AreaSizeEnum::TallArea: { - // Tall Area: 1x2 grid (2 maps total, vertically adjacent) - sibling_maps = {parent_id, parent_id + 8}; - LOG_DEBUG("OverworldGraphicsManager", - "RefreshMultiAreaMapsSafely: Tall area siblings: %d, %d", - parent_id, parent_id + 8); - break; - } - - default: - LOG_WARN("OverworldGraphicsManager", - "RefreshMultiAreaMapsSafely: Unknown area size %d for map %d", - static_cast(area_size), map_index); - return; - } - - // Mark all siblings as being processed to prevent recursion - for (int sibling : sibling_maps) { - currently_processing.insert(sibling); - } - - // Only refresh siblings that are visible/current and need updating - for (int sibling : sibling_maps) { - if (sibling == map_index) { - continue; // Skip self (already processed above) - } - - // Bounds check - if (sibling < 0 || sibling >= zelda3::kNumOverworldMaps) { - continue; - } - - // Only refresh if it's visible or current - bool is_current_map = (sibling == current_map_); - bool is_current_world = (sibling / 0x40 == current_world_); - bool needs_refresh = (*maps_bmp_)[sibling].modified(); - - if ((is_current_map || is_current_world) && needs_refresh) { - LOG_DEBUG("OverworldGraphicsManager", - "RefreshMultiAreaMapsSafely: Refreshing %s area sibling map %d " - "(parent: %d)", - (area_size == AreaSizeEnum::LargeArea) ? "large" - : (area_size == AreaSizeEnum::WideArea) ? "wide" - : "tall", - sibling, parent_id); - - // Direct refresh without calling RefreshChildMapOnDemand to avoid recursion - auto* sibling_map = overworld_->mutable_overworld_map(sibling); - if (sibling_map && (*maps_bmp_)[sibling].modified()) { - sibling_map->LoadAreaGraphics(); - - if (auto status = sibling_map->BuildTileset(); !status.ok()) { - LOG_ERROR("OverworldGraphicsManager", - "RefreshMultiAreaMapsSafely: Failed to refresh sibling map %d: %s", - sibling, status.message().data()); - continue; - } - - if (auto status = sibling_map->BuildTiles16Gfx(*overworld_->mutable_tiles16(), - overworld_->tiles16().size()); !status.ok()) { - LOG_ERROR("OverworldGraphicsManager", - "RefreshMultiAreaMapsSafely: Failed to build tiles16 graphics for sibling map %d: %s", - sibling, status.message().data()); - continue; - } - - if (auto status = sibling_map->LoadPalette(); !status.ok()) { - LOG_ERROR("OverworldGraphicsManager", - "RefreshMultiAreaMapsSafely: Failed to load palette for sibling map %d: %s", - sibling, status.message().data()); - continue; - } - - if (auto status = sibling_map->BuildBitmap(overworld_->GetMapTiles(current_world_)); status.ok()) { - (*maps_bmp_)[sibling].set_data(sibling_map->bitmap_data()); - (*maps_bmp_)[sibling].SetPalette(overworld_->current_area_palette()); - (*maps_bmp_)[sibling].set_modified(false); - - // Update texture if it exists - if ((*maps_bmp_)[sibling].texture()) { - Renderer::Get().UpdateBitmap(&(*maps_bmp_)[sibling]); - } else { - EnsureMapTexture(sibling); - } - } else { - LOG_ERROR("OverworldGraphicsManager", - "RefreshMultiAreaMapsSafely: Failed to build bitmap for sibling map %d: %s", - sibling, status.message().data()); - } - } - } else if (!is_current_map && !is_current_world) { - // Mark non-visible siblings for deferred refresh - (*maps_bmp_)[sibling].set_modified(true); - } - } - - // Clear processing set after completion - for (int sibling : sibling_maps) { - currently_processing.erase(sibling); - } -} - -absl::Status OverworldGraphicsManager::RefreshMapPalette() { - RETURN_IF_ERROR( - overworld_->mutable_overworld_map(current_map_)->LoadPalette()); - const auto current_map_palette = overworld_->current_area_palette(); - - // Check if ZSCustomOverworld v3 is present - uint8_t asm_version = (*rom_)[zelda3::OverworldCustomASMHasBeenApplied]; - bool use_v3_area_sizes = (asm_version >= 3 && asm_version != 0xFF); - - if (use_v3_area_sizes) { - // Use v3 area size system - using zelda3::AreaSizeEnum; - auto area_size = overworld_->overworld_map(current_map_)->area_size(); - - if (area_size != AreaSizeEnum::SmallArea) { - // Get all sibling maps that need palette updates - std::vector sibling_maps; - int parent_id = overworld_->overworld_map(current_map_)->parent(); - - switch (area_size) { - case AreaSizeEnum::LargeArea: - // 2x2 grid: parent, parent+1, parent+8, parent+9 - sibling_maps = {parent_id, parent_id + 1, parent_id + 8, - parent_id + 9}; - break; - case AreaSizeEnum::WideArea: - // 2x1 grid: parent, parent+1 - sibling_maps = {parent_id, parent_id + 1}; - break; - case AreaSizeEnum::TallArea: - // 1x2 grid: parent, parent+8 - sibling_maps = {parent_id, parent_id + 8}; - break; - default: - break; - } - - // Update palette for all siblings - for (int sibling_index : sibling_maps) { - if (sibling_index < 0 || sibling_index >= zelda3::kNumOverworldMaps) { - continue; - } - RETURN_IF_ERROR( - overworld_->mutable_overworld_map(sibling_index)->LoadPalette()); - (*maps_bmp_)[sibling_index].SetPalette(current_map_palette); - } - } else { - // Small area - only update current map - (*maps_bmp_)[current_map_].SetPalette(current_map_palette); - } - } else { - // Legacy logic for vanilla and v2 ROMs - if (overworld_->overworld_map(current_map_)->is_large_map()) { - // We need to update the map and its siblings if it's a large map - for (int i = 1; i < 4; i++) { - int sibling_index = - overworld_->overworld_map(current_map_)->parent() + i; - if (i >= 2) - sibling_index += 6; - RETURN_IF_ERROR( - overworld_->mutable_overworld_map(sibling_index)->LoadPalette()); - (*maps_bmp_)[sibling_index].SetPalette(current_map_palette); - } - } - (*maps_bmp_)[current_map_].SetPalette(current_map_palette); - } - - return absl::OkStatus(); -} - -absl::Status OverworldGraphicsManager::RefreshTile16Blockset() { - LOG_DEBUG("OverworldGraphicsManager", "RefreshTile16Blockset called"); - if (current_blockset_ == - overworld_->overworld_map(current_map_)->area_graphics()) { - return absl::OkStatus(); - } - current_blockset_ = overworld_->overworld_map(current_map_)->area_graphics(); - - overworld_->set_current_map(current_map_); - *palette_ = overworld_->current_area_palette(); - - const auto tile16_data = overworld_->tile16_blockset_data(); - - gfx::UpdateTilemap(*tile16_blockset_, tile16_data); - tile16_blockset_->atlas.SetPalette(*palette_); - return absl::OkStatus(); -} - -void OverworldGraphicsManager::ForceRefreshGraphics(int map_index) { - if (map_index < 0 || map_index >= zelda3::kNumOverworldMaps) { - return; - } - - LOG_INFO("OverworldGraphicsManager", - "ForceRefreshGraphics: Forcing graphics reload for map %d", map_index); - - // Mark bitmap as modified to force refresh - (*maps_bmp_)[map_index].set_modified(true); - - // Clear the blockset cache to force tile16 reload - current_blockset_ = 0xFF; - - // If this is the current map, also ensure sibling maps are refreshed for multi-area maps - uint8_t asm_version = (*rom_)[zelda3::OverworldCustomASMHasBeenApplied]; - bool use_v3_area_sizes = (asm_version >= 3 && asm_version != 0xFF); - - auto* map = overworld_->mutable_overworld_map(map_index); - if (use_v3_area_sizes) { - using zelda3::AreaSizeEnum; - auto area_size = map->area_size(); - - if (area_size != AreaSizeEnum::SmallArea) { - std::vector sibling_maps; - int parent_id = map->parent(); - - switch (area_size) { - case AreaSizeEnum::LargeArea: - sibling_maps = {parent_id, parent_id + 1, parent_id + 8, parent_id + 9}; - break; - case AreaSizeEnum::WideArea: - sibling_maps = {parent_id, parent_id + 1}; - break; - case AreaSizeEnum::TallArea: - sibling_maps = {parent_id, parent_id + 8}; - break; - default: - break; - } - - // Mark all sibling maps as needing refresh - for (int sibling : sibling_maps) { - if (sibling >= 0 && sibling < zelda3::kNumOverworldMaps) { - (*maps_bmp_)[sibling].set_modified(true); - } - } - } - } else if (map->is_large_map()) { - // Legacy large map handling - int parent_id = map->parent(); - for (int i = 0; i < 4; ++i) { - int sibling = parent_id + (i < 2 ? i : i + 6); - if (sibling >= 0 && sibling < zelda3::kNumOverworldMaps) { - (*maps_bmp_)[sibling].set_modified(true); - } - } - } -} - -} // namespace editor -} // namespace yaze - diff --git a/src/app/editor/overworld/overworld_graphics_manager.h b/src/app/editor/overworld/overworld_graphics_manager.h deleted file mode 100644 index 50a85a5d..00000000 --- a/src/app/editor/overworld/overworld_graphics_manager.h +++ /dev/null @@ -1,204 +0,0 @@ -#ifndef YAZE_APP_EDITOR_OVERWORLD_OVERWORLD_GRAPHICS_MANAGER_H -#define YAZE_APP_EDITOR_OVERWORLD_OVERWORLD_GRAPHICS_MANAGER_H - -#include -#include -#include - -#include "absl/status/status.h" -#include "app/gfx/bitmap.h" -#include "app/gfx/snes_palette.h" -#include "app/gfx/tilemap.h" -#include "app/rom.h" -#include "app/zelda3/overworld/overworld.h" -#include "app/zelda3/overworld/overworld_map.h" - -namespace yaze { -namespace editor { - -using Tilemap = gfx::Tilemap; - -/** - * @class OverworldGraphicsManager - * @brief Manages all graphics-related operations for the Overworld Editor - * - * This class handles: - * - Graphics loading and initialization - * - Sprite graphics loading - * - Deferred texture processing for smooth loading - * - Map texture management - * - Map refreshing (full and on-demand) - * - Palette refreshing - * - Tile16 blockset refreshing - * - Multi-area map coordination - * - * Separating graphics management from the main OverworldEditor improves: - * - Code organization and maintainability - * - Performance optimization opportunities - * - Testing and debugging - * - Clear separation of concerns - */ -class OverworldGraphicsManager { - public: - OverworldGraphicsManager( - zelda3::Overworld* overworld, Rom* rom, - std::array* maps_bmp, - gfx::Bitmap* tile16_blockset_bmp, gfx::Bitmap* current_gfx_bmp, - std::vector* sprite_previews, gfx::SnesPalette* palette, - Tilemap* tile16_blockset) - : overworld_(overworld), - rom_(rom), - maps_bmp_(maps_bmp), - tile16_blockset_bmp_(tile16_blockset_bmp), - current_gfx_bmp_(current_gfx_bmp), - sprite_previews_(sprite_previews), - palette_(palette), - tile16_blockset_(tile16_blockset) {} - - // ============================================================================ - // Loading Operations - // ============================================================================ - - /** - * @brief Load all overworld graphics (maps, tilesets, sprites) - * - * This uses a multi-phase loading strategy: - * - Phase 1: Create bitmaps without textures - * - Phase 2: Create bitmaps for essential maps only - * - Phase 3: Create textures for visible maps - * - Deferred loading for remaining maps - */ - absl::Status LoadGraphics(); - - /** - * @brief Load sprite graphics for all overworld maps - */ - absl::Status LoadSpriteGraphics(); - - // ============================================================================ - // Texture Management - // ============================================================================ - - /** - * @brief Process deferred texture creation (called per frame) - * - * Creates textures gradually to avoid frame drops. - * Prioritizes textures for the current world and visible maps. - */ - void ProcessDeferredTextures(); - - /** - * @brief Ensure a specific map has a texture created - * - * @param map_index Index of the map to ensure texture for - */ - void EnsureMapTexture(int map_index); - - // ============================================================================ - // Refresh Operations - // ============================================================================ - - /** - * @brief Refresh the current overworld map - */ - void RefreshOverworldMap(); - - /** - * @brief Refresh a specific map on-demand (only if visible) - * - * @param map_index Index of the map to refresh - */ - void RefreshOverworldMapOnDemand(int map_index); - - /** - * @brief Refresh a child map (legacy method) - * - * @param map_index Index of the map to refresh - */ - void RefreshChildMap(int map_index); - - /** - * @brief Refresh a child map with selective updates - * - * @param map_index Index of the map to refresh - */ - void RefreshChildMapOnDemand(int map_index); - - /** - * @brief Safely refresh multi-area maps (large, wide, tall) - * - * Handles coordination of multi-area maps without recursion. - * - * @param map_index Index of the map to refresh - * @param map Pointer to the OverworldMap object - */ - void RefreshMultiAreaMapsSafely(int map_index, zelda3::OverworldMap* map); - - /** - * @brief Refresh the palette for the current map - * - * Also handles palette updates for multi-area maps. - */ - absl::Status RefreshMapPalette(); - - /** - * @brief Refresh the tile16 blockset - * - * This should be called whenever area graphics change. - */ - absl::Status RefreshTile16Blockset(); - - /** - * @brief Force a graphics refresh for a specific map - * - * Marks the map's bitmap as modified and clears the blockset cache - * to force a full reload on next refresh. Use this when graphics - * properties change (area_graphics, animated_gfx, custom tilesets). - * - * @param map_index Index of the map to force refresh - */ - void ForceRefreshGraphics(int map_index); - - // ============================================================================ - // State Management - // ============================================================================ - - void set_current_map(int map_index) { current_map_ = map_index; } - void set_current_world(int world_index) { current_world_ = world_index; } - void set_current_blockset(uint8_t blockset) { current_blockset_ = blockset; } - - int current_map() const { return current_map_; } - int current_world() const { return current_world_; } - uint8_t current_blockset() const { return current_blockset_; } - - bool all_gfx_loaded() const { return all_gfx_loaded_; } - bool map_blockset_loaded() const { return map_blockset_loaded_; } - - private: - // Core dependencies - zelda3::Overworld* overworld_; - Rom* rom_; - std::array* maps_bmp_; - gfx::Bitmap* tile16_blockset_bmp_; - gfx::Bitmap* current_gfx_bmp_; - std::vector* sprite_previews_; - gfx::SnesPalette* palette_; - Tilemap* tile16_blockset_; - - // State tracking - int current_map_ = 0; - int current_world_ = 0; - uint8_t current_blockset_ = 0; - bool all_gfx_loaded_ = false; - bool map_blockset_loaded_ = false; - - // Deferred texture loading - std::vector deferred_map_textures_; - std::mutex deferred_textures_mutex_; -}; - -} // namespace editor -} // namespace yaze - -#endif // YAZE_APP_EDITOR_OVERWORLD_OVERWORLD_GRAPHICS_MANAGER_H - diff --git a/src/app/gfx/arena.cc b/src/app/gfx/arena.cc index 947a47aa..7d8931d4 100644 --- a/src/app/gfx/arena.cc +++ b/src/app/gfx/arena.cc @@ -1,6 +1,7 @@ #include "app/gfx/arena.h" #include +#include #include "util/sdl_deleter.h" @@ -443,5 +444,66 @@ 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 lock(deferred_mutex_); + deferred_textures_.emplace_back(bitmap, priority); +} + +std::vector Arena::GetNextDeferredTextureBatch( + int high_priority_limit, int low_priority_limit) { + std::lock_guard lock(deferred_mutex_); + + std::vector 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(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(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 lock(deferred_mutex_); + deferred_textures_.clear(); +} + } // namespace gfx } // namespace yaze \ No newline at end of file diff --git a/src/app/gfx/arena.h b/src/app/gfx/arena.h index 73313d7b..373db503 100644 --- a/src/app/gfx/arena.h +++ b/src/app/gfx/arena.h @@ -4,12 +4,14 @@ #include #include #include +#include #include #include #include #include "util/sdl_deleter.h" #include "app/gfx/background_buffer.h" +#include "app/gfx/bitmap.h" namespace yaze { namespace gfx { @@ -161,6 +163,41 @@ 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 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,6 +250,16 @@ class Arena { // 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 deferred_textures_; + std::mutex deferred_mutex_; }; } // namespace gfx