diff --git a/src/app/editor/dungeon/dungeon_canvas_viewer.cc b/src/app/editor/dungeon/dungeon_canvas_viewer.cc index 99ebb8dc..1b55b1fe 100644 --- a/src/app/editor/dungeon/dungeon_canvas_viewer.cc +++ b/src/app/editor/dungeon/dungeon_canvas_viewer.cc @@ -511,6 +511,12 @@ void DungeonCanvasViewer::DrawDungeonCanvas(int room_id) { room.LoadObjects(); } + // CRITICAL: Process texture queue BEFORE drawing to ensure textures are ready + // This must happen before DrawRoomBackgroundLayers() attempts to draw bitmaps + if (rom_ && rom_->is_loaded()) { + gfx::Arena::Get().ProcessTextureQueue(nullptr); + } + // Draw the room's background layers to canvas // This already includes objects rendered by ObjectDrawer in Room::RenderObjectsToBackground() DrawRoomBackgroundLayers(room_id); @@ -545,13 +551,6 @@ void DungeonCanvasViewer::DrawDungeonCanvas(int room_id) { canvas_.DrawGrid(); canvas_.DrawOverlay(); - // Process queued texture commands - if (rom_ && rom_->is_loaded()) { - // Process texture queue using Arena's stored renderer - // The renderer was initialized in EditorManager::LoadAssets() - gfx::Arena::Get().ProcessTextureQueue(nullptr); - } - // Draw layer information overlay if (rooms_ && rom_->is_loaded()) { auto& room = (*rooms_)[room_id]; @@ -842,11 +841,12 @@ void DungeonCanvasViewer::DrawRoomBackgroundLayers(int room_id) { if (layer_settings.bg1_visible && bg1_bitmap.is_active() && bg1_bitmap.width() > 0 && bg1_bitmap.height() > 0) { if (!bg1_bitmap.texture()) { // Queue texture creation for background layer 1 via Arena's deferred system + // BATCHING FIX: Don't process immediately - let the main loop handle batching gfx::Arena::Get().QueueTextureCommand( gfx::Arena::TextureCommandType::CREATE, &bg1_bitmap); - - // CRITICAL FIX: Process texture queue immediately to ensure texture is created before drawing - gfx::Arena::Get().ProcessTextureQueue(nullptr); + + // Queue will be processed at the end of the frame in DrawDungeonCanvas() + // This allows multiple rooms to batch their texture operations together } // Only draw if texture was successfully created @@ -864,11 +864,12 @@ void DungeonCanvasViewer::DrawRoomBackgroundLayers(int room_id) { if (layer_settings.bg2_visible && bg2_bitmap.is_active() && bg2_bitmap.width() > 0 && bg2_bitmap.height() > 0) { if (!bg2_bitmap.texture()) { // Queue texture creation for background layer 2 via Arena's deferred system + // BATCHING FIX: Don't process immediately - let the main loop handle batching gfx::Arena::Get().QueueTextureCommand( gfx::Arena::TextureCommandType::CREATE, &bg2_bitmap); - - // CRITICAL FIX: Process texture queue immediately to ensure texture is created before drawing - gfx::Arena::Get().ProcessTextureQueue(nullptr); + + // Queue will be processed at the end of the frame in DrawDungeonCanvas() + // This allows multiple rooms to batch their texture operations together } // Only draw if texture was successfully created diff --git a/src/app/rom.h b/src/app/rom.h index 16c13643..093f8798 100644 --- a/src/app/rom.h +++ b/src/app/rom.h @@ -196,6 +196,7 @@ class Rom { bool is_loaded() const { return !rom_data_.empty(); } bool dirty() const { return dirty_; } + void set_dirty(bool dirty) { dirty_ = dirty; } void ClearDirty() { dirty_ = false; } auto title() const { return title_; } auto size() const { return size_; } diff --git a/src/app/zelda3/dungeon/object_drawer.cc b/src/app/zelda3/dungeon/object_drawer.cc index 82546401..01a0b5aa 100644 --- a/src/app/zelda3/dungeon/object_drawer.cc +++ b/src/app/zelda3/dungeon/object_drawer.cc @@ -18,8 +18,8 @@ absl::Status ObjectDrawer::DrawObject(const RoomObject& object, gfx::BackgroundBuffer& bg1, gfx::BackgroundBuffer& bg2, const gfx::PaletteGroup& palette_group) { - LOG_DEBUG("ObjectDrawer", "Drawing object 0x%02X at (%d,%d) size=%d", - object.id_, object.x_, object.y_, object.size_); + LOG_DEBUG("ObjectDrawer", "Drawing object 0x%02X at (%d,%d) size=%d tiles=%zu", + object.id_, object.x_, object.y_, object.size_, object.tiles().size()); if (!rom_ || !rom_->is_loaded()) { return absl::FailedPreconditionError("ROM not loaded"); @@ -37,7 +37,7 @@ absl::Status ObjectDrawer::DrawObject(const RoomObject& object, // Select buffer based on layer auto& target_bg = (object.layer_ == RoomObject::LayerType::BG2) ? bg2 : bg1; - // Skip objects that don't have tiles loaded - check mutable object + // Skip objects that don't have tiles loaded if (mutable_obj.tiles().empty()) { return absl::OkStatus(); } @@ -51,8 +51,8 @@ absl::Status ObjectDrawer::DrawObject(const RoomObject& object, LOG_DEBUG("ObjectDrawer", "Using fallback 1x1 drawing for object %04X", object.id_); // Fallback to simple 1x1 drawing using first 8x8 tile if (!mutable_obj.tiles().empty()) { - const auto& tile16 = mutable_obj.tiles()[0]; - WriteTile8(target_bg, object.x_, object.y_, tile16.tile0_); + const auto& tile_info = mutable_obj.tiles()[0]; + WriteTile8(target_bg, object.x_, object.y_, tile_info); } return absl::OkStatus(); } @@ -74,6 +74,25 @@ absl::Status ObjectDrawer::DrawObjectList( DrawObject(object, bg1, bg2, palette_group); } + // CRITICAL: Sync bitmap data to SDL surfaces after all objects are drawn + // ObjectDrawer writes directly to bitmap.mutable_data(), but textures are created from SDL surfaces + auto& bg1_bmp = bg1.bitmap(); + auto& bg2_bmp = bg2.bitmap(); + + if (bg1_bmp.modified() && bg1_bmp.surface() && bg1_bmp.mutable_data().size() > 0) { + SDL_LockSurface(bg1_bmp.surface()); + memcpy(bg1_bmp.surface()->pixels, bg1_bmp.mutable_data().data(), bg1_bmp.mutable_data().size()); + SDL_UnlockSurface(bg1_bmp.surface()); + printf("[ObjectDrawer] Synced BG1 bitmap data to SDL surface (%zu bytes)\n", bg1_bmp.mutable_data().size()); + } + + if (bg2_bmp.modified() && bg2_bmp.surface() && bg2_bmp.mutable_data().size() > 0) { + SDL_LockSurface(bg2_bmp.surface()); + memcpy(bg2_bmp.surface()->pixels, bg2_bmp.mutable_data().data(), bg2_bmp.mutable_data().size()); + SDL_UnlockSurface(bg2_bmp.surface()); + printf("[ObjectDrawer] Synced BG2 bitmap data to SDL surface (%zu bytes)\n", bg2_bmp.mutable_data().size()); + } + return absl::OkStatus(); } @@ -120,71 +139,71 @@ void ObjectDrawer::InitializeDrawRoutines() { draw_routines_.reserve(35); // Routine 0 - draw_routines_.push_back([](ObjectDrawer* self, const RoomObject& obj, gfx::BackgroundBuffer& bg, const std::vector& tiles) { + draw_routines_.push_back([](ObjectDrawer* self, const RoomObject& obj, gfx::BackgroundBuffer& bg, std::span tiles) { self->DrawRightwards2x2_1to15or32(obj, bg, tiles); }); // Routine 1 - draw_routines_.push_back([](ObjectDrawer* self, const RoomObject& obj, gfx::BackgroundBuffer& bg, const std::vector& tiles) { + draw_routines_.push_back([](ObjectDrawer* self, const RoomObject& obj, gfx::BackgroundBuffer& bg, std::span tiles) { self->DrawRightwards2x4_1to15or26(obj, bg, tiles); }); // Routine 2 - draw_routines_.push_back([](ObjectDrawer* self, const RoomObject& obj, gfx::BackgroundBuffer& bg, const std::vector& tiles) { + draw_routines_.push_back([](ObjectDrawer* self, const RoomObject& obj, gfx::BackgroundBuffer& bg, std::span tiles) { self->DrawRightwards2x4spaced4_1to16(obj, bg, tiles); }); // Routine 3 - draw_routines_.push_back([](ObjectDrawer* self, const RoomObject& obj, gfx::BackgroundBuffer& bg, const std::vector& tiles) { + draw_routines_.push_back([](ObjectDrawer* self, const RoomObject& obj, gfx::BackgroundBuffer& bg, std::span tiles) { self->DrawRightwards2x4spaced4_1to16_BothBG(obj, bg, tiles); }); // Routine 4 - draw_routines_.push_back([](ObjectDrawer* self, const RoomObject& obj, gfx::BackgroundBuffer& bg, const std::vector& tiles) { + draw_routines_.push_back([](ObjectDrawer* self, const RoomObject& obj, gfx::BackgroundBuffer& bg, std::span tiles) { self->DrawRightwards2x2_1to16(obj, bg, tiles); }); // Routine 5 - draw_routines_.push_back([](ObjectDrawer* self, const RoomObject& obj, gfx::BackgroundBuffer& bg, const std::vector& tiles) { + draw_routines_.push_back([](ObjectDrawer* self, const RoomObject& obj, gfx::BackgroundBuffer& bg, std::span tiles) { self->DrawDiagonalAcute_1to16(obj, bg, tiles); }); // Routine 6 - draw_routines_.push_back([](ObjectDrawer* self, const RoomObject& obj, gfx::BackgroundBuffer& bg, const std::vector& tiles) { + draw_routines_.push_back([](ObjectDrawer* self, const RoomObject& obj, gfx::BackgroundBuffer& bg, std::span tiles) { self->DrawDiagonalGrave_1to16(obj, bg, tiles); }); // Routine 7 - draw_routines_.push_back([](ObjectDrawer* self, const RoomObject& obj, gfx::BackgroundBuffer& bg, const std::vector& tiles) { + draw_routines_.push_back([](ObjectDrawer* self, const RoomObject& obj, gfx::BackgroundBuffer& bg, std::span tiles) { self->DrawDownwards2x2_1to15or32(obj, bg, tiles); }); // Routine 8 - draw_routines_.push_back([](ObjectDrawer* self, const RoomObject& obj, gfx::BackgroundBuffer& bg, const std::vector& tiles) { + draw_routines_.push_back([](ObjectDrawer* self, const RoomObject& obj, gfx::BackgroundBuffer& bg, std::span tiles) { self->DrawDownwards4x2_1to15or26(obj, bg, tiles); }); // Routine 9 - draw_routines_.push_back([](ObjectDrawer* self, const RoomObject& obj, gfx::BackgroundBuffer& bg, const std::vector& tiles) { + draw_routines_.push_back([](ObjectDrawer* self, const RoomObject& obj, gfx::BackgroundBuffer& bg, std::span tiles) { self->DrawDownwards4x2_1to16_BothBG(obj, bg, tiles); }); // Routine 10 - draw_routines_.push_back([](ObjectDrawer* self, const RoomObject& obj, gfx::BackgroundBuffer& bg, const std::vector& tiles) { + draw_routines_.push_back([](ObjectDrawer* self, const RoomObject& obj, gfx::BackgroundBuffer& bg, std::span tiles) { self->DrawDownwardsDecor4x2spaced4_1to16(obj, bg, tiles); }); // Routine 11 - draw_routines_.push_back([](ObjectDrawer* self, const RoomObject& obj, gfx::BackgroundBuffer& bg, const std::vector& tiles) { + draw_routines_.push_back([](ObjectDrawer* self, const RoomObject& obj, gfx::BackgroundBuffer& bg, std::span tiles) { self->DrawDownwards2x2_1to16(obj, bg, tiles); }); // Routine 12 - draw_routines_.push_back([](ObjectDrawer* self, const RoomObject& obj, gfx::BackgroundBuffer& bg, const std::vector& tiles) { + draw_routines_.push_back([](ObjectDrawer* self, const RoomObject& obj, gfx::BackgroundBuffer& bg, std::span tiles) { self->DrawDownwardsHasEdge1x1_1to16_plus3(obj, bg, tiles); }); // Routine 13 - draw_routines_.push_back([](ObjectDrawer* self, const RoomObject& obj, gfx::BackgroundBuffer& bg, const std::vector& tiles) { + draw_routines_.push_back([](ObjectDrawer* self, const RoomObject& obj, gfx::BackgroundBuffer& bg, std::span tiles) { self->DrawDownwardsEdge1x1_1to16(obj, bg, tiles); }); // Routine 14 - draw_routines_.push_back([](ObjectDrawer* self, const RoomObject& obj, gfx::BackgroundBuffer& bg, const std::vector& tiles) { + draw_routines_.push_back([](ObjectDrawer* self, const RoomObject& obj, gfx::BackgroundBuffer& bg, std::span tiles) { self->DrawDownwardsLeftCorners2x1_1to16_plus12(obj, bg, tiles); }); // Routine 15 - draw_routines_.push_back([](ObjectDrawer* self, const RoomObject& obj, gfx::BackgroundBuffer& bg, const std::vector& tiles) { + draw_routines_.push_back([](ObjectDrawer* self, const RoomObject& obj, gfx::BackgroundBuffer& bg, std::span tiles) { self->DrawDownwardsRightCorners2x1_1to16_plus12(obj, bg, tiles); }); // Routine 16 - draw_routines_.push_back([](ObjectDrawer* self, const RoomObject& obj, gfx::BackgroundBuffer& bg, const std::vector& tiles) { + draw_routines_.push_back([](ObjectDrawer* self, const RoomObject& obj, gfx::BackgroundBuffer& bg, std::span tiles) { self->DrawRightwards4x4_1to16(obj, bg, tiles); }); @@ -206,106 +225,98 @@ int ObjectDrawer::GetDrawRoutineId(int16_t object_id) const { // ============================================================================ void ObjectDrawer::DrawRightwards2x2_1to15or32(const RoomObject& obj, gfx::BackgroundBuffer& bg, - const std::vector& tiles) { + std::span tiles) { // Pattern: Draws 2x2 tiles rightward (object 0x00) // Size byte determines how many times to repeat (1-15 or 32) int size = obj.size_; if (size == 0) size = 32; // Special case for object 0x00 for (int s = 0; s < size; s++) { - if (tiles.size() >= 1) { - // Draw 2x2 pattern using 8x8 tiles from the first Tile16 - const auto& tile16 = tiles[0]; - WriteTile8(bg, obj.x_ + (s * 2), obj.y_, tile16.tile0_); // Top-left - WriteTile8(bg, obj.x_ + (s * 2) + 1, obj.y_, tile16.tile1_); // Top-right - WriteTile8(bg, obj.x_ + (s * 2), obj.y_ + 1, tile16.tile2_); // Bottom-left - WriteTile8(bg, obj.x_ + (s * 2) + 1, obj.y_ + 1, tile16.tile3_); // Bottom-right + if (tiles.size() >= 4) { + // Draw 2x2 pattern using 8x8 tiles from the span + WriteTile8(bg, obj.x_ + (s * 2), obj.y_, tiles[0]); // Top-left + WriteTile8(bg, obj.x_ + (s * 2) + 1, obj.y_, tiles[1]); // Top-right + WriteTile8(bg, obj.x_ + (s * 2), obj.y_ + 1, tiles[2]); // Bottom-left + WriteTile8(bg, obj.x_ + (s * 2) + 1, obj.y_ + 1, tiles[3]); // Bottom-right } } } void ObjectDrawer::DrawRightwards2x4_1to15or26(const RoomObject& obj, gfx::BackgroundBuffer& bg, - const std::vector& tiles) { + std::span tiles) { // Pattern: Draws 2x4 tiles rightward (objects 0x01-0x02) int size = obj.size_; if (size == 0) size = 26; // Special case for (int s = 0; s < size; s++) { - if (tiles.size() >= 1) { - // Draw 2x4 pattern using 8x8 tiles from the first Tile16 - const auto& tile16 = tiles[0]; - // For 2x4, we'll use the same tile16 pattern repeated - WriteTile8(bg, obj.x_ + (s * 2), obj.y_, tile16.tile0_); // Top-left - WriteTile8(bg, obj.x_ + (s * 2) + 1, obj.y_, tile16.tile1_); // Top-right - WriteTile8(bg, obj.x_ + (s * 2), obj.y_ + 1, tile16.tile2_); // Mid-left - WriteTile8(bg, obj.x_ + (s * 2) + 1, obj.y_ + 1, tile16.tile3_); // Mid-right - WriteTile8(bg, obj.x_ + (s * 2), obj.y_ + 2, tile16.tile0_); // Bottom-left (repeat) - WriteTile8(bg, obj.x_ + (s * 2) + 1, obj.y_ + 2, tile16.tile1_); // Bottom-right (repeat) - WriteTile8(bg, obj.x_ + (s * 2), obj.y_ + 3, tile16.tile2_); // Bottom-left (repeat) - WriteTile8(bg, obj.x_ + (s * 2) + 1, obj.y_ + 3, tile16.tile3_); // Bottom-right (repeat) + if (tiles.size() >= 4) { + // For 2x4, we'll use the same tile pattern repeated + WriteTile8(bg, obj.x_ + (s * 2), obj.y_, tiles[0]); // Top-left + WriteTile8(bg, obj.x_ + (s * 2) + 1, obj.y_, tiles[1]); // Top-right + WriteTile8(bg, obj.x_ + (s * 2), obj.y_ + 1, tiles[2]); // Mid-left + WriteTile8(bg, obj.x_ + (s * 2) + 1, obj.y_ + 1, tiles[3]); // Mid-right + WriteTile8(bg, obj.x_ + (s * 2), obj.y_ + 2, tiles[0]); // Bottom-left (repeat) + WriteTile8(bg, obj.x_ + (s * 2) + 1, obj.y_ + 2, tiles[1]); // Bottom-right (repeat) + WriteTile8(bg, obj.x_ + (s * 2), obj.y_ + 3, tiles[2]); // Bottom-left (repeat) + WriteTile8(bg, obj.x_ + (s * 2) + 1, obj.y_ + 3, tiles[3]); // Bottom-right (repeat) } } } void ObjectDrawer::DrawRightwards2x4spaced4_1to16(const RoomObject& obj, gfx::BackgroundBuffer& bg, - const std::vector& tiles) { + std::span tiles) { // Pattern: Draws 2x4 tiles rightward with spacing (objects 0x03-0x04) int size = obj.size_ & 0x0F; for (int s = 0; s < size; s++) { - if (tiles.size() >= 1) { - // Draw 2x4 pattern with spacing using 8x8 tiles from first Tile16 - const auto& tile16 = tiles[0]; - WriteTile8(bg, obj.x_ + (s * 6), obj.y_, tile16.tile0_); // Top-left - WriteTile8(bg, obj.x_ + (s * 6) + 1, obj.y_, tile16.tile1_); // Top-right - WriteTile8(bg, obj.x_ + (s * 6), obj.y_ + 1, tile16.tile2_); // Mid-left - WriteTile8(bg, obj.x_ + (s * 6) + 1, obj.y_ + 1, tile16.tile3_); // Mid-right - WriteTile8(bg, obj.x_ + (s * 6), obj.y_ + 2, tile16.tile0_); // Bottom-left (repeat) - WriteTile8(bg, obj.x_ + (s * 6) + 1, obj.y_ + 2, tile16.tile1_); // Bottom-right (repeat) - WriteTile8(bg, obj.x_ + (s * 6), obj.y_ + 3, tile16.tile2_); // Bottom-left (repeat) - WriteTile8(bg, obj.x_ + (s * 6) + 1, obj.y_ + 3, tile16.tile3_); // Bottom-right (repeat) + if (tiles.size() >= 4) { + // Draw 2x4 pattern with spacing using 8x8 tiles from span + WriteTile8(bg, obj.x_ + (s * 6), obj.y_, tiles[0]); // Top-left + WriteTile8(bg, obj.x_ + (s * 6) + 1, obj.y_, tiles[1]); // Top-right + WriteTile8(bg, obj.x_ + (s * 6), obj.y_ + 1, tiles[2]); // Mid-left + WriteTile8(bg, obj.x_ + (s * 6) + 1, obj.y_ + 1, tiles[3]); // Mid-right + WriteTile8(bg, obj.x_ + (s * 6), obj.y_ + 2, tiles[0]); // Bottom-left (repeat) + WriteTile8(bg, obj.x_ + (s * 6) + 1, obj.y_ + 2, tiles[1]); // Bottom-right (repeat) + WriteTile8(bg, obj.x_ + (s * 6), obj.y_ + 3, tiles[2]); // Bottom-left (repeat) + WriteTile8(bg, obj.x_ + (s * 6) + 1, obj.y_ + 3, tiles[3]); // Bottom-right (repeat) } } } void ObjectDrawer::DrawRightwards2x4spaced4_1to16_BothBG(const RoomObject& obj, gfx::BackgroundBuffer& bg, - const std::vector& tiles) { + std::span tiles) { // Pattern: Same as above but draws to both BG1 and BG2 (objects 0x05-0x06) DrawRightwards2x4spaced4_1to16(obj, bg, tiles); // Note: BothBG would require access to both buffers - simplified for now } void ObjectDrawer::DrawRightwards2x2_1to16(const RoomObject& obj, gfx::BackgroundBuffer& bg, - const std::vector& tiles) { + std::span tiles) { // Pattern: Draws 2x2 tiles rightward (objects 0x07-0x08) int size = obj.size_ & 0x0F; for (int s = 0; s < size; s++) { - if (tiles.size() >= 1) { - // Draw 2x2 pattern using 8x8 tiles from first Tile16 - const auto& tile16 = tiles[0]; - WriteTile8(bg, obj.x_ + (s * 2), obj.y_, tile16.tile0_); // Top-left - WriteTile8(bg, obj.x_ + (s * 2) + 1, obj.y_, tile16.tile1_); // Top-right - WriteTile8(bg, obj.x_ + (s * 2), obj.y_ + 1, tile16.tile2_); // Bottom-left - WriteTile8(bg, obj.x_ + (s * 2) + 1, obj.y_ + 1, tile16.tile3_); // Bottom-right + if (tiles.size() >= 4) { + // Draw 2x2 pattern using 8x8 tiles from span + WriteTile8(bg, obj.x_ + (s * 2), obj.y_, tiles[0]); // Top-left + WriteTile8(bg, obj.x_ + (s * 2) + 1, obj.y_, tiles[1]); // Top-right + WriteTile8(bg, obj.x_ + (s * 2), obj.y_ + 1, tiles[2]); // Bottom-left + WriteTile8(bg, obj.x_ + (s * 2) + 1, obj.y_ + 1, tiles[3]); // Bottom-right } } } void ObjectDrawer::DrawDiagonalAcute_1to16(const RoomObject& obj, gfx::BackgroundBuffer& bg, - const std::vector& tiles) { + std::span tiles) { // Pattern: Diagonal line going down-right (/) (object 0x09) int size = obj.size_ & 0x0F; for (int s = 0; s < size + 6; s++) { - if (tiles.size() >= 1) { - // Use first tile16 for diagonal pattern - const auto& tile16 = tiles[0]; + if (tiles.size() >= 4) { + // Use first tile span for diagonal pattern for (int i = 0; i < 5; i++) { - // Cycle through the 4 tiles in the tile16 - const gfx::TileInfo& tile_info = (i % 4 == 0) ? tile16.tile0_ : - (i % 4 == 1) ? tile16.tile1_ : - (i % 4 == 2) ? tile16.tile2_ : tile16.tile3_; + // Cycle through the 4 tiles in the span + const gfx::TileInfo& tile_info = tiles[i % 4]; WriteTile8(bg, obj.x_ + s, obj.y_ + (i - s), tile_info); } } @@ -313,19 +324,16 @@ void ObjectDrawer::DrawDiagonalAcute_1to16(const RoomObject& obj, gfx::Backgroun } void ObjectDrawer::DrawDiagonalGrave_1to16(const RoomObject& obj, gfx::BackgroundBuffer& bg, - const std::vector& tiles) { + std::span tiles) { // Pattern: Diagonal line going down-left (\) (objects 0x0A-0x0B) int size = obj.size_ & 0x0F; for (int s = 0; s < size + 6; s++) { - if (tiles.size() >= 1) { - // Use first tile16 for diagonal pattern - const auto& tile16 = tiles[0]; + if (tiles.size() >= 4) { + // Use first tile span for diagonal pattern for (int i = 0; i < 5; i++) { - // Cycle through the 4 tiles in the tile16 - const gfx::TileInfo& tile_info = (i % 4 == 0) ? tile16.tile0_ : - (i % 4 == 1) ? tile16.tile1_ : - (i % 4 == 2) ? tile16.tile2_ : tile16.tile3_; + // Cycle through the 4 tiles in the span + const gfx::TileInfo& tile_info = tiles[i % 4]; WriteTile8(bg, obj.x_ + s, obj.y_ + (i + s), tile_info); } } @@ -333,290 +341,232 @@ void ObjectDrawer::DrawDiagonalGrave_1to16(const RoomObject& obj, gfx::Backgroun } void ObjectDrawer::DrawDiagonalAcute_1to16_BothBG(const RoomObject& obj, gfx::BackgroundBuffer& bg, - const std::vector& tiles) { + std::span tiles) { // Pattern: Diagonal acute for both BG layers (objects 0x15-0x1F) DrawDiagonalAcute_1to16(obj, bg, tiles); } void ObjectDrawer::DrawDiagonalGrave_1to16_BothBG(const RoomObject& obj, gfx::BackgroundBuffer& bg, - const std::vector& tiles) { + std::span tiles) { // Pattern: Diagonal grave for both BG layers (objects 0x16-0x20) DrawDiagonalGrave_1to16(obj, bg, tiles); } void ObjectDrawer::DrawRightwards1x2_1to16_plus2(const RoomObject& obj, gfx::BackgroundBuffer& bg, - const std::vector& tiles) { + std::span tiles) { // Pattern: 1x2 tiles rightward with +2 offset (object 0x21) int size = obj.size_ & 0x0F; for (int s = 0; s < size; s++) { - if (tiles.size() >= 1) { - // Use first tile16 for 1x2 pattern - const auto& tile16 = tiles[0]; - WriteTile8(bg, obj.x_ + s + 2, obj.y_, tile16.tile0_); - WriteTile8(bg, obj.x_ + s + 2, obj.y_ + 1, tile16.tile2_); + if (tiles.size() >= 2) { + // Use first tile span for 1x2 pattern + WriteTile8(bg, obj.x_ + s + 2, obj.y_, tiles[0]); + WriteTile8(bg, obj.x_ + s + 2, obj.y_ + 1, tiles[1]); } } } void ObjectDrawer::DrawRightwardsHasEdge1x1_1to16_plus3(const RoomObject& obj, gfx::BackgroundBuffer& bg, - const std::vector& tiles) { + std::span tiles) { // Pattern: 1x1 tiles with edge detection +3 offset (object 0x22) int size = obj.size_ & 0x0F; for (int s = 0; s < size; s++) { if (tiles.size() >= 1) { - // Use first 8x8 tile from first tile16 - const auto& tile16 = tiles[0]; - WriteTile8(bg, obj.x_ + s + 3, obj.y_, tile16.tile0_); + // Use first 8x8 tile from span + WriteTile8(bg, obj.x_ + s + 3, obj.y_, tiles[0]); } } } void ObjectDrawer::DrawRightwardsHasEdge1x1_1to16_plus2(const RoomObject& obj, gfx::BackgroundBuffer& bg, - const std::vector& tiles) { + std::span tiles) { // Pattern: 1x1 tiles with edge detection +2 offset (objects 0x23-0x2E) int size = obj.size_ & 0x0F; for (int s = 0; s < size; s++) { if (tiles.size() >= 1) { - // Use first 8x8 tile from first tile16 - const auto& tile16 = tiles[0]; - WriteTile8(bg, obj.x_ + s + 2, obj.y_, tile16.tile0_); + // Use first 8x8 tile from span + WriteTile8(bg, obj.x_ + s + 2, obj.y_, tiles[0]); } } } void ObjectDrawer::DrawRightwardsTopCorners1x2_1to16_plus13(const RoomObject& obj, gfx::BackgroundBuffer& bg, - const std::vector& tiles) { + std::span tiles) { // Pattern: Top corner 1x2 tiles with +13 offset (object 0x2F) int size = obj.size_ & 0x0F; for (int s = 0; s < size; s++) { - if (tiles.size() >= 1) { - // Use first tile16 for 1x2 pattern - const auto& tile16 = tiles[0]; - WriteTile8(bg, obj.x_ + s + 13, obj.y_, tile16.tile0_); - WriteTile8(bg, obj.x_ + s + 13, obj.y_ + 1, tile16.tile2_); + if (tiles.size() >= 2) { + // Use first tile span for 1x2 pattern + WriteTile8(bg, obj.x_ + s + 13, obj.y_, tiles[0]); + WriteTile8(bg, obj.x_ + s + 13, obj.y_ + 1, tiles[1]); } } } void ObjectDrawer::DrawRightwardsBottomCorners1x2_1to16_plus13(const RoomObject& obj, gfx::BackgroundBuffer& bg, - const std::vector& tiles) { + const std::span tiles) { // Pattern: Bottom corner 1x2 tiles with +13 offset (object 0x30) int size = obj.size_ & 0x0F; for (int s = 0; s < size; s++) { - if (tiles.size() >= 1) { - // Use first tile16 for 1x2 pattern - const auto& tile16 = tiles[0]; - WriteTile8(bg, obj.x_ + s + 13, obj.y_ + 1, tile16.tile0_); - WriteTile8(bg, obj.x_ + s + 13, obj.y_ + 2, tile16.tile2_); + if (tiles.size() >= 2) { + // Use first tile span for 1x2 pattern + WriteTile8(bg, obj.x_ + s + 13, obj.y_ + 1, tiles[0]); + WriteTile8(bg, obj.x_ + s + 13, obj.y_ + 2, tiles[1]); } } } void ObjectDrawer::CustomDraw(const RoomObject& obj, gfx::BackgroundBuffer& bg, - const std::vector& tiles) { + std::span tiles) { // Pattern: Custom draw routine (objects 0x31-0x32) // For now, fall back to simple 1x1 if (tiles.size() >= 1) { - // Use first 8x8 tile from first tile16 - const auto& tile16 = tiles[0]; - WriteTile8(bg, obj.x_, obj.y_, tile16.tile0_); + // Use first 8x8 tile from span + WriteTile8(bg, obj.x_, obj.y_, tiles[0]); } } void ObjectDrawer::DrawRightwards4x4_1to16(const RoomObject& obj, gfx::BackgroundBuffer& bg, - const std::vector& tiles) { + std::span tiles) { // Pattern: 4x4 block rightward (object 0x33) int size = obj.size_ & 0x0F; for (int s = 0; s < size; s++) { - if (tiles.size() >= 1) { - // Use first tile16 for 4x4 pattern (repeat the 2x2 pattern) - const auto& tile16 = tiles[0]; - // Draw 2x2 pattern repeated to make 4x4 - WriteTile8(bg, obj.x_ + (s * 4), obj.y_, tile16.tile0_); // Top-left - WriteTile8(bg, obj.x_ + (s * 4) + 1, obj.y_, tile16.tile1_); // Top-right - WriteTile8(bg, obj.x_ + (s * 4), obj.y_ + 1, tile16.tile2_); // Bottom-left - WriteTile8(bg, obj.x_ + (s * 4) + 1, obj.y_ + 1, tile16.tile3_); // Bottom-right - WriteTile8(bg, obj.x_ + (s * 4) + 2, obj.y_, tile16.tile0_); // Top-left (repeat) - WriteTile8(bg, obj.x_ + (s * 4) + 3, obj.y_, tile16.tile1_); // Top-right (repeat) - WriteTile8(bg, obj.x_ + (s * 4) + 2, obj.y_ + 1, tile16.tile2_); // Bottom-left (repeat) - WriteTile8(bg, obj.x_ + (s * 4) + 3, obj.y_ + 1, tile16.tile3_); // Bottom-right (repeat) - WriteTile8(bg, obj.x_ + (s * 4), obj.y_ + 2, tile16.tile0_); // Top-left (repeat) - WriteTile8(bg, obj.x_ + (s * 4) + 1, obj.y_ + 2, tile16.tile1_); // Top-right (repeat) - WriteTile8(bg, obj.x_ + (s * 4), obj.y_ + 3, tile16.tile2_); // Bottom-left (repeat) - WriteTile8(bg, obj.x_ + (s * 4) + 1, obj.y_ + 3, tile16.tile3_); // Bottom-right (repeat) - WriteTile8(bg, obj.x_ + (s * 4) + 2, obj.y_ + 2, tile16.tile0_); // Top-left (repeat) - WriteTile8(bg, obj.x_ + (s * 4) + 3, obj.y_ + 2, tile16.tile1_); // Top-right (repeat) - WriteTile8(bg, obj.x_ + (s * 4) + 2, obj.y_ + 3, tile16.tile2_); // Bottom-left (repeat) - WriteTile8(bg, obj.x_ + (s * 4) + 3, obj.y_ + 3, tile16.tile3_); // Bottom-right (repeat) + if (tiles.size() >= 16) { + // Draw 4x4 pattern using 8x8 tiles from span + for (int y = 0; y < 4; ++y) { + for (int x = 0; x < 4; ++x) { + WriteTile8(bg, obj.x_ + (s * 4) + x, obj.y_ + y, tiles[y * 4 + x]); + } + } } } } void ObjectDrawer::DrawRightwards1x1Solid_1to16_plus3(const RoomObject& obj, gfx::BackgroundBuffer& bg, - const std::vector& tiles) { + std::span tiles) { // Pattern: 1x1 solid tiles +3 offset (object 0x34) int size = obj.size_ & 0x0F; for (int s = 0; s < size; s++) { if (tiles.size() >= 1) { - // Use first 8x8 tile from first tile16 - const auto& tile16 = tiles[0]; - WriteTile8(bg, obj.x_ + s + 3, obj.y_, tile16.tile0_); + // Use first 8x8 tile from span + WriteTile8(bg, obj.x_ + s + 3, obj.y_, tiles[0]); } } } void ObjectDrawer::DrawDoorSwitcherer(const RoomObject& obj, gfx::BackgroundBuffer& bg, - const std::vector& tiles) { + std::span tiles) { // Pattern: Door switcher (object 0x35) // Special door logic - simplified for now if (tiles.size() >= 1) { - // Use first 8x8 tile from first tile16 - const auto& tile16 = tiles[0]; - WriteTile8(bg, obj.x_, obj.y_, tile16.tile0_); + // Use first 8x8 tile from span + WriteTile8(bg, obj.x_, obj.y_, tiles[0]); } } void ObjectDrawer::DrawRightwardsDecor4x4spaced2_1to16(const RoomObject& obj, gfx::BackgroundBuffer& bg, - const std::vector& tiles) { + std::span tiles) { // Pattern: 4x4 decoration with spacing (objects 0x36-0x37) int size = obj.size_ & 0x0F; for (int s = 0; s < size; s++) { - if (tiles.size() >= 1) { - // Use first tile16 for 4x4 pattern with spacing - const auto& tile16 = tiles[0]; - // Draw 2x2 pattern repeated to make 4x4 with spacing - WriteTile8(bg, obj.x_ + (s * 6), obj.y_, tile16.tile0_); // Top-left - WriteTile8(bg, obj.x_ + (s * 6) + 1, obj.y_, tile16.tile1_); // Top-right - WriteTile8(bg, obj.x_ + (s * 6), obj.y_ + 1, tile16.tile2_); // Bottom-left - WriteTile8(bg, obj.x_ + (s * 6) + 1, obj.y_ + 1, tile16.tile3_); // Bottom-right - WriteTile8(bg, obj.x_ + (s * 6) + 2, obj.y_, tile16.tile0_); // Top-left (repeat) - WriteTile8(bg, obj.x_ + (s * 6) + 3, obj.y_, tile16.tile1_); // Top-right (repeat) - WriteTile8(bg, obj.x_ + (s * 6) + 2, obj.y_ + 1, tile16.tile2_); // Bottom-left (repeat) - WriteTile8(bg, obj.x_ + (s * 6) + 3, obj.y_ + 1, tile16.tile3_); // Bottom-right (repeat) - WriteTile8(bg, obj.x_ + (s * 6), obj.y_ + 2, tile16.tile0_); // Top-left (repeat) - WriteTile8(bg, obj.x_ + (s * 6) + 1, obj.y_ + 2, tile16.tile1_); // Top-right (repeat) - WriteTile8(bg, obj.x_ + (s * 6), obj.y_ + 3, tile16.tile2_); // Bottom-left (repeat) - WriteTile8(bg, obj.x_ + (s * 6) + 1, obj.y_ + 3, tile16.tile3_); // Bottom-right (repeat) - WriteTile8(bg, obj.x_ + (s * 6) + 2, obj.y_ + 2, tile16.tile0_); // Top-left (repeat) - WriteTile8(bg, obj.x_ + (s * 6) + 3, obj.y_ + 2, tile16.tile1_); // Top-right (repeat) - WriteTile8(bg, obj.x_ + (s * 6) + 2, obj.y_ + 3, tile16.tile2_); // Bottom-left (repeat) - WriteTile8(bg, obj.x_ + (s * 6) + 3, obj.y_ + 3, tile16.tile3_); // Bottom-right (repeat) + if (tiles.size() >= 16) { + // Draw 4x4 pattern with spacing using 8x8 tiles from span + for (int y = 0; y < 4; ++y) { + for (int x = 0; x < 4; ++x) { + WriteTile8(bg, obj.x_ + (s * 6) + x, obj.y_ + y, tiles[y * 4 + x]); + } + } } } } void ObjectDrawer::DrawRightwardsStatue2x3spaced2_1to16(const RoomObject& obj, gfx::BackgroundBuffer& bg, - const std::vector& tiles) { + std::span tiles) { // Pattern: 2x3 statue with spacing (object 0x38) int size = obj.size_ & 0x0F; for (int s = 0; s < size; s++) { - if (tiles.size() >= 1) { - // Use first tile16 for 2x3 statue pattern - const auto& tile16 = tiles[0]; - // Draw 2x3 pattern using 8x8 tiles - WriteTile8(bg, obj.x_ + (s * 4), obj.y_, tile16.tile0_); // Top-left - WriteTile8(bg, obj.x_ + (s * 4) + 1, obj.y_, tile16.tile1_); // Top-right - WriteTile8(bg, obj.x_ + (s * 4), obj.y_ + 1, tile16.tile2_); // Mid-left - WriteTile8(bg, obj.x_ + (s * 4) + 1, obj.y_ + 1, tile16.tile3_); // Mid-right - WriteTile8(bg, obj.x_ + (s * 4), obj.y_ + 2, tile16.tile0_); // Bottom-left (repeat) - WriteTile8(bg, obj.x_ + (s * 4) + 1, obj.y_ + 2, tile16.tile1_); // Bottom-right (repeat) + if (tiles.size() >= 6) { + // Draw 2x3 pattern using 8x8 tiles from span + for (int y = 0; y < 3; ++y) { + for (int x = 0; x < 2; ++x) { + WriteTile8(bg, obj.x_ + (s * 4) + x, obj.y_ + y, tiles[y * 2 + x]); + } + } } } } void ObjectDrawer::DrawRightwardsPillar2x4spaced4_1to16(const RoomObject& obj, gfx::BackgroundBuffer& bg, - const std::vector& tiles) { + std::span tiles) { // Pattern: 2x4 pillar with spacing (objects 0x39, 0x3D) int size = obj.size_ & 0x0F; for (int s = 0; s < size; s++) { - if (tiles.size() >= 1) { - // Use first tile16 for 2x4 pillar pattern - const auto& tile16 = tiles[0]; - // Draw 2x4 pattern using 8x8 tiles - WriteTile8(bg, obj.x_ + (s * 6), obj.y_, tile16.tile0_); // Top-left - WriteTile8(bg, obj.x_ + (s * 6) + 1, obj.y_, tile16.tile1_); // Top-right - WriteTile8(bg, obj.x_ + (s * 6), obj.y_ + 1, tile16.tile2_); // Mid-left - WriteTile8(bg, obj.x_ + (s * 6) + 1, obj.y_ + 1, tile16.tile3_); // Mid-right - WriteTile8(bg, obj.x_ + (s * 6), obj.y_ + 2, tile16.tile0_); // Bottom-left (repeat) - WriteTile8(bg, obj.x_ + (s * 6) + 1, obj.y_ + 2, tile16.tile1_); // Bottom-right (repeat) - WriteTile8(bg, obj.x_ + (s * 6), obj.y_ + 3, tile16.tile2_); // Bottom-left (repeat) - WriteTile8(bg, obj.x_ + (s * 6) + 1, obj.y_ + 3, tile16.tile3_); // Bottom-right (repeat) + if (tiles.size() >= 8) { + // Draw 2x4 pattern using 8x8 tiles from span + for (int y = 0; y < 4; ++y) { + for (int x = 0; x < 2; ++x) { + WriteTile8(bg, obj.x_ + (s * 6) + x, obj.y_ + y, tiles[y * 2 + x]); + } + } } } } void ObjectDrawer::DrawRightwardsDecor4x3spaced4_1to16(const RoomObject& obj, gfx::BackgroundBuffer& bg, - const std::vector& tiles) { + std::span tiles) { // Pattern: 4x3 decoration with spacing (objects 0x3A-0x3B) int size = obj.size_ & 0x0F; for (int s = 0; s < size; s++) { - if (tiles.size() >= 1) { - // Use first tile16 for 4x3 decoration pattern - const auto& tile16 = tiles[0]; - // Draw 4x3 pattern using 8x8 tiles - WriteTile8(bg, obj.x_ + (s * 6), obj.y_, tile16.tile0_); // Top-left - WriteTile8(bg, obj.x_ + (s * 6) + 1, obj.y_, tile16.tile1_); // Top-right - WriteTile8(bg, obj.x_ + (s * 6) + 2, obj.y_, tile16.tile0_); // Top-left (repeat) - WriteTile8(bg, obj.x_ + (s * 6) + 3, obj.y_, tile16.tile1_); // Top-right (repeat) - WriteTile8(bg, obj.x_ + (s * 6), obj.y_ + 1, tile16.tile2_); // Mid-left - WriteTile8(bg, obj.x_ + (s * 6) + 1, obj.y_ + 1, tile16.tile3_); // Mid-right - WriteTile8(bg, obj.x_ + (s * 6) + 2, obj.y_ + 1, tile16.tile2_); // Mid-left (repeat) - WriteTile8(bg, obj.x_ + (s * 6) + 3, obj.y_ + 1, tile16.tile3_); // Mid-right (repeat) - WriteTile8(bg, obj.x_ + (s * 6), obj.y_ + 2, tile16.tile0_); // Bottom-left (repeat) - WriteTile8(bg, obj.x_ + (s * 6) + 1, obj.y_ + 2, tile16.tile1_); // Bottom-right (repeat) - WriteTile8(bg, obj.x_ + (s * 6) + 2, obj.y_ + 2, tile16.tile0_); // Bottom-left (repeat) - WriteTile8(bg, obj.x_ + (s * 6) + 3, obj.y_ + 2, tile16.tile1_); // Bottom-right (repeat) + if (tiles.size() >= 12) { + // Draw 4x3 pattern using 8x8 tiles from span + for (int y = 0; y < 3; ++y) { + for (int x = 0; x < 4; ++x) { + WriteTile8(bg, obj.x_ + (s * 6) + x, obj.y_ + y, tiles[y * 4 + x]); + } + } } } } void ObjectDrawer::DrawRightwardsDoubled2x2spaced2_1to16(const RoomObject& obj, gfx::BackgroundBuffer& bg, - const std::vector& tiles) { + std::span tiles) { // Pattern: Doubled 2x2 with spacing (object 0x3C) int size = obj.size_ & 0x0F; for (int s = 0; s < size; s++) { - if (tiles.size() >= 1) { - // Use first tile16 for doubled 2x2 pattern - const auto& tile16 = tiles[0]; - // Draw doubled 2x2 pattern using 8x8 tiles - WriteTile8(bg, obj.x_ + (s * 6), obj.y_, tile16.tile0_); // Top-left - WriteTile8(bg, obj.x_ + (s * 6) + 1, obj.y_, tile16.tile1_); // Top-right - WriteTile8(bg, obj.x_ + (s * 6) + 2, obj.y_, tile16.tile0_); // Top-left (repeat) - WriteTile8(bg, obj.x_ + (s * 6) + 3, obj.y_, tile16.tile1_); // Top-right (repeat) - WriteTile8(bg, obj.x_ + (s * 6), obj.y_ + 1, tile16.tile2_); // Bottom-left - WriteTile8(bg, obj.x_ + (s * 6) + 1, obj.y_ + 1, tile16.tile3_); // Bottom-right - WriteTile8(bg, obj.x_ + (s * 6) + 2, obj.y_ + 1, tile16.tile2_); // Bottom-left (repeat) - WriteTile8(bg, obj.x_ + (s * 6) + 3, obj.y_ + 1, tile16.tile3_); // Bottom-right (repeat) + if (tiles.size() >= 8) { + // Draw doubled 2x2 pattern using 8x8 tiles from span + for (int y = 0; y < 2; ++y) { + for (int x = 0; x < 4; ++x) { // Draw a 4x2 area + WriteTile8(bg, obj.x_ + (s * 6) + x, obj.y_ + y, tiles[y * 4 + x]); + } + } } } } void ObjectDrawer::DrawRightwardsDecor2x2spaced12_1to16(const RoomObject& obj, gfx::BackgroundBuffer& bg, - const std::vector& tiles) { + std::span tiles) { // Pattern: 2x2 decoration with large spacing (object 0x3E) int size = obj.size_ & 0x0F; for (int s = 0; s < size; s++) { - if (tiles.size() >= 1) { - // Use first tile16 for 2x2 decoration with large spacing - const auto& tile16 = tiles[0]; - // Draw 2x2 decoration with 12-tile spacing using 8x8 tiles - WriteTile8(bg, obj.x_ + (s * 14), obj.y_, tile16.tile0_); // Top-left - WriteTile8(bg, obj.x_ + (s * 14) + 1, obj.y_, tile16.tile1_); // Top-right - WriteTile8(bg, obj.x_ + (s * 14), obj.y_ + 1, tile16.tile2_); // Bottom-left - WriteTile8(bg, obj.x_ + (s * 14) + 1, obj.y_ + 1, tile16.tile3_); // Bottom-right + if (tiles.size() >= 4) { + // Draw 2x2 decoration with 12-tile spacing using 8x8 tiles from span + WriteTile8(bg, obj.x_ + (s * 14), obj.y_, tiles[0]); // Top-left + WriteTile8(bg, obj.x_ + (s * 14) + 1, obj.y_, tiles[1]); // Top-right + WriteTile8(bg, obj.x_ + (s * 14), obj.y_ + 1, tiles[2]); // Bottom-left + WriteTile8(bg, obj.x_ + (s * 14) + 1, obj.y_ + 1, tiles[3]); // Bottom-right } } } @@ -626,26 +576,25 @@ void ObjectDrawer::DrawRightwardsDecor2x2spaced12_1to16(const RoomObject& obj, g // ============================================================================ void ObjectDrawer::DrawDownwards2x2_1to15or32(const RoomObject& obj, gfx::BackgroundBuffer& bg, - const std::vector& tiles) { + std::span tiles) { // Pattern: Draws 2x2 tiles downward (object 0x60) // Size byte determines how many times to repeat (1-15 or 32) int size = obj.size_; if (size == 0) size = 32; // Special case for object 0x60 for (int s = 0; s < size; s++) { - if (tiles.size() >= 1) { - // Draw 2x2 pattern using 8x8 tiles from the first Tile16 - const auto& tile16 = tiles[0]; - WriteTile8(bg, obj.x_, obj.y_ + (s * 2), tile16.tile0_); // Top-left - WriteTile8(bg, obj.x_ + 1, obj.y_ + (s * 2), tile16.tile1_); // Top-right - WriteTile8(bg, obj.x_, obj.y_ + (s * 2) + 1, tile16.tile2_); // Bottom-left - WriteTile8(bg, obj.x_ + 1, obj.y_ + (s * 2) + 1, tile16.tile3_); // Bottom-right + if (tiles.size() >= 4) { + // Draw 2x2 pattern using 8x8 tiles from the span + WriteTile8(bg, obj.x_, obj.y_ + (s * 2), tiles[0]); // Top-left + WriteTile8(bg, obj.x_ + 1, obj.y_ + (s * 2), tiles[1]); // Top-right + WriteTile8(bg, obj.x_, obj.y_ + (s * 2) + 1, tiles[2]); // Bottom-left + WriteTile8(bg, obj.x_ + 1, obj.y_ + (s * 2) + 1, tiles[3]); // Bottom-right } } } void ObjectDrawer::DrawDownwards4x2_1to15or26(const RoomObject& obj, gfx::BackgroundBuffer& bg, - const std::vector& tiles) { + std::span tiles) { // Pattern: Draws 4x2 tiles downward (objects 0x61-0x62) int size = obj.size_; if (size == 0) size = 26; // Special case @@ -654,136 +603,126 @@ void ObjectDrawer::DrawDownwards4x2_1to15or26(const RoomObject& obj, gfx::Backgr obj.id_, tiles.size(), size); for (int s = 0; s < size; s++) { - if (tiles.size() >= 2) { - // Draw 4x2 pattern using 8 tiles from 2 Tile16s (like ZScream) - const auto& tile16_0 = tiles[0]; - const auto& tile16_1 = tiles[1]; - + if (tiles.size() >= 8) { + // Draw 4x2 pattern using 8 tiles from span // Top row: tiles 0,1,2,3 - WriteTile8(bg, obj.x_, obj.y_ + (s * 2), tile16_0.tile0_); // Top-left - WriteTile8(bg, obj.x_ + 1, obj.y_ + (s * 2), tile16_0.tile1_); // Top-right - WriteTile8(bg, obj.x_ + 2, obj.y_ + (s * 2), tile16_0.tile2_); // Top-middle-left - WriteTile8(bg, obj.x_ + 3, obj.y_ + (s * 2), tile16_0.tile3_); // Top-middle-right + WriteTile8(bg, obj.x_, obj.y_ + (s * 2), tiles[0]); // Top-left + WriteTile8(bg, obj.x_ + 1, obj.y_ + (s * 2), tiles[1]); // Top-right + WriteTile8(bg, obj.x_ + 2, obj.y_ + (s * 2), tiles[2]); // Top-middle-left + WriteTile8(bg, obj.x_ + 3, obj.y_ + (s * 2), tiles[3]); // Top-middle-right // Bottom row: tiles 4,5,6,7 - WriteTile8(bg, obj.x_, obj.y_ + (s * 2) + 1, tile16_1.tile0_); // Bottom-left - WriteTile8(bg, obj.x_ + 1, obj.y_ + (s * 2) + 1, tile16_1.tile1_); // Bottom-right - WriteTile8(bg, obj.x_ + 2, obj.y_ + (s * 2) + 1, tile16_1.tile2_); // Bottom-middle-left - WriteTile8(bg, obj.x_ + 3, obj.y_ + (s * 2) + 1, tile16_1.tile3_); // Bottom-middle-right - } else if (tiles.size() >= 1) { - // Fallback: use only first Tile16 - const auto& tile16 = tiles[0]; - WriteTile8(bg, obj.x_, obj.y_ + (s * 2), tile16.tile0_); // Top-left - WriteTile8(bg, obj.x_ + 1, obj.y_ + (s * 2), tile16.tile1_); // Top-right - WriteTile8(bg, obj.x_ + 2, obj.y_ + (s * 2), tile16.tile0_); // Top-left (repeat) - WriteTile8(bg, obj.x_ + 3, obj.y_ + (s * 2), tile16.tile1_); // Top-right (repeat) - WriteTile8(bg, obj.x_, obj.y_ + (s * 2) + 1, tile16.tile2_); // Bottom-left - WriteTile8(bg, obj.x_ + 1, obj.y_ + (s * 2) + 1, tile16.tile3_); // Bottom-right - WriteTile8(bg, obj.x_ + 2, obj.y_ + (s * 2) + 1, tile16.tile2_); // Bottom-left (repeat) - WriteTile8(bg, obj.x_ + 3, obj.y_ + (s * 2) + 1, tile16.tile3_); // Bottom-right (repeat) + WriteTile8(bg, obj.x_, obj.y_ + (s * 2) + 1, tiles[4]); // Bottom-left + WriteTile8(bg, obj.x_ + 1, obj.y_ + (s * 2) + 1, tiles[5]); // Bottom-right + WriteTile8(bg, obj.x_ + 2, obj.y_ + (s * 2) + 1, tiles[6]); // Bottom-middle-left + WriteTile8(bg, obj.x_ + 3, obj.y_ + (s * 2) + 1, tiles[7]); // Bottom-middle-right + } else if (tiles.size() >= 4) { + // Fallback: use only first 4 tiles and repeat + WriteTile8(bg, obj.x_, obj.y_ + (s * 2), tiles[0]); + WriteTile8(bg, obj.x_ + 1, obj.y_ + (s * 2), tiles[1]); + WriteTile8(bg, obj.x_ + 2, obj.y_ + (s * 2), tiles[0]); + WriteTile8(bg, obj.x_ + 3, obj.y_ + (s * 2), tiles[1]); + WriteTile8(bg, obj.x_, obj.y_ + (s * 2) + 1, tiles[2]); + WriteTile8(bg, obj.x_ + 1, obj.y_ + (s * 2) + 1, tiles[3]); + WriteTile8(bg, obj.x_ + 2, obj.y_ + (s * 2) + 1, tiles[2]); + WriteTile8(bg, obj.x_ + 3, obj.y_ + (s * 2) + 1, tiles[3]); } } } void ObjectDrawer::DrawDownwards4x2_1to16_BothBG(const RoomObject& obj, gfx::BackgroundBuffer& bg, - const std::vector& tiles) { + std::span tiles) { // Pattern: Same as above but draws to both BG1 and BG2 (objects 0x63-0x64) DrawDownwards4x2_1to15or26(obj, bg, tiles); // Note: BothBG would require access to both buffers - simplified for now } void ObjectDrawer::DrawDownwardsDecor4x2spaced4_1to16(const RoomObject& obj, gfx::BackgroundBuffer& bg, - const std::vector& tiles) { + std::span tiles) { // Pattern: Draws 4x2 decoration downward with spacing (objects 0x65-0x66) int size = obj.size_ & 0x0F; for (int s = 0; s < size; s++) { - if (tiles.size() >= 1) { - // Draw 4x2 pattern with spacing using 8x8 tiles from first Tile16 - const auto& tile16 = tiles[0]; - WriteTile8(bg, obj.x_, obj.y_ + (s * 6), tile16.tile0_); // Top-left - WriteTile8(bg, obj.x_ + 1, obj.y_ + (s * 6), tile16.tile1_); // Top-right - WriteTile8(bg, obj.x_ + 2, obj.y_ + (s * 6), tile16.tile0_); // Top-left (repeat) - WriteTile8(bg, obj.x_ + 3, obj.y_ + (s * 6), tile16.tile1_); // Top-right (repeat) - WriteTile8(bg, obj.x_, obj.y_ + (s * 6) + 1, tile16.tile2_); // Bottom-left - WriteTile8(bg, obj.x_ + 1, obj.y_ + (s * 6) + 1, tile16.tile3_); // Bottom-right - WriteTile8(bg, obj.x_ + 2, obj.y_ + (s * 6) + 1, tile16.tile2_); // Bottom-left (repeat) - WriteTile8(bg, obj.x_ + 3, obj.y_ + (s * 6) + 1, tile16.tile3_); // Bottom-right (repeat) + if (tiles.size() >= 8) { + // Draw 4x2 pattern with spacing using 8x8 tiles from span + WriteTile8(bg, obj.x_, obj.y_ + (s * 6), tiles[0]); + WriteTile8(bg, obj.x_ + 1, obj.y_ + (s * 6), tiles[1]); + WriteTile8(bg, obj.x_ + 2, obj.y_ + (s * 6), tiles[2]); + WriteTile8(bg, obj.x_ + 3, obj.y_ + (s * 6), tiles[3]); + WriteTile8(bg, obj.x_, obj.y_ + (s * 6) + 1, tiles[4]); + WriteTile8(bg, obj.x_ + 1, obj.y_ + (s * 6) + 1, tiles[5]); + WriteTile8(bg, obj.x_ + 2, obj.y_ + (s * 6) + 1, tiles[6]); + WriteTile8(bg, obj.x_ + 3, obj.y_ + (s * 6) + 1, tiles[7]); } } } void ObjectDrawer::DrawDownwards2x2_1to16(const RoomObject& obj, gfx::BackgroundBuffer& bg, - const std::vector& tiles) { + std::span tiles) { // Pattern: Draws 2x2 tiles downward (objects 0x67-0x68) int size = obj.size_ & 0x0F; for (int s = 0; s < size; s++) { - if (tiles.size() >= 1) { - // Draw 2x2 pattern using 8x8 tiles from first Tile16 - const auto& tile16 = tiles[0]; - WriteTile8(bg, obj.x_, obj.y_ + (s * 2), tile16.tile0_); // Top-left - WriteTile8(bg, obj.x_ + 1, obj.y_ + (s * 2), tile16.tile1_); // Top-right - WriteTile8(bg, obj.x_, obj.y_ + (s * 2) + 1, tile16.tile2_); // Bottom-left - WriteTile8(bg, obj.x_ + 1, obj.y_ + (s * 2) + 1, tile16.tile3_); // Bottom-right + if (tiles.size() >= 4) { + // Draw 2x2 pattern using 8x8 tiles from span + WriteTile8(bg, obj.x_, obj.y_ + (s * 2), tiles[0]); + WriteTile8(bg, obj.x_ + 1, obj.y_ + (s * 2), tiles[1]); + WriteTile8(bg, obj.x_, obj.y_ + (s * 2) + 1, tiles[2]); + WriteTile8(bg, obj.x_ + 1, obj.y_ + (s * 2) + 1, tiles[3]); } } } void ObjectDrawer::DrawDownwardsHasEdge1x1_1to16_plus3(const RoomObject& obj, gfx::BackgroundBuffer& bg, - const std::vector& tiles) { + std::span tiles) { // Pattern: 1x1 tiles with edge detection +3 offset downward (object 0x69) int size = obj.size_ & 0x0F; for (int s = 0; s < size; s++) { if (tiles.size() >= 1) { - // Use first 8x8 tile from first tile16 - const auto& tile16 = tiles[0]; - WriteTile8(bg, obj.x_ + 3, obj.y_ + s, tile16.tile0_); + // Use first 8x8 tile from span + WriteTile8(bg, obj.x_ + 3, obj.y_ + s, tiles[0]); } } } void ObjectDrawer::DrawDownwardsEdge1x1_1to16(const RoomObject& obj, gfx::BackgroundBuffer& bg, - const std::vector& tiles) { + std::span tiles) { // Pattern: 1x1 edge tiles downward (objects 0x6A-0x6B) int size = obj.size_ & 0x0F; for (int s = 0; s < size; s++) { if (tiles.size() >= 1) { - // Use first 8x8 tile from first tile16 - const auto& tile16 = tiles[0]; - WriteTile8(bg, obj.x_, obj.y_ + s, tile16.tile0_); + // Use first 8x8 tile from span + WriteTile8(bg, obj.x_, obj.y_ + s, tiles[0]); } } } void ObjectDrawer::DrawDownwardsLeftCorners2x1_1to16_plus12(const RoomObject& obj, gfx::BackgroundBuffer& bg, - const std::vector& tiles) { + std::span tiles) { // Pattern: Left corner 2x1 tiles with +12 offset downward (object 0x6C) int size = obj.size_ & 0x0F; for (int s = 0; s < size; s++) { - if (tiles.size() >= 1) { - // Use first tile16 for 2x1 pattern - const auto& tile16 = tiles[0]; - WriteTile8(bg, obj.x_ + 12, obj.y_ + s, tile16.tile0_); - WriteTile8(bg, obj.x_ + 12 + 1, obj.y_ + s, tile16.tile1_); + if (tiles.size() >= 2) { + // Use first tile span for 2x1 pattern + WriteTile8(bg, obj.x_ + 12, obj.y_ + s, tiles[0]); + WriteTile8(bg, obj.x_ + 12 + 1, obj.y_ + s, tiles[1]); } } } void ObjectDrawer::DrawDownwardsRightCorners2x1_1to16_plus12(const RoomObject& obj, gfx::BackgroundBuffer& bg, - const std::vector& tiles) { + std::span tiles) { // Pattern: Right corner 2x1 tiles with +12 offset downward (object 0x6D) int size = obj.size_ & 0x0F; for (int s = 0; s < size; s++) { - if (tiles.size() >= 1) { - // Use first tile16 for 2x1 pattern - const auto& tile16 = tiles[0]; - WriteTile8(bg, obj.x_ + 12, obj.y_ + s, tile16.tile0_); - WriteTile8(bg, obj.x_ + 12 + 1, obj.y_ + s, tile16.tile1_); + if (tiles.size() >= 2) { + // Use first tile span for 2x1 pattern + WriteTile8(bg, obj.x_ + 12, obj.y_ + s, tiles[0]); + WriteTile8(bg, obj.x_ + 12 + 1, obj.y_ + s, tiles[1]); } } } @@ -818,79 +757,77 @@ bool ObjectDrawer::IsValidTilePosition(int tile_x, int tile_y) const { tile_y >= 0 && tile_y < kMaxTilesY; } -void ObjectDrawer::DrawTileToBitmap(gfx::Bitmap& bitmap, const gfx::TileInfo& tile_info, +void ObjectDrawer::DrawTileToBitmap(gfx::Bitmap& bitmap, const gfx::TileInfo& tile_info, int pixel_x, int pixel_y, const uint8_t* tiledata) { // Draw an 8x8 tile directly to bitmap at pixel coordinates if (!tiledata) return; - + // DEBUG: Check if bitmap is valid if (!bitmap.is_active() || bitmap.width() == 0 || bitmap.height() == 0) { - LOG_DEBUG("ObjectDrawer", "ERROR: Invalid bitmap - active=%d, size=%dx%d", + LOG_DEBUG("ObjectDrawer", "ERROR: Invalid bitmap - active=%d, size=%dx%d", bitmap.is_active(), bitmap.width(), bitmap.height()); return; } - - // CRITICAL FIX: current_gfx16_ is organized as 16 sheets of 128x128 pixels - // Each sheet is 0x800 bytes (2048 bytes = 128*128/8 = 16384 pixels) - // Total buffer size: 16 * 0x800 = 0x8000 bytes (32768 bytes) - // Tile IDs are 0-511, distributed across sheets: - // Sheet 0: tiles 0-127 - // Sheet 1: tiles 128-255 - // ... - // Sheet 15: tiles 480-511 (but only uses first 32 tiles) - // - // Within each sheet, tiles are arranged in a 16x8 grid (128 tiles total): - // Row 0: tiles 0-15 - // Row 1: tiles 16-31 - // ... - // Row 7: tiles 112-127 - - int sheet_index = tile_info.id_ / 128; // Which 128-tile sheet (0-15)? - int tile_in_sheet = tile_info.id_ % 128; // Which tile within that sheet (0-127)? - int tile_x_in_sheet = (tile_in_sheet % 16) * 8; // 16 tiles per row, 8 pixels each - int tile_y_in_sheet = (tile_in_sheet / 16) * 8; // 8 rows, 8 pixels each - + + // Calculate tile position in graphics sheet (128 pixels wide, 16 tiles per row) + int tile_sheet_x = (tile_info.id_ % 16) * 8; // 16 tiles per row + int tile_sheet_y = (tile_info.id_ / 16) * 8; // Each row is 16 tiles + // Palettes are 3bpp (8 colors). Convert palette index to base color offset. uint8_t palette_offset = (tile_info.palette_ & 0x0F) * 8; - + + // DEBUG: Log tile info for first few tiles + static int debug_tile_count = 0; + if (debug_tile_count < 5) { + printf("[ObjectDrawer] DrawTile: id=0x%03X pos=(%d,%d) sheet=(%d,%d) pal=%d mirror=(h:%d,v:%d)\n", + tile_info.id_, pixel_x, pixel_y, tile_sheet_x, tile_sheet_y, + tile_info.palette_, tile_info.horizontal_mirror_, tile_info.vertical_mirror_); + debug_tile_count++; + } + // Draw 8x8 pixels + int pixels_written = 0; + int pixels_transparent = 0; for (int py = 0; py < 8; py++) { for (int px = 0; px < 8; px++) { // Apply mirroring int src_x = tile_info.horizontal_mirror_ ? (7 - px) : px; int src_y = tile_info.vertical_mirror_ ? (7 - py) : py; - - // Calculate source pixel index in current_gfx16_ buffer - // Buffer layout: [Sheet 0: 0x800 bytes][Sheet 1: 0x800 bytes]...[Sheet 15: 0x800 bytes] - // Within each sheet: row-major order, 128 pixels per row - int src_index = (sheet_index * 0x800) + // Sheet offset (2048 bytes per sheet) - (tile_y_in_sheet + src_y) * 128 + // Row within sheet (128 pixels/row) - (tile_x_in_sheet + src_x); // Column within row - - // Bounds check for graphics buffer - if (src_index < 0 || src_index >= 0x4000) { - continue; // Out of bounds, skip pixel - } - + + // Read pixel from graphics sheet + int src_index = (tile_sheet_y + src_y) * 128 + (tile_sheet_x + src_x); uint8_t pixel_index = tiledata[src_index]; if (pixel_index == 0) { - continue; // Transparent pixel + pixels_transparent++; + continue; } - uint8_t final_color = pixel_index + palette_offset; int dest_x = pixel_x + px; int dest_y = pixel_y + py; - + if (dest_x >= 0 && dest_x < bitmap.width() && dest_y >= 0 && dest_y < bitmap.height()) { int dest_index = dest_y * bitmap.width() + dest_x; if (dest_index >= 0 && dest_index < static_cast(bitmap.mutable_data().size())) { bitmap.mutable_data()[dest_index] = final_color; + pixels_written++; } } } } + + // Mark bitmap as modified if we wrote any pixels + if (pixels_written > 0) { + bitmap.set_modified(true); + } + + // DEBUG: Log pixel writing stats for first few tiles + if (debug_tile_count < 5) { + printf("[ObjectDrawer] Tile 0x%03X: wrote %d pixels, %d transparent, src_index_range=[%d,%d]\n", + tile_info.id_, pixels_written, pixels_transparent, + (tile_sheet_y * 128 + tile_sheet_x), + (tile_sheet_y + 7) * 128 + (tile_sheet_x + 7)); + } } } // namespace zelda3 -} // namespace yaze - +} // namespace yaze \ No newline at end of file diff --git a/src/app/zelda3/dungeon/object_drawer.h b/src/app/zelda3/dungeon/object_drawer.h index 1cd49002..c683b775 100644 --- a/src/app/zelda3/dungeon/object_drawer.h +++ b/src/app/zelda3/dungeon/object_drawer.h @@ -86,77 +86,77 @@ class ObjectDrawer { private: // Draw routine function type using DrawRoutine = std::function&)>; + std::span)>; // Core draw routines (based on ZScream's subtype1_routines table) void DrawRightwards2x2_1to15or32(const RoomObject& obj, gfx::BackgroundBuffer& bg, - const std::vector& tiles); + std::span tiles); void DrawRightwards2x4_1to15or26(const RoomObject& obj, gfx::BackgroundBuffer& bg, - const std::vector& tiles); + std::span tiles); void DrawRightwards2x4spaced4_1to16(const RoomObject& obj, gfx::BackgroundBuffer& bg, - const std::vector& tiles); + std::span tiles); void DrawRightwards2x4spaced4_1to16_BothBG(const RoomObject& obj, gfx::BackgroundBuffer& bg, - const std::vector& tiles); + std::span tiles); void DrawRightwards2x2_1to16(const RoomObject& obj, gfx::BackgroundBuffer& bg, - const std::vector& tiles); + std::span tiles); void DrawDiagonalAcute_1to16(const RoomObject& obj, gfx::BackgroundBuffer& bg, - const std::vector& tiles); + std::span tiles); void DrawDiagonalGrave_1to16(const RoomObject& obj, gfx::BackgroundBuffer& bg, - const std::vector& tiles); + std::span tiles); void DrawDiagonalAcute_1to16_BothBG(const RoomObject& obj, gfx::BackgroundBuffer& bg, - const std::vector& tiles); + std::span tiles); void DrawDiagonalGrave_1to16_BothBG(const RoomObject& obj, gfx::BackgroundBuffer& bg, - const std::vector& tiles); + std::span tiles); void DrawRightwards1x2_1to16_plus2(const RoomObject& obj, gfx::BackgroundBuffer& bg, - const std::vector& tiles); + std::span tiles); void DrawRightwardsHasEdge1x1_1to16_plus3(const RoomObject& obj, gfx::BackgroundBuffer& bg, - const std::vector& tiles); + std::span tiles); void DrawRightwardsHasEdge1x1_1to16_plus2(const RoomObject& obj, gfx::BackgroundBuffer& bg, - const std::vector& tiles); + std::span tiles); void DrawRightwardsTopCorners1x2_1to16_plus13(const RoomObject& obj, gfx::BackgroundBuffer& bg, - const std::vector& tiles); + std::span tiles); void DrawRightwardsBottomCorners1x2_1to16_plus13(const RoomObject& obj, gfx::BackgroundBuffer& bg, - const std::vector& tiles); + std::span tiles); void CustomDraw(const RoomObject& obj, gfx::BackgroundBuffer& bg, - const std::vector& tiles); + std::span tiles); void DrawRightwards4x4_1to16(const RoomObject& obj, gfx::BackgroundBuffer& bg, - const std::vector& tiles); + std::span tiles); void DrawRightwards1x1Solid_1to16_plus3(const RoomObject& obj, gfx::BackgroundBuffer& bg, - const std::vector& tiles); + std::span tiles); void DrawDoorSwitcherer(const RoomObject& obj, gfx::BackgroundBuffer& bg, - const std::vector& tiles); + std::span tiles); void DrawRightwardsDecor4x4spaced2_1to16(const RoomObject& obj, gfx::BackgroundBuffer& bg, - const std::vector& tiles); + std::span tiles); void DrawRightwardsStatue2x3spaced2_1to16(const RoomObject& obj, gfx::BackgroundBuffer& bg, - const std::vector& tiles); + std::span tiles); void DrawRightwardsPillar2x4spaced4_1to16(const RoomObject& obj, gfx::BackgroundBuffer& bg, - const std::vector& tiles); + std::span tiles); void DrawRightwardsDecor4x3spaced4_1to16(const RoomObject& obj, gfx::BackgroundBuffer& bg, - const std::vector& tiles); + std::span tiles); void DrawRightwardsDoubled2x2spaced2_1to16(const RoomObject& obj, gfx::BackgroundBuffer& bg, - const std::vector& tiles); + std::span tiles); void DrawRightwardsDecor2x2spaced12_1to16(const RoomObject& obj, gfx::BackgroundBuffer& bg, - const std::vector& tiles); + std::span tiles); // Downwards draw routines (missing implementation) void DrawDownwards2x2_1to15or32(const RoomObject& obj, gfx::BackgroundBuffer& bg, - const std::vector& tiles); + std::span tiles); void DrawDownwards4x2_1to15or26(const RoomObject& obj, gfx::BackgroundBuffer& bg, - const std::vector& tiles); + std::span tiles); void DrawDownwards4x2_1to16_BothBG(const RoomObject& obj, gfx::BackgroundBuffer& bg, - const std::vector& tiles); + std::span tiles); void DrawDownwardsDecor4x2spaced4_1to16(const RoomObject& obj, gfx::BackgroundBuffer& bg, - const std::vector& tiles); + std::span tiles); void DrawDownwards2x2_1to16(const RoomObject& obj, gfx::BackgroundBuffer& bg, - const std::vector& tiles); + std::span tiles); void DrawDownwardsHasEdge1x1_1to16_plus3(const RoomObject& obj, gfx::BackgroundBuffer& bg, - const std::vector& tiles); + std::span tiles); void DrawDownwardsEdge1x1_1to16(const RoomObject& obj, gfx::BackgroundBuffer& bg, - const std::vector& tiles); + std::span tiles); void DrawDownwardsLeftCorners2x1_1to16_plus12(const RoomObject& obj, gfx::BackgroundBuffer& bg, - const std::vector& tiles); + std::span tiles); void DrawDownwardsRightCorners2x1_1to16_plus12(const RoomObject& obj, gfx::BackgroundBuffer& bg, - const std::vector& tiles); + std::span tiles); // Utility methods void WriteTile8(gfx::BackgroundBuffer& bg, int tile_x, int tile_y, diff --git a/src/app/zelda3/dungeon/object_parser.cc b/src/app/zelda3/dungeon/object_parser.cc index b6b29d3b..10203714 100644 --- a/src/app/zelda3/dungeon/object_parser.cc +++ b/src/app/zelda3/dungeon/object_parser.cc @@ -5,6 +5,7 @@ #include "absl/strings/str_format.h" #include "app/zelda3/dungeon/room_object.h" +#include "util/log.h" // ROM addresses for object data (PC addresses, not SNES) static constexpr int kRoomObjectSubtype1 = 0x0A8000; @@ -15,7 +16,7 @@ static constexpr int kRoomObjectTileAddress = 0x0AB000; namespace yaze { namespace zelda3 { -absl::StatusOr> ObjectParser::ParseObject(int16_t object_id) { +absl::StatusOr> ObjectParser::ParseObject(int16_t object_id) { if (rom_ == nullptr) { return absl::InvalidArgumentError("ROM is null"); } @@ -116,7 +117,7 @@ absl::StatusOr ObjectParser::ParseObjectSize(int16_t object_id, return info; } -absl::StatusOr> ObjectParser::ParseSubtype1(int16_t object_id) { +absl::StatusOr> ObjectParser::ParseSubtype1(int16_t object_id) { int index = object_id & 0xFF; int tile_ptr = kRoomObjectSubtype1 + (index * 2); @@ -134,7 +135,7 @@ absl::StatusOr> ObjectParser::ParseSubtype1(int16_t obj return ReadTileData(tile_data_ptr, 8); } -absl::StatusOr> ObjectParser::ParseSubtype2(int16_t object_id) { +absl::StatusOr> ObjectParser::ParseSubtype2(int16_t object_id) { int index = object_id & 0x7F; int tile_ptr = kRoomObjectSubtype2 + (index * 2); @@ -152,7 +153,7 @@ absl::StatusOr> ObjectParser::ParseSubtype2(int16_t obj return ReadTileData(tile_data_ptr, 8); } -absl::StatusOr> ObjectParser::ParseSubtype3(int16_t object_id) { +absl::StatusOr> ObjectParser::ParseSubtype3(int16_t object_id) { int index = object_id & 0xFF; int tile_ptr = kRoomObjectSubtype3 + (index * 2); @@ -170,30 +171,42 @@ absl::StatusOr> ObjectParser::ParseSubtype3(int16_t obj return ReadTileData(tile_data_ptr, 8); } -absl::StatusOr> ObjectParser::ReadTileData(int address, int tile_count) { - if (address < 0 || address + (tile_count * 8) >= (int)rom_->size()) { +absl::StatusOr> ObjectParser::ReadTileData(int address, int tile_count) { + // Each tile is stored as a 16-bit word (2 bytes), not 8 bytes! + // ZScream: tiles.Add(new Tile(ROM.DATA[pos + ((i * 2))], ROM.DATA[pos + ((i * 2)) + 1])); + if (address < 0 || address + (tile_count * 2) >= (int)rom_->size()) { return absl::OutOfRangeError( absl::StrFormat("Tile data address out of range: %#06x", address)); } - std::vector tiles; + std::vector tiles; tiles.reserve(tile_count); + // DEBUG: Log first tile read + static int debug_read_count = 0; + bool should_log = (debug_read_count < 3); + for (int i = 0; i < tile_count; i++) { - int tile_offset = address + (i * 8); + int tile_offset = address + (i * 2); // 2 bytes per tile word - // Read 4 words (8 bytes) per tile - uint16_t w0 = rom_->data()[tile_offset] | (rom_->data()[tile_offset + 1] << 8); - uint16_t w1 = rom_->data()[tile_offset + 2] | (rom_->data()[tile_offset + 3] << 8); - uint16_t w2 = rom_->data()[tile_offset + 4] | (rom_->data()[tile_offset + 5] << 8); - uint16_t w3 = rom_->data()[tile_offset + 6] | (rom_->data()[tile_offset + 7] << 8); + // Read 1 word (2 bytes) per tile - this is the SNES tile format + uint16_t tile_word = rom_->data()[tile_offset] | (rom_->data()[tile_offset + 1] << 8); - tiles.emplace_back( - gfx::WordToTileInfo(w0), - gfx::WordToTileInfo(w1), - gfx::WordToTileInfo(w2), - gfx::WordToTileInfo(w3) - ); + auto tile_info = gfx::WordToTileInfo(tile_word); + tiles.push_back(tile_info); + + // DEBUG: Log first few tiles + if (should_log && i < 4) { + printf("[ObjectParser] ReadTile[%d]: addr=0x%06X word=0x%04X → id=0x%03X pal=%d mirror=(h:%d,v:%d)\n", + i, tile_offset, tile_word, tile_info.id_, tile_info.palette_, + tile_info.horizontal_mirror_, tile_info.vertical_mirror_); + } + } + + if (should_log) { + printf("[ObjectParser] ReadTileData: addr=0x%06X count=%d → loaded %zu tiles\n", + address, tile_count, tiles.size()); + debug_read_count++; } return tiles; diff --git a/src/app/zelda3/dungeon/object_parser.h b/src/app/zelda3/dungeon/object_parser.h index 33be5555..7259f1ec 100644 --- a/src/app/zelda3/dungeon/object_parser.h +++ b/src/app/zelda3/dungeon/object_parser.h @@ -97,7 +97,7 @@ class ObjectParser { * @param object_id The object ID to parse * @return StatusOr containing the parsed tile data */ - absl::StatusOr> ParseObject(int16_t object_id); + absl::StatusOr> ParseObject(int16_t object_id); /** * @brief Parse object routine data @@ -141,17 +141,17 @@ class ObjectParser { /** * @brief Parse subtype 1 objects (0x00-0xFF) */ - absl::StatusOr> ParseSubtype1(int16_t object_id); + absl::StatusOr> ParseSubtype1(int16_t object_id); /** * @brief Parse subtype 2 objects (0x100-0x1FF) */ - absl::StatusOr> ParseSubtype2(int16_t object_id); + absl::StatusOr> ParseSubtype2(int16_t object_id); /** * @brief Parse subtype 3 objects (0x200+) */ - absl::StatusOr> ParseSubtype3(int16_t object_id); + absl::StatusOr> ParseSubtype3(int16_t object_id); /** * @brief Read tile data from ROM @@ -160,7 +160,7 @@ class ObjectParser { * @param tile_count Number of tiles to read * @return StatusOr containing tile data */ - absl::StatusOr> ReadTileData(int address, + absl::StatusOr> ReadTileData(int address, int tile_count); Rom* rom_; diff --git a/src/app/zelda3/dungeon/room.cc b/src/app/zelda3/dungeon/room.cc index 9e6dcbf5..162d9151 100644 --- a/src/app/zelda3/dungeon/room.cc +++ b/src/app/zelda3/dungeon/room.cc @@ -227,16 +227,21 @@ constexpr int kGfxBufferRoomSpriteLastLineOffset = 0x88; void Room::CopyRoomGraphicsToBuffer() { if (!rom_ || !rom_->is_loaded()) { + printf("[CopyRoomGraphicsToBuffer] ROM not loaded\n"); return; } auto gfx_buffer_data = rom()->mutable_graphics_buffer(); if (!gfx_buffer_data || gfx_buffer_data->empty()) { + printf("[CopyRoomGraphicsToBuffer] Graphics buffer is null or empty\n"); return; } + printf("[CopyRoomGraphicsToBuffer] Room %d: Copying graphics from blocks\n", room_id_); + // Copy room graphics to buffer int sheet_pos = 0; + int bytes_copied = 0; for (int i = 0; i < 16; i++) { // Validate block index if (blocks_[i] < 0 || blocks_[i] > 255) { @@ -268,6 +273,7 @@ void Room::CopyRoomGraphicsToBuffer() { int gfx_index = data + sheet_pos; if (gfx_index >= 0 && gfx_index < static_cast(sizeof(current_gfx16_))) { current_gfx16_[gfx_index] = map_byte; + if (map_byte != 0) bytes_copied++; } } data++; @@ -276,33 +282,95 @@ void Room::CopyRoomGraphicsToBuffer() { sheet_pos += kGfxBufferRoomOffset; } + printf("[CopyRoomGraphicsToBuffer] Room %d: Copied %d non-zero bytes to current_gfx16_\n", room_id_, bytes_copied); LoadAnimatedGraphics(); } void Room::RenderRoomGraphics() { - CopyRoomGraphicsToBuffer(); - LoadLayoutTilesToBuffer(); - + // PERFORMANCE OPTIMIZATION: Check if room properties have changed + bool properties_changed = false; + + // Check if graphics properties changed + if (cached_blockset_ != blockset || cached_spriteset_ != spriteset || + cached_palette_ != palette || cached_layout_ != layout || + cached_floor1_graphics_ != floor1_graphics_ || cached_floor2_graphics_ != floor2_graphics_) { + cached_blockset_ = blockset; + cached_spriteset_ = spriteset; + cached_palette_ = palette; + cached_layout_ = layout; + cached_floor1_graphics_ = floor1_graphics_; + cached_floor2_graphics_ = floor2_graphics_; + graphics_dirty_ = true; + properties_changed = true; + } + + // Check if effect/tags changed + if (cached_effect_ != static_cast(effect_) || + cached_tag1_ != tag1_ || cached_tag2_ != tag2_) { + cached_effect_ = static_cast(effect_); + cached_tag1_ = tag1_; + cached_tag2_ = tag2_; + objects_dirty_ = true; + properties_changed = true; + } + + // If nothing changed and textures exist, skip rendering + if (!properties_changed && !graphics_dirty_ && !objects_dirty_ && !layout_dirty_ && !textures_dirty_) { + auto& bg1_bmp = bg1_buffer_.bitmap(); + auto& bg2_bmp = bg2_buffer_.bitmap(); + if (bg1_bmp.texture() && bg2_bmp.texture()) { + LOG_DEBUG("[RenderRoomGraphics]", "Room %d: No changes detected, skipping render", room_id_); + return; + } + } + + LOG_DEBUG("[RenderRoomGraphics]", "Room %d: Rendering graphics (dirty_flags: g=%d o=%d l=%d t=%d)", + room_id_, graphics_dirty_, objects_dirty_, layout_dirty_, textures_dirty_); + + // STEP 0: Load graphics if needed + if (graphics_dirty_) { + CopyRoomGraphicsToBuffer(); + graphics_dirty_ = false; + } + + // STEP 1: Load layout tiles if needed + if (layout_dirty_) { + LoadLayoutTilesToBuffer(); + layout_dirty_ = false; + } + // Debug: Log floor graphics values - LOG_DEBUG("[RenderRoomGraphics]", "Room %d: floor1=%d, floor2=%d, blocks_size=%zu", + LOG_DEBUG("[RenderRoomGraphics]", "Room %d: floor1=%d, floor2=%d, blocks_size=%zu", room_id_, floor1_graphics_, floor2_graphics_, blocks_.size()); - + // LoadGraphicsSheetsIntoArena() removed - using per-room graphics instead // Arena sheets are optional and not needed for room rendering - // STEP 1: Draw floor tiles to bitmaps (base layer) - bg1_buffer_.DrawFloor(rom()->vector(), tile_address, - tile_address_floor, floor1_graphics_); - bg2_buffer_.DrawFloor(rom()->vector(), tile_address, - tile_address_floor, floor2_graphics_); - - // STEP 2: Draw background tiles (walls/structure) to buffers - bg1_buffer_.DrawBackground(std::span(current_gfx16_)); - bg2_buffer_.DrawBackground(std::span(current_gfx16_)); - + // STEP 2: Draw floor tiles to bitmaps (base layer) - if graphics changed OR bitmaps not created yet + bool need_floor_draw = graphics_dirty_; auto& bg1_bmp = bg1_buffer_.bitmap(); auto& bg2_bmp = bg2_buffer_.bitmap(); - + + // Always draw floor if bitmaps don't exist yet (first time rendering) + if (!bg1_bmp.is_active() || bg1_bmp.width() == 0 || !bg2_bmp.is_active() || bg2_bmp.width() == 0) { + need_floor_draw = true; + LOG_DEBUG("[RenderRoomGraphics]", "Room %d: Bitmaps not created yet, forcing floor draw", room_id_); + } + + if (need_floor_draw) { + bg1_buffer_.DrawFloor(rom()->vector(), tile_address, + tile_address_floor, floor1_graphics_); + bg2_buffer_.DrawFloor(rom()->vector(), tile_address, + tile_address_floor, floor2_graphics_); + } + + // STEP 3: Draw background tiles (walls/structure) to buffers - if graphics changed OR bitmaps just created + bool need_bg_draw = graphics_dirty_ || need_floor_draw; + if (need_bg_draw) { + bg1_buffer_.DrawBackground(std::span(current_gfx16_)); + bg2_buffer_.DrawBackground(std::span(current_gfx16_)); + } + // Get and apply palette BEFORE rendering objects (so objects use correct colors) auto& dungeon_pal_group = rom()->mutable_palette_group()->dungeon_main; int num_palettes = dungeon_pal_group.size(); @@ -354,6 +422,9 @@ void Room::RenderRoomGraphics() { gfx::Arena::TextureCommandType::CREATE, &bg2_bmp); } + // Mark textures as clean after successful queuing + textures_dirty_ = false; + // REMOVED: Don't process texture queue here - let it be batched! // Processing happens once per frame in DrawDungeonCanvas() // This dramatically improves performance when multiple rooms are open @@ -388,8 +459,8 @@ void Room::LoadLayoutTilesToBuffer() { tiles_skipped++; continue; } - const auto* tile16 = tile_result.value(); - uint16_t tile_word = gfx::TileInfoToWord(tile16->tile0_); + const auto* tile_info = tile_result.value(); + uint16_t tile_word = gfx::TileInfoToWord(*tile_info); if (layout_obj.GetLayerValue() == 1) { bg2_buffer_.SetTileAt(x, y, tile_word); @@ -404,12 +475,23 @@ void Room::LoadLayoutTilesToBuffer() { } void Room::RenderObjectsToBackground() { - LOG_DEBUG("[RenderObjectsToBackground]", "Starting object rendering"); - + LOG_DEBUG("[RenderObjectsToBackground]", "Starting object rendering for room %d", room_id_); + if (!rom_ || !rom_->is_loaded()) { LOG_DEBUG("[RenderObjectsToBackground]", "ROM not loaded, aborting"); return; } + + // PERFORMANCE OPTIMIZATION: Only render objects if they have changed or if graphics changed + // Also render if bitmaps were just created (need_floor_draw was true in RenderRoomGraphics) + auto& bg1_bmp = bg1_buffer_.bitmap(); + auto& bg2_bmp = bg2_buffer_.bitmap(); + bool bitmaps_exist = bg1_bmp.is_active() && bg1_bmp.width() > 0 && bg2_bmp.is_active() && bg2_bmp.width() > 0; + + if (!objects_dirty_ && !graphics_dirty_ && bitmaps_exist) { + LOG_DEBUG("[RenderObjectsToBackground]", "Room %d: Objects not dirty, skipping render", room_id_); + return; + } // Get palette group for object rendering (use SAME lookup as RenderRoomGraphics) auto& dungeon_pal_group = rom()->mutable_palette_group()->dungeon_main; @@ -446,10 +528,14 @@ void Room::RenderObjectsToBackground() { // Pass the room-specific graphics buffer (current_gfx16_) so objects use correct tiles ObjectDrawer drawer(rom_, current_gfx16_.data()); auto status = drawer.DrawObjectList(tile_objects_, bg1_buffer_, bg2_buffer_, palette_group); - + // Log only failures, not successes if (!status.ok()) { LOG_DEBUG("[RenderObjectsToBackground]", "ObjectDrawer failed: %s", std::string(status.message()).c_str()); + } else { + // Mark objects as clean after successful render + objects_dirty_ = false; + LOG_DEBUG("[RenderObjectsToBackground]", "Room %d: Objects rendered successfully", room_id_); } } diff --git a/src/app/zelda3/dungeon/room.h b/src/app/zelda3/dungeon/room.h index 0b5356b2..08fe0241 100644 --- a/src/app/zelda3/dungeon/room.h +++ b/src/app/zelda3/dungeon/room.h @@ -226,6 +226,7 @@ class Room { void ClearTileObjects() { tile_objects_.clear(); } void AddTileObject(const RoomObject& object) { tile_objects_.push_back(object); + MarkObjectsDirty(); } // Enhanced object manipulation (Phase 3) @@ -234,9 +235,15 @@ class Room { absl::Status UpdateObject(size_t index, const RoomObject& object); absl::StatusOr FindObjectAt(int x, int y, int layer) const; bool ValidateObject(const RoomObject& object) const; + + // Performance optimization: Mark objects as dirty when modified + void MarkObjectsDirty() { objects_dirty_ = true; textures_dirty_ = true; } + void MarkGraphicsDirty() { graphics_dirty_ = true; textures_dirty_ = true; } + void MarkLayoutDirty() { layout_dirty_ = true; textures_dirty_ = true; } void RemoveTileObject(size_t index) { if (index < tile_objects_.size()) { tile_objects_.erase(tile_objects_.begin() + index); + MarkObjectsDirty(); } } size_t GetTileObjectCount() const { return tile_objects_.size(); } @@ -248,18 +255,49 @@ class Room { // For undo/redo functionality void SetTileObjects(const std::vector& objects) { tile_objects_ = objects; + MarkObjectsDirty(); } // Public setters for LoadRoomFromRom function void SetBg2(background2 bg2) { bg2_ = bg2; } void SetCollision(CollisionKey collision) { collision_ = collision; } void SetIsLight(bool is_light) { is_light_ = is_light; } - void SetPalette(uint8_t palette) { this->palette = palette; } - void SetBlockset(uint8_t blockset) { this->blockset = blockset; } - void SetSpriteset(uint8_t spriteset) { this->spriteset = spriteset; } - void SetEffect(EffectKey effect) { effect_ = effect; } - void SetTag1(TagKey tag1) { tag1_ = tag1; } - void SetTag2(TagKey tag2) { tag2_ = tag2; } + void SetPalette(uint8_t palette) { + if (this->palette != palette) { + this->palette = palette; + MarkGraphicsDirty(); + } + } + void SetBlockset(uint8_t blockset) { + if (this->blockset != blockset) { + this->blockset = blockset; + MarkGraphicsDirty(); + } + } + void SetSpriteset(uint8_t spriteset) { + if (this->spriteset != spriteset) { + this->spriteset = spriteset; + MarkGraphicsDirty(); + } + } + void SetEffect(EffectKey effect) { + if (effect_ != effect) { + effect_ = effect; + MarkObjectsDirty(); + } + } + void SetTag1(TagKey tag1) { + if (tag1_ != tag1) { + tag1_ = tag1; + MarkObjectsDirty(); + } + } + void SetTag2(TagKey tag2) { + if (tag2_ != tag2) { + tag2_ = tag2; + MarkObjectsDirty(); + } + } void SetStaircasePlane(int index, uint8_t plane) { if (index >= 0 && index < 4) staircase_plane_[index] = plane; } @@ -320,13 +358,17 @@ class Room { // Floor graphics accessors (use these instead of direct members!) uint8_t floor1() const { return floor1_graphics_; } uint8_t floor2() const { return floor2_graphics_; } - void set_floor1(uint8_t value) { - floor1_graphics_ = value; - // TODO: Trigger re-render if needed + void set_floor1(uint8_t value) { + if (floor1_graphics_ != value) { + floor1_graphics_ = value; + MarkGraphicsDirty(); + } } - void set_floor2(uint8_t value) { - floor2_graphics_ = value; - // TODO: Trigger re-render if needed + void set_floor2(uint8_t value) { + if (floor2_graphics_ != value) { + floor2_graphics_ = value; + MarkGraphicsDirty(); + } } // Enhanced object parsing methods void ParseObjectsFromLocation(int objects_location); @@ -363,6 +405,23 @@ class Room { bool is_dark_; bool is_floor_ = true; + // Performance optimization: Cache room properties to avoid unnecessary re-renders + uint8_t cached_blockset_ = 0xFF; + uint8_t cached_spriteset_ = 0xFF; + uint8_t cached_palette_ = 0xFF; + uint8_t cached_layout_ = 0xFF; + uint8_t cached_floor1_graphics_ = 0xFF; + uint8_t cached_floor2_graphics_ = 0xFF; + uint8_t cached_effect_ = 0xFF; + TagKey cached_tag1_ = TagKey::Nothing; + TagKey cached_tag2_ = TagKey::Nothing; + + // Dirty flags for selective rendering + bool graphics_dirty_ = true; + bool objects_dirty_ = true; + bool layout_dirty_ = true; + bool textures_dirty_ = true; + int room_id_; int animated_frame_; diff --git a/src/app/zelda3/dungeon/room_layout.cc b/src/app/zelda3/dungeon/room_layout.cc index c5fcc1cf..25d9735b 100644 --- a/src/app/zelda3/dungeon/room_layout.cc +++ b/src/app/zelda3/dungeon/room_layout.cc @@ -2,6 +2,7 @@ #include "absl/strings/str_format.h" #include "app/snes.h" +#include "app/zelda3/dungeon/dungeon_rom_addresses.h" namespace yaze::zelda3 { diff --git a/src/app/zelda3/dungeon/room_object.cc b/src/app/zelda3/dungeon/room_object.cc index b472deee..a9f9acf3 100644 --- a/src/app/zelda3/dungeon/room_object.cc +++ b/src/app/zelda3/dungeon/room_object.cc @@ -96,8 +96,10 @@ void RoomObject::EnsureTilesLoaded() { uint16_t w3 = (uint16_t)(rom_data[pos + 6] | (rom_data[pos + 7] << 8)); tiles_.clear(); - tiles_.push_back(gfx::Tile16(gfx::WordToTileInfo(w0), gfx::WordToTileInfo(w1), - gfx::WordToTileInfo(w2), gfx::WordToTileInfo(w3))); + tiles_.push_back(gfx::WordToTileInfo(w0)); + tiles_.push_back(gfx::WordToTileInfo(w1)); + tiles_.push_back(gfx::WordToTileInfo(w2)); + tiles_.push_back(gfx::WordToTileInfo(w3)); tile_count_ = 1; tiles_loaded_ = true; } @@ -118,7 +120,7 @@ absl::Status RoomObject::LoadTilesWithParser() { return absl::OkStatus(); } -absl::StatusOr> RoomObject::GetTiles() const { +absl::StatusOr> RoomObject::GetTiles() const { if (!tiles_loaded_) { const_cast(this)->EnsureTilesLoaded(); } @@ -127,10 +129,10 @@ absl::StatusOr> RoomObject::GetTiles() const { return absl::FailedPreconditionError("No tiles loaded for object"); } - return std::span(tiles_.data(), tiles_.size()); + return std::span(tiles_.data(), tiles_.size()); } -absl::StatusOr RoomObject::GetTile(int index) const { +absl::StatusOr RoomObject::GetTile(int index) const { if (!tiles_loaded_) { const_cast(this)->EnsureTilesLoaded(); } diff --git a/src/app/zelda3/dungeon/room_object.h b/src/app/zelda3/dungeon/room_object.h index 15139479..1c9f6639 100644 --- a/src/app/zelda3/dungeon/room_object.h +++ b/src/app/zelda3/dungeon/room_object.h @@ -84,14 +84,14 @@ class RoomObject { absl::Status LoadTilesWithParser(); // Getter for tiles - const std::vector& tiles() const { return tiles_; } - std::vector& mutable_tiles() { return tiles_; } + const std::vector& tiles() const { return tiles_; } + std::vector& mutable_tiles() { return tiles_; } // Get tile data through Arena system - returns references, not copies - absl::StatusOr> GetTiles() const; + absl::StatusOr> GetTiles() const; // Get individual tile by index - uses Arena lookup - absl::StatusOr GetTile(int index) const; + absl::StatusOr GetTile(int index) const; // Get tile count without loading all tiles int GetTileCount() const; @@ -161,7 +161,7 @@ class RoomObject { // Tile data storage - using Arena system for efficient memory management // Instead of copying Tile16 vectors, we store references to Arena-managed data - mutable std::vector tiles_; // Fallback for compatibility + mutable std::vector tiles_; // Individual tiles like ZScream mutable bool tiles_loaded_ = false; mutable int tile_count_ = 0; mutable int tile_data_ptr_ = -1; // Pointer to tile data in ROM diff --git a/test/unit/zelda3/object_parser_test.cc b/test/unit/zelda3/object_parser_test.cc index dfe26130..2e292cdf 100644 --- a/test/unit/zelda3/object_parser_test.cc +++ b/test/unit/zelda3/object_parser_test.cc @@ -69,10 +69,7 @@ TEST_F(ObjectParserTest, ParseSubtype1Object) { // Verify tile data was parsed correctly for (const auto& tile : tiles) { - EXPECT_NE(tile.tile0_.id_, 0); - EXPECT_NE(tile.tile1_.id_, 0); - EXPECT_NE(tile.tile2_.id_, 0); - EXPECT_NE(tile.tile3_.id_, 0); + EXPECT_NE(tile.id_, 0); } }