gl_rasterizer: Emulate viewport flipping with ARB_clip_control
Emulates negative y viewports with ARB_clip_control. This allows us to more easily emulated pipelines with tessellation and/or geometry shader stages. It also avoids corrupting games with transform feedbacks and negative viewports (gl_Position.y was being modified).
This commit is contained in:
parent
c414ebaa9c
commit
f019817f8f
|
@ -257,10 +257,8 @@ void RasterizerOpenGL::SetupShaders(GLenum primitive_mode) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
const std::size_t stage{index == 0 ? 0 : index - 1}; // Stage indices are 0 - 5
|
|
||||||
|
|
||||||
GLShader::MaxwellUniformData ubo{};
|
GLShader::MaxwellUniformData ubo{};
|
||||||
ubo.SetFromRegs(gpu, stage);
|
ubo.SetFromRegs(gpu);
|
||||||
const auto [buffer, offset] =
|
const auto [buffer, offset] =
|
||||||
buffer_cache.UploadHostMemory(&ubo, sizeof(ubo), device.GetUniformBufferAlignment());
|
buffer_cache.UploadHostMemory(&ubo, sizeof(ubo), device.GetUniformBufferAlignment());
|
||||||
|
|
||||||
|
@ -269,10 +267,11 @@ void RasterizerOpenGL::SetupShaders(GLenum primitive_mode) {
|
||||||
|
|
||||||
Shader shader{shader_cache.GetStageProgram(program)};
|
Shader shader{shader_cache.GetStageProgram(program)};
|
||||||
|
|
||||||
const auto stage_enum = static_cast<Maxwell::ShaderStage>(stage);
|
// Stage indices are 0 - 5
|
||||||
SetupDrawConstBuffers(stage_enum, shader);
|
const auto stage = static_cast<Maxwell::ShaderStage>(index == 0 ? 0 : index - 1);
|
||||||
SetupDrawGlobalMemory(stage_enum, shader);
|
SetupDrawConstBuffers(stage, shader);
|
||||||
const auto texture_buffer_usage{SetupDrawTextures(stage_enum, shader, base_bindings)};
|
SetupDrawGlobalMemory(stage, shader);
|
||||||
|
const auto texture_buffer_usage{SetupDrawTextures(stage, shader, base_bindings)};
|
||||||
|
|
||||||
const ProgramVariant variant{base_bindings, primitive_mode, texture_buffer_usage};
|
const ProgramVariant variant{base_bindings, primitive_mode, texture_buffer_usage};
|
||||||
const auto [program_handle, next_bindings] = shader->GetProgramHandle(variant);
|
const auto [program_handle, next_bindings] = shader->GetProgramHandle(variant);
|
||||||
|
@ -1055,6 +1054,15 @@ void RasterizerOpenGL::SyncViewport(OpenGLState& current_state) {
|
||||||
}
|
}
|
||||||
state.depth_clamp.far_plane = regs.view_volume_clip_control.depth_clamp_far != 0;
|
state.depth_clamp.far_plane = regs.view_volume_clip_control.depth_clamp_far != 0;
|
||||||
state.depth_clamp.near_plane = regs.view_volume_clip_control.depth_clamp_near != 0;
|
state.depth_clamp.near_plane = regs.view_volume_clip_control.depth_clamp_near != 0;
|
||||||
|
|
||||||
|
bool flip_y = false;
|
||||||
|
if (regs.viewport_transform[0].scale_y < 0.0) {
|
||||||
|
flip_y = !flip_y;
|
||||||
|
}
|
||||||
|
if (regs.screen_y_control.y_negate != 0) {
|
||||||
|
flip_y = !flip_y;
|
||||||
|
}
|
||||||
|
state.clip_control.origin = flip_y ? GL_UPPER_LEFT : GL_LOWER_LEFT;
|
||||||
}
|
}
|
||||||
|
|
||||||
void RasterizerOpenGL::SyncClipEnabled(
|
void RasterizerOpenGL::SyncClipEnabled(
|
||||||
|
@ -1077,26 +1085,24 @@ void RasterizerOpenGL::SyncClipCoef() {
|
||||||
}
|
}
|
||||||
|
|
||||||
void RasterizerOpenGL::SyncCullMode() {
|
void RasterizerOpenGL::SyncCullMode() {
|
||||||
auto& maxwell3d = system.GPU().Maxwell3D();
|
const auto& regs = system.GPU().Maxwell3D().regs;
|
||||||
|
|
||||||
const auto& regs = maxwell3d.regs;
|
|
||||||
|
|
||||||
state.cull.enabled = regs.cull.enabled != 0;
|
state.cull.enabled = regs.cull.enabled != 0;
|
||||||
if (state.cull.enabled) {
|
if (state.cull.enabled) {
|
||||||
state.cull.front_face = MaxwellToGL::FrontFace(regs.cull.front_face);
|
|
||||||
state.cull.mode = MaxwellToGL::CullFace(regs.cull.cull_face);
|
state.cull.mode = MaxwellToGL::CullFace(regs.cull.cull_face);
|
||||||
|
}
|
||||||
|
|
||||||
const bool flip_triangles{regs.screen_y_control.triangle_rast_flip == 0 ||
|
state.cull.front_face = MaxwellToGL::FrontFace(regs.cull.front_face);
|
||||||
regs.viewport_transform[0].scale_y < 0.0f};
|
|
||||||
|
|
||||||
// If the GPU is configured to flip the rasterized triangles, then we need to flip the
|
// If the GPU is configured to flip the rasterized triangles, then we need to flip the
|
||||||
// notion of front and back. Note: We flip the triangles when the value of the register is 0
|
// notion of front and back.
|
||||||
// because OpenGL already does it for us.
|
const bool flip_triangles{regs.screen_y_control.triangle_rast_flip != 0 &&
|
||||||
if (flip_triangles) {
|
regs.viewport_transform[0].scale_y > 0.0f};
|
||||||
if (state.cull.front_face == GL_CCW)
|
if (flip_triangles) {
|
||||||
state.cull.front_face = GL_CW;
|
if (state.cull.front_face == GL_CCW) {
|
||||||
else if (state.cull.front_face == GL_CW)
|
state.cull.front_face = GL_CW;
|
||||||
state.cull.front_face = GL_CCW;
|
} else if (state.cull.front_face == GL_CW) {
|
||||||
|
state.cull.front_face = GL_CCW;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1872,10 +1872,6 @@ private:
|
||||||
Expression EmitVertex(Operation operation) {
|
Expression EmitVertex(Operation operation) {
|
||||||
ASSERT_MSG(stage == ProgramType::Geometry,
|
ASSERT_MSG(stage == ProgramType::Geometry,
|
||||||
"EmitVertex is expected to be used in a geometry shader.");
|
"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("gl_Position.xy *= viewport_flip.xy;");
|
|
||||||
code.AddLine("EmitVertex();");
|
code.AddLine("EmitVertex();");
|
||||||
return {};
|
return {};
|
||||||
}
|
}
|
||||||
|
@ -1883,14 +1879,12 @@ private:
|
||||||
Expression EndPrimitive(Operation operation) {
|
Expression EndPrimitive(Operation operation) {
|
||||||
ASSERT_MSG(stage == ProgramType::Geometry,
|
ASSERT_MSG(stage == ProgramType::Geometry,
|
||||||
"EndPrimitive is expected to be used in a geometry shader.");
|
"EndPrimitive is expected to be used in a geometry shader.");
|
||||||
|
|
||||||
code.AddLine("EndPrimitive();");
|
code.AddLine("EndPrimitive();");
|
||||||
return {};
|
return {};
|
||||||
}
|
}
|
||||||
|
|
||||||
Expression YNegate(Operation operation) {
|
Expression YNegate(Operation operation) {
|
||||||
// Config pack's third value is Y_NEGATE's state.
|
return {"y_negate", Type::Float};
|
||||||
return {"config_pack[2]", Type::Uint};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
template <u32 element>
|
template <u32 element>
|
||||||
|
|
|
@ -20,8 +20,7 @@ std::string GenerateVertexShader(const Device& device, const ShaderIR& ir, const
|
||||||
std::string out = GetCommonDeclarations();
|
std::string out = GetCommonDeclarations();
|
||||||
out += R"(
|
out += R"(
|
||||||
layout (std140, binding = EMULATION_UBO_BINDING) uniform vs_config {
|
layout (std140, binding = EMULATION_UBO_BINDING) uniform vs_config {
|
||||||
vec4 viewport_flip;
|
float y_direction;
|
||||||
uvec4 config_pack; // instance_id, flip_stage, y_direction, padding
|
|
||||||
};
|
};
|
||||||
|
|
||||||
)";
|
)";
|
||||||
|
@ -35,23 +34,10 @@ layout (std140, binding = EMULATION_UBO_BINDING) uniform vs_config {
|
||||||
void main() {
|
void main() {
|
||||||
execute_vertex();
|
execute_vertex();
|
||||||
)";
|
)";
|
||||||
|
|
||||||
if (ir_b) {
|
if (ir_b) {
|
||||||
out += " execute_vertex_b();";
|
out += " execute_vertex_b();";
|
||||||
}
|
}
|
||||||
|
out += "}\n";
|
||||||
out += R"(
|
|
||||||
|
|
||||||
// Set Position Y direction
|
|
||||||
gl_Position.y *= utof(config_pack[2]);
|
|
||||||
// Check if the flip stage is VertexB
|
|
||||||
// Config pack's second value is flip_stage
|
|
||||||
if (config_pack[1] == 1) {
|
|
||||||
// Viewport can be flipped, which is unsupported by glViewport
|
|
||||||
gl_Position.xy *= viewport_flip.xy;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
)";
|
|
||||||
return out;
|
return out;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -59,8 +45,7 @@ std::string GenerateGeometryShader(const Device& device, const ShaderIR& ir) {
|
||||||
std::string out = GetCommonDeclarations();
|
std::string out = GetCommonDeclarations();
|
||||||
out += R"(
|
out += R"(
|
||||||
layout (std140, binding = EMULATION_UBO_BINDING) uniform gs_config {
|
layout (std140, binding = EMULATION_UBO_BINDING) uniform gs_config {
|
||||||
vec4 viewport_flip;
|
float y_direction;
|
||||||
uvec4 config_pack; // instance_id, flip_stage, y_direction, padding
|
|
||||||
};
|
};
|
||||||
|
|
||||||
)";
|
)";
|
||||||
|
@ -87,8 +72,7 @@ layout (location = 6) out vec4 FragColor6;
|
||||||
layout (location = 7) out vec4 FragColor7;
|
layout (location = 7) out vec4 FragColor7;
|
||||||
|
|
||||||
layout (std140, binding = EMULATION_UBO_BINDING) uniform fs_config {
|
layout (std140, binding = EMULATION_UBO_BINDING) uniform fs_config {
|
||||||
vec4 viewport_flip;
|
float y_direction;
|
||||||
uvec4 config_pack; // instance_id, flip_stage, y_direction, padding
|
|
||||||
};
|
};
|
||||||
|
|
||||||
)";
|
)";
|
||||||
|
|
|
@ -40,27 +40,12 @@ void ProgramManager::UpdatePipeline() {
|
||||||
old_state = current_state;
|
old_state = current_state;
|
||||||
}
|
}
|
||||||
|
|
||||||
void MaxwellUniformData::SetFromRegs(const Maxwell3D& maxwell, std::size_t shader_stage) {
|
void MaxwellUniformData::SetFromRegs(const Maxwell3D& maxwell) {
|
||||||
const auto& regs = maxwell.regs;
|
const auto& regs = maxwell.regs;
|
||||||
const auto& state = maxwell.state;
|
const auto& state = maxwell.state;
|
||||||
|
|
||||||
// TODO(bunnei): Support more than one viewport
|
|
||||||
viewport_flip[0] = regs.viewport_transform[0].scale_x < 0.0 ? -1.0f : 1.0f;
|
|
||||||
viewport_flip[1] = regs.viewport_transform[0].scale_y < 0.0 ? -1.0f : 1.0f;
|
|
||||||
|
|
||||||
instance_id = state.current_instance;
|
|
||||||
|
|
||||||
// Assign in which stage the position has to be flipped
|
|
||||||
// (the last stage before the fragment shader).
|
|
||||||
constexpr u32 geometry_index = static_cast<u32>(Maxwell3D::Regs::ShaderProgram::Geometry);
|
|
||||||
if (maxwell.regs.shader_config[geometry_index].enable) {
|
|
||||||
flip_stage = geometry_index;
|
|
||||||
} else {
|
|
||||||
flip_stage = static_cast<u32>(Maxwell3D::Regs::ShaderProgram::VertexB);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Y_NEGATE controls what value S2R returns for the Y_DIRECTION system value.
|
// Y_NEGATE controls what value S2R returns for the Y_DIRECTION system value.
|
||||||
y_direction = regs.screen_y_control.y_negate == 0 ? 1.f : -1.f;
|
y_direction = regs.screen_y_control.y_negate == 0 ? 1.0f : -1.0f;
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace OpenGL::GLShader
|
} // namespace OpenGL::GLShader
|
||||||
|
|
|
@ -18,17 +18,12 @@ namespace OpenGL::GLShader {
|
||||||
/// @note Always keep a vec4 at the end. The GL spec is not clear whether the alignment at
|
/// @note Always keep a vec4 at the end. The GL spec is not clear whether the alignment at
|
||||||
/// the end of a uniform block is included in UNIFORM_BLOCK_DATA_SIZE or not.
|
/// the end of a uniform block is included in UNIFORM_BLOCK_DATA_SIZE or not.
|
||||||
/// Not following that rule will cause problems on some AMD drivers.
|
/// Not following that rule will cause problems on some AMD drivers.
|
||||||
struct MaxwellUniformData {
|
struct alignas(16) MaxwellUniformData {
|
||||||
void SetFromRegs(const Tegra::Engines::Maxwell3D& maxwell, std::size_t shader_stage);
|
void SetFromRegs(const Tegra::Engines::Maxwell3D& maxwell);
|
||||||
|
|
||||||
alignas(16) GLvec4 viewport_flip;
|
GLfloat y_direction;
|
||||||
struct alignas(16) {
|
|
||||||
GLuint instance_id;
|
|
||||||
GLuint flip_stage;
|
|
||||||
GLfloat y_direction;
|
|
||||||
};
|
|
||||||
};
|
};
|
||||||
static_assert(sizeof(MaxwellUniformData) == 32, "MaxwellUniformData structure size is incorrect");
|
static_assert(sizeof(MaxwellUniformData) == 16, "MaxwellUniformData structure size is incorrect");
|
||||||
static_assert(sizeof(MaxwellUniformData) < 16384,
|
static_assert(sizeof(MaxwellUniformData) < 16384,
|
||||||
"MaxwellUniformData structure must be less than 16kb as per the OpenGL spec");
|
"MaxwellUniformData structure must be less than 16kb as per the OpenGL spec");
|
||||||
|
|
||||||
|
|
|
@ -410,6 +410,12 @@ void OpenGLState::ApplyAlphaTest() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void OpenGLState::ApplyClipControl() {
|
||||||
|
if (UpdateValue(cur_state.clip_control.origin, clip_control.origin)) {
|
||||||
|
glClipControl(clip_control.origin, GL_NEGATIVE_ONE_TO_ONE);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void OpenGLState::ApplyTextures() {
|
void OpenGLState::ApplyTextures() {
|
||||||
if (const auto update = UpdateArray(cur_state.textures, textures)) {
|
if (const auto update = UpdateArray(cur_state.textures, textures)) {
|
||||||
glBindTextures(update->first, update->second, textures.data() + update->first);
|
glBindTextures(update->first, update->second, textures.data() + update->first);
|
||||||
|
@ -453,6 +459,7 @@ void OpenGLState::Apply() {
|
||||||
ApplyImages();
|
ApplyImages();
|
||||||
ApplyPolygonOffset();
|
ApplyPolygonOffset();
|
||||||
ApplyAlphaTest();
|
ApplyAlphaTest();
|
||||||
|
ApplyClipControl();
|
||||||
}
|
}
|
||||||
|
|
||||||
void OpenGLState::EmulateViewportWithScissor() {
|
void OpenGLState::EmulateViewportWithScissor() {
|
||||||
|
|
|
@ -146,6 +146,10 @@ public:
|
||||||
|
|
||||||
std::array<bool, 8> clip_distance = {}; // GL_CLIP_DISTANCE
|
std::array<bool, 8> clip_distance = {}; // GL_CLIP_DISTANCE
|
||||||
|
|
||||||
|
struct {
|
||||||
|
GLenum origin = GL_LOWER_LEFT;
|
||||||
|
} clip_control;
|
||||||
|
|
||||||
OpenGLState();
|
OpenGLState();
|
||||||
|
|
||||||
/// Get the currently active OpenGL state
|
/// Get the currently active OpenGL state
|
||||||
|
@ -182,6 +186,7 @@ public:
|
||||||
void ApplyDepthClamp();
|
void ApplyDepthClamp();
|
||||||
void ApplyPolygonOffset();
|
void ApplyPolygonOffset();
|
||||||
void ApplyAlphaTest();
|
void ApplyAlphaTest();
|
||||||
|
void ApplyClipControl();
|
||||||
|
|
||||||
/// Resets any references to the given resource
|
/// Resets any references to the given resource
|
||||||
OpenGLState& UnbindTexture(GLuint handle);
|
OpenGLState& UnbindTexture(GLuint handle);
|
||||||
|
|
|
@ -817,6 +817,9 @@ QStringList GMainWindow::GetUnsupportedGLExtensions() {
|
||||||
if (!GLAD_GL_ARB_multi_bind) {
|
if (!GLAD_GL_ARB_multi_bind) {
|
||||||
unsupported_ext.append(QStringLiteral("ARB_multi_bind"));
|
unsupported_ext.append(QStringLiteral("ARB_multi_bind"));
|
||||||
}
|
}
|
||||||
|
if (!GLAD_GL_ARB_clip_control) {
|
||||||
|
unsupported_ext.append(QStringLiteral("ARB_clip_control"));
|
||||||
|
}
|
||||||
|
|
||||||
// Extensions required to support some texture formats.
|
// Extensions required to support some texture formats.
|
||||||
if (!GLAD_GL_EXT_texture_compression_s3tc) {
|
if (!GLAD_GL_EXT_texture_compression_s3tc) {
|
||||||
|
|
|
@ -62,6 +62,8 @@ bool EmuWindow_SDL2_GL::SupportsRequiredGLExtensions() {
|
||||||
unsupported_ext.push_back("ARB_texture_mirror_clamp_to_edge");
|
unsupported_ext.push_back("ARB_texture_mirror_clamp_to_edge");
|
||||||
if (!GLAD_GL_ARB_multi_bind)
|
if (!GLAD_GL_ARB_multi_bind)
|
||||||
unsupported_ext.push_back("ARB_multi_bind");
|
unsupported_ext.push_back("ARB_multi_bind");
|
||||||
|
if (!GLAD_GL_ARB_clip_control)
|
||||||
|
unsupported_ext.push_back("ARB_clip_control");
|
||||||
|
|
||||||
// Extensions required to support some texture formats.
|
// Extensions required to support some texture formats.
|
||||||
if (!GLAD_GL_EXT_texture_compression_s3tc)
|
if (!GLAD_GL_EXT_texture_compression_s3tc)
|
||||||
|
|
Reference in New Issue