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:
@@ -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
|
||||
|
||||
@@ -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
@@ -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,
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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_;
|
||||
|
||||
@@ -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_);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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_;
|
||||
|
||||
|
||||
@@ -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 {
|
||||
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user