implemented background tile fetching
This commit is contained in:
parent
f4e6198a99
commit
e7b78f281f
|
@ -52,6 +52,15 @@ uint8_t Bus::Tick()
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void Bus::PPUTick()
|
||||||
|
{
|
||||||
|
if (ppuClock == 0)
|
||||||
|
cpu.Tick();
|
||||||
|
|
||||||
|
ppu.Tick();
|
||||||
|
ppuClock = (ppuClock + 1) % 3;
|
||||||
|
}
|
||||||
|
|
||||||
bool Bus::Instruction()
|
bool Bus::Instruction()
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
|
|
|
@ -45,6 +45,8 @@ public:
|
||||||
*/
|
*/
|
||||||
uint8_t Tick();
|
uint8_t Tick();
|
||||||
|
|
||||||
|
void PPUTick();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Advance the emulator by one CPU instruction.
|
* @brief Advance the emulator by one CPU instruction.
|
||||||
*/
|
*/
|
||||||
|
@ -87,4 +89,6 @@ private:
|
||||||
PPU ppu;
|
PPU ppu;
|
||||||
Cartridge cartridge;
|
Cartridge cartridge;
|
||||||
ControllerPort controllerPort;
|
ControllerPort controllerPort;
|
||||||
|
|
||||||
|
uint8_t ppuClock = 0;
|
||||||
};
|
};
|
||||||
|
|
179
src/PPU.cpp
179
src/PPU.cpp
|
@ -16,9 +16,15 @@ void PPU::Powerup()
|
||||||
ppuscroll.y = 0x00;
|
ppuscroll.y = 0x00;
|
||||||
ppuaddr.Raw = 0x0000;
|
ppuaddr.Raw = 0x0000;
|
||||||
|
|
||||||
x = 0;
|
// This will cause the Tick() functioon to set both to 0 on the first tick
|
||||||
y = 0;
|
x = 400;
|
||||||
|
y = 400;
|
||||||
|
fineX = 0;
|
||||||
addressLatch = 0;
|
addressLatch = 0;
|
||||||
|
|
||||||
|
scanlineType = ScanlineType::Visible;
|
||||||
|
cycleType = CycleType::Idle;
|
||||||
|
fetchPhase = FetchingPhase::NametableByte;
|
||||||
}
|
}
|
||||||
|
|
||||||
void PPU::Reset()
|
void PPU::Reset()
|
||||||
|
@ -28,13 +34,63 @@ void PPU::Reset()
|
||||||
ppuscroll.x = 0x00;
|
ppuscroll.x = 0x00;
|
||||||
ppuscroll.y = 0x00;
|
ppuscroll.y = 0x00;
|
||||||
|
|
||||||
x = 0;
|
// This will cause the Tick() functioon to set both to 0 on the first tick
|
||||||
y = 0;
|
x = 400;
|
||||||
addressLatch = 1;
|
y = 400;
|
||||||
|
fineX = 0;
|
||||||
|
addressLatch = 0;
|
||||||
|
|
||||||
|
scanlineType = ScanlineType::Visible;
|
||||||
|
cycleType = CycleType::Idle;
|
||||||
|
fetchPhase = FetchingPhase::NametableByte;
|
||||||
}
|
}
|
||||||
|
|
||||||
void PPU::Tick()
|
void PPU::Tick()
|
||||||
{
|
{
|
||||||
|
// Advance pixel counters
|
||||||
|
x++;
|
||||||
|
if (x > 340)
|
||||||
|
{
|
||||||
|
x = 0;
|
||||||
|
y++;
|
||||||
|
if (y > 261)
|
||||||
|
y = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
current.CoarseX++;
|
||||||
|
if (current.CoarseX > 31)
|
||||||
|
{
|
||||||
|
current.CoarseX = 0;
|
||||||
|
current.NametableSel ^= 0x1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (x == 256)
|
||||||
|
{
|
||||||
|
if (current.FineY < 7)
|
||||||
|
{
|
||||||
|
current.FineY++;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
current.FineY = 0;
|
||||||
|
if (current.CoarseY == 29)
|
||||||
|
{
|
||||||
|
current.CoarseY = 0;
|
||||||
|
current.NametableSel ^= 0x2;
|
||||||
|
}
|
||||||
|
else if (current.CoarseY == 31)
|
||||||
|
{
|
||||||
|
current.CoarseY = 0;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
current.CoarseY++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
UpdateState();
|
||||||
|
|
||||||
// On this cycle the VBlankStarted bit is set in the ppustatus
|
// On this cycle the VBlankStarted bit is set in the ppustatus
|
||||||
if (y == 241 && x == 1)
|
if (y == 241 && x == 1)
|
||||||
{
|
{
|
||||||
|
@ -50,15 +106,13 @@ void PPU::Tick()
|
||||||
if (y == 261 && x == 1)
|
if (y == 261 && x == 1)
|
||||||
ppustatus.Flag.VBlankStarted = 0;
|
ppustatus.Flag.VBlankStarted = 0;
|
||||||
|
|
||||||
// Advance pixel counters
|
|
||||||
x++;
|
// Need to render
|
||||||
if (x > 340)
|
if (scanlineType == ScanlineType::Visible || scanlineType == ScanlineType::PreRender)
|
||||||
{
|
{
|
||||||
x = 0;
|
PerformRenderAction();
|
||||||
y++;
|
|
||||||
if (y > 261)
|
|
||||||
y = 0;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Byte PPU::ReadRegister(Byte id)
|
Byte PPU::ReadRegister(Byte id)
|
||||||
|
@ -107,6 +161,7 @@ void PPU::WriteRegister(Byte id, Byte val)
|
||||||
{
|
{
|
||||||
case 0:
|
case 0:
|
||||||
ppuctrl.Raw = val;
|
ppuctrl.Raw = val;
|
||||||
|
temporary.NametableSel = ppuctrl.Flag.BaseNametableAddr;
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case 1:
|
case 1:
|
||||||
|
@ -122,18 +177,37 @@ void PPU::WriteRegister(Byte id, Byte val)
|
||||||
// determines whether the hi or lo byte should be written next
|
// determines whether the hi or lo byte should be written next
|
||||||
case 5:
|
case 5:
|
||||||
if (addressLatch == 0)
|
if (addressLatch == 0)
|
||||||
|
{
|
||||||
ppuscroll.x = val;
|
ppuscroll.x = val;
|
||||||
|
temporary.CoarseX = (val >> 3);
|
||||||
|
fineX = val & 0x3;
|
||||||
|
}
|
||||||
else
|
else
|
||||||
|
{
|
||||||
ppuscroll.y = val;
|
ppuscroll.y = val;
|
||||||
|
temporary.CoarseY = (val >> 3);
|
||||||
|
temporary.FineY = val & 0x3;
|
||||||
|
}
|
||||||
|
|
||||||
addressLatch = !addressLatch;
|
addressLatch = !addressLatch;
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case 6:
|
case 6:
|
||||||
if (addressLatch == 0)
|
if (addressLatch == 0)
|
||||||
|
{
|
||||||
ppuaddr.Bytes.hi = val;
|
ppuaddr.Bytes.hi = val;
|
||||||
|
temporary.Raw &= 0xFF;
|
||||||
|
temporary.Raw |= ((Word)(val & 0x3F) << 8);
|
||||||
|
temporary.FineY &= 0x3;
|
||||||
|
}
|
||||||
else
|
else
|
||||||
|
{
|
||||||
ppuaddr.Bytes.lo = val;
|
ppuaddr.Bytes.lo = val;
|
||||||
|
temporary.Raw &= ((Word)0xFF << 8);
|
||||||
|
temporary.Raw |= val;
|
||||||
|
|
||||||
|
current.Raw = temporary.Raw;
|
||||||
|
}
|
||||||
|
|
||||||
addressLatch = !addressLatch;
|
addressLatch = !addressLatch;
|
||||||
break;
|
break;
|
||||||
|
@ -160,3 +234,84 @@ void PPU::Write(Word addr, Byte val)
|
||||||
{
|
{
|
||||||
bus->WritePPU(addr, val);
|
bus->WritePPU(addr, val);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void PPU::UpdateState()
|
||||||
|
{
|
||||||
|
switch (y)
|
||||||
|
{
|
||||||
|
case 0:
|
||||||
|
scanlineType = ScanlineType::Visible;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 240:
|
||||||
|
scanlineType = ScanlineType::PostRender;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 241:
|
||||||
|
scanlineType = ScanlineType::VBlank;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 261:
|
||||||
|
scanlineType = ScanlineType::PreRender;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (x)
|
||||||
|
{
|
||||||
|
case 0:
|
||||||
|
cycleType = CycleType::Idle;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 1:
|
||||||
|
cycleType = CycleType::Fetching;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 257:
|
||||||
|
cycleType = CycleType::SpriteFetching;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 321:
|
||||||
|
cycleType = CycleType::PreFetching;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 337:
|
||||||
|
cycleType = CycleType::UnknownFetching;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void PPU::PerformRenderAction()
|
||||||
|
{
|
||||||
|
if (cycleType == CycleType::Idle)
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (memoryAccessLatch == 1)
|
||||||
|
{
|
||||||
|
switch (fetchPhase)
|
||||||
|
{
|
||||||
|
case FetchingPhase::NametableByte:
|
||||||
|
nametableByte = Read(0x2000 | (current.Raw & 0x0FFF));
|
||||||
|
|
||||||
|
if(cycleType != CycleType::UnknownFetching)
|
||||||
|
fetchPhase = FetchingPhase::AttributeTableByte;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case FetchingPhase::AttributeTableByte:
|
||||||
|
attributeTableByte = Read(0x23C0 | (current.Raw & 0x0FFF) | ((current.Raw >> 4) & 0x38) | ((current.Raw >> 2) & 0x07));
|
||||||
|
fetchPhase = FetchingPhase::PatternTableLo;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case FetchingPhase::PatternTableLo:
|
||||||
|
patternTableLo = Read(ppuctrl.Flag.BackgrPatternTableAddr | nametableByte);
|
||||||
|
fetchPhase = FetchingPhase::PatternTableHi;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case FetchingPhase::PatternTableHi:
|
||||||
|
patternTableLo = Read((ppuctrl.Flag.BackgrPatternTableAddr | nametableByte) + 8);
|
||||||
|
fetchPhase = FetchingPhase::NametableByte;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
memoryAccessLatch = 1 - memoryAccessLatch;
|
||||||
|
}
|
||||||
|
|
60
src/PPU.hpp
60
src/PPU.hpp
|
@ -4,6 +4,44 @@
|
||||||
|
|
||||||
class Bus;
|
class Bus;
|
||||||
|
|
||||||
|
enum class ScanlineType
|
||||||
|
{
|
||||||
|
PreRender,
|
||||||
|
Visible,
|
||||||
|
PostRender,
|
||||||
|
VBlank
|
||||||
|
};
|
||||||
|
|
||||||
|
enum class CycleType
|
||||||
|
{
|
||||||
|
Idle,
|
||||||
|
Fetching,
|
||||||
|
SpriteFetching,
|
||||||
|
PreFetching,
|
||||||
|
UnknownFetching
|
||||||
|
};
|
||||||
|
|
||||||
|
enum class FetchingPhase
|
||||||
|
{
|
||||||
|
NametableByte,
|
||||||
|
AttributeTableByte,
|
||||||
|
PatternTableLo,
|
||||||
|
PatternTableHi
|
||||||
|
};
|
||||||
|
|
||||||
|
union VRAMAddress
|
||||||
|
{
|
||||||
|
struct
|
||||||
|
{
|
||||||
|
Byte CoarseX : 5;
|
||||||
|
Byte CoarseY : 5;
|
||||||
|
Byte NametableSel : 2;
|
||||||
|
Byte FineY : 3;
|
||||||
|
};
|
||||||
|
|
||||||
|
Word Raw;
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief The PPU of the NES.
|
* @brief The PPU of the NES.
|
||||||
*/
|
*/
|
||||||
|
@ -59,6 +97,9 @@ private:
|
||||||
*/
|
*/
|
||||||
void Write(Word addr, Byte val);
|
void Write(Word addr, Byte val);
|
||||||
|
|
||||||
|
void UpdateState();
|
||||||
|
void PerformRenderAction();
|
||||||
|
|
||||||
private: // Registers
|
private: // Registers
|
||||||
|
|
||||||
union
|
union
|
||||||
|
@ -115,12 +156,25 @@ private: // Registers
|
||||||
|
|
||||||
Address ppuaddr;
|
Address ppuaddr;
|
||||||
|
|
||||||
// Current x, y position the PPU operates on
|
VRAMAddress current{ 0 };
|
||||||
uint16_t x, y;
|
VRAMAddress temporary{ 0 };
|
||||||
Byte latch = 0;
|
uint16_t fineX;
|
||||||
bool addressLatch = false;
|
bool addressLatch = false;
|
||||||
|
|
||||||
|
Byte latch = 0;
|
||||||
|
|
||||||
|
Word x, y;
|
||||||
|
Byte nametableByte = 0x00;
|
||||||
|
Byte attributeTableByte = 0x00;
|
||||||
|
Byte patternTableLo = 0x00;
|
||||||
|
Byte patternTableHi = 0x00;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
ScanlineType scanlineType;
|
||||||
|
CycleType cycleType;
|
||||||
|
FetchingPhase fetchPhase;
|
||||||
|
|
||||||
|
uint8_t memoryAccessLatch = 0;
|
||||||
bool isFrameDone = false;
|
bool isFrameDone = false;
|
||||||
Bus* bus;
|
Bus* bus;
|
||||||
};
|
};
|
||||||
|
|
|
@ -16,9 +16,13 @@ Debugger::Debugger(Bus* bus) :
|
||||||
bus(bus)
|
bus(bus)
|
||||||
{
|
{
|
||||||
windows.push_back(new CPUWatcher(this, &bus->cpu));
|
windows.push_back(new CPUWatcher(this, &bus->cpu));
|
||||||
windows.push_back(new PPUWatcher(this, &bus->ppu));
|
|
||||||
|
ppuWatcher = new PPUWatcher(this, &bus->ppu);
|
||||||
|
windows.push_back(ppuWatcher);
|
||||||
|
|
||||||
disassembler = new Disassembler(this, &bus->cpu);
|
disassembler = new Disassembler(this, &bus->cpu);
|
||||||
windows.push_back(disassembler);
|
windows.push_back(disassembler);
|
||||||
|
|
||||||
windows.push_back(new MemoryViewer(this, bus));
|
windows.push_back(new MemoryViewer(this, bus));
|
||||||
windows.push_back(new NametableViewer(this, bus));
|
windows.push_back(new NametableViewer(this, bus));
|
||||||
windows.push_back(new ControllerPortViewer(this, &bus->controllerPort));
|
windows.push_back(new ControllerPortViewer(this, &bus->controllerPort));
|
||||||
|
@ -37,7 +41,7 @@ bool Debugger::Frame()
|
||||||
while (!bus->ppu.IsFrameDone())
|
while (!bus->ppu.IsFrameDone())
|
||||||
{
|
{
|
||||||
bus->Tick();
|
bus->Tick();
|
||||||
if (disassembler->BreakpointHit())
|
if (disassembler->BreakpointHit() || ppuWatcher->BreakpointHit())
|
||||||
{
|
{
|
||||||
running = false;
|
running = false;
|
||||||
break;
|
break;
|
||||||
|
@ -92,13 +96,18 @@ void Debugger::Render()
|
||||||
if (running)
|
if (running)
|
||||||
ImGui::PushStyleColor(ImGuiCol_Button, ImVec4{ 0.7f, 0.7f, 0.7f, 0.7f });
|
ImGui::PushStyleColor(ImGuiCol_Button, ImVec4{ 0.7f, 0.7f, 0.7f, 0.7f });
|
||||||
|
|
||||||
|
if (ImGui::Button("PPU Tick"))
|
||||||
|
bus->PPUTick();
|
||||||
|
|
||||||
|
ImGui::SameLine();
|
||||||
|
|
||||||
if (ImGui::Button("Single Step"))
|
if (ImGui::Button("Single Step"))
|
||||||
bus->Instruction();
|
bus->Instruction();
|
||||||
|
|
||||||
ImGui::SameLine();
|
ImGui::SameLine();
|
||||||
|
|
||||||
if (ImGui::Button("Single Frame"))
|
if (ImGui::Button("Single Frame"))
|
||||||
bus->Frame();
|
Frame();
|
||||||
|
|
||||||
if (running)
|
if (running)
|
||||||
ImGui::PopStyleColor();
|
ImGui::PopStyleColor();
|
||||||
|
|
|
@ -5,6 +5,7 @@
|
||||||
|
|
||||||
class Bus;
|
class Bus;
|
||||||
class Disassembler;
|
class Disassembler;
|
||||||
|
class PPUWatcher;
|
||||||
|
|
||||||
class Debugger
|
class Debugger
|
||||||
{
|
{
|
||||||
|
@ -27,4 +28,5 @@ private:
|
||||||
|
|
||||||
std::vector<DebugWindow*> windows;
|
std::vector<DebugWindow*> windows;
|
||||||
Disassembler* disassembler;
|
Disassembler* disassembler;
|
||||||
|
PPUWatcher* ppuWatcher;
|
||||||
};
|
};
|
||||||
|
|
|
@ -98,7 +98,7 @@ void NametableViewer::DisplayNametable(uint8_t index)
|
||||||
|
|
||||||
for (Word hi = 0x0; hi <= 0x3F; hi++)
|
for (Word hi = 0x0; hi <= 0x3F; hi++)
|
||||||
{
|
{
|
||||||
Byte hiOffset = hi << 4;
|
Word hiOffset = hi << 4;
|
||||||
ImGui::TableNextColumn();
|
ImGui::TableNextColumn();
|
||||||
|
|
||||||
ImGui::PushStyleColor(ImGuiCol_Text, ImVec4(0.7f, 0.7f, 0.7f, 1.0f));
|
ImGui::PushStyleColor(ImGuiCol_Text, ImVec4(0.7f, 0.7f, 0.7f, 1.0f));
|
||||||
|
|
|
@ -1,11 +1,38 @@
|
||||||
#include "PPUWatcher.hpp"
|
#include "PPUWatcher.hpp"
|
||||||
|
|
||||||
|
#include <map>
|
||||||
#include <imgui/imgui.h>
|
#include <imgui/imgui.h>
|
||||||
#include "../PPU.hpp"
|
#include "../PPU.hpp"
|
||||||
|
|
||||||
|
static const std::map<ScanlineType, std::string> scanlineTypeNames = {
|
||||||
|
{ScanlineType::PreRender, "Pre-render"},
|
||||||
|
{ScanlineType::Visible, "Visible"},
|
||||||
|
{ScanlineType::PostRender, "Post-render"},
|
||||||
|
{ScanlineType::VBlank, "VBlank"}
|
||||||
|
};
|
||||||
|
|
||||||
|
static const std::map<CycleType, std::string> cycleTypeNames = {
|
||||||
|
{CycleType::Idle, "Idle"},
|
||||||
|
{CycleType::Fetching, "Fetching BG Tiles"},
|
||||||
|
{CycleType::SpriteFetching, "Sprite Fetching"},
|
||||||
|
{CycleType::PreFetching, "Pre-Fetching"},
|
||||||
|
{CycleType::UnknownFetching, "Random Fetches"}
|
||||||
|
};
|
||||||
|
|
||||||
|
static const std::map<FetchingPhase, std::string> fetchingPhaseNames = {
|
||||||
|
{FetchingPhase::NametableByte, "Nametable"},
|
||||||
|
{FetchingPhase::AttributeTableByte, "Attribute Table"},
|
||||||
|
{FetchingPhase::PatternTableLo, "Pattern Table (lo)"},
|
||||||
|
{FetchingPhase::PatternTableHi, "Pattern Table (hi)"},
|
||||||
|
};
|
||||||
|
|
||||||
PPUWatcher::PPUWatcher(Debugger* debugger, PPU* ppu) :
|
PPUWatcher::PPUWatcher(Debugger* debugger, PPU* ppu) :
|
||||||
DebugWindow("PPU Watch", debugger), ppu(ppu)
|
DebugWindow("PPU Watch", debugger), ppu(ppu)
|
||||||
{
|
{
|
||||||
|
breakpoints.emplace_back(ScanlineType::PreRender, "Pre-Render");
|
||||||
|
breakpoints.emplace_back(ScanlineType::Visible, "Visible");
|
||||||
|
breakpoints.emplace_back(ScanlineType::PostRender, "Post-Render");
|
||||||
|
breakpoints.emplace_back(ScanlineType::VBlank, "VBlank");
|
||||||
}
|
}
|
||||||
|
|
||||||
void PPUWatcher::OnRender()
|
void PPUWatcher::OnRender()
|
||||||
|
@ -18,135 +45,205 @@ void PPUWatcher::OnRender()
|
||||||
}
|
}
|
||||||
|
|
||||||
ImGui::Text("On Pixel (%d, %d)", ppu->x, ppu->y);
|
ImGui::Text("On Pixel (%d, %d)", ppu->x, ppu->y);
|
||||||
|
ImGui::Text("Scanline : %s", scanlineTypeNames.find(ppu->scanlineType)->second.c_str());
|
||||||
|
ImGui::Text("Cycle : %s", cycleTypeNames.find(ppu->cycleType)->second.c_str());
|
||||||
|
ImGui::Text("Fetching Phase: %s", fetchingPhaseNames.find(ppu->fetchPhase)->second.c_str());
|
||||||
|
|
||||||
ImGui::Separator();
|
ImGui::Separator();
|
||||||
|
|
||||||
if (ImGui::CollapsingHeader("PPUCTRL"))
|
if (ImGui::CollapsingHeader("Internal Registers"))
|
||||||
{
|
{
|
||||||
if (ImGui::BeginTable("ppuctrl", 2))
|
ImGui::Text("Current VRAM Address (v) : %02X ($%04X)", ppu->current.Raw, 0x2000 | (ppu->current.Raw & 0x0FFF));
|
||||||
|
if (ImGui::TreeNode("Breakdown (v)"))
|
||||||
{
|
{
|
||||||
ImGui::TableNextColumn();
|
ImGui::Text("Coarse X : %02X", ppu->current.CoarseX);
|
||||||
ImGui::Text("Base Nametable Addr");
|
ImGui::Text("Coarse Y : %02X", ppu->current.CoarseY);
|
||||||
ImGui::TableNextColumn();
|
ImGui::Text("Nametable: %02X", ppu->current.NametableSel);
|
||||||
ImGui::Text("$%04X", 0x2000 + 0x400 * ppu->ppuctrl.Flag.BaseNametableAddr);
|
ImGui::Text("Fine Y : %02X", ppu->current.FineY);
|
||||||
|
|
||||||
ImGui::TableNextColumn();
|
ImGui::TreePop();
|
||||||
ImGui::Text("VRAM Addr Increment");
|
}
|
||||||
ImGui::TableNextColumn();
|
|
||||||
ImGui::Text("%d", ppu->ppuctrl.Flag.VRAMAddrIncrement ? 32 : 1);
|
|
||||||
|
|
||||||
ImGui::TableNextColumn();
|
ImGui::Text("Temporary VRAM Address (t): %02X ($%04X)", ppu->temporary.Raw, 0x2000 | (ppu->current.Raw & 0x0FFF));
|
||||||
ImGui::Text("Sprite Pattern Table Addr");
|
if (ImGui::TreeNode("Breakdown (t)"))
|
||||||
ImGui::TableNextColumn();
|
{
|
||||||
ImGui::Text("$%04X", ppu->ppuctrl.Flag.SpritePatternTableAddr ? 0x1000 : 0x0000);
|
ImGui::Text("Coarse X : %02X", ppu->temporary.CoarseX);
|
||||||
|
ImGui::Text("Coarse Y : %02X", ppu->temporary.CoarseY);
|
||||||
|
ImGui::Text("Nametable: %02X", ppu->temporary.NametableSel);
|
||||||
|
ImGui::Text("Fine Y : %02X", ppu->temporary.FineY);
|
||||||
|
|
||||||
ImGui::TableNextColumn();
|
ImGui::TreePop();
|
||||||
ImGui::Text("Backgr Pattern Table Addr");
|
}
|
||||||
ImGui::TableNextColumn();
|
|
||||||
ImGui::Text("$%04X", ppu->ppuctrl.Flag.BackgrPatternTableAddr ? 0x1000 : 0x0000);
|
|
||||||
|
|
||||||
ImGui::TableNextColumn();
|
ImGui::Text("Fine X Scroll : %02X", ppu->fineX);
|
||||||
ImGui::Text("Master/Slave");
|
ImGui::Text("Write Toggle Bit : %02X", ppu->addressLatch);
|
||||||
ImGui::TableNextColumn();
|
}
|
||||||
ImGui::Text(ppu->ppuctrl.Flag.MasterSlaveSelect ? "Output to EXT" : "Read from EXT");
|
|
||||||
|
|
||||||
ImGui::TableNextColumn();
|
if (ImGui::CollapsingHeader("Latches"))
|
||||||
ImGui::Text("VBlank NMI Generation");
|
{
|
||||||
ImGui::TableNextColumn();
|
ImGui::Text("Nametable Byte : %02X", ppu->nametableByte);
|
||||||
ImGui::Text(ppu->ppuctrl.Flag.VBlankNMI ? "On" : "Off");
|
ImGui::Text("Attribute Byte : %02X", ppu->attributeTableByte);
|
||||||
|
ImGui::Text("Pattern Tile Lo: %02X", ppu->patternTableLo);
|
||||||
|
ImGui::Text("Pattern Tile Hi: %02X", ppu->patternTableHi);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ImGui::CollapsingHeader("Registers"))
|
||||||
|
{
|
||||||
|
if (ImGui::TreeNode("PPUCTRL"))
|
||||||
|
{
|
||||||
|
if (ImGui::BeginTable("ppuctrl", 2))
|
||||||
|
{
|
||||||
|
ImGui::TableNextColumn();
|
||||||
|
ImGui::Text("Base Nametable Addr");
|
||||||
|
ImGui::TableNextColumn();
|
||||||
|
ImGui::Text("$%04X", 0x2000 + 0x400 * ppu->ppuctrl.Flag.BaseNametableAddr);
|
||||||
|
|
||||||
ImGui::EndTable();
|
ImGui::TableNextColumn();
|
||||||
|
ImGui::Text("VRAM Addr Increment");
|
||||||
|
ImGui::TableNextColumn();
|
||||||
|
ImGui::Text("%d", ppu->ppuctrl.Flag.VRAMAddrIncrement ? 32 : 1);
|
||||||
|
|
||||||
|
ImGui::TableNextColumn();
|
||||||
|
ImGui::Text("Sprite Pattern Table Addr");
|
||||||
|
ImGui::TableNextColumn();
|
||||||
|
ImGui::Text("$%04X", ppu->ppuctrl.Flag.SpritePatternTableAddr ? 0x1000 : 0x0000);
|
||||||
|
|
||||||
|
ImGui::TableNextColumn();
|
||||||
|
ImGui::Text("Backgr Pattern Table Addr");
|
||||||
|
ImGui::TableNextColumn();
|
||||||
|
ImGui::Text("$%04X", ppu->ppuctrl.Flag.BackgrPatternTableAddr ? 0x1000 : 0x0000);
|
||||||
|
|
||||||
|
ImGui::TableNextColumn();
|
||||||
|
ImGui::Text("Master/Slave");
|
||||||
|
ImGui::TableNextColumn();
|
||||||
|
ImGui::Text(ppu->ppuctrl.Flag.MasterSlaveSelect ? "Output to EXT" : "Read from EXT");
|
||||||
|
|
||||||
|
ImGui::TableNextColumn();
|
||||||
|
ImGui::Text("VBlank NMI Generation");
|
||||||
|
ImGui::TableNextColumn();
|
||||||
|
ImGui::Text(ppu->ppuctrl.Flag.VBlankNMI ? "On" : "Off");
|
||||||
|
|
||||||
|
ImGui::EndTable();
|
||||||
|
ImGui::TreePop();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ImGui::TreeNode("PPUMASK"))
|
||||||
|
{
|
||||||
|
if (ImGui::BeginTable("ppumask", 2))
|
||||||
|
{
|
||||||
|
ImGui::TableNextColumn();
|
||||||
|
ImGui::Text("Greyscale");
|
||||||
|
ImGui::TableNextColumn();
|
||||||
|
ImGui::Text(ppu->ppumask.Flag.Greyscale ? "On" : "Off");
|
||||||
|
|
||||||
|
ImGui::TableNextColumn();
|
||||||
|
ImGui::Text("Left Col Background");
|
||||||
|
ImGui::TableNextColumn();
|
||||||
|
ImGui::Text(ppu->ppumask.Flag.BackgroundOnLeft ? "Show" : "Hide");
|
||||||
|
|
||||||
|
ImGui::TableNextColumn();
|
||||||
|
ImGui::Text("Left Col Sprites");
|
||||||
|
ImGui::TableNextColumn();
|
||||||
|
ImGui::Text(ppu->ppumask.Flag.SpriteOnLeft ? "Show" : "Hide");
|
||||||
|
|
||||||
|
ImGui::TableNextColumn();
|
||||||
|
ImGui::Text("Show Background");
|
||||||
|
ImGui::TableNextColumn();
|
||||||
|
ImGui::Text(ppu->ppumask.Flag.ShowBackground ? "On" : "Off");
|
||||||
|
|
||||||
|
ImGui::TableNextColumn();
|
||||||
|
ImGui::Text("Show Sprites");
|
||||||
|
ImGui::TableNextColumn();
|
||||||
|
ImGui::Text(ppu->ppumask.Flag.ShowSprites ? "On" : "Off");
|
||||||
|
|
||||||
|
ImGui::TableNextColumn();
|
||||||
|
ImGui::Text("Emphasized Colors");
|
||||||
|
ImGui::TableNextColumn();
|
||||||
|
ImGui::Text("%s%s%s", ppu->ppumask.Flag.EmphasizeRed ? "R" : "-", ppu->ppumask.Flag.EmphasizeGreen ? "G" : "-", ppu->ppumask.Flag.EmphasizeBlue ? "B" : "-");
|
||||||
|
|
||||||
|
ImGui::EndTable();
|
||||||
|
ImGui::TreePop();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ImGui::TreeNode("PPUSTATUS"))
|
||||||
|
{
|
||||||
|
if (ImGui::BeginTable("ppustatus", 2))
|
||||||
|
{
|
||||||
|
ImGui::TableNextColumn();
|
||||||
|
ImGui::Text("Lower 5 Bits");
|
||||||
|
ImGui::TableNextColumn();
|
||||||
|
ImGui::Text("%02X", ppu->ppustatus.Flag.Unused);
|
||||||
|
|
||||||
|
ImGui::TableNextColumn();
|
||||||
|
ImGui::Text("Sprite Overflow");
|
||||||
|
ImGui::TableNextColumn();
|
||||||
|
ImGui::Text(ppu->ppustatus.Flag.SpriteOverflow ? "Yes" : "No");
|
||||||
|
|
||||||
|
ImGui::TableNextColumn();
|
||||||
|
ImGui::Text("Sprite 0 Hit");
|
||||||
|
ImGui::TableNextColumn();
|
||||||
|
ImGui::Text(ppu->ppustatus.Flag.SpriteZeroHit ? "Yes" : "No");
|
||||||
|
|
||||||
|
ImGui::TableNextColumn();
|
||||||
|
ImGui::Text("VBlank Started");
|
||||||
|
ImGui::TableNextColumn();
|
||||||
|
ImGui::Text(ppu->ppustatus.Flag.VBlankStarted ? "Yes" : "No");
|
||||||
|
|
||||||
|
ImGui::EndTable();
|
||||||
|
ImGui::TreePop();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ImGui::TreeNode("PPUSCROLL & PPUADDR"))
|
||||||
|
{
|
||||||
|
if (ImGui::BeginTable("ppuscrolladdr", 2))
|
||||||
|
{
|
||||||
|
ImGui::TableNextColumn();
|
||||||
|
ImGui::Text("Scroll X");
|
||||||
|
ImGui::TableNextColumn();
|
||||||
|
ImGui::Text("%d", ppu->ppuscroll.x);
|
||||||
|
|
||||||
|
ImGui::TableNextColumn();
|
||||||
|
ImGui::Text("Scroll Y");
|
||||||
|
ImGui::TableNextColumn();
|
||||||
|
ImGui::Text("%d", ppu->ppuscroll.x);
|
||||||
|
|
||||||
|
ImGui::TableNextColumn();
|
||||||
|
ImGui::Text("Address");
|
||||||
|
ImGui::TableNextColumn();
|
||||||
|
ImGui::Text("$%04X", ppu->ppuaddr.Raw);
|
||||||
|
|
||||||
|
ImGui::EndTable();
|
||||||
|
ImGui::TreePop();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (ImGui::CollapsingHeader("PPUMASK"))
|
if (ImGui::CollapsingHeader("Breakpoints"))
|
||||||
{
|
{
|
||||||
if (ImGui::BeginTable("ppumask", 2))
|
ImGui::Text("Break at beginning of");
|
||||||
|
|
||||||
|
for (FrameStateBreakpoint& breakpoint : breakpoints)
|
||||||
{
|
{
|
||||||
ImGui::TableNextColumn();
|
ImGui::Checkbox(breakpoint.name.c_str(), &breakpoint.enabled);
|
||||||
ImGui::Text("Greyscale");
|
|
||||||
ImGui::TableNextColumn();
|
|
||||||
ImGui::Text(ppu->ppumask.Flag.Greyscale ? "On" : "Off");
|
|
||||||
|
|
||||||
ImGui::TableNextColumn();
|
|
||||||
ImGui::Text("Left Col Background");
|
|
||||||
ImGui::TableNextColumn();
|
|
||||||
ImGui::Text(ppu->ppumask.Flag.BackgroundOnLeft ? "Show" : "Hide");
|
|
||||||
|
|
||||||
ImGui::TableNextColumn();
|
|
||||||
ImGui::Text("Left Col Sprites");
|
|
||||||
ImGui::TableNextColumn();
|
|
||||||
ImGui::Text(ppu->ppumask.Flag.SpriteOnLeft ? "Show" : "Hide");
|
|
||||||
|
|
||||||
ImGui::TableNextColumn();
|
|
||||||
ImGui::Text("Show Background");
|
|
||||||
ImGui::TableNextColumn();
|
|
||||||
ImGui::Text(ppu->ppumask.Flag.ShowBackground ? "On" : "Off");
|
|
||||||
|
|
||||||
ImGui::TableNextColumn();
|
|
||||||
ImGui::Text("Show Sprites");
|
|
||||||
ImGui::TableNextColumn();
|
|
||||||
ImGui::Text(ppu->ppumask.Flag.ShowSprites ? "On" : "Off");
|
|
||||||
|
|
||||||
ImGui::TableNextColumn();
|
|
||||||
ImGui::Text("Emphasized Colors");
|
|
||||||
ImGui::TableNextColumn();
|
|
||||||
ImGui::Text("%s%s%s", ppu->ppumask.Flag.EmphasizeRed ? "R" : "-", ppu->ppumask.Flag.EmphasizeGreen ? "G" : "-", ppu->ppumask.Flag.EmphasizeBlue ? "B" : "-");
|
|
||||||
|
|
||||||
ImGui::EndTable();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (ImGui::CollapsingHeader("PPUSTATUS"))
|
|
||||||
{
|
|
||||||
if (ImGui::BeginTable("ppustatus", 2))
|
|
||||||
{
|
|
||||||
ImGui::TableNextColumn();
|
|
||||||
ImGui::Text("Lower 5 Bits");
|
|
||||||
ImGui::TableNextColumn();
|
|
||||||
ImGui::Text("%02X", ppu->ppustatus.Flag.Unused);
|
|
||||||
|
|
||||||
ImGui::TableNextColumn();
|
|
||||||
ImGui::Text("Sprite Overflow");
|
|
||||||
ImGui::TableNextColumn();
|
|
||||||
ImGui::Text(ppu->ppustatus.Flag.SpriteOverflow ? "Yes" : "No");
|
|
||||||
|
|
||||||
ImGui::TableNextColumn();
|
|
||||||
ImGui::Text("Sprite 0 Hit");
|
|
||||||
ImGui::TableNextColumn();
|
|
||||||
ImGui::Text(ppu->ppustatus.Flag.SpriteZeroHit ? "Yes" : "No");
|
|
||||||
|
|
||||||
ImGui::TableNextColumn();
|
|
||||||
ImGui::Text("VBlank Started");
|
|
||||||
ImGui::TableNextColumn();
|
|
||||||
ImGui::Text(ppu->ppustatus.Flag.VBlankStarted ? "Yes" : "No");
|
|
||||||
|
|
||||||
ImGui::EndTable();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (ImGui::CollapsingHeader("PPUSCROLL & PPUADDR"))
|
|
||||||
{
|
|
||||||
if (ImGui::BeginTable("ppuscrolladdr", 2))
|
|
||||||
{
|
|
||||||
ImGui::TableNextColumn();
|
|
||||||
ImGui::Text("Scroll X");
|
|
||||||
ImGui::TableNextColumn();
|
|
||||||
ImGui::Text("%d", ppu->ppuscroll.x);
|
|
||||||
|
|
||||||
ImGui::TableNextColumn();
|
|
||||||
ImGui::Text("Scroll Y");
|
|
||||||
ImGui::TableNextColumn();
|
|
||||||
ImGui::Text("%d", ppu->ppuscroll.x);
|
|
||||||
|
|
||||||
ImGui::TableNextColumn();
|
|
||||||
ImGui::Text("Address");
|
|
||||||
ImGui::TableNextColumn();
|
|
||||||
ImGui::Text("$%04X", ppu->ppuaddr.Raw);
|
|
||||||
|
|
||||||
ImGui::EndTable();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
ImGui::End();
|
ImGui::End();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool PPUWatcher::BreakpointHit()
|
||||||
|
{
|
||||||
|
if (ppu->x > 2)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
for (const FrameStateBreakpoint& breakpoint : breakpoints)
|
||||||
|
{
|
||||||
|
if (breakpoint.enabled && breakpoint.location == ppu->scanlineType)
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
|
@ -1,8 +1,19 @@
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
|
#include <vector>
|
||||||
#include "DebugWindow.hpp"
|
#include "DebugWindow.hpp"
|
||||||
|
#include "../PPU.hpp"
|
||||||
|
|
||||||
class PPU;
|
struct FrameStateBreakpoint
|
||||||
|
{
|
||||||
|
FrameStateBreakpoint(ScanlineType location, const std::string& name) :
|
||||||
|
location(location), name(name), enabled(false)
|
||||||
|
{ }
|
||||||
|
|
||||||
|
ScanlineType location;
|
||||||
|
std::string name;
|
||||||
|
bool enabled;
|
||||||
|
};
|
||||||
|
|
||||||
class PPUWatcher :
|
class PPUWatcher :
|
||||||
public DebugWindow
|
public DebugWindow
|
||||||
|
@ -11,7 +22,9 @@ public:
|
||||||
PPUWatcher(Debugger* debugger, PPU* ppu);
|
PPUWatcher(Debugger* debugger, PPU* ppu);
|
||||||
|
|
||||||
virtual void OnRender() override;
|
virtual void OnRender() override;
|
||||||
|
bool BreakpointHit();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
PPU* ppu;
|
PPU* ppu;
|
||||||
|
std::vector<FrameStateBreakpoint> breakpoints;
|
||||||
};
|
};
|
||||||
|
|
Loading…
Reference in a new issue