fix: Improve tile drawing logic and optimize texture processing

- Enhanced the DrawTileToBitmap function to correctly calculate tile positions within the graphics buffer, addressing potential out-of-bounds issues.
- Added detailed comments to clarify the organization of tile sheets and pixel indexing.
- Optimized the RenderRoomGraphics function by deferring texture processing to batch updates, significantly improving performance when multiple rooms are open.
This commit is contained in:
scawful
2025-10-10 13:55:39 -04:00
parent fefd60da6e
commit 8c26f17594
2 changed files with 52 additions and 22 deletions

View File

@@ -818,42 +818,69 @@ bool ObjectDrawer::IsValidTilePosition(int tile_x, int tile_y) const {
tile_y >= 0 && tile_y < kMaxTilesY; 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) { int pixel_x, int pixel_y, const uint8_t* tiledata) {
// Draw an 8x8 tile directly to bitmap at pixel coordinates // Draw an 8x8 tile directly to bitmap at pixel coordinates
if (!tiledata) return; if (!tiledata) return;
// DEBUG: Check if bitmap is valid // DEBUG: Check if bitmap is valid
if (!bitmap.is_active() || bitmap.width() == 0 || bitmap.height() == 0) { 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()); bitmap.is_active(), bitmap.width(), bitmap.height());
return; return;
} }
// Calculate tile position in graphics sheet (128 pixels wide, 16 tiles per row) // CRITICAL FIX: current_gfx16_ is organized as 16 sheets of 128x128 pixels
int tile_sheet_x = (tile_info.id_ % 16) * 8; // 16 tiles per row // Each sheet is 0x800 bytes (2048 bytes = 128*128/8 = 16384 pixels)
int tile_sheet_y = (tile_info.id_ / 16) * 8; // Each row is 16 tiles // 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
// Palettes are 3bpp (8 colors). Convert palette index to base color offset. // Palettes are 3bpp (8 colors). Convert palette index to base color offset.
uint8_t palette_offset = (tile_info.palette_ & 0x0F) * 8; uint8_t palette_offset = (tile_info.palette_ & 0x0F) * 8;
// Draw 8x8 pixels // Draw 8x8 pixels
for (int py = 0; py < 8; py++) { for (int py = 0; py < 8; py++) {
for (int px = 0; px < 8; px++) { for (int px = 0; px < 8; px++) {
// Apply mirroring // Apply mirroring
int src_x = tile_info.horizontal_mirror_ ? (7 - px) : px; int src_x = tile_info.horizontal_mirror_ ? (7 - px) : px;
int src_y = tile_info.vertical_mirror_ ? (7 - py) : py; int src_y = tile_info.vertical_mirror_ ? (7 - py) : py;
// Read pixel from graphics sheet // Calculate source pixel index in current_gfx16_ buffer
int src_index = (tile_sheet_y + src_y) * 128 + (tile_sheet_x + src_x); // 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
}
uint8_t pixel_index = tiledata[src_index]; uint8_t pixel_index = tiledata[src_index];
if (pixel_index == 0) { if (pixel_index == 0) {
continue; continue; // Transparent pixel
} }
uint8_t final_color = pixel_index + palette_offset; uint8_t final_color = pixel_index + palette_offset;
int dest_x = pixel_x + px; int dest_x = pixel_x + px;
int dest_y = pixel_y + py; int dest_y = pixel_y + py;
if (dest_x >= 0 && dest_x < bitmap.width() && dest_y >= 0 && dest_y < bitmap.height()) { if (dest_x >= 0 && dest_x < bitmap.width() && dest_y >= 0 && dest_y < bitmap.height()) {
int dest_index = dest_y * bitmap.width() + dest_x; int dest_index = dest_y * bitmap.width() + dest_x;
if (dest_index >= 0 && dest_index < static_cast<int>(bitmap.mutable_data().size())) { if (dest_index >= 0 && dest_index < static_cast<int>(bitmap.mutable_data().size())) {

View File

@@ -335,27 +335,30 @@ void Room::RenderRoomGraphics() {
// ObjectDrawer will write indexed pixel data that uses the palette we just set // ObjectDrawer will write indexed pixel data that uses the palette we just set
RenderObjectsToBackground(); RenderObjectsToBackground();
// Update textures with all the data (floor + background + objects + palette) // PERFORMANCE OPTIMIZATION: Queue texture commands but DON'T process immediately
// This allows multiple rooms to batch their texture updates together
// The dungeon_canvas_viewer.cc:552 will process all queued textures once per frame
if (bg1_bmp.texture()) { if (bg1_bmp.texture()) {
// Texture exists - UPDATE it with new object data // Texture exists - UPDATE it with new object data
LOG_DEBUG("[RenderRoomGraphics]", "Queueing UPDATE for existing textures"); LOG_DEBUG("[RenderRoomGraphics]", "Queueing UPDATE for existing textures (deferred)");
gfx::Arena::Get().QueueTextureCommand( gfx::Arena::Get().QueueTextureCommand(
gfx::Arena::TextureCommandType::UPDATE, &bg1_bmp); gfx::Arena::TextureCommandType::UPDATE, &bg1_bmp);
gfx::Arena::Get().QueueTextureCommand( gfx::Arena::Get().QueueTextureCommand(
gfx::Arena::TextureCommandType::UPDATE, &bg2_bmp); gfx::Arena::TextureCommandType::UPDATE, &bg2_bmp);
} else { } else {
// No texture yet - CREATE it // No texture yet - CREATE it
LOG_DEBUG("[RenderRoomGraphics]", "Queueing CREATE for new textures"); LOG_DEBUG("[RenderRoomGraphics]", "Queueing CREATE for new textures (deferred)");
gfx::Arena::Get().QueueTextureCommand( gfx::Arena::Get().QueueTextureCommand(
gfx::Arena::TextureCommandType::CREATE, &bg1_bmp); gfx::Arena::TextureCommandType::CREATE, &bg1_bmp);
gfx::Arena::Get().QueueTextureCommand( gfx::Arena::Get().QueueTextureCommand(
gfx::Arena::TextureCommandType::CREATE, &bg2_bmp); gfx::Arena::TextureCommandType::CREATE, &bg2_bmp);
} }
// CRITICAL: Process texture queue immediately so objects appear! // REMOVED: Don't process texture queue here - let it be batched!
// Don't wait for next frame - update NOW! // Processing happens once per frame in DrawDungeonCanvas()
gfx::Arena::Get().ProcessTextureQueue(nullptr); // This dramatically improves performance when multiple rooms are open
LOG_DEBUG("[RenderRoomGraphics]", "Processed texture queue immediately"); // gfx::Arena::Get().ProcessTextureQueue(nullptr); // OLD: Caused slowdown!
LOG_DEBUG("[RenderRoomGraphics]", "Texture commands queued for batch processing");
} }
void Room::LoadLayoutTilesToBuffer() { void Room::LoadLayoutTilesToBuffer() {