feat: Enhance object rendering and tile management in Zelda3

- Introduced a new method `set_dirty` in the `Rom` class to manage the dirty state of ROM data.
- Updated `DungeonCanvasViewer` to process texture queues before drawing, improving texture readiness and rendering performance.
- Refactored `ObjectDrawer` to utilize `TileInfo` instead of `Tile16`, enhancing tile data handling and consistency across the codebase.
- Improved the `ObjectParser` to read tile data as `TileInfo`, ensuring accurate parsing and memory management.
- Added performance optimizations in the `Room` class to track changes in properties and reduce unnecessary rendering.
- Enhanced logging for debugging purposes during tile drawing and object rendering processes.
This commit is contained in:
scawful
2025-10-10 16:19:45 -04:00
parent 434a11734c
commit 4c3cb2581d
12 changed files with 557 additions and 460 deletions

View File

@@ -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

View File

@@ -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_; }

File diff suppressed because it is too large Load Diff

View File

@@ -86,77 +86,77 @@ class ObjectDrawer {
private:
// Draw routine function type
using DrawRoutine = std::function<void(ObjectDrawer*, const RoomObject&, gfx::BackgroundBuffer&,
const std::vector<gfx::Tile16>&)>;
std::span<const gfx::TileInfo>)>;
// Core draw routines (based on ZScream's subtype1_routines table)
void DrawRightwards2x2_1to15or32(const RoomObject& obj, gfx::BackgroundBuffer& bg,
const std::vector<gfx::Tile16>& tiles);
std::span<const gfx::TileInfo> tiles);
void DrawRightwards2x4_1to15or26(const RoomObject& obj, gfx::BackgroundBuffer& bg,
const std::vector<gfx::Tile16>& tiles);
std::span<const gfx::TileInfo> tiles);
void DrawRightwards2x4spaced4_1to16(const RoomObject& obj, gfx::BackgroundBuffer& bg,
const std::vector<gfx::Tile16>& tiles);
std::span<const gfx::TileInfo> tiles);
void DrawRightwards2x4spaced4_1to16_BothBG(const RoomObject& obj, gfx::BackgroundBuffer& bg,
const std::vector<gfx::Tile16>& tiles);
std::span<const gfx::TileInfo> tiles);
void DrawRightwards2x2_1to16(const RoomObject& obj, gfx::BackgroundBuffer& bg,
const std::vector<gfx::Tile16>& tiles);
std::span<const gfx::TileInfo> tiles);
void DrawDiagonalAcute_1to16(const RoomObject& obj, gfx::BackgroundBuffer& bg,
const std::vector<gfx::Tile16>& tiles);
std::span<const gfx::TileInfo> tiles);
void DrawDiagonalGrave_1to16(const RoomObject& obj, gfx::BackgroundBuffer& bg,
const std::vector<gfx::Tile16>& tiles);
std::span<const gfx::TileInfo> tiles);
void DrawDiagonalAcute_1to16_BothBG(const RoomObject& obj, gfx::BackgroundBuffer& bg,
const std::vector<gfx::Tile16>& tiles);
std::span<const gfx::TileInfo> tiles);
void DrawDiagonalGrave_1to16_BothBG(const RoomObject& obj, gfx::BackgroundBuffer& bg,
const std::vector<gfx::Tile16>& tiles);
std::span<const gfx::TileInfo> tiles);
void DrawRightwards1x2_1to16_plus2(const RoomObject& obj, gfx::BackgroundBuffer& bg,
const std::vector<gfx::Tile16>& tiles);
std::span<const gfx::TileInfo> tiles);
void DrawRightwardsHasEdge1x1_1to16_plus3(const RoomObject& obj, gfx::BackgroundBuffer& bg,
const std::vector<gfx::Tile16>& tiles);
std::span<const gfx::TileInfo> tiles);
void DrawRightwardsHasEdge1x1_1to16_plus2(const RoomObject& obj, gfx::BackgroundBuffer& bg,
const std::vector<gfx::Tile16>& tiles);
std::span<const gfx::TileInfo> tiles);
void DrawRightwardsTopCorners1x2_1to16_plus13(const RoomObject& obj, gfx::BackgroundBuffer& bg,
const std::vector<gfx::Tile16>& tiles);
std::span<const gfx::TileInfo> tiles);
void DrawRightwardsBottomCorners1x2_1to16_plus13(const RoomObject& obj, gfx::BackgroundBuffer& bg,
const std::vector<gfx::Tile16>& tiles);
std::span<const gfx::TileInfo> tiles);
void CustomDraw(const RoomObject& obj, gfx::BackgroundBuffer& bg,
const std::vector<gfx::Tile16>& tiles);
std::span<const gfx::TileInfo> tiles);
void DrawRightwards4x4_1to16(const RoomObject& obj, gfx::BackgroundBuffer& bg,
const std::vector<gfx::Tile16>& tiles);
std::span<const gfx::TileInfo> tiles);
void DrawRightwards1x1Solid_1to16_plus3(const RoomObject& obj, gfx::BackgroundBuffer& bg,
const std::vector<gfx::Tile16>& tiles);
std::span<const gfx::TileInfo> tiles);
void DrawDoorSwitcherer(const RoomObject& obj, gfx::BackgroundBuffer& bg,
const std::vector<gfx::Tile16>& tiles);
std::span<const gfx::TileInfo> tiles);
void DrawRightwardsDecor4x4spaced2_1to16(const RoomObject& obj, gfx::BackgroundBuffer& bg,
const std::vector<gfx::Tile16>& tiles);
std::span<const gfx::TileInfo> tiles);
void DrawRightwardsStatue2x3spaced2_1to16(const RoomObject& obj, gfx::BackgroundBuffer& bg,
const std::vector<gfx::Tile16>& tiles);
std::span<const gfx::TileInfo> tiles);
void DrawRightwardsPillar2x4spaced4_1to16(const RoomObject& obj, gfx::BackgroundBuffer& bg,
const std::vector<gfx::Tile16>& tiles);
std::span<const gfx::TileInfo> tiles);
void DrawRightwardsDecor4x3spaced4_1to16(const RoomObject& obj, gfx::BackgroundBuffer& bg,
const std::vector<gfx::Tile16>& tiles);
std::span<const gfx::TileInfo> tiles);
void DrawRightwardsDoubled2x2spaced2_1to16(const RoomObject& obj, gfx::BackgroundBuffer& bg,
const std::vector<gfx::Tile16>& tiles);
std::span<const gfx::TileInfo> tiles);
void DrawRightwardsDecor2x2spaced12_1to16(const RoomObject& obj, gfx::BackgroundBuffer& bg,
const std::vector<gfx::Tile16>& tiles);
std::span<const gfx::TileInfo> tiles);
// Downwards draw routines (missing implementation)
void DrawDownwards2x2_1to15or32(const RoomObject& obj, gfx::BackgroundBuffer& bg,
const std::vector<gfx::Tile16>& tiles);
std::span<const gfx::TileInfo> tiles);
void DrawDownwards4x2_1to15or26(const RoomObject& obj, gfx::BackgroundBuffer& bg,
const std::vector<gfx::Tile16>& tiles);
std::span<const gfx::TileInfo> tiles);
void DrawDownwards4x2_1to16_BothBG(const RoomObject& obj, gfx::BackgroundBuffer& bg,
const std::vector<gfx::Tile16>& tiles);
std::span<const gfx::TileInfo> tiles);
void DrawDownwardsDecor4x2spaced4_1to16(const RoomObject& obj, gfx::BackgroundBuffer& bg,
const std::vector<gfx::Tile16>& tiles);
std::span<const gfx::TileInfo> tiles);
void DrawDownwards2x2_1to16(const RoomObject& obj, gfx::BackgroundBuffer& bg,
const std::vector<gfx::Tile16>& tiles);
std::span<const gfx::TileInfo> tiles);
void DrawDownwardsHasEdge1x1_1to16_plus3(const RoomObject& obj, gfx::BackgroundBuffer& bg,
const std::vector<gfx::Tile16>& tiles);
std::span<const gfx::TileInfo> tiles);
void DrawDownwardsEdge1x1_1to16(const RoomObject& obj, gfx::BackgroundBuffer& bg,
const std::vector<gfx::Tile16>& tiles);
std::span<const gfx::TileInfo> tiles);
void DrawDownwardsLeftCorners2x1_1to16_plus12(const RoomObject& obj, gfx::BackgroundBuffer& bg,
const std::vector<gfx::Tile16>& tiles);
std::span<const gfx::TileInfo> tiles);
void DrawDownwardsRightCorners2x1_1to16_plus12(const RoomObject& obj, gfx::BackgroundBuffer& bg,
const std::vector<gfx::Tile16>& tiles);
std::span<const gfx::TileInfo> tiles);
// Utility methods
void WriteTile8(gfx::BackgroundBuffer& bg, int tile_x, int tile_y,

View File

@@ -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<std::vector<gfx::Tile16>> ObjectParser::ParseObject(int16_t object_id) {
absl::StatusOr<std::vector<gfx::TileInfo>> ObjectParser::ParseObject(int16_t object_id) {
if (rom_ == nullptr) {
return absl::InvalidArgumentError("ROM is null");
}
@@ -116,7 +117,7 @@ absl::StatusOr<ObjectSizeInfo> ObjectParser::ParseObjectSize(int16_t object_id,
return info;
}
absl::StatusOr<std::vector<gfx::Tile16>> ObjectParser::ParseSubtype1(int16_t object_id) {
absl::StatusOr<std::vector<gfx::TileInfo>> ObjectParser::ParseSubtype1(int16_t object_id) {
int index = object_id & 0xFF;
int tile_ptr = kRoomObjectSubtype1 + (index * 2);
@@ -134,7 +135,7 @@ absl::StatusOr<std::vector<gfx::Tile16>> ObjectParser::ParseSubtype1(int16_t obj
return ReadTileData(tile_data_ptr, 8);
}
absl::StatusOr<std::vector<gfx::Tile16>> ObjectParser::ParseSubtype2(int16_t object_id) {
absl::StatusOr<std::vector<gfx::TileInfo>> ObjectParser::ParseSubtype2(int16_t object_id) {
int index = object_id & 0x7F;
int tile_ptr = kRoomObjectSubtype2 + (index * 2);
@@ -152,7 +153,7 @@ absl::StatusOr<std::vector<gfx::Tile16>> ObjectParser::ParseSubtype2(int16_t obj
return ReadTileData(tile_data_ptr, 8);
}
absl::StatusOr<std::vector<gfx::Tile16>> ObjectParser::ParseSubtype3(int16_t object_id) {
absl::StatusOr<std::vector<gfx::TileInfo>> ObjectParser::ParseSubtype3(int16_t object_id) {
int index = object_id & 0xFF;
int tile_ptr = kRoomObjectSubtype3 + (index * 2);
@@ -170,30 +171,42 @@ absl::StatusOr<std::vector<gfx::Tile16>> ObjectParser::ParseSubtype3(int16_t obj
return ReadTileData(tile_data_ptr, 8);
}
absl::StatusOr<std::vector<gfx::Tile16>> ObjectParser::ReadTileData(int address, int tile_count) {
if (address < 0 || address + (tile_count * 8) >= (int)rom_->size()) {
absl::StatusOr<std::vector<gfx::TileInfo>> 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<gfx::Tile16> tiles;
std::vector<gfx::TileInfo> 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;

View File

@@ -97,7 +97,7 @@ class ObjectParser {
* @param object_id The object ID to parse
* @return StatusOr containing the parsed tile data
*/
absl::StatusOr<std::vector<gfx::Tile16>> ParseObject(int16_t object_id);
absl::StatusOr<std::vector<gfx::TileInfo>> 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<std::vector<gfx::Tile16>> ParseSubtype1(int16_t object_id);
absl::StatusOr<std::vector<gfx::TileInfo>> ParseSubtype1(int16_t object_id);
/**
* @brief Parse subtype 2 objects (0x100-0x1FF)
*/
absl::StatusOr<std::vector<gfx::Tile16>> ParseSubtype2(int16_t object_id);
absl::StatusOr<std::vector<gfx::TileInfo>> ParseSubtype2(int16_t object_id);
/**
* @brief Parse subtype 3 objects (0x200+)
*/
absl::StatusOr<std::vector<gfx::Tile16>> ParseSubtype3(int16_t object_id);
absl::StatusOr<std::vector<gfx::TileInfo>> 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<std::vector<gfx::Tile16>> ReadTileData(int address,
absl::StatusOr<std::vector<gfx::TileInfo>> ReadTileData(int address,
int tile_count);
Rom* rom_;

View File

@@ -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<int>(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<uint8_t>(effect_) ||
cached_tag1_ != tag1_ || cached_tag2_ != tag2_) {
cached_effect_ = static_cast<uint8_t>(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<uint8_t>(current_gfx16_));
bg2_buffer_.DrawBackground(std::span<uint8_t>(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<uint8_t>(current_gfx16_));
bg2_buffer_.DrawBackground(std::span<uint8_t>(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_);
}
}

View File

@@ -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<size_t> 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<RoomObject>& 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_;

View File

@@ -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 {

View File

@@ -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<std::span<const gfx::Tile16>> RoomObject::GetTiles() const {
absl::StatusOr<std::span<const gfx::TileInfo>> RoomObject::GetTiles() const {
if (!tiles_loaded_) {
const_cast<RoomObject*>(this)->EnsureTilesLoaded();
}
@@ -127,10 +129,10 @@ absl::StatusOr<std::span<const gfx::Tile16>> RoomObject::GetTiles() const {
return absl::FailedPreconditionError("No tiles loaded for object");
}
return std::span<const gfx::Tile16>(tiles_.data(), tiles_.size());
return std::span<const gfx::TileInfo>(tiles_.data(), tiles_.size());
}
absl::StatusOr<const gfx::Tile16*> RoomObject::GetTile(int index) const {
absl::StatusOr<const gfx::TileInfo*> RoomObject::GetTile(int index) const {
if (!tiles_loaded_) {
const_cast<RoomObject*>(this)->EnsureTilesLoaded();
}

View File

@@ -84,14 +84,14 @@ class RoomObject {
absl::Status LoadTilesWithParser();
// Getter for tiles
const std::vector<gfx::Tile16>& tiles() const { return tiles_; }
std::vector<gfx::Tile16>& mutable_tiles() { return tiles_; }
const std::vector<gfx::TileInfo>& tiles() const { return tiles_; }
std::vector<gfx::TileInfo>& mutable_tiles() { return tiles_; }
// Get tile data through Arena system - returns references, not copies
absl::StatusOr<std::span<const gfx::Tile16>> GetTiles() const;
absl::StatusOr<std::span<const gfx::TileInfo>> GetTiles() const;
// Get individual tile by index - uses Arena lookup
absl::StatusOr<const gfx::Tile16*> GetTile(int index) const;
absl::StatusOr<const gfx::TileInfo*> 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<gfx::Tile16> tiles_; // Fallback for compatibility
mutable std::vector<gfx::TileInfo> 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

View File

@@ -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);
}
}