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(); 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 // Draw the room's background layers to canvas
// This already includes objects rendered by ObjectDrawer in Room::RenderObjectsToBackground() // This already includes objects rendered by ObjectDrawer in Room::RenderObjectsToBackground()
DrawRoomBackgroundLayers(room_id); DrawRoomBackgroundLayers(room_id);
@@ -545,13 +551,6 @@ void DungeonCanvasViewer::DrawDungeonCanvas(int room_id) {
canvas_.DrawGrid(); canvas_.DrawGrid();
canvas_.DrawOverlay(); 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 // Draw layer information overlay
if (rooms_ && rom_->is_loaded()) { if (rooms_ && rom_->is_loaded()) {
auto& room = (*rooms_)[room_id]; 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 (layer_settings.bg1_visible && bg1_bitmap.is_active() && bg1_bitmap.width() > 0 && bg1_bitmap.height() > 0) {
if (!bg1_bitmap.texture()) { if (!bg1_bitmap.texture()) {
// Queue texture creation for background layer 1 via Arena's deferred system // 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::Get().QueueTextureCommand(
gfx::Arena::TextureCommandType::CREATE, &bg1_bitmap); gfx::Arena::TextureCommandType::CREATE, &bg1_bitmap);
// CRITICAL FIX: Process texture queue immediately to ensure texture is created before drawing // Queue will be processed at the end of the frame in DrawDungeonCanvas()
gfx::Arena::Get().ProcessTextureQueue(nullptr); // This allows multiple rooms to batch their texture operations together
} }
// Only draw if texture was successfully created // 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 (layer_settings.bg2_visible && bg2_bitmap.is_active() && bg2_bitmap.width() > 0 && bg2_bitmap.height() > 0) {
if (!bg2_bitmap.texture()) { if (!bg2_bitmap.texture()) {
// Queue texture creation for background layer 2 via Arena's deferred system // 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::Get().QueueTextureCommand(
gfx::Arena::TextureCommandType::CREATE, &bg2_bitmap); gfx::Arena::TextureCommandType::CREATE, &bg2_bitmap);
// CRITICAL FIX: Process texture queue immediately to ensure texture is created before drawing // Queue will be processed at the end of the frame in DrawDungeonCanvas()
gfx::Arena::Get().ProcessTextureQueue(nullptr); // This allows multiple rooms to batch their texture operations together
} }
// Only draw if texture was successfully created // Only draw if texture was successfully created

View File

@@ -196,6 +196,7 @@ class Rom {
bool is_loaded() const { return !rom_data_.empty(); } bool is_loaded() const { return !rom_data_.empty(); }
bool dirty() const { return dirty_; } bool dirty() const { return dirty_; }
void set_dirty(bool dirty) { dirty_ = dirty; }
void ClearDirty() { dirty_ = false; } void ClearDirty() { dirty_ = false; }
auto title() const { return title_; } auto title() const { return title_; }
auto size() const { return size_; } auto size() const { return size_; }

File diff suppressed because it is too large Load Diff

View File

@@ -86,77 +86,77 @@ class ObjectDrawer {
private: private:
// Draw routine function type // Draw routine function type
using DrawRoutine = std::function<void(ObjectDrawer*, const RoomObject&, gfx::BackgroundBuffer&, 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) // Core draw routines (based on ZScream's subtype1_routines table)
void DrawRightwards2x2_1to15or32(const RoomObject& obj, gfx::BackgroundBuffer& bg, 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, 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, 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, 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, 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, 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, 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, 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, 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, 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, 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, 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, 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, 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, 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, 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, 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, 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, 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, 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, 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, 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, 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, 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) // Downwards draw routines (missing implementation)
void DrawDownwards2x2_1to15or32(const RoomObject& obj, gfx::BackgroundBuffer& bg, 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, 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, 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, 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, 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, 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, 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, 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, void DrawDownwardsRightCorners2x1_1to16_plus12(const RoomObject& obj, gfx::BackgroundBuffer& bg,
const std::vector<gfx::Tile16>& tiles); std::span<const gfx::TileInfo> tiles);
// Utility methods // Utility methods
void WriteTile8(gfx::BackgroundBuffer& bg, int tile_x, int tile_y, void WriteTile8(gfx::BackgroundBuffer& bg, int tile_x, int tile_y,

View File

@@ -5,6 +5,7 @@
#include "absl/strings/str_format.h" #include "absl/strings/str_format.h"
#include "app/zelda3/dungeon/room_object.h" #include "app/zelda3/dungeon/room_object.h"
#include "util/log.h"
// ROM addresses for object data (PC addresses, not SNES) // ROM addresses for object data (PC addresses, not SNES)
static constexpr int kRoomObjectSubtype1 = 0x0A8000; static constexpr int kRoomObjectSubtype1 = 0x0A8000;
@@ -15,7 +16,7 @@ static constexpr int kRoomObjectTileAddress = 0x0AB000;
namespace yaze { namespace yaze {
namespace zelda3 { 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) { if (rom_ == nullptr) {
return absl::InvalidArgumentError("ROM is null"); return absl::InvalidArgumentError("ROM is null");
} }
@@ -116,7 +117,7 @@ absl::StatusOr<ObjectSizeInfo> ObjectParser::ParseObjectSize(int16_t object_id,
return info; 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 index = object_id & 0xFF;
int tile_ptr = kRoomObjectSubtype1 + (index * 2); 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); 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 index = object_id & 0x7F;
int tile_ptr = kRoomObjectSubtype2 + (index * 2); 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); 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 index = object_id & 0xFF;
int tile_ptr = kRoomObjectSubtype3 + (index * 2); 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); return ReadTileData(tile_data_ptr, 8);
} }
absl::StatusOr<std::vector<gfx::Tile16>> ObjectParser::ReadTileData(int address, int tile_count) { absl::StatusOr<std::vector<gfx::TileInfo>> ObjectParser::ReadTileData(int address, int tile_count) {
if (address < 0 || address + (tile_count * 8) >= (int)rom_->size()) { // 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( return absl::OutOfRangeError(
absl::StrFormat("Tile data address out of range: %#06x", address)); absl::StrFormat("Tile data address out of range: %#06x", address));
} }
std::vector<gfx::Tile16> tiles; std::vector<gfx::TileInfo> tiles;
tiles.reserve(tile_count); 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++) { 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 // Read 1 word (2 bytes) per tile - this is the SNES tile format
uint16_t w0 = rom_->data()[tile_offset] | (rom_->data()[tile_offset + 1] << 8); uint16_t tile_word = 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);
tiles.emplace_back( auto tile_info = gfx::WordToTileInfo(tile_word);
gfx::WordToTileInfo(w0), tiles.push_back(tile_info);
gfx::WordToTileInfo(w1),
gfx::WordToTileInfo(w2), // DEBUG: Log first few tiles
gfx::WordToTileInfo(w3) 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; return tiles;

View File

@@ -97,7 +97,7 @@ class ObjectParser {
* @param object_id The object ID to parse * @param object_id The object ID to parse
* @return StatusOr containing the parsed tile data * @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 * @brief Parse object routine data
@@ -141,17 +141,17 @@ class ObjectParser {
/** /**
* @brief Parse subtype 1 objects (0x00-0xFF) * @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) * @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+) * @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 * @brief Read tile data from ROM
@@ -160,7 +160,7 @@ class ObjectParser {
* @param tile_count Number of tiles to read * @param tile_count Number of tiles to read
* @return StatusOr containing tile data * @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); int tile_count);
Rom* rom_; Rom* rom_;

View File

@@ -227,16 +227,21 @@ constexpr int kGfxBufferRoomSpriteLastLineOffset = 0x88;
void Room::CopyRoomGraphicsToBuffer() { void Room::CopyRoomGraphicsToBuffer() {
if (!rom_ || !rom_->is_loaded()) { if (!rom_ || !rom_->is_loaded()) {
printf("[CopyRoomGraphicsToBuffer] ROM not loaded\n");
return; return;
} }
auto gfx_buffer_data = rom()->mutable_graphics_buffer(); auto gfx_buffer_data = rom()->mutable_graphics_buffer();
if (!gfx_buffer_data || gfx_buffer_data->empty()) { if (!gfx_buffer_data || gfx_buffer_data->empty()) {
printf("[CopyRoomGraphicsToBuffer] Graphics buffer is null or empty\n");
return; return;
} }
printf("[CopyRoomGraphicsToBuffer] Room %d: Copying graphics from blocks\n", room_id_);
// Copy room graphics to buffer // Copy room graphics to buffer
int sheet_pos = 0; int sheet_pos = 0;
int bytes_copied = 0;
for (int i = 0; i < 16; i++) { for (int i = 0; i < 16; i++) {
// Validate block index // Validate block index
if (blocks_[i] < 0 || blocks_[i] > 255) { if (blocks_[i] < 0 || blocks_[i] > 255) {
@@ -268,6 +273,7 @@ void Room::CopyRoomGraphicsToBuffer() {
int gfx_index = data + sheet_pos; int gfx_index = data + sheet_pos;
if (gfx_index >= 0 && gfx_index < static_cast<int>(sizeof(current_gfx16_))) { if (gfx_index >= 0 && gfx_index < static_cast<int>(sizeof(current_gfx16_))) {
current_gfx16_[gfx_index] = map_byte; current_gfx16_[gfx_index] = map_byte;
if (map_byte != 0) bytes_copied++;
} }
} }
data++; data++;
@@ -276,12 +282,62 @@ void Room::CopyRoomGraphicsToBuffer() {
sheet_pos += kGfxBufferRoomOffset; sheet_pos += kGfxBufferRoomOffset;
} }
printf("[CopyRoomGraphicsToBuffer] Room %d: Copied %d non-zero bytes to current_gfx16_\n", room_id_, bytes_copied);
LoadAnimatedGraphics(); LoadAnimatedGraphics();
} }
void Room::RenderRoomGraphics() { void Room::RenderRoomGraphics() {
CopyRoomGraphicsToBuffer(); // PERFORMANCE OPTIMIZATION: Check if room properties have changed
LoadLayoutTilesToBuffer(); 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 // 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",
@@ -290,19 +346,31 @@ void Room::RenderRoomGraphics() {
// LoadGraphicsSheetsIntoArena() removed - using per-room graphics instead // LoadGraphicsSheetsIntoArena() removed - using per-room graphics instead
// Arena sheets are optional and not needed for room rendering // Arena sheets are optional and not needed for room rendering
// STEP 1: Draw floor tiles to bitmaps (base layer) // STEP 2: Draw floor tiles to bitmaps (base layer) - if graphics changed OR bitmaps not created yet
bg1_buffer_.DrawFloor(rom()->vector(), tile_address, bool need_floor_draw = graphics_dirty_;
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_));
auto& bg1_bmp = bg1_buffer_.bitmap(); auto& bg1_bmp = bg1_buffer_.bitmap();
auto& bg2_bmp = bg2_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) // Get and apply palette BEFORE rendering objects (so objects use correct colors)
auto& dungeon_pal_group = rom()->mutable_palette_group()->dungeon_main; auto& dungeon_pal_group = rom()->mutable_palette_group()->dungeon_main;
int num_palettes = dungeon_pal_group.size(); int num_palettes = dungeon_pal_group.size();
@@ -354,6 +422,9 @@ void Room::RenderRoomGraphics() {
gfx::Arena::TextureCommandType::CREATE, &bg2_bmp); 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! // REMOVED: Don't process texture queue here - let it be batched!
// Processing happens once per frame in DrawDungeonCanvas() // Processing happens once per frame in DrawDungeonCanvas()
// This dramatically improves performance when multiple rooms are open // This dramatically improves performance when multiple rooms are open
@@ -388,8 +459,8 @@ void Room::LoadLayoutTilesToBuffer() {
tiles_skipped++; tiles_skipped++;
continue; continue;
} }
const auto* tile16 = tile_result.value(); const auto* tile_info = tile_result.value();
uint16_t tile_word = gfx::TileInfoToWord(tile16->tile0_); uint16_t tile_word = gfx::TileInfoToWord(*tile_info);
if (layout_obj.GetLayerValue() == 1) { if (layout_obj.GetLayerValue() == 1) {
bg2_buffer_.SetTileAt(x, y, tile_word); bg2_buffer_.SetTileAt(x, y, tile_word);
@@ -404,13 +475,24 @@ void Room::LoadLayoutTilesToBuffer() {
} }
void Room::RenderObjectsToBackground() { 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()) { if (!rom_ || !rom_->is_loaded()) {
LOG_DEBUG("[RenderObjectsToBackground]", "ROM not loaded, aborting"); LOG_DEBUG("[RenderObjectsToBackground]", "ROM not loaded, aborting");
return; 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) // Get palette group for object rendering (use SAME lookup as RenderRoomGraphics)
auto& dungeon_pal_group = rom()->mutable_palette_group()->dungeon_main; auto& dungeon_pal_group = rom()->mutable_palette_group()->dungeon_main;
int num_palettes = dungeon_pal_group.size(); int num_palettes = dungeon_pal_group.size();
@@ -450,6 +532,10 @@ void Room::RenderObjectsToBackground() {
// Log only failures, not successes // Log only failures, not successes
if (!status.ok()) { if (!status.ok()) {
LOG_DEBUG("[RenderObjectsToBackground]", "ObjectDrawer failed: %s", std::string(status.message()).c_str()); 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 ClearTileObjects() { tile_objects_.clear(); }
void AddTileObject(const RoomObject& object) { void AddTileObject(const RoomObject& object) {
tile_objects_.push_back(object); tile_objects_.push_back(object);
MarkObjectsDirty();
} }
// Enhanced object manipulation (Phase 3) // Enhanced object manipulation (Phase 3)
@@ -234,9 +235,15 @@ class Room {
absl::Status UpdateObject(size_t index, const RoomObject& object); absl::Status UpdateObject(size_t index, const RoomObject& object);
absl::StatusOr<size_t> FindObjectAt(int x, int y, int layer) const; absl::StatusOr<size_t> FindObjectAt(int x, int y, int layer) const;
bool ValidateObject(const RoomObject& object) 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) { void RemoveTileObject(size_t index) {
if (index < tile_objects_.size()) { if (index < tile_objects_.size()) {
tile_objects_.erase(tile_objects_.begin() + index); tile_objects_.erase(tile_objects_.begin() + index);
MarkObjectsDirty();
} }
} }
size_t GetTileObjectCount() const { return tile_objects_.size(); } size_t GetTileObjectCount() const { return tile_objects_.size(); }
@@ -248,18 +255,49 @@ class Room {
// For undo/redo functionality // For undo/redo functionality
void SetTileObjects(const std::vector<RoomObject>& objects) { void SetTileObjects(const std::vector<RoomObject>& objects) {
tile_objects_ = objects; tile_objects_ = objects;
MarkObjectsDirty();
} }
// Public setters for LoadRoomFromRom function // Public setters for LoadRoomFromRom function
void SetBg2(background2 bg2) { bg2_ = bg2; } void SetBg2(background2 bg2) { bg2_ = bg2; }
void SetCollision(CollisionKey collision) { collision_ = collision; } void SetCollision(CollisionKey collision) { collision_ = collision; }
void SetIsLight(bool is_light) { is_light_ = is_light; } void SetIsLight(bool is_light) { is_light_ = is_light; }
void SetPalette(uint8_t palette) { this->palette = palette; } void SetPalette(uint8_t palette) {
void SetBlockset(uint8_t blockset) { this->blockset = blockset; } if (this->palette != palette) {
void SetSpriteset(uint8_t spriteset) { this->spriteset = spriteset; } this->palette = palette;
void SetEffect(EffectKey effect) { effect_ = effect; } MarkGraphicsDirty();
void SetTag1(TagKey tag1) { tag1_ = tag1; } }
void SetTag2(TagKey tag2) { tag2_ = tag2; } }
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) { void SetStaircasePlane(int index, uint8_t plane) {
if (index >= 0 && index < 4) staircase_plane_[index] = plane; if (index >= 0 && index < 4) staircase_plane_[index] = plane;
} }
@@ -321,12 +359,16 @@ class Room {
uint8_t floor1() const { return floor1_graphics_; } uint8_t floor1() const { return floor1_graphics_; }
uint8_t floor2() const { return floor2_graphics_; } uint8_t floor2() const { return floor2_graphics_; }
void set_floor1(uint8_t value) { void set_floor1(uint8_t value) {
floor1_graphics_ = value; if (floor1_graphics_ != value) {
// TODO: Trigger re-render if needed floor1_graphics_ = value;
MarkGraphicsDirty();
}
} }
void set_floor2(uint8_t value) { void set_floor2(uint8_t value) {
floor2_graphics_ = value; if (floor2_graphics_ != value) {
// TODO: Trigger re-render if needed floor2_graphics_ = value;
MarkGraphicsDirty();
}
} }
// Enhanced object parsing methods // Enhanced object parsing methods
void ParseObjectsFromLocation(int objects_location); void ParseObjectsFromLocation(int objects_location);
@@ -363,6 +405,23 @@ class Room {
bool is_dark_; bool is_dark_;
bool is_floor_ = true; 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 room_id_;
int animated_frame_; int animated_frame_;

View File

@@ -2,6 +2,7 @@
#include "absl/strings/str_format.h" #include "absl/strings/str_format.h"
#include "app/snes.h" #include "app/snes.h"
#include "app/zelda3/dungeon/dungeon_rom_addresses.h"
namespace yaze::zelda3 { 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)); uint16_t w3 = (uint16_t)(rom_data[pos + 6] | (rom_data[pos + 7] << 8));
tiles_.clear(); tiles_.clear();
tiles_.push_back(gfx::Tile16(gfx::WordToTileInfo(w0), gfx::WordToTileInfo(w1), tiles_.push_back(gfx::WordToTileInfo(w0));
gfx::WordToTileInfo(w2), gfx::WordToTileInfo(w3))); tiles_.push_back(gfx::WordToTileInfo(w1));
tiles_.push_back(gfx::WordToTileInfo(w2));
tiles_.push_back(gfx::WordToTileInfo(w3));
tile_count_ = 1; tile_count_ = 1;
tiles_loaded_ = true; tiles_loaded_ = true;
} }
@@ -118,7 +120,7 @@ absl::Status RoomObject::LoadTilesWithParser() {
return absl::OkStatus(); 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_) { if (!tiles_loaded_) {
const_cast<RoomObject*>(this)->EnsureTilesLoaded(); 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 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_) { if (!tiles_loaded_) {
const_cast<RoomObject*>(this)->EnsureTilesLoaded(); const_cast<RoomObject*>(this)->EnsureTilesLoaded();
} }

View File

@@ -84,14 +84,14 @@ class RoomObject {
absl::Status LoadTilesWithParser(); absl::Status LoadTilesWithParser();
// Getter for tiles // Getter for tiles
const std::vector<gfx::Tile16>& tiles() const { return tiles_; } const std::vector<gfx::TileInfo>& tiles() const { return tiles_; }
std::vector<gfx::Tile16>& mutable_tiles() { return tiles_; } std::vector<gfx::TileInfo>& mutable_tiles() { return tiles_; }
// Get tile data through Arena system - returns references, not copies // 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 // 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 // Get tile count without loading all tiles
int GetTileCount() const; int GetTileCount() const;
@@ -161,7 +161,7 @@ class RoomObject {
// Tile data storage - using Arena system for efficient memory management // Tile data storage - using Arena system for efficient memory management
// Instead of copying Tile16 vectors, we store references to Arena-managed data // 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 bool tiles_loaded_ = false;
mutable int tile_count_ = 0; mutable int tile_count_ = 0;
mutable int tile_data_ptr_ = -1; // Pointer to tile data in ROM 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 // Verify tile data was parsed correctly
for (const auto& tile : tiles) { for (const auto& tile : tiles) {
EXPECT_NE(tile.tile0_.id_, 0); EXPECT_NE(tile.id_, 0);
EXPECT_NE(tile.tile1_.id_, 0);
EXPECT_NE(tile.tile2_.id_, 0);
EXPECT_NE(tile.tile3_.id_, 0);
} }
} }