diff --git a/src/pse/gpu.cpp b/src/pse/gpu.cpp index 08362abeb..148fe86cb 100644 --- a/src/pse/gpu.cpp +++ b/src/pse/gpu.cpp @@ -4,6 +4,14 @@ #include "dma.h" Log_SetChannel(GPU); +static constexpr s32 S11ToS32(u32 value) +{ + if (value & (UINT16_C(1) << 10)) + return static_cast(UINT32_C(0xFFFFF800) | value); + else + return value; +} + GPU::GPU() = default; GPU::~GPU() = default; @@ -80,7 +88,17 @@ u32 GPU::DMARead() void GPU::DMAWrite(u32 value) { - Log_ErrorPrintf("GPU DMA Write %08X", value); + switch (m_GPUSTAT.dma_direction) + { + case DMADirection::CPUtoGP0: + WriteGP0(value); + break; + + default: + Log_ErrorPrintf("Unhandled GPU DMA write mode %u for value %08X", + static_cast(m_GPUSTAT.dma_direction.GetValue()), value); + break; + } } u32 GPU::ReadGPUREAD() @@ -91,8 +109,96 @@ u32 GPU::ReadGPUREAD() void GPU::WriteGP0(u32 value) { - const u8 command = Truncate8(value >> 24); - Log_ErrorPrintf("Unimplemented GP0 command 0x%02X", command); + Assert(m_GP0_command_length < MAX_GP0_COMMAND_LENGTH); + + m_GP0_command[m_GP0_command_length++] = value; + + const u8 command = Truncate8(m_GP0_command[0] >> 24); + const u32 param = m_GP0_command[0] & UINT32_C(0x00FFFFFF); + switch (command) + { + case 0x00: // NOP + break; + + 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_texture_config.x_flip = (param & (UINT32_C(1) << 12)) != 0; + m_texture_config.y_flip = (param & (UINT32_C(1) << 13)) != 0; + Log_DebugPrintf("Set draw mode %08X", param); + } + break; + + case 0xE2: // set texture window + { + m_texture_config.window_mask_x = param & UINT32_C(0x1F); + m_texture_config.window_mask_y = (param >> 5) & UINT32_C(0x1F); + m_texture_config.window_offset_x = (param >> 10) & UINT32_C(0x1F); + m_texture_config.window_offset_y = (param >> 15) & UINT32_C(0x1F); + Log_DebugPrintf("Set texture window %02X %02X %02X %02X", m_texture_config.window_mask_x, + m_texture_config.window_mask_y, m_texture_config.window_offset_x, + m_texture_config.window_offset_y); + } + break; + + case 0xE3: // Set drawing area top left + { + m_drawing_area.top_left_x = param & UINT32_C(0x3FF); + m_drawing_area.top_left_y = (param >> 10) & UINT32_C(0x1FF); + Log_DebugPrintf("Set drawing area top-left: (%u, %u)", m_drawing_area.top_left_x, m_drawing_area.top_left_y); + } + break; + + case 0xE4: // Set drawing area bottom right + { + m_drawing_area.bottom_right_x = param & UINT32_C(0x3FF); + m_drawing_area.bottom_right_y = (param >> 10) & UINT32_C(0x1FF); + Log_DebugPrintf("Set drawing area bottom-right: (%u, %u)", m_drawing_area.bottom_right_x, + m_drawing_area.bottom_right_y); + } + break; + + case 0xE5: // Set drawing offset + { + m_drawing_offset.x = S11ToS32(param & UINT32_C(0x7FF)); + m_drawing_offset.y = S11ToS32((param >> 11) & UINT32_C(0x7FF)); + Log_DebugPrintf("Set drawing offset (%d, %d)", m_drawing_offset.x, m_drawing_offset.y); + } + break; + + 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)); + } + break; + + default: + { + if (command < 0x20) + { + } + else if (command < 0x40) + { + // Draw polygon + if (!HandleRenderPolygonCommand()) + return; + + break; + } + + Log_ErrorPrintf("Unimplemented GP0 command 0x%02X", command); + } + break; + } + + m_GP0_command.fill(UINT32_C(0)); + m_GP0_command_length = 0; } void GPU::WriteGP1(u32 value) @@ -114,3 +220,26 @@ void GPU::WriteGP1(u32 value) break; } } + +bool GPU::HandleRenderPolygonCommand() +{ + const u8 command = Truncate8(m_GP0_command[0] >> 24); + const bool semi_transparent = !!(command & 0x02); + const bool textured = !!(command & 0x04); + const bool four_points = !!(command & 0x08); + const bool shaded = !!(command & 0x10); + + // shaded vertices use the colour from the first word for the first vertex + const u8 words_per_vertex = 1 + BoolToUInt8(textured) + BoolToUInt8(shaded); + const u8 num_vertices = four_points ? 4 : 3; + const u8 total_words = words_per_vertex * num_vertices + BoolToUInt8(!shaded); + if (m_GP0_command_length < total_words) + return false; + + Log_DebugPrintf("Render %s %s %s %s polygon (%u verts, %u words per vert)", + four_points ? "four-point" : "three-point", semi_transparent ? "semi-transparent" : "opaque", + textured ? "textured" : "non-textured", shaded ? "shaded" : "monochrome", ZeroExtend32(num_vertices), + ZeroExtend32(words_per_vertex)); + + return true; +} diff --git a/src/pse/gpu.h b/src/pse/gpu.h index 857a534da..4e3ba49ad 100644 --- a/src/pse/gpu.h +++ b/src/pse/gpu.h @@ -23,6 +23,8 @@ public: void DMAWrite(u32 value); private: + static constexpr u32 MAX_GP0_COMMAND_LENGTH = 12; + enum class DMADirection : u32 { Off = 0, @@ -37,6 +39,9 @@ private: void WriteGP0(u32 value); void WriteGP1(u32 value); + // Rendering commands, returns false if not enough data is provided + bool HandleRenderPolygonCommand(); + Bus* m_bus = nullptr; DMA* m_dma = nullptr; @@ -69,4 +74,29 @@ private: BitField dma_direction; BitField drawing_even_line; } m_GPUSTAT = {}; + + struct TextureConfig + { + u8 window_mask_x; // in 8 pixel steps + u8 window_mask_y; // in 8 pixel steps + u8 window_offset_x; // in 8 pixel steps + u8 window_offset_y; // in 8 pixel steps + bool x_flip; + bool y_flip; + } m_texture_config = {}; + + struct DrawingArea + { + u32 top_left_x, top_left_y; + u32 bottom_right_x, bottom_right_y; + } m_drawing_area = {}; + + struct DrawingOffset + { + s32 x; + s32 y; + } m_drawing_offset = {}; + + std::array m_GP0_command = {}; + u32 m_GP0_command_length = 0; };