diff --git a/src/app/editor/dungeon/dungeon_canvas_viewer.cc b/src/app/editor/dungeon/dungeon_canvas_viewer.cc index f178c9a1..19178ec8 100644 --- a/src/app/editor/dungeon/dungeon_canvas_viewer.cc +++ b/src/app/editor/dungeon/dungeon_canvas_viewer.cc @@ -102,8 +102,17 @@ void DungeonCanvasViewer::DrawDungeonCanvas(int room_id) { if (rooms_ && rom_->is_loaded()) { auto& room = (*rooms_)[room_id]; - // Automatically load room graphics if not already loaded - if (room.blocks().empty()) { + // Check if THIS ROOM's buffers need rendering (not global arena!) + auto& bg1_bitmap = room.bg1_buffer().bitmap(); + bool needs_render = !bg1_bitmap.is_active() || bg1_bitmap.width() == 0; + + printf("[DrawCanvas] Room %d: needs_render=%d, bg1_active=%d, blocks=%zu, objects=%zu\n", + room_id, needs_render, bg1_bitmap.is_active(), + room.blocks().size(), room.GetTileObjects().size()); + + // Render immediately if needed + if (needs_render) { + printf("[DrawCanvas] Rendering room %d graphics...\n", room_id); (void)LoadAndRenderRoomGraphics(room_id); } @@ -112,10 +121,7 @@ void DungeonCanvasViewer::DrawDungeonCanvas(int room_id) { room.LoadObjects(); } - // NOTE: Don't draw objects here - RenderRoomBackgroundLayers() already does it - // via room.RenderRoomGraphics() which calls RenderObjectsToBackground() - - // Render background layers from arena buffers + // Render the room's background layers RenderRoomBackgroundLayers(room_id); // Render room objects with proper graphics (old system as fallback) @@ -558,22 +564,30 @@ void DungeonCanvasViewer::CalculateWallDimensions(const zelda3::RoomObject& obje // Room graphics management methods absl::Status DungeonCanvasViewer::LoadAndRenderRoomGraphics(int room_id) { + printf("[LoadAndRender] START room_id=%d\n", room_id); + if (room_id < 0 || room_id >= 128) { + printf("[LoadAndRender] ERROR: Invalid room ID\n"); return absl::InvalidArgumentError("Invalid room ID"); } if (!rom_ || !rom_->is_loaded()) { + printf("[LoadAndRender] ERROR: ROM not loaded\n"); return absl::FailedPreconditionError("ROM not loaded"); } if (!rooms_) { + printf("[LoadAndRender] ERROR: Room data not available\n"); return absl::FailedPreconditionError("Room data not available"); } auto& room = (*rooms_)[room_id]; + printf("[LoadAndRender] Got room reference\n"); // Load room graphics with proper blockset + printf("[LoadAndRender] Loading graphics for blockset %d\n", room.blockset); room.LoadRoomGraphics(room.blockset); + printf("[LoadAndRender] Graphics loaded\n"); // Load the room's palette with bounds checking if (room.palette < rom_->paletteset_ids.size() && @@ -586,16 +600,22 @@ absl::Status DungeonCanvasViewer::LoadAndRenderRoomGraphics(int room_id) { auto full_palette = rom_->palette_group().dungeon_main[current_palette_group_id_]; ASSIGN_OR_RETURN(current_palette_group_, gfx::CreatePaletteGroupFromLargePalette(full_palette)); + printf("[LoadAndRender] Palette loaded: group_id=%zu\n", current_palette_group_id_); } } } // Render the room graphics to the graphics arena + printf("[LoadAndRender] Calling room.RenderRoomGraphics()...\n"); room.RenderRoomGraphics(); + printf("[LoadAndRender] RenderRoomGraphics() complete\n"); // Update the background layers with proper palette + printf("[LoadAndRender] Updating background layers...\n"); RETURN_IF_ERROR(UpdateRoomBackgroundLayers(room_id)); + printf("[LoadAndRender] UpdateRoomBackgroundLayers() complete\n"); + printf("[LoadAndRender] SUCCESS\n"); return absl::OkStatus(); } @@ -655,86 +675,53 @@ absl::Status DungeonCanvasViewer::UpdateRoomBackgroundLayers(int room_id) { } void DungeonCanvasViewer::RenderRoomBackgroundLayers(int room_id) { - if (room_id < 0 || room_id >= 128) { - printf("[Canvas] Invalid room_id: %d\n", room_id); - return; - } + if (room_id < 0 || room_id >= 128 || !rooms_) return; - if (!rom_ || !rom_->is_loaded()) { - printf("[Canvas] ROM not loaded\n"); - return; - } + auto& room = (*rooms_)[room_id]; - if (!rooms_) { - printf("[Canvas] Rooms pointer is null\n"); - return; - } + // Use THIS room's own buffers, not global arena! + auto& bg1_bitmap = room.bg1_buffer().bitmap(); + auto& bg2_bitmap = room.bg2_buffer().bitmap(); - // Get canvas dimensions - int canvas_width = canvas_.width(); - int canvas_height = canvas_.height(); - - printf("[Canvas] Canvas size: %dx%d\n", canvas_width, canvas_height); - - if (canvas_width <= 0 || canvas_height <= 0) { - printf("[Canvas] Invalid canvas dimensions\n"); - return; - } - - // Render BG1 (background layer 1) - main room graphics - auto& bg1_bitmap = gfx::Arena::Get().bg1().bitmap(); - printf("[Canvas] BG1: active=%d, size=%dx%d, texture=%p\n", - bg1_bitmap.is_active(), bg1_bitmap.width(), bg1_bitmap.height(), - (void*)bg1_bitmap.texture()); + printf("[RenderLayers] Room %d: BG1 active=%d, texture=%p\n", + room_id, bg1_bitmap.is_active(), (void*)bg1_bitmap.texture()); if (bg1_bitmap.is_active() && bg1_bitmap.width() > 0 && bg1_bitmap.height() > 0) { - // Ensure texture exists if (!bg1_bitmap.texture()) { - printf("[Canvas] WARNING: BG1 has no texture, creating...\n"); + printf("[RenderLayers] Creating BG1 texture...\n"); core::Renderer::Get().RenderBitmap(&bg1_bitmap); } + printf("[RenderLayers] Drawing BG1 bitmap: %dx%d, texture=%p\n", + bg1_bitmap.width(), bg1_bitmap.height(), (void*)bg1_bitmap.texture()); - // Scale to fit canvas - float scale_x = static_cast(canvas_width) / bg1_bitmap.width(); - float scale_y = static_cast(canvas_height) / bg1_bitmap.height(); - float scale = std::min(scale_x, scale_y); + // DEBUG: Check SDL texture format + Uint32 format; + int access, w, h; + if (SDL_QueryTexture(bg1_bitmap.texture(), &format, &access, &w, &h) == 0) { + printf("[RenderLayers] BG1 texture format: %s (%u), access: %d, size: %dx%d\n", + SDL_GetPixelFormatName(format), format, access, w, h); + } - int scaled_width = static_cast(bg1_bitmap.width() * scale); - int scaled_height = static_cast(bg1_bitmap.height() * scale); - int offset_x = (canvas_width - scaled_width) / 2; - int offset_y = (canvas_height - scaled_height) / 2; - - printf("[Canvas] Drawing BG1 at offset=(%d,%d), scaled_size=%dx%d, scale=%.2f\n", - offset_x, offset_y, scaled_width, scaled_height, scale); - - canvas_.DrawBitmap(bg1_bitmap, offset_x, offset_y, scale, 255); + canvas_.DrawBitmap(bg1_bitmap, 0, 0, 1.0f, 255); } else { - printf("[Canvas] BG1 not ready for rendering\n"); + printf("[RenderLayers] BG1 not ready: active=%d, w=%d, h=%d\n", + bg1_bitmap.is_active(), bg1_bitmap.width(), bg1_bitmap.height()); } - // Render BG2 (background layer 2) - sprite graphics (overlay) - auto& bg2_bitmap = gfx::Arena::Get().bg2().bitmap(); if (bg2_bitmap.is_active() && bg2_bitmap.width() > 0 && bg2_bitmap.height() > 0) { if (!bg2_bitmap.texture()) { core::Renderer::Get().RenderBitmap(&bg2_bitmap); } - - float scale_x = static_cast(canvas_width) / bg2_bitmap.width(); - float scale_y = static_cast(canvas_height) / bg2_bitmap.height(); - float scale = std::min(scale_x, scale_y); - - int scaled_width = static_cast(bg2_bitmap.width() * scale); - int scaled_height = static_cast(bg2_bitmap.height() * scale); - int offset_x = (canvas_width - scaled_width) / 2; - int offset_y = (canvas_height - scaled_height) / 2; - - printf("[Canvas] Drawing BG2 at offset=(%d,%d), scaled_size=%dx%d, scale=%.2f\n", - offset_x, offset_y, scaled_width, scaled_height, scale); - - canvas_.DrawBitmap(bg2_bitmap, offset_x, offset_y, scale, 200); + canvas_.DrawBitmap(bg2_bitmap, 0, 0, 1.0f, 200); } - printf("[Canvas] RenderRoomBackgroundLayers complete\n"); + // TEST: Draw a bright red rectangle to verify canvas drawing works + ImDrawList* draw_list = ImGui::GetWindowDrawList(); + ImVec2 canvas_pos = ImGui::GetCursorScreenPos(); + draw_list->AddRectFilled( + ImVec2(canvas_pos.x + 50, canvas_pos.y + 50), + ImVec2(canvas_pos.x + 150, canvas_pos.y + 150), + IM_COL32(255, 0, 0, 255)); // Bright red } } // namespace yaze::editor diff --git a/src/app/gfx/background_buffer.cc b/src/app/gfx/background_buffer.cc index c7ae471d..91b43f54 100644 --- a/src/app/gfx/background_buffer.cc +++ b/src/app/gfx/background_buffer.cc @@ -35,53 +35,90 @@ void BackgroundBuffer::ClearBuffer() { std::ranges::fill(buffer_, 0); } void BackgroundBuffer::DrawTile(const TileInfo& tile, uint8_t* canvas, const uint8_t* tiledata, int indexoffset) { - int tx = (tile.id_ / 16 * 512) + ((tile.id_ & 0xF) * 4); + // tiledata is a 128-pixel-wide indexed bitmap (16 tiles/row * 8 pixels/tile) + // Calculate tile position in the tilesheet + int tile_x = (tile.id_ % 16) * 8; // 16 tiles per row, 8 pixels per tile + int tile_y = (tile.id_ / 16) * 8; // Each row is 16 tiles - // Clamp palette to 0-5 (90 colors / 16 = 5.625, so max palette is 5) - // Palettes 6-7 would require colors 96-127, which don't exist in dungeon palettes - uint8_t clamped_palette = tile.palette_ & 0x07; // Get palette 0-7 - if (clamped_palette > 5) { - clamped_palette = clamped_palette % 6; // Wrap palette 6->0, 7->1 + // Dungeon graphics are 3BPP: 8 colors per palette (0-7, 8-15, 16-23, etc.) + // NOT 4BPP which would be 16 colors per palette! + // Clamp palette to 0-10 (90 colors / 8 = 11.25, so max palette is 10) + uint8_t clamped_palette = tile.palette_ & 0x0F; + if (clamped_palette > 10) { + clamped_palette = clamped_palette % 11; } - uint8_t palnibble = (uint8_t)(clamped_palette << 4); - uint8_t r = tile.horizontal_mirror_ ? 1 : 0; + // For 3BPP: palette offset = palette * 8 (not * 16!) + uint8_t palette_offset = (uint8_t)(clamped_palette * 8); - for (int yl = 0; yl < 512; yl += 64) { - int my = indexoffset + (tile.vertical_mirror_ ? 448 - yl : yl); - - for (int xl = 0; xl < 4; xl++) { - int mx = 2 * (tile.horizontal_mirror_ ? 3 - xl : xl); - - uint8_t pixel = tiledata[tx + yl + xl]; - - int index = mx + my; - canvas[index + r ^ 1] = (uint8_t)((pixel & 0x0F) | palnibble); - canvas[index + r] = (uint8_t)((pixel >> 4) | palnibble); + // Copy 8x8 pixels from tiledata to canvas + for (int py = 0; py < 8; py++) { + for (int px = 0; px < 8; px++) { + // Apply mirroring + int src_x = tile.horizontal_mirror_ ? (7 - px) : px; + int src_y = tile.vertical_mirror_ ? (7 - py) : py; + + // Read pixel from tiledata (128-pixel-wide bitmap) + int src_index = (tile_y + src_y) * 128 + (tile_x + src_x); + uint8_t pixel_index = tiledata[src_index]; + + // Apply palette offset and write to canvas + // For 3BPP: final color = base_pixel (0-7) + palette_offset (0, 8, 16, 24, ...) + uint8_t final_color = pixel_index + palette_offset; + int dest_index = indexoffset + (py * width_) + px; + canvas[dest_index] = final_color; } } } void BackgroundBuffer::DrawBackground(std::span gfx16_data) { - // Initialize bitmap - bitmap_.Create(width_, height_, 8, std::vector(width_ * height_, 0)); int tiles_w = width_ / 8; int tiles_h = height_ / 8; if ((int)buffer_.size() < tiles_w * tiles_h) { buffer_.resize(tiles_w * tiles_h); } + + // CRITICAL: Always create a fresh bitmap for each DrawBackground call + // This ensures we're rendering the current tilemap state, not stale data + printf("[BG:DrawBackground] Creating fresh bitmap for rendering\n"); + bitmap_.Create(width_, height_, 8, std::vector(width_ * height_, 0)); + + // DEBUG: Check if gfx16_data has actual graphics + if (gfx16_data.size() < 100) { + printf("[BG:DrawBackground] WARNING: gfx16_data is too small (%zu bytes)\n", gfx16_data.size()); + } else { + // Sample first 32 bytes + printf("[BG:DrawBackground] gfx16_data size=%zu, first 32 bytes: ", gfx16_data.size()); + for (size_t i = 0; i < 32 && i < gfx16_data.size(); i++) { + printf("%02X ", gfx16_data[i]); + } + printf("\n"); + } // For each tile on the tile buffer + int drawn_count = 0; for (int yy = 0; yy < tiles_h; yy++) { for (int xx = 0; xx < tiles_w; xx++) { uint16_t word = buffer_[xx + yy * tiles_w]; // Prevent draw if tile == 0xFFFF since it's 0 indexed if (word == 0xFFFF) continue; auto tile = gfx::WordToTileInfo(word); - DrawTile(tile, bitmap_.mutable_data().data(), gfx16_data.data(), - (yy * 512) + (xx * 8)); + + // Calculate pixel offset for tile position (xx, yy) in the 512x512 bitmap + // Each tile is 8x8, so pixel Y = yy * 8, pixel X = xx * 8 + // Linear offset = (pixel_y * width) + pixel_x = (yy * 8 * 512) + (xx * 8) + int tile_offset = (yy * 8 * width_) + (xx * 8); + DrawTile(tile, bitmap_.mutable_data().data(), gfx16_data.data(), tile_offset); + drawn_count++; } } + // CRITICAL: Sync bitmap data back to SDL surface! + // DrawTile() writes to bitmap_.mutable_data(), but the SDL surface needs updating + if (bitmap_.surface() && bitmap_.mutable_data().size() > 0) { + SDL_LockSurface(bitmap_.surface()); + memcpy(bitmap_.surface()->pixels, bitmap_.mutable_data().data(), bitmap_.mutable_data().size()); + SDL_UnlockSurface(bitmap_.surface()); + } } void BackgroundBuffer::DrawFloor(const std::vector& rom_data, diff --git a/src/app/zelda3/dungeon/room.cc b/src/app/zelda3/dungeon/room.cc index dc8b790c..461efb64 100644 --- a/src/app/zelda3/dungeon/room.cc +++ b/src/app/zelda3/dungeon/room.cc @@ -260,9 +260,12 @@ void Room::CopyRoomGraphicsToBuffer() { int buffer_index = data + block_offset; if (buffer_index >= 0 && buffer_index < static_cast(gfx_buffer_data->size())) { uint8_t map_byte = (*gfx_buffer_data)[buffer_index]; - if (i < 4) { - map_byte += kGfxBufferRoomSpriteLastLineOffset; - } + // NOTE: DO NOT apply sprite offset here! + // current_gfx16_ holds pixel data (palette indices 0-7), not tile IDs. + // The 0x88 offset is for tile IDs in tilemaps, not raw pixel data. + // if (i < 4) { + // map_byte += kGfxBufferRoomSpriteLastLineOffset; + // } // Validate current_gfx16_ access int gfx_index = data + sheet_pos; @@ -280,75 +283,45 @@ void Room::CopyRoomGraphicsToBuffer() { } void Room::RenderRoomGraphics() { - std::printf("\n=== RenderRoomGraphics Room %d ===\n", room_id_); - CopyRoomGraphicsToBuffer(); - std::printf("1. Graphics buffer copied\n"); - gfx::Arena::Get().bg1().DrawFloor(rom()->vector(), tile_address, - tile_address_floor, floor1_graphics_); - gfx::Arena::Get().bg2().DrawFloor(rom()->vector(), tile_address, - tile_address_floor, floor2_graphics_); - std::printf("2. Floor pattern drawn\n"); + bg1_buffer_.DrawFloor(rom()->vector(), tile_address, + tile_address_floor, floor1_graphics_); + bg2_buffer_.DrawFloor(rom()->vector(), tile_address, + tile_address_floor, floor2_graphics_); // Render layout and object tiles to background buffers RenderObjectsToBackground(); - std::printf("3. Objects rendered to buffer\n"); - gfx::Arena::Get().bg1().DrawBackground(std::span(current_gfx16_)); - gfx::Arena::Get().bg2().DrawBackground(std::span(current_gfx16_)); - std::printf("4. Background drawn from buffer\n"); + bg1_buffer_.DrawBackground(std::span(current_gfx16_)); + bg2_buffer_.DrawBackground(std::span(current_gfx16_)); - auto& bg1_bmp = gfx::Arena::Get().bg1().bitmap(); - auto& bg2_bmp = gfx::Arena::Get().bg2().bitmap(); - std::printf("5. BG1 bitmap: active=%d, size=%dx%d, data_size=%zu\n", - bg1_bmp.is_active(), bg1_bmp.width(), bg1_bmp.height(), bg1_bmp.vector().size()); - - // Get the palette for this room - just use the 90-color palette as-is - // The SNES will index into this palette correctly without needing expansion + auto& bg1_bmp = bg1_buffer_.bitmap(); + auto& bg2_bmp = bg2_buffer_.bitmap(); + + // Get and apply palette FIRST (before marking modified) auto& dungeon_pal_group = rom()->mutable_palette_group()->dungeon_main; int num_palettes = dungeon_pal_group.size(); int palette_id = palette; - std::printf("5a. Dungeon palette group has %d palettes total\n", num_palettes); - - // Validate palette ID and fall back to palette 0 if invalid if (palette_id < 0 || palette_id >= num_palettes) { - std::printf("5a. WARNING: palette_id %d is out of bounds [0, %d), using palette 0\n", + std::printf("5. WARNING: palette_id %d is out of bounds [0, %d), using palette 0\n", palette_id, num_palettes); palette_id = 0; } - // Load the 90-color dungeon palette directly - // The palette contains colors for BG layers - sprite colors are handled separately - auto bg1_palette = dungeon_pal_group[palette_id]; // Use operator[] to get a proper reference - - std::printf("5a. Palette loaded: room palette_id=%d (requested=%d), size=%zu colors\n", - palette_id, palette, bg1_palette.size()); + auto bg1_palette = dungeon_pal_group[palette_id]; - // CRITICAL: Only apply palette if it's valid if (bg1_palette.size() > 0) { - bg1_bmp.SetPaletteWithTransparent(bg1_palette, 0); - bg2_bmp.SetPaletteWithTransparent(bg1_palette, 0); - std::printf("5b. Palette applied to bitmaps\n"); - } else { - std::printf("5b. WARNING: Palette is empty, skipping SetPalette\n"); + // Use SetPalette() to apply the FULL 90-color dungeon palette + // SetPaletteWithTransparent() only extracts 8 colors, which is wrong for dungeons! + bg1_bmp.SetPalette(bg1_palette); + bg2_bmp.SetPalette(bg1_palette); } - - // ALWAYS recreate textures when palette changes (UpdateBitmap doesn't update palette!) - std::printf("6. Recreating bitmap textures with new palette\n"); - core::Renderer::Get().CreateAndRenderBitmap( - 0x200, 0x200, 8, gfx::Arena::Get().bg1().bitmap().vector(), - gfx::Arena::Get().bg1().bitmap(), bg1_palette); - core::Renderer::Get().CreateAndRenderBitmap( - 0x200, 0x200, 8, gfx::Arena::Get().bg2().bitmap().vector(), - gfx::Arena::Get().bg2().bitmap(), bg1_palette); - std::printf("7. BG1 has texture: %d\n", bg1_bmp.texture() != nullptr); - std::printf("=== RenderRoomGraphics Complete ===\n\n"); - - // Run comprehensive diagnostic - DiagnoseRoomRendering(*this, room_id_); + // CRITICAL: Recreate textures with the palette applied! + core::Renderer::Get().RenderBitmap(&bg1_buffer_.bitmap()); + core::Renderer::Get().RenderBitmap(&bg2_buffer_.bitmap()); } void Room::RenderObjectsToBackground() { @@ -359,9 +332,9 @@ void Room::RenderObjectsToBackground() { std::printf("RenderObjectsToBackground: Room %d has %zu objects\n", room_id_, tile_objects_.size()); - // Get references to the background buffers - auto& bg1 = gfx::Arena::Get().bg1(); - auto& bg2 = gfx::Arena::Get().bg2(); + // Get references to THIS room's background buffers + auto& bg1 = bg1_buffer_; + auto& bg2 = bg2_buffer_; // Render tile objects to their respective layers int rendered_count = 0; diff --git a/src/app/zelda3/dungeon/room.h b/src/app/zelda3/dungeon/room.h index 6dc9cb15..fca26a9d 100644 --- a/src/app/zelda3/dungeon/room.h +++ b/src/app/zelda3/dungeon/room.h @@ -8,6 +8,7 @@ #include #include "app/rom.h" +#include "app/gfx/background_buffer.h" #include "app/zelda3/dungeon/room_layout.h" #include "app/zelda3/dungeon/room_object.h" #include "app/zelda3/sprite/sprite.h" @@ -348,11 +349,21 @@ class Room { auto rom() { return rom_; } auto mutable_rom() { return rom_; } const std::array& get_gfx_buffer() const { return current_gfx16_; } + + // Per-room background buffers (not shared via arena!) + auto& bg1_buffer() { return bg1_buffer_; } + auto& bg2_buffer() { return bg2_buffer_; } + const auto& bg1_buffer() const { return bg1_buffer_; } + const auto& bg2_buffer() const { return bg2_buffer_; } private: Rom* rom_; std::array current_gfx16_; + + // Each room has its OWN background buffers and bitmaps + gfx::BackgroundBuffer bg1_buffer_{512, 512}; + gfx::BackgroundBuffer bg2_buffer_{512, 512}; bool is_light_; bool is_loaded_ = false; diff --git a/src/app/zelda3/dungeon/room_diagnostic.cc b/src/app/zelda3/dungeon/room_diagnostic.cc index bcf0d10a..d0ec7e89 100644 --- a/src/app/zelda3/dungeon/room_diagnostic.cc +++ b/src/app/zelda3/dungeon/room_diagnostic.cc @@ -55,10 +55,10 @@ void DiagnoseRoomRendering(Room& room, int room_id) { } std::printf(" Note: current_gfx16_ is internal, assuming populated after CopyRoomGraphicsToBuffer()\n"); - // Step 4: Check background buffers in arena - std::printf("\n=== Step 4: Background Buffers (Arena) ===\n"); - auto& bg1 = gfx::Arena::Get().bg1(); - auto& bg2 = gfx::Arena::Get().bg2(); + // Step 4: Check THIS ROOM's background buffers (not arena!) + std::printf("\n=== Step 4: Room Background Buffers ===\n"); + auto& bg1 = room.bg1_buffer(); + auto& bg2 = room.bg2_buffer(); auto bg1_buffer = bg1.buffer(); auto bg2_buffer = bg2.buffer();