From e1fea1e0c594cc7c5a404e7006a4b4b2f29200ae Mon Sep 17 00:00:00 2001 From: ReinUsesLisp Date: Mon, 24 Dec 2018 02:24:38 -0300 Subject: [PATCH] video_core: Implement IR based geometry shaders --- .../renderer_opengl/gl_shader_gen.cpp | 14 ++-- src/video_core/shader/decode/other.cpp | 25 +++++++ src/video_core/shader/glsl_decompiler.cpp | 70 ++++++++++++++++++- src/video_core/shader/shader_ir.h | 3 + 4 files changed, 102 insertions(+), 10 deletions(-) diff --git a/src/video_core/renderer_opengl/gl_shader_gen.cpp b/src/video_core/renderer_opengl/gl_shader_gen.cpp index 59f45cde3d..743a9c90e1 100644 --- a/src/video_core/renderer_opengl/gl_shader_gen.cpp +++ b/src/video_core/renderer_opengl/gl_shader_gen.cpp @@ -80,16 +80,11 @@ ProgramResult GenerateGeometryShader(const ShaderSetup& setup) { // Version is intentionally skipped in shader generation, it's added by the lazy compilation. const std::string id = fmt::format("{:016x}", setup.program.unique_identifier); - std::string out = out += "// Shader Unique Id: GS" + id + '\n'; + std::string out = "// Shader Unique Id: GS" + id + '\n'; out += "#extension GL_ARB_separate_shader_objects : enable\n"; out += GetCommonDeclarations(); - ShaderIR program_ir(setup.program.code, PROGRAM_OFFSET); - ProgramResult program = - Decompile(program_ir, Maxwell3D::Regs::ShaderStage::Geometry, "geometry"); - - out += R"( -out gl_PerVertex { + out += R"(out gl_PerVertex { vec4 gl_Position; }; @@ -103,9 +98,12 @@ layout (std140) uniform gs_config { }; )"; + ShaderIR program_ir(setup.program.code, PROGRAM_OFFSET); + ProgramResult program = + Decompile(program_ir, Maxwell3D::Regs::ShaderStage::Geometry, "geometry"); out += program.first; - out = R"( + out += R"( void main() { execute_geometry(); };)"; diff --git a/src/video_core/shader/decode/other.cpp b/src/video_core/shader/decode/other.cpp index 9630ef831f..1918762b8d 100644 --- a/src/video_core/shader/decode/other.cpp +++ b/src/video_core/shader/decode/other.cpp @@ -12,6 +12,7 @@ namespace VideoCommon::Shader { using Tegra::Shader::ConditionCode; using Tegra::Shader::Instruction; using Tegra::Shader::OpCode; +using Tegra::Shader::Register; u32 ShaderIR::DecodeOther(BasicBlock& bb, u32 pc) { const Instruction instr = {program_code[pc]}; @@ -140,6 +141,30 @@ u32 ShaderIR::DecodeOther(BasicBlock& bb, u32 pc) { SetRegister(bb, instr.gpr0, value); break; } + case OpCode::Id::OUT_R: { + UNIMPLEMENTED_IF_MSG(instr.gpr20.Value() != Register::ZeroIndex, + "Stream buffer is not supported"); + + if (instr.out.emit) { + // gpr0 is used to store the next address and gpr8 contains the address to emit. + // Hardware uses pointers here but we just ignore it + bb.push_back(Operation(OperationCode::EmitVertex)); + SetRegister(bb, instr.gpr0, Immediate(0)); + } + if (instr.out.cut) { + bb.push_back(Operation(OperationCode::EndPrimitive)); + } + break; + } + case OpCode::Id::ISBERD: { + UNIMPLEMENTED_IF(instr.isberd.o != 0); + UNIMPLEMENTED_IF(instr.isberd.skew != 0); + UNIMPLEMENTED_IF(instr.isberd.shift != Tegra::Shader::IsberdShift::None); + UNIMPLEMENTED_IF(instr.isberd.mode != Tegra::Shader::IsberdMode::None); + LOG_WARNING(HW_GPU, "ISBERD instruction is incomplete"); + SetRegister(bb, instr.gpr0, GetRegister(instr.gpr8)); + break; + } case OpCode::Id::DEPBAR: { LOG_WARNING(HW_GPU, "DEPBAR instruction is stubbed"); break; diff --git a/src/video_core/shader/glsl_decompiler.cpp b/src/video_core/shader/glsl_decompiler.cpp index 2e9323df7a..9b443e61a4 100644 --- a/src/video_core/shader/glsl_decompiler.cpp +++ b/src/video_core/shader/glsl_decompiler.cpp @@ -89,6 +89,22 @@ static std::string GetSwizzle(u32 elem) { return swizzle; } +/// Translate topology +static std::string GetTopologyName(Tegra::Shader::OutputTopology topology) { + switch (topology) { + case Tegra::Shader::OutputTopology::PointList: + return "points"; + case Tegra::Shader::OutputTopology::LineStrip: + return "line_strip"; + case Tegra::Shader::OutputTopology::TriangleStrip: + return "triangle_strip"; + default: + UNIMPLEMENTED_MSG("Unknown output topology: {}", static_cast(topology)); + return "points"; + } +} + +/// Returns true if an object has to be treated as precise static bool IsPrecise(Operation operand) { const auto& meta = operand.GetMeta(); @@ -115,6 +131,7 @@ public: void Decompile() { DeclareVertex(); + DeclareGeometry(); DeclareRegisters(); DeclarePredicates(); DeclareLocalMemory(); @@ -212,6 +229,16 @@ private: code.AddNewLine(); } + void DeclareGeometry() { + if (stage != ShaderStage::Geometry) + return; + + const auto topology = GetTopologyName(header.common3.output_topology); + const auto max_vertices = std::to_string(header.common4.max_output_vertices); + code.AddLine("layout (" + topology + ", max_vertices = " + max_vertices + ") out;"); + code.AddNewLine(); + } + void DeclareRegisters() { const auto& registers = ir.GetRegisters(); for (const u32 gpr : registers) { @@ -419,9 +446,24 @@ private: const auto attribute = abuf->GetIndex(); const auto element = abuf->GetElement(); + const auto GeometryPass = [&](const std::string& name) { + if (stage == ShaderStage::Geometry && abuf->GetBuffer()) { + // TODO(Rodrigo): Guard geometry inputs against out of bound reads. Some games + // set an 0x80000000 index for those and the shader fails to build. Find out why + // this happens and what's its intent. + return "gs_" + name + "[ftou(" + Visit(abuf->GetBuffer()) + + ") % MAX_VERTEX_INPUT]"; + } + return name; + }; + switch (attribute) { case Attribute::Index::Position: - return element == 3 ? "1.0f" : "gl_FragCoord" + GetSwizzle(element); + if (stage != ShaderStage::Fragment) { + return GeometryPass("position") + GetSwizzle(element); + } else { + return element == 3 ? "1.0f" : "gl_FragCoord" + GetSwizzle(element); + } case Attribute::Index::PointCoord: switch (element) { case 0: @@ -460,7 +502,7 @@ private: default: if (attribute >= Attribute::Index::Attribute_0 && attribute <= Attribute::Index::Attribute_31) { - return GetInputAttribute(attribute) + GetSwizzle(abuf->GetElement()); + return GeometryPass(GetInputAttribute(attribute)) + GetSwizzle(element); } break; } @@ -1226,6 +1268,27 @@ private: return {}; } + std::string EmitVertex(Operation operation) { + ASSERT_MSG(stage == ShaderStage::Geometry, + "EmitVertex is expected to be used in a geometry shader."); + + // If a geometry shader is attached, it will always flip (it's the last stage before + // fragment). For more info about flipping, refer to gl_shader_gen.cpp. + code.AddLine("position.xy *= viewport_flip.xy;"); + code.AddLine("gl_Position = position;"); + code.AddLine("position.w = 1.0;"); + code.AddLine("EmitVertex();"); + return {}; + } + + std::string EndPrimitive(Operation operation) { + ASSERT_MSG(stage == ShaderStage::Geometry, + "EndPrimitive is expected to be used in a geometry shader."); + + code.AddLine("EndPrimitive();"); + return {}; + } + std::string YNegate(Operation operation) { // Config pack's third value is Y_NEGATE's state. return "uintBitsToFloat(config_pack[2])"; @@ -1361,6 +1424,9 @@ private: &Exit, &Kil, + &EmitVertex, + &EndPrimitive, + &YNegate, }; diff --git a/src/video_core/shader/shader_ir.h b/src/video_core/shader/shader_ir.h index 0ea2df6bd6..5676d32a9e 100644 --- a/src/video_core/shader/shader_ir.h +++ b/src/video_core/shader/shader_ir.h @@ -172,6 +172,9 @@ enum class OperationCode { Exit, /// () -> void Kil, /// () -> void + EmitVertex, /// () -> void + EndPrimitive, /// () -> void + YNegate, /// () -> float Amount,