From e689b1f06e295f7c606db6ea648114d5c7d0cb6a Mon Sep 17 00:00:00 2001 From: TJnotJT Date: Tue, 31 Mar 2026 22:57:39 -0400 Subject: [PATCH] GS: Add utility functions for vertices/quads. --- pcsx2/GS/GSState.cpp | 250 +++++++++++++++++++++++++++ pcsx2/GS/GSState.h | 26 +++ pcsx2/GS/Renderers/Common/GSVertex.h | 34 ++++ 3 files changed, 310 insertions(+) diff --git a/pcsx2/GS/GSState.cpp b/pcsx2/GS/GSState.cpp index 803f974258417..1a4241d98032c 100644 --- a/pcsx2/GS/GSState.cpp +++ b/pcsx2/GS/GSState.cpp @@ -4540,6 +4540,256 @@ __forceinline bool GSState::EarlyDetectShuffle(u32 prim) return false; } +__fi GSVector4 GSState::GetXYWindow(const GSVertex& v) +{ + return GSVector4(GetVertexXY(v) - m_context->scissor.xyof.xyxy()) / 16.0f; +} + +template +__fi GSVector4 GSState::GetTexCoordsImpl(const GSVertex& v, float q) +{ + if constexpr (fst) + { + return GSVector4(GetVertexUV(v)) / 16.0f; + } + else + { + const float tw = static_cast(1 << m_context->TEX0.TW); + const float th = static_cast(1 << m_context->TEX0.TH); + const GSVector4 tex_size(tw, th, tw, th); + return GSVector4(GetVertexST(v) / q * tex_size); + } +} + +template +__fi GSVector4 GSState::GetTexCoordsImpl(const GSVertex& v) +{ + return GetTexCoordsImpl(v, v.RGBAQ.Q); +} + +__fi GSVector4 GSState::GetTexCoords(const GSVertex& v, float q) +{ + if (PRIM->FST) + { + return GetTexCoordsImpl(v, q); + } + else + { + return GetTexCoordsImpl(v, q); + } +} + +__fi GSVector4 GSState::GetTexCoords(const GSVertex& v) +{ + return GetTexCoords(v, v.RGBAQ.Q); +} + +template +bool GSState::GetQuadCornersImpl(const GSVertex* v, const u16* i, GSVertex& vout0, GSVertex& vout1) +{ + static_assert(primclass == GS_SPRITE_CLASS || primclass == GS_TRIANGLE_CLASS); + + if constexpr (primclass == GS_TRIANGLE_CLASS) + { + TriangleOrdering tri0; + TriangleOrdering tri1; + + const u16* i0 = i + 0; + const u16* i1 = i + 3; + + if (!AreTrianglesQuad(v, i0, i1, &tri0, &tri1)) + return false; + + vout0 = v[i0[tri0.b]]; + vout1 = v[i1[tri1.b]]; + } + else + { + // primclass == GS_SPRITE_CLASS + vout0 = v[i[0]]; + vout1 = v[i[1]]; + } + + return true; +} + +template +void GSState::GetQuadBBoxWindowImpl(const GSVertex& v0, const GSVertex& v1, GSVector4& xyout) +{ + const GSVector4 xy0 = GetXYWindow(v0); + const GSVector4 xy1 = GetXYWindow(v1); + + xyout = xy0.min(xy1).xyzw(xy0.max(xy1)); +} + +template +void GSState::GetQuadBBoxWindowImpl(const GSVertex& v0, const GSVertex& v1, GSVector4& xyout, GSVector4& texout, bool keep_tex_order) +{ + if constexpr (!tme) + { + GetQuadBBoxWindowImpl(v0, v1, xyout); + return; + } + + GSVector4 xy0 = GetXYWindow(v0); + GSVector4 xy1 = GetXYWindow(v1); + GSVector4 tex0 = GetTexCoordsImpl(v0, primclass == GS_SPRITE_CLASS ? v1.RGBAQ.Q : v0.RGBAQ.Q); + GSVector4 tex1 = GetTexCoordsImpl(v1, v1.RGBAQ.Q); + + if (!keep_tex_order) + { + xyout = xy0.min(xy1).xyzw(xy0.max(xy1)); + texout = tex0.min(tex1).xyzw(tex0.max(tex1)); + } + else + { + xyout = xy0.xyzw(xy1); + texout = tex0.xyzw(tex1); + + const int swap = (xy0 > xy1).mask(); + + if (swap & 1) + { + xyout = xyout.zyxw(); + texout = texout.zyxw(); + } + + if (swap & 2) + { + xyout = xyout.xwzy(); + texout = texout.xwzy(); + } + } +} + +#define GEN_TMPL_SELECT_1(func, ...) \ +if (m_vt.m_primclass == GS_TRIANGLE_CLASS) \ +{ \ + func(__VA_ARGS__); \ +} \ +else if (m_vt.m_primclass == GS_SPRITE_CLASS) \ +{ \ + func(__VA_ARGS__); \ +} \ +else \ +{ \ + pxFail("Wrong prim class."); \ +} + + +#define GEN_TMPL_SELECT_2(func, ...) \ +if (m_vt.m_primclass == GS_TRIANGLE_CLASS) \ +{ \ + if (PRIM->TME) \ + { \ + if (PRIM->FST) \ + { \ + return func(__VA_ARGS__); \ + } \ + else \ + { \ + return func(__VA_ARGS__); \ + } \ + } \ + else \ + { \ + return func(__VA_ARGS__); \ + } \ +} \ +else if (m_vt.m_primclass == GS_SPRITE_CLASS) \ +{ \ + if (PRIM->TME) \ + { \ + if (PRIM->FST) \ + { \ + return func(__VA_ARGS__); \ + } \ + else \ + { \ + return func(__VA_ARGS__); \ + } \ + } \ + else \ + { \ + return func(__VA_ARGS__); \ + } \ +} \ +else \ +{ \ + pxFail("Wrong prim class."); \ +} + +bool GSState::GetQuadCorners(const GSVertex* v, const u16* i, GSVertex& vout0, GSVertex& vout1) +{ + GEN_TMPL_SELECT_2(GetQuadCornersImpl, v, i, vout0, vout1); + return false; +} + +void GSState::GetQuadBBoxWindow(const GSVertex& v0, const GSVertex& v1, GSVector4& xyout) +{ + GEN_TMPL_SELECT_1(GetQuadBBoxWindowImpl, v0, v1, xyout); +} + +void GSState::GetQuadBBoxWindow(const GSVertex& v0, const GSVertex& v1, GSVector4& xyout, GSVector4& texout, bool keep_tex_order) +{ + GEN_TMPL_SELECT_2(GetQuadBBoxWindowImpl, v0, v1, xyout, texout, keep_tex_order); +} + +#undef GEN_TMPL_SELECT_2 +#undef GEN_TMPL_SELECT_1 + +void GSState::GetQuadRasterizedPoints(GSVector4& xy, GSVector4& tex, bool keep_order) +{ + // Swap so that coordinates are top-left and bottom-right. + const int swap = (xy.xyxy() > xy.zwzw()).mask(); + + if (swap & 1) + { + xy = xy.zyxw(); + tex = tex.zyxw(); + } + + if (swap & 2) + { + xy = xy.xwzy(); + tex = tex.xwzy(); + } + + const GSVector4 grad = (tex.zwzw() - tex.xyxy()) / (xy.zwzw() - xy.xyxy()); + + // Round XY to contained pixels. Omit bottom-right pixels on the edge. + GSVector4 xy_round = xy.ceil().xyzw(xy.floor()); + const GSVector4 bottom_right = GSVector4::zero().xyzw(xy == xy_round); + xy_round = xy_round.blend32(xy_round - GSVector4(1.0f), bottom_right); + + // Interpolate texture coords. + tex += grad * (xy_round - xy); + + xy = xy_round; + + // Swap back to original order if needed. + if (keep_order) + { + if (swap & 1) + { + xy = xy.zyxw(); + tex = tex.zyxw(); + } + + if (swap & 2) + { + xy = xy.xwzy(); + tex = tex.xwzy(); + } + } +} + +void GSState::GetQuadRasterizedPoints(GSVector4& xy, bool keep_order) +{ + GSVector4 tex_ignore; + GetQuadRasterizedPoints(xy, tex_ignore, keep_order); +} + __forceinline bool GSState::IsAutoFlushDraw(u32 prim, int& tex_layer) { if (!PRIM->TME || (GSConfig.UserHacks_AutoFlush == GSHWAutoFlushLevel::SpritesOnly && prim != GS_SPRITE)) diff --git a/pcsx2/GS/GSState.h b/pcsx2/GS/GSState.h index 3a0d59ebe95cf..377f3aa3fbb89 100644 --- a/pcsx2/GS/GSState.h +++ b/pcsx2/GS/GSState.h @@ -211,6 +211,32 @@ class GSState : public GSAlignedClass<32> void CalcAlphaMinMax(const int tex_min, const int tex_max); void CorrectATEAlphaMinMax(const u32 atst, const int aref); + // Utility functions for getting position/texture coordinates. + GSVector4 GetXYWindow(const GSVertex& v); + template + GSVector4 GetTexCoordsImpl(const GSVertex& v, float q); + template + GSVector4 GetTexCoordsImpl(const GSVertex& v); + GSVector4 GetTexCoords(const GSVertex& v, float q); + GSVector4 GetTexCoords(const GSVertex& v); + + // Utility functions to detect and get corners of quads. + template + static bool GetQuadCornersImpl(const GSVertex* v, const u16* i, GSVertex& vout0, GSVertex& vout1); + bool GetQuadCorners(const GSVertex* v, const u16* i, GSVertex& vout0, GSVertex& vout1); + + // Utility functions to get window/texture coordinates of a quad. + template + void GetQuadBBoxWindowImpl(const GSVertex& v0, const GSVertex& v1, GSVector4& xyout); + template + void GetQuadBBoxWindowImpl(const GSVertex& v0, const GSVertex& v1, GSVector4& xyout, GSVector4& texout, bool keep_tex_order = true); + void GetQuadBBoxWindow(const GSVertex& v0, const GSVertex& v1, GSVector4& xyout); + void GetQuadBBoxWindow(const GSVertex& v0, const GSVertex& v1, GSVector4& xyout, GSVector4& texout, bool keep_tex_order = true); + + // Adjusts a quad so that it contains exactly the centers of the pixels that the GS would rasterize. + static void GetQuadRasterizedPoints(GSVector4& xy, bool keep_order = true); + static void GetQuadRasterizedPoints(GSVector4& xy, GSVector4& tex, bool keep_order = true); + public: enum EEGS_TransferType { diff --git a/pcsx2/GS/Renderers/Common/GSVertex.h b/pcsx2/GS/Renderers/Common/GSVertex.h index eb82b71b1209b..a35cd8f64e09e 100644 --- a/pcsx2/GS/Renderers/Common/GSVertex.h +++ b/pcsx2/GS/Renderers/Common/GSVertex.h @@ -44,3 +44,37 @@ struct alignas(32) GSVertexPT1 static_assert(sizeof(GSVertexPT1) == sizeof(GSVertex)); +__forceinline_odr GSVector4i GetVertexXY(const GSVertex& v) +{ + return GSVector4i(v.m[1]).upl16().xyxy(); +} + +__forceinline_odr GSVector4i GetVertexZ(const GSVertex& v) +{ + return GSVector4i(v.m[1]).yyyy(); +} + +__forceinline_odr GSVector4i GetVertexUV(const GSVertex& v) +{ + return GSVector4i(v.m[1]).uph16().xyxy(); +} + +__forceinline_odr GSVector4 GetVertexST(const GSVertex& v) +{ + return GSVector4::cast(GSVector4i(v.m[0])).xyxy(); +} + +__forceinline_odr GSVector4i GetVertexRGBA(const GSVertex& v) +{ + return GSVector4i(v.m[0]).uph8().upl16(); +} + +__forceinline_odr GSVector4 GetVertexQ(const GSVertex& v) +{ + return GSVector4::cast(GSVector4i(v.m[0])).wwww(); +} + +__forceinline_odr GSVector4i GetVertexFOG(const GSVertex& v) +{ + return GSVector4i(v.m[1]).wwww(); +} \ No newline at end of file