mirror of
https://github.com/ARMSX2/ARMSX2.git
synced 2026-08-24 16:50:16 -07:00
GV7-0e: PCRTC_SYNC + VSYNC records with inline executors
PCRTCDisplays is not vsync-only state — the HW Draw() heuristics read it per draw — so under the split it is duplicated front/back and refreshed by a PCRTC_SYNC record carrying the whole digested GSPCRTCRegs plus the pre-decrement scanmask counter. GSvsync now digests (unchanged), submits the PCRTC record, flushes, then submits a VSYNC record carrying field/registers_written/idle_frame; the executor runs the whole VSync() body (Merge, present, capture). Record order reproduces today's semantics: vsync-flushed draws see the fresh display state, mid-frame draws the previous frame's. Merge's scanmask decrement stays back-side; the front mirrors it at enqueue once the copies are distinct (GV7-1). GSPCRTCRegs hoists to GSBackQueue.h as the record payload type; GSState keeps an alias. Gate: gs_vertex_tests 17/17; gsrunner PNG hashes bit-identical to the GV-0 baselines on all dumps, Vulkan and SW. Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude
parent
f7816e589c
commit
7e612f4750
+5
-1
@@ -455,10 +455,14 @@ void GSvsync(u32 field, bool registers_written)
|
||||
g_gs_renderer->PCRTCDisplays.CalculateDisplayOffset(g_gs_renderer->m_scanmask_used);
|
||||
g_gs_renderer->PCRTCDisplays.CalculateFramebufferOffset(g_gs_renderer->m_scanmask_used, g_gs_renderer->m_regs->DISP[0].DISPFB, g_gs_renderer->m_regs->DISP[1].DISPFB);
|
||||
|
||||
// The PCRTC record must precede the vsync-flushed draw records — those draws
|
||||
// see the fresh display state, mid-frame draws saw the previous frame's.
|
||||
g_gs_renderer->SubmitPcrtcSync();
|
||||
|
||||
// Do not move the flush into the VSync() method. It's here because EE transfers
|
||||
// get cleared in HW VSync, and may be needed for a buffered draw (FFX FMVs).
|
||||
g_gs_renderer->Flush(GSState::VSYNC);
|
||||
g_gs_renderer->VSync(field, registers_written, g_gs_renderer->IsIdleFrame());
|
||||
g_gs_renderer->SubmitVsync(field, registers_written);
|
||||
}
|
||||
|
||||
int GSfreeze(FreezeAction mode, freezeData* data)
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "GS/GS.h"
|
||||
#include "GS/GSRegs.h"
|
||||
#include "GS/GSVector.h"
|
||||
#include "GS/GSDrawingContext.h"
|
||||
@@ -96,6 +97,97 @@ namespace GSBackQueue
|
||||
u32 tail;
|
||||
};
|
||||
|
||||
// PCRTC digest state — hoisted from GSState (GSvsync writes it once per
|
||||
// frame from the privileged registers; the Draw() heuristics and the Merge
|
||||
// circuit read it back-side, so it ships whole in PCRTC_SYNC records).
|
||||
struct GSPCRTCRegs
|
||||
{
|
||||
struct PCRTCDisplay
|
||||
{
|
||||
bool enabled;
|
||||
int FBP;
|
||||
int FBW;
|
||||
int PSM;
|
||||
int DBY;
|
||||
int DBX;
|
||||
GSRegDISPFB prevFramebufferReg;
|
||||
GSVector2i prevDisplayOffset;
|
||||
GSVector2i displayOffset;
|
||||
GSVector4i displayRect;
|
||||
GSVector2i magnification;
|
||||
GSVector2i prevFramebufferOffsets;
|
||||
GSVector2i framebufferOffsets;
|
||||
GSVector4i framebufferRect;
|
||||
|
||||
__fi int Block() const { return FBP << 5; }
|
||||
};
|
||||
|
||||
int videomode = 0;
|
||||
int interlaced = 0;
|
||||
int FFMD = 0;
|
||||
bool PCRTCSameSrc = false;
|
||||
bool toggling_field = false;
|
||||
PCRTCDisplay PCRTCDisplays[2] = {};
|
||||
|
||||
bool IsAnalogue();
|
||||
|
||||
// Calculates which display is closest to matching zero offsets in either direction.
|
||||
GSVector2i NearestToZeroOffset();
|
||||
|
||||
void SetVideoMode(GSVideoMode videoModeIn);
|
||||
|
||||
// Enable each of the displays.
|
||||
void EnableDisplays(GSRegPMODE pmode, GSRegSMODE2 smode2, bool smodetoggle);
|
||||
|
||||
void CheckSameSource();
|
||||
|
||||
bool FrameWrap();
|
||||
|
||||
// If the start point of both frames match, we can do a single read
|
||||
bool FrameRectMatch();
|
||||
|
||||
GSVector2i GetResolution();
|
||||
|
||||
GSVector4i GetFramebufferRect(int display);
|
||||
|
||||
int GetFramebufferBitDepth();
|
||||
|
||||
GSVector2i GetFramebufferSize(int display);
|
||||
|
||||
// Sets up the rectangles for both the framebuffer read and the displays for the merge circuit.
|
||||
void SetRects(int display, GSRegDISPLAY displayReg, GSRegDISPFB framebufferReg);
|
||||
|
||||
// Calculate framebuffer read offsets, should be considered if only one circuit is enabled, or difference is more than 1 line.
|
||||
// Only considered if "Anti-blur" is enabled.
|
||||
void CalculateFramebufferOffset(bool scanmask, GSRegDISPFB framebuffer0Reg, GSRegDISPFB framebuffer1Reg);
|
||||
|
||||
// Used in software mode to align the buffer when reading. Offset is accounted for (block aligned) by GetOutput.
|
||||
void RemoveFramebufferOffset(int display);
|
||||
|
||||
// If the two displays are offset from each other, move them to the correct offsets.
|
||||
// If using screen offsets, calculate the positions here.
|
||||
void CalculateDisplayOffset(bool scanmask);
|
||||
};
|
||||
|
||||
// Once-per-frame PCRTC digest, shipped BEFORE the vsync-flushed draw
|
||||
// records so those draws see the fresh display state, exactly like today
|
||||
// (GSvsync digests, then flushes). Mid-frame draws keep seeing the previous
|
||||
// frame's digest, also like today.
|
||||
struct PcrtcSyncRecord
|
||||
{
|
||||
GSPCRTCRegs displays;
|
||||
u8 scanmask_used; // pre-decrement value; Merge's decrement stays back-side
|
||||
};
|
||||
|
||||
// End of frame: the whole VSync() body (Merge -> present -> capture ->
|
||||
// perfmon frame tick) runs back-side.
|
||||
struct VsyncRecord
|
||||
{
|
||||
u32 field;
|
||||
bool registers_written;
|
||||
bool idle_frame;
|
||||
};
|
||||
|
||||
// One flushed draw (today's FlushPrim tail: vertex trace -> texel rounding ->
|
||||
// Draw() -> perfmon). Self-contained: the executor installs the env snapshots
|
||||
// and scalars, then runs the tail against the referenced buffers, which it
|
||||
|
||||
@@ -3240,6 +3240,25 @@ void GSState::ExecClutLoadRecord(const GSBackQueue::ClutLoadRecord& rec)
|
||||
m_mem.m_clut.WriteLoad(rec.TEX0, rec.TEXCLUT);
|
||||
}
|
||||
|
||||
void GSState::SubmitPcrtcSync()
|
||||
{
|
||||
GSBackQueue::PcrtcSyncRecord rec;
|
||||
std::memcpy(&rec.displays, &PCRTCDisplays, sizeof(rec.displays));
|
||||
rec.scanmask_used = m_scanmask_used;
|
||||
|
||||
ExecPcrtcSyncRecord(rec);
|
||||
}
|
||||
|
||||
void GSState::ExecPcrtcSyncRecord(const GSBackQueue::PcrtcSyncRecord& rec)
|
||||
{
|
||||
// Inline this re-writes the digest it was captured from; on the back object
|
||||
// (GV7-1) it refreshes the copy read by the Draw() heuristics and the Merge
|
||||
// circuit. Merge's scanmask decrement stays back-side; the front mirrors it
|
||||
// at enqueue once the copies are distinct.
|
||||
std::memcpy(&PCRTCDisplays, &rec.displays, sizeof(PCRTCDisplays));
|
||||
m_scanmask_used = rec.scanmask_used;
|
||||
}
|
||||
|
||||
void GSState::Move()
|
||||
{
|
||||
// ffxii uses this to move the top/bottom of the scrolling menus offscreen and then blends them back over the text to create a shading effect
|
||||
|
||||
+5
-67
@@ -477,74 +477,10 @@ public:
|
||||
std::vector<size_t> m_drawlist;
|
||||
std::vector<GSVector4i> m_drawlist_bbox;
|
||||
|
||||
struct GSPCRTCRegs
|
||||
{
|
||||
struct PCRTCDisplay
|
||||
{
|
||||
bool enabled;
|
||||
int FBP;
|
||||
int FBW;
|
||||
int PSM;
|
||||
int DBY;
|
||||
int DBX;
|
||||
GSRegDISPFB prevFramebufferReg;
|
||||
GSVector2i prevDisplayOffset;
|
||||
GSVector2i displayOffset;
|
||||
GSVector4i displayRect;
|
||||
GSVector2i magnification;
|
||||
GSVector2i prevFramebufferOffsets;
|
||||
GSVector2i framebufferOffsets;
|
||||
GSVector4i framebufferRect;
|
||||
// Definition hoisted to GSBackQueue.h (PCRTC_SYNC record payload type).
|
||||
using GSPCRTCRegs = GSBackQueue::GSPCRTCRegs;
|
||||
|
||||
__fi int Block() const { return FBP << 5; }
|
||||
};
|
||||
|
||||
int videomode = 0;
|
||||
int interlaced = 0;
|
||||
int FFMD = 0;
|
||||
bool PCRTCSameSrc = false;
|
||||
bool toggling_field = false;
|
||||
PCRTCDisplay PCRTCDisplays[2] = {};
|
||||
|
||||
bool IsAnalogue();
|
||||
|
||||
// Calculates which display is closest to matching zero offsets in either direction.
|
||||
GSVector2i NearestToZeroOffset();
|
||||
|
||||
void SetVideoMode(GSVideoMode videoModeIn);
|
||||
|
||||
// Enable each of the displays.
|
||||
void EnableDisplays(GSRegPMODE pmode, GSRegSMODE2 smode2, bool smodetoggle);
|
||||
|
||||
void CheckSameSource();
|
||||
|
||||
bool FrameWrap();
|
||||
|
||||
// If the start point of both frames match, we can do a single read
|
||||
bool FrameRectMatch();
|
||||
|
||||
GSVector2i GetResolution();
|
||||
|
||||
GSVector4i GetFramebufferRect(int display);
|
||||
|
||||
int GetFramebufferBitDepth();
|
||||
|
||||
GSVector2i GetFramebufferSize(int display);
|
||||
|
||||
// Sets up the rectangles for both the framebuffer read and the displays for the merge circuit.
|
||||
void SetRects(int display, GSRegDISPLAY displayReg, GSRegDISPFB framebufferReg);
|
||||
|
||||
// Calculate framebuffer read offsets, should be considered if only one circuit is enabled, or difference is more than 1 line.
|
||||
// Only considered if "Anti-blur" is enabled.
|
||||
void CalculateFramebufferOffset(bool scanmask, GSRegDISPFB framebuffer0Reg, GSRegDISPFB framebuffer1Reg);
|
||||
|
||||
// Used in software mode to align the buffer when reading. Offset is accounted for (block aligned) by GetOutput.
|
||||
void RemoveFramebufferOffset(int display);
|
||||
|
||||
// If the two displays are offset from each other, move them to the correct offsets.
|
||||
// If using screen offsets, calculate the positions here.
|
||||
void CalculateDisplayOffset(bool scanmask);
|
||||
} PCRTCDisplays;
|
||||
GSPCRTCRegs PCRTCDisplays;
|
||||
|
||||
public:
|
||||
/// Returns the appropriate directory for draw dumping.
|
||||
@@ -600,6 +536,8 @@ public:
|
||||
void SubmitClutLoad(const GIFRegTEX0& TEX0, const GIFRegTEXCLUT& TEXCLUT);
|
||||
void ExecClutLoadRecord(const GSBackQueue::ClutLoadRecord& rec);
|
||||
void ExecDrawRecord(const GSBackQueue::DrawRecord& rec);
|
||||
void SubmitPcrtcSync();
|
||||
void ExecPcrtcSyncRecord(const GSBackQueue::PcrtcSyncRecord& rec);
|
||||
|
||||
GSVector4i GetTEX0Rect(GSDrawingContext prev_ctx);
|
||||
void CheckWriteOverlap(bool req_write, bool req_read);
|
||||
|
||||
@@ -580,6 +580,21 @@ void GSRenderer::EndPresentFrame()
|
||||
ImGuiManager::NewFrame();
|
||||
}
|
||||
|
||||
void GSRenderer::SubmitVsync(u32 field, bool registers_written)
|
||||
{
|
||||
GSBackQueue::VsyncRecord rec;
|
||||
rec.field = field;
|
||||
rec.registers_written = registers_written;
|
||||
rec.idle_frame = IsIdleFrame(); // front-computable: compares serials against the last frame's
|
||||
|
||||
ExecVsyncRecord(rec);
|
||||
}
|
||||
|
||||
void GSRenderer::ExecVsyncRecord(const GSBackQueue::VsyncRecord& rec)
|
||||
{
|
||||
VSync(rec.field, rec.registers_written, rec.idle_frame);
|
||||
}
|
||||
|
||||
void GSRenderer::VSync(u32 field, bool registers_written, bool idle_frame)
|
||||
{
|
||||
if (GSConfig.ShouldDump(s_n, g_perfmon.GetFrame()))
|
||||
|
||||
@@ -41,6 +41,8 @@ public:
|
||||
virtual void UpdateRenderFixes();
|
||||
|
||||
virtual void VSync(u32 field, bool registers_written, bool idle_frame);
|
||||
void SubmitVsync(u32 field, bool registers_written);
|
||||
void ExecVsyncRecord(const GSBackQueue::VsyncRecord& rec);
|
||||
virtual bool CanUpscale() { return false; }
|
||||
virtual float GetUpscaleMultiplier() { return 1.0f; }
|
||||
virtual float GetTextureScaleFactor() { return 1.0f; }
|
||||
|
||||
Reference in New Issue
Block a user