mirror of
https://github.com/RetroDECK/Duckstation.git
synced 2024-11-23 06:15:38 +00:00
GPU: Split command handlers into seperate file
This commit is contained in:
parent
246b17454e
commit
baf97cb864
|
@ -40,6 +40,7 @@
|
||||||
<ClCompile Include="cpu_core.cpp" />
|
<ClCompile Include="cpu_core.cpp" />
|
||||||
<ClCompile Include="cpu_disasm.cpp" />
|
<ClCompile Include="cpu_disasm.cpp" />
|
||||||
<ClCompile Include="digital_controller.cpp" />
|
<ClCompile Include="digital_controller.cpp" />
|
||||||
|
<ClCompile Include="gpu_commands.cpp" />
|
||||||
<ClCompile Include="gte.cpp" />
|
<ClCompile Include="gte.cpp" />
|
||||||
<ClCompile Include="dma.cpp" />
|
<ClCompile Include="dma.cpp" />
|
||||||
<ClCompile Include="gpu.cpp" />
|
<ClCompile Include="gpu.cpp" />
|
||||||
|
|
|
@ -21,6 +21,7 @@
|
||||||
<ClCompile Include="mdec.cpp" />
|
<ClCompile Include="mdec.cpp" />
|
||||||
<ClCompile Include="memory_card.cpp" />
|
<ClCompile Include="memory_card.cpp" />
|
||||||
<ClCompile Include="settings.cpp" />
|
<ClCompile Include="settings.cpp" />
|
||||||
|
<ClCompile Include="gpu_commands.cpp" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<ClInclude Include="types.h" />
|
<ClInclude Include="types.h" />
|
||||||
|
|
340
src/core/gpu.cpp
340
src/core/gpu.cpp
|
@ -9,9 +9,6 @@
|
||||||
#include <imgui.h>
|
#include <imgui.h>
|
||||||
Log_SetChannel(GPU);
|
Log_SetChannel(GPU);
|
||||||
|
|
||||||
static u32 s_cpu_to_vram_dump_id = 1;
|
|
||||||
static u32 s_vram_to_cpu_dump_id = 1;
|
|
||||||
|
|
||||||
GPU::GPU() = default;
|
GPU::GPU() = default;
|
||||||
|
|
||||||
GPU::~GPU() = default;
|
GPU::~GPU() = default;
|
||||||
|
@ -250,6 +247,7 @@ void GPU::DMAWrite(const u32* words, u32 word_count)
|
||||||
{
|
{
|
||||||
case DMADirection::CPUtoGP0:
|
case DMADirection::CPUtoGP0:
|
||||||
{
|
{
|
||||||
|
#if 0
|
||||||
// partial command buffered? have to go through the slow path
|
// partial command buffered? have to go through the slow path
|
||||||
if (!m_GP0_buffer.empty())
|
if (!m_GP0_buffer.empty())
|
||||||
{
|
{
|
||||||
|
@ -292,6 +290,10 @@ void GPU::DMAWrite(const u32* words, u32 word_count)
|
||||||
}
|
}
|
||||||
|
|
||||||
UpdateGPUSTAT();
|
UpdateGPUSTAT();
|
||||||
|
#else
|
||||||
|
for (u32 i = 0; i < word_count; i++)
|
||||||
|
WriteGP0(words[i]);
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
@ -433,7 +435,8 @@ void GPU::WriteGP0(u32 value)
|
||||||
Assert(m_GP0_buffer.size() <= 1048576);
|
Assert(m_GP0_buffer.size() <= 1048576);
|
||||||
|
|
||||||
const u32* command_ptr = m_GP0_buffer.data();
|
const u32* command_ptr = m_GP0_buffer.data();
|
||||||
if (HandleGP0Command(command_ptr, static_cast<u32>(m_GP0_buffer.size())))
|
const u32 command = m_GP0_buffer[0] >> 24;
|
||||||
|
if ((this->*s_GP0_command_handler_table[command])(command_ptr, static_cast<u32>(m_GP0_buffer.size())))
|
||||||
{
|
{
|
||||||
DebugAssert((command_ptr - m_GP0_buffer.data()) == m_GP0_buffer.size());
|
DebugAssert((command_ptr - m_GP0_buffer.data()) == m_GP0_buffer.size());
|
||||||
m_GP0_buffer.clear();
|
m_GP0_buffer.clear();
|
||||||
|
@ -442,130 +445,6 @@ void GPU::WriteGP0(u32 value)
|
||||||
UpdateGPUSTAT();
|
UpdateGPUSTAT();
|
||||||
}
|
}
|
||||||
|
|
||||||
bool GPU::HandleGP0Command(const u32*& command_ptr, u32 command_size)
|
|
||||||
{
|
|
||||||
const u8 command = Truncate8(command_ptr[0] >> 24);
|
|
||||||
|
|
||||||
if (command >= 0x20 && command <= 0x7F)
|
|
||||||
{
|
|
||||||
// Draw polygon
|
|
||||||
return HandleRenderCommand(command_ptr, command_size);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
const u32 param = command_ptr[0] & UINT32_C(0x00FFFFFF);
|
|
||||||
|
|
||||||
switch (command)
|
|
||||||
{
|
|
||||||
case 0x00: // NOP
|
|
||||||
case 0x01: // Clear cache
|
|
||||||
command_ptr++;
|
|
||||||
return true;
|
|
||||||
|
|
||||||
case 0x02: // Fill Rectangle
|
|
||||||
return HandleFillRectangleCommand(command_ptr, command_size);
|
|
||||||
|
|
||||||
case 0xA0: // Copy Rectangle CPU->VRAM
|
|
||||||
return HandleCopyRectangleCPUToVRAMCommand(command_ptr, command_size);
|
|
||||||
|
|
||||||
case 0xC0: // Copy Rectangle VRAM->CPU
|
|
||||||
return HandleCopyRectangleVRAMToCPUCommand(command_ptr, command_size);
|
|
||||||
|
|
||||||
case 0x80: // Copy Rectangle VRAM->VRAM
|
|
||||||
return HandleCopyRectangleVRAMToVRAMCommand(command_ptr, command_size);
|
|
||||||
|
|
||||||
case 0xE1: // Set draw mode
|
|
||||||
{
|
|
||||||
// 0..10 bits match GPUSTAT
|
|
||||||
const u32 MASK = ((UINT32_C(1) << 11) - 1);
|
|
||||||
m_GPUSTAT.bits = (m_GPUSTAT.bits & ~MASK) | param & MASK;
|
|
||||||
m_GPUSTAT.texture_disable = (param & (UINT32_C(1) << 11)) != 0;
|
|
||||||
m_render_state.texture_x_flip = (param & (UINT32_C(1) << 12)) != 0;
|
|
||||||
m_render_state.texture_y_flip = (param & (UINT32_C(1) << 13)) != 0;
|
|
||||||
Log_DebugPrintf("Set draw mode %08X", param);
|
|
||||||
command_ptr++;
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
case 0xE2: // set texture window
|
|
||||||
{
|
|
||||||
m_render_state.SetTextureWindow(param);
|
|
||||||
Log_DebugPrintf("Set texture window %02X %02X %02X %02X", m_render_state.texture_window_mask_x,
|
|
||||||
m_render_state.texture_window_mask_y, m_render_state.texture_window_offset_x,
|
|
||||||
m_render_state.texture_window_offset_y);
|
|
||||||
command_ptr++;
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
case 0xE3: // Set drawing area top left
|
|
||||||
{
|
|
||||||
const u32 left = param & UINT32_C(0x3FF);
|
|
||||||
const u32 top = (param >> 10) & UINT32_C(0x1FF);
|
|
||||||
Log_DebugPrintf("Set drawing area top-left: (%u, %u)", left, top);
|
|
||||||
if (m_drawing_area.left != left || m_drawing_area.top != top)
|
|
||||||
{
|
|
||||||
FlushRender();
|
|
||||||
|
|
||||||
m_drawing_area.left = left;
|
|
||||||
m_drawing_area.top = top;
|
|
||||||
UpdateDrawingArea();
|
|
||||||
}
|
|
||||||
command_ptr++;
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
case 0xE4: // Set drawing area bottom right
|
|
||||||
{
|
|
||||||
const u32 right = param & UINT32_C(0x3FF);
|
|
||||||
const u32 bottom = (param >> 10) & UINT32_C(0x1FF);
|
|
||||||
Log_DebugPrintf("Set drawing area bottom-right: (%u, %u)", m_drawing_area.right, m_drawing_area.bottom);
|
|
||||||
if (m_drawing_area.right != right || m_drawing_area.bottom != bottom)
|
|
||||||
{
|
|
||||||
FlushRender();
|
|
||||||
|
|
||||||
m_drawing_area.right = right;
|
|
||||||
m_drawing_area.bottom = bottom;
|
|
||||||
UpdateDrawingArea();
|
|
||||||
}
|
|
||||||
command_ptr++;
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
case 0xE5: // Set drawing offset
|
|
||||||
{
|
|
||||||
const s32 x = SignExtendN<11, s32>(param & UINT32_C(0x7FF));
|
|
||||||
const s32 y = SignExtendN<11, s32>((param >> 11) & UINT32_C(0x7FF));
|
|
||||||
Log_DebugPrintf("Set drawing offset (%d, %d)", m_drawing_offset.x, m_drawing_offset.y);
|
|
||||||
if (m_drawing_offset.x != x || m_drawing_offset.y != y)
|
|
||||||
{
|
|
||||||
FlushRender();
|
|
||||||
|
|
||||||
m_drawing_offset.x = x;
|
|
||||||
m_drawing_offset.y = y;
|
|
||||||
}
|
|
||||||
command_ptr++;
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
case 0xE6: // Mask bit setting
|
|
||||||
{
|
|
||||||
m_GPUSTAT.draw_set_mask_bit = (param & UINT32_C(0x01)) != 0;
|
|
||||||
m_GPUSTAT.draw_to_masked_pixels = (param & UINT32_C(0x01)) != 0;
|
|
||||||
Log_DebugPrintf("Set mask bit %u %u", BoolToUInt32(m_GPUSTAT.draw_set_mask_bit),
|
|
||||||
BoolToUInt32(m_GPUSTAT.draw_to_masked_pixels));
|
|
||||||
command_ptr++;
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
default:
|
|
||||||
{
|
|
||||||
Log_ErrorPrintf("Unimplemented GP0 command 0x%02X", command);
|
|
||||||
command_ptr++;
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void GPU::WriteGP1(u32 value)
|
void GPU::WriteGP1(u32 value)
|
||||||
{
|
{
|
||||||
|
@ -732,211 +611,6 @@ void GPU::HandleGetGPUInfoCommand(u32 value)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
bool GPU::HandleRenderCommand(const u32*& command_ptr, u32 command_size)
|
|
||||||
{
|
|
||||||
const RenderCommand rc{command_ptr[0]};
|
|
||||||
u8 words_per_vertex;
|
|
||||||
u32 num_vertices;
|
|
||||||
u32 total_words;
|
|
||||||
switch (rc.primitive)
|
|
||||||
{
|
|
||||||
case Primitive::Polygon:
|
|
||||||
{
|
|
||||||
// shaded vertices use the colour from the first word for the first vertex
|
|
||||||
words_per_vertex = 1 + BoolToUInt8(rc.texture_enable) + BoolToUInt8(rc.shading_enable);
|
|
||||||
num_vertices = rc.quad_polygon ? 4 : 3;
|
|
||||||
total_words = words_per_vertex * num_vertices + BoolToUInt8(!rc.shading_enable);
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
|
|
||||||
case Primitive::Line:
|
|
||||||
{
|
|
||||||
words_per_vertex = 1 + BoolToUInt8(rc.shading_enable);
|
|
||||||
if (rc.polyline)
|
|
||||||
{
|
|
||||||
// polyline goes until we hit the termination code
|
|
||||||
num_vertices = 0;
|
|
||||||
bool found_terminator = false;
|
|
||||||
for (u32 pos = 1 + BoolToUInt32(!rc.shading_enable); pos < command_size; pos += words_per_vertex)
|
|
||||||
{
|
|
||||||
if (command_ptr[pos] == 0x55555555)
|
|
||||||
{
|
|
||||||
found_terminator = true;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
num_vertices++;
|
|
||||||
}
|
|
||||||
if (!found_terminator)
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
num_vertices = 2;
|
|
||||||
}
|
|
||||||
|
|
||||||
total_words = words_per_vertex * num_vertices + BoolToUInt8(!rc.shading_enable);
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
|
|
||||||
case Primitive::Rectangle:
|
|
||||||
{
|
|
||||||
words_per_vertex =
|
|
||||||
2 + BoolToUInt8(rc.texture_enable) + BoolToUInt8(rc.rectangle_size == DrawRectangleSize::Variable);
|
|
||||||
num_vertices = 1;
|
|
||||||
total_words = words_per_vertex;
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
|
|
||||||
default:
|
|
||||||
UnreachableCode();
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (command_size < total_words)
|
|
||||||
return false;
|
|
||||||
|
|
||||||
static constexpr std::array<const char*, 4> primitive_names = {{"", "polygon", "line", "rectangle"}};
|
|
||||||
|
|
||||||
Log_DebugPrintf("Render %s %s %s %s %s (%u verts, %u words per vert)", rc.quad_polygon ? "four-point" : "three-point",
|
|
||||||
rc.transparency_enable ? "semi-transparent" : "opaque",
|
|
||||||
rc.texture_enable ? "textured" : "non-textured", rc.shading_enable ? "shaded" : "monochrome",
|
|
||||||
primitive_names[static_cast<u8>(rc.primitive.GetValue())], ZeroExtend32(num_vertices),
|
|
||||||
ZeroExtend32(words_per_vertex));
|
|
||||||
|
|
||||||
DispatchRenderCommand(rc, num_vertices, command_ptr);
|
|
||||||
command_ptr += total_words;
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool GPU::HandleFillRectangleCommand(const u32*& command_ptr, u32 command_size)
|
|
||||||
{
|
|
||||||
if (command_size < 3)
|
|
||||||
return false;
|
|
||||||
|
|
||||||
FlushRender();
|
|
||||||
|
|
||||||
const u32 color = command_ptr[0] & UINT32_C(0x00FFFFFF);
|
|
||||||
const u32 dst_x = command_ptr[1] & UINT32_C(0xFFFF);
|
|
||||||
const u32 dst_y = command_ptr[1] >> 16;
|
|
||||||
const u32 width = command_ptr[2] & UINT32_C(0xFFFF);
|
|
||||||
const u32 height = command_ptr[2] >> 16;
|
|
||||||
command_ptr += 3;
|
|
||||||
|
|
||||||
Log_DebugPrintf("Fill VRAM rectangle offset=(%u,%u), size=(%u,%u)", dst_x, dst_y, width, height);
|
|
||||||
|
|
||||||
// Drop higher precision when filling. Bit15 is set to 0.
|
|
||||||
// TODO: Force 8-bit color option.
|
|
||||||
const u16 color16 = RGBA8888ToRGBA5551(color);
|
|
||||||
|
|
||||||
FillVRAM(dst_x, dst_y, width, height, color16);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool GPU::HandleCopyRectangleCPUToVRAMCommand(const u32*& command_ptr, u32 command_size)
|
|
||||||
{
|
|
||||||
if (command_size < 3)
|
|
||||||
return false;
|
|
||||||
|
|
||||||
const u32 copy_width = command_ptr[2] & UINT32_C(0xFFFF);
|
|
||||||
const u32 copy_height = command_ptr[2] >> 16;
|
|
||||||
const u32 num_pixels = copy_width * copy_height;
|
|
||||||
const u32 num_words = 3 + ((num_pixels + 1) / 2);
|
|
||||||
if (command_size < num_words)
|
|
||||||
return false;
|
|
||||||
|
|
||||||
const u32 dst_x = command_ptr[1] & UINT32_C(0xFFFF);
|
|
||||||
const u32 dst_y = command_ptr[1] >> 16;
|
|
||||||
|
|
||||||
Log_DebugPrintf("Copy rectangle from CPU to VRAM offset=(%u,%u), size=(%u,%u)", dst_x, dst_y, copy_width,
|
|
||||||
copy_height);
|
|
||||||
|
|
||||||
if ((dst_x + copy_width) > VRAM_WIDTH || (dst_y + copy_height) > VRAM_HEIGHT)
|
|
||||||
{
|
|
||||||
Panic("Out of bounds VRAM copy");
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (m_debug_options.dump_cpu_to_vram_copies)
|
|
||||||
{
|
|
||||||
DumpVRAMToFile(SmallString::FromFormat("cpu_to_vram_copy_%u.png", s_cpu_to_vram_dump_id++), copy_width, copy_height,
|
|
||||||
sizeof(u16) * copy_width, &command_ptr[3], true);
|
|
||||||
}
|
|
||||||
|
|
||||||
FlushRender();
|
|
||||||
UpdateVRAM(dst_x, dst_y, copy_width, copy_height, &command_ptr[3]);
|
|
||||||
command_ptr += num_words;
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool GPU::HandleCopyRectangleVRAMToCPUCommand(const u32*& command_ptr, u32 command_size)
|
|
||||||
{
|
|
||||||
if (command_size < 3)
|
|
||||||
return false;
|
|
||||||
|
|
||||||
const u32 width = command_ptr[2] & UINT32_C(0xFFFF);
|
|
||||||
const u32 height = command_ptr[2] >> 16;
|
|
||||||
const u32 num_pixels = width * height;
|
|
||||||
const u32 num_words = ((num_pixels + 1) / 2);
|
|
||||||
const u32 src_x = command_ptr[1] & UINT32_C(0xFFFF);
|
|
||||||
const u32 src_y = command_ptr[1] >> 16;
|
|
||||||
command_ptr += 3;
|
|
||||||
|
|
||||||
Log_DebugPrintf("Copy rectangle from VRAM to CPU offset=(%u,%u), size=(%u,%u)", src_x, src_y, width, height);
|
|
||||||
|
|
||||||
if ((src_x + width) > VRAM_WIDTH || (src_y + height) > VRAM_HEIGHT)
|
|
||||||
{
|
|
||||||
Panic("Out of bounds VRAM copy");
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
// all rendering should be done first...
|
|
||||||
FlushRender();
|
|
||||||
|
|
||||||
// TODO: A better way of doing this..
|
|
||||||
std::vector<u32> temp(num_words);
|
|
||||||
ReadVRAM(src_x, src_y, width, height, temp.data());
|
|
||||||
for (const u32 bits : temp)
|
|
||||||
m_GPUREAD_buffer.push_back(bits);
|
|
||||||
|
|
||||||
if (m_debug_options.dump_vram_to_cpu_copies)
|
|
||||||
{
|
|
||||||
DumpVRAMToFile(SmallString::FromFormat("vram_to_cpu_copy_%u.png", s_cpu_to_vram_dump_id++), width, height,
|
|
||||||
sizeof(u16) * width, temp.data(), true);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Is this correct?
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool GPU::HandleCopyRectangleVRAMToVRAMCommand(const u32*& command_ptr, u32 command_size)
|
|
||||||
{
|
|
||||||
if (command_size < 4)
|
|
||||||
return false;
|
|
||||||
|
|
||||||
const u32 src_x = command_ptr[1] & UINT32_C(0xFFFF);
|
|
||||||
const u32 src_y = command_ptr[1] >> 16;
|
|
||||||
const u32 dst_x = command_ptr[2] & UINT32_C(0xFFFF);
|
|
||||||
const u32 dst_y = command_ptr[2] >> 16;
|
|
||||||
const u32 width = command_ptr[3] & UINT32_C(0xFFFF);
|
|
||||||
const u32 height = command_ptr[3] >> 16;
|
|
||||||
command_ptr += 4;
|
|
||||||
|
|
||||||
Log_DebugPrintf("Copy rectangle from VRAM to VRAM src=(%u,%u), dst=(%u,%u), size=(%u,%u)", src_x, src_y, dst_x, dst_y,
|
|
||||||
width, height);
|
|
||||||
|
|
||||||
if ((src_x + width) > VRAM_WIDTH || (src_y + height) > VRAM_HEIGHT || (dst_x + width) > VRAM_WIDTH ||
|
|
||||||
(dst_y + height) > VRAM_HEIGHT)
|
|
||||||
{
|
|
||||||
Panic("Out of bounds VRAM copy");
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
FlushRender();
|
|
||||||
CopyVRAM(src_x, src_y, dst_x, dst_y, width, height);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
void GPU::UpdateDisplay() {}
|
void GPU::UpdateDisplay() {}
|
||||||
|
|
||||||
void GPU::UpdateDrawingArea() {}
|
void GPU::UpdateDrawingArea() {}
|
||||||
|
|
|
@ -184,14 +184,6 @@ protected:
|
||||||
void WriteGP1(u32 value);
|
void WriteGP1(u32 value);
|
||||||
void HandleGetGPUInfoCommand(u32 value);
|
void HandleGetGPUInfoCommand(u32 value);
|
||||||
|
|
||||||
// Rendering commands, returns false if not enough data is provided
|
|
||||||
bool HandleGP0Command(const u32*& command_ptr, u32 command_size);
|
|
||||||
bool HandleRenderCommand(const u32*& command_ptr, u32 command_size);
|
|
||||||
bool HandleFillRectangleCommand(const u32*& command_ptr, u32 command_size);
|
|
||||||
bool HandleCopyRectangleCPUToVRAMCommand(const u32*& command_ptr, u32 command_size);
|
|
||||||
bool HandleCopyRectangleVRAMToCPUCommand(const u32*& command_ptr, u32 command_size);
|
|
||||||
bool HandleCopyRectangleVRAMToVRAMCommand(const u32*& command_ptr, u32 command_size);
|
|
||||||
|
|
||||||
// Rendering in the backend
|
// Rendering in the backend
|
||||||
virtual void UpdateDisplay();
|
virtual void UpdateDisplay();
|
||||||
virtual void UpdateDrawingArea();
|
virtual void UpdateDrawingArea();
|
||||||
|
@ -357,4 +349,28 @@ protected:
|
||||||
std::deque<u32> m_GPUREAD_buffer;
|
std::deque<u32> m_GPUREAD_buffer;
|
||||||
|
|
||||||
DebugOptions m_debug_options;
|
DebugOptions m_debug_options;
|
||||||
|
|
||||||
|
private:
|
||||||
|
using GP0CommandHandler = bool (GPU::*)(const u32*&, u32);
|
||||||
|
using GP0CommandHandlerTable = std::array<GP0CommandHandler, 256>;
|
||||||
|
static constexpr GP0CommandHandlerTable GenerateGP0CommandHandlerTable();
|
||||||
|
|
||||||
|
// Rendering commands, returns false if not enough data is provided
|
||||||
|
bool HandleUnknownGP0Command(const u32*& command_ptr, u32 command_size);
|
||||||
|
bool HandleNOPCommand(const u32*& command_ptr, u32 command_size);
|
||||||
|
bool HandleClearCacheCommand(const u32*& command_ptr, u32 command_size);
|
||||||
|
bool HandleInterruptRequestCommand(const u32*& command_ptr, u32 command_size);
|
||||||
|
bool HandleSetDrawModeCommand(const u32*& command_ptr, u32 command_size);
|
||||||
|
bool HandleSetTextureWindowCommand(const u32*& command_ptr, u32 command_size);
|
||||||
|
bool HandleSetDrawingAreaTopLeftCommand(const u32*& command_ptr, u32 command_size);
|
||||||
|
bool HandleSetDrawingAreaBottomRightCommand(const u32*& command_ptr, u32 command_size);
|
||||||
|
bool HandleSetDrawingOffsetCommand(const u32*& command_ptr, u32 command_size);
|
||||||
|
bool HandleSetMaskBitCommand(const u32*& command_ptr, u32 command_size);
|
||||||
|
bool HandleRenderCommand(const u32*& command_ptr, u32 command_size);
|
||||||
|
bool HandleFillRectangleCommand(const u32*& command_ptr, u32 command_size);
|
||||||
|
bool HandleCopyRectangleCPUToVRAMCommand(const u32*& command_ptr, u32 command_size);
|
||||||
|
bool HandleCopyRectangleVRAMToCPUCommand(const u32*& command_ptr, u32 command_size);
|
||||||
|
bool HandleCopyRectangleVRAMToVRAMCommand(const u32*& command_ptr, u32 command_size);
|
||||||
|
|
||||||
|
static const GP0CommandHandlerTable s_GP0_command_handler_table;
|
||||||
};
|
};
|
||||||
|
|
368
src/core/gpu_commands.cpp
Normal file
368
src/core/gpu_commands.cpp
Normal file
|
@ -0,0 +1,368 @@
|
||||||
|
#include "YBaseLib/Log.h"
|
||||||
|
#include "YBaseLib/String.h"
|
||||||
|
#include "gpu.h"
|
||||||
|
#include "interrupt_controller.h"
|
||||||
|
Log_SetChannel(GPU);
|
||||||
|
|
||||||
|
static u32 s_cpu_to_vram_dump_id = 1;
|
||||||
|
static u32 s_vram_to_cpu_dump_id = 1;
|
||||||
|
|
||||||
|
constexpr GPU::GP0CommandHandlerTable GPU::GenerateGP0CommandHandlerTable()
|
||||||
|
{
|
||||||
|
GP0CommandHandlerTable table = {};
|
||||||
|
for (u32 i = 0; i < static_cast<u32>(table.size()); i++)
|
||||||
|
table[i] = &GPU::HandleUnknownGP0Command;
|
||||||
|
table[0x00] = &GPU::HandleNOPCommand;
|
||||||
|
table[0x01] = &GPU::HandleClearCacheCommand;
|
||||||
|
table[0x02] = &GPU::HandleFillRectangleCommand;
|
||||||
|
table[0x03] = &GPU::HandleNOPCommand;
|
||||||
|
for (u32 i = 0x04; i <= 0x1E; i++)
|
||||||
|
table[i] = &GPU::HandleNOPCommand;
|
||||||
|
table[0x1F] = &GPU::HandleInterruptRequestCommand;
|
||||||
|
for (u32 i = 0x20; i <= 0x7F; i++)
|
||||||
|
table[i] = &GPU::HandleRenderCommand;
|
||||||
|
table[0xE0] = &GPU::HandleNOPCommand;
|
||||||
|
table[0xE1] = &GPU::HandleSetDrawModeCommand;
|
||||||
|
table[0xE2] = &GPU::HandleSetTextureWindowCommand;
|
||||||
|
table[0xE3] = &GPU::HandleSetDrawingAreaTopLeftCommand;
|
||||||
|
table[0xE4] = &GPU::HandleSetDrawingAreaBottomRightCommand;
|
||||||
|
table[0xE5] = &GPU::HandleSetDrawingOffsetCommand;
|
||||||
|
table[0xE6] = &GPU::HandleSetMaskBitCommand;
|
||||||
|
for (u32 i = 0xE7; i <= 0xEF; i++)
|
||||||
|
table[i] = &GPU::HandleNOPCommand;
|
||||||
|
for (u32 i = 0x80; i <= 0x9F; i++)
|
||||||
|
table[i] = &GPU::HandleCopyRectangleVRAMToVRAMCommand;
|
||||||
|
for (u32 i = 0xA0; i <= 0xBF; i++)
|
||||||
|
table[i] = &GPU::HandleCopyRectangleCPUToVRAMCommand;
|
||||||
|
for (u32 i = 0xC0; i <= 0xDF; i++)
|
||||||
|
table[i] = &GPU::HandleCopyRectangleVRAMToCPUCommand;
|
||||||
|
|
||||||
|
return table;
|
||||||
|
}
|
||||||
|
|
||||||
|
constexpr GPU::GP0CommandHandlerTable GPU::s_GP0_command_handler_table = GPU::GenerateGP0CommandHandlerTable();
|
||||||
|
|
||||||
|
bool GPU::HandleUnknownGP0Command(const u32*& command_ptr, u32 command_size)
|
||||||
|
{
|
||||||
|
const u32 command = *(command_ptr++) >> 24;
|
||||||
|
Log_ErrorPrintf("Unimplemented GP0 command 0x%02X", command);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool GPU::HandleNOPCommand(const u32*& command_ptr, u32 command_size)
|
||||||
|
{
|
||||||
|
command_ptr++;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool GPU::HandleClearCacheCommand(const u32*& command_ptr, u32 command_size)
|
||||||
|
{
|
||||||
|
Log_DebugPrintf("GP0 clear cache");
|
||||||
|
command_ptr++;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool GPU::HandleInterruptRequestCommand(const u32*& command_ptr, u32 command_size)
|
||||||
|
{
|
||||||
|
Log_WarningPrintf("GP0 interrupt request");
|
||||||
|
if (!m_GPUSTAT.interrupt_request)
|
||||||
|
{
|
||||||
|
m_GPUSTAT.interrupt_request = true;
|
||||||
|
m_interrupt_controller->InterruptRequest(InterruptController::IRQ::GPU);
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool GPU::HandleSetDrawModeCommand(const u32*& command_ptr, u32 command_size)
|
||||||
|
{
|
||||||
|
const u32 param = *(command_ptr++) & UINT32_C(0x00FFFFFF);
|
||||||
|
|
||||||
|
// 0..10 bits match GPUSTAT
|
||||||
|
const u32 MASK = ((UINT32_C(1) << 11) - 1);
|
||||||
|
m_GPUSTAT.bits = (m_GPUSTAT.bits & ~MASK) | param & MASK;
|
||||||
|
m_GPUSTAT.texture_disable = (param & (UINT32_C(1) << 11)) != 0;
|
||||||
|
m_render_state.texture_x_flip = (param & (UINT32_C(1) << 12)) != 0;
|
||||||
|
m_render_state.texture_y_flip = (param & (UINT32_C(1) << 13)) != 0;
|
||||||
|
Log_DebugPrintf("Set draw mode %08X", param);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool GPU::HandleSetTextureWindowCommand(const u32*& command_ptr, u32 command_size)
|
||||||
|
{
|
||||||
|
const u32 param = *(command_ptr++) & UINT32_C(0x00FFFFFF);
|
||||||
|
m_render_state.SetTextureWindow(param);
|
||||||
|
Log_DebugPrintf("Set texture window %02X %02X %02X %02X", m_render_state.texture_window_mask_x,
|
||||||
|
m_render_state.texture_window_mask_y, m_render_state.texture_window_offset_x,
|
||||||
|
m_render_state.texture_window_offset_y);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool GPU::HandleSetDrawingAreaTopLeftCommand(const u32*& command_ptr, u32 command_size)
|
||||||
|
{
|
||||||
|
const u32 param = *(command_ptr++) & UINT32_C(0x00FFFFFF);
|
||||||
|
const u32 left = param & UINT32_C(0x3FF);
|
||||||
|
const u32 top = (param >> 10) & UINT32_C(0x1FF);
|
||||||
|
Log_DebugPrintf("Set drawing area top-left: (%u, %u)", left, top);
|
||||||
|
if (m_drawing_area.left != left || m_drawing_area.top != top)
|
||||||
|
{
|
||||||
|
FlushRender();
|
||||||
|
|
||||||
|
m_drawing_area.left = left;
|
||||||
|
m_drawing_area.top = top;
|
||||||
|
UpdateDrawingArea();
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool GPU::HandleSetDrawingAreaBottomRightCommand(const u32*& command_ptr, u32 command_size)
|
||||||
|
{
|
||||||
|
const u32 param = *(command_ptr++) & UINT32_C(0x00FFFFFF);
|
||||||
|
|
||||||
|
const u32 right = param & UINT32_C(0x3FF);
|
||||||
|
const u32 bottom = (param >> 10) & UINT32_C(0x1FF);
|
||||||
|
Log_DebugPrintf("Set drawing area bottom-right: (%u, %u)", m_drawing_area.right, m_drawing_area.bottom);
|
||||||
|
if (m_drawing_area.right != right || m_drawing_area.bottom != bottom)
|
||||||
|
{
|
||||||
|
FlushRender();
|
||||||
|
|
||||||
|
m_drawing_area.right = right;
|
||||||
|
m_drawing_area.bottom = bottom;
|
||||||
|
UpdateDrawingArea();
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool GPU::HandleSetDrawingOffsetCommand(const u32*& command_ptr, u32 command_size)
|
||||||
|
{
|
||||||
|
const u32 param = *(command_ptr++) & UINT32_C(0x00FFFFFF);
|
||||||
|
const s32 x = SignExtendN<11, s32>(param & UINT32_C(0x7FF));
|
||||||
|
const s32 y = SignExtendN<11, s32>((param >> 11) & UINT32_C(0x7FF));
|
||||||
|
Log_DebugPrintf("Set drawing offset (%d, %d)", m_drawing_offset.x, m_drawing_offset.y);
|
||||||
|
if (m_drawing_offset.x != x || m_drawing_offset.y != y)
|
||||||
|
{
|
||||||
|
FlushRender();
|
||||||
|
|
||||||
|
m_drawing_offset.x = x;
|
||||||
|
m_drawing_offset.y = y;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool GPU::HandleSetMaskBitCommand(const u32*& command_ptr, u32 command_size)
|
||||||
|
{
|
||||||
|
const u32 param = *(command_ptr++) & UINT32_C(0x00FFFFFF);
|
||||||
|
|
||||||
|
m_GPUSTAT.draw_set_mask_bit = (param & UINT32_C(0x01)) != 0;
|
||||||
|
m_GPUSTAT.draw_to_masked_pixels = (param & UINT32_C(0x01)) != 0;
|
||||||
|
Log_DebugPrintf("Set mask bit %u %u", BoolToUInt32(m_GPUSTAT.draw_set_mask_bit),
|
||||||
|
BoolToUInt32(m_GPUSTAT.draw_to_masked_pixels));
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool GPU::HandleRenderCommand(const u32*& command_ptr, u32 command_size)
|
||||||
|
{
|
||||||
|
const RenderCommand rc{command_ptr[0]};
|
||||||
|
u8 words_per_vertex;
|
||||||
|
u32 num_vertices;
|
||||||
|
u32 total_words;
|
||||||
|
switch (rc.primitive)
|
||||||
|
{
|
||||||
|
case Primitive::Polygon:
|
||||||
|
{
|
||||||
|
// shaded vertices use the colour from the first word for the first vertex
|
||||||
|
words_per_vertex = 1 + BoolToUInt8(rc.texture_enable) + BoolToUInt8(rc.shading_enable);
|
||||||
|
num_vertices = rc.quad_polygon ? 4 : 3;
|
||||||
|
total_words = words_per_vertex * num_vertices + BoolToUInt8(!rc.shading_enable);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case Primitive::Line:
|
||||||
|
{
|
||||||
|
words_per_vertex = 1 + BoolToUInt8(rc.shading_enable);
|
||||||
|
if (rc.polyline)
|
||||||
|
{
|
||||||
|
// polyline goes until we hit the termination code
|
||||||
|
num_vertices = 0;
|
||||||
|
bool found_terminator = false;
|
||||||
|
for (u32 pos = 1 + BoolToUInt32(!rc.shading_enable); pos < command_size; pos += words_per_vertex)
|
||||||
|
{
|
||||||
|
if (command_ptr[pos] == 0x55555555)
|
||||||
|
{
|
||||||
|
found_terminator = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
num_vertices++;
|
||||||
|
}
|
||||||
|
if (!found_terminator)
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
num_vertices = 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
total_words = words_per_vertex * num_vertices + BoolToUInt8(!rc.shading_enable);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case Primitive::Rectangle:
|
||||||
|
{
|
||||||
|
words_per_vertex =
|
||||||
|
2 + BoolToUInt8(rc.texture_enable) + BoolToUInt8(rc.rectangle_size == DrawRectangleSize::Variable);
|
||||||
|
num_vertices = 1;
|
||||||
|
total_words = words_per_vertex;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
UnreachableCode();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (command_size < total_words)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
static constexpr std::array<const char*, 4> primitive_names = {{"", "polygon", "line", "rectangle"}};
|
||||||
|
|
||||||
|
Log_DebugPrintf("Render %s %s %s %s %s (%u verts, %u words per vert)", rc.quad_polygon ? "four-point" : "three-point",
|
||||||
|
rc.transparency_enable ? "semi-transparent" : "opaque",
|
||||||
|
rc.texture_enable ? "textured" : "non-textured", rc.shading_enable ? "shaded" : "monochrome",
|
||||||
|
primitive_names[static_cast<u8>(rc.primitive.GetValue())], ZeroExtend32(num_vertices),
|
||||||
|
ZeroExtend32(words_per_vertex));
|
||||||
|
|
||||||
|
DispatchRenderCommand(rc, num_vertices, command_ptr);
|
||||||
|
command_ptr += total_words;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool GPU::HandleFillRectangleCommand(const u32*& command_ptr, u32 command_size)
|
||||||
|
{
|
||||||
|
if (command_size < 3)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
FlushRender();
|
||||||
|
|
||||||
|
const u32 color = command_ptr[0] & UINT32_C(0x00FFFFFF);
|
||||||
|
const u32 dst_x = command_ptr[1] & UINT32_C(0xFFFF);
|
||||||
|
const u32 dst_y = command_ptr[1] >> 16;
|
||||||
|
const u32 width = command_ptr[2] & UINT32_C(0xFFFF);
|
||||||
|
const u32 height = command_ptr[2] >> 16;
|
||||||
|
command_ptr += 3;
|
||||||
|
|
||||||
|
Log_DebugPrintf("Fill VRAM rectangle offset=(%u,%u), size=(%u,%u)", dst_x, dst_y, width, height);
|
||||||
|
|
||||||
|
// Drop higher precision when filling. Bit15 is set to 0.
|
||||||
|
// TODO: Force 8-bit color option.
|
||||||
|
const u16 color16 = RGBA8888ToRGBA5551(color);
|
||||||
|
|
||||||
|
FillVRAM(dst_x, dst_y, width, height, color16);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool GPU::HandleCopyRectangleCPUToVRAMCommand(const u32*& command_ptr, u32 command_size)
|
||||||
|
{
|
||||||
|
if (command_size < 3)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
const u32 copy_width = command_ptr[2] & UINT32_C(0xFFFF);
|
||||||
|
const u32 copy_height = command_ptr[2] >> 16;
|
||||||
|
const u32 num_pixels = copy_width * copy_height;
|
||||||
|
const u32 num_words = 3 + ((num_pixels + 1) / 2);
|
||||||
|
if (command_size < num_words)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
const u32 dst_x = command_ptr[1] & UINT32_C(0xFFFF);
|
||||||
|
const u32 dst_y = command_ptr[1] >> 16;
|
||||||
|
|
||||||
|
Log_DebugPrintf("Copy rectangle from CPU to VRAM offset=(%u,%u), size=(%u,%u)", dst_x, dst_y, copy_width,
|
||||||
|
copy_height);
|
||||||
|
|
||||||
|
if ((dst_x + copy_width) > VRAM_WIDTH || (dst_y + copy_height) > VRAM_HEIGHT)
|
||||||
|
{
|
||||||
|
Panic("Out of bounds VRAM copy");
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (m_debug_options.dump_cpu_to_vram_copies)
|
||||||
|
{
|
||||||
|
DumpVRAMToFile(SmallString::FromFormat("cpu_to_vram_copy_%u.png", s_cpu_to_vram_dump_id++), copy_width, copy_height,
|
||||||
|
sizeof(u16) * copy_width, &command_ptr[3], true);
|
||||||
|
}
|
||||||
|
|
||||||
|
FlushRender();
|
||||||
|
UpdateVRAM(dst_x, dst_y, copy_width, copy_height, &command_ptr[3]);
|
||||||
|
command_ptr += num_words;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool GPU::HandleCopyRectangleVRAMToCPUCommand(const u32*& command_ptr, u32 command_size)
|
||||||
|
{
|
||||||
|
if (command_size < 3)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
const u32 width = command_ptr[2] & UINT32_C(0xFFFF);
|
||||||
|
const u32 height = command_ptr[2] >> 16;
|
||||||
|
const u32 num_pixels = width * height;
|
||||||
|
const u32 num_words = ((num_pixels + 1) / 2);
|
||||||
|
const u32 src_x = command_ptr[1] & UINT32_C(0xFFFF);
|
||||||
|
const u32 src_y = command_ptr[1] >> 16;
|
||||||
|
command_ptr += 3;
|
||||||
|
|
||||||
|
Log_DebugPrintf("Copy rectangle from VRAM to CPU offset=(%u,%u), size=(%u,%u)", src_x, src_y, width, height);
|
||||||
|
|
||||||
|
if ((src_x + width) > VRAM_WIDTH || (src_y + height) > VRAM_HEIGHT)
|
||||||
|
{
|
||||||
|
Panic("Out of bounds VRAM copy");
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// all rendering should be done first...
|
||||||
|
FlushRender();
|
||||||
|
|
||||||
|
// TODO: A better way of doing this..
|
||||||
|
std::vector<u32> temp(num_words);
|
||||||
|
ReadVRAM(src_x, src_y, width, height, temp.data());
|
||||||
|
for (const u32 bits : temp)
|
||||||
|
m_GPUREAD_buffer.push_back(bits);
|
||||||
|
|
||||||
|
if (m_debug_options.dump_vram_to_cpu_copies)
|
||||||
|
{
|
||||||
|
DumpVRAMToFile(SmallString::FromFormat("vram_to_cpu_copy_%u.png", s_cpu_to_vram_dump_id++), width, height,
|
||||||
|
sizeof(u16) * width, temp.data(), true);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Is this correct?
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool GPU::HandleCopyRectangleVRAMToVRAMCommand(const u32*& command_ptr, u32 command_size)
|
||||||
|
{
|
||||||
|
if (command_size < 4)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
const u32 src_x = command_ptr[1] & UINT32_C(0xFFFF);
|
||||||
|
const u32 src_y = command_ptr[1] >> 16;
|
||||||
|
const u32 dst_x = command_ptr[2] & UINT32_C(0xFFFF);
|
||||||
|
const u32 dst_y = command_ptr[2] >> 16;
|
||||||
|
const u32 width = command_ptr[3] & UINT32_C(0xFFFF);
|
||||||
|
const u32 height = command_ptr[3] >> 16;
|
||||||
|
command_ptr += 4;
|
||||||
|
|
||||||
|
Log_DebugPrintf("Copy rectangle from VRAM to VRAM src=(%u,%u), dst=(%u,%u), size=(%u,%u)", src_x, src_y, dst_x, dst_y,
|
||||||
|
width, height);
|
||||||
|
|
||||||
|
if ((src_x + width) > VRAM_WIDTH || (src_y + height) > VRAM_HEIGHT || (dst_x + width) > VRAM_WIDTH ||
|
||||||
|
(dst_y + height) > VRAM_HEIGHT)
|
||||||
|
{
|
||||||
|
Panic("Out of bounds VRAM copy");
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
FlushRender();
|
||||||
|
CopyVRAM(src_x, src_y, dst_x, dst_y, width, height);
|
||||||
|
return true;
|
||||||
|
}
|
Loading…
Reference in a new issue