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:
@@ -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_;
|
||||
|
||||
Reference in New Issue
Block a user