gl_shader_decompiler: Implement geometry shaders

This commit is contained in:
ReinUsesLisp 2018-10-06 23:17:31 -03:00
parent 4d0c682468
commit ee4d538850
10 changed files with 536 additions and 121 deletions

View file

@ -314,6 +314,15 @@ enum class TextureMiscMode : u64 {
PTP,
};
enum class IsberdMode : u64 {
None = 0,
Patch = 1,
Prim = 2,
Attr = 3,
};
enum class IsberdShift : u64 { None = 0, U16 = 1, B32 = 2 };
enum class IpaInterpMode : u64 {
Linear = 0,
Perspective = 1,
@ -340,6 +349,87 @@ struct IpaMode {
}
};
enum class SystemVariable : u64 {
LaneId = 0x00,
VirtCfg = 0x02,
VirtId = 0x03,
Pm0 = 0x04,
Pm1 = 0x05,
Pm2 = 0x06,
Pm3 = 0x07,
Pm4 = 0x08,
Pm5 = 0x09,
Pm6 = 0x0a,
Pm7 = 0x0b,
OrderingTicket = 0x0f,
PrimType = 0x10,
InvocationId = 0x11,
Ydirection = 0x12,
ThreadKill = 0x13,
ShaderType = 0x14,
DirectBeWriteAddressLow = 0x15,
DirectBeWriteAddressHigh = 0x16,
DirectBeWriteEnabled = 0x17,
MachineId0 = 0x18,
MachineId1 = 0x19,
MachineId2 = 0x1a,
MachineId3 = 0x1b,
Affinity = 0x1c,
InvocationInfo = 0x1d,
WscaleFactorXY = 0x1e,
WscaleFactorZ = 0x1f,
Tid = 0x20,
TidX = 0x21,
TidY = 0x22,
TidZ = 0x23,
CtaParam = 0x24,
CtaIdX = 0x25,
CtaIdY = 0x26,
CtaIdZ = 0x27,
NtId = 0x28,
CirQueueIncrMinusOne = 0x29,
Nlatc = 0x2a,
SmSpaVersion = 0x2c,
MultiPassShaderInfo = 0x2d,
LwinHi = 0x2e,
SwinHi = 0x2f,
SwinLo = 0x30,
SwinSz = 0x31,
SmemSz = 0x32,
SmemBanks = 0x33,
LwinLo = 0x34,
LwinSz = 0x35,
LmemLosz = 0x36,
LmemHioff = 0x37,
EqMask = 0x38,
LtMask = 0x39,
LeMask = 0x3a,
GtMask = 0x3b,
GeMask = 0x3c,
RegAlloc = 0x3d,
CtxAddr = 0x3e, // .fmask = F_SM50
BarrierAlloc = 0x3e, // .fmask = F_SM60
GlobalErrorStatus = 0x40,
WarpErrorStatus = 0x42,
WarpErrorStatusClear = 0x43,
PmHi0 = 0x48,
PmHi1 = 0x49,
PmHi2 = 0x4a,
PmHi3 = 0x4b,
PmHi4 = 0x4c,
PmHi5 = 0x4d,
PmHi6 = 0x4e,
PmHi7 = 0x4f,
ClockLo = 0x50,
ClockHi = 0x51,
GlobalTimerLo = 0x52,
GlobalTimerHi = 0x53,
HwTaskId = 0x60,
CircularQueueEntryIndex = 0x61,
CircularQueueEntryAddressLow = 0x62,
CircularQueueEntryAddressHigh = 0x63,
};
union Instruction {
Instruction& operator=(const Instruction& instr) {
value = instr.value;
@ -914,6 +1004,18 @@ union Instruction {
}
} bra;
union {
BitField<39, 1, u64> emit; // EmitVertex
BitField<40, 1, u64> cut; // EndPrimitive
} out;
union {
BitField<31, 1, u64> skew;
BitField<32, 1, u64> o;
BitField<33, 2, IsberdMode> mode;
BitField<47, 2, IsberdShift> shift;
} isberd;
union {
BitField<20, 16, u64> imm20_16;
BitField<36, 1, u64> product_shift_left;
@ -936,6 +1038,10 @@ union Instruction {
BitField<36, 5, u64> index;
} cbuf36;
// Unsure about the size of this one.
// It's always used with a gpr0, so any size should be fine.
BitField<20, 8, SystemVariable> sys20;
BitField<47, 1, u64> generates_cc;
BitField<61, 1, u64> is_b_imm;
BitField<60, 1, u64> is_b_gpr;
@ -975,6 +1081,8 @@ public:
TMML, // Texture Mip Map Level
EXIT,
IPA,
OUT_R, // Emit vertex/primitive
ISBERD,
FFMA_IMM, // Fused Multiply and Add
FFMA_CR,
FFMA_RC,
@ -1034,6 +1142,7 @@ public:
MOV_C,
MOV_R,
MOV_IMM,
MOV_SYS,
MOV32_IMM,
SHL_C,
SHL_R,
@ -1209,6 +1318,8 @@ private:
INST("1101111101011---", Id::TMML, Type::Memory, "TMML"),
INST("111000110000----", Id::EXIT, Type::Trivial, "EXIT"),
INST("11100000--------", Id::IPA, Type::Trivial, "IPA"),
INST("1111101111100---", Id::OUT_R, Type::Trivial, "OUT_R"),
INST("1110111111010---", Id::ISBERD, Type::Trivial, "ISBERD"),
INST("0011001-1-------", Id::FFMA_IMM, Type::Ffma, "FFMA_IMM"),
INST("010010011-------", Id::FFMA_CR, Type::Ffma, "FFMA_CR"),
INST("010100011-------", Id::FFMA_RC, Type::Ffma, "FFMA_RC"),
@ -1255,6 +1366,7 @@ private:
INST("0100110010011---", Id::MOV_C, Type::Arithmetic, "MOV_C"),
INST("0101110010011---", Id::MOV_R, Type::Arithmetic, "MOV_R"),
INST("0011100-10011---", Id::MOV_IMM, Type::Arithmetic, "MOV_IMM"),
INST("1111000011001---", Id::MOV_SYS, Type::Trivial, "MOV_SYS"),
INST("000000010000----", Id::MOV32_IMM, Type::ArithmeticImmediate, "MOV32_IMM"),
INST("0100110001100---", Id::FMNMX_C, Type::Arithmetic, "FMNMX_C"),
INST("0101110001100---", Id::FMNMX_R, Type::Arithmetic, "FMNMX_R"),

View file

@ -255,7 +255,7 @@ DrawParameters RasterizerOpenGL::SetupDraw() {
return params;
}
void RasterizerOpenGL::SetupShaders() {
void RasterizerOpenGL::SetupShaders(GLenum primitive_mode) {
MICROPROFILE_SCOPE(OpenGL_Shader);
const auto& gpu = Core::System::GetInstance().GPU().Maxwell3D();
@ -270,6 +270,11 @@ void RasterizerOpenGL::SetupShaders() {
// Skip stages that are not enabled
if (!gpu.regs.IsShaderConfigEnabled(index)) {
switch (program) {
case Maxwell::ShaderProgram::Geometry:
shader_program_manager->UseTrivialGeometryShader();
break;
}
continue;
}
@ -288,11 +293,18 @@ void RasterizerOpenGL::SetupShaders() {
switch (program) {
case Maxwell::ShaderProgram::VertexA:
case Maxwell::ShaderProgram::VertexB: {
shader_program_manager->UseProgrammableVertexShader(shader->GetProgramHandle());
shader_program_manager->UseProgrammableVertexShader(
shader->GetProgramHandle(primitive_mode));
break;
}
case Maxwell::ShaderProgram::Geometry: {
shader_program_manager->UseProgrammableGeometryShader(
shader->GetProgramHandle(primitive_mode));
break;
}
case Maxwell::ShaderProgram::Fragment: {
shader_program_manager->UseProgrammableFragmentShader(shader->GetProgramHandle());
shader_program_manager->UseProgrammableFragmentShader(
shader->GetProgramHandle(primitive_mode));
break;
}
default:
@ -302,12 +314,13 @@ void RasterizerOpenGL::SetupShaders() {
}
// Configure the const buffers for this shader stage.
current_constbuffer_bindpoint = SetupConstBuffers(static_cast<Maxwell::ShaderStage>(stage),
shader, current_constbuffer_bindpoint);
current_constbuffer_bindpoint =
SetupConstBuffers(static_cast<Maxwell::ShaderStage>(stage), shader, primitive_mode,
current_constbuffer_bindpoint);
// Configure the textures for this shader stage.
current_texture_bindpoint = SetupTextures(static_cast<Maxwell::ShaderStage>(stage), shader,
current_texture_bindpoint);
primitive_mode, current_texture_bindpoint);
// When VertexA is enabled, we have dual vertex shaders
if (program == Maxwell::ShaderProgram::VertexA) {
@ -317,8 +330,6 @@ void RasterizerOpenGL::SetupShaders() {
}
state.Apply();
shader_program_manager->UseTrivialGeometryShader();
}
std::size_t RasterizerOpenGL::CalculateVertexArraysSize() const {
@ -580,7 +591,7 @@ void RasterizerOpenGL::DrawArrays() {
SetupVertexArrays();
DrawParameters params = SetupDraw();
SetupShaders();
SetupShaders(params.primitive_mode);
buffer_cache.Unmap();
@ -719,7 +730,7 @@ void RasterizerOpenGL::SamplerInfo::SyncWithConfig(const Tegra::Texture::TSCEntr
}
u32 RasterizerOpenGL::SetupConstBuffers(Maxwell::ShaderStage stage, Shader& shader,
u32 current_bindpoint) {
GLenum primitive_mode, u32 current_bindpoint) {
MICROPROFILE_SCOPE(OpenGL_UBO);
const auto& gpu = Core::System::GetInstance().GPU();
const auto& maxwell3d = gpu.Maxwell3D();
@ -771,7 +782,7 @@ u32 RasterizerOpenGL::SetupConstBuffers(Maxwell::ShaderStage stage, Shader& shad
buffer.address, size, static_cast<std::size_t>(uniform_buffer_alignment));
// Now configure the bindpoint of the buffer inside the shader
glUniformBlockBinding(shader->GetProgramHandle(),
glUniformBlockBinding(shader->GetProgramHandle(primitive_mode),
shader->GetProgramResourceIndex(used_buffer),
current_bindpoint + bindpoint);
@ -787,7 +798,8 @@ u32 RasterizerOpenGL::SetupConstBuffers(Maxwell::ShaderStage stage, Shader& shad
return current_bindpoint + static_cast<u32>(entries.size());
}
u32 RasterizerOpenGL::SetupTextures(Maxwell::ShaderStage stage, Shader& shader, u32 current_unit) {
u32 RasterizerOpenGL::SetupTextures(Maxwell::ShaderStage stage, Shader& shader,
GLenum primitive_mode, u32 current_unit) {
MICROPROFILE_SCOPE(OpenGL_Texture);
const auto& gpu = Core::System::GetInstance().GPU();
const auto& maxwell3d = gpu.Maxwell3D();
@ -802,8 +814,8 @@ u32 RasterizerOpenGL::SetupTextures(Maxwell::ShaderStage stage, Shader& shader,
// Bind the uniform to the sampler.
glProgramUniform1i(shader->GetProgramHandle(), shader->GetUniformLocation(entry),
current_bindpoint);
glProgramUniform1i(shader->GetProgramHandle(primitive_mode),
shader->GetUniformLocation(entry), current_bindpoint);
const auto texture = maxwell3d.GetStageTexture(entry.GetStage(), entry.GetOffset());

View file

@ -120,7 +120,7 @@ private:
* @returns The next available bindpoint for use in the next shader stage.
*/
u32 SetupConstBuffers(Tegra::Engines::Maxwell3D::Regs::ShaderStage stage, Shader& shader,
u32 current_bindpoint);
GLenum primitive_mode, u32 current_bindpoint);
/*
* Configures the current textures to use for the draw command.
@ -130,7 +130,7 @@ private:
* @returns The next available bindpoint for use in the next shader stage.
*/
u32 SetupTextures(Tegra::Engines::Maxwell3D::Regs::ShaderStage stage, Shader& shader,
u32 current_unit);
GLenum primitive_mode, u32 current_unit);
/// Syncs the viewport to match the guest state
void SyncViewport();
@ -207,7 +207,7 @@ private:
DrawParameters SetupDraw();
void SetupShaders();
void SetupShaders(GLenum primitive_mode);
enum class AccelDraw { Disabled, Arrays, Indexed };
AccelDraw accelerate_draw = AccelDraw::Disabled;

View file

@ -68,6 +68,10 @@ CachedShader::CachedShader(VAddr addr, Maxwell::ShaderProgram program_type)
program_result = GLShader::GenerateVertexShader(setup);
gl_type = GL_VERTEX_SHADER;
break;
case Maxwell::ShaderProgram::Geometry:
program_result = GLShader::GenerateGeometryShader(setup);
gl_type = GL_GEOMETRY_SHADER;
break;
case Maxwell::ShaderProgram::Fragment:
program_result = GLShader::GenerateFragmentShader(setup);
gl_type = GL_FRAGMENT_SHADER;
@ -80,11 +84,16 @@ CachedShader::CachedShader(VAddr addr, Maxwell::ShaderProgram program_type)
entries = program_result.second;
OGLShader shader;
shader.Create(program_result.first.c_str(), gl_type);
program.Create(true, shader.handle);
SetShaderUniformBlockBindings(program.handle);
VideoCore::LabelGLObject(GL_PROGRAM, program.handle, addr);
if (program_type != Maxwell::ShaderProgram::Geometry) {
OGLShader shader;
shader.Create(program_result.first.c_str(), gl_type);
program.Create(true, shader.handle);
SetShaderUniformBlockBindings(program.handle);
VideoCore::LabelGLObject(GL_PROGRAM, program.handle, addr);
} else {
// Store shader's code to lazily build it on draw
geometry_programs.code = program_result.first;
}
}
GLuint CachedShader::GetProgramResourceIndex(const GLShader::ConstBufferEntry& buffer) {
@ -110,6 +119,21 @@ GLint CachedShader::GetUniformLocation(const GLShader::SamplerEntry& sampler) {
return search->second;
}
GLuint CachedShader::LazyGeometryProgram(OGLProgram& target_program,
const std::string& glsl_topology,
const std::string& debug_name) {
if (target_program.handle != 0) {
return target_program.handle;
}
const std::string source{geometry_programs.code + "layout (" + glsl_topology + ") in;\n"};
OGLShader shader;
shader.Create(source.c_str(), GL_GEOMETRY_SHADER);
target_program.Create(true, shader.handle);
SetShaderUniformBlockBindings(target_program.handle);
VideoCore::LabelGLObject(GL_PROGRAM, target_program.handle, addr, debug_name);
return target_program.handle;
};
Shader ShaderCacheOpenGL::GetStageProgram(Maxwell::ShaderProgram program) {
const VAddr program_addr{GetShaderAddress(program)};

View file

@ -7,6 +7,7 @@
#include <map>
#include <memory>
#include "common/assert.h"
#include "common/common_types.h"
#include "video_core/rasterizer_cache.h"
#include "video_core/renderer_opengl/gl_resource_manager.h"
@ -38,8 +39,31 @@ public:
}
/// Gets the GL program handle for the shader
GLuint GetProgramHandle() const {
return program.handle;
GLuint GetProgramHandle(GLenum primitive_mode) {
if (program_type != Maxwell::ShaderProgram::Geometry) {
return program.handle;
}
switch (primitive_mode) {
case GL_POINTS:
return LazyGeometryProgram(geometry_programs.points, "points", "ShaderPoints");
case GL_LINES:
case GL_LINE_STRIP:
return LazyGeometryProgram(geometry_programs.lines, "lines", "ShaderLines");
case GL_LINES_ADJACENCY:
case GL_LINE_STRIP_ADJACENCY:
return LazyGeometryProgram(geometry_programs.lines_adjacency, "lines_adjacency",
"ShaderLinesAdjacency");
case GL_TRIANGLES:
case GL_TRIANGLE_STRIP:
case GL_TRIANGLE_FAN:
return LazyGeometryProgram(geometry_programs.triangles, "triangles", "ShaderTriangles");
case GL_TRIANGLES_ADJACENCY:
case GL_TRIANGLE_STRIP_ADJACENCY:
return LazyGeometryProgram(geometry_programs.triangles_adjacency, "triangles_adjacency",
"ShaderLines");
default:
UNREACHABLE_MSG("Unknown primitive mode.");
}
}
/// Gets the GL program resource location for the specified resource, caching as needed
@ -49,12 +73,30 @@ public:
GLint GetUniformLocation(const GLShader::SamplerEntry& sampler);
private:
/// Generates a geometry shader or returns one that already exists.
GLuint LazyGeometryProgram(OGLProgram& target_program, const std::string& glsl_topology,
const std::string& debug_name);
VAddr addr;
Maxwell::ShaderProgram program_type;
GLShader::ShaderSetup setup;
GLShader::ShaderEntries entries;
// Non-geometry program.
OGLProgram program;
// Geometry programs. These are needed because GLSL needs an input topology but it's not
// declared by the hardware. Workaround this issue by generating a different shader per input
// topology class.
struct {
std::string code;
OGLProgram points;
OGLProgram lines;
OGLProgram lines_adjacency;
OGLProgram triangles;
OGLProgram triangles_adjacency;
} geometry_programs;
std::map<u32, GLuint> resource_cache;
std::map<u32, GLint> uniform_cache;
};

View file

@ -7,6 +7,7 @@
#include <string>
#include <string_view>
#include <boost/optional.hpp>
#include <fmt/format.h>
#include "common/assert.h"
@ -29,11 +30,32 @@ using Tegra::Shader::SubOp;
constexpr u32 PROGRAM_END = MAX_PROGRAM_CODE_LENGTH;
constexpr u32 PROGRAM_HEADER_SIZE = sizeof(Tegra::Shader::Header);
constexpr u32 POSITION_VARYING_LOCATION = 15;
constexpr u32 MAX_GEOMETRY_BUFFERS = 6;
constexpr u32 MAX_ATTRIBUTES = 0x100; // Size in vec4s, this value is untested
class DecompileFail : public std::runtime_error {
public:
using std::runtime_error::runtime_error;
};
/// 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:
LOG_CRITICAL(Render_OpenGL, "Unknown output topology {}", static_cast<u32>(topology));
UNREACHABLE();
return "points";
}
}
/// Describes the behaviour of code path of a given entry point and a return point.
enum class ExitMethod {
Undetermined, ///< Internal value. Only occur when analyzing JMP loop.
@ -253,8 +275,9 @@ enum class InternalFlag : u64 {
class GLSLRegisterManager {
public:
GLSLRegisterManager(ShaderWriter& shader, ShaderWriter& declarations,
const Maxwell3D::Regs::ShaderStage& stage, const std::string& suffix)
: shader{shader}, declarations{declarations}, stage{stage}, suffix{suffix} {
const Maxwell3D::Regs::ShaderStage& stage, const std::string& suffix,
const Tegra::Shader::Header& header)
: shader{shader}, declarations{declarations}, stage{stage}, suffix{suffix}, header{header} {
BuildRegisterList();
BuildInputList();
}
@ -358,11 +381,13 @@ public:
* @param reg The destination register to use.
* @param elem The element to use for the operation.
* @param attribute The input attribute to use as the source value.
* @param vertex The register that decides which vertex to read from (used in GS).
*/
void SetRegisterToInputAttibute(const Register& reg, u64 elem, Attribute::Index attribute,
const Tegra::Shader::IpaMode& input_mode) {
const Tegra::Shader::IpaMode& input_mode,
boost::optional<Register> vertex = {}) {
const std::string dest = GetRegisterAsFloat(reg);
const std::string src = GetInputAttribute(attribute, input_mode) + GetSwizzle(elem);
const std::string src = GetInputAttribute(attribute, input_mode, vertex) + GetSwizzle(elem);
shader.AddLine(dest + " = " + src + ';');
}
@ -391,16 +416,29 @@ public:
* are stored as floats, so this may require conversion.
* @param attribute The destination output attribute.
* @param elem The element to use for the operation.
* @param reg The register to use as the source value.
* @param val_reg The register to use as the source value.
* @param buf_reg The register that tells which buffer to write to (used in geometry shaders).
*/
void SetOutputAttributeToRegister(Attribute::Index attribute, u64 elem, const Register& reg) {
void SetOutputAttributeToRegister(Attribute::Index attribute, u64 elem, const Register& val_reg,
const Register& buf_reg) {
const std::string dest = GetOutputAttribute(attribute);
const std::string src = GetRegisterAsFloat(reg);
const std::string src = GetRegisterAsFloat(val_reg);
if (!dest.empty()) {
// Can happen with unknown/unimplemented output attributes, in which case we ignore the
// instruction for now.
shader.AddLine(dest + GetSwizzle(elem) + " = " + src + ';');
if (stage == Maxwell3D::Regs::ShaderStage::Geometry) {
// TODO(Rodrigo): nouveau sets some attributes after setting emitting a geometry
// shader. These instructions use a dirty register as buffer index. To avoid some
// drivers from complaining for the out of boundary writes, guard them.
const std::string buf_index{"min(" + GetRegisterAsInteger(buf_reg) + ", " +
std::to_string(MAX_GEOMETRY_BUFFERS - 1) + ')'};
shader.AddLine("amem[" + buf_index + "][" +
std::to_string(static_cast<u32>(attribute)) + ']' +
GetSwizzle(elem) + " = " + src + ';');
} else {
shader.AddLine(dest + GetSwizzle(elem) + " = " + src + ';');
}
}
}
@ -441,58 +479,18 @@ public:
}
}
/// Add declarations for registers
/// Add declarations.
void GenerateDeclarations(const std::string& suffix) {
for (const auto& reg : regs) {
declarations.AddLine(GLSLRegister::GetTypeString() + ' ' + reg.GetPrefixString() +
std::to_string(reg.GetIndex()) + '_' + suffix + " = 0;");
}
declarations.AddNewLine();
for (u32 ii = 0; ii < static_cast<u64>(InternalFlag::Amount); ii++) {
const InternalFlag code = static_cast<InternalFlag>(ii);
declarations.AddLine("bool " + GetInternalFlag(code) + " = false;");
}
declarations.AddNewLine();
for (const auto element : declr_input_attribute) {
// TODO(bunnei): Use proper number of elements for these
u32 idx =
static_cast<u32>(element.first) - static_cast<u32>(Attribute::Index::Attribute_0);
declarations.AddLine("layout(location = " + std::to_string(idx) + ")" +
GetInputFlags(element.first) + "in vec4 " +
GetInputAttribute(element.first, element.second) + ';');
}
declarations.AddNewLine();
for (const auto& index : declr_output_attribute) {
// TODO(bunnei): Use proper number of elements for these
declarations.AddLine("layout(location = " +
std::to_string(static_cast<u32>(index) -
static_cast<u32>(Attribute::Index::Attribute_0)) +
") out vec4 " + GetOutputAttribute(index) + ';');
}
declarations.AddNewLine();
for (const auto& entry : GetConstBuffersDeclarations()) {
declarations.AddLine("layout(std140) uniform " + entry.GetName());
declarations.AddLine('{');
declarations.AddLine(" vec4 c" + std::to_string(entry.GetIndex()) +
"[MAX_CONSTBUFFER_ELEMENTS];");
declarations.AddLine("};");
declarations.AddNewLine();
}
declarations.AddNewLine();
const auto& samplers = GetSamplers();
for (const auto& sampler : samplers) {
declarations.AddLine("uniform " + sampler.GetTypeString() + ' ' + sampler.GetName() +
';');
}
declarations.AddNewLine();
GenerateRegisters(suffix);
GenerateInternalFlags();
GenerateInputAttrs();
GenerateOutputAttrs();
GenerateConstBuffers();
GenerateSamplers();
GenerateGeometry();
}
/// Returns a list of constant buffer declarations
/// Returns a list of constant buffer declarations.
std::vector<ConstBufferEntry> GetConstBuffersDeclarations() const {
std::vector<ConstBufferEntry> result;
std::copy_if(declr_const_buffers.begin(), declr_const_buffers.end(),
@ -500,7 +498,7 @@ public:
return result;
}
/// Returns a list of samplers used in the shader
/// Returns a list of samplers used in the shader.
const std::vector<SamplerEntry>& GetSamplers() const {
return used_samplers;
}
@ -509,7 +507,7 @@ public:
/// necessary.
std::string AccessSampler(const Sampler& sampler, Tegra::Shader::TextureType type,
bool is_array, bool is_shadow) {
const std::size_t offset = static_cast<std::size_t>(sampler.index.Value());
const auto offset = static_cast<std::size_t>(sampler.index.Value());
// If this sampler has already been used, return the existing mapping.
const auto itr =
@ -530,6 +528,125 @@ public:
}
private:
/// Generates declarations for registers.
void GenerateRegisters(const std::string& suffix) {
for (const auto& reg : regs) {
declarations.AddLine(GLSLRegister::GetTypeString() + ' ' + reg.GetPrefixString() +
std::to_string(reg.GetIndex()) + '_' + suffix + " = 0;");
}
declarations.AddNewLine();
}
/// Generates declarations for internal flags.
void GenerateInternalFlags() {
for (u32 ii = 0; ii < static_cast<u64>(InternalFlag::Amount); ii++) {
const InternalFlag code = static_cast<InternalFlag>(ii);
declarations.AddLine("bool " + GetInternalFlag(code) + " = false;");
}
declarations.AddNewLine();
}
/// Generates declarations for input attributes.
void GenerateInputAttrs() {
if (stage != Maxwell3D::Regs::ShaderStage::Vertex) {
const std::string attr =
stage == Maxwell3D::Regs::ShaderStage::Geometry ? "gs_position[]" : "position";
declarations.AddLine("layout (location = " + std::to_string(POSITION_VARYING_LOCATION) +
") in vec4 " + attr + ';');
}
for (const auto element : declr_input_attribute) {
// TODO(bunnei): Use proper number of elements for these
u32 idx =
static_cast<u32>(element.first) - static_cast<u32>(Attribute::Index::Attribute_0);
ASSERT(idx != POSITION_VARYING_LOCATION);
std::string attr{GetInputAttribute(element.first, element.second)};
if (stage == Maxwell3D::Regs::ShaderStage::Geometry) {
attr = "gs_" + attr + "[]";
}
declarations.AddLine("layout (location = " + std::to_string(idx) + ") " +
GetInputFlags(element.first) + "in vec4 " + attr + ';');
}
declarations.AddNewLine();
}
/// Generates declarations for output attributes.
void GenerateOutputAttrs() {
if (stage != Maxwell3D::Regs::ShaderStage::Fragment) {
declarations.AddLine("layout (location = " + std::to_string(POSITION_VARYING_LOCATION) +
") out vec4 position;");
}
for (const auto& index : declr_output_attribute) {
// TODO(bunnei): Use proper number of elements for these
declarations.AddLine("layout (location = " +
std::to_string(static_cast<u32>(index) -
static_cast<u32>(Attribute::Index::Attribute_0)) +
") out vec4 " + GetOutputAttribute(index) + ';');
}
declarations.AddNewLine();
}
/// Generates declarations for constant buffers.
void GenerateConstBuffers() {
for (const auto& entry : GetConstBuffersDeclarations()) {
declarations.AddLine("layout (std140) uniform " + entry.GetName());
declarations.AddLine('{');
declarations.AddLine(" vec4 c" + std::to_string(entry.GetIndex()) +
"[MAX_CONSTBUFFER_ELEMENTS];");
declarations.AddLine("};");
declarations.AddNewLine();
}
declarations.AddNewLine();
}
/// Generates declarations for samplers.
void GenerateSamplers() {
const auto& samplers = GetSamplers();
for (const auto& sampler : samplers) {
declarations.AddLine("uniform " + sampler.GetTypeString() + ' ' + sampler.GetName() +
';');
}
declarations.AddNewLine();
}
/// Generates declarations used for geometry shaders.
void GenerateGeometry() {
if (stage != Maxwell3D::Regs::ShaderStage::Geometry)
return;
declarations.AddLine(
"layout (" + GetTopologyName(header.common3.output_topology) +
", max_vertices = " + std::to_string(header.common4.max_output_vertices) + ") out;");
declarations.AddNewLine();
declarations.AddLine("vec4 amem[" + std::to_string(MAX_GEOMETRY_BUFFERS) + "][" +
std::to_string(MAX_ATTRIBUTES) + "];");
declarations.AddNewLine();
constexpr char buffer[] = "amem[output_buffer]";
declarations.AddLine("void emit_vertex(uint output_buffer) {");
++declarations.scope;
for (const auto element : declr_output_attribute) {
declarations.AddLine(GetOutputAttribute(element) + " = " + buffer + '[' +
std::to_string(static_cast<u32>(element)) + "];");
}
declarations.AddLine("position = " + std::string(buffer) + '[' +
std::to_string(static_cast<u32>(Attribute::Index::Position)) + "];");
// 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.
declarations.AddLine("position.xy *= viewport_flip.xy;");
declarations.AddLine("gl_Position = position;");
declarations.AddLine("position.w = 1.0;");
declarations.AddLine("EmitVertex();");
--declarations.scope;
declarations.AddLine('}');
declarations.AddNewLine();
}
/// Generates code representing a temporary (GPR) register.
std::string GetRegister(const Register& reg, unsigned elem) {
if (reg == Register::ZeroIndex) {
@ -586,11 +703,19 @@ private:
/// Generates code representing an input attribute register.
std::string GetInputAttribute(Attribute::Index attribute,
const Tegra::Shader::IpaMode& input_mode) {
const Tegra::Shader::IpaMode& input_mode,
boost::optional<Register> vertex = {}) {
auto GeometryPass = [&](const std::string& name) {
if (stage == Maxwell3D::Regs::ShaderStage::Geometry && vertex) {
return "gs_" + name + '[' + GetRegisterAsInteger(vertex.value(), 0, false) + ']';
}
return name;
};
switch (attribute) {
case Attribute::Index::Position:
if (stage != Maxwell3D::Regs::ShaderStage::Fragment) {
return "position";
return GeometryPass("position");
} else {
return "vec4(gl_FragCoord.x, gl_FragCoord.y, gl_FragCoord.z, 1.0)";
}
@ -619,7 +744,7 @@ private:
UNREACHABLE();
}
}
return "input_attribute_" + std::to_string(index);
return GeometryPass("input_attribute_" + std::to_string(index));
}
LOG_CRITICAL(HW_GPU, "Unhandled input attribute: {}", static_cast<u32>(attribute));
@ -672,7 +797,7 @@ private:
return out;
}
/// Generates code representing an output attribute register.
/// Generates code representing the declaration name of an output attribute register.
std::string GetOutputAttribute(Attribute::Index attribute) {
switch (attribute) {
case Attribute::Index::Position:
@ -708,6 +833,7 @@ private:
std::vector<SamplerEntry> used_samplers;
const Maxwell3D::Regs::ShaderStage& stage;
const std::string& suffix;
const Tegra::Shader::Header& header;
};
class GLSLGenerator {
@ -1103,8 +1229,8 @@ private:
return offset + 1;
}
shader.AddLine("// " + std::to_string(offset) + ": " + opcode->GetName() + " (" +
std::to_string(instr.value) + ')');
shader.AddLine(
fmt::format("// {}: {} (0x{:016x})", offset, opcode->GetName(), instr.value));
using Tegra::Shader::Pred;
ASSERT_MSG(instr.pred.full_pred != Pred::NeverExecute,
@ -1826,7 +1952,7 @@ private:
const auto LoadNextElement = [&](u32 reg_offset) {
regs.SetRegisterToInputAttibute(instr.gpr0.Value() + reg_offset, next_element,
static_cast<Attribute::Index>(next_index),
input_mode);
input_mode, instr.gpr39.Value());
// Load the next attribute element into the following register. If the element
// to load goes beyond the vec4 size, load the first element of the next
@ -1890,8 +2016,8 @@ private:
const auto StoreNextElement = [&](u32 reg_offset) {
regs.SetOutputAttributeToRegister(static_cast<Attribute::Index>(next_index),
next_element,
instr.gpr0.Value() + reg_offset);
next_element, instr.gpr0.Value() + reg_offset,
instr.gpr39.Value());
// Load the next attribute element into the following register. If the element
// to load goes beyond the vec4 size, load the first element of the next
@ -2738,6 +2864,52 @@ private:
break;
}
case OpCode::Id::OUT_R: {
ASSERT(instr.gpr20.Value() == Register::ZeroIndex);
ASSERT_MSG(stage == Maxwell3D::Regs::ShaderStage::Geometry,
"OUT is expected to be used in a geometry shader.");
if (instr.out.emit) {
// gpr0 is used to store the next address. Hardware returns a pointer but
// we just return the next index with a cyclic cap.
const std::string current{regs.GetRegisterAsInteger(instr.gpr8, 0, false)};
const std::string next = "((" + current + " + 1" + ") % " +
std::to_string(MAX_GEOMETRY_BUFFERS) + ')';
shader.AddLine("emit_vertex(" + current + ");");
regs.SetRegisterToInteger(instr.gpr0, false, 0, next, 1, 1);
}
if (instr.out.cut) {
shader.AddLine("EndPrimitive();");
}
break;
}
case OpCode::Id::MOV_SYS: {
switch (instr.sys20) {
case Tegra::Shader::SystemVariable::InvocationInfo: {
LOG_WARNING(HW_GPU, "MOV_SYS instruction with InvocationInfo is incomplete");
regs.SetRegisterToInteger(instr.gpr0, false, 0, "0u", 1, 1);
break;
}
default: {
LOG_CRITICAL(HW_GPU, "Unhandled system move: {}",
static_cast<u32>(instr.sys20.Value()));
UNREACHABLE();
}
}
break;
}
case OpCode::Id::ISBERD: {
ASSERT(instr.isberd.o == 0);
ASSERT(instr.isberd.skew == 0);
ASSERT(instr.isberd.shift == Tegra::Shader::IsberdShift::None);
ASSERT(instr.isberd.mode == Tegra::Shader::IsberdMode::None);
ASSERT_MSG(stage == Maxwell3D::Regs::ShaderStage::Geometry,
"ISBERD is expected to be used in a geometry shader.");
LOG_WARNING(HW_GPU, "ISBERD instruction is incomplete");
regs.SetRegisterToFloat(instr.gpr0, 0, regs.GetRegisterAsFloat(instr.gpr8), 1, 1);
break;
}
case OpCode::Id::BRA: {
ASSERT_MSG(instr.bra.constant_buffer == 0,
"BRA with constant buffers are not implemented");
@ -2911,7 +3083,7 @@ private:
ShaderWriter shader;
ShaderWriter declarations;
GLSLRegisterManager regs{shader, declarations, stage, suffix};
GLSLRegisterManager regs{shader, declarations, stage, suffix, header};
// Declarations
std::set<std::string> declr_predicates;

View file

@ -17,7 +17,18 @@ ProgramResult GenerateVertexShader(const ShaderSetup& setup) {
std::string out = "#version 430 core\n";
out += "#extension GL_ARB_separate_shader_objects : enable\n\n";
out += Decompiler::GetCommonDeclarations();
out += "bool exec_vertex();\n";
out += R"(
out gl_PerVertex {
vec4 gl_Position;
};
layout(std140) uniform vs_config {
vec4 viewport_flip;
uvec4 instance_id;
uvec4 flip_stage;
};
)";
if (setup.IsDualProgram()) {
out += "bool exec_vertex_b();\n";
@ -28,19 +39,18 @@ ProgramResult GenerateVertexShader(const ShaderSetup& setup) {
Maxwell3D::Regs::ShaderStage::Vertex, "vertex")
.get_value_or({});
out += program.first;
if (setup.IsDualProgram()) {
ProgramResult program_b =
Decompiler::DecompileProgram(setup.program.code_b, PROGRAM_OFFSET,
Maxwell3D::Regs::ShaderStage::Vertex, "vertex_b")
.get_value_or({});
out += program_b.first;
}
out += R"(
out gl_PerVertex {
vec4 gl_Position;
};
out vec4 position;
layout (std140) uniform vs_config {
vec4 viewport_flip;
uvec4 instance_id;
};
void main() {
position = vec4(0.0, 0.0, 0.0, 0.0);
exec_vertex();
@ -52,27 +62,52 @@ void main() {
out += R"(
// Viewport can be flipped, which is unsupported by glViewport
position.xy *= viewport_flip.xy;
// Check if the flip stage is VertexB
if (flip_stage[0] == 1) {
// Viewport can be flipped, which is unsupported by glViewport
position.xy *= viewport_flip.xy;
}
gl_Position = position;
// TODO(bunnei): This is likely a hack, position.w should be interpolated as 1.0
// For now, this is here to bring order in lieu of proper emulation
position.w = 1.0;
if (flip_stage[0] == 1) {
position.w = 1.0;
}
}
)";
return {out, program.second};
}
ProgramResult GenerateGeometryShader(const ShaderSetup& setup) {
std::string out = "#version 430 core\n";
out += "#extension GL_ARB_separate_shader_objects : enable\n\n";
out += Decompiler::GetCommonDeclarations();
out += "bool exec_geometry();\n";
ProgramResult program =
Decompiler::DecompileProgram(setup.program.code, PROGRAM_OFFSET,
Maxwell3D::Regs::ShaderStage::Geometry, "geometry")
.get_value_or({});
out += R"(
out gl_PerVertex {
vec4 gl_Position;
};
layout (std140) uniform gs_config {
vec4 viewport_flip;
uvec4 instance_id;
uvec4 flip_stage;
};
void main() {
exec_geometry();
}
)";
out += program.first;
if (setup.IsDualProgram()) {
ProgramResult program_b =
Decompiler::DecompileProgram(setup.program.code_b, PROGRAM_OFFSET,
Maxwell3D::Regs::ShaderStage::Vertex, "vertex_b")
.get_value_or({});
out += program_b.first;
}
return {out, program.second};
}
@ -87,7 +122,6 @@ ProgramResult GenerateFragmentShader(const ShaderSetup& setup) {
Maxwell3D::Regs::ShaderStage::Fragment, "fragment")
.get_value_or({});
out += R"(
in vec4 position;
layout(location = 0) out vec4 FragColor0;
layout(location = 1) out vec4 FragColor1;
layout(location = 2) out vec4 FragColor2;
@ -100,6 +134,7 @@ layout(location = 7) out vec4 FragColor7;
layout (std140) uniform fs_config {
vec4 viewport_flip;
uvec4 instance_id;
uvec4 flip_stage;
};
void main() {
@ -110,5 +145,4 @@ void main() {
out += program.first;
return {out, program.second};
}
} // namespace OpenGL::GLShader

View file

@ -195,6 +195,12 @@ private:
*/
ProgramResult GenerateVertexShader(const ShaderSetup& setup);
/**
* Generates the GLSL geometry shader program source code for the given GS program
* @returns String of the shader source code
*/
ProgramResult GenerateGeometryShader(const ShaderSetup& setup);
/**
* Generates the GLSL fragment shader program source code for the given FS program
* @returns String of the shader source code

View file

@ -18,6 +18,14 @@ void MaxwellUniformData::SetFromRegs(const Maxwell3D::State::ShaderStageInfo& sh
// We only assign the instance to the first component of the vector, the rest is just padding.
instance_id[0] = state.current_instance;
// Assign in which stage the position has to be flipped
// (the last stage before the fragment shader).
if (gpu.regs.shader_config[static_cast<u32>(Maxwell3D::Regs::ShaderProgram::Geometry)].enable) {
flip_stage[0] = static_cast<u32>(Maxwell3D::Regs::ShaderProgram::Geometry);
} else {
flip_stage[0] = static_cast<u32>(Maxwell3D::Regs::ShaderProgram::VertexB);
}
}
} // namespace OpenGL::GLShader

View file

@ -21,8 +21,9 @@ struct MaxwellUniformData {
void SetFromRegs(const Maxwell3D::State::ShaderStageInfo& shader_stage);
alignas(16) GLvec4 viewport_flip;
alignas(16) GLuvec4 instance_id;
alignas(16) GLuvec4 flip_stage;
};
static_assert(sizeof(MaxwellUniformData) == 32, "MaxwellUniformData structure size is incorrect");
static_assert(sizeof(MaxwellUniformData) == 48, "MaxwellUniformData structure size is incorrect");
static_assert(sizeof(MaxwellUniformData) < 16384,
"MaxwellUniformData structure must be less than 16kb as per the OpenGL spec");
@ -36,6 +37,10 @@ public:
vs = program;
}
void UseProgrammableGeometryShader(GLuint program) {
gs = program;
}
void UseProgrammableFragmentShader(GLuint program) {
fs = program;
}