mirror of
https://github.com/hrydgard/ppsspp.git
synced 2026-10-14 15:00:30 +02:00
Sample the last texel where the projected q isn't positive
The GE doesn't divide by a q <= 0: it samples the last texel in u and v, whatever s and t are, in clamp and repeat modes alike (gpu/texmtx/negq). The hardware backends divided, which mirrors a projected texture behind its projector: Fate/Extra's character shadows ran up the school's walls (#18667). The software renderer sampled (0, 0) instead. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
b9b1a29c42
commit
763bcde73e
5 files changed
+29
-16
No files matched your search
@@ -556,6 +556,13 @@ bool GenerateFragmentShader(const FShaderID &id, char *buffer, const ShaderLangu
|
||||
|
||||
if (doTexture) {
|
||||
char texcoord[64] = "v_texcoord";
|
||||
if (doTextureProjection) {
|
||||
// Where q isn't positive, the GE samples the last texel instead of dividing (gpu/texmtx/negq, and
|
||||
// GE_NONPOSITIVE_Q_UV in the software renderer). Dividing by a negative q mirrors the texture behind the
|
||||
// projector: Fate/Extra's projected shadows ran up the walls (#18667).
|
||||
WRITE(p, " vec3 projTexcoord = v_texcoord.z > 0.0 ? v_texcoord : vec3(0.99998474, 0.99998474, 1.0);\n");
|
||||
truncate_cpy(texcoord, "projTexcoord");
|
||||
}
|
||||
// TODO: Not sure the right way to do this for projection.
|
||||
// This path destroys resolution on older PowerVR no matter what I do if projection is needed,
|
||||
// so we disable it on SGX 540 and lesser, and live with the consequences.
|
||||
@@ -572,8 +579,8 @@ bool GenerateFragmentShader(const FShaderID &id, char *buffer, const ShaderLangu
|
||||
std::string ucoord = "v_texcoord.x";
|
||||
std::string vcoord = "v_texcoord.y";
|
||||
if (doTextureProjection) {
|
||||
ucoord = "(v_texcoord.x / v_texcoord.z)";
|
||||
vcoord = "(v_texcoord.y / v_texcoord.z)";
|
||||
ucoord = "(projTexcoord.x / projTexcoord.z)";
|
||||
vcoord = "(projTexcoord.y / projTexcoord.z)";
|
||||
}
|
||||
|
||||
std::string modulo = (gl_extensions.bugs & BUG_PVR_SHADER_PRECISION_BAD) ? "mymod" : "mod";
|
||||
@@ -602,13 +609,13 @@ bool GenerateFragmentShader(const FShaderID &id, char *buffer, const ShaderLangu
|
||||
if (compat.shaderLanguage == HLSL_D3D11) {
|
||||
if (texture3D) {
|
||||
if (doTextureProjection) {
|
||||
WRITE(p, " vec4 t = tex.Sample(texSamp, vec3(v_texcoord.xy / v_texcoord.z, u_mipBias));\n");
|
||||
WRITE(p, " vec4 t = tex.Sample(texSamp, vec3(%s.xy / %s.z, u_mipBias));\n", texcoord, texcoord);
|
||||
} else {
|
||||
WRITE(p, " vec4 t = tex.Sample(texSamp, vec3(%s.xy, u_mipBias));\n", texcoord);
|
||||
}
|
||||
} else {
|
||||
if (doTextureProjection) {
|
||||
WRITE(p, " vec4 t = tex.Sample(texSamp, v_texcoord.xy / v_texcoord.z);\n");
|
||||
WRITE(p, " vec4 t = tex.Sample(texSamp, %s.xy / %s.z);\n", texcoord, texcoord);
|
||||
} else {
|
||||
WRITE(p, " vec4 t = tex.Sample(texSamp, %s.xy);\n", texcoord);
|
||||
}
|
||||
|
||||
+11
-5
@@ -451,12 +451,18 @@ inline float GEUVProduct(double d) {
|
||||
return (float)d;
|
||||
}
|
||||
|
||||
// Where q isn't positive, the GE samples the last texel in u and v, whatever s and t are and in both clamp
|
||||
// and wrap modes (gpu/texmtx/negq): a saturated coordinate. 1 - 2^-16 lands on texel size - 1 with the highest
|
||||
// subtexel fraction, for any texture size.
|
||||
constexpr float GE_NONPOSITIVE_Q_UV = 1.0f - 1.0f / 65536.0f;
|
||||
constexpr int32_t GE_NONPOSITIVE_Q_UV_BITS = 0x3F7FFF00;
|
||||
|
||||
// The rasterizer's texture coordinates for four pixels (Rasterizer's GetTextureCoordinatesGE): from the UV
|
||||
// planes' values at them (each times 2^scaleExp), q as a float24, and s and t as GEUVProduct(float24 *
|
||||
// GERecip(q)), or 0 where q isn't positive. False, with the outputs untouched, when a lane needs more than
|
||||
// the vector path does (a value of 2^24 or more, a denormal or out of range result): then do it scalar.
|
||||
// The core takes the values as lanes, each below 2^24; with check false, the caller knows they're inside
|
||||
// what the vector path covers (GEUVSpanSafe).
|
||||
// GERecip(q)), or GE_NONPOSITIVE_Q_UV where q isn't positive. False, with the outputs untouched, when a lane
|
||||
// needs more than the vector path does (a value of 2^24 or more, a denormal or out of range result): then do
|
||||
// it scalar. The core takes the values as lanes, each below 2^24; with check false, the caller knows they're
|
||||
// inside what the vector path covers (GEUVSpanSafe).
|
||||
template <bool check>
|
||||
inline bool GEUVSpanCore(Vec4S32 qv, Vec4S32 sv, Vec4S32 tv, int expQ, int expS, int expT, float *s, float *t, float *q) {
|
||||
const Vec4S32 expMask = Vec4S32::Splat(0x7F800000);
|
||||
@@ -517,7 +523,7 @@ inline bool GEUVSpanCore(Vec4S32 qv, Vec4S32 sv, Vec4S32 tv, int expQ, int expS,
|
||||
const Vec4S32 sign = xb & Vec4S32::Splat((int)0x80000000);
|
||||
const Vec4S32 result = (Vec4S32FromBits(Vec4F32FromS32(m)) + adjust.Shl<23>()) | sign;
|
||||
// Zero stays as it is, with its sign.
|
||||
return ((result.AndNot(xZero) | (xb & xZero)) & valid);
|
||||
return ((result.AndNot(xZero) | (xb & xZero)) & valid) | Vec4S32::Splat(GE_NONPOSITIVE_Q_UV_BITS).AndNot(valid);
|
||||
};
|
||||
const Vec4S32 so = product(sb);
|
||||
const Vec4S32 to = product(tb);
|
||||
|
||||
@@ -1236,8 +1236,8 @@ static inline void GetTextureCoordinatesGE(const UVPlanes &planes, const int64_t
|
||||
const float q = TruncateToFloat24((float)((double)qs[i] * planes.scaleQ));
|
||||
qOut[i] = q;
|
||||
if (!(q > 0.0f)) {
|
||||
s[i] = 0.0f;
|
||||
t[i] = 0.0f;
|
||||
s[i] = GE_NONPOSITIVE_Q_UV;
|
||||
t[i] = GE_NONPOSITIVE_Q_UV;
|
||||
continue;
|
||||
}
|
||||
const double r = GERecip(q);
|
||||
@@ -2616,8 +2616,8 @@ static float LineUVComponentAt(const int64_t c[2], double scale, const VertexDat
|
||||
static void LineTextureCoordinatesAt(const LineUV &uv, const VertexData &v0, const VertexData &v1, int px, int py, float &s, float &t, float &q) {
|
||||
q = LineUVComponentAt(uv.q, uv.scaleQ, v0, v1, px, py);
|
||||
if (!(q > 0.0f)) {
|
||||
s = 0.0f;
|
||||
t = 0.0f;
|
||||
s = GE_NONPOSITIVE_Q_UV;
|
||||
t = GE_NONPOSITIVE_Q_UV;
|
||||
return;
|
||||
}
|
||||
const double r = GERecip(q);
|
||||
|
||||
+1
-1
Submodule pspautotests updated: c662bd80cd...e1d59fe556.
@@ -281,7 +281,7 @@ static bool TestGEUVSpan() {
|
||||
fast++;
|
||||
for (int l = 0; l < 4; ++l) {
|
||||
const float wq = TruncateToFloat24((float)ldexp((double)qs[l], expQ));
|
||||
float ws = 0.0f, wt = 0.0f;
|
||||
float ws = GE_NONPOSITIVE_Q_UV, wt = GE_NONPOSITIVE_Q_UV;
|
||||
if (wq > 0.0f) {
|
||||
const double r = GERecip(wq);
|
||||
ws = GEUVProduct((double)TruncateToFloat24((float)ldexp((double)ss[l], expS)) * r);
|
||||
@@ -312,7 +312,7 @@ static bool TestGEUVSpan() {
|
||||
GEUVSpanCore<false>(Vec4S32::Load(qv), Vec4S32::Load(sv), Vec4S32::Load(tv), expQ, expS, expT, s, t, q);
|
||||
for (int l = 0; l < 4; ++l) {
|
||||
const float wq = TruncateToFloat24((float)ldexp((double)qv[l], expQ));
|
||||
float ws = 0.0f, wt = 0.0f;
|
||||
float ws = GE_NONPOSITIVE_Q_UV, wt = GE_NONPOSITIVE_Q_UV;
|
||||
if (wq > 0.0f) {
|
||||
const double r = GERecip(wq);
|
||||
ws = GEUVProduct((double)TruncateToFloat24((float)ldexp((double)sv[l], expS)) * r);
|
||||
|
||||
Reference in new issue
Block a user