@@ -3,6 +3,8 @@
#include "cpu_core.h"
#include "cpu_disasm.h"
#include "gte.h"
#include "pgxp.h"
#include "settings.h"
Log_SetChannel(CPU::Recompiler);

// TODO: Turn load+sext/zext into a single signed/unsigned load
@@ -1115,19 +1117,32 @@ bool CodeGenerator::Compile_Load(const CodeBlockInstruction& cbi)
{
case InstructionOp::lb:
case InstructionOp::lbu:
{
result = EmitLoadGuestMemory(cbi, address, RegSize_8);
ConvertValueSizeInPlace(&result, RegSize_32, (cbi.instruction.op == InstructionOp::lb));
break;
if (g_settings.gpu_pgxp_enable)
EmitFunctionCall(nullptr, PGXP::CPU_LBx, Value::FromConstantU32(cbi.instruction.bits), result, address);
}
break;

case InstructionOp::lh:
case InstructionOp::lhu:
{
result = EmitLoadGuestMemory(cbi, address, RegSize_16);
ConvertValueSizeInPlace(&result, RegSize_32, (cbi.instruction.op == InstructionOp::lh));
break;

if (g_settings.gpu_pgxp_enable)
EmitFunctionCall(nullptr, PGXP::CPU_LHx, Value::FromConstantU32(cbi.instruction.bits), result, address);
}
break;

case InstructionOp::lw:
{
result = EmitLoadGuestMemory(cbi, address, RegSize_32);
break;
if (g_settings.gpu_pgxp_enable)
EmitFunctionCall(nullptr, PGXP::CPU_LW, Value::FromConstantU32(cbi.instruction.bits), result, address);
}
break;

default:
UnreachableCode();
@@ -1153,16 +1168,34 @@ bool CodeGenerator::Compile_Store(const CodeBlockInstruction& cbi)
switch (cbi.instruction.op)
{
case InstructionOp::sb:
{
EmitStoreGuestMemory(cbi, address, value.ViewAsSize(RegSize_8));
break;
if (g_settings.gpu_pgxp_enable)
{
EmitFunctionCall(nullptr, PGXP::CPU_SB, Value::FromConstantU32(cbi.instruction.bits),
value.ViewAsSize(RegSize_8), address);
}
}
break;

case InstructionOp::sh:
{
EmitStoreGuestMemory(cbi, address, value.ViewAsSize(RegSize_16));
break;
if (g_settings.gpu_pgxp_enable)
{
EmitFunctionCall(nullptr, PGXP::CPU_SH, Value::FromConstantU32(cbi.instruction.bits),
value.ViewAsSize(RegSize_16), address);
}
}
break;

case InstructionOp::sw:
{
EmitStoreGuestMemory(cbi, address, value);
break;
if (g_settings.gpu_pgxp_enable)
EmitFunctionCall(nullptr, PGXP::CPU_SW, Value::FromConstantU32(cbi.instruction.bits), value, address);
}
break;

default:
UnreachableCode();
@@ -1827,11 +1860,17 @@ bool CodeGenerator::Compile_cop2(const CodeBlockInstruction& cbi)
{
Value value = EmitLoadGuestMemory(cbi, address, RegSize_32);
DoGTERegisterWrite(reg, value);

if (g_settings.gpu_pgxp_enable)
EmitFunctionCall(nullptr, PGXP::CPU_LWC2, Value::FromConstantU32(cbi.instruction.bits), value, address);
}
else
{
Value value = DoGTERegisterRead(reg);
EmitStoreGuestMemory(cbi, address, value);

if (g_settings.gpu_pgxp_enable)
EmitFunctionCall(nullptr, PGXP::CPU_SWC2, Value::FromConstantU32(cbi.instruction.bits), value, address);
}

InstructionEpilogue(cbi);
@@ -1851,7 +1890,19 @@ bool CodeGenerator::Compile_cop2(const CodeBlockInstruction& cbi)
((cbi.instruction.cop.CommonOp() == CopCommonInstruction::cfcn) ? 32 : 0);

InstructionPrologue(cbi, 1);
m_register_cache.WriteGuestRegisterDelayed(cbi.instruction.r.rt, DoGTERegisterRead(reg));

Value value = DoGTERegisterRead(reg);

// PGXP done first here before ownership is transferred.
if (g_settings.gpu_pgxp_enable)
{
EmitFunctionCall(
nullptr, (cbi.instruction.cop.CommonOp() == CopCommonInstruction::cfcn) ? PGXP::CPU_CFC2 : PGXP::CPU_MFC2,
Value::FromConstantU32(cbi.instruction.bits), value, value);
}

m_register_cache.WriteGuestRegisterDelayed(cbi.instruction.r.rt, std::move(value));

InstructionEpilogue(cbi);
return true;
}
@@ -1863,7 +1914,17 @@ bool CodeGenerator::Compile_cop2(const CodeBlockInstruction& cbi)
((cbi.instruction.cop.CommonOp() == CopCommonInstruction::ctcn) ? 32 : 0);

InstructionPrologue(cbi, 1);
DoGTERegisterWrite(reg, m_register_cache.ReadGuestRegister(cbi.instruction.r.rt));

Value value = m_register_cache.ReadGuestRegister(cbi.instruction.r.rt);
DoGTERegisterWrite(reg, value);

if (g_settings.gpu_pgxp_enable)
{
EmitFunctionCall(
nullptr, (cbi.instruction.cop.CommonOp() == CopCommonInstruction::ctcn) ? PGXP::CPU_CTC2 : PGXP::CPU_MTC2,
Value::FromConstantU32(cbi.instruction.bits), value, value);
}

InstructionEpilogue(cbi);
return true;
}
@@ -429,7 +429,8 @@ void DMA::UnhaltTransfer(TickCount ticks)
TickCount DMA::TransferMemoryToDevice(Channel channel, u32 address, u32 increment, u32 word_count)
{
const u32* src_pointer = reinterpret_cast<u32*>(Bus::g_ram + address);
if (static_cast<s32>(increment) < 0 || ((address + (increment * word_count)) & ADDRESS_MASK) <= address)
if (channel != Channel::GPU &&
(static_cast<s32>(increment) < 0 || ((address + (increment * word_count)) & ADDRESS_MASK) <= address))
{
// Use temp buffer if it's wrapping around
if (m_transfer_buffer.size() < word_count)
@@ -447,8 +448,21 @@ TickCount DMA::TransferMemoryToDevice(Channel channel, u32 address, u32 incremen
switch (channel)
{
case Channel::GPU:
g_gpu->DMAWrite(src_pointer, word_count);
break;
{
if (g_gpu->BeginDMAWrite())
{
u8* ram_pointer = Bus::g_ram;
for (u32 i = 0; i < word_count; i++)
{
u32 value;
std::memcpy(&value, &ram_pointer[address], sizeof(u32));
g_gpu->DMAWrite(address, value);
address = (address + increment) & ADDRESS_MASK;
}
g_gpu->EndDMAWrite();
}
}
break;

case Channel::SPU:
g_spu.DMAWrite(src_pointer, word_count);
@@ -349,32 +349,17 @@ void GPU::DMARead(u32* words, u32 word_count)
words[i] = ReadGPUREAD();
}

void GPU::DMAWrite(const u32* words, u32 word_count)
void GPU::EndDMAWrite()
{
switch (m_GPUSTAT.dma_direction)
m_fifo_pushed = true;
if (!m_syncing)
{
case DMADirection::CPUtoGP0:
{
m_fifo.PushRange(words, word_count);
m_fifo_pushed = true;
if (!m_syncing)
{
ExecuteCommands();
UpdateCommandTickEvent();
}
else
{
UpdateDMARequest();
}
}
break;

default:
{
Log_ErrorPrintf("Unhandled GPU DMA write mode %u for %u words",
static_cast<u32>(m_GPUSTAT.dma_direction.GetValue()), word_count);
}
break;
ExecuteCommands();
UpdateCommandTickEvent();
}
else
{
UpdateDMARequest();
}
}

@@ -136,7 +136,13 @@ class GPU

// DMA access
void DMARead(u32* words, u32 word_count);
void DMAWrite(const u32* words, u32 word_count);

ALWAYS_INLINE bool BeginDMAWrite() const { return (m_GPUSTAT.dma_direction == DMADirection::CPUtoGP0); }
ALWAYS_INLINE void DMAWrite(u32 address, u32 value)
{
m_fifo.Push((ZeroExtend64(address) << 32) | ZeroExtend64(value));
}
void EndDMAWrite();

/// Returns the number of pending GPU ticks.
TickCount GetPendingCRTCTicks() const;
@@ -276,6 +282,14 @@ class GPU
// Sprites/rectangles should be clipped to 12 bits before drawing.
static constexpr s32 TruncateVertexPosition(s32 x) { return SignExtendN<11, s32>(x); }

struct NativeVertex
{
s16 x;
s16 y;
u32 color;
u16 texcoord;
};

union VRAMPixel
{
u16 bits;
@@ -700,11 +714,15 @@ class GPU
u16 row;
} m_vram_transfer = {};

HeapFIFOQueue<u32, MAX_FIFO_SIZE> m_fifo;
HeapFIFOQueue<u64, MAX_FIFO_SIZE> m_fifo;
std::vector<u32> m_blit_buffer;
u32 m_blit_remaining_words;
RenderCommand m_render_command{};

ALWAYS_INLINE u32 FifoPop() { return Truncate32(m_fifo.Pop()); }
ALWAYS_INLINE u32 FifoPeek() { return Truncate32(m_fifo.Peek()); }
ALWAYS_INLINE u32 FifoPeek(u32 i) { return Truncate32(m_fifo.Peek(i)); }

TickCount m_max_run_ahead = 128;
u32 m_fifo_size = 128;

@@ -33,7 +33,7 @@ void GPU::ExecuteCommands()
{
case BlitterState::Idle:
{
const u32 command = m_fifo.Peek(0) >> 24;
const u32 command = FifoPeek(0) >> 24;
if ((this->*s_GP0_command_handler_table[command])())
continue;
else
@@ -45,8 +45,11 @@ void GPU::ExecuteCommands()
DebugAssert(m_blit_remaining_words > 0);
const u32 words_to_copy = std::min(m_blit_remaining_words, m_fifo.GetSize());
const size_t old_size = m_blit_buffer.size();
m_blit_buffer.resize(m_blit_buffer.size() + words_to_copy);
m_fifo.PopRange(&m_blit_buffer[old_size], words_to_copy);
// m_blit_buffer.resize(m_blit_buffer.size() + words_to_copy);
// FifoPopRange(&m_blit_buffer[old_size], words_to_copy);
m_blit_buffer.reserve(m_blit_buffer.size() + words_to_copy);
for (u32 i = 0; i < words_to_copy; i++)
m_blit_buffer.push_back(FifoPop());
m_blit_remaining_words -= words_to_copy;
AddCommandTicks(words_to_copy);

@@ -72,7 +75,7 @@ void GPU::ExecuteCommands()
{
// polyline must have at least two vertices, and the terminator is (word & 0xf000f000) == 0x50005000.
// terminator is on the first word for the vertex
if ((m_fifo.Peek(terminator_index) & UINT32_C(0xF000F000)) == UINT32_C(0x50005000))
if ((FifoPeek(terminator_index) & UINT32_C(0xF000F000)) == UINT32_C(0x50005000))
break;
}

@@ -81,8 +84,11 @@ void GPU::ExecuteCommands()
if (words_to_copy > 0)
{
const size_t old_size = m_blit_buffer.size();
m_blit_buffer.resize(m_blit_buffer.size() + words_to_copy);
m_fifo.PopRange(&m_blit_buffer[old_size], words_to_copy);
// m_blit_buffer.resize(m_blit_buffer.size() + words_to_copy);
// FifoPopRange(&m_blit_buffer[old_size], words_to_copy);
m_blit_buffer.reserve(m_blit_buffer.size() + words_to_copy);
for (u32 i = 0; i < words_to_copy; i++)
m_blit_buffer.push_back(FifoPop());
}

Log_DebugPrintf("Added %u words to polyline", words_to_copy);
@@ -170,12 +176,12 @@ GPU::GP0CommandHandlerTable GPU::GenerateGP0CommandHandlerTable()

bool GPU::HandleUnknownGP0Command()
{
const u32 command = m_fifo.Peek() >> 24;
const u32 command = FifoPeek() >> 24;
Log_ErrorPrintf("Unimplemented GP0 command 0x%02X", command);

SmallString dump;
for (u32 i = 0; i < m_fifo.GetSize(); i++)
dump.AppendFormattedString("%s0x%08X", (i > 0) ? " " : "", m_fifo.Peek(i));
dump.AppendFormattedString("%s0x%08X", (i > 0) ? " " : "", FifoPeek(i));
Log_ErrorPrintf("FIFO: %s", dump.GetCharArray());

m_fifo.RemoveOne();
@@ -216,7 +222,7 @@ bool GPU::HandleInterruptRequestCommand()

bool GPU::HandleSetDrawModeCommand()
{
const u32 param = m_fifo.Pop() & 0x00FFFFFFu;
const u32 param = FifoPop() & 0x00FFFFFFu;
Log_DebugPrintf("Set draw mode %08X", param);
SetDrawMode(Truncate16(param));
AddCommandTicks(1);
@@ -226,7 +232,7 @@ bool GPU::HandleSetDrawModeCommand()

bool GPU::HandleSetTextureWindowCommand()
{
const u32 param = m_fifo.Pop() & 0x00FFFFFFu;
const u32 param = FifoPop() & 0x00FFFFFFu;
SetTextureWindow(param);
Log_DebugPrintf("Set texture window %02X %02X %02X %02X", m_draw_mode.texture_window_mask_x,
m_draw_mode.texture_window_mask_y, m_draw_mode.texture_window_offset_x,
@@ -239,7 +245,7 @@ bool GPU::HandleSetTextureWindowCommand()

bool GPU::HandleSetDrawingAreaTopLeftCommand()
{
const u32 param = m_fifo.Pop() & 0x00FFFFFFu;
const u32 param = FifoPop() & 0x00FFFFFFu;
const u32 left = param & VRAM_WIDTH_MASK;
const u32 top = (param >> 10) & VRAM_HEIGHT_MASK;
Log_DebugPrintf("Set drawing area top-left: (%u, %u)", left, top);
@@ -259,7 +265,7 @@ bool GPU::HandleSetDrawingAreaTopLeftCommand()

bool GPU::HandleSetDrawingAreaBottomRightCommand()
{
const u32 param = m_fifo.Pop() & 0x00FFFFFFu;
const u32 param = FifoPop() & 0x00FFFFFFu;

const u32 right = param & VRAM_WIDTH_MASK;
const u32 bottom = (param >> 10) & VRAM_HEIGHT_MASK;
@@ -280,7 +286,7 @@ bool GPU::HandleSetDrawingAreaBottomRightCommand()

bool GPU::HandleSetDrawingOffsetCommand()
{
const u32 param = m_fifo.Pop() & 0x00FFFFFFu;
const u32 param = FifoPop() & 0x00FFFFFFu;
const s32 x = SignExtendN<11, s32>(param & 0x7FFu);
const s32 y = SignExtendN<11, s32>((param >> 11) & 0x7FFu);
Log_DebugPrintf("Set drawing offset (%d, %d)", m_drawing_offset.x, m_drawing_offset.y);
@@ -299,7 +305,7 @@ bool GPU::HandleSetDrawingOffsetCommand()

bool GPU::HandleSetMaskBitCommand()
{
const u32 param = m_fifo.Pop() & 0x00FFFFFFu;
const u32 param = FifoPop() & 0x00FFFFFFu;

constexpr u32 gpustat_mask = (1 << 11) | (1 << 12);
const u32 gpustat_bits = (param & 0x03) << 11;
@@ -318,7 +324,7 @@ bool GPU::HandleSetMaskBitCommand()

bool GPU::HandleRenderPolygonCommand()
{
const RenderCommand rc{m_fifo.Peek(0)};
const RenderCommand rc{FifoPeek(0)};

// shaded vertices use the colour from the first word for the first vertex
const u32 words_per_vertex = 1 + BoolToUInt32(rc.texture_enable) + BoolToUInt32(rc.shading_enable);
@@ -344,10 +350,10 @@ bool GPU::HandleRenderPolygonCommand()
// set draw state up
if (rc.texture_enable)
{
const u16 texpage_attribute = Truncate16((rc.shading_enable ? m_fifo.Peek(5) : m_fifo.Peek(4)) >> 16);
const u16 texpage_attribute = Truncate16((rc.shading_enable ? FifoPeek(5) : FifoPeek(4)) >> 16);
SetDrawMode((texpage_attribute & DrawMode::Reg::POLYGON_TEXPAGE_MASK) |
(m_draw_mode.mode_reg.bits & ~DrawMode::Reg::POLYGON_TEXPAGE_MASK));
SetTexturePalette(Truncate16(m_fifo.Peek(2) >> 16));
SetTexturePalette(Truncate16(FifoPeek(2) >> 16));
}

m_stats.num_vertices += num_vertices;
@@ -362,7 +368,7 @@ bool GPU::HandleRenderPolygonCommand()

bool GPU::HandleRenderRectangleCommand()
{
const RenderCommand rc{m_fifo.Peek(0)};
const RenderCommand rc{FifoPeek(0)};
const u32 total_words =
2 + BoolToUInt32(rc.texture_enable) + BoolToUInt32(rc.rectangle_size == DrawRectangleSize::Variable);

@@ -372,7 +378,7 @@ bool GPU::HandleRenderRectangleCommand()
SynchronizeCRTC();

if (rc.texture_enable)
SetTexturePalette(Truncate16(m_fifo.Peek(2) >> 16));
SetTexturePalette(Truncate16(FifoPeek(2) >> 16));

const TickCount setup_ticks = 16;
AddCommandTicks(setup_ticks);
@@ -394,7 +400,7 @@ bool GPU::HandleRenderRectangleCommand()

bool GPU::HandleRenderLineCommand()
{
const RenderCommand rc{m_fifo.Peek(0)};
const RenderCommand rc{FifoPeek(0)};
const u32 total_words = rc.shading_enable ? 4 : 3;
CHECK_COMMAND_SIZE(total_words);

@@ -417,7 +423,7 @@ bool GPU::HandleRenderLineCommand()
bool GPU::HandleRenderPolyLineCommand()
{
// always read the first two vertices, we test for the terminator after that
const RenderCommand rc{m_fifo.Peek(0)};
const RenderCommand rc{FifoPeek(0)};
const u32 min_words = rc.shading_enable ? 3 : 4;
CHECK_COMMAND_SIZE(min_words);

@@ -434,8 +440,11 @@ bool GPU::HandleRenderPolyLineCommand()
m_fifo.RemoveOne();

const u32 words_to_pop = min_words - 1;
m_blit_buffer.resize(words_to_pop);
m_fifo.PopRange(m_blit_buffer.data(), words_to_pop);
// m_blit_buffer.resize(words_to_pop);
// FifoPopRange(m_blit_buffer.data(), words_to_pop);
m_blit_buffer.reserve(words_to_pop);
for (u32 i = 0; i < words_to_pop; i++)
m_blit_buffer.push_back(Truncate32(FifoPop()));

// polyline goes via a different path through the blit buffer
m_blitter_state = BlitterState::DrawingPolyLine;
@@ -452,11 +461,11 @@ bool GPU::HandleFillRectangleCommand()

FlushRender();

const u32 color = m_fifo.Pop() & 0x00FFFFFF;
const u32 dst_x = m_fifo.Peek() & 0x3F0;
const u32 dst_y = (m_fifo.Pop() >> 16) & VRAM_COORD_MASK;
const u32 width = ((m_fifo.Peek() & VRAM_WIDTH_MASK) + 0xF) & ~0xF;
const u32 height = (m_fifo.Pop() >> 16) & VRAM_HEIGHT_MASK;
const u32 color = FifoPop() & 0x00FFFFFF;
const u32 dst_x = FifoPeek() & 0x3F0;
const u32 dst_y = (FifoPop() >> 16) & VRAM_COORD_MASK;
const u32 width = ((FifoPeek() & VRAM_WIDTH_MASK) + 0xF) & ~0xF;
const u32 height = (FifoPop() >> 16) & VRAM_HEIGHT_MASK;

Log_DebugPrintf("Fill VRAM rectangle offset=(%u,%u), size=(%u,%u)", dst_x, dst_y, width, height);

@@ -472,10 +481,10 @@ bool GPU::HandleCopyRectangleCPUToVRAMCommand()
CHECK_COMMAND_SIZE(3);
m_fifo.RemoveOne();

const u32 dst_x = m_fifo.Peek() & VRAM_COORD_MASK;
const u32 dst_y = (m_fifo.Pop() >> 16) & VRAM_COORD_MASK;
const u32 copy_width = ReplaceZero(m_fifo.Peek() & VRAM_WIDTH_MASK, 0x400);
const u32 copy_height = ReplaceZero((m_fifo.Pop() >> 16) & VRAM_HEIGHT_MASK, 0x200);
const u32 dst_x = FifoPeek() & VRAM_COORD_MASK;
const u32 dst_y = (FifoPop() >> 16) & VRAM_COORD_MASK;
const u32 copy_width = ReplaceZero(FifoPeek() & VRAM_WIDTH_MASK, 0x400);
const u32 copy_height = ReplaceZero((FifoPop() >> 16) & VRAM_HEIGHT_MASK, 0x200);
const u32 num_pixels = copy_width * copy_height;
const u32 num_words = ((num_pixels + 1) / 2);

@@ -520,10 +529,10 @@ bool GPU::HandleCopyRectangleVRAMToCPUCommand()
CHECK_COMMAND_SIZE(3);
m_fifo.RemoveOne();

m_vram_transfer.x = Truncate16(m_fifo.Peek() & VRAM_COORD_MASK);
m_vram_transfer.y = Truncate16((m_fifo.Pop() >> 16) & VRAM_COORD_MASK);
m_vram_transfer.width = ((Truncate16(m_fifo.Peek()) - 1) & VRAM_WIDTH_MASK) + 1;
m_vram_transfer.height = ((Truncate16(m_fifo.Pop() >> 16) - 1) & VRAM_HEIGHT_MASK) + 1;
m_vram_transfer.x = Truncate16(FifoPeek() & VRAM_COORD_MASK);
m_vram_transfer.y = Truncate16((FifoPop() >> 16) & VRAM_COORD_MASK);
m_vram_transfer.width = ((Truncate16(FifoPeek()) - 1) & VRAM_WIDTH_MASK) + 1;
m_vram_transfer.height = ((Truncate16(FifoPop() >> 16) - 1) & VRAM_HEIGHT_MASK) + 1;

Log_DebugPrintf("Copy rectangle from VRAM to CPU offset=(%u,%u), size=(%u,%u)", m_vram_transfer.x, m_vram_transfer.y,
m_vram_transfer.width, m_vram_transfer.height);
@@ -554,12 +563,12 @@ bool GPU::HandleCopyRectangleVRAMToVRAMCommand()
CHECK_COMMAND_SIZE(4);
m_fifo.RemoveOne();

const u32 src_x = m_fifo.Peek() & VRAM_COORD_MASK;
const u32 src_y = (m_fifo.Pop() >> 16) & VRAM_COORD_MASK;
const u32 dst_x = m_fifo.Peek() & VRAM_COORD_MASK;
const u32 dst_y = (m_fifo.Pop() >> 16) & VRAM_COORD_MASK;
const u32 width = ReplaceZero(m_fifo.Peek() & VRAM_WIDTH_MASK, 0x400);
const u32 height = ReplaceZero((m_fifo.Pop() >> 16) & VRAM_HEIGHT_MASK, 0x200);
const u32 src_x = FifoPeek() & VRAM_COORD_MASK;
const u32 src_y = (FifoPop() >> 16) & VRAM_COORD_MASK;
const u32 dst_x = FifoPeek() & VRAM_COORD_MASK;
const u32 dst_y = (FifoPop() >> 16) & VRAM_COORD_MASK;
const u32 width = ReplaceZero(FifoPeek() & VRAM_WIDTH_MASK, 0x400);
const u32 height = ReplaceZero((FifoPop() >> 16) & VRAM_HEIGHT_MASK, 0x200);

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);

Large diffs are not rendered by default.

@@ -55,24 +55,26 @@ class GPU_HW : public GPU

struct BatchVertex
{
s32 x;
s32 y;
s32 z;
float x;
float y;
float z;
float w;
u32 color;
u32 texpage;
u16 u; // 16-bit texcoords are needed for 256 extent rectangles
u16 v;

ALWAYS_INLINE void Set(s32 x_, s32 y_, s32 z_, u32 color_, u32 texpage_, u16 packed_texcoord)
ALWAYS_INLINE void Set(float x_, float y_, float z_, float w_, u32 color_, u32 texpage_, u16 packed_texcoord)
{
Set(x_, y_, z_, color_, texpage_, packed_texcoord & 0xFF, (packed_texcoord >> 8));
Set(x_, y_, z_, w_, color_, texpage_, packed_texcoord & 0xFF, (packed_texcoord >> 8));
}

ALWAYS_INLINE void Set(s32 x_, s32 y_, s32 z_, u32 color_, u32 texpage_, u16 u_, u16 v_)
ALWAYS_INLINE void Set(float x_, float y_, float z_, float w_, u32 color_, u32 texpage_, u16 u_, u16 v_)
{
x = x_;
y = y_;
z = z_;
w = w_;
color = color_;
texpage = texpage_;
u = u_;
@@ -191,7 +193,7 @@ class GPU_HW : public GPU
/// Returns the value to be written to the depth buffer for the current operation for mask bit emulation.
ALWAYS_INLINE float GetCurrentNormalizedVertexDepth() const
{
return (static_cast<float>(m_current_depth) / 65535.0f);
return 1.0f - (static_cast<float>(m_current_depth) / 65535.0f);
}

/// Returns the interlaced mode to use when scanning out/displaying.
@@ -234,7 +236,7 @@ class GPU_HW : public GPU

/// Handles quads with flipped texture coordinate directions.
static void HandleFlippedQuadTextureCoordinates(BatchVertex* vertices);
static void FixLineVertexCoordinates(BatchVertex& start, BatchVertex& end, s32 dx, s32 dy);
static void FixLineVertexCoordinates(s32& start_x, s32& start_y, s32& end_x, s32& end_y, s32 dx, s32 dy);

HeapArray<u16, VRAM_WIDTH * VRAM_HEIGHT> m_vram_shadow;

@@ -263,7 +263,7 @@ bool GPU_HW_D3D11::CreateTextureBuffer()
bool GPU_HW_D3D11::CreateBatchInputLayout()
{
static constexpr std::array<D3D11_INPUT_ELEMENT_DESC, 4> attributes = {
{{"ATTR", 0, DXGI_FORMAT_R32G32B32_SINT, 0, offsetof(BatchVertex, x), D3D11_INPUT_PER_VERTEX_DATA, 0},
{{"ATTR", 0, DXGI_FORMAT_R32G32B32A32_FLOAT, 0, offsetof(BatchVertex, x), D3D11_INPUT_PER_VERTEX_DATA, 0},
{"ATTR", 1, DXGI_FORMAT_R8G8B8A8_UNORM, 0, offsetof(BatchVertex, color), D3D11_INPUT_PER_VERTEX_DATA, 0},
{"ATTR", 2, DXGI_FORMAT_R32_UINT, 0, offsetof(BatchVertex, u), D3D11_INPUT_PER_VERTEX_DATA, 0},
{"ATTR", 3, DXGI_FORMAT_R32_UINT, 0, offsetof(BatchVertex, texpage), D3D11_INPUT_PER_VERTEX_DATA, 0}}};
@@ -291,7 +291,7 @@ bool GPU_HW_OpenGL::CreateVertexBuffer()
glEnableVertexAttribArray(1);
glEnableVertexAttribArray(2);
glEnableVertexAttribArray(3);
glVertexAttribIPointer(0, 3, GL_INT, sizeof(BatchVertex), reinterpret_cast<void*>(offsetof(BatchVertex, x)));
glVertexAttribPointer(0, 4, GL_FLOAT, false, sizeof(BatchVertex), reinterpret_cast<void*>(offsetof(BatchVertex, x)));
glVertexAttribPointer(1, 4, GL_UNSIGNED_BYTE, true, sizeof(BatchVertex),
reinterpret_cast<void*>(offsetof(BatchVertex, color)));
glVertexAttribIPointer(2, 1, GL_UNSIGNED_INT, sizeof(BatchVertex), reinterpret_cast<void*>(offsetof(BatchVertex, u)));
@@ -516,12 +516,12 @@ std::string GPU_HW_ShaderGen::GenerateBatchVertexShader(bool textured, bool upsc
const char* output_block_suffix = upscaled_lines ? "VS" : "";
if (textured)
{
DeclareVertexEntryPoint(ss, {"int3 a_pos", "float4 a_col0", "uint a_texcoord", "uint a_texpage"}, 1, 1,
DeclareVertexEntryPoint(ss, {"float4 a_pos", "float4 a_col0", "uint a_texcoord", "uint a_texpage"}, 1, 1,
{{"nointerpolation", "uint4 v_texpage"}}, false, output_block_suffix);
}
else
{
DeclareVertexEntryPoint(ss, {"int3 a_pos", "float4 a_col0"}, 1, 0, {}, false, output_block_suffix);
DeclareVertexEntryPoint(ss, {"float4 a_pos", "float4 a_col0"}, 1, 0, {}, false, output_block_suffix);
}

ss << R"(
@@ -532,9 +532,10 @@ std::string GPU_HW_ShaderGen::GenerateBatchVertexShader(bool textured, bool upsc
float vertex_offset = (RESOLUTION_SCALE == 1u) ? 0.5 : 0.0;
// 0..+1023 -> -1..1
float pos_x = ((float(a_pos.x) + vertex_offset) / 512.0) - 1.0;
float pos_y = ((float(a_pos.y) + vertex_offset) / -256.0) + 1.0;
float pos_z = 1.0 - (float(a_pos.z) / 65535.0);
float pos_x = ((a_pos.x + vertex_offset) / 512.0) - 1.0;
float pos_y = ((a_pos.y + vertex_offset) / -256.0) + 1.0;
float pos_z = a_pos.z;
float pos_w = a_pos.w;
#if API_OPENGL || API_OPENGL_ES
// OpenGL seems to be off by one pixel in the Y direction due to lower-left origin, but only on
@@ -550,7 +551,7 @@ std::string GPU_HW_ShaderGen::GenerateBatchVertexShader(bool textured, bool upsc
pos_y = -pos_y;
#endif
v_pos = float4(pos_x, pos_y, pos_z, 1.0);
v_pos = float4(pos_x * pos_w, pos_y * pos_w, pos_z * pos_w, pos_w);
v_col0 = a_col0;
#if TEXTURED
@@ -669,7 +669,7 @@ bool GPU_HW_Vulkan::CompilePipelines()
gpbuilder.SetRenderPass(m_vram_render_pass, 0);

gpbuilder.AddVertexBuffer(0, sizeof(BatchVertex), VK_VERTEX_INPUT_RATE_VERTEX);
gpbuilder.AddVertexAttribute(0, 0, VK_FORMAT_R32G32B32_SINT, offsetof(BatchVertex, x));
gpbuilder.AddVertexAttribute(0, 0, VK_FORMAT_R32G32B32A32_SFLOAT, offsetof(BatchVertex, x));
gpbuilder.AddVertexAttribute(1, 0, VK_FORMAT_R8G8B8A8_UNORM, offsetof(BatchVertex, color));
if (textured)
{
@@ -227,18 +227,18 @@ void GPU_SW::DispatchRenderCommand()
for (u32 i = 0; i < num_vertices; i++)
{
SWVertex& vert = vertices[i];
const u32 color_rgb = (shaded && i > 0) ? (m_fifo.Pop() & UINT32_C(0x00FFFFFF)) : first_color;
const u32 color_rgb = (shaded && i > 0) ? (FifoPop() & UINT32_C(0x00FFFFFF)) : first_color;
vert.color_r = Truncate8(color_rgb);
vert.color_g = Truncate8(color_rgb >> 8);
vert.color_b = Truncate8(color_rgb >> 16);

const VertexPosition vp{m_fifo.Pop()};
const VertexPosition vp{FifoPop()};
vert.x = vp.x;
vert.y = vp.y;

if (textured)
{
std::tie(vert.texcoord_x, vert.texcoord_y) = UnpackTexcoord(Truncate16(m_fifo.Pop()));
std::tie(vert.texcoord_x, vert.texcoord_y) = UnpackTexcoord(Truncate16(FifoPop()));
}
else
{
@@ -262,8 +262,8 @@ void GPU_SW::DispatchRenderCommand()
case Primitive::Rectangle:
{
const auto [r, g, b] = UnpackColorRGB24(rc.color_for_first_vertex);
const VertexPosition vp{m_fifo.Pop()};
const u32 texcoord_and_palette = rc.texture_enable ? m_fifo.Pop() : 0;
const VertexPosition vp{FifoPop()};
const u32 texcoord_and_palette = rc.texture_enable ? FifoPop() : 0;
const auto [texcoord_x, texcoord_y] = UnpackTexcoord(Truncate16(texcoord_and_palette));

s32 width;
@@ -284,7 +284,7 @@ void GPU_SW::DispatchRenderCommand()
break;
default:
{
const u32 width_and_height = m_fifo.Pop();
const u32 width_and_height = FifoPop();
width = static_cast<s32>(width_and_height & VRAM_WIDTH_MASK);
height = static_cast<s32>((width_and_height >> 16) & VRAM_HEIGHT_MASK);

@@ -321,7 +321,7 @@ void GPU_SW::DispatchRenderCommand()
// first vertex
SWVertex* p0 = &vertices[0];
SWVertex* p1 = &vertices[1];
p0->SetPosition(VertexPosition{rc.polyline ? m_blit_buffer[buffer_pos++] : m_fifo.Pop()});
p0->SetPosition(VertexPosition{rc.polyline ? m_blit_buffer[buffer_pos++] : Truncate32(FifoPop())});
p0->SetColorRGB24(first_color);

// remaining vertices in line strip
@@ -335,8 +335,8 @@ void GPU_SW::DispatchRenderCommand()
}
else
{
p1->SetColorRGB24(shaded ? (m_fifo.Pop() & UINT32_C(0x00FFFFFF)) : first_color);
p1->SetPosition(VertexPosition{m_fifo.Pop()});
p1->SetColorRGB24(shaded ? (FifoPop() & UINT32_C(0x00FFFFFF)) : first_color);
p1->SetPosition(VertexPosition{Truncate32(FifoPop())});
}

// down here because of the FIFO pops
@@ -3,6 +3,7 @@
#include "common/bitutils.h"
#include "common/state_wrapper.h"
#include "cpu_core.h"
#include "pgxp.h"
#include "settings.h"
#include <algorithm>
#include <array>
@@ -621,6 +622,21 @@ static void RTPS(const s16 V[3], u8 shift, bool lm, bool last)
CheckMACOverflow<0>(Sy);
PushSXY(s32(Sx >> 16), s32(Sy >> 16));

if (g_settings.gpu_pgxp_enable)
{
// this can potentially use increased precision on Z
const float precise_z = std::max<float>((float)REGS.H / 2.f, (float)REGS.SZ3);
const float precise_h_div_sz = (float)REGS.H / precise_z;
const float fofx = ((float)REGS.OFX / (float)(1 << 16));
const float fofy = ((float)REGS.OFY / (float)(1 << 16));
float precise_x = fofx + ((float)REGS.IR1 * precise_h_div_sz) * ((g_settings.gpu_widescreen_hack) ? 0.75f : 1.00f);
float precise_y = fofy + ((float)REGS.IR2 * precise_h_div_sz);

precise_x = std::clamp<float>(precise_x, -0x400, 0x3ff);
precise_y = std::clamp<float>(precise_y, -0x400, 0x3ff);
PGXP::GTE_PushSXYZ2f(precise_x, precise_y, precise_z, REGS.dr32[14]);
}

if (last)
{
// MAC0=(((H*20000h/SZ3)+1)/2)*DQA+DQB, IR0=MAC0/1000h ;Depth cueing 0..+1000h
@@ -664,6 +680,19 @@ static void Execute_NCLIP(Instruction inst)
REGS.FLAG.UpdateError();
}

static void Execute_NCLIP_PGXP(Instruction inst)
{
if (PGXP::GTE_NCLIP_valid(REGS.dr32[12], REGS.dr32[13], REGS.dr32[14]))
{
REGS.FLAG.Clear();
REGS.MAC0 = static_cast<s32>(PGXP::GTE_NCLIP());
}
else
{
Execute_NCLIP(inst);
}
}

static void Execute_AVSZ3(Instruction inst)
{
REGS.FLAG.Clear();
@@ -994,8 +1023,13 @@ void ExecuteInstruction(u32 inst_bits)
break;

case 0x06:
Execute_NCLIP(inst);
break;
{
if (g_settings.gpu_pgxp_enable && g_settings.gpu_pgxp_culling)
Execute_NCLIP_PGXP(inst);
else
Execute_NCLIP(inst);
}
break;

case 0x0C:
Execute_OP(inst);
@@ -1092,7 +1126,12 @@ InstructionImpl GetInstructionImpl(u32 inst_bits)
return &Execute_RTPS;

case 0x06:
return &Execute_NCLIP;
{
if (g_settings.gpu_pgxp_enable && g_settings.gpu_pgxp_culling)
return &Execute_NCLIP_PGXP;
else
return &Execute_NCLIP;
}

case 0x0C:
return &Execute_OP;
@@ -8,12 +8,13 @@
#include "common/log.h"
#include "common/string_util.h"
#include "controller.h"
#include "cpu_core.h"
#include "cpu_code_cache.h"
#include "cpu_core.h"
#include "dma.h"
#include "gpu.h"
#include "gte.h"
#include "host_display.h"
#include "pgxp.h"
#include "save_state_version.h"
#include "system.h"
#include <cmath>
@@ -367,6 +368,10 @@ void HostInterface::SetDefaultSettings(SettingsInterface& si)
si.SetBoolValue("GPU", "DisableInterlacing", false);
si.SetBoolValue("GPU", "ForceNTSCTimings", false);
si.SetBoolValue("GPU", "WidescreenHack", false);
si.SetBoolValue("GPU", "PGXPEnable", false);
si.SetBoolValue("GPU", "PGXPCulling", true);
si.SetBoolValue("GPU", "PGXPTextureCorrection", true);
si.SetBoolValue("GPU", "PGXPVertexCache", false);

si.SetStringValue("Display", "CropMode", Settings::GetDisplayCropModeName(Settings::DEFAULT_DISPLAY_CROP_MODE));
si.SetStringValue("Display", "AspectRatio",
@@ -485,6 +490,19 @@ void HostInterface::CheckForSettingsChanges(const Settings& old_settings)
g_gpu->UpdateSettings();
}

if (g_settings.gpu_pgxp_enable != old_settings.gpu_pgxp_enable ||
(g_settings.gpu_pgxp_enable && g_settings.gpu_pgxp_culling != old_settings.gpu_pgxp_culling))
{
if (g_settings.IsUsingCodeCache())
{
ReportFormattedMessage("PGXP %s, recompiling all blocks.", g_settings.gpu_pgxp_enable ? "enabled" : "disabled");
CPU::CodeCache::Flush();
}

if (g_settings.gpu_pgxp_enable)
PGXP::Initialize();
}

if (g_settings.cdrom_read_thread != old_settings.cdrom_read_thread)
g_cdrom.SetUseReadThread(g_settings.cdrom_read_thread);

@@ -625,8 +643,7 @@ void HostInterface::ToggleSoftwareRendering()
if (System::IsShutdown() || g_settings.gpu_renderer == GPURenderer::Software)
return;

const GPURenderer new_renderer =
g_gpu->IsHardwareRenderer() ? GPURenderer::Software : g_settings.gpu_renderer;
const GPURenderer new_renderer = g_gpu->IsHardwareRenderer() ? GPURenderer::Software : g_settings.gpu_renderer;

AddFormattedOSDMessage(2.0f, "Switching to %s renderer...", Settings::GetRendererDisplayName(new_renderer));
System::RecreateGPU(new_renderer);

Large diffs are not rendered by default.

@@ -0,0 +1,54 @@
/***************************************************************************
* Original copyright notice from PGXP code from Beetle PSX. *
* Copyright (C) 2016 by iCatButler *
* *
* This program is free software; you can redistribute it and/or modify *
* it under the terms of the GNU General Public License as published by *
* the Free Software Foundation; either version 2 of the License, or *
* (at your option) any later version. *
* *
* This program is distributed in the hope that it will be useful, *
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
* GNU General Public License for more details. *
* *
* You should have received a copy of the GNU General Public License *
* along with this program; if not, write to the *
* Free Software Foundation, Inc., *
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. *
***************************************************************************/

#pragma once
#include "types.h"

namespace PGXP {

void Initialize();

// -- GTE functions
// Transforms
void GTE_PushSXYZ2f(float _x, float _y, float _z, unsigned int _v);
void GTE_PushSXYZ2s(s64 _x, s64 _y, s64 _z, u32 v);
int GTE_NCLIP_valid(u32 sxy0, u32 sxy1, u32 sxy2);
float GTE_NCLIP();

// Data transfer tracking
void CPU_MFC2(u32 instr, u32 rtVal, u32 rdVal); // copy GTE data reg to GPR reg (MFC2)
void CPU_MTC2(u32 instr, u32 rdVal, u32 rtVal); // copy GPR reg to GTE data reg (MTC2)
void CPU_CFC2(u32 instr, u32 rtVal, u32 rdVal); // copy GTE ctrl reg to GPR reg (CFC2)
void CPU_CTC2(u32 instr, u32 rdVal, u32 rtVal); // copy GPR reg to GTE ctrl reg (CTC2)
// Memory Access
void CPU_LWC2(u32 instr, u32 rtVal, u32 addr); // copy memory to GTE reg
void CPU_SWC2(u32 instr, u32 rtVal, u32 addr); // copy GTE reg to memory

bool GetPreciseVertex(u32 addr, u32 value, int x, int y, int xOffs, int yOffs, float* out_x, float* out_y, float* out_w);

// -- CPU functions
void CPU_LW(u32 instr, u32 rtVal, u32 addr);
void CPU_LHx(u32 instr, u32 rtVal, u32 addr);
void CPU_LBx(u32 instr, u32 rtVal, u32 addr);
void CPU_SB(u32 instr, u8 rtVal, u32 addr);
void CPU_SH(u32 instr, u16 rtVal, u32 addr);
void CPU_SW(u32 instr, u32 rtVal, u32 addr);

} // namespace PGXP
@@ -101,6 +101,10 @@ void Settings::Load(SettingsInterface& si)
gpu_disable_interlacing = si.GetBoolValue("GPU", "DisableInterlacing", false);
gpu_force_ntsc_timings = si.GetBoolValue("GPU", "ForceNTSCTimings", false);
gpu_widescreen_hack = si.GetBoolValue("GPU", "WidescreenHack", false);
gpu_pgxp_enable = si.GetBoolValue("GPU", "PGXPEnable", false);
gpu_pgxp_culling = si.GetBoolValue("GPU", "PGXPCulling", true);
gpu_pgxp_texture_correction = si.GetBoolValue("GPU", "PGXPTextureCorrection", true);
gpu_pgxp_vertex_cache = si.GetBoolValue("GPU", "PGXPVertexCache", false);

display_crop_mode =
ParseDisplayCropMode(
@@ -203,6 +207,10 @@ void Settings::Save(SettingsInterface& si) const
si.SetBoolValue("GPU", "DisableInterlacing", gpu_disable_interlacing);
si.SetBoolValue("GPU", "ForceNTSCTimings", gpu_force_ntsc_timings);
si.SetBoolValue("GPU", "WidescreenHack", gpu_widescreen_hack);
si.SetBoolValue("GPU", "PGXPEnable", gpu_pgxp_enable);
si.SetBoolValue("GPU", "PGXPCulling", gpu_pgxp_culling);
si.SetBoolValue("GPU", "PGXPTextureCorrection", gpu_pgxp_texture_correction);
si.SetBoolValue("GPU", "PGXPVertexCache", gpu_pgxp_vertex_cache);

si.SetStringValue("Display", "CropMode", GetDisplayCropModeName(display_crop_mode));
si.SetStringValue("Display", "AspectRatio", GetDisplayAspectRatioName(display_aspect_ratio));
@@ -88,6 +88,10 @@ struct Settings
bool gpu_disable_interlacing = false;
bool gpu_force_ntsc_timings = false;
bool gpu_widescreen_hack = false;
bool gpu_pgxp_enable = false;
bool gpu_pgxp_culling = true;
bool gpu_pgxp_texture_correction = true;
bool gpu_pgxp_vertex_cache = false;
DisplayCropMode display_crop_mode = DisplayCropMode::None;
DisplayAspectRatio display_aspect_ratio = DisplayAspectRatio::R4_3;
bool display_linear_filtering = true;
@@ -146,6 +150,7 @@ struct Settings
bool log_to_window = false;
bool log_to_file = false;

ALWAYS_INLINE bool IsUsingCodeCache() const { return (cpu_execution_mode != CPUExecutionMode::Interpreter); }
ALWAYS_INLINE bool IsUsingRecompiler() const { return (cpu_execution_mode == CPUExecutionMode::Recompiler); }
ALWAYS_INLINE bool IsUsingSoftwareRenderer() const { return (gpu_renderer == GPURenderer::Software); }

@@ -352,7 +352,7 @@ void LibretroHostInterface::OnSystemDestroyed()
m_using_hardware_renderer = false;
}

static std::array<retro_core_option_definition, 23> s_option_definitions = {{
static std::array<retro_core_option_definition, 27> s_option_definitions = {{
{"Console.Region",
"Console Region",
"Determines which region/hardware to emulate. Auto-Detect will use the region of the disc inserted.",
@@ -447,6 +447,29 @@ static std::array<retro_core_option_definition, 23> s_option_definitions = {{
"backgrounds, this enhancement will not work as expected.",
{{"true", "Enabled"}, {"false", "Disabled"}},
"false"},
{"GPU.PGXPEnable",
"PGXP Geometry Correction",
"Reduces \"wobbly\" polygons by attempting to preserve the fractional component through memory transfers. Only "
"works with the hardware renderers, and may not be compatible with all games.",
{{"true", "Enabled"}, {"false", "Disabled"}},
"false"},
{"GPU.PGXPCulling",
"PGXP Culling Correction",
"Increases the precision of polygon culling, reducing the number of holes in geometry. Requires geometry correction "
"enabled.",
{{"true", "Enabled"}, {"false", "Disabled"}},
"true"},
{"GPU.PGXPTextureCorrection",
"PGXP Texture Correction",
"Uses perspective-correct interpolation for texture coordinates and colors, straightening out warped textures. "
"Requires geometry correction enabled.",
{{"true", "Enabled"}, {"false", "Disabled"}},
"true"},
{"GPU.PGXPVertexCache",
"PGXP Vertex Cache",
"Uses screen coordinates as a fallback when tracking vertices through memory fails. May improve PGXP compatibility.",
{{"true", "Enabled"}, {"false", "Disabled"}},
"false"},
{"Display.CropMode",
"Crop Mode",
"Changes how much of the image is cropped. Some games display garbage in the overscan area which is typically "
@@ -40,11 +40,20 @@ GPUSettingsWidget::GPUSettingsWidget(QtHostInterface* host_interface, QWidget* p
"TextureFiltering");
SettingWidgetBinder::BindWidgetToBoolSetting(m_host_interface, m_ui.widescreenHack, "GPU", "WidescreenHack");

SettingWidgetBinder::BindWidgetToBoolSetting(m_host_interface, m_ui.pgxpEnable, "GPU", "PGXPEnable", false);
SettingWidgetBinder::BindWidgetToBoolSetting(m_host_interface, m_ui.pgxpCulling, "GPU", "PGXPCulling", true);
SettingWidgetBinder::BindWidgetToBoolSetting(m_host_interface, m_ui.pgxpTextureCorrection, "GPU",
"PGXPTextureCorrection", true);
SettingWidgetBinder::BindWidgetToBoolSetting(m_host_interface, m_ui.pgxpVertexCache, "GPU", "PGXPVertexCache", false);

connect(m_ui.resolutionScale, QOverload<int>::of(&QComboBox::currentIndexChanged), this,
&GPUSettingsWidget::updateScaledDitheringEnabled);
connect(m_ui.trueColor, &QCheckBox::stateChanged, this, &GPUSettingsWidget::updateScaledDitheringEnabled);
updateScaledDitheringEnabled();

connect(m_ui.pgxpEnable, &QCheckBox::stateChanged, this, &GPUSettingsWidget::updatePGXPSettingsEnabled);
updatePGXPSettingsEnabled();

connect(m_ui.renderer, QOverload<int>::of(&QComboBox::currentIndexChanged), this,
&GPUSettingsWidget::populateGPUAdapters);
connect(m_ui.adapter, QOverload<int>::of(&QComboBox::currentIndexChanged), this,
@@ -126,6 +135,19 @@ GPUSettingsWidget::GPUSettingsWidget(QtHostInterface* host_interface, QWidget* p
tr("Scales vertex positions in screen-space to a widescreen aspect ratio, essentially "
"increasing the field of view from 4:3 to 16:9 in 3D games. For 2D games, or games which "
"use pre-rendered backgrounds, this enhancement will not work as expected."));
dialog->registerWidgetHelp(
m_ui.pgxpEnable, tr("Geometry Correction"), tr("Unchecked"),
tr("Reduces \"wobbly\" polygons by attempting to preserve the fractional component through memory transfers. Only "
"works with the hardware renderers, and may not be compatible with all games."));
dialog->registerWidgetHelp(m_ui.pgxpCulling, tr("Culling Correction"), tr("Checked"),
tr("Increases the precision of polygon culling, reducing the number of holes in geometry. "
"Requires geometry correction enabled."));
dialog->registerWidgetHelp(m_ui.pgxpTextureCorrection, tr("Texture Correction"), tr("Checked"),
tr("Uses perspective-correct interpolation for texture coordinates and colors, "
"straightening out warped textures. Requires geometry correction enabled."));
dialog->registerWidgetHelp(m_ui.pgxpVertexCache, tr("Vertex Cache"), tr("Unchecked"),
tr("Uses screen coordinates as a fallback when tracking vertices through memory fails. "
"May improve PGXP compatibility."));
}

GPUSettingsWidget::~GPUSettingsWidget() = default;
@@ -232,3 +254,11 @@ void GPUSettingsWidget::onGPUAdapterIndexChanged()

m_host_interface->SetStringSettingValue("GPU", "Adapter", m_ui.adapter->currentText().toUtf8().constData());
}

void GPUSettingsWidget::updatePGXPSettingsEnabled()
{
const bool enabled = m_ui.pgxpEnable->isChecked();
m_ui.pgxpCulling->setEnabled(enabled);
m_ui.pgxpTextureCorrection->setEnabled(enabled);
m_ui.pgxpVertexCache->setEnabled(enabled);
}
@@ -19,6 +19,7 @@ private Q_SLOTS:
void updateScaledDitheringEnabled();
void populateGPUAdapters();
void onGPUAdapterIndexChanged();
void updatePGXPSettingsEnabled();

private:
void setupAdditionalUi();
@@ -7,13 +7,13 @@
<x>0</x>
<y>0</y>
<width>448</width>
<height>307</height>
<height>720</height>
</rect>
</property>
<property name="windowTitle">
<string>Form</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout">
<layout class="QVBoxLayout" name="verticalLayout_2">
<property name="leftMargin">
<number>0</number>
</property>
@@ -27,165 +27,221 @@
<number>0</number>
</property>
<item>
<widget class="QGroupBox" name="groupBox">
<property name="title">
<string>Basic</string>
<widget class="QScrollArea" name="scrollArea">
<property name="widgetResizable">
<bool>true</bool>
</property>
<layout class="QFormLayout" name="formLayout_3">
<item row="0" column="0">
<widget class="QLabel" name="label">
<property name="text">
<string>Renderer:</string>
</property>
</widget>
</item>
<item row="0" column="1">
<widget class="QComboBox" name="renderer"/>
</item>
<item row="1" column="0">
<widget class="QLabel" name="label_5">
<property name="text">
<string>Adapter:</string>
</property>
</widget>
</item>
<item row="1" column="1">
<widget class="QComboBox" name="adapter"/>
</item>
<item row="2" column="0" colspan="2">
<widget class="QCheckBox" name="useDebugDevice">
<property name="text">
<string>Use Debug Device</string>
</property>
</widget>
</item>
</layout>
<widget class="QWidget" name="scrollAreaWidgetContents">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>423</width>
<height>762</height>
</rect>
</property>
<layout class="QVBoxLayout" name="verticalLayout_3">
<item>
<widget class="QGroupBox" name="groupBox">
<property name="title">
<string>Basic</string>
</property>
<layout class="QFormLayout" name="formLayout_3">
<item row="0" column="0">
<widget class="QLabel" name="label">
<property name="text">
<string>Renderer:</string>
</property>
</widget>
</item>
<item row="0" column="1">
<widget class="QComboBox" name="renderer"/>
</item>
<item row="1" column="0">
<widget class="QLabel" name="label_5">
<property name="text">
<string>Adapter:</string>
</property>
</widget>
</item>
<item row="1" column="1">
<widget class="QComboBox" name="adapter"/>
</item>
<item row="2" column="0" colspan="2">
<widget class="QCheckBox" name="useDebugDevice">
<property name="text">
<string>Use Debug Device</string>
</property>
</widget>
</item>
</layout>
</widget>
</item>
<item>
<widget class="QGroupBox" name="groupBox_3">
<property name="title">
<string>Screen Display</string>
</property>
<layout class="QFormLayout" name="formLayout_4">
<item row="0" column="0">
<widget class="QLabel" name="label_4">
<property name="text">
<string>Aspect Ratio:</string>
</property>
</widget>
</item>
<item row="0" column="1">
<widget class="QComboBox" name="displayAspectRatio"/>
</item>
<item row="1" column="0">
<widget class="QLabel" name="label_3">
<property name="text">
<string>Crop:</string>
</property>
</widget>
</item>
<item row="1" column="1">
<widget class="QComboBox" name="displayCropMode"/>
</item>
<item row="2" column="0" colspan="2">
<widget class="QCheckBox" name="displayLinearFiltering">
<property name="text">
<string>Linear Upscaling</string>
</property>
</widget>
</item>
<item row="3" column="0" colspan="2">
<widget class="QCheckBox" name="displayIntegerScaling">
<property name="text">
<string>Integer Upscaling</string>
</property>
</widget>
</item>
<item row="4" column="0" colspan="2">
<widget class="QCheckBox" name="vsync">
<property name="text">
<string>VSync</string>
</property>
</widget>
</item>
</layout>
</widget>
</item>
<item>
<widget class="QGroupBox" name="groupBox_2">
<property name="title">
<string>Enhancements</string>
</property>
<layout class="QFormLayout" name="formLayout_2">
<item row="0" column="0">
<widget class="QLabel" name="label_2">
<property name="text">
<string>Resolution Scale:</string>
</property>
</widget>
</item>
<item row="0" column="1">
<widget class="QComboBox" name="resolutionScale"/>
</item>
<item row="1" column="0" colspan="2">
<widget class="QCheckBox" name="trueColor">
<property name="text">
<string>True Color Rendering (24-bit, disables dithering)</string>
</property>
</widget>
</item>
<item row="2" column="0" colspan="2">
<widget class="QCheckBox" name="scaledDithering">
<property name="text">
<string>Scaled Dithering (scale dither pattern to resolution)</string>
</property>
</widget>
</item>
<item row="3" column="0" colspan="2">
<widget class="QCheckBox" name="disableInterlacing">
<property name="text">
<string>Disable Interlacing (force progressive render/scan)</string>
</property>
</widget>
</item>
<item row="4" column="0" colspan="2">
<widget class="QCheckBox" name="forceNTSCTimings">
<property name="text">
<string>Force NTSC Timings (60hz-on-PAL)</string>
</property>
</widget>
</item>
<item row="5" column="0" colspan="2">
<widget class="QCheckBox" name="linearTextureFiltering">
<property name="text">
<string>Bilinear Texture Filtering</string>
</property>
</widget>
</item>
<item row="6" column="0" colspan="2">
<widget class="QCheckBox" name="widescreenHack">
<property name="text">
<string>Widescreen Hack</string>
</property>
</widget>
</item>
</layout>
</widget>
</item>
<item>
<widget class="QGroupBox" name="groupBox_4">
<property name="title">
<string>PGXP</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout">
<item>
<widget class="QCheckBox" name="pgxpEnable">
<property name="text">
<string>Geometry Correction</string>
</property>
</widget>
</item>
<item>
<widget class="QCheckBox" name="pgxpCulling">
<property name="text">
<string>Culling Correction</string>
</property>
</widget>
</item>
<item>
<widget class="QCheckBox" name="pgxpTextureCorrection">
<property name="text">
<string>Texture Correction</string>
</property>
</widget>
</item>
<item>
<widget class="QCheckBox" name="pgxpVertexCache">
<property name="text">
<string>Vertex Cache</string>
</property>
</widget>
</item>
</layout>
</widget>
</item>
<item>
<spacer name="verticalSpacer">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>40</height>
</size>
</property>
</spacer>
</item>
</layout>
</widget>
</widget>
</item>
<item>
<widget class="QGroupBox" name="groupBox_3">
<property name="title">
<string>Screen Display</string>
</property>
<layout class="QFormLayout" name="formLayout_4">
<item row="0" column="0">
<widget class="QLabel" name="label_4">
<property name="text">
<string>Aspect Ratio:</string>
</property>
</widget>
</item>
<item row="0" column="1">
<widget class="QComboBox" name="displayAspectRatio"/>
</item>
<item row="1" column="0">
<widget class="QLabel" name="label_3">
<property name="text">
<string>Crop:</string>
</property>
</widget>
</item>
<item row="1" column="1">
<widget class="QComboBox" name="displayCropMode"/>
</item>
<item row="2" column="0" colspan="2">
<widget class="QCheckBox" name="displayLinearFiltering">
<property name="text">
<string>Linear Upscaling</string>
</property>
</widget>
</item>
<item row="3" column="0" colspan="2">
<widget class="QCheckBox" name="displayIntegerScaling">
<property name="text">
<string>Integer Upscaling</string>
</property>
</widget>
</item>
<item row="4" column="0" colspan="2">
<widget class="QCheckBox" name="vsync">
<property name="text">
<string>VSync</string>
</property>
</widget>
</item>
</layout>
</widget>
</item>
<item>
<widget class="QGroupBox" name="groupBox_2">
<property name="title">
<string>Enhancements</string>
</property>
<layout class="QFormLayout" name="formLayout_2">
<item row="0" column="0">
<widget class="QLabel" name="label_2">
<property name="text">
<string>Resolution Scale:</string>
</property>
</widget>
</item>
<item row="0" column="1">
<widget class="QComboBox" name="resolutionScale"/>
</item>
<item row="1" column="0" colspan="2">
<widget class="QCheckBox" name="trueColor">
<property name="text">
<string>True Color Rendering (24-bit, disables dithering)</string>
</property>
</widget>
</item>
<item row="2" column="0" colspan="2">
<widget class="QCheckBox" name="scaledDithering">
<property name="text">
<string>Scaled Dithering (scale dither pattern to resolution)</string>
</property>
</widget>
</item>
<item row="3" column="0" colspan="2">
<widget class="QCheckBox" name="disableInterlacing">
<property name="text">
<string>Disable Interlacing (force progressive render/scan)</string>
</property>
</widget>
</item>
<item row="4" column="0" colspan="2">
<widget class="QCheckBox" name="forceNTSCTimings">
<property name="text">
<string>Force NTSC Timings (60hz-on-PAL)</string>
</property>
</widget>
</item>
<item row="5" column="0" colspan="2">
<widget class="QCheckBox" name="linearTextureFiltering">
<property name="text">
<string>Bilinear Texture Filtering</string>
</property>
</widget>
</item>
<item row="6" column="0" colspan="2">
<widget class="QCheckBox" name="widescreenHack">
<property name="text">
<string>Widescreen Hack</string>
</property>
</widget>
</item>
</layout>
</widget>
</item>
<item>
<spacer name="verticalSpacer">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>40</height>
</size>
</property>
</spacer>
</item>
</layout>
</widget>
<resources/>
@@ -858,6 +858,18 @@ void SDLHostInterface::DrawQuickSettingsMenu()
ImGui::EndMenu();
}

if (ImGui::BeginMenu("PGXP"))
{
settings_changed |= ImGui::MenuItem("PGXP Enabled", nullptr, &m_settings_copy.gpu_pgxp_enable);
settings_changed |=
ImGui::MenuItem("PGXP Culling", nullptr, &m_settings_copy.gpu_pgxp_culling, m_settings_copy.gpu_pgxp_enable);
settings_changed |= ImGui::MenuItem("PGXP Texture Correction", nullptr,
&m_settings_copy.gpu_pgxp_texture_correction, m_settings_copy.gpu_pgxp_enable);
settings_changed |= ImGui::MenuItem("PGXP Vertex Cache", nullptr, &m_settings_copy.gpu_pgxp_vertex_cache,
m_settings_copy.gpu_pgxp_enable);
ImGui::EndMenu();
}

settings_changed |= ImGui::MenuItem("True (24-Bit) Color", nullptr, &m_settings_copy.gpu_true_color);
settings_changed |= ImGui::MenuItem("Scaled Dithering", nullptr, &m_settings_copy.gpu_scaled_dithering);
settings_changed |= ImGui::MenuItem("Texture Filtering", nullptr, &m_settings_copy.gpu_texture_filtering);
@@ -1316,6 +1328,11 @@ void SDLHostInterface::DrawSettingsWindow()
settings_changed |= ImGui::Checkbox("Disable Interlacing", &m_settings_copy.gpu_disable_interlacing);
settings_changed |= ImGui::Checkbox("Force NTSC Timings", &m_settings_copy.gpu_force_ntsc_timings);
settings_changed |= ImGui::Checkbox("Widescreen Hack", &m_settings_copy.gpu_widescreen_hack);

settings_changed |= ImGui::Checkbox("PGXP Enabled", &m_settings_copy.gpu_pgxp_enable);
settings_changed |= ImGui::Checkbox("PGXP Culling", &m_settings_copy.gpu_pgxp_culling);
settings_changed |= ImGui::Checkbox("PGXP Texture Correction", &m_settings_copy.gpu_pgxp_texture_correction);
settings_changed |= ImGui::Checkbox("PGXP Vertex Cache", &m_settings_copy.gpu_pgxp_vertex_cache);
}

ImGui::EndTabItem();
@@ -8,11 +8,13 @@
#include "controller_interface.h"
#include "core/cdrom.h"
#include "core/controller.h"
#include "core/cpu_code_cache.h"
#include "core/dma.h"
#include "core/game_list.h"
#include "core/gpu.h"
#include "core/host_display.h"
#include "core/mdec.h"
#include "core/pgxp.h"
#include "core/save_state_version.h"
#include "core/spu.h"
#include "core/system.h"
@@ -1295,6 +1297,22 @@ void CommonHostInterface::RegisterGraphicsHotkeys()
ToggleSoftwareRendering();
});

RegisterHotkey(StaticString("Graphics"), StaticString("TogglePGXP"), StaticString("Toggle PGXP"),
[this](bool pressed) {
if (!pressed)
{
g_settings.gpu_pgxp_enable = !g_settings.gpu_pgxp_enable;
ReportFormattedMessage("PGXP is now %s.", g_settings.gpu_pgxp_enable ? "enabled" : "disabled");

if (g_settings.gpu_pgxp_enable)
PGXP::Initialize();

// we need to recompile all blocks if pgxp is toggled on/off
if (g_settings.IsUsingCodeCache())
CPU::CodeCache::Flush();
}
});

RegisterHotkey(StaticString("Graphics"), StaticString("IncreaseResolutionScale"),
StaticString("Increase Resolution Scale"), [this](bool pressed) {
if (!pressed)