Skip to content

Commit baea6f6

Browse files
committed
GS: Accurate UV rounding for axis-aligned prims.
1 parent 91b5102 commit baea6f6

29 files changed

+851
-23
lines changed

bin/resources/shaders/dx11/tfx.fx

Lines changed: 57 additions & 0 deletions
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
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
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,35 @@ 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+
// Top-left X, Y of the prim saved in unused texture coords.
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+
int2 close = int2(abs(uv - uvi) < PS_ROUND_UV_THRESHOLD);
352+
353+
// Round only if close to a half texel.
354+
uv = bool2(close & round_down) ? uvi - PS_ROUND_UV_THRESHOLD : uv;
355+
uv = bool2(close & round_up) ? uvi + PS_ROUND_UV_THRESHOLD : uv;
356+
357+
return float4(uv / 16.0f / WH.xy, uv); // Return normalized and unnormalized coords.
358+
#else
359+
return float4(0.0f, 0.0f, 0.0f, 0.0f);
360+
#endif
361+
}
362+
325363
float4x4 sample_4c(float4 uv, float uv_w, int2 xy)
326364
{
327365
float4x4 c;
@@ -756,6 +794,10 @@ float4 ps_color(PS_INPUT input)
756794
#if PS_FST == 0
757795
float2 st = input.t.xy / input.t.w;
758796
float2 st_int = input.ti.zw / input.t.w;
797+
#elif PS_ROUND_UV != 0
798+
float4 ti_rounded = round_uv(input);
799+
float2 st = ti_rounded.xy;
800+
float2 st_int = ti_rounded.zw;
759801
#else
760802
float2 st = input.ti.xy;
761803
float2 st_int = input.ti.zw;
@@ -1253,6 +1295,17 @@ cbuffer cb0
12531295
uint BaseVertex; // Only used in DX11.
12541296
};
12551297

1298+
uint4 extract_round_uv_bits(float q)
1299+
{
1300+
uint qi = asuint(q);
1301+
return uint4(
1302+
(qi >> 0) & 0xFFF, // Prim left
1303+
(qi >> 12) & 0xFFF, // Prim top
1304+
(qi >> 24) & 0xF, // Round U flags
1305+
(qi >> 28) & 0xF // Round V flags
1306+
);
1307+
}
1308+
12561309
VS_OUTPUT vs_main(VS_INPUT input)
12571310
{
12581311
// Clamp to max depth, gs doesn't wrap
@@ -1302,6 +1355,10 @@ VS_OUTPUT vs_main(VS_INPUT input)
13021355
output.c = input.c;
13031356
output.t.z = input.f.r;
13041357

1358+
#if VS_ROUND_UV
1359+
output.rounduv = extract_round_uv_bits(input.q);
1360+
#endif
1361+
13051362
return output;
13061363
}
13071364

bin/resources/shaders/opengl/tfx_fs.glsl

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,9 @@ in SHADER
7070
#else
7171
flat vec4 c;
7272
#endif
73+
#if PS_ROUND_UV
74+
flat uvec4 rounduv;
75+
#endif
7376
} PSin;
7477

7578
#define TARGET_0_QUALIFIER out
@@ -252,6 +255,35 @@ vec4 clamp_wrap_uv(vec4 uv)
252255
return uv_out;
253256
}
254257

258+
vec4 round_uv()
259+
{
260+
#if PS_ROUND_UV
261+
// Top-left X, Y of the prim saved in unused texture coords.
262+
ivec2 topleft = ivec2(equal(ivec2(gl_FragCoord.xy), ivec2(PSin.rounduv.xy)));
263+
264+
// Get flags for whether to round U, V.
265+
ivec2 round_flags = ivec2(PSin.rounduv.zw);
266+
267+
// Being on the top or left pixels converts round down to round up.
268+
ivec2 round_down = ivec2(equal(round_flags, ivec2(PS_ROUND_UV_DOWN))) & ~topleft;
269+
ivec2 round_up = ivec2(equal(round_flags, ivec2(PS_ROUND_UV_UP))) |
270+
(ivec2(equal(round_flags, ivec2(PS_ROUND_UV_DOWN))) & topleft);
271+
272+
vec2 uv = PSin.t_int.zw; // Unnormalized UVs.
273+
vec2 uvi = round(PSin.t_int.zw / 8.0f) * 8.0f; // Nearest half texel.
274+
275+
ivec2 close = ivec2(lessThan(abs(uv - uvi), vec2(PS_ROUND_UV_THRESHOLD)));
276+
277+
// Round only if close to a half texel.
278+
uv = mix(uv, uvi - PS_ROUND_UV_THRESHOLD, bvec2(close & round_down));
279+
uv = mix(uv, uvi + PS_ROUND_UV_THRESHOLD, bvec2(close & round_up));
280+
281+
return vec4(uv / 16.0f / WH.xy, uv); // Return normalized and unnormalized coords.
282+
#else
283+
return vec4(0.0f);
284+
#endif
285+
}
286+
255287
mat4 sample_4c(vec4 uv)
256288
{
257289
mat4 c;
@@ -661,6 +693,10 @@ vec4 ps_color()
661693
#if (PS_FST == 0)
662694
vec2 st = PSin.t_float.xy / vec2(PSin.t_float.w);
663695
vec2 st_int = PSin.t_int.zw / vec2(PSin.t_float.w);
696+
#elif PS_ROUND_UV != 0
697+
vec4 t_int_rounded = round_uv();
698+
vec2 st = t_int_rounded.xy;
699+
vec2 st_int = t_int_rounded.zw;
664700
#else
665701
// Note xy are normalized coordinate
666702
vec2 st = PSin.t_int.xy;

bin/resources/shaders/opengl/tfx_vgs.glsl

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

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

37+
uvec4 extract_round_uv_bits(float q)
38+
{
39+
uint qi = floatBitsToUint(q);
40+
return uvec4(
41+
(qi >> 0) & 0xFFF, // Prim left
42+
(qi >> 12) & 0xFFF, // Prim top
43+
(qi >> 24) & 0xF, // Round U flags
44+
(qi >> 28) & 0xF // Round V flags
45+
);
46+
}
47+
3448
#if VS_EXPAND == 0
3549

3650
layout(location = 0) in vec2 i_st;
@@ -78,11 +92,15 @@ void vs_main()
7892
texture_coord();
7993

8094
VSout.c = i_c;
81-
VSout.t_float.z = i_f.x; // pack for with texture
95+
VSout.t_float.z = i_f.x; // pack fog with texture
8296

8397
#if VS_POINT_SIZE
8498
gl_PointSize = PointSize.x;
8599
#endif
100+
101+
#if VS_ROUND_UV
102+
VSout.rounduv = extract_round_uv_bits(i_q);
103+
#endif
86104
}
87105

88106
#else // VS_EXPAND
@@ -108,6 +126,9 @@ struct ProcessedVertex
108126
vec4 t_float;
109127
vec4 t_int;
110128
vec4 c;
129+
#if VS_ROUND_UV
130+
uvec4 rounduv;
131+
#endif
111132
};
112133

113134
ProcessedVertex load_vertex(uint index)
@@ -147,6 +168,10 @@ ProcessedVertex load_vertex(uint index)
147168
vtx.c = i_c;
148169
vtx.t_float.z = i_f.x;
149170

171+
#if VS_ROUND_UV
172+
vtx.rounduv = extract_round_uv_bits(i_q);
173+
#endif
174+
150175
return vtx;
151176
}
152177

@@ -210,6 +235,9 @@ void main()
210235
VSout.t_float = vtx.t_float;
211236
VSout.t_int = vtx.t_int;
212237
VSout.c = vtx.c;
238+
#if VS_ROUND_UV
239+
VSout.rounduv = vtx.rounduv;
240+
#endif
213241
}
214242

215243
#endif // VS_EXPAND

bin/resources/shaders/vulkan/tfx.glsl

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,8 +28,22 @@ layout(location = 0) out VSOutput
2828
#else
2929
flat vec4 c;
3030
#endif
31+
#if VS_ROUND_UV
32+
flat uvec4 rounduv;
33+
#endif
3134
} vsOut;
3235

36+
uvec4 extract_round_uv_bits(float q)
37+
{
38+
uint qi = floatBitsToUint(q);
39+
return uvec4(
40+
(qi >> 0) & 0xFFF, // Prim left
41+
(qi >> 12) & 0xFFF, // Prim top
42+
(qi >> 24) & 0xF, // Round U flags
43+
(qi >> 28) & 0xF // Round V flags
44+
);
45+
}
46+
3347
#if VS_EXPAND == 0
3448

3549
layout(location = 0) in vec2 a_st;
@@ -84,6 +98,10 @@ void main()
8498

8599
vsOut.c = vec4(a_c);
86100
vsOut.t.z = a_f.r;
101+
102+
#if VS_ROUND_UV
103+
vsOut.rounduv = extract_round_uv_bits(a_q);
104+
#endif
87105
}
88106

89107
#else // VS_EXPAND
@@ -109,6 +127,9 @@ struct ProcessedVertex
109127
vec4 t;
110128
vec4 ti;
111129
vec4 c;
130+
#if VS_ROUND_UV
131+
uvec4 rounduv;
132+
#endif
112133
};
113134

114135
ProcessedVertex load_vertex(uint index)
@@ -153,6 +174,10 @@ ProcessedVertex load_vertex(uint index)
153174
vtx.c = a_c;
154175
vtx.t.z = a_f.r;
155176

177+
#if VS_ROUND_UV
178+
vtx.rounduv = extract_round_uv_bits(a_q);
179+
#endif
180+
156181
return vtx;
157182
}
158183

@@ -217,6 +242,9 @@ void main()
217242
vsOut.t = vtx.t;
218243
vsOut.ti = vtx.ti;
219244
vsOut.c = vtx.c;
245+
#if VS_ROUND_UV
246+
vsOut.rounduv = vtx.rounduv;
247+
#endif
220248
}
221249

222250
#endif // VS_EXPAND
@@ -291,6 +319,7 @@ void main()
291319
#define PS_ZFLOOR 0
292320
#define PS_FEEDBACK_LOOP 0
293321
#define PS_TEX_IS_FB 0
322+
#define PS_ROUND_UV 0
294323
#endif
295324

296325
#define SW_BLEND (PS_BLEND_A || PS_BLEND_B || PS_BLEND_D)
@@ -333,6 +362,9 @@ layout(location = 0) in VSOutput
333362
#else
334363
flat vec4 c;
335364
#endif
365+
#if PS_ROUND_UV
366+
flat uvec4 rounduv;
367+
#endif
336368
} vsIn;
337369

338370
#if !PS_NO_COLOR && !PS_NO_COLOR1
@@ -503,6 +535,35 @@ vec4 clamp_wrap_uv(vec4 uv)
503535
return uv;
504536
}
505537

538+
vec4 round_uv()
539+
{
540+
#if PS_ROUND_UV
541+
// Whether we are at the top or left of the prim.
542+
ivec2 topleft = ivec2(equal(ivec2(gl_FragCoord.xy), ivec2(vsIn.rounduv.xy)));
543+
544+
// Extract flags for whether to round U, V.
545+
ivec2 round_flags = ivec2(vsIn.rounduv.zw);
546+
547+
// Being on the top or left pixels converts round down to round up.
548+
ivec2 round_down = ivec2(equal(round_flags, ivec2(PS_ROUND_UV_DOWN))) & ~topleft;
549+
ivec2 round_up = ivec2(equal(round_flags, ivec2(PS_ROUND_UV_UP))) |
550+
(ivec2(equal(round_flags, ivec2(PS_ROUND_UV_DOWN))) & topleft);
551+
552+
vec2 uv = vsIn.ti.zw; // Unnormalized UVs.
553+
vec2 uvi = round(vsIn.ti.zw / 8.0f) * 8.0f; // Nearest half texel.
554+
555+
ivec2 close = ivec2(lessThan(abs(uv - uvi), vec2(PS_ROUND_UV_THRESHOLD)));
556+
557+
// Round only if close to a half texel.
558+
uv = mix(uv, uvi - PS_ROUND_UV_THRESHOLD, bvec2(close & round_down));
559+
uv = mix(uv, uvi + PS_ROUND_UV_THRESHOLD, bvec2(close & round_up));
560+
561+
return vec4(uv / 16.0f / WH.xy, uv); // Return normalized and unnormalized coords.
562+
#else
563+
return vec4(0.0f);
564+
#endif
565+
}
566+
506567
mat4 sample_4c(vec4 uv)
507568
{
508569
mat4 c;
@@ -923,6 +984,10 @@ vec4 ps_color()
923984
#if PS_FST == 0
924985
vec2 st = vsIn.t.xy / vsIn.t.w;
925986
vec2 st_int = vsIn.ti.zw / vsIn.t.w;
987+
#elif PS_ROUND_UV != 0
988+
vec4 ti_rounded = round_uv();
989+
vec2 st = ti_rounded.xy;
990+
vec2 st_int = ti_rounded.zw;
926991
#else
927992
vec2 st = vsIn.ti.xy;
928993
vec2 st_int = vsIn.ti.zw;

pcsx2/Config.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -764,6 +764,7 @@ struct Pcsx2Config
764764
PreloadFrameWithGSData : 1,
765765
Mipmap : 1,
766766
HWMipmap : 1,
767+
AccurateUVRounding : 1,
767768
ManualUserHacks : 1,
768769
UserHacks_AlignSpriteX : 1,
769770
UserHacks_CPUFBConversion : 1,

0 commit comments

Comments
 (0)