Skip to content

Commit a0f4efb

Browse files
committed
GS: Accurate UV rounding for axis-aligned prims.
1 parent bc62452 commit a0f4efb

32 files changed

+1187
-34
lines changed

bin/resources/shaders/dx11/tfx.fx

Lines changed: 73 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
#define VS_IIP 0
1414
#define VS_TME 1
1515
#define VS_FST 1
16+
#define VS_ROUND_UV 0
1617
#endif
1718

1819
#ifndef GS_IIP
@@ -79,6 +80,7 @@
7980
#define PS_NO_COLOR 0
8081
#define PS_NO_COLOR1 0
8182
#define PS_DATE 0
83+
#define PS_ROUND_UV 0
8284
#endif
8385

8486
#define SW_BLEND (PS_BLEND_A || PS_BLEND_B || PS_BLEND_D)
@@ -108,6 +110,10 @@ struct VS_OUTPUT
108110
#else
109111
nointerpolation float4 c : COLOR0;
110112
#endif
113+
114+
#if VS_ROUND_UV != 0
115+
nointerpolation uint4 rounduv : TEXCOORD3;
116+
#endif
111117
};
112118

113119
struct PS_INPUT
@@ -120,6 +126,9 @@ struct PS_INPUT
120126
#else
121127
nointerpolation float4 c : COLOR0;
122128
#endif
129+
#if PS_ROUND_UV != 0
130+
nointerpolation uint4 rounduv : TEXCOORD3;
131+
#endif
123132
#if (PS_DATE >= 1 && PS_DATE <= 3) || GS_FORWARD_PRIMID
124133
uint primid : SV_PrimitiveID;
125134
#endif
@@ -322,6 +331,37 @@ float4 clamp_wrap_uv(float4 uv)
322331
return uv;
323332
}
324333

334+
float4 round_uv(PS_INPUT input)
335+
{
336+
#if PS_ROUND_UV
337+
// Check if we're at the prim top or left.
338+
int2 topleft = int2(int2(input.p.xy) == int2(input.rounduv.xy));
339+
340+
// Get flags for whether to round U, V.
341+
int2 round_flags = int2(input.rounduv.zw);
342+
343+
// Being on the top or left pixels converts round down to round up.
344+
int2 round_down = int2(round_flags == PS_ROUND_UV_DOWN) & ~topleft;
345+
int2 round_up = int2(round_flags == PS_ROUND_UV_UP) |
346+
(int2(round_flags == PS_ROUND_UV_DOWN) & topleft);
347+
348+
float2 uv = input.ti.zw; // Unnormalized UVs.
349+
float2 uvi = round(input.ti.zw / 8.0f) * 8.0f; // Nearest half texel.
350+
351+
// Round only if close to a half texel.
352+
int2 close = int2(abs(uv - uvi) <= PS_ROUND_UV_THRESHOLD);
353+
round_down &= close;
354+
round_up &= close;
355+
356+
uv = bool2(round_down) ? uvi - PS_ROUND_UV_THRESHOLD : uv;
357+
uv = bool2(round_up) ? uvi + PS_ROUND_UV_THRESHOLD : uv;
358+
359+
return float4(uv / 16.0f / WH.xy, uv); // Return normalized and unnormalized coords.
360+
#else
361+
return float4(0.0f, 0.0f, 0.0f, 0.0f);
362+
#endif
363+
}
364+
325365
float4x4 sample_4c(float4 uv, float uv_w, int2 xy)
326366
{
327367
float4x4 c;
@@ -756,6 +796,10 @@ float4 ps_color(PS_INPUT input)
756796
#if PS_FST == 0
757797
float2 st = input.t.xy / input.t.w;
758798
float2 st_int = input.ti.zw / input.t.w;
799+
#elif PS_ROUND_UV != 0
800+
float4 ti_rounded = round_uv(input);
801+
float2 st = ti_rounded.xy;
802+
float2 st_int = ti_rounded.zw;
759803
#else
760804
float2 st = input.ti.xy;
761805
float2 st_int = input.ti.zw;
@@ -1253,6 +1297,22 @@ cbuffer cb0
12531297
uint BaseVertex; // Only used in DX11.
12541298
};
12551299

1300+
float2 sign_extend_16_bit(float2 uv)
1301+
{
1302+
return uv > float(0x7FFF) ? uv - float(0x10000) : uv;
1303+
}
1304+
1305+
uint4 extract_round_uv_bits(float q)
1306+
{
1307+
uint qi = asuint(q);
1308+
return uint4(
1309+
(qi >> 0) & 0xFFF, // Prim left
1310+
(qi >> 12) & 0xFFF, // Prim top
1311+
(qi >> 24) & 0xF, // Round U flags
1312+
(qi >> 28) & 0xF // Round V flags
1313+
);
1314+
}
1315+
12561316
VS_OUTPUT vs_main(VS_INPUT input)
12571317
{
12581318
// Clamp to max depth, gs doesn't wrap
@@ -1272,7 +1332,11 @@ VS_OUTPUT vs_main(VS_INPUT input)
12721332

12731333
if(VS_TME)
12741334
{
1275-
float2 uv = input.uv - TextureOffset;
1335+
#if VS_ROUND_UV == 0
1336+
float2 uv = input.uv - TextureOffset;
1337+
#else
1338+
float2 uv = sign_extend_16_bit(input.uv) - TextureOffset; // Extend sign bit in case ST was converted to UV.
1339+
#endif
12761340
float2 st = input.st - TextureOffset;
12771341

12781342
// Integer nomalized
@@ -1291,12 +1355,20 @@ VS_OUTPUT vs_main(VS_INPUT input)
12911355
// Float coords
12921356
output.t.xy = st;
12931357
output.t.w = input.q;
1358+
1359+
// Get UV rounding info saved in Q.
1360+
#if VS_ROUND_UV
1361+
output.rounduv = extract_round_uv_bits(input.q);
1362+
#endif
12941363
}
12951364
else
12961365
{
12971366
output.t.xy = 0;
12981367
output.t.w = 1.0f;
12991368
output.ti = 0;
1369+
#if VS_ROUND_UV
1370+
output.rounduv = 0;
1371+
#endif
13001372
}
13011373

13021374
output.c = input.c;

bin/resources/shaders/opengl/tfx_fs.glsl

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,10 @@ in SHADER
7070
#else
7171
flat vec4 c;
7272
#endif
73+
74+
#if PS_ROUND_UV != 0
75+
flat uvec4 rounduv;
76+
#endif
7377
} PSin;
7478

7579
#define TARGET_0_QUALIFIER out
@@ -252,6 +256,37 @@ vec4 clamp_wrap_uv(vec4 uv)
252256
return uv_out;
253257
}
254258

259+
vec4 round_uv()
260+
{
261+
#if PS_ROUND_UV
262+
// Check if we're at the prim top or left.
263+
ivec2 topleft = ivec2(equal(ivec2(gl_FragCoord.xy), ivec2(PSin.rounduv.xy)));
264+
265+
// Get flags for whether to round U, V.
266+
ivec2 round_flags = ivec2(PSin.rounduv.zw);
267+
268+
// Being on the top or left pixels converts round down to round up.
269+
ivec2 round_down = ivec2(equal(round_flags, ivec2(PS_ROUND_UV_DOWN))) & ~topleft;
270+
ivec2 round_up = ivec2(equal(round_flags, ivec2(PS_ROUND_UV_UP))) |
271+
(ivec2(equal(round_flags, ivec2(PS_ROUND_UV_DOWN))) & topleft);
272+
273+
vec2 uv = PSin.t_int.zw; // Unnormalized UVs.
274+
vec2 uvi = round(PSin.t_int.zw / 8.0f) * 8.0f; // Nearest half texel.
275+
276+
// Round only if close to a half texel.
277+
ivec2 close = ivec2(lessThanEqual(abs(uv - uvi), vec2(PS_ROUND_UV_THRESHOLD)));
278+
round_down &= close;
279+
round_up &= close;
280+
281+
uv = mix(uv, uvi - vec2(PS_ROUND_UV_THRESHOLD), bvec2(round_down));
282+
uv = mix(uv, uvi + vec2(PS_ROUND_UV_THRESHOLD), bvec2(round_up));
283+
284+
return vec4(uv / 16.0f / WH.xy, uv); // Return normalized and unnormalized coords.
285+
#else
286+
return vec4(0.0f);
287+
#endif
288+
}
289+
255290
mat4 sample_4c(vec4 uv)
256291
{
257292
mat4 c;
@@ -661,6 +696,10 @@ vec4 ps_color()
661696
#if (PS_FST == 0)
662697
vec2 st = PSin.t_float.xy / vec2(PSin.t_float.w);
663698
vec2 st_int = PSin.t_int.zw / vec2(PSin.t_float.w);
699+
#elif PS_ROUND_UV != 0
700+
vec4 t_int_rounded = round_uv();
701+
vec2 st = t_int_rounded.xy;
702+
vec2 st_int = t_int_rounded.zw;
664703
#else
665704
// Note xy are normalized coordinate
666705
vec2 st = PSin.t_int.xy;

bin/resources/shaders/opengl/tfx_vgs.glsl

Lines changed: 46 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -27,10 +27,29 @@ out SHADER
2727
#else
2828
flat vec4 c;
2929
#endif
30+
#if VS_ROUND_UV != 0
31+
flat uvec4 rounduv;
32+
#endif
3033
} VSout;
3134

3235
const float exp_min32 = exp2(-32.0f);
3336

37+
vec2 sign_extend_16_bit(vec2 uv)
38+
{
39+
return mix(uv, uv - vec2(0x10000), greaterThan(uv, vec2(0x7FFF)));
40+
}
41+
42+
uvec4 extract_round_uv_bits(float q)
43+
{
44+
uint qi = floatBitsToUint(q);
45+
return uvec4(
46+
(qi >> 0) & 0xFFF, // Prim left
47+
(qi >> 12) & 0xFFF, // Prim top
48+
(qi >> 24) & 0xF, // Round U flags
49+
(qi >> 28) & 0xF // Round V flags
50+
);
51+
}
52+
3453
#if VS_EXPAND == 0
3554

3655
layout(location = 0) in vec2 i_st;
@@ -43,7 +62,11 @@ layout(location = 7) in vec4 i_f;
4362

4463
void texture_coord()
4564
{
46-
vec2 uv = vec2(i_uv) - TextureOffset;
65+
#if VS_ROUND_UV == 0
66+
vec2 uv = vec2(i_uv) - TextureOffset;
67+
#else
68+
vec2 uv = sign_extend_16_bit(vec2(i_uv)) - TextureOffset; // Extend sign bit in case ST was converted to UV.
69+
#endif
4770
vec2 st = i_st - TextureOffset;
4871

4972
// Float coordinate
@@ -59,6 +82,11 @@ void texture_coord()
5982
// Some games uses float coordinate for post-processing effect
6083
VSout.t_int.zw = st / TextureScale;
6184
#endif
85+
86+
// Get UV rounding info saved in Q.
87+
#if VS_ROUND_UV
88+
VSout.rounduv = extract_round_uv_bits(i_q);
89+
#endif
6290
}
6391

6492
void vs_main()
@@ -78,7 +106,7 @@ void vs_main()
78106
texture_coord();
79107

80108
VSout.c = i_c;
81-
VSout.t_float.z = i_f.x; // pack for with texture
109+
VSout.t_float.z = i_f.x; // pack fog with texture
82110

83111
#if VS_POINT_SIZE
84112
gl_PointSize = PointSize.x;
@@ -108,6 +136,9 @@ struct ProcessedVertex
108136
vec4 t_float;
109137
vec4 t_int;
110138
vec4 c;
139+
#if VS_ROUND_UV
140+
uvec4 rounduv;
141+
#endif
111142
};
112143

113144
ProcessedVertex load_vertex(uint index)
@@ -131,7 +162,11 @@ ProcessedVertex load_vertex(uint index)
131162
vtx.p.z = float(z) * exp_min32;
132163
vtx.p.w = 1.0f;
133164

134-
vec2 uv = vec2(i_uv) - TextureOffset;
165+
#if VS_ROUND_UV == 0
166+
vec2 uv = vec2(i_uv) - TextureOffset;
167+
#else
168+
vec2 uv = sign_extend_16_bit(vec2(i_uv)) - TextureOffset; // Extend sign bit in case ST was converted to UV.
169+
#endif
135170
vec2 st = i_st - TextureOffset;
136171

137172
vtx.t_float.xy = st;
@@ -144,6 +179,11 @@ ProcessedVertex load_vertex(uint index)
144179
vtx.t_int.zw = st / TextureScale;
145180
#endif
146181

182+
// Get UV rounding info saved in Q.
183+
#if VS_ROUND_UV
184+
vtx.rounduv = extract_round_uv_bits(i_q);
185+
#endif
186+
147187
vtx.c = i_c;
148188
vtx.t_float.z = i_f.x;
149189

@@ -210,6 +250,9 @@ void main()
210250
VSout.t_float = vtx.t_float;
211251
VSout.t_int = vtx.t_int;
212252
VSout.c = vtx.c;
253+
#if VS_ROUND_UV
254+
VSout.rounduv = vtx.rounduv;
255+
#endif
213256
}
214257

215258
#endif // VS_EXPAND

0 commit comments

Comments
 (0)