@@ -13,9 +13,18 @@
#include " VideoCommon/VideoConfig.h"
#include " VideoCommon/XFMemory.h"
constexpr std::array<const char *, 4 > primitives_ogl = {
{" points" , " lines" , " triangles" , " triangles" }};
constexpr std::array<const char *, 4 > primitives_d3d = {{" point" , " line" , " triangle" , " triangle" }};
constexpr std::array<const char *, 4 > primitives_ogl{
" points" ,
" lines" ,
" triangles" ,
" triangles" ,
};
constexpr std::array<const char *, 4 > primitives_d3d{
" point" ,
" line" ,
" triangle" ,
" triangle" ,
};
bool geometry_shader_uid_data::IsPassthrough () const
{
@@ -51,10 +60,10 @@ ShaderCode GenerateGeometryShaderCode(APIType ApiType, const ShaderHostConfig& h
const bool msaa = host_config.msaa ;
const bool ssaa = host_config.ssaa ;
const bool stereo = host_config.stereo ;
const PrimitiveType primitive_type = static_cast <PrimitiveType>(uid_data->primitive_type );
const unsigned primitive_type_index = static_cast <unsigned >(uid_data->primitive_type );
const unsigned vertex_in = std::min (static_cast <unsigned >(primitive_type_index) + 1 , 3u );
unsigned vertex_out = primitive_type == PrimitiveType::TriangleStrip ? 3 : 4 ;
const auto primitive_type = static_cast <PrimitiveType>(uid_data->primitive_type );
const auto primitive_type_index = static_cast <unsigned >(uid_data->primitive_type );
const auto vertex_in = std::min (static_cast <unsigned >(primitive_type_index) + 1 , 3u );
u32 vertex_out = primitive_type == PrimitiveType::TriangleStrip ? 3 : 4 ;
if (wireframe)
vertex_out++;
@@ -64,173 +73,175 @@ ShaderCode GenerateGeometryShaderCode(APIType ApiType, const ShaderHostConfig& h
// Insert layout parameters
if (host_config.backend_gs_instancing )
{
out.Write (" layout(%s , invocations = %d ) in;\n " , primitives_ogl[primitive_type_index],
stereo ? 2 : 1 );
out.Write (" layout(%s_strip , max_vertices = %d ) out;\n " , wireframe ? " line" : " triangle" ,
vertex_out);
out.WriteFmt (" layout({} , invocations = {} ) in;\n " , primitives_ogl[primitive_type_index],
stereo ? 2 : 1 );
out.WriteFmt (" layout({}_strip , max_vertices = {} ) out;\n " , wireframe ? " line" : " triangle" ,
vertex_out);
}
else
{
out.Write (" layout(%s ) in;\n " , primitives_ogl[primitive_type_index]);
out.Write (" layout(%s_strip , max_vertices = %d ) out;\n " , wireframe ? " line" : " triangle" ,
stereo ? vertex_out * 2 : vertex_out);
out.WriteFmt (" layout({} ) in;\n " , primitives_ogl[primitive_type_index]);
out.WriteFmt (" layout({}_strip , max_vertices = {} ) out;\n " , wireframe ? " line" : " triangle" ,
stereo ? vertex_out * 2 : vertex_out);
}
}
out.Write ( " %s " , s_lighting_struct);
out.WriteFmt ( " {} " , s_lighting_struct);
// uniforms
if (ApiType == APIType::OpenGL || ApiType == APIType::Vulkan)
out.Write (" UBO_BINDING(std140, 3) uniform GSBlock {\n " );
out.WriteFmt (" UBO_BINDING(std140, 3) uniform GSBlock { {\n " );
else
out.Write (" cbuffer GSBlock {\n " );
out.WriteFmt (" cbuffer GSBlock { {\n " );
out.Write (" \t float4 " I_STEREOPARAMS " ;\n "
" \t float4 " I_LINEPTPARAMS " ;\n "
" \t int4 " I_TEXOFFSET " ;\n "
" };\n " );
out.WriteFmt (" \t float4 " I_STEREOPARAMS " ;\n "
" \t float4 " I_LINEPTPARAMS " ;\n "
" \t int4 " I_TEXOFFSET " ;\n "
" } };\n " );
out.Write (" struct VS_OUTPUT {\n " );
out.WriteFmt (" struct VS_OUTPUT { {\n " );
GenerateVSOutputMembers (out, ApiType, uid_data->numTexGens , host_config, " " );
out.Write ( " };\n " );
out.WriteFmt ( " } };\n " );
if (ApiType == APIType::OpenGL || ApiType == APIType::Vulkan)
{
if (host_config.backend_gs_instancing )
out.Write (" #define InstanceID gl_InvocationID\n " );
out.WriteFmt (" #define InstanceID gl_InvocationID\n " );
out.Write (" VARYING_LOCATION(0) in VertexData {\n " );
out.WriteFmt (" VARYING_LOCATION(0) in VertexData { {\n " );
GenerateVSOutputMembers (out, ApiType, uid_data->numTexGens , host_config,
GetInterpolationQualifier (msaa, ssaa, true , true ));
out.Write (" } vs[%d ];\n " , vertex_in);
out.WriteFmt (" }} vs[{} ];\n " , vertex_in);
out.Write (" VARYING_LOCATION(0) out VertexData {\n " );
out.WriteFmt (" VARYING_LOCATION(0) out VertexData { {\n " );
GenerateVSOutputMembers (out, ApiType, uid_data->numTexGens , host_config,
GetInterpolationQualifier (msaa, ssaa, true , false ));
if (stereo)
out.Write (" \t flat int layer;\n " );
out.WriteFmt (" \t flat int layer;\n " );
out.Write ( " } ps;\n " );
out.WriteFmt ( " } } ps;\n " );
out.Write (" void main()\n {\n " );
out.WriteFmt (" void main()\n { {\n " );
}
else // D3D
{
out.Write (" struct VertexData {\n " );
out.Write (" \t VS_OUTPUT o;\n " );
out.WriteFmt (" struct VertexData { {\n " );
out.WriteFmt (" \t VS_OUTPUT o;\n " );
if (stereo)
out.Write (" \t uint layer : SV_RenderTargetArrayIndex;\n " );
out.WriteFmt (" \t uint layer : SV_RenderTargetArrayIndex;\n " );
out.Write ( " };\n " );
out.WriteFmt ( " } };\n " );
if (host_config.backend_gs_instancing )
{
out.Write (" [maxvertexcount(%d)]\n [instance(%d)]\n " , vertex_out, stereo ? 2 : 1 );
out.Write (" void main(%s VS_OUTPUT o[%d], inout %sStream<VertexData> output, in uint "
" InstanceID : SV_GSInstanceID)\n {\n " ,
primitives_d3d[primitive_type_index], vertex_in, wireframe ? " Line" : " Triangle" );
out.WriteFmt (" [maxvertexcount({})]\n [instance({})]\n " , vertex_out, stereo ? 2 : 1 );
out.WriteFmt (" void main({} VS_OUTPUT o[{}], inout {}Stream<VertexData> output, in uint "
" InstanceID : SV_GSInstanceID)\n {{\n " ,
primitives_d3d[primitive_type_index], vertex_in,
wireframe ? " Line" : " Triangle" );
}
else
{
out.Write (" [maxvertexcount(%d)]\n " , stereo ? vertex_out * 2 : vertex_out);
out.Write (" void main(%s VS_OUTPUT o[%d], inout %sStream<VertexData> output)\n {\n " ,
primitives_d3d[primitive_type_index], vertex_in, wireframe ? " Line" : " Triangle" );
out.WriteFmt (" [maxvertexcount({})]\n " , stereo ? vertex_out * 2 : vertex_out);
out.WriteFmt (" void main({} VS_OUTPUT o[{}], inout {}Stream<VertexData> output)\n {{\n " ,
primitives_d3d[primitive_type_index], vertex_in,
wireframe ? " Line" : " Triangle" );
}
out.Write (" \t VertexData ps;\n " );
out.WriteFmt (" \t VertexData ps;\n " );
}
if (primitive_type == PrimitiveType::Lines)
{
if (ApiType == APIType::OpenGL || ApiType == APIType::Vulkan)
{
out.Write (" \t VS_OUTPUT start, end;\n " );
out.WriteFmt (" \t VS_OUTPUT start, end;\n " );
AssignVSOutputMembers (out, " start" , " vs[0]" , uid_data->numTexGens , host_config);
AssignVSOutputMembers (out, " end" , " vs[1]" , uid_data->numTexGens , host_config);
}
else
{
out.Write (" \t VS_OUTPUT start = o[0];\n " );
out. Write ( " \t VS_OUTPUT end = o[1];\n " );
out.WriteFmt (" \t VS_OUTPUT start = o[0];\n "
" \t VS_OUTPUT end = o[1];\n " );
}
// GameCube/Wii's line drawing algorithm is a little quirky. It does not
// use the correct line caps. Instead, the line caps are vertical or
// horizontal depending the slope of the line.
out.Write (" \t float2 offset;\n "
" \t float2 to = abs(end.pos.xy / end.pos.w - start.pos.xy / start.pos.w);\n "
// FIXME: What does real hardware do when line is at a 45-degree angle?
// FIXME: Lines aren't drawn at the correct width. See Twilight Princess map.
" \t if (" I_LINEPTPARAMS " .y * to.y > " I_LINEPTPARAMS " .x * to.x) {\n "
// Line is more tall. Extend geometry left and right.
// Lerp LineWidth/2 from [0..VpWidth] to [-1..1]
" \t\t offset = float2(" I_LINEPTPARAMS " .z / " I_LINEPTPARAMS " .x, 0);\n "
" \t } else {\n "
// Line is more wide. Extend geometry up and down.
// Lerp LineWidth/2 from [0..VpHeight] to [1..-1]
" \t\t offset = float2(0, -" I_LINEPTPARAMS " .z / " I_LINEPTPARAMS " .y);\n "
" \t }\n " );
out.WriteFmt (" \t float2 offset;\n "
" \t float2 to = abs(end.pos.xy / end.pos.w - start.pos.xy / start.pos.w);\n "
// FIXME: What does real hardware do when line is at a 45-degree angle?
// FIXME: Lines aren't drawn at the correct width. See Twilight Princess map.
" \t if (" I_LINEPTPARAMS " .y * to.y > " I_LINEPTPARAMS " .x * to.x) { {\n "
// Line is more tall. Extend geometry left and right.
// Lerp LineWidth/2 from [0..VpWidth] to [-1..1]
" \t\t offset = float2(" I_LINEPTPARAMS " .z / " I_LINEPTPARAMS " .x, 0);\n "
" \t }} else { {\n "
// Line is more wide. Extend geometry up and down.
// Lerp LineWidth/2 from [0..VpHeight] to [1..-1]
" \t\t offset = float2(0, -" I_LINEPTPARAMS " .z / " I_LINEPTPARAMS " .y);\n "
" \t } }\n " );
}
else if (primitive_type == PrimitiveType::Points)
{
if (ApiType == APIType::OpenGL || ApiType == APIType::Vulkan)
{
out.Write (" \t VS_OUTPUT center;\n " );
out.WriteFmt (" \t VS_OUTPUT center;\n " );
AssignVSOutputMembers (out, " center" , " vs[0]" , uid_data->numTexGens , host_config);
}
else
{
out.Write (" \t VS_OUTPUT center = o[0];\n " );
out.WriteFmt (" \t VS_OUTPUT center = o[0];\n " );
}
// Offset from center to upper right vertex
// Lerp PointSize/2 from [0,0..VpWidth,VpHeight] to [-1,1..1,-1]
out.Write (" \t float2 offset = float2(" I_LINEPTPARAMS " .w / " I_LINEPTPARAMS
" .x, -" I_LINEPTPARAMS " .w / " I_LINEPTPARAMS " .y) * center.pos.w;\n " );
out.WriteFmt (" \t float2 offset = float2(" I_LINEPTPARAMS " .w / " I_LINEPTPARAMS
" .x, -" I_LINEPTPARAMS " .w / " I_LINEPTPARAMS " .y) * center.pos.w;\n " );
}
if (stereo)
{
// If the GPU supports invocation we don't need a for loop and can simply use the
// invocation identifier to determine which layer we're rendering.
if (host_config.backend_gs_instancing )
out.Write (" \t int eye = InstanceID;\n " );
out.WriteFmt (" \t int eye = InstanceID;\n " );
else
out.Write (" \t for (int eye = 0; eye < 2; ++eye) {\n " );
out.WriteFmt (" \t for (int eye = 0; eye < 2; ++eye) { {\n " );
}
if (wireframe)
out.Write (" \t VS_OUTPUT first;\n " );
out.WriteFmt (" \t VS_OUTPUT first;\n " );
out.Write (" \t for (int i = 0; i < %d ; ++i) {\n " , vertex_in);
out.WriteFmt (" \t for (int i = 0; i < {} ; ++i) { {\n " , vertex_in);
if (ApiType == APIType::OpenGL || ApiType == APIType::Vulkan)
{
out.Write (" \t VS_OUTPUT f;\n " );
out.WriteFmt (" \t VS_OUTPUT f;\n " );
AssignVSOutputMembers (out, " f" , " vs[i]" , uid_data->numTexGens , host_config);
if (host_config.backend_depth_clamp &&
DriverDetails::HasBug (DriverDetails::BUG_BROKEN_CLIP_DISTANCE))
{
// On certain GPUs we have to consume the clip distance from the vertex shader
// or else the other vertex shader outputs will get corrupted.
out.Write (" \t f.clipDist0 = gl_in[i].gl_ClipDistance[0];\n " );
out. Write ( " \t f.clipDist1 = gl_in[i].gl_ClipDistance[1];\n " );
out.WriteFmt (" \t f.clipDist0 = gl_in[i].gl_ClipDistance[0];\n "
" \t f.clipDist1 = gl_in[i].gl_ClipDistance[1];\n " );
}
}
else
{
out.Write (" \t VS_OUTPUT f = o[i];\n " );
out.WriteFmt (" \t VS_OUTPUT f = o[i];\n " );
}
if (stereo)
{
// Select the output layer
out.Write (" \t ps.layer = eye;\n " );
out.WriteFmt (" \t ps.layer = eye;\n " );
if (ApiType == APIType::OpenGL || ApiType == APIType::Vulkan)
out.Write (" \t gl_Layer = eye;\n " );
out.WriteFmt (" \t gl_Layer = eye;\n " );
// For stereoscopy add a small horizontal offset in Normalized Device Coordinates proportional
// to the depth of the vertex. We retrieve the depth value from the w-component of the projected
@@ -239,56 +250,56 @@ ShaderCode GenerateGeometryShaderCode(APIType ApiType, const ShaderHostConfig& h
// the depth value. This results in objects at a distance smaller than the convergence
// distance to seemingly appear in front of the screen.
// This formula is based on page 13 of the "Nvidia 3D Vision Automatic, Best Practices Guide"
out.Write (" \t float hoffset = (eye == 0) ? " I_STEREOPARAMS " .x : " I_STEREOPARAMS " .y;\n " );
out.Write (" \t f.pos.x += hoffset * (f.pos.w - " I_STEREOPARAMS " .z);\n " );
out.WriteFmt (" \t float hoffset = (eye == 0) ? " I_STEREOPARAMS " .x : " I_STEREOPARAMS " .y;\n " );
out.WriteFmt (" \t f.pos.x += hoffset * (f.pos.w - " I_STEREOPARAMS " .z);\n " );
}
if (primitive_type == PrimitiveType::Lines)
{
out.Write (" \t VS_OUTPUT l = f;\n "
" \t VS_OUTPUT r = f;\n " );
out.WriteFmt (" \t VS_OUTPUT l = f;\n "
" \t VS_OUTPUT r = f;\n " );
out.Write (" \t l.pos.xy -= offset * l.pos.w;\n "
" \t r.pos.xy += offset * r.pos.w;\n " );
out.WriteFmt (" \t l.pos.xy -= offset * l.pos.w;\n "
" \t r.pos.xy += offset * r.pos.w;\n " );
out.Write (" \t if (" I_TEXOFFSET " [2] != 0) {\n " );
out.Write (" \t float texOffset = 1.0 / float(" I_TEXOFFSET " [2]);\n " );
out.WriteFmt (" \t if (" I_TEXOFFSET " [2] != 0) { {\n " );
out.WriteFmt (" \t float texOffset = 1.0 / float(" I_TEXOFFSET " [2]);\n " );
for (unsigned int i = 0 ; i < uid_data->numTexGens ; ++i)
for (u32 i = 0 ; i < uid_data->numTexGens ; ++i)
{
out.Write (" \t if (((" I_TEXOFFSET " [0] >> %d ) & 0x1) != 0)\n " , i);
out.Write (" \t\t r.tex%d .x += texOffset;\n " , i);
out.WriteFmt (" \t if (((" I_TEXOFFSET " [0] >> {} ) & 0x1) != 0)\n " , i);
out.WriteFmt (" \t\t r.tex{} .x += texOffset;\n " , i);
}
out.Write (" \t }\n " );
out.WriteFmt (" \t } }\n " );
EmitVertex (out, host_config, uid_data, " l" , ApiType, wireframe, true );
EmitVertex (out, host_config, uid_data, " r" , ApiType, wireframe);
}
else if (primitive_type == PrimitiveType::Points)
{
out.Write (" \t VS_OUTPUT ll = f;\n "
" \t VS_OUTPUT lr = f;\n "
" \t VS_OUTPUT ul = f;\n "
" \t VS_OUTPUT ur = f;\n " );
out.WriteFmt (" \t VS_OUTPUT ll = f;\n "
" \t VS_OUTPUT lr = f;\n "
" \t VS_OUTPUT ul = f;\n "
" \t VS_OUTPUT ur = f;\n " );
out.Write (" \t ll.pos.xy += float2(-1,-1) * offset;\n "
" \t lr.pos.xy += float2(1,-1) * offset;\n "
" \t ul.pos.xy += float2(-1,1) * offset;\n "
" \t ur.pos.xy += offset;\n " );
out.WriteFmt (" \t ll.pos.xy += float2(-1,-1) * offset;\n "
" \t lr.pos.xy += float2(1,-1) * offset;\n "
" \t ul.pos.xy += float2(-1,1) * offset;\n "
" \t ur.pos.xy += offset;\n " );
out.Write (" \t if (" I_TEXOFFSET " [3] != 0) {\n " );
out.Write (" \t float2 texOffset = float2(1.0 / float(" I_TEXOFFSET
" [3]), 1.0 / float(" I_TEXOFFSET " [3]));\n " );
out.WriteFmt (" \t if (" I_TEXOFFSET " [3] != 0) { {\n " );
out.WriteFmt (" \t float2 texOffset = float2(1.0 / float(" I_TEXOFFSET
" [3]), 1.0 / float(" I_TEXOFFSET " [3]));\n " );
for (unsigned int i = 0 ; i < uid_data->numTexGens ; ++i)
for (u32 i = 0 ; i < uid_data->numTexGens ; ++i)
{
out.Write (" \t if (((" I_TEXOFFSET " [1] >> %d ) & 0x1) != 0) {\n " , i);
out.Write (" \t\t ul.tex%d .xy += float2(0,1) * texOffset;\n " , i);
out.Write (" \t\t ur.tex%d .xy += texOffset;\n " , i);
out.Write (" \t\t lr.tex%d .xy += float2(1,0) * texOffset;\n " , i);
out.Write (" \t }\n " );
out.WriteFmt (" \t if (((" I_TEXOFFSET " [1] >> {} ) & 0x1) != 0) { {\n " , i);
out.WriteFmt (" \t\t ul.tex{} .xy += float2(0,1) * texOffset;\n " , i);
out.WriteFmt (" \t\t ur.tex{} .xy += texOffset;\n " , i);
out.WriteFmt (" \t\t lr.tex{} .xy += float2(1,0) * texOffset;\n " , i);
out.WriteFmt (" \t } }\n " );
}
out.Write (" \t }\n " );
out.WriteFmt (" \t } }\n " );
EmitVertex (out, host_config, uid_data, " ll" , ApiType, wireframe, true );
EmitVertex (out, host_config, uid_data, " lr" , ApiType, wireframe);
@@ -300,14 +311,14 @@ ShaderCode GenerateGeometryShaderCode(APIType ApiType, const ShaderHostConfig& h
EmitVertex (out, host_config, uid_data, " f" , ApiType, wireframe, true );
}
out.Write (" \t }\n " );
out.WriteFmt (" \t } }\n " );
EndPrimitive (out, host_config, uid_data, ApiType, wireframe);
if (stereo && !host_config.backend_gs_instancing )
out.Write (" \t }\n " );
out.WriteFmt (" \t } }\n " );
out.Write ( " }\n " );
out.WriteFmt ( " } }\n " );
return out;
}
@@ -317,34 +328,34 @@ static void EmitVertex(ShaderCode& out, const ShaderHostConfig& host_config,
APIType ApiType, bool wireframe, bool first_vertex)
{
if (wireframe && first_vertex)
out.Write (" \t if (i == 0) first = %s ;\n " , vertex);
out.WriteFmt (" \t if (i == 0) first = {} ;\n " , vertex);
if (ApiType == APIType::OpenGL)
{
out.Write (" \t gl_Position = %s .pos;\n " , vertex);
out.WriteFmt (" \t gl_Position = {} .pos;\n " , vertex);
if (host_config.backend_depth_clamp )
{
out.Write (" \t gl_ClipDistance[0] = %s .clipDist0;\n " , vertex);
out.Write (" \t gl_ClipDistance[1] = %s .clipDist1;\n " , vertex);
out.WriteFmt (" \t gl_ClipDistance[0] = {} .clipDist0;\n " , vertex);
out.WriteFmt (" \t gl_ClipDistance[1] = {} .clipDist1;\n " , vertex);
}
AssignVSOutputMembers (out, " ps" , vertex, uid_data->numTexGens , host_config);
}
else if (ApiType == APIType::Vulkan)
{
// Vulkan NDC space has Y pointing down (right-handed NDC space).
out.Write (" \t gl_Position = %s .pos;\n " , vertex);
out.Write (" \t gl_Position.y = -gl_Position.y;\n " );
out.WriteFmt (" \t gl_Position = {} .pos;\n " , vertex);
out.WriteFmt (" \t gl_Position.y = -gl_Position.y;\n " );
AssignVSOutputMembers (out, " ps" , vertex, uid_data->numTexGens , host_config);
}
else
{
out.Write (" \t ps.o = %s ;\n " , vertex);
out.WriteFmt (" \t ps.o = {} ;\n " , vertex);
}
if (ApiType == APIType::OpenGL || ApiType == APIType::Vulkan)
out.Write (" \t EmitVertex();\n " );
out.WriteFmt (" \t EmitVertex();\n " );
else
out.Write (" \t output.Append(ps);\n " );
out.WriteFmt (" \t output.Append(ps);\n " );
}
static void EndPrimitive (ShaderCode& out, const ShaderHostConfig& host_config,
@@ -354,9 +365,9 @@ static void EndPrimitive(ShaderCode& out, const ShaderHostConfig& host_config,
EmitVertex (out, host_config, uid_data, " first" , ApiType, wireframe);
if (ApiType == APIType::OpenGL || ApiType == APIType::Vulkan)
out.Write (" \t EndPrimitive();\n " );
out.WriteFmt (" \t EndPrimitive();\n " );
else
out.Write (" \t output.RestartStrip();\n " );
out.WriteFmt (" \t output.RestartStrip();\n " );
}
void EnumerateGeometryShaderUids (const std::function<void (const GeometryShaderUid&)>& callback)