1 // Copyright 2008 Dolphin Emulator Project
2 // Licensed under GPLv2+
3 // Refer to the license.txt file included.
4
5 #include "VideoCommon/VertexShaderGen.h"
6
7 #include "Common/Assert.h"
8 #include "Common/CommonTypes.h"
9 #include "VideoCommon/BPMemory.h"
10 #include "VideoCommon/LightingShaderGen.h"
11 #include "VideoCommon/NativeVertexFormat.h"
12 #include "VideoCommon/VertexLoaderManager.h"
13 #include "VideoCommon/VideoCommon.h"
14 #include "VideoCommon/VideoConfig.h"
15 #include "VideoCommon/XFMemory.h"
16
GetVertexShaderUid()17 VertexShaderUid GetVertexShaderUid()
18 {
19 ASSERT(bpmem.genMode.numtexgens == xfmem.numTexGen.numTexGens);
20 ASSERT(bpmem.genMode.numcolchans == xfmem.numChan.numColorChans);
21
22 VertexShaderUid out;
23 vertex_shader_uid_data* const uid_data = out.GetUidData();
24 uid_data->numTexGens = xfmem.numTexGen.numTexGens;
25 uid_data->components = VertexLoaderManager::g_current_components;
26 uid_data->numColorChans = xfmem.numChan.numColorChans;
27
28 GetLightingShaderUid(uid_data->lighting);
29
30 // transform texcoords
31 for (unsigned int i = 0; i < uid_data->numTexGens; ++i)
32 {
33 auto& texinfo = uid_data->texMtxInfo[i];
34
35 texinfo.sourcerow = xfmem.texMtxInfo[i].sourcerow;
36 texinfo.texgentype = xfmem.texMtxInfo[i].texgentype;
37 texinfo.inputform = xfmem.texMtxInfo[i].inputform;
38
39 // first transformation
40 switch (texinfo.texgentype)
41 {
42 case XF_TEXGEN_EMBOSS_MAP: // calculate tex coords into bump map
43 if (uid_data->components & (VB_HAS_NRM1 | VB_HAS_NRM2))
44 {
45 // transform the light dir into tangent space
46 texinfo.embosslightshift = xfmem.texMtxInfo[i].embosslightshift;
47 texinfo.embosssourceshift = xfmem.texMtxInfo[i].embosssourceshift;
48 }
49 else
50 {
51 texinfo.embosssourceshift = xfmem.texMtxInfo[i].embosssourceshift;
52 }
53 break;
54 case XF_TEXGEN_COLOR_STRGBC0:
55 case XF_TEXGEN_COLOR_STRGBC1:
56 break;
57 case XF_TEXGEN_REGULAR:
58 default:
59 uid_data->texMtxInfo_n_projection |= xfmem.texMtxInfo[i].projection << i;
60 break;
61 }
62
63 uid_data->dualTexTrans_enabled = xfmem.dualTexTrans.enabled;
64 // CHECKME: does this only work for regular tex gen types?
65 if (uid_data->dualTexTrans_enabled && texinfo.texgentype == XF_TEXGEN_REGULAR)
66 {
67 auto& postInfo = uid_data->postMtxInfo[i];
68 postInfo.index = xfmem.postMtxInfo[i].index;
69 postInfo.normalize = xfmem.postMtxInfo[i].normalize;
70 }
71 }
72
73 return out;
74 }
75
GenerateVertexShaderCode(APIType api_type,const ShaderHostConfig & host_config,const vertex_shader_uid_data * uid_data)76 ShaderCode GenerateVertexShaderCode(APIType api_type, const ShaderHostConfig& host_config,
77 const vertex_shader_uid_data* uid_data)
78 {
79 ShaderCode out;
80
81 const bool per_pixel_lighting = g_ActiveConfig.bEnablePixelLighting;
82 const bool msaa = host_config.msaa;
83 const bool ssaa = host_config.ssaa;
84 const bool vertex_rounding = host_config.vertex_rounding;
85
86 out.Write("%s", s_lighting_struct);
87
88 // uniforms
89 if (api_type == APIType::OpenGL || api_type == APIType::Vulkan)
90 out.Write("UBO_BINDING(std140, 2) uniform VSBlock {\n");
91 else
92 out.Write("cbuffer VSBlock {\n");
93
94 out.Write(s_shader_uniforms);
95 out.Write("};\n");
96
97 out.Write("struct VS_OUTPUT {\n");
98 GenerateVSOutputMembers(out, api_type, uid_data->numTexGens, host_config, "");
99 out.Write("};\n");
100
101 if (api_type == APIType::OpenGL || api_type == APIType::Vulkan)
102 {
103 out.Write("ATTRIBUTE_LOCATION(%d) in float4 rawpos;\n", SHADER_POSITION_ATTRIB);
104 if (uid_data->components & VB_HAS_POSMTXIDX)
105 out.Write("ATTRIBUTE_LOCATION(%d) in uint4 posmtx;\n", SHADER_POSMTX_ATTRIB);
106 if (uid_data->components & VB_HAS_NRM0)
107 out.Write("ATTRIBUTE_LOCATION(%d) in float3 rawnorm0;\n", SHADER_NORM0_ATTRIB);
108 if (uid_data->components & VB_HAS_NRM1)
109 out.Write("ATTRIBUTE_LOCATION(%d) in float3 rawnorm1;\n", SHADER_NORM1_ATTRIB);
110 if (uid_data->components & VB_HAS_NRM2)
111 out.Write("ATTRIBUTE_LOCATION(%d) in float3 rawnorm2;\n", SHADER_NORM2_ATTRIB);
112
113 if (uid_data->components & VB_HAS_COL0)
114 out.Write("ATTRIBUTE_LOCATION(%d) in float4 rawcolor0;\n", SHADER_COLOR0_ATTRIB);
115 if (uid_data->components & VB_HAS_COL1)
116 out.Write("ATTRIBUTE_LOCATION(%d) in float4 rawcolor1;\n", SHADER_COLOR1_ATTRIB);
117
118 for (int i = 0; i < 8; ++i)
119 {
120 u32 hastexmtx = (uid_data->components & (VB_HAS_TEXMTXIDX0 << i));
121 if ((uid_data->components & (VB_HAS_UV0 << i)) || hastexmtx)
122 {
123 out.Write("ATTRIBUTE_LOCATION(%d) in float%d rawtex%d;\n", SHADER_TEXTURE0_ATTRIB + i,
124 hastexmtx ? 3 : 2, i);
125 }
126 }
127
128 if (host_config.backend_geometry_shaders)
129 {
130 out.Write("VARYING_LOCATION(0) out VertexData {\n");
131 GenerateVSOutputMembers(out, api_type, uid_data->numTexGens, host_config,
132 GetInterpolationQualifier(msaa, ssaa, true, false));
133 out.Write("} vs;\n");
134 }
135 else
136 {
137 // Let's set up attributes
138 u32 counter = 0;
139 out.Write("VARYING_LOCATION(%u) %s out float4 colors_0;\n", counter++,
140 GetInterpolationQualifier(msaa, ssaa));
141 out.Write("VARYING_LOCATION(%u) %s out float4 colors_1;\n", counter++,
142 GetInterpolationQualifier(msaa, ssaa));
143 for (u32 i = 0; i < uid_data->numTexGens; ++i)
144 {
145 out.Write("VARYING_LOCATION(%u) %s out float3 tex%u;\n", counter++,
146 GetInterpolationQualifier(msaa, ssaa), i);
147 }
148 if (!host_config.fast_depth_calc)
149 out.Write("VARYING_LOCATION(%u) %s out float4 clipPos;\n", counter++,
150 GetInterpolationQualifier(msaa, ssaa));
151 if (per_pixel_lighting)
152 {
153 out.Write("VARYING_LOCATION(%u) %s out float3 Normal;\n", counter++,
154 GetInterpolationQualifier(msaa, ssaa));
155 out.Write("VARYING_LOCATION(%u) %s out float3 WorldPos;\n", counter++,
156 GetInterpolationQualifier(msaa, ssaa));
157 }
158 }
159
160 out.Write("void main()\n{\n");
161 }
162 else // D3D
163 {
164 out.Write("VS_OUTPUT main(\n");
165
166 // inputs
167 if (uid_data->components & VB_HAS_NRM0)
168 out.Write(" float3 rawnorm0 : NORMAL0,\n");
169 if (uid_data->components & VB_HAS_NRM1)
170 out.Write(" float3 rawnorm1 : NORMAL1,\n");
171 if (uid_data->components & VB_HAS_NRM2)
172 out.Write(" float3 rawnorm2 : NORMAL2,\n");
173 if (uid_data->components & VB_HAS_COL0)
174 out.Write(" float4 rawcolor0 : COLOR0,\n");
175 if (uid_data->components & VB_HAS_COL1)
176 out.Write(" float4 rawcolor1 : COLOR1,\n");
177 for (int i = 0; i < 8; ++i)
178 {
179 u32 hastexmtx = (uid_data->components & (VB_HAS_TEXMTXIDX0 << i));
180 if ((uid_data->components & (VB_HAS_UV0 << i)) || hastexmtx)
181 out.Write(" float%d rawtex%d : TEXCOORD%d,\n", hastexmtx ? 3 : 2, i, i);
182 }
183 if (uid_data->components & VB_HAS_POSMTXIDX)
184 out.Write(" uint4 posmtx : BLENDINDICES,\n");
185 out.Write(" float4 rawpos : POSITION) {\n");
186 }
187
188 out.Write("VS_OUTPUT o;\n");
189
190 // transforms
191 if (uid_data->components & VB_HAS_POSMTXIDX)
192 {
193 out.Write("int posidx = int(posmtx.r);\n");
194 out.Write("float4 pos = float4(dot(" I_TRANSFORMMATRICES
195 "[posidx], rawpos), dot(" I_TRANSFORMMATRICES
196 "[posidx+1], rawpos), dot(" I_TRANSFORMMATRICES "[posidx+2], rawpos), 1);\n");
197
198 if (uid_data->components & VB_HAS_NRMALL)
199 {
200 out.Write("int normidx = posidx & 31;\n");
201 out.Write("float3 N0 = " I_NORMALMATRICES "[normidx].xyz, N1 = " I_NORMALMATRICES
202 "[normidx+1].xyz, N2 = " I_NORMALMATRICES "[normidx+2].xyz;\n");
203 }
204
205 if (uid_data->components & VB_HAS_NRM0)
206 out.Write("float3 _norm0 = normalize(float3(dot(N0, rawnorm0), dot(N1, rawnorm0), dot(N2, "
207 "rawnorm0)));\n");
208 if (uid_data->components & VB_HAS_NRM1)
209 out.Write(
210 "float3 _norm1 = float3(dot(N0, rawnorm1), dot(N1, rawnorm1), dot(N2, rawnorm1));\n");
211 if (uid_data->components & VB_HAS_NRM2)
212 out.Write(
213 "float3 _norm2 = float3(dot(N0, rawnorm2), dot(N1, rawnorm2), dot(N2, rawnorm2));\n");
214 }
215 else
216 {
217 out.Write("float4 pos = float4(dot(" I_POSNORMALMATRIX "[0], rawpos), dot(" I_POSNORMALMATRIX
218 "[1], rawpos), dot(" I_POSNORMALMATRIX "[2], rawpos), 1.0);\n");
219 if (uid_data->components & VB_HAS_NRM0)
220 out.Write("float3 _norm0 = normalize(float3(dot(" I_POSNORMALMATRIX
221 "[3].xyz, rawnorm0), dot(" I_POSNORMALMATRIX
222 "[4].xyz, rawnorm0), dot(" I_POSNORMALMATRIX "[5].xyz, rawnorm0)));\n");
223 if (uid_data->components & VB_HAS_NRM1)
224 out.Write("float3 _norm1 = float3(dot(" I_POSNORMALMATRIX
225 "[3].xyz, rawnorm1), dot(" I_POSNORMALMATRIX
226 "[4].xyz, rawnorm1), dot(" I_POSNORMALMATRIX "[5].xyz, rawnorm1));\n");
227 if (uid_data->components & VB_HAS_NRM2)
228 out.Write("float3 _norm2 = float3(dot(" I_POSNORMALMATRIX
229 "[3].xyz, rawnorm2), dot(" I_POSNORMALMATRIX
230 "[4].xyz, rawnorm2), dot(" I_POSNORMALMATRIX "[5].xyz, rawnorm2));\n");
231 }
232
233 if (!(uid_data->components & VB_HAS_NRM0))
234 out.Write("float3 _norm0 = float3(0.0, 0.0, 0.0);\n");
235
236 out.Write("o.pos = float4(dot(" I_PROJECTION "[0], pos), dot(" I_PROJECTION
237 "[1], pos), dot(" I_PROJECTION "[2], pos), dot(" I_PROJECTION "[3], pos));\n");
238
239 out.Write("int4 lacc;\n"
240 "float3 ldir, h, cosAttn, distAttn;\n"
241 "float dist, dist2, attn;\n");
242
243 GenerateLightingShaderCode(out, uid_data->lighting, uid_data->components, "rawcolor",
244 "o.colors_");
245
246 // transform texcoords
247 out.Write("float4 coord = float4(0.0, 0.0, 1.0, 1.0);\n");
248 for (unsigned int i = 0; i < uid_data->numTexGens; ++i)
249 {
250 auto& texinfo = uid_data->texMtxInfo[i];
251
252 out.Write("{\n");
253 out.Write("coord = float4(0.0, 0.0, 1.0, 1.0);\n");
254 switch (texinfo.sourcerow)
255 {
256 case XF_SRCGEOM_INROW:
257 out.Write("coord.xyz = rawpos.xyz;\n");
258 break;
259 case XF_SRCNORMAL_INROW:
260 if (uid_data->components & VB_HAS_NRM0)
261 {
262 out.Write("coord.xyz = rawnorm0.xyz;\n");
263 }
264 break;
265 case XF_SRCCOLORS_INROW:
266 ASSERT(texinfo.texgentype == XF_TEXGEN_COLOR_STRGBC0 ||
267 texinfo.texgentype == XF_TEXGEN_COLOR_STRGBC1);
268 break;
269 case XF_SRCBINORMAL_T_INROW:
270 if (uid_data->components & VB_HAS_NRM1)
271 {
272 out.Write("coord.xyz = rawnorm1.xyz;\n");
273 }
274 break;
275 case XF_SRCBINORMAL_B_INROW:
276 if (uid_data->components & VB_HAS_NRM2)
277 {
278 out.Write("coord.xyz = rawnorm2.xyz;\n");
279 }
280 break;
281 default:
282 ASSERT(texinfo.sourcerow <= XF_SRCTEX7_INROW);
283 if (uid_data->components & (VB_HAS_UV0 << (texinfo.sourcerow - XF_SRCTEX0_INROW)))
284 out.Write("coord = float4(rawtex%d.x, rawtex%d.y, 1.0, 1.0);\n",
285 texinfo.sourcerow - XF_SRCTEX0_INROW, texinfo.sourcerow - XF_SRCTEX0_INROW);
286 break;
287 }
288 // Input form of AB11 sets z element to 1.0
289
290 if (texinfo.inputform == XF_TEXINPUT_AB11)
291 out.Write("coord.z = 1.0;\n");
292
293 // first transformation
294 switch (texinfo.texgentype)
295 {
296 case XF_TEXGEN_EMBOSS_MAP: // calculate tex coords into bump map
297
298 if (uid_data->components & (VB_HAS_NRM1 | VB_HAS_NRM2))
299 {
300 // transform the light dir into tangent space
301 out.Write("ldir = normalize(" LIGHT_POS ".xyz - pos.xyz);\n",
302 LIGHT_POS_PARAMS(texinfo.embosslightshift));
303 out.Write(
304 "o.tex%d.xyz = o.tex%d.xyz + float3(dot(ldir, _norm1), dot(ldir, _norm2), 0.0);\n", i,
305 texinfo.embosssourceshift);
306 }
307 else
308 {
309 // The following assert was triggered in House of the Dead Overkill and Star Wars Rogue
310 // Squadron 2
311 // ASSERT(0); // should have normals
312 out.Write("o.tex%d.xyz = o.tex%d.xyz;\n", i, texinfo.embosssourceshift);
313 }
314
315 break;
316 case XF_TEXGEN_COLOR_STRGBC0:
317 out.Write("o.tex%d.xyz = float3(o.colors_0.x, o.colors_0.y, 1);\n", i);
318 break;
319 case XF_TEXGEN_COLOR_STRGBC1:
320 out.Write("o.tex%d.xyz = float3(o.colors_1.x, o.colors_1.y, 1);\n", i);
321 break;
322 case XF_TEXGEN_REGULAR:
323 default:
324 if (uid_data->components & (VB_HAS_TEXMTXIDX0 << i))
325 {
326 out.Write("int tmp = int(rawtex%d.z);\n", i);
327 if (((uid_data->texMtxInfo_n_projection >> i) & 1) == XF_TEXPROJ_STQ)
328 out.Write("o.tex%d.xyz = float3(dot(coord, " I_TRANSFORMMATRICES
329 "[tmp]), dot(coord, " I_TRANSFORMMATRICES
330 "[tmp+1]), dot(coord, " I_TRANSFORMMATRICES "[tmp+2]));\n",
331 i);
332 else
333 out.Write("o.tex%d.xyz = float3(dot(coord, " I_TRANSFORMMATRICES
334 "[tmp]), dot(coord, " I_TRANSFORMMATRICES "[tmp+1]), 1);\n",
335 i);
336 }
337 else
338 {
339 if (((uid_data->texMtxInfo_n_projection >> i) & 1) == XF_TEXPROJ_STQ)
340 out.Write("o.tex%d.xyz = float3(dot(coord, " I_TEXMATRICES
341 "[%d]), dot(coord, " I_TEXMATRICES "[%d]), dot(coord, " I_TEXMATRICES
342 "[%d]));\n",
343 i, 3 * i, 3 * i + 1, 3 * i + 2);
344 else
345 out.Write("o.tex%d.xyz = float3(dot(coord, " I_TEXMATRICES
346 "[%d]), dot(coord, " I_TEXMATRICES "[%d]), 1);\n",
347 i, 3 * i, 3 * i + 1);
348 }
349 break;
350 }
351
352 // CHECKME: does this only work for regular tex gen types?
353 if (uid_data->dualTexTrans_enabled && texinfo.texgentype == XF_TEXGEN_REGULAR)
354 {
355 auto& postInfo = uid_data->postMtxInfo[i];
356
357 out.Write("float4 P0 = " I_POSTTRANSFORMMATRICES "[%d];\n"
358 "float4 P1 = " I_POSTTRANSFORMMATRICES "[%d];\n"
359 "float4 P2 = " I_POSTTRANSFORMMATRICES "[%d];\n",
360 postInfo.index & 0x3f, (postInfo.index + 1) & 0x3f, (postInfo.index + 2) & 0x3f);
361
362 if (postInfo.normalize)
363 out.Write("o.tex%d.xyz = normalize(o.tex%d.xyz);\n", i, i);
364
365 // multiply by postmatrix
366 out.Write("o.tex%d.xyz = float3(dot(P0.xyz, o.tex%d.xyz) + P0.w, dot(P1.xyz, o.tex%d.xyz) + "
367 "P1.w, dot(P2.xyz, o.tex%d.xyz) + P2.w);\n",
368 i, i, i, i);
369 }
370
371 // When q is 0, the GameCube appears to have a special case
372 // This can be seen in devkitPro's neheGX Lesson08 example for Wii
373 // Makes differences in Rogue Squadron 3 (Hoth sky) and The Last Story (shadow culling)
374 // TODO: check if this only affects XF_TEXGEN_REGULAR
375 if (texinfo.texgentype == XF_TEXGEN_REGULAR)
376 {
377 out.Write("if(o.tex%d.z == 0.0f)\n", i);
378 out.Write(
379 "\to.tex%d.xy = clamp(o.tex%d.xy / 2.0f, float2(-1.0f,-1.0f), float2(1.0f,1.0f));\n", i,
380 i);
381 }
382
383 out.Write("}\n");
384 }
385
386 if (uid_data->numColorChans == 0)
387 {
388 if (uid_data->components & VB_HAS_COL0)
389 out.Write("o.colors_0 = rawcolor0;\n");
390 else
391 out.Write("o.colors_0 = float4(1.0, 1.0, 1.0, 1.0);\n");
392 }
393 if (uid_data->numColorChans < 2)
394 {
395 if (uid_data->components & VB_HAS_COL1)
396 out.Write("o.colors_1 = rawcolor1;\n");
397 else
398 out.Write("o.colors_1 = o.colors_0;\n");
399 }
400
401 // clipPos/w needs to be done in pixel shader, not here
402 if (!host_config.fast_depth_calc)
403 out.Write("o.clipPos = o.pos;\n");
404
405 if (per_pixel_lighting)
406 {
407 out.Write("o.Normal = _norm0;\n");
408 out.Write("o.WorldPos = pos.xyz;\n");
409
410 if (uid_data->components & VB_HAS_COL0)
411 out.Write("o.colors_0 = rawcolor0;\n");
412
413 if (uid_data->components & VB_HAS_COL1)
414 out.Write("o.colors_1 = rawcolor1;\n");
415 }
416
417 // If we can disable the incorrect depth clipping planes using depth clamping, then we can do
418 // our own depth clipping and calculate the depth range before the perspective divide if
419 // necessary.
420 if (host_config.backend_depth_clamp)
421 {
422 // Since we're adjusting z for the depth range before the perspective divide, we have to do our
423 // own clipping. We want to clip so that -w <= z <= 0, which matches the console -1..0 range.
424 // We adjust our depth value for clipping purposes to match the perspective projection in the
425 // software backend, which is a hack to fix Sonic Adventure and Unleashed games.
426 out.Write("float clipDepth = o.pos.z * (1.0 - 1e-7);\n");
427 out.Write("float clipDist0 = clipDepth + o.pos.w;\n"); // Near: z < -w
428 out.Write("float clipDist1 = -clipDepth;\n"); // Far: z > 0
429 if (host_config.backend_geometry_shaders)
430 {
431 out.Write("o.clipDist0 = clipDist0;\n");
432 out.Write("o.clipDist1 = clipDist1;\n");
433 }
434 }
435
436 // Write the true depth value. If the game uses depth textures, then the pixel shader will
437 // override it with the correct values if not then early z culling will improve speed.
438 // There are two different ways to do this, when the depth range is oversized, we process
439 // the depth range in the vertex shader, if not we let the host driver handle it.
440 //
441 // Adjust z for the depth range. We're using an equation which incorperates a depth inversion,
442 // so we can map the console -1..0 range to the 0..1 range used in the depth buffer.
443 // We have to handle the depth range in the vertex shader instead of after the perspective
444 // divide, because some games will use a depth range larger than what is allowed by the
445 // graphics API. These large depth ranges will still be clipped to the 0..1 range, so these
446 // games effectively add a depth bias to the values written to the depth buffer.
447 out.Write("o.pos.z = o.pos.w * " I_PIXELCENTERCORRECTION ".w - "
448 "o.pos.z * " I_PIXELCENTERCORRECTION ".z;\n");
449
450 if (!host_config.backend_clip_control)
451 {
452 // If the graphics API doesn't support a depth range of 0..1, then we need to map z to
453 // the -1..1 range. Unfortunately we have to use a substraction, which is a lossy floating-point
454 // operation that can introduce a round-trip error.
455 out.Write("o.pos.z = o.pos.z * 2.0 - o.pos.w;\n");
456 }
457
458 // Correct for negative viewports by mirroring all vertices. We need to negate the height here,
459 // since the viewport height is already negated by the render backend.
460 out.Write("o.pos.xy *= sign(" I_PIXELCENTERCORRECTION ".xy * float2(1.0, -1.0));\n");
461
462 // The console GPU places the pixel center at 7/12 in screen space unless
463 // antialiasing is enabled, while D3D and OpenGL place it at 0.5. This results
464 // in some primitives being placed one pixel too far to the bottom-right,
465 // which in turn can be critical if it happens for clear quads.
466 // Hence, we compensate for this pixel center difference so that primitives
467 // get rasterized correctly.
468 out.Write("o.pos.xy = o.pos.xy - o.pos.w * " I_PIXELCENTERCORRECTION ".xy;\n");
469
470 if (vertex_rounding)
471 {
472 // By now our position is in clip space
473 // however, higher resolutions than the Wii outputs
474 // cause an additional pixel offset
475 // due to a higher pixel density
476 // we need to correct this by converting our
477 // clip-space position into the Wii's screen-space
478 // acquire the right pixel and then convert it back
479 out.Write("if (o.pos.w == 1.0f)\n");
480 out.Write("{\n");
481
482 out.Write("\tfloat ss_pixel_x = ((o.pos.x + 1.0f) * (" I_VIEWPORT_SIZE ".x * 0.5f));\n");
483 out.Write("\tfloat ss_pixel_y = ((o.pos.y + 1.0f) * (" I_VIEWPORT_SIZE ".y * 0.5f));\n");
484
485 out.Write("\tss_pixel_x = round(ss_pixel_x);\n");
486 out.Write("\tss_pixel_y = round(ss_pixel_y);\n");
487
488 out.Write("\to.pos.x = ((ss_pixel_x / (" I_VIEWPORT_SIZE ".x * 0.5f)) - 1.0f);\n");
489 out.Write("\to.pos.y = ((ss_pixel_y / (" I_VIEWPORT_SIZE ".y * 0.5f)) - 1.0f);\n");
490 out.Write("}\n");
491 }
492
493 if (api_type == APIType::OpenGL || api_type == APIType::Vulkan)
494 {
495 if (host_config.backend_geometry_shaders)
496 {
497 AssignVSOutputMembers(out, "vs", "o", uid_data->numTexGens, host_config);
498 }
499 else
500 {
501 // TODO: Pass interface blocks between shader stages even if geometry shaders
502 // are not supported, however that will require at least OpenGL 3.2 support.
503 for (unsigned int i = 0; i < uid_data->numTexGens; ++i)
504 out.Write("tex%d.xyz = o.tex%d;\n", i, i);
505 if (!host_config.fast_depth_calc)
506 out.Write("clipPos = o.clipPos;\n");
507 if (per_pixel_lighting)
508 {
509 out.Write("Normal = o.Normal;\n");
510 out.Write("WorldPos = o.WorldPos;\n");
511 }
512 out.Write("colors_0 = o.colors_0;\n");
513 out.Write("colors_1 = o.colors_1;\n");
514 }
515
516 if (host_config.backend_depth_clamp)
517 {
518 out.Write("gl_ClipDistance[0] = clipDist0;\n");
519 out.Write("gl_ClipDistance[1] = clipDist1;\n");
520 }
521
522 // Vulkan NDC space has Y pointing down (right-handed NDC space).
523 if (api_type == APIType::Vulkan)
524 out.Write("gl_Position = float4(o.pos.x, -o.pos.y, o.pos.z, o.pos.w);\n");
525 else
526 out.Write("gl_Position = o.pos;\n");
527 }
528 else // D3D
529 {
530 out.Write("return o;\n");
531 }
532 out.Write("}\n");
533
534 return out;
535 }
536