From 8ec723adb6524741d734c4b778f7cff07005a7e0 Mon Sep 17 00:00:00 2001 From: scawful Date: Sat, 4 Oct 2025 02:11:11 -0400 Subject: [PATCH] feat: Implement room rendering diagnostics and integrate into Room rendering process (WIP) --- src/app/gfx/background_buffer.cc | 26 ++- src/app/zelda3/dungeon/room.cc | 60 +++++-- src/app/zelda3/dungeon/room_diagnostic.cc | 193 ++++++++++++++++++++++ src/app/zelda3/dungeon/room_diagnostic.h | 16 ++ src/app/zelda3/zelda3.cmake | 1 + 5 files changed, 271 insertions(+), 25 deletions(-) create mode 100644 src/app/zelda3/dungeon/room_diagnostic.cc create mode 100644 src/app/zelda3/dungeon/room_diagnostic.h diff --git a/src/app/gfx/background_buffer.cc b/src/app/gfx/background_buffer.cc index 0d53e3d2..6579b1fb 100644 --- a/src/app/gfx/background_buffer.cc +++ b/src/app/gfx/background_buffer.cc @@ -101,17 +101,27 @@ void BackgroundBuffer::DrawFloor(const std::vector& rom_data, rom_data[tile_address_floor + f + 7]); // Draw the floor tiles in a pattern + // Convert TileInfo to 16-bit words with palette information + uint16_t word1 = gfx::TileInfoToWord(floorTile1); + uint16_t word2 = gfx::TileInfoToWord(floorTile2); + uint16_t word3 = gfx::TileInfoToWord(floorTile3); + uint16_t word4 = gfx::TileInfoToWord(floorTile4); + uint16_t word5 = gfx::TileInfoToWord(floorTile5); + uint16_t word6 = gfx::TileInfoToWord(floorTile6); + uint16_t word7 = gfx::TileInfoToWord(floorTile7); + uint16_t word8 = gfx::TileInfoToWord(floorTile8); + for (int xx = 0; xx < 16; xx++) { for (int yy = 0; yy < 32; yy++) { - SetTileAt((xx * 4), (yy * 2), floorTile1.id_); - SetTileAt((xx * 4) + 1, (yy * 2), floorTile2.id_); - SetTileAt((xx * 4) + 2, (yy * 2), floorTile3.id_); - SetTileAt((xx * 4) + 3, (yy * 2), floorTile4.id_); + SetTileAt((xx * 4), (yy * 2), word1); + SetTileAt((xx * 4) + 1, (yy * 2), word2); + SetTileAt((xx * 4) + 2, (yy * 2), word3); + SetTileAt((xx * 4) + 3, (yy * 2), word4); - SetTileAt((xx * 4), (yy * 2) + 1, floorTile5.id_); - SetTileAt((xx * 4) + 1, (yy * 2) + 1, floorTile6.id_); - SetTileAt((xx * 4) + 2, (yy * 2) + 1, floorTile7.id_); - SetTileAt((xx * 4) + 3, (yy * 2) + 1, floorTile8.id_); + SetTileAt((xx * 4), (yy * 2) + 1, word5); + SetTileAt((xx * 4) + 1, (yy * 2) + 1, word6); + SetTileAt((xx * 4) + 2, (yy * 2) + 1, word7); + SetTileAt((xx * 4) + 3, (yy * 2) + 1, word8); } } } diff --git a/src/app/zelda3/dungeon/room.cc b/src/app/zelda3/dungeon/room.cc index 7a8fa17f..c51c8463 100644 --- a/src/app/zelda3/dungeon/room.cc +++ b/src/app/zelda3/dungeon/room.cc @@ -2,6 +2,7 @@ #include +#include #include #include @@ -10,6 +11,7 @@ #include "app/gfx/arena.h" #include "app/rom.h" #include "app/snes.h" +#include "app/zelda3/dungeon/room_diagnostic.h" #include "app/zelda3/dungeon/room_object.h" #include "app/zelda3/sprite/sprite.h" #include "util/log.h" @@ -308,26 +310,31 @@ void Room::RenderRoomGraphics() { 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 auto bg1_palette = rom()->mutable_palette_group()->get_group("dungeon_main")[0].palette(0); + + std::printf("5a. Palette loaded: size=%zu colors\n", bg1_palette.size()); - if (!gfx::Arena::Get().bg1().bitmap().is_active()) { - std::printf("6a. Creating new bitmap textures\n"); - core::Renderer::Get().CreateAndRenderBitmap( - 0x200, 0x200, 0x200, gfx::Arena::Get().bg1().bitmap().vector(), - gfx::Arena::Get().bg1().bitmap(), bg1_palette); - core::Renderer::Get().CreateAndRenderBitmap( - 0x200, 0x200, 0x200, gfx::Arena::Get().bg2().bitmap().vector(), - gfx::Arena::Get().bg2().bitmap(), bg1_palette); - } else { - std::printf("6b. Updating existing bitmap textures\n"); - // Update the bitmap - core::Renderer::Get().UpdateBitmap(&gfx::Arena::Get().bg1().bitmap()); - core::Renderer::Get().UpdateBitmap(&gfx::Arena::Get().bg2().bitmap()); - } + // CRITICAL: Apply palette to bitmaps BEFORE creating/updating textures + bg1_bmp.SetPaletteWithTransparent(bg1_palette, 0); + bg2_bmp.SetPaletteWithTransparent(bg1_palette, 0); + std::printf("5b. Palette applied to bitmaps\n"); + + // 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, 0x200, gfx::Arena::Get().bg1().bitmap().vector(), + gfx::Arena::Get().bg1().bitmap(), bg1_palette); + core::Renderer::Get().CreateAndRenderBitmap( + 0x200, 0x200, 0x200, 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_); } void Room::RenderObjectsToBackground() { @@ -372,14 +379,33 @@ void Room::RenderObjectsToBackground() { bool is_bg2 = (obj.layer_ == RoomObject::LayerType::BG2); auto& target_buffer = is_bg2 ? bg2 : bg1; + // Calculate the width of the object in Tile16 units + // Most objects are arranged in a grid, typically 1-8 tiles wide + // We calculate width based on square root for square objects, + // or use a more flexible approach for rectangular objects + int tiles_wide = 1; + if (tiles.size() > 1) { + // Try to determine optimal layout based on tile count + // Common patterns: 1x1, 2x2, 4x1, 2x4, 4x4, 8x1, etc. + int sq = static_cast(std::sqrt(tiles.size())); + if (sq * sq == static_cast(tiles.size())) { + tiles_wide = sq; // Perfect square (4, 9, 16, etc.) + } else if (tiles.size() <= 4) { + tiles_wide = tiles.size(); // Small objects laid out horizontally + } else { + // For larger objects, try common widths (4 or 8) + tiles_wide = (tiles.size() >= 8) ? 8 : 4; + } + } + // Draw each Tile16 from the object // Each Tile16 is a 16x16 tile made of 4 TileInfo (8x8) tiles for (size_t i = 0; i < tiles.size(); i++) { const auto& tile16 = tiles[i]; - // Calculate tile16 position (in 16x16 units, so multiply by 2 for 8x8 units) - int base_x = obj_x + ((i % 4) * 2); // Assume 4-tile16 width for now - int base_y = obj_y + ((i / 4) * 2); + // Calculate tile16 position based on calculated width (in 16x16 units, so multiply by 2 for 8x8 units) + int base_x = obj_x + ((i % tiles_wide) * 2); + int base_y = obj_y + ((i / tiles_wide) * 2); // Each Tile16 contains 4 TileInfo objects arranged as: // [0][1] (top-left, top-right) diff --git a/src/app/zelda3/dungeon/room_diagnostic.cc b/src/app/zelda3/dungeon/room_diagnostic.cc new file mode 100644 index 00000000..bcf0d10a --- /dev/null +++ b/src/app/zelda3/dungeon/room_diagnostic.cc @@ -0,0 +1,193 @@ +#include "room.h" + +#include +#include + +#include "app/gfx/arena.h" +#include "app/gfx/bitmap.h" + +namespace yaze { +namespace zelda3 { + +void DiagnoseRoomRendering(Room& room, int room_id) { + std::printf("\n========== ROOM RENDERING DIAGNOSTIC ==========\n"); + std::printf("Room ID: %d\n\n", room_id); + + // Step 1: Check ROM and graphics buffer + std::printf("=== Step 1: ROM and Graphics Buffer ===\n"); + auto* rom = room.rom(); + if (!rom) { + std::printf("❌ ROM pointer is NULL\n"); + return; + } + std::printf("✓ ROM pointer valid\n"); + std::printf(" ROM loaded: %s\n", rom->is_loaded() ? "YES" : "NO"); + std::printf(" ROM size: %zu bytes\n", rom->size()); + + auto* gfx_buffer = rom->mutable_graphics_buffer(); + if (!gfx_buffer || gfx_buffer->empty()) { + std::printf("❌ Graphics buffer empty\n"); + return; + } + std::printf("✓ Graphics buffer loaded: %zu bytes\n", gfx_buffer->size()); + + // Step 2: Check room blocks + std::printf("\n=== Step 2: Room Graphics Blocks ===\n"); + auto blocks = room.blocks(); + std::printf(" Blocks loaded: %s\n", blocks.empty() ? "NO" : "YES"); + if (!blocks.empty()) { + std::printf(" Block indices: "); + for (int i = 0; i < 16; i++) { + std::printf("%d ", blocks[i]); + if (i == 7) std::printf("\n "); + } + std::printf("\n"); + } + + // Step 3: Check current_gfx16_ buffer + std::printf("\n=== Step 3: current_gfx16_ Buffer ===\n"); + // Sample first 100 bytes to check if populated + bool has_data = false; + int non_zero_count = 0; + for (int i = 0; i < 100 && i < 32768; i++) { + // Access through room's internal buffer would require making it accessible + // For now, just note this step + } + 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(); + auto bg1_buffer = bg1.buffer(); + auto bg2_buffer = bg2.buffer(); + + std::printf("BG1 Buffer:\n"); + std::printf(" Size: %zu\n", bg1_buffer.size()); + int bg1_non_ff = 0; + int bg1_non_zero = 0; + for (const auto& word : bg1_buffer) { + if (word != 0xFFFF) bg1_non_ff++; + if (word != 0) bg1_non_zero++; + } + std::printf(" Non-0xFFFF tiles: %d / %zu\n", bg1_non_ff, bg1_buffer.size()); + std::printf(" Non-zero tiles: %d / %zu\n", bg1_non_zero, bg1_buffer.size()); + + // Sample first 10 non-0xFFFF tiles + std::printf(" Sample tiles (first 10 non-0xFFFF): "); + int sample_count = 0; + for (size_t i = 0; i < bg1_buffer.size() && sample_count < 10; i++) { + if (bg1_buffer[i] != 0xFFFF) { + std::printf("0x%04X ", bg1_buffer[i]); + sample_count++; + } + } + std::printf("\n"); + + std::printf("\nBG2 Buffer:\n"); + std::printf(" Size: %zu\n", bg2_buffer.size()); + int bg2_non_ff = 0; + int bg2_non_zero = 0; + for (const auto& word : bg2_buffer) { + if (word != 0xFFFF) bg2_non_ff++; + if (word != 0) bg2_non_zero++; + } + std::printf(" Non-0xFFFF tiles: %d / %zu\n", bg2_non_ff, bg2_buffer.size()); + std::printf(" Non-zero tiles: %d / %zu\n", bg2_non_zero, bg2_buffer.size()); + + // Step 5: Check bitmaps + std::printf("\n=== Step 5: Bitmaps ===\n"); + auto& bg1_bitmap = bg1.bitmap(); + auto& bg2_bitmap = bg2.bitmap(); + + std::printf("BG1 Bitmap:\n"); + std::printf(" Active: %s\n", bg1_bitmap.is_active() ? "YES" : "NO"); + std::printf(" Dimensions: %dx%d\n", bg1_bitmap.width(), bg1_bitmap.height()); + std::printf(" Data size: %zu bytes\n", bg1_bitmap.vector().size()); + std::printf(" Modified: %s\n", bg1_bitmap.modified() ? "YES" : "NO"); + + if (!bg1_bitmap.vector().empty()) { + // Sample first 100 pixels + int non_zero_pixels = 0; + std::vector unique_colors; + for (size_t i = 0; i < 100 && i < bg1_bitmap.vector().size(); i++) { + uint8_t pixel = bg1_bitmap.vector()[i]; + if (pixel != 0) non_zero_pixels++; + if (std::find(unique_colors.begin(), unique_colors.end(), pixel) == unique_colors.end()) { + unique_colors.push_back(pixel); + } + } + std::printf(" First 100 pixels: %d non-zero, %zu unique colors\n", + non_zero_pixels, unique_colors.size()); + std::printf(" Unique colors: "); + for (size_t i = 0; i < std::min(10, unique_colors.size()); i++) { + std::printf("%d ", unique_colors[i]); + } + std::printf("\n"); + } + + std::printf("\nBG2 Bitmap:\n"); + std::printf(" Active: %s\n", bg2_bitmap.is_active() ? "YES" : "NO"); + std::printf(" Dimensions: %dx%d\n", bg2_bitmap.width(), bg2_bitmap.height()); + std::printf(" Data size: %zu bytes\n", bg2_bitmap.vector().size()); + + // Step 6: Check textures + std::printf("\n=== Step 6: SDL Textures ===\n"); + std::printf("BG1 Texture:\n"); + std::printf(" Texture pointer: %p\n", (void*)bg1_bitmap.texture()); + std::printf(" Texture valid: %s\n", bg1_bitmap.texture() != nullptr ? "YES" : "NO"); + + std::printf("\nBG2 Texture:\n"); + std::printf(" Texture pointer: %p\n", (void*)bg2_bitmap.texture()); + std::printf(" Texture valid: %s\n", bg2_bitmap.texture() != nullptr ? "YES" : "NO"); + + // Step 7: Check palette + std::printf("\n=== Step 7: Palette ===\n"); + auto& palette = bg1_bitmap.palette(); + std::printf(" Palette size: %zu colors\n", palette.size()); + if (!palette.empty()) { + std::printf(" First 8 colors (SNES format): "); + for (size_t i = 0; i < 8 && i < palette.size(); i++) { + std::printf("0x%04X ", palette[i].snes()); + } + std::printf("\n"); + + // Check the actual colors being used by the bitmap pixels + std::printf(" Colors at pixel indices being used:\n"); + std::vector sample_indices; + for (size_t i = 0; i < 100 && i < bg1_bitmap.vector().size(); i++) { + uint8_t idx = bg1_bitmap.vector()[i]; + if (std::find(sample_indices.begin(), sample_indices.end(), idx) == sample_indices.end()) { + sample_indices.push_back(idx); + } + } + for (size_t i = 0; i < std::min(10, sample_indices.size()); i++) { + uint8_t idx = sample_indices[i]; + if (idx < palette.size()) { + auto color = palette[idx]; + auto rgb_vec = color.rgb(); + std::printf(" [%d] = SNES:0x%04X RGB:(%.0f,%.0f,%.0f)\n", + idx, color.snes(), rgb_vec.x * 255, rgb_vec.y * 255, rgb_vec.z * 255); + } + } + } + + // Step 8: Check room objects + std::printf("\n=== Step 8: Room Objects ===\n"); + const auto& objects = room.GetTileObjects(); + std::printf(" Object count: %zu\n", objects.size()); + if (!objects.empty()) { + std::printf(" First 5 objects:\n"); + for (size_t i = 0; i < 5 && i < objects.size(); i++) { + const auto& obj = objects[i]; + std::printf(" [%zu] ID=0x%03X, Pos=(%d,%d), Size=%d, Layer=%d\n", + i, obj.id_, obj.x_, obj.y_, obj.size_, obj.GetLayerValue()); + } + } + + std::printf("\n========== DIAGNOSTIC COMPLETE ==========\n\n"); +} + +} // namespace zelda3 +} // namespace yaze + diff --git a/src/app/zelda3/dungeon/room_diagnostic.h b/src/app/zelda3/dungeon/room_diagnostic.h new file mode 100644 index 00000000..2a4e9b24 --- /dev/null +++ b/src/app/zelda3/dungeon/room_diagnostic.h @@ -0,0 +1,16 @@ +#ifndef YAZE_APP_ZELDA3_DUNGEON_ROOM_DIAGNOSTIC_H +#define YAZE_APP_ZELDA3_DUNGEON_ROOM_DIAGNOSTIC_H + +namespace yaze { +namespace zelda3 { + +class Room; + +// Comprehensive diagnostic function to trace room rendering pipeline +void DiagnoseRoomRendering(Room& room, int room_id); + +} // namespace zelda3 +} // namespace yaze + +#endif // YAZE_APP_ZELDA3_DUNGEON_ROOM_DIAGNOSTIC_H + diff --git a/src/app/zelda3/zelda3.cmake b/src/app/zelda3/zelda3.cmake index 063b37a1..4d07bb91 100644 --- a/src/app/zelda3/zelda3.cmake +++ b/src/app/zelda3/zelda3.cmake @@ -15,6 +15,7 @@ set( app/zelda3/dungeon/object_parser.cc app/zelda3/dungeon/object_renderer.cc app/zelda3/dungeon/room_layout.cc + app/zelda3/dungeon/room_diagnostic.cc app/zelda3/dungeon/dungeon_editor_system.cc app/zelda3/dungeon/dungeon_object_editor.cc ) \ No newline at end of file