Add Debugger interface, RoomObject class
- Log instructions to debugger using experiment flag - Use BitmapManager for more functionality - Draw framebuffer and integrated debugger
This commit is contained in:
@@ -13,7 +13,8 @@ class ExperimentFlags {
|
||||
public:
|
||||
struct Flags {
|
||||
bool kDrawOverworldSprites = false;
|
||||
bool kUseBitmapManager = false;
|
||||
bool kUseBitmapManager = true;
|
||||
bool kLogInstructions = true;
|
||||
};
|
||||
|
||||
ExperimentFlags() = default;
|
||||
|
||||
@@ -19,7 +19,7 @@ void DungeonEditor::Update() {
|
||||
for (int i = 0; i < 0x100; i++) {
|
||||
rooms_.emplace_back(zelda3::dungeon::Room(i));
|
||||
rooms_[i].LoadHeader();
|
||||
rooms_[i].LoadRoomGraphics(rooms_[i].blockset);
|
||||
// rooms_[i].LoadRoomGraphics(rooms_[i].blockset);
|
||||
}
|
||||
is_loaded_ = true;
|
||||
}
|
||||
@@ -38,7 +38,7 @@ void DungeonEditor::Update() {
|
||||
ImGui::TableNextRow();
|
||||
ImGui::TableNextColumn();
|
||||
if (rom()->isLoaded()) {
|
||||
if (ImGuiID child_id = ImGui::GetID((void *)(intptr_t)9);
|
||||
if (ImGuiID child_id = ImGui::GetID((void*)(intptr_t)9);
|
||||
ImGui::BeginChild(child_id, ImGui::GetContentRegionAvail(), true,
|
||||
ImGuiWindowFlags_AlwaysVerticalScrollbar)) {
|
||||
int i = 0;
|
||||
@@ -56,6 +56,9 @@ void DungeonEditor::Update() {
|
||||
ImGui::TableNextColumn();
|
||||
DrawDungeonTabView();
|
||||
ImGui::TableNextColumn();
|
||||
if (ImGui::Button("dungeon object renderer")) {
|
||||
object_renderer_.RenderObjectsAsBitmaps();
|
||||
}
|
||||
DrawTileSelector();
|
||||
ImGui::EndTable();
|
||||
}
|
||||
@@ -150,13 +153,13 @@ void DungeonEditor::DrawDungeonCanvas(int room_id) {
|
||||
|
||||
canvas_.DrawBackground();
|
||||
canvas_.DrawContextMenu();
|
||||
canvas_.DrawBitmap(rooms_[room_id].current_graphics_, 2, is_loaded_);
|
||||
canvas_.DrawGrid();
|
||||
canvas_.DrawOverlay();
|
||||
}
|
||||
|
||||
void DungeonEditor::DrawToolset() {
|
||||
if (ImGui::BeginTable("DWToolset", 9, toolset_table_flags_, ImVec2(0, 0))) {
|
||||
if (ImGui::BeginTable("DWToolset", 9, ImGuiTableFlags_SizingFixedFit,
|
||||
ImVec2(0, 0))) {
|
||||
ImGui::TableSetupColumn("#undoTool");
|
||||
ImGui::TableSetupColumn("#redoTool");
|
||||
ImGui::TableSetupColumn("#history");
|
||||
@@ -209,7 +212,7 @@ void DungeonEditor::DrawRoomGraphics() {
|
||||
void DungeonEditor::DrawTileSelector() {
|
||||
if (ImGui::BeginTabBar("##TabBar", ImGuiTabBarFlags_FittingPolicyScroll)) {
|
||||
if (ImGui::BeginTabItem("Room Graphics")) {
|
||||
if (ImGuiID child_id = ImGui::GetID((void *)(intptr_t)3);
|
||||
if (ImGuiID child_id = ImGui::GetID((void*)(intptr_t)3);
|
||||
ImGui::BeginChild(child_id, ImGui::GetContentRegionAvail(), true,
|
||||
ImGuiWindowFlags_AlwaysVerticalScrollbar)) {
|
||||
DrawRoomGraphics();
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
#include "app/gui/icons.h"
|
||||
#include "app/rom.h"
|
||||
#include "zelda3/dungeon/room.h"
|
||||
#include "zelda3/dungeon/room_object.h"
|
||||
|
||||
namespace yaze {
|
||||
namespace app {
|
||||
@@ -32,6 +33,7 @@ class DungeonEditor : public SharedROM {
|
||||
ImVector<int> active_rooms_;
|
||||
|
||||
std::vector<zelda3::dungeon::Room> rooms_;
|
||||
zelda3::dungeon::DungeonObjectRenderer object_renderer_;
|
||||
|
||||
gui::Canvas canvas_;
|
||||
gui::Canvas room_gfx_canvas_;
|
||||
|
||||
@@ -187,10 +187,6 @@ void MasterEditor::DrawFileMenu() {
|
||||
static bool save_as_menu = false;
|
||||
|
||||
if (ImGui::BeginMenu("File")) {
|
||||
if (ImGui::MenuItem("New", "Ctrl+N")) {
|
||||
// TODO: Implement new ROM creation.
|
||||
}
|
||||
|
||||
if (ImGui::MenuItem("Open", "Ctrl+O")) {
|
||||
ImGuiFileDialog::Instance()->OpenDialog("ChooseFileDlgKey", "Open ROM",
|
||||
".sfc,.smc", ".");
|
||||
@@ -209,10 +205,13 @@ void MasterEditor::DrawFileMenu() {
|
||||
if (ImGui::BeginMenu("Options")) {
|
||||
ImGui::MenuItem("Backup ROM", "", &backup_rom_);
|
||||
ImGui::Separator();
|
||||
ImGui::Text("Experiment Flags");
|
||||
ImGui::Checkbox("Enable Overworld Sprites",
|
||||
&mutable_flags()->kDrawOverworldSprites);
|
||||
ImGui::Checkbox("Use Bitmap Manager",
|
||||
&mutable_flags()->kUseBitmapManager);
|
||||
ImGui::Checkbox("Log Instructions to Debugger",
|
||||
&mutable_flags()->kLogInstructions);
|
||||
ImGui::EndMenu();
|
||||
}
|
||||
|
||||
|
||||
@@ -32,7 +32,8 @@ absl::Status OverworldEditor::Update() {
|
||||
// Initialize overworld graphics, maps, and palettes
|
||||
if (rom()->isLoaded() && !all_gfx_loaded_) {
|
||||
RETURN_IF_ERROR(LoadGraphics())
|
||||
tile16_editor_.InitBlockset(tile16_blockset_bmp_);
|
||||
tile16_editor_.InitBlockset(tile16_blockset_bmp_, tile16_individual_,
|
||||
tile8_individual_);
|
||||
gfx_group_editor_.InitBlockset(tile16_blockset_bmp_);
|
||||
all_gfx_loaded_ = true;
|
||||
}
|
||||
@@ -349,10 +350,10 @@ void OverworldEditor::RenderUpdatedMapBitmap(const ImVec2 &click_position,
|
||||
void OverworldEditor::QueueROMChanges(int index, ushort new_tile16) {
|
||||
// Store the changes made by the user to the ROM (or project file)
|
||||
rom()->QueueChanges([&]() {
|
||||
overworld_.SaveOverworldMaps();
|
||||
PRINT_IF_ERROR(overworld_.SaveOverworldMaps());
|
||||
if (!overworld_.CreateTile32Tilemap()) {
|
||||
// overworld_.SaveMap16Tiles();
|
||||
overworld_.SaveMap32Tiles();
|
||||
PRINT_IF_ERROR(overworld_.SaveMap32Tiles());
|
||||
} else {
|
||||
std::cout << "Failed to create tile32 tilemap" << std::endl;
|
||||
}
|
||||
@@ -410,14 +411,16 @@ void OverworldEditor::DrawTile8Selector() {
|
||||
ImVec2(0x100 + 1, kNumSheetsToLoad * 0x40 + 1));
|
||||
graphics_bin_canvas_.DrawContextMenu();
|
||||
if (all_gfx_loaded_) {
|
||||
for (const auto &[key, value] : graphics_bin_) {
|
||||
// for (const auto &[key, value] : graphics_bin_) {
|
||||
for (auto &[key, value] : rom()->BitmapManager()) {
|
||||
int offset = 0x40 * (key + 1);
|
||||
int top_left_y = graphics_bin_canvas_.GetZeroPoint().y + 2;
|
||||
if (key >= 1) {
|
||||
top_left_y = graphics_bin_canvas_.GetZeroPoint().y + 0x40 * key;
|
||||
}
|
||||
auto texture = value.get()->texture();
|
||||
graphics_bin_canvas_.GetDrawList()->AddImage(
|
||||
(void *)value.texture(),
|
||||
(void *)texture,
|
||||
ImVec2(graphics_bin_canvas_.GetZeroPoint().x + 2, top_left_y),
|
||||
ImVec2(graphics_bin_canvas_.GetZeroPoint().x + 0x100,
|
||||
graphics_bin_canvas_.GetZeroPoint().y + offset));
|
||||
@@ -521,7 +524,7 @@ absl::Status OverworldEditor::LoadGraphics() {
|
||||
}
|
||||
|
||||
if (flags()->kDrawOverworldSprites) {
|
||||
LoadSpriteGraphics();
|
||||
RETURN_IF_ERROR(LoadSpriteGraphics());
|
||||
}
|
||||
|
||||
return absl::OkStatus();
|
||||
|
||||
@@ -158,6 +158,9 @@ class OverworldEditor : public Editor,
|
||||
std::vector<Bytes> tile16_individual_data_;
|
||||
std::vector<gfx::Bitmap> tile16_individual_;
|
||||
|
||||
std::vector<Bytes> tile8_individual_data_;
|
||||
std::vector<gfx::Bitmap> tile8_individual_;
|
||||
|
||||
Tile16Editor tile16_editor_;
|
||||
GfxGroupEditor gfx_group_editor_;
|
||||
PaletteEditor palette_editor_;
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
#include <cstdint>
|
||||
#include <iomanip>
|
||||
#include <iostream>
|
||||
#include <sstream>
|
||||
#include <vector>
|
||||
|
||||
namespace yaze {
|
||||
@@ -1092,26 +1093,57 @@ void CPU::ExecuteInstruction(uint8_t opcode) {
|
||||
break;
|
||||
}
|
||||
|
||||
// Log the address and opcode.
|
||||
std::cout << "$" << std::uppercase << std::setw(2) << std::setfill('0')
|
||||
<< static_cast<int>(DB) << ":" << std::hex << PC << ": 0x"
|
||||
<< std::hex << static_cast<int>(opcode) << " "
|
||||
<< opcode_to_mnemonic.at(opcode) << " ";
|
||||
if (flags()->kLogInstructions) {
|
||||
std::ostringstream oss;
|
||||
oss << "$" << std::uppercase << std::setw(2) << std::setfill('0')
|
||||
<< static_cast<int>(DB) << ":" << std::hex << PC << ": 0x" << std::hex
|
||||
<< static_cast<int>(opcode) << " " << opcode_to_mnemonic.at(opcode)
|
||||
<< " ";
|
||||
|
||||
// Log the operand.
|
||||
if (operand) {
|
||||
if (immediate) {
|
||||
std::cout << "#";
|
||||
}
|
||||
std::cout << "$";
|
||||
if (accumulator_mode) {
|
||||
std::cout << std::hex << std::setw(2) << std::setfill('0') << operand;
|
||||
} else {
|
||||
std::cout << std::hex << std::setw(4) << std::setfill('0')
|
||||
// Log the operand.
|
||||
std::string ops;
|
||||
if (operand) {
|
||||
if (immediate) {
|
||||
ops += "#";
|
||||
}
|
||||
std::ostringstream oss_ops;
|
||||
oss_ops << "$";
|
||||
if (accumulator_mode) {
|
||||
oss_ops << std::hex << std::setw(2) << std::setfill('0')
|
||||
<< static_cast<int>(operand);
|
||||
} else {
|
||||
oss_ops << std::hex << std::setw(4) << std::setfill('0')
|
||||
<< static_cast<int>(operand);
|
||||
}
|
||||
ops = oss_ops.str();
|
||||
}
|
||||
|
||||
oss << ops << std::endl;
|
||||
|
||||
InstructionEntry entry(PC, opcode, ops, oss.str());
|
||||
instruction_log_.push_back(entry);
|
||||
} else {
|
||||
// Log the address and opcode.
|
||||
std::cout << "$" << std::uppercase << std::setw(2) << std::setfill('0')
|
||||
<< static_cast<int>(DB) << ":" << std::hex << PC << ": 0x"
|
||||
<< std::hex << static_cast<int>(opcode) << " "
|
||||
<< opcode_to_mnemonic.at(opcode) << " ";
|
||||
|
||||
// Log the operand.
|
||||
if (operand) {
|
||||
if (immediate) {
|
||||
std::cout << "#";
|
||||
}
|
||||
std::cout << "$";
|
||||
if (accumulator_mode) {
|
||||
std::cout << std::hex << std::setw(2) << std::setfill('0') << operand;
|
||||
} else {
|
||||
std::cout << std::hex << std::setw(4) << std::setfill('0')
|
||||
<< static_cast<int>(operand);
|
||||
}
|
||||
}
|
||||
std::cout << std::endl;
|
||||
}
|
||||
std::cout << std::endl;
|
||||
}
|
||||
|
||||
// Interrupt Vectors
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
#include <unordered_map>
|
||||
#include <vector>
|
||||
|
||||
#include "app/core/common.h"
|
||||
#include "app/emu/clock.h"
|
||||
#include "app/emu/debug/log.h"
|
||||
#include "app/emu/memory/memory.h"
|
||||
@@ -14,6 +15,25 @@ namespace yaze {
|
||||
namespace app {
|
||||
namespace emu {
|
||||
|
||||
class InstructionEntry {
|
||||
public:
|
||||
// Constructor
|
||||
InstructionEntry(uint32_t addr, uint8_t op, const std::string& ops,
|
||||
const std::string& instr)
|
||||
: address(addr), opcode(op), operands(ops), instruction(instr) {}
|
||||
|
||||
// Getters for the class members
|
||||
uint32_t GetAddress() const { return address; }
|
||||
uint8_t GetOpcode() const { return opcode; }
|
||||
const std::string& GetOperands() const { return operands; }
|
||||
const std::string& GetInstruction() const { return instruction; }
|
||||
|
||||
uint32_t address; // Memory address of the instruction
|
||||
uint8_t opcode; // Opcode of the instruction
|
||||
std::string operands; // Operand(s) of the instruction, if any
|
||||
std::string instruction; // Human-readable instruction text
|
||||
};
|
||||
|
||||
const std::unordered_map<uint8_t, std::string> opcode_to_mnemonic = {
|
||||
{0x00, "BRK"}, {0x01, "ORA"}, {0x02, "COP"}, {0x03, "ORA"}, {0x04, "TSB"},
|
||||
{0x05, "ORA"}, {0x06, "ASL"}, {0x07, "ORA"}, {0x08, "PHP"}, {0x09, "ORA"},
|
||||
@@ -72,19 +92,20 @@ const std::unordered_map<uint8_t, std::string> opcode_to_mnemonic = {
|
||||
|
||||
const int kCpuClockSpeed = 21477272; // 21.477272 MHz
|
||||
|
||||
class CPU : public Memory, public Loggable {
|
||||
class CPU : public Memory, public Loggable, public core::ExperimentFlags {
|
||||
public:
|
||||
explicit CPU(Memory& mem, Clock& vclock) : memory(mem), clock(vclock) {}
|
||||
|
||||
void Init() {
|
||||
clock.SetFrequency(kCpuClockSpeed);
|
||||
memory.ClearMemory();
|
||||
}
|
||||
|
||||
void Update();
|
||||
void ExecuteInstruction(uint8_t opcode);
|
||||
void HandleInterrupts();
|
||||
|
||||
std::vector<InstructionEntry> instruction_log_;
|
||||
|
||||
// ==========================================================================
|
||||
// Addressing Modes
|
||||
|
||||
|
||||
@@ -13,7 +13,7 @@ namespace emu {
|
||||
|
||||
void Emulator::Run() {
|
||||
// Initialize the emulator if a ROM is loaded
|
||||
if (!snes_.running()) {
|
||||
if (!snes_.running() && loading_) {
|
||||
if (rom()->isLoaded()) {
|
||||
snes_.Init(*rom());
|
||||
running_ = true;
|
||||
@@ -23,13 +23,17 @@ void Emulator::Run() {
|
||||
// Render the emulator output
|
||||
RenderNavBar();
|
||||
|
||||
RenderEmulator();
|
||||
|
||||
if (running_) {
|
||||
// Handle user input events
|
||||
HandleEvents();
|
||||
// Update the emulator state
|
||||
UpdateEmulator();
|
||||
|
||||
RenderEmulator();
|
||||
}
|
||||
|
||||
if (debugger_) {
|
||||
RenderDebugger();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -38,14 +42,32 @@ void Emulator::RenderEmulator() {
|
||||
// You can use the ImGui::Image function to display the emulator output as a
|
||||
// texture
|
||||
// ...
|
||||
ImVec2 size = ImVec2(320, 240);
|
||||
if (snes_.running()) {
|
||||
ImGui::Image((void*)snes_.Ppu().GetScreen()->texture(), size, ImVec2(0, 0),
|
||||
ImVec2(1, 1));
|
||||
ImGui::Separator();
|
||||
} else {
|
||||
ImGui::Dummy(size);
|
||||
ImGui::Separator();
|
||||
ImGui::Text("Emulator output not available.");
|
||||
}
|
||||
}
|
||||
|
||||
void Emulator::RenderNavBar() {
|
||||
MENU_BAR()
|
||||
|
||||
if (ImGui::BeginMenu("Game")) {
|
||||
MENU_ITEM("Power Off") {}
|
||||
MENU_ITEM("Pause") {}
|
||||
MENU_ITEM("Load ROM") { loading_ = true; }
|
||||
MENU_ITEM("Power Off") {
|
||||
running_ = false;
|
||||
loading_ = false;
|
||||
debugger_ = false;
|
||||
}
|
||||
MENU_ITEM("Pause") {
|
||||
running_ = false;
|
||||
debugger_ = false;
|
||||
}
|
||||
MENU_ITEM("Reset") {}
|
||||
|
||||
MENU_ITEM("Save State") {}
|
||||
@@ -56,7 +78,15 @@ void Emulator::RenderNavBar() {
|
||||
|
||||
if (ImGui::BeginMenu("Debug")) {
|
||||
ImGui::MenuItem("PPU Register Viewer", nullptr, &show_ppu_reg_viewer_);
|
||||
MENU_ITEM("Debugger") {}
|
||||
MENU_ITEM("Debugger") { debugger_ = !debugger_; }
|
||||
if (ImGui::MenuItem("Integrated Debugger", nullptr,
|
||||
&integrated_debugger_mode_)) {
|
||||
separate_debugger_mode_ = !integrated_debugger_mode_;
|
||||
}
|
||||
if (ImGui::MenuItem("Separate Debugger Windows", nullptr,
|
||||
&separate_debugger_mode_)) {
|
||||
integrated_debugger_mode_ = !separate_debugger_mode_;
|
||||
}
|
||||
MENU_ITEM("Memory Viewer") {}
|
||||
MENU_ITEM("Tile Viewer") {}
|
||||
ImGui::EndMenu();
|
||||
@@ -82,6 +112,102 @@ void Emulator::UpdateEmulator() {
|
||||
snes_.Run();
|
||||
}
|
||||
|
||||
void Emulator::RenderDebugger() {
|
||||
// Define a lambda with the actual debugger
|
||||
auto debugger = [&]() {
|
||||
if (ImGui::BeginTable(
|
||||
"DebugTable", 2,
|
||||
ImGuiTableFlags_Resizable | ImGuiTableFlags_ScrollY)) {
|
||||
ImGui::TableNextColumn();
|
||||
RenderCpuState(snes_.Cpu());
|
||||
|
||||
ImGui::TableNextColumn();
|
||||
RenderCPUInstructionLog(snes_.Cpu().instruction_log_);
|
||||
|
||||
ImGui::EndTable();
|
||||
}
|
||||
};
|
||||
|
||||
if (integrated_debugger_mode_) {
|
||||
debugger();
|
||||
} else if (separate_debugger_mode_) {
|
||||
ImGui::Begin("Debugger");
|
||||
debugger();
|
||||
ImGui::End();
|
||||
}
|
||||
}
|
||||
|
||||
void Emulator::RenderCpuState(CPU& cpu) {
|
||||
ImGui::Columns(2, "RegistersColumns");
|
||||
ImGui::Separator();
|
||||
ImGui::Text("A: 0x%04X", cpu.A);
|
||||
ImGui::NextColumn();
|
||||
ImGui::Text("D: 0x%04X", cpu.D);
|
||||
ImGui::NextColumn();
|
||||
ImGui::Text("X: 0x%04X", cpu.X);
|
||||
ImGui::NextColumn();
|
||||
ImGui::Text("DB: 0x%02X", cpu.DB);
|
||||
ImGui::NextColumn();
|
||||
ImGui::Text("Y: 0x%04X", cpu.Y);
|
||||
ImGui::NextColumn();
|
||||
ImGui::Text("PB: 0x%02X", cpu.PB);
|
||||
ImGui::NextColumn();
|
||||
ImGui::Text("PC: 0x%04X", cpu.PC);
|
||||
ImGui::NextColumn();
|
||||
ImGui::Text("E: %d", cpu.E);
|
||||
ImGui::NextColumn();
|
||||
ImGui::Columns(1);
|
||||
ImGui::Separator();
|
||||
// Call Stack
|
||||
if (ImGui::CollapsingHeader("Call Stack")) {
|
||||
// For each return address in the call stack:
|
||||
ImGui::Text("Return Address: 0x%08X", 0xFFFFFF); // Placeholder
|
||||
}
|
||||
}
|
||||
|
||||
void Emulator::RenderMemoryViewer() {
|
||||
// Render memory viewer
|
||||
}
|
||||
|
||||
namespace {
|
||||
bool ShouldDisplay(const InstructionEntry& entry, const char* filter,
|
||||
bool showAll) {
|
||||
// Implement logic to determine if the entry should be displayed based on the
|
||||
// filter and showAll flag
|
||||
return true;
|
||||
}
|
||||
} // namespace
|
||||
|
||||
void Emulator::RenderCPUInstructionLog(
|
||||
const std::vector<InstructionEntry>& instructionLog) {
|
||||
if (ImGui::CollapsingHeader("CPU Instruction Log")) {
|
||||
// Filtering options
|
||||
static char filterBuf[256];
|
||||
ImGui::InputText("Filter", filterBuf, IM_ARRAYSIZE(filterBuf));
|
||||
ImGui::SameLine();
|
||||
if (ImGui::Button("Clear")) { /* Clear filter logic */
|
||||
}
|
||||
|
||||
// Toggle for showing all opcodes
|
||||
static bool showAllOpcodes = false;
|
||||
ImGui::Checkbox("Show All Opcodes", &showAllOpcodes);
|
||||
|
||||
// Instruction list
|
||||
ImGui::BeginChild("InstructionList");
|
||||
for (const auto& entry : instructionLog) {
|
||||
if (ShouldDisplay(entry, filterBuf, showAllOpcodes)) {
|
||||
if (ImGui::Selectable(absl::StrFormat("%04X: %02X %s %s", entry.address,
|
||||
entry.opcode, entry.operands,
|
||||
entry.instruction)
|
||||
.c_str())) {
|
||||
// Logic to handle click (e.g., jump to address, set breakpoint)
|
||||
}
|
||||
}
|
||||
}
|
||||
ImGui::EndChild();
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace emu
|
||||
} // namespace app
|
||||
} // namespace yaze
|
||||
|
||||
@@ -29,11 +29,22 @@ class Emulator : public SharedROM {
|
||||
// Updates the emulator state (CPU, PPU, APU, etc.)
|
||||
void UpdateEmulator();
|
||||
|
||||
void RenderDebugger();
|
||||
void RenderCpuState(CPU& cpu);
|
||||
void RenderMemoryViewer();
|
||||
|
||||
void RenderCPUInstructionLog(
|
||||
const std::vector<InstructionEntry>& instructionLog);
|
||||
|
||||
// Member variables to store internal state and resources
|
||||
SNES snes_;
|
||||
|
||||
bool running_ = false;
|
||||
bool debugger_ = false;
|
||||
bool loading_ = false;
|
||||
bool show_ppu_reg_viewer_ = false;
|
||||
bool integrated_debugger_mode_ = true;
|
||||
bool separate_debugger_mode_ = false;
|
||||
};
|
||||
|
||||
} // namespace emu
|
||||
|
||||
149
src/app/emu/memory/mock_memory.h
Normal file
149
src/app/emu/memory/mock_memory.h
Normal file
@@ -0,0 +1,149 @@
|
||||
#ifndef YAZE_TEST_MOCK_MOCK_MEMORY_H
|
||||
#define YAZE_TEST_MOCK_MOCK_MEMORY_H
|
||||
|
||||
#include <gmock/gmock.h>
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
#include "app/emu/clock.h"
|
||||
#include "app/emu/cpu.h"
|
||||
#include "app/emu/memory/memory.h"
|
||||
|
||||
using yaze::app::emu::Clock;
|
||||
using yaze::app::emu::CPU;
|
||||
using yaze::app::emu::Memory;
|
||||
|
||||
class MockClock : public Clock {
|
||||
public:
|
||||
MOCK_METHOD(void, UpdateClock, (double delta), (override));
|
||||
MOCK_METHOD(unsigned long long, GetCycleCount, (), (const, override));
|
||||
MOCK_METHOD(void, ResetAccumulatedTime, (), (override));
|
||||
MOCK_METHOD(void, SetFrequency, (float new_frequency), (override));
|
||||
MOCK_METHOD(float, GetFrequency, (), (const, override));
|
||||
};
|
||||
|
||||
class MockMemory : public Memory {
|
||||
public:
|
||||
MOCK_CONST_METHOD1(ReadByte, uint8_t(uint16_t address));
|
||||
MOCK_CONST_METHOD1(ReadWord, uint16_t(uint16_t address));
|
||||
MOCK_CONST_METHOD1(ReadWordLong, uint32_t(uint16_t address));
|
||||
MOCK_METHOD(std::vector<uint8_t>, ReadByteVector,
|
||||
(uint16_t address, uint16_t length), (const, override));
|
||||
|
||||
MOCK_METHOD2(WriteByte, void(uint32_t address, uint8_t value));
|
||||
MOCK_METHOD2(WriteWord, void(uint32_t address, uint16_t value));
|
||||
|
||||
MOCK_METHOD1(PushByte, void(uint8_t value));
|
||||
MOCK_METHOD0(PopByte, uint8_t());
|
||||
MOCK_METHOD1(PushWord, void(uint16_t value));
|
||||
MOCK_METHOD0(PopWord, uint16_t());
|
||||
MOCK_METHOD1(PushLong, void(uint32_t value));
|
||||
MOCK_METHOD0(PopLong, uint32_t());
|
||||
|
||||
MOCK_CONST_METHOD0(SP, int16_t());
|
||||
MOCK_METHOD1(SetSP, void(int16_t value));
|
||||
|
||||
MOCK_METHOD1(SetMemory, void(const std::vector<uint8_t>& data));
|
||||
MOCK_METHOD1(LoadData, void(const std::vector<uint8_t>& data));
|
||||
|
||||
MOCK_METHOD0(ClearMemory, void());
|
||||
|
||||
MOCK_CONST_METHOD1(at, uint8_t(int i));
|
||||
uint8_t operator[](int i) const override { return at(i); }
|
||||
|
||||
void SetMemoryContents(const std::vector<uint8_t>& data) {
|
||||
memory_.resize(64000);
|
||||
std::copy(data.begin(), data.end(), memory_.begin());
|
||||
}
|
||||
|
||||
void SetMemoryContents(const std::vector<uint16_t>& data) {
|
||||
memory_.resize(64000);
|
||||
int i = 0;
|
||||
for (const auto& each : data) {
|
||||
memory_[i] = each & 0xFF;
|
||||
memory_[i + 1] = (each >> 8) & 0xFF;
|
||||
i += 2;
|
||||
}
|
||||
}
|
||||
|
||||
void InsertMemory(const uint64_t address, const std::vector<uint8_t>& data) {
|
||||
int i = 0;
|
||||
for (const auto& each : data) {
|
||||
memory_[address + i] = each;
|
||||
i++;
|
||||
}
|
||||
}
|
||||
|
||||
void Init() {
|
||||
ON_CALL(*this, ReadByte(::testing::_))
|
||||
.WillByDefault(
|
||||
[this](uint16_t address) { return memory_.at(address); });
|
||||
ON_CALL(*this, ReadWord(::testing::_))
|
||||
.WillByDefault([this](uint16_t address) {
|
||||
return static_cast<uint16_t>(memory_.at(address)) |
|
||||
(static_cast<uint16_t>(memory_.at(address + 1)) << 8);
|
||||
});
|
||||
ON_CALL(*this, ReadWordLong(::testing::_))
|
||||
.WillByDefault([this](uint16_t address) {
|
||||
return static_cast<uint32_t>(memory_.at(address)) |
|
||||
(static_cast<uint32_t>(memory_.at(address + 1)) << 8) |
|
||||
(static_cast<uint32_t>(memory_.at(address + 2)) << 16);
|
||||
});
|
||||
ON_CALL(*this, ReadByteVector(::testing::_, ::testing::_))
|
||||
.WillByDefault([this](uint16_t address, uint16_t length) {
|
||||
std::vector<uint8_t> data;
|
||||
for (int i = 0; i < length; i++) {
|
||||
data.push_back(memory_.at(address + i));
|
||||
}
|
||||
return data;
|
||||
});
|
||||
ON_CALL(*this, WriteByte(::testing::_, ::testing::_))
|
||||
.WillByDefault([this](uint32_t address, uint8_t value) {
|
||||
memory_[address] = value;
|
||||
});
|
||||
ON_CALL(*this, WriteWord(::testing::_, ::testing::_))
|
||||
.WillByDefault([this](uint32_t address, uint16_t value) {
|
||||
memory_[address] = value & 0xFF;
|
||||
memory_[address + 1] = (value >> 8) & 0xFF;
|
||||
});
|
||||
ON_CALL(*this, PushByte(::testing::_)).WillByDefault([this](uint8_t value) {
|
||||
memory_.at(SP_) = value;
|
||||
});
|
||||
ON_CALL(*this, PopByte()).WillByDefault([this]() {
|
||||
uint8_t value = memory_.at(SP_);
|
||||
this->SetSP(SP_ + 1);
|
||||
return value;
|
||||
});
|
||||
ON_CALL(*this, PushWord(::testing::_))
|
||||
.WillByDefault([this](uint16_t value) {
|
||||
memory_.at(SP_) = value & 0xFF;
|
||||
memory_.at(SP_ + 1) = (value >> 8) & 0xFF;
|
||||
});
|
||||
ON_CALL(*this, PopWord()).WillByDefault([this]() {
|
||||
uint16_t value = static_cast<uint16_t>(memory_.at(SP_)) |
|
||||
(static_cast<uint16_t>(memory_.at(SP_ + 1)) << 8);
|
||||
this->SetSP(SP_ + 2);
|
||||
return value;
|
||||
});
|
||||
ON_CALL(*this, PushLong(::testing::_))
|
||||
.WillByDefault([this](uint32_t value) {
|
||||
memory_.at(SP_) = value & 0xFF;
|
||||
memory_.at(SP_ + 1) = (value >> 8) & 0xFF;
|
||||
memory_.at(SP_ + 2) = (value >> 16) & 0xFF;
|
||||
});
|
||||
ON_CALL(*this, PopLong()).WillByDefault([this]() {
|
||||
uint32_t value = static_cast<uint32_t>(memory_.at(SP_)) |
|
||||
(static_cast<uint32_t>(memory_.at(SP_ + 1)) << 8) |
|
||||
(static_cast<uint32_t>(memory_.at(SP_ + 2)) << 16);
|
||||
this->SetSP(SP_ + 3);
|
||||
return value;
|
||||
});
|
||||
ON_CALL(*this, ClearMemory()).WillByDefault([this]() {
|
||||
memory_.resize(64000, 0x00);
|
||||
});
|
||||
}
|
||||
|
||||
std::vector<uint8_t> memory_;
|
||||
uint16_t SP_ = 0x01FF;
|
||||
};
|
||||
|
||||
#endif // YAZE_TEST_MOCK_MOCK_MEMORY_H
|
||||
@@ -63,6 +63,9 @@ class SNES : public DMA {
|
||||
|
||||
bool running() const { return running_; }
|
||||
|
||||
auto Cpu() -> CPU& { return cpu; }
|
||||
auto Ppu() -> PPU& { return ppu; }
|
||||
|
||||
private:
|
||||
void WriteToRegister(uint16_t address, uint8_t value) {
|
||||
memory_.WriteByte(address, value);
|
||||
|
||||
@@ -46,7 +46,11 @@ void PPU::UpdateInternalState(int cycles) {
|
||||
void PPU::RenderScanline() {
|
||||
for (int y = 0; y < 240; ++y) {
|
||||
for (int x = 0; x < 256; ++x) {
|
||||
frame_buffer_[y * 256 + x] = (x + y) % 256; // Simple gradient pattern
|
||||
// Calculate the color index based on the x and y coordinates
|
||||
uint8_t color_index = (x + y) % 8;
|
||||
|
||||
// Set the pixel in the frame buffer to the calculated color index
|
||||
frame_buffer_[y * 256 + x] = color_index;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -267,7 +271,12 @@ void PPU::ApplyEffects() {}
|
||||
|
||||
void PPU::ComposeLayers() {}
|
||||
|
||||
void PPU::DisplayFrameBuffer() {}
|
||||
void PPU::DisplayFrameBuffer() {
|
||||
if (!screen_->IsActive()) {
|
||||
screen_->Create(256, 240, 24, frame_buffer_);
|
||||
rom()->RenderBitmap(screen_.get());
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace emu
|
||||
} // namespace app
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
#include "app/emu/clock.h"
|
||||
#include "app/emu/memory/memory.h"
|
||||
#include "app/emu/video/ppu_registers.h"
|
||||
#include "app/rom.h"
|
||||
|
||||
namespace yaze {
|
||||
namespace app {
|
||||
@@ -234,7 +235,7 @@ struct BackgroundLayer {
|
||||
|
||||
const int kPpuClockSpeed = 5369318; // 5.369318 MHz
|
||||
|
||||
class PPU : public Observer {
|
||||
class PPU : public Observer, public SharedROM {
|
||||
public:
|
||||
// Initializes the PPU with the necessary resources and dependencies
|
||||
PPU(Memory& memory, Clock& clock) : memory_(memory), clock_(clock) {}
|
||||
@@ -243,6 +244,8 @@ class PPU : public Observer {
|
||||
void Init() {
|
||||
clock_.SetFrequency(kPpuClockSpeed);
|
||||
frame_buffer_.resize(256 * 240, 0);
|
||||
screen_ = std::make_shared<gfx::Bitmap>(256, 240, 8, 0x100);
|
||||
screen_->SetActive(false);
|
||||
}
|
||||
|
||||
// Resets the PPU to its initial state
|
||||
@@ -261,6 +264,8 @@ class PPU : public Observer {
|
||||
// Returns the pixel data for the current frame
|
||||
const std::vector<uint8_t>& GetFrameBuffer() const { return frame_buffer_; }
|
||||
|
||||
auto GetScreen() const { return screen_; }
|
||||
|
||||
private:
|
||||
// Updates internal state based on PPU register settings
|
||||
void UpdateModeSettings();
|
||||
@@ -300,6 +305,7 @@ class PPU : public Observer {
|
||||
std::vector<SpriteAttributes> sprites_;
|
||||
std::vector<uint8_t> tile_data_;
|
||||
std::vector<uint8_t> frame_buffer_;
|
||||
std::shared_ptr<gfx::Bitmap> screen_;
|
||||
|
||||
uint16_t oam_address_;
|
||||
uint16_t tile_data_size_;
|
||||
|
||||
@@ -37,9 +37,9 @@ Bitmap::Bitmap(int width, int height, int depth, uchar *data, int data_size) {
|
||||
Create(width, height, depth, data, data_size);
|
||||
}
|
||||
|
||||
Bitmap::Bitmap(int width, int height, int depth, Bytes data) {
|
||||
Create(width, height, depth, data);
|
||||
}
|
||||
// Bitmap::Bitmap(int width, int height, int depth, Bytes data) {
|
||||
// Create(width, height, depth, data);
|
||||
// }
|
||||
|
||||
// Pass raw pixel data directly to the surface
|
||||
void Bitmap::Create(int width, int height, int depth, uchar *data) {
|
||||
@@ -89,7 +89,7 @@ void Bitmap::Create(int width, int height, int depth, uchar *data, int size) {
|
||||
GrayscalePalette(surface_->format->palette);
|
||||
}
|
||||
|
||||
void Bitmap::Create(int width, int height, int depth, Bytes data) {
|
||||
void Bitmap::Create(int width, int height, int depth, const Bytes &data) {
|
||||
active_ = true;
|
||||
width_ = width;
|
||||
height_ = height;
|
||||
|
||||
@@ -20,15 +20,33 @@ namespace gfx {
|
||||
class Bitmap {
|
||||
public:
|
||||
Bitmap() = default;
|
||||
|
||||
Bitmap(int width, int height, int depth, int data_size);
|
||||
Bitmap(int width, int height, int depth, Bytes data);
|
||||
// Bitmap(int width, int height, int depth, Bytes data);
|
||||
|
||||
Bitmap(int width, int height, int depth, const Bytes &data)
|
||||
: width_(width), height_(height), depth_(depth), data_(data) {
|
||||
CreateTextureFromData();
|
||||
}
|
||||
|
||||
// Function to create texture from pixel data
|
||||
void CreateTextureFromData() {
|
||||
// Safely create the texture from the raw data
|
||||
// Assuming a function exists that converts Bytes to the appropriate format
|
||||
auto raw_pixel_data = data_;
|
||||
surface_ = std::shared_ptr<SDL_Surface>(
|
||||
SDL_CreateRGBSurfaceWithFormat(0, width_, height_, depth_,
|
||||
SDL_PIXELFORMAT_INDEX8),
|
||||
SDL_Surface_Deleter());
|
||||
surface_->pixels = data_.data();
|
||||
}
|
||||
|
||||
[[deprecated]] Bitmap(int width, int height, int depth, uchar *data);
|
||||
[[deprecated]] Bitmap(int width, int height, int depth, uchar *data,
|
||||
int data_size);
|
||||
|
||||
void Create(int width, int height, int depth, int data_size);
|
||||
void Create(int width, int height, int depth, Bytes data);
|
||||
void Create(int width, int height, int depth, const Bytes &data);
|
||||
|
||||
[[deprecated]] void Create(int width, int height, int depth, uchar *data);
|
||||
[[deprecated]] void Create(int width, int height, int depth, uchar *data,
|
||||
@@ -73,8 +91,7 @@ class Bitmap {
|
||||
height_ = 0;
|
||||
depth_ = 0;
|
||||
data_size_ = 0;
|
||||
palette_.Clear(); // assuming there's a Clear() method or similar on
|
||||
// SNESPalette
|
||||
palette_.Clear();
|
||||
}
|
||||
|
||||
int width() const { return width_; }
|
||||
@@ -84,6 +101,7 @@ class Bitmap {
|
||||
auto at(int i) const { return pixel_data_[i]; }
|
||||
auto texture() const { return texture_.get(); }
|
||||
auto IsActive() const { return active_; }
|
||||
auto SetActive(bool active) { active_ = active; }
|
||||
|
||||
private:
|
||||
struct SDL_Texture_Deleter {
|
||||
@@ -137,7 +155,7 @@ class BitmapManager {
|
||||
if (it != bitmap_cache_.end()) {
|
||||
return it->second;
|
||||
}
|
||||
return nullptr; // or handle the error accordingly
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
using value_type = std::pair<const int, std::shared_ptr<gfx::Bitmap>>;
|
||||
@@ -153,8 +171,6 @@ class BitmapManager {
|
||||
const_iterator cbegin() const noexcept { return bitmap_cache_.cbegin(); }
|
||||
const_iterator cend() const noexcept { return bitmap_cache_.cend(); }
|
||||
|
||||
|
||||
|
||||
std::shared_ptr<gfx::Bitmap> GetBitmap(int id) {
|
||||
auto it = bitmap_cache_.find(id);
|
||||
if (it != bitmap_cache_.end()) {
|
||||
|
||||
@@ -4,6 +4,107 @@
|
||||
#include <imgui/imgui_internal.h>
|
||||
|
||||
#include "absl/strings/string_view.h"
|
||||
namespace ImGui {
|
||||
|
||||
static inline ImGuiInputTextFlags InputScalar_DefaultCharsFilter(
|
||||
ImGuiDataType data_type, const char* format) {
|
||||
if (data_type == ImGuiDataType_Float || data_type == ImGuiDataType_Double)
|
||||
return ImGuiInputTextFlags_CharsScientific;
|
||||
const char format_last_char = format[0] ? format[strlen(format) - 1] : 0;
|
||||
return (format_last_char == 'x' || format_last_char == 'X')
|
||||
? ImGuiInputTextFlags_CharsHexadecimal
|
||||
: ImGuiInputTextFlags_CharsDecimal;
|
||||
}
|
||||
|
||||
bool InputScalarLeft(const char* label, ImGuiDataType data_type, void* p_data,
|
||||
const void* p_step, const void* p_step_fast,
|
||||
const char* format, float input_width,
|
||||
ImGuiInputTextFlags flags) {
|
||||
ImGuiWindow* window = ImGui::GetCurrentWindow();
|
||||
if (window->SkipItems) return false;
|
||||
|
||||
ImGuiContext& g = *GImGui;
|
||||
ImGuiStyle& style = g.Style;
|
||||
|
||||
if (format == NULL) format = DataTypeGetInfo(data_type)->PrintFmt;
|
||||
|
||||
char buf[64];
|
||||
DataTypeFormatString(buf, IM_ARRAYSIZE(buf), data_type, p_data, format);
|
||||
|
||||
// Testing ActiveId as a minor optimization as filtering is not needed until
|
||||
// active
|
||||
if (g.ActiveId == 0 && (flags & (ImGuiInputTextFlags_CharsDecimal |
|
||||
ImGuiInputTextFlags_CharsHexadecimal |
|
||||
ImGuiInputTextFlags_CharsScientific)) == 0)
|
||||
flags |= InputScalar_DefaultCharsFilter(data_type, format);
|
||||
flags |=
|
||||
ImGuiInputTextFlags_AutoSelectAll |
|
||||
ImGuiInputTextFlags_NoMarkEdited; // We call MarkItemEdited() ourselves
|
||||
// by comparing the actual data rather
|
||||
// than the string.
|
||||
|
||||
bool value_changed = false;
|
||||
if (p_step == NULL) {
|
||||
ImGui::SetNextItemWidth(input_width);
|
||||
if (InputText(label, buf, IM_ARRAYSIZE(buf), flags))
|
||||
value_changed = DataTypeApplyFromText(buf, data_type, p_data, format);
|
||||
} else {
|
||||
const float button_size = GetFrameHeight();
|
||||
|
||||
BeginGroup(); // The only purpose of the group here is to allow the caller
|
||||
// to query item data e.g. IsItemActive()
|
||||
PushID(label);
|
||||
SetNextItemWidth(ImMax(
|
||||
1.0f, CalcItemWidth() - (button_size + style.ItemInnerSpacing.x) * 2));
|
||||
|
||||
// Place the label on the left of the input field
|
||||
ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing,
|
||||
ImVec2{style.ItemSpacing.x, style.ItemSpacing.y * 2});
|
||||
ImGui::PushStyleVar(ImGuiStyleVar_FramePadding,
|
||||
ImVec2{style.FramePadding.x, style.FramePadding.y * 2});
|
||||
ImGui::AlignTextToFramePadding();
|
||||
ImGui::Text("%s", label);
|
||||
ImGui::SameLine();
|
||||
ImGui::SetNextItemWidth(input_width);
|
||||
if (InputText("", buf, IM_ARRAYSIZE(buf),
|
||||
flags)) // PushId(label) + "" gives us the expected ID
|
||||
// from outside point of view
|
||||
value_changed = DataTypeApplyFromText(buf, data_type, p_data, format);
|
||||
IMGUI_TEST_ENGINE_ITEM_INFO(
|
||||
g.LastItemData.ID, label,
|
||||
g.LastItemData.StatusFlags | ImGuiItemStatusFlags_Inputable);
|
||||
|
||||
// Step buttons
|
||||
const ImVec2 backup_frame_padding = style.FramePadding;
|
||||
style.FramePadding.x = style.FramePadding.y;
|
||||
ImGuiButtonFlags button_flags =
|
||||
ImGuiButtonFlags_Repeat | ImGuiButtonFlags_DontClosePopups;
|
||||
if (flags & ImGuiInputTextFlags_ReadOnly) BeginDisabled();
|
||||
SameLine(0, style.ItemInnerSpacing.x);
|
||||
if (ButtonEx("-", ImVec2(button_size, button_size), button_flags)) {
|
||||
DataTypeApplyOp(data_type, '-', p_data, p_data,
|
||||
g.IO.KeyCtrl && p_step_fast ? p_step_fast : p_step);
|
||||
value_changed = true;
|
||||
}
|
||||
SameLine(0, style.ItemInnerSpacing.x);
|
||||
if (ButtonEx("+", ImVec2(button_size, button_size), button_flags)) {
|
||||
DataTypeApplyOp(data_type, '+', p_data, p_data,
|
||||
g.IO.KeyCtrl && p_step_fast ? p_step_fast : p_step);
|
||||
value_changed = true;
|
||||
}
|
||||
if (flags & ImGuiInputTextFlags_ReadOnly) EndDisabled();
|
||||
|
||||
style.FramePadding = backup_frame_padding;
|
||||
|
||||
PopID();
|
||||
EndGroup();
|
||||
ImGui::PopStyleVar(2);
|
||||
}
|
||||
if (value_changed) MarkItemEdited(g.LastItemData.ID);
|
||||
|
||||
return value_changed;
|
||||
}
|
||||
} // namespace ImGui
|
||||
|
||||
namespace yaze {
|
||||
namespace app {
|
||||
@@ -24,16 +125,16 @@ bool InputHexShort(const char* label, uint32_t* data) {
|
||||
ImGuiInputTextFlags_CharsHexadecimal);
|
||||
}
|
||||
|
||||
bool InputHexWord(const char* label, uint16_t* data) {
|
||||
return ImGui::InputScalar(label, ImGuiDataType_U16, data, &kStepOneHex,
|
||||
&kStepFastHex, "%04X",
|
||||
ImGuiInputTextFlags_CharsHexadecimal);
|
||||
bool InputHexWord(const char* label, uint16_t* data, float input_width) {
|
||||
return ImGui::InputScalarLeft(label, ImGuiDataType_U16, data, &kStepOneHex,
|
||||
&kStepFastHex, "%04X", input_width,
|
||||
ImGuiInputTextFlags_CharsHexadecimal);
|
||||
}
|
||||
|
||||
bool InputHexByte(const char* label, uint8_t* data) {
|
||||
return ImGui::InputScalar(label, ImGuiDataType_U8, data, &kStepOneHex,
|
||||
&kStepFastHex, "%02X",
|
||||
ImGuiInputTextFlags_CharsHexadecimal);
|
||||
bool InputHexByte(const char* label, uint8_t* data, float input_width) {
|
||||
return ImGui::InputScalarLeft(label, ImGuiDataType_U8, data, &kStepOneHex,
|
||||
&kStepFastHex, "%02X", input_width,
|
||||
ImGuiInputTextFlags_CharsHexadecimal);
|
||||
}
|
||||
|
||||
void ItemLabel(absl::string_view title, ItemLabelFlags flags) {
|
||||
|
||||
@@ -17,8 +17,10 @@ constexpr ImVec2 kZeroPos = ImVec2(0, 0);
|
||||
|
||||
IMGUI_API bool InputHex(const char* label, uint64_t* data);
|
||||
IMGUI_API bool InputHexShort(const char* label, uint32_t* data);
|
||||
IMGUI_API bool InputHexWord(const char* label, uint16_t* data);
|
||||
IMGUI_API bool InputHexByte(const char* label, uint8_t* data);
|
||||
IMGUI_API bool InputHexWord(const char* label, uint16_t* data,
|
||||
float input_width = 50.f);
|
||||
IMGUI_API bool InputHexByte(const char* label, uint8_t* data,
|
||||
float input_width = 50.f);
|
||||
|
||||
using ItemLabelFlags = enum ItemLabelFlag {
|
||||
Left = 1u << 0u,
|
||||
|
||||
@@ -249,33 +249,30 @@ absl::Status ROM::LoadAllGraphicsData() {
|
||||
core::kTilesheetHeight,
|
||||
core::kTilesheetDepth);
|
||||
graphics_manager_[i]->CreateTexture(renderer_);
|
||||
|
||||
} else {
|
||||
graphics_bin_[i] =
|
||||
gfx::Bitmap(core::kTilesheetWidth, core::kTilesheetHeight,
|
||||
core::kTilesheetDepth, converted_sheet.data(), 0x1000);
|
||||
graphics_bin_.at(i).CreateTexture(renderer_);
|
||||
}
|
||||
graphics_bin_[i] =
|
||||
gfx::Bitmap(core::kTilesheetWidth, core::kTilesheetHeight,
|
||||
core::kTilesheetDepth, converted_sheet.data(), 0x1000);
|
||||
graphics_bin_.at(i).CreateTexture(renderer_);
|
||||
|
||||
if (flags()->kUseBitmapManager) {
|
||||
for (int j = 0; j < graphics_manager_[i].get()->size(); ++j) {
|
||||
graphics_buffer_.push_back(graphics_manager_[i]->at(j));
|
||||
}
|
||||
} else {
|
||||
for (int j = 0; j < graphics_bin_[i].size(); ++j) {
|
||||
graphics_buffer_.push_back(graphics_bin_.at(i).at(j));
|
||||
}
|
||||
}
|
||||
for (int j = 0; j < graphics_bin_[i].size(); ++j) {
|
||||
graphics_buffer_.push_back(graphics_bin_.at(i).at(j));
|
||||
}
|
||||
|
||||
} else {
|
||||
if (flags()->kUseBitmapManager) {
|
||||
for (int j = 0; j < graphics_manager_[i].get()->size(); ++j) {
|
||||
graphics_buffer_.push_back(0xFF);
|
||||
}
|
||||
} else {
|
||||
for (int j = 0; j < graphics_bin_[0].size(); ++j) {
|
||||
for (int j = 0; j < graphics_manager_[0].get()->size(); ++j) {
|
||||
graphics_buffer_.push_back(0xFF);
|
||||
}
|
||||
}
|
||||
for (int j = 0; j < graphics_bin_[0].size(); ++j) {
|
||||
graphics_buffer_.push_back(0xFF);
|
||||
}
|
||||
}
|
||||
}
|
||||
return absl::OkStatus();
|
||||
|
||||
@@ -464,7 +464,7 @@ class ROM : public core::ExperimentFlags {
|
||||
bitmap->UpdateTexture(renderer_);
|
||||
}
|
||||
|
||||
auto BitmapManager() const { return graphics_manager_; }
|
||||
auto BitmapManager() { return graphics_manager_; }
|
||||
|
||||
std::vector<std::vector<uint8_t>> main_blockset_ids;
|
||||
std::vector<std::vector<uint8_t>> room_blockset_ids;
|
||||
|
||||
@@ -78,6 +78,64 @@ void DrawDungeonRoomBG2(std::vector<uint8_t>& tiles_bg2_buffer,
|
||||
}
|
||||
}
|
||||
|
||||
void Room::LoadHeader() {
|
||||
// Address of the room header
|
||||
int headerPointer = (rom()->data()[core::room_header_pointer + 2] << 16) +
|
||||
(rom()->data()[core::room_header_pointer + 1] << 8) +
|
||||
(rom()->data()[core::room_header_pointer]);
|
||||
headerPointer = core::SnesToPc(headerPointer);
|
||||
|
||||
int address = (rom()->data()[core::room_header_pointers_bank] << 16) +
|
||||
(rom()->data()[(headerPointer + 1) + (room_id_ * 2)] << 8) +
|
||||
rom()->data()[(headerPointer) + (room_id_ * 2)];
|
||||
|
||||
auto header_location = core::SnesToPc(address);
|
||||
|
||||
// bg2 = (Background2)((rom()->data()[header_location] >> 5) & 0x07);
|
||||
// collision = (CollisionKey)((rom()->data()[header_location] >> 2) & 0x07);
|
||||
// light = ((rom()->data()[header_location]) & 0x01) == 1;
|
||||
|
||||
if (light) {
|
||||
bg2 = Background2::DarkRoom;
|
||||
}
|
||||
|
||||
palette = ((rom()->data()[header_location + 1] & 0x3F));
|
||||
blockset = (rom()->data()[header_location + 2]);
|
||||
spriteset = (rom()->data()[header_location + 3]);
|
||||
// effect = (EffectKey)((rom()->data()[header_location + 4]));
|
||||
// tag1 = (TagKey)((rom()->data()[header_location + 5]));
|
||||
// tag2 = (TagKey)((rom()->data()[header_location + 6]));
|
||||
|
||||
// holewarpPlane = ((rom()->data()[header_location + 7]) & 0x03);
|
||||
staircase_plane[0] = ((rom()->data()[header_location + 7] >> 2) & 0x03);
|
||||
staircase_plane[1] = ((rom()->data()[header_location + 7] >> 4) & 0x03);
|
||||
staircase_plane[2] = ((rom()->data()[header_location + 7] >> 6) & 0x03);
|
||||
staircase_plane[3] = ((rom()->data()[header_location + 8]) & 0x03);
|
||||
|
||||
// if (holewarpPlane == 2) {
|
||||
// Console::WriteLine("Room Index Plane 1 : Used in room id = " +
|
||||
// index.ToString("X2"));
|
||||
// } else if (staircasePlane[0] == 2) {
|
||||
// Console::WriteLine("Room Index Plane 1 : Used in room id = " +
|
||||
// index.ToString("X2"));
|
||||
// } else if (staircasePlane[1] == 2) {
|
||||
// Console::WriteLine("Room Index Plane 1 : Used in room id = " +
|
||||
// index.ToString("X2"));
|
||||
// } else if (staircasePlane[2] == 2) {
|
||||
// Console::WriteLine("Room Index Plane 1 : Used in room id = " +
|
||||
// index.ToString("X2"));
|
||||
// } else if (staircasePlane[3] == 2) {
|
||||
// Console::WriteLine("Room Index Plane 1 : Used in room id = " +
|
||||
// index.ToString("X2"));
|
||||
// }
|
||||
|
||||
// holewarp = (rom()->data()[header_location + 9]);
|
||||
staircase_rooms[0] = (rom()->data()[header_location + 10]);
|
||||
staircase_rooms[1] = (rom()->data()[header_location + 11]);
|
||||
staircase_rooms[2] = (rom()->data()[header_location + 12]);
|
||||
staircase_rooms[3] = (rom()->data()[header_location + 13]);
|
||||
}
|
||||
|
||||
void Room::LoadSprites() {
|
||||
auto rom_data = rom()->vector();
|
||||
int spritePointer = (0x04 << 16) + (rom_data[rooms_sprite_pointer + 1] << 8) +
|
||||
@@ -295,6 +353,10 @@ RoomObject Room::AddObject(short oid, uint8_t x, uint8_t y, uint8_t size,
|
||||
}
|
||||
|
||||
void Room::LoadRoomGraphics(uchar entrance_blockset) {
|
||||
auto mainGfx = rom()->main_blockset_ids;
|
||||
auto roomGfx = rom()->room_blockset_ids;
|
||||
auto spriteGfx = rom()->spriteset_ids;
|
||||
|
||||
for (int i = 0; i < 8; i++) {
|
||||
blocks[i] = mainGfx[BackgroundTileset][i];
|
||||
if (i >= 6 && i <= 6) {
|
||||
|
||||
@@ -80,6 +80,8 @@ void DrawDungeonRoomBG2(std::vector<uint8_t>& tiles_bg2_buffer,
|
||||
|
||||
class DungeonDestination {
|
||||
public:
|
||||
DungeonDestination() = default;
|
||||
~DungeonDestination() = default;
|
||||
DungeonDestination(uint8_t i) : Index(i) {}
|
||||
|
||||
uint8_t Index;
|
||||
@@ -89,6 +91,7 @@ class DungeonDestination {
|
||||
};
|
||||
|
||||
struct object_door {
|
||||
object_door() = default;
|
||||
object_door(short id, uint8_t x, uint8_t y, uint8_t size, uint8_t layer)
|
||||
: id_(id), x_(x), y_(y), size_(size), layer_(layer) {}
|
||||
|
||||
@@ -101,6 +104,7 @@ struct object_door {
|
||||
};
|
||||
|
||||
struct ChestData {
|
||||
ChestData() = default;
|
||||
ChestData(uchar i, bool s) : id_(i), size_(s){};
|
||||
|
||||
uchar id_;
|
||||
@@ -111,6 +115,10 @@ struct StaircaseRooms {};
|
||||
|
||||
class Room : public SharedROM {
|
||||
public:
|
||||
Room() = default;
|
||||
Room(int room_id) : room_id_(room_id) {}
|
||||
~Room() = default;
|
||||
void LoadHeader();
|
||||
void LoadSprites();
|
||||
void LoadChests();
|
||||
|
||||
@@ -124,19 +132,29 @@ class Room : public SharedROM {
|
||||
|
||||
void LoadRoomFromROM();
|
||||
|
||||
uint8_t floor1 = 0;
|
||||
uint8_t floor2 = 0;
|
||||
uint8_t blockset = 0;
|
||||
uint8_t spriteset = 0;
|
||||
uint8_t palette = 0;
|
||||
uint8_t layout = 0;
|
||||
|
||||
uint16_t message_id_ = 0;
|
||||
|
||||
gfx::Bitmap current_graphics_;
|
||||
|
||||
private:
|
||||
int animated_frame = 0;
|
||||
|
||||
int room_id_ = 0;
|
||||
|
||||
uint8_t floor1;
|
||||
uint8_t floor2;
|
||||
uint8_t blockset;
|
||||
uint8_t spriteset;
|
||||
uint8_t palette;
|
||||
uint8_t layout;
|
||||
bool light;
|
||||
bool is_loaded_ = false;
|
||||
Background2 bg2;
|
||||
|
||||
uint8_t staircase_plane[4];
|
||||
uint8_t staircase_rooms[4];
|
||||
|
||||
ushort message_id_ = 0;
|
||||
uchar BackgroundTileset;
|
||||
uchar SpriteTileset;
|
||||
uchar Layer2Behavior;
|
||||
@@ -147,11 +165,6 @@ class Room : public SharedROM {
|
||||
std::array<uchar, 16> blocks;
|
||||
std::array<uchar, 16> ChestList;
|
||||
|
||||
uint8_t mainGfx[37][8];
|
||||
uint8_t roomGfx[82][4];
|
||||
uint8_t spriteGfx[144][4];
|
||||
uint8_t paletteGfx[72][4];
|
||||
|
||||
std::vector<zelda3::Sprite> sprites_;
|
||||
std::vector<StaircaseRooms> staircaseRooms;
|
||||
|
||||
@@ -171,8 +184,6 @@ class Room : public SharedROM {
|
||||
std::vector<ChestData> chests_in_room;
|
||||
std::vector<uint8_t> current_gfx16_;
|
||||
std::vector<RoomObject> tilesObjects;
|
||||
|
||||
gfx::Bitmap current_graphics_;
|
||||
};
|
||||
|
||||
} // namespace dungeon
|
||||
|
||||
@@ -7,6 +7,8 @@
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "app/emu/cpu.h"
|
||||
#include "app/emu/video/ppu.h"
|
||||
#include "app/gfx/snes_palette.h"
|
||||
#include "app/gfx/snes_tile.h"
|
||||
#include "app/rom.h"
|
||||
@@ -17,6 +19,61 @@ namespace app {
|
||||
namespace zelda3 {
|
||||
namespace dungeon {
|
||||
|
||||
class DungeonObjectRenderer : public SharedROM {
|
||||
public:
|
||||
|
||||
struct PseudoVram {
|
||||
std::vector<gfx::Bitmap> sheets;
|
||||
};
|
||||
|
||||
void CreateVramFromRoomBlockset() {
|
||||
auto bitmap_manager = rom()->BitmapManager();
|
||||
uint16_t room_id = 0;
|
||||
auto room_blockset = rom()->room_blockset_ids[room_id];
|
||||
|
||||
for (const auto blockset_id : room_blockset) {
|
||||
auto blockset = bitmap_manager[(uint16_t)blockset_id];
|
||||
vram_.sheets.push_back(*blockset.get());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
void RenderObjectsAsBitmaps() {
|
||||
auto subtype1_ptr = core::subtype1_tiles;
|
||||
auto subtype1_routine_ptr =
|
||||
core::subtype1_tiles + 0x200; // Where the draw routines start
|
||||
|
||||
auto subtype2_ptr = core::subtype2_tiles;
|
||||
auto subtype2_routine_ptr =
|
||||
core::subtype2_tiles + 0x80; // Where the draw routines start
|
||||
|
||||
auto subtype3_ptr = core::subtype3_tiles;
|
||||
auto subtype3_routine_ptr =
|
||||
core::subtype3_tiles + 0x100; // Where the draw routines start
|
||||
|
||||
auto data = (*rom()).vector();
|
||||
// Construct a copy of the rooms VRAM
|
||||
// Jump to the routine that draws the object based on the ID
|
||||
// Run the routine and get the VRAM data using the CPU and PPU
|
||||
// Render the VRAM data to a bitmap
|
||||
|
||||
memory_.Initialize(data);
|
||||
cpu.PC = subtype1_routine_ptr;
|
||||
cpu.JMP(cpu.FetchWord());
|
||||
auto dest = cpu.PC + 0x10;
|
||||
while (cpu.PC < dest) {
|
||||
cpu.ExecuteInstruction(cpu.FetchByte());
|
||||
}
|
||||
}
|
||||
|
||||
emu::MemoryImpl memory_;
|
||||
emu::ClockImpl clock_;
|
||||
emu::CPU cpu{memory_, clock_};
|
||||
emu::PPU ppu{memory_, clock_};
|
||||
gfx::Bitmap bitmap;
|
||||
PseudoVram vram_;
|
||||
};
|
||||
|
||||
enum class SpecialObjectType { Chest, BigChest, InterroomStairs };
|
||||
|
||||
struct Tile {};
|
||||
@@ -223,9 +280,7 @@ class Subtype2_Multiple : public RoomObject {
|
||||
// Other member functions and variables
|
||||
};
|
||||
|
||||
class Subtype3 : public RoomObject {
|
||||
|
||||
};
|
||||
class Subtype3 : public RoomObject {};
|
||||
|
||||
} // namespace dungeon
|
||||
} // namespace zelda3
|
||||
|
||||
@@ -11,6 +11,7 @@
|
||||
#include "absl/container/flat_hash_map.h"
|
||||
#include "absl/status/status.h"
|
||||
#include "app/core/constants.h"
|
||||
#include "app/core/common.h"
|
||||
#include "app/gfx/bitmap.h"
|
||||
#include "app/gfx/compression.h"
|
||||
#include "app/gfx/snes_tile.h"
|
||||
|
||||
Reference in New Issue
Block a user