summaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/common/threadsafe_queue.h11
-rw-r--r--src/video_core/CMakeLists.txt2
-rw-r--r--src/video_core/engines/maxwell_3d.cpp6
-rw-r--r--src/video_core/engines/maxwell_3d.h27
-rw-r--r--src/video_core/engines/shader_bytecode.h2
-rw-r--r--src/video_core/renderer_opengl/gl_rasterizer.cpp7
-rw-r--r--src/video_core/renderer_opengl/gl_rasterizer.h3
-rw-r--r--src/video_core/renderer_opengl/gl_shader_cache.cpp33
-rw-r--r--src/video_core/renderer_opengl/gl_shader_decompiler.cpp1
-rw-r--r--src/video_core/renderer_opengl/gl_state.cpp5
-rw-r--r--src/video_core/renderer_opengl/gl_state.h4
-rw-r--r--src/video_core/renderer_opengl/renderer_opengl.cpp224
-rw-r--r--src/video_core/renderer_opengl/renderer_opengl.h22
-rw-r--r--src/video_core/renderer_vulkan/fixed_pipeline_state.cpp296
-rw-r--r--src/video_core/renderer_vulkan/fixed_pipeline_state.h282
-rw-r--r--src/video_core/renderer_vulkan/shaders/blit.frag24
-rw-r--r--src/video_core/renderer_vulkan/shaders/blit.vert28
-rw-r--r--src/video_core/renderer_vulkan/shaders/quad_array.comp37
-rw-r--r--src/video_core/renderer_vulkan/shaders/uint8.comp33
-rw-r--r--src/video_core/renderer_vulkan/vk_device.cpp22
-rw-r--r--src/video_core/renderer_vulkan/vk_device.h9
-rw-r--r--src/video_core/renderer_vulkan/vk_resource_manager.cpp36
-rw-r--r--src/video_core/renderer_vulkan/vk_resource_manager.h16
-rw-r--r--src/video_core/renderer_vulkan/vk_scheduler.cpp150
-rw-r--r--src/video_core/renderer_vulkan/vk_scheduler.h198
-rw-r--r--src/video_core/renderer_vulkan/vk_shader_decompiler.cpp107
-rw-r--r--src/video_core/renderer_vulkan/vk_shader_decompiler.h4
-rw-r--r--src/video_core/shader/decode/conversion.cpp15
-rw-r--r--src/video_core/shader/decode/memory.cpp38
-rw-r--r--src/video_core/shader/decode/texture.cpp13
-rw-r--r--src/video_core/texture_cache/surface_params.cpp38
-rw-r--r--src/video_core/texture_cache/surface_params.h14
-rw-r--r--src/video_core/texture_cache/texture_cache.h89
-rw-r--r--src/video_core/textures/decoders.h4
34 files changed, 1525 insertions, 275 deletions
diff --git a/src/common/threadsafe_queue.h b/src/common/threadsafe_queue.h
index e714ba5b3..8268bbd5c 100644
--- a/src/common/threadsafe_queue.h
+++ b/src/common/threadsafe_queue.h
@@ -46,9 +46,16 @@ public:
ElementPtr* new_ptr = new ElementPtr();
write_ptr->next.store(new_ptr, std::memory_order_release);
write_ptr = new_ptr;
- cv.notify_one();
- ++size;
+ const size_t previous_size{size++};
+
+ // Acquire the mutex and then immediately release it as a fence.
+ // TODO(bunnei): This can be replaced with C++20 waitable atomics when properly supported.
+ // See discussion on https://github.com/yuzu-emu/yuzu/pull/3173 for details.
+ if (previous_size == 0) {
+ std::lock_guard lock{cv_mutex};
+ }
+ cv.notify_one();
}
void Pop() {
diff --git a/src/video_core/CMakeLists.txt b/src/video_core/CMakeLists.txt
index 3b20c7d34..e615b238e 100644
--- a/src/video_core/CMakeLists.txt
+++ b/src/video_core/CMakeLists.txt
@@ -151,6 +151,8 @@ add_library(video_core STATIC
if (ENABLE_VULKAN)
target_sources(video_core PRIVATE
renderer_vulkan/declarations.h
+ renderer_vulkan/fixed_pipeline_state.cpp
+ renderer_vulkan/fixed_pipeline_state.h
renderer_vulkan/maxwell_to_vk.cpp
renderer_vulkan/maxwell_to_vk.h
renderer_vulkan/vk_buffer_cache.cpp
diff --git a/src/video_core/engines/maxwell_3d.cpp b/src/video_core/engines/maxwell_3d.cpp
index 15a7a9d6a..e1cb8b0b0 100644
--- a/src/video_core/engines/maxwell_3d.cpp
+++ b/src/video_core/engines/maxwell_3d.cpp
@@ -88,11 +88,11 @@ void Maxwell3D::InitializeRegisterDefaults() {
color_mask.A.Assign(1);
}
- // Commercial games seem to assume this value is enabled and nouveau sets this value manually.
+ // NVN games expect these values to be enabled at boot
+ regs.rasterize_enable = 1;
regs.rt_separate_frag_data = 1;
-
- // Some games (like Super Mario Odyssey) assume that SRGB is enabled.
regs.framebuffer_srgb = 1;
+
mme_inline[MAXWELL3D_REG_INDEX(draw.vertex_end_gl)] = true;
mme_inline[MAXWELL3D_REG_INDEX(draw.vertex_begin_gl)] = true;
mme_inline[MAXWELL3D_REG_INDEX(vertex_buffer.count)] = true;
diff --git a/src/video_core/engines/maxwell_3d.h b/src/video_core/engines/maxwell_3d.h
index dbb4e597f..a35e7a195 100644
--- a/src/video_core/engines/maxwell_3d.h
+++ b/src/video_core/engines/maxwell_3d.h
@@ -657,7 +657,11 @@ public:
std::array<f32, 4> tess_level_outer;
std::array<f32, 2> tess_level_inner;
- INSERT_UNION_PADDING_WORDS(0x102);
+ INSERT_UNION_PADDING_WORDS(0x10);
+
+ u32 rasterize_enable;
+
+ INSERT_UNION_PADDING_WORDS(0xF1);
u32 tfb_enabled;
@@ -707,13 +711,15 @@ public:
u32 color_mask_common;
- INSERT_UNION_PADDING_WORDS(0x6);
-
- u32 rt_separate_frag_data;
+ INSERT_UNION_PADDING_WORDS(0x2);
f32 depth_bounds[2];
- INSERT_UNION_PADDING_WORDS(0xA);
+ INSERT_UNION_PADDING_WORDS(0x2);
+
+ u32 rt_separate_frag_data;
+
+ INSERT_UNION_PADDING_WORDS(0xC);
struct {
u32 address_high;
@@ -1030,7 +1036,12 @@ public:
BitField<4, 1, u32> depth_clamp_far;
} view_volume_clip_control;
- INSERT_UNION_PADDING_WORDS(0x21);
+ INSERT_UNION_PADDING_WORDS(0x1F);
+
+ u32 depth_bounds_enable;
+
+ INSERT_UNION_PADDING_WORDS(1);
+
struct {
u32 enable;
LogicOperation operation;
@@ -1420,6 +1431,7 @@ ASSERT_REG_POSITION(sync_info, 0xB2);
ASSERT_REG_POSITION(tess_mode, 0xC8);
ASSERT_REG_POSITION(tess_level_outer, 0xC9);
ASSERT_REG_POSITION(tess_level_inner, 0xCD);
+ASSERT_REG_POSITION(rasterize_enable, 0xDF);
ASSERT_REG_POSITION(tfb_enabled, 0x1D1);
ASSERT_REG_POSITION(rt, 0x200);
ASSERT_REG_POSITION(viewport_transform, 0x280);
@@ -1439,7 +1451,7 @@ ASSERT_REG_POSITION(stencil_back_func_mask, 0x3D6);
ASSERT_REG_POSITION(stencil_back_mask, 0x3D7);
ASSERT_REG_POSITION(color_mask_common, 0x3E4);
ASSERT_REG_POSITION(rt_separate_frag_data, 0x3EB);
-ASSERT_REG_POSITION(depth_bounds, 0x3EC);
+ASSERT_REG_POSITION(depth_bounds, 0x3E7);
ASSERT_REG_POSITION(zeta, 0x3F8);
ASSERT_REG_POSITION(clear_flags, 0x43E);
ASSERT_REG_POSITION(vertex_attrib_format, 0x458);
@@ -1495,6 +1507,7 @@ ASSERT_REG_POSITION(cull, 0x646);
ASSERT_REG_POSITION(pixel_center_integer, 0x649);
ASSERT_REG_POSITION(viewport_transform_enabled, 0x64B);
ASSERT_REG_POSITION(view_volume_clip_control, 0x64F);
+ASSERT_REG_POSITION(depth_bounds_enable, 0x66F);
ASSERT_REG_POSITION(logic_op, 0x671);
ASSERT_REG_POSITION(clear_buffers, 0x674);
ASSERT_REG_POSITION(color_mask, 0x680);
diff --git a/src/video_core/engines/shader_bytecode.h b/src/video_core/engines/shader_bytecode.h
index 1cb0ac0c2..412ca5551 100644
--- a/src/video_core/engines/shader_bytecode.h
+++ b/src/video_core/engines/shader_bytecode.h
@@ -1973,7 +1973,7 @@ private:
INST("1101-01---------", Id::TLDS, Type::Texture, "TLDS"),
INST("110010----111---", Id::TLD4, Type::Texture, "TLD4"),
INST("1101111011111---", Id::TLD4_B, Type::Texture, "TLD4_B"),
- INST("11011111--00----", Id::TLD4S, Type::Texture, "TLD4S"),
+ INST("11011111-0------", Id::TLD4S, Type::Texture, "TLD4S"),
INST("110111110110----", Id::TMML_B, Type::Texture, "TMML_B"),
INST("1101111101011---", Id::TMML, Type::Texture, "TMML"),
INST("11011110011110--", Id::TXD_B, Type::Texture, "TXD_B"),
diff --git a/src/video_core/renderer_opengl/gl_rasterizer.cpp b/src/video_core/renderer_opengl/gl_rasterizer.cpp
index f20967d85..dbb08dd80 100644
--- a/src/video_core/renderer_opengl/gl_rasterizer.cpp
+++ b/src/video_core/renderer_opengl/gl_rasterizer.cpp
@@ -514,6 +514,7 @@ void RasterizerOpenGL::Clear() {
ConfigureClearFramebuffer(clear_state, use_color, use_depth, use_stencil);
SyncViewport(clear_state);
+ SyncRasterizeEnable(clear_state);
if (regs.clear_flags.scissor) {
SyncScissorTest(clear_state);
}
@@ -541,6 +542,7 @@ void RasterizerOpenGL::Clear() {
void RasterizerOpenGL::DrawPrelude() {
auto& gpu = system.GPU().Maxwell3D();
+ SyncRasterizeEnable(state);
SyncColorMask();
SyncFragmentColorClampState();
SyncMultiSampleState();
@@ -1133,6 +1135,11 @@ void RasterizerOpenGL::SyncStencilTestState() {
}
}
+void RasterizerOpenGL::SyncRasterizeEnable(OpenGLState& current_state) {
+ const auto& regs = system.GPU().Maxwell3D().regs;
+ current_state.rasterizer_discard = regs.rasterize_enable == 0;
+}
+
void RasterizerOpenGL::SyncColorMask() {
auto& maxwell3d = system.GPU().Maxwell3D();
if (!maxwell3d.dirty.color_mask) {
diff --git a/src/video_core/renderer_opengl/gl_rasterizer.h b/src/video_core/renderer_opengl/gl_rasterizer.h
index 04c1ca551..6a27cf497 100644
--- a/src/video_core/renderer_opengl/gl_rasterizer.h
+++ b/src/video_core/renderer_opengl/gl_rasterizer.h
@@ -168,6 +168,9 @@ private:
/// Syncs the point state to match the guest state
void SyncPointState();
+ /// Syncs the rasterizer enable state to match the guest state
+ void SyncRasterizeEnable(OpenGLState& current_state);
+
/// Syncs Color Mask
void SyncColorMask();
diff --git a/src/video_core/renderer_opengl/gl_shader_cache.cpp b/src/video_core/renderer_opengl/gl_shader_cache.cpp
index 270a9dc2b..de742d11c 100644
--- a/src/video_core/renderer_opengl/gl_shader_cache.cpp
+++ b/src/video_core/renderer_opengl/gl_shader_cache.cpp
@@ -112,25 +112,25 @@ constexpr GLenum GetGLShaderType(ShaderType shader_type) {
}
/// Describes primitive behavior on geometry shaders
-constexpr std::tuple<const char*, const char*, u32> GetPrimitiveDescription(GLenum primitive_mode) {
+constexpr std::pair<const char*, u32> GetPrimitiveDescription(GLenum primitive_mode) {
switch (primitive_mode) {
case GL_POINTS:
- return {"points", "Points", 1};
+ return {"points", 1};
case GL_LINES:
case GL_LINE_STRIP:
- return {"lines", "Lines", 2};
+ return {"lines", 2};
case GL_LINES_ADJACENCY:
case GL_LINE_STRIP_ADJACENCY:
- return {"lines_adjacency", "LinesAdj", 4};
+ return {"lines_adjacency", 4};
case GL_TRIANGLES:
case GL_TRIANGLE_STRIP:
case GL_TRIANGLE_FAN:
- return {"triangles", "Triangles", 3};
+ return {"triangles", 3};
case GL_TRIANGLES_ADJACENCY:
case GL_TRIANGLE_STRIP_ADJACENCY:
- return {"triangles_adjacency", "TrianglesAdj", 6};
+ return {"triangles_adjacency", 6};
default:
- return {"points", "Invalid", 1};
+ return {"points", 1};
}
}
@@ -264,30 +264,25 @@ CachedProgram BuildShader(const Device& device, u64 unique_identifier, ShaderTyp
"#extension GL_NV_shader_thread_group : require\n"
"#extension GL_NV_shader_thread_shuffle : require\n";
}
- source += '\n';
if (shader_type == ShaderType::Geometry) {
- const auto [glsl_topology, debug_name, max_vertices] =
- GetPrimitiveDescription(variant.primitive_mode);
-
- source += fmt::format("layout ({}) in;\n\n", glsl_topology);
+ const auto [glsl_topology, max_vertices] = GetPrimitiveDescription(variant.primitive_mode);
source += fmt::format("#define MAX_VERTEX_INPUT {}\n", max_vertices);
+ source += fmt::format("layout ({}) in;\n", glsl_topology);
}
if (shader_type == ShaderType::Compute) {
+ if (variant.local_memory_size > 0) {
+ source += fmt::format("#define LOCAL_MEMORY_SIZE {}\n",
+ Common::AlignUp(variant.local_memory_size, 4) / 4);
+ }
source +=
fmt::format("layout (local_size_x = {}, local_size_y = {}, local_size_z = {}) in;\n",
variant.block_x, variant.block_y, variant.block_z);
if (variant.shared_memory_size > 0) {
- // TODO(Rodrigo): We should divide by four here, but having a larger shared memory pool
- // avoids out of bound stores. Find out why shared memory size is being invalid.
+ // shared_memory_size is described in number of words
source += fmt::format("shared uint smem[{}];\n", variant.shared_memory_size);
}
-
- if (variant.local_memory_size > 0) {
- source += fmt::format("#define LOCAL_MEMORY_SIZE {}\n",
- Common::AlignUp(variant.local_memory_size, 4) / 4);
- }
}
source += '\n';
diff --git a/src/video_core/renderer_opengl/gl_shader_decompiler.cpp b/src/video_core/renderer_opengl/gl_shader_decompiler.cpp
index 83a6769ae..a311dbcfe 100644
--- a/src/video_core/renderer_opengl/gl_shader_decompiler.cpp
+++ b/src/video_core/renderer_opengl/gl_shader_decompiler.cpp
@@ -399,6 +399,7 @@ public:
DeclareConstantBuffers();
DeclareGlobalMemory();
DeclareSamplers();
+ DeclareImages();
DeclarePhysicalAttributeReader();
code.AddLine("void execute_{}() {{", suffix);
diff --git a/src/video_core/renderer_opengl/gl_state.cpp b/src/video_core/renderer_opengl/gl_state.cpp
index ccc1e050a..df2e2395a 100644
--- a/src/video_core/renderer_opengl/gl_state.cpp
+++ b/src/video_core/renderer_opengl/gl_state.cpp
@@ -182,6 +182,10 @@ void OpenGLState::ApplyCulling() {
}
}
+void OpenGLState::ApplyRasterizerDiscard() {
+ Enable(GL_RASTERIZER_DISCARD, cur_state.rasterizer_discard, rasterizer_discard);
+}
+
void OpenGLState::ApplyColorMask() {
if (!dirty.color_mask) {
return;
@@ -455,6 +459,7 @@ void OpenGLState::Apply() {
ApplyPointSize();
ApplyFragmentColorClamp();
ApplyMultisample();
+ ApplyRasterizerDiscard();
ApplyColorMask();
ApplyDepthClamp();
ApplyViewport();
diff --git a/src/video_core/renderer_opengl/gl_state.h b/src/video_core/renderer_opengl/gl_state.h
index 0b5895084..fb180f302 100644
--- a/src/video_core/renderer_opengl/gl_state.h
+++ b/src/video_core/renderer_opengl/gl_state.h
@@ -48,6 +48,8 @@ public:
GLuint index = 0;
} primitive_restart; // GL_PRIMITIVE_RESTART
+ bool rasterizer_discard = false; // GL_RASTERIZER_DISCARD
+
struct ColorMask {
GLboolean red_enabled = GL_TRUE;
GLboolean green_enabled = GL_TRUE;
@@ -56,6 +58,7 @@ public:
};
std::array<ColorMask, Tegra::Engines::Maxwell3D::Regs::NumRenderTargets>
color_mask; // GL_COLOR_WRITEMASK
+
struct {
bool test_enabled = false; // GL_STENCIL_TEST
struct {
@@ -174,6 +177,7 @@ public:
void ApplyMultisample();
void ApplySRgb();
void ApplyCulling();
+ void ApplyRasterizerDiscard();
void ApplyColorMask();
void ApplyDepth();
void ApplyPrimitiveRestart();
diff --git a/src/video_core/renderer_opengl/renderer_opengl.cpp b/src/video_core/renderer_opengl/renderer_opengl.cpp
index a57a564f7..bba16afaf 100644
--- a/src/video_core/renderer_opengl/renderer_opengl.cpp
+++ b/src/video_core/renderer_opengl/renderer_opengl.cpp
@@ -24,19 +24,21 @@
namespace OpenGL {
-static const char vertex_shader[] = R"(
-#version 150 core
+namespace {
-in vec2 vert_position;
-in vec2 vert_tex_coord;
-out vec2 frag_tex_coord;
+constexpr char vertex_shader[] = R"(
+#version 430 core
+
+layout (location = 0) in vec2 vert_position;
+layout (location = 1) in vec2 vert_tex_coord;
+layout (location = 0) out vec2 frag_tex_coord;
// This is a truncated 3x3 matrix for 2D transformations:
// The upper-left 2x2 submatrix performs scaling/rotation/mirroring.
// The third column performs translation.
// The third row could be used for projection, which we don't need in 2D. It hence is assumed to
// implicitly be [0, 0, 1]
-uniform mat3x2 modelview_matrix;
+layout (location = 0) uniform mat3x2 modelview_matrix;
void main() {
// Multiply input position by the rotscale part of the matrix and then manually translate by
@@ -47,34 +49,29 @@ void main() {
}
)";
-static const char fragment_shader[] = R"(
-#version 150 core
+constexpr char fragment_shader[] = R"(
+#version 430 core
-in vec2 frag_tex_coord;
-out vec4 color;
+layout (location = 0) in vec2 frag_tex_coord;
+layout (location = 0) out vec4 color;
-uniform sampler2D color_texture;
+layout (binding = 0) uniform sampler2D color_texture;
void main() {
- // Swap RGBA -> ABGR so we don't have to do this on the CPU. This needs to change if we have to
- // support more framebuffer pixel formats.
color = texture(color_texture, frag_tex_coord);
}
)";
-/**
- * Vertex structure that the drawn screen rectangles are composed of.
- */
+constexpr GLint PositionLocation = 0;
+constexpr GLint TexCoordLocation = 1;
+constexpr GLint ModelViewMatrixLocation = 0;
+
struct ScreenRectVertex {
- ScreenRectVertex(GLfloat x, GLfloat y, GLfloat u, GLfloat v) {
- position[0] = x;
- position[1] = y;
- tex_coord[0] = u;
- tex_coord[1] = v;
- }
+ constexpr ScreenRectVertex(GLfloat x, GLfloat y, GLfloat u, GLfloat v)
+ : position{{x, y}}, tex_coord{{u, v}} {}
- GLfloat position[2];
- GLfloat tex_coord[2];
+ std::array<GLfloat, 2> position;
+ std::array<GLfloat, 2> tex_coord;
};
/**
@@ -84,18 +81,82 @@ struct ScreenRectVertex {
* The projection part of the matrix is trivial, hence these operations are represented
* by a 3x2 matrix.
*/
-static std::array<GLfloat, 3 * 2> MakeOrthographicMatrix(const float width, const float height) {
+std::array<GLfloat, 3 * 2> MakeOrthographicMatrix(float width, float height) {
std::array<GLfloat, 3 * 2> matrix; // Laid out in column-major order
// clang-format off
- matrix[0] = 2.f / width; matrix[2] = 0.f; matrix[4] = -1.f;
- matrix[1] = 0.f; matrix[3] = -2.f / height; matrix[5] = 1.f;
+ matrix[0] = 2.f / width; matrix[2] = 0.f; matrix[4] = -1.f;
+ matrix[1] = 0.f; matrix[3] = -2.f / height; matrix[5] = 1.f;
// Last matrix row is implicitly assumed to be [0, 0, 1].
// clang-format on
return matrix;
}
+const char* GetSource(GLenum source) {
+ switch (source) {
+ case GL_DEBUG_SOURCE_API:
+ return "API";
+ case GL_DEBUG_SOURCE_WINDOW_SYSTEM:
+ return "WINDOW_SYSTEM";
+ case GL_DEBUG_SOURCE_SHADER_COMPILER:
+ return "SHADER_COMPILER";
+ case GL_DEBUG_SOURCE_THIRD_PARTY:
+ return "THIRD_PARTY";
+ case GL_DEBUG_SOURCE_APPLICATION:
+ return "APPLICATION";
+ case GL_DEBUG_SOURCE_OTHER:
+ return "OTHER";
+ default:
+ UNREACHABLE();
+ return "Unknown source";
+ }
+}
+
+const char* GetType(GLenum type) {
+ switch (type) {
+ case GL_DEBUG_TYPE_ERROR:
+ return "ERROR";
+ case GL_DEBUG_TYPE_DEPRECATED_BEHAVIOR:
+ return "DEPRECATED_BEHAVIOR";
+ case GL_DEBUG_TYPE_UNDEFINED_BEHAVIOR:
+ return "UNDEFINED_BEHAVIOR";
+ case GL_DEBUG_TYPE_PORTABILITY:
+ return "PORTABILITY";
+ case GL_DEBUG_TYPE_PERFORMANCE:
+ return "PERFORMANCE";
+ case GL_DEBUG_TYPE_OTHER:
+ return "OTHER";
+ case GL_DEBUG_TYPE_MARKER:
+ return "MARKER";
+ default:
+ UNREACHABLE();
+ return "Unknown type";
+ }
+}
+
+void APIENTRY DebugHandler(GLenum source, GLenum type, GLuint id, GLenum severity, GLsizei length,
+ const GLchar* message, const void* user_param) {
+ const char format[] = "{} {} {}: {}";
+ const char* const str_source = GetSource(source);
+ const char* const str_type = GetType(type);
+
+ switch (severity) {
+ case GL_DEBUG_SEVERITY_HIGH:
+ LOG_CRITICAL(Render_OpenGL, format, str_source, str_type, id, message);
+ break;
+ case GL_DEBUG_SEVERITY_MEDIUM:
+ LOG_WARNING(Render_OpenGL, format, str_source, str_type, id, message);
+ break;
+ case GL_DEBUG_SEVERITY_NOTIFICATION:
+ case GL_DEBUG_SEVERITY_LOW:
+ LOG_DEBUG(Render_OpenGL, format, str_source, str_type, id, message);
+ break;
+ }
+}
+
+} // Anonymous namespace
+
RendererOpenGL::RendererOpenGL(Core::Frontend::EmuWindow& emu_window, Core::System& system)
: VideoCore::RendererBase{emu_window}, emu_window{emu_window}, system{system} {}
@@ -138,9 +199,6 @@ void RendererOpenGL::SwapBuffers(const Tegra::FramebufferConfig* framebuffer) {
prev_state.Apply();
}
-/**
- * Loads framebuffer from emulated memory into the active OpenGL texture.
- */
void RendererOpenGL::LoadFBToScreenInfo(const Tegra::FramebufferConfig& framebuffer) {
// Framebuffer orientation handling
framebuffer_transform_flags = framebuffer.transform_flags;
@@ -181,19 +239,12 @@ void RendererOpenGL::LoadFBToScreenInfo(const Tegra::FramebufferConfig& framebuf
glPixelStorei(GL_UNPACK_ROW_LENGTH, 0);
}
-/**
- * Fills active OpenGL texture with the given RGB color. Since the color is solid, the texture can
- * be 1x1 but will stretch across whatever it's rendered on.
- */
void RendererOpenGL::LoadColorToActiveGLTexture(u8 color_r, u8 color_g, u8 color_b, u8 color_a,
const TextureInfo& texture) {
const u8 framebuffer_data[4] = {color_a, color_b, color_g, color_r};
glClearTexImage(texture.resource.handle, 0, GL_RGBA, GL_UNSIGNED_BYTE, framebuffer_data);
}
-/**
- * Initializes the OpenGL state and creates persistent objects.
- */
void RendererOpenGL::InitOpenGLObjects() {
glClearColor(Settings::values.bg_red, Settings::values.bg_green, Settings::values.bg_blue,
0.0f);
@@ -203,10 +254,6 @@ void RendererOpenGL::InitOpenGLObjects() {
state.draw.shader_program = shader.handle;
state.AllDirty();
state.Apply();
- uniform_modelview_matrix = glGetUniformLocation(shader.handle, "modelview_matrix");
- uniform_color_texture = glGetUniformLocation(shader.handle, "color_texture");
- attrib_position = glGetAttribLocation(shader.handle, "vert_position");
- attrib_tex_coord = glGetAttribLocation(shader.handle, "vert_tex_coord");
// Generate VBO handle for drawing
vertex_buffer.Create();
@@ -217,14 +264,14 @@ void RendererOpenGL::InitOpenGLObjects() {
// Attach vertex data to VAO
glNamedBufferData(vertex_buffer.handle, sizeof(ScreenRectVertex) * 4, nullptr, GL_STREAM_DRAW);
- glVertexArrayAttribFormat(vertex_array.handle, attrib_position, 2, GL_FLOAT, GL_FALSE,
+ glVertexArrayAttribFormat(vertex_array.handle, PositionLocation, 2, GL_FLOAT, GL_FALSE,
offsetof(ScreenRectVertex, position));
- glVertexArrayAttribFormat(vertex_array.handle, attrib_tex_coord, 2, GL_FLOAT, GL_FALSE,
+ glVertexArrayAttribFormat(vertex_array.handle, TexCoordLocation, 2, GL_FLOAT, GL_FALSE,
offsetof(ScreenRectVertex, tex_coord));
- glVertexArrayAttribBinding(vertex_array.handle, attrib_position, 0);
- glVertexArrayAttribBinding(vertex_array.handle, attrib_tex_coord, 0);
- glEnableVertexArrayAttrib(vertex_array.handle, attrib_position);
- glEnableVertexArrayAttrib(vertex_array.handle, attrib_tex_coord);
+ glVertexArrayAttribBinding(vertex_array.handle, PositionLocation, 0);
+ glVertexArrayAttribBinding(vertex_array.handle, TexCoordLocation, 0);
+ glEnableVertexArrayAttrib(vertex_array.handle, PositionLocation);
+ glEnableVertexArrayAttrib(vertex_array.handle, TexCoordLocation);
glVertexArrayVertexBuffer(vertex_array.handle, 0, vertex_buffer.handle, 0,
sizeof(ScreenRectVertex));
@@ -331,18 +378,18 @@ void RendererOpenGL::DrawScreenTriangles(const ScreenInfo& screen_info, float x,
static_cast<f32>(screen_info.texture.height);
}
- std::array<ScreenRectVertex, 4> vertices = {{
+ const std::array vertices = {
ScreenRectVertex(x, y, texcoords.top * scale_u, left * scale_v),
ScreenRectVertex(x + w, y, texcoords.bottom * scale_u, left * scale_v),
ScreenRectVertex(x, y + h, texcoords.top * scale_u, right * scale_v),
ScreenRectVertex(x + w, y + h, texcoords.bottom * scale_u, right * scale_v),
- }};
+ };
state.textures[0] = screen_info.display_texture;
state.framebuffer_srgb.enabled = screen_info.display_srgb;
state.AllDirty();
state.Apply();
- glNamedBufferSubData(vertex_buffer.handle, 0, sizeof(vertices), vertices.data());
+ glNamedBufferSubData(vertex_buffer.handle, 0, sizeof(vertices), std::data(vertices));
glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);
// Restore default state
state.framebuffer_srgb.enabled = false;
@@ -351,9 +398,6 @@ void RendererOpenGL::DrawScreenTriangles(const ScreenInfo& screen_info, float x,
state.Apply();
}
-/**
- * Draws the emulated screens to the emulator window.
- */
void RendererOpenGL::DrawScreen(const Layout::FramebufferLayout& layout) {
if (renderer_settings.set_background_color) {
// Update background color before drawing
@@ -367,21 +411,17 @@ void RendererOpenGL::DrawScreen(const Layout::FramebufferLayout& layout) {
glClear(GL_COLOR_BUFFER_BIT);
// Set projection matrix
- std::array<GLfloat, 3 * 2> ortho_matrix =
- MakeOrthographicMatrix((float)layout.width, (float)layout.height);
- glUniformMatrix3x2fv(uniform_modelview_matrix, 1, GL_FALSE, ortho_matrix.data());
-
- // Bind texture in Texture Unit 0
- glActiveTexture(GL_TEXTURE0);
- glUniform1i(uniform_color_texture, 0);
+ const std::array ortho_matrix =
+ MakeOrthographicMatrix(static_cast<float>(layout.width), static_cast<float>(layout.height));
+ glUniformMatrix3x2fv(ModelViewMatrixLocation, 1, GL_FALSE, ortho_matrix.data());
- DrawScreenTriangles(screen_info, (float)screen.left, (float)screen.top,
- (float)screen.GetWidth(), (float)screen.GetHeight());
+ DrawScreenTriangles(screen_info, static_cast<float>(screen.left),
+ static_cast<float>(screen.top), static_cast<float>(screen.GetWidth()),
+ static_cast<float>(screen.GetHeight()));
m_current_frame++;
}
-/// Updates the framerate
void RendererOpenGL::UpdateFramerate() {}
void RendererOpenGL::CaptureScreenshot() {
@@ -418,63 +458,6 @@ void RendererOpenGL::CaptureScreenshot() {
renderer_settings.screenshot_requested = false;
}
-static const char* GetSource(GLenum source) {
-#define RET(s) \
- case GL_DEBUG_SOURCE_##s: \
- return #s
- switch (source) {
- RET(API);
- RET(WINDOW_SYSTEM);
- RET(SHADER_COMPILER);
- RET(THIRD_PARTY);
- RET(APPLICATION);
- RET(OTHER);
- default:
- UNREACHABLE();
- return "Unknown source";
- }
-#undef RET
-}
-
-static const char* GetType(GLenum type) {
-#define RET(t) \
- case GL_DEBUG_TYPE_##t: \
- return #t
- switch (type) {
- RET(ERROR);
- RET(DEPRECATED_BEHAVIOR);
- RET(UNDEFINED_BEHAVIOR);
- RET(PORTABILITY);
- RET(PERFORMANCE);
- RET(OTHER);
- RET(MARKER);
- default:
- UNREACHABLE();
- return "Unknown type";
- }
-#undef RET
-}
-
-static void APIENTRY DebugHandler(GLenum source, GLenum type, GLuint id, GLenum severity,
- GLsizei length, const GLchar* message, const void* user_param) {
- const char format[] = "{} {} {}: {}";
- const char* const str_source = GetSource(source);
- const char* const str_type = GetType(type);
-
- switch (severity) {
- case GL_DEBUG_SEVERITY_HIGH:
- LOG_CRITICAL(Render_OpenGL, format, str_source, str_type, id, message);
- break;
- case GL_DEBUG_SEVERITY_MEDIUM:
- LOG_WARNING(Render_OpenGL, format, str_source, str_type, id, message);
- break;
- case GL_DEBUG_SEVERITY_NOTIFICATION:
- case GL_DEBUG_SEVERITY_LOW:
- LOG_DEBUG(Render_OpenGL, format, str_source, str_type, id, message);
- break;
- }
-}
-
bool RendererOpenGL::Init() {
Core::Frontend::ScopeAcquireWindowContext acquire_context{render_window};
@@ -495,7 +478,6 @@ bool RendererOpenGL::Init() {
return true;
}
-/// Shutdown the renderer
void RendererOpenGL::ShutDown() {}
} // namespace OpenGL
diff --git a/src/video_core/renderer_opengl/renderer_opengl.h b/src/video_core/renderer_opengl/renderer_opengl.h
index cf26628ca..b56328a7f 100644
--- a/src/video_core/renderer_opengl/renderer_opengl.h
+++ b/src/video_core/renderer_opengl/renderer_opengl.h
@@ -59,21 +59,31 @@ public:
void ShutDown() override;
private:
+ /// Initializes the OpenGL state and creates persistent objects.
void InitOpenGLObjects();
+
void AddTelemetryFields();
+
void CreateRasterizer();
void ConfigureFramebufferTexture(TextureInfo& texture,
const Tegra::FramebufferConfig& framebuffer);
+
+ /// Draws the emulated screens to the emulator window.
void DrawScreen(const Layout::FramebufferLayout& layout);
+
void DrawScreenTriangles(const ScreenInfo& screen_info, float x, float y, float w, float h);
+
+ /// Updates the framerate.
void UpdateFramerate();
void CaptureScreenshot();
- // Loads framebuffer from emulated memory into the display information structure
+ /// Loads framebuffer from emulated memory into the active OpenGL texture.
void LoadFBToScreenInfo(const Tegra::FramebufferConfig& framebuffer);
- // Fills active OpenGL texture with the given RGBA color.
+
+ /// Fills active OpenGL texture with the given RGB color.Since the color is solid, the texture
+ /// can be 1x1 but will stretch across whatever it's rendered on.
void LoadColorToActiveGLTexture(u8 color_r, u8 color_g, u8 color_b, u8 color_a,
const TextureInfo& texture);
@@ -94,14 +104,6 @@ private:
/// OpenGL framebuffer data
std::vector<u8> gl_framebuffer_data;
- // Shader uniform location indices
- GLuint uniform_modelview_matrix;
- GLuint uniform_color_texture;
-
- // Shader attribute input indices
- GLuint attrib_position;
- GLuint attrib_tex_coord;
-
/// Used for transforming the framebuffer orientation
Tegra::FramebufferConfig::TransformFlags framebuffer_transform_flags;
Common::Rectangle<int> framebuffer_crop_rect;
diff --git a/src/video_core/renderer_vulkan/fixed_pipeline_state.cpp b/src/video_core/renderer_vulkan/fixed_pipeline_state.cpp
new file mode 100644
index 000000000..5a490f6ef
--- /dev/null
+++ b/src/video_core/renderer_vulkan/fixed_pipeline_state.cpp
@@ -0,0 +1,296 @@
+// Copyright 2019 yuzu Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#include <tuple>
+
+#include <boost/functional/hash.hpp>
+
+#include "common/common_types.h"
+#include "video_core/renderer_vulkan/fixed_pipeline_state.h"
+
+namespace Vulkan {
+
+namespace {
+
+constexpr FixedPipelineState::DepthStencil GetDepthStencilState(const Maxwell& regs) {
+ const FixedPipelineState::StencilFace front_stencil(
+ regs.stencil_front_op_fail, regs.stencil_front_op_zfail, regs.stencil_front_op_zpass,
+ regs.stencil_front_func_func);
+ const FixedPipelineState::StencilFace back_stencil =
+ regs.stencil_two_side_enable
+ ? FixedPipelineState::StencilFace(regs.stencil_back_op_fail, regs.stencil_back_op_zfail,
+ regs.stencil_back_op_zpass,
+ regs.stencil_back_func_func)
+ : front_stencil;
+ return FixedPipelineState::DepthStencil(
+ regs.depth_test_enable == 1, regs.depth_write_enabled == 1, regs.depth_bounds_enable == 1,
+ regs.stencil_enable == 1, regs.depth_test_func, front_stencil, back_stencil);
+}
+
+constexpr FixedPipelineState::InputAssembly GetInputAssemblyState(const Maxwell& regs) {
+ return FixedPipelineState::InputAssembly(
+ regs.draw.topology, regs.primitive_restart.enabled,
+ regs.draw.topology == Maxwell::PrimitiveTopology::Points ? regs.point_size : 0.0f);
+}
+
+constexpr FixedPipelineState::BlendingAttachment GetBlendingAttachmentState(
+ const Maxwell& regs, std::size_t render_target) {
+ const auto& mask = regs.color_mask[regs.color_mask_common ? 0 : render_target];
+ const std::array components = {mask.R != 0, mask.G != 0, mask.B != 0, mask.A != 0};
+
+ const FixedPipelineState::BlendingAttachment default_blending(
+ false, Maxwell::Blend::Equation::Add, Maxwell::Blend::Factor::One,
+ Maxwell::Blend::Factor::Zero, Maxwell::Blend::Equation::Add, Maxwell::Blend::Factor::One,
+ Maxwell::Blend::Factor::Zero, components);
+ if (render_target >= regs.rt_control.count) {
+ return default_blending;
+ }
+
+ if (!regs.independent_blend_enable) {
+ const auto& src = regs.blend;
+ if (!src.enable[render_target]) {
+ return default_blending;
+ }
+ return FixedPipelineState::BlendingAttachment(
+ true, src.equation_rgb, src.factor_source_rgb, src.factor_dest_rgb, src.equation_a,
+ src.factor_source_a, src.factor_dest_a, components);
+ }
+
+ if (!regs.blend.enable[render_target]) {
+ return default_blending;
+ }
+ const auto& src = regs.independent_blend[render_target];
+ return FixedPipelineState::BlendingAttachment(
+ true, src.equation_rgb, src.factor_source_rgb, src.factor_dest_rgb, src.equation_a,
+ src.factor_source_a, src.factor_dest_a, components);
+}
+
+constexpr FixedPipelineState::ColorBlending GetColorBlendingState(const Maxwell& regs) {
+ return FixedPipelineState::ColorBlending(
+ {regs.blend_color.r, regs.blend_color.g, regs.blend_color.b, regs.blend_color.a},
+ regs.rt_control.count,
+ {GetBlendingAttachmentState(regs, 0), GetBlendingAttachmentState(regs, 1),
+ GetBlendingAttachmentState(regs, 2), GetBlendingAttachmentState(regs, 3),
+ GetBlendingAttachmentState(regs, 4), GetBlendingAttachmentState(regs, 5),
+ GetBlendingAttachmentState(regs, 6), GetBlendingAttachmentState(regs, 7)});
+}
+
+constexpr FixedPipelineState::Tessellation GetTessellationState(const Maxwell& regs) {
+ return FixedPipelineState::Tessellation(regs.patch_vertices, regs.tess_mode.prim,
+ regs.tess_mode.spacing, regs.tess_mode.cw != 0);
+}
+
+constexpr std::size_t Point = 0;
+constexpr std::size_t Line = 1;
+constexpr std::size_t Polygon = 2;
+constexpr std::array PolygonOffsetEnableLUT = {
+ Point, // Points
+ Line, // Lines
+ Line, // LineLoop
+ Line, // LineStrip
+ Polygon, // Triangles
+ Polygon, // TriangleStrip
+ Polygon, // TriangleFan
+ Polygon, // Quads
+ Polygon, // QuadStrip
+ Polygon, // Polygon
+ Line, // LinesAdjacency
+ Line, // LineStripAdjacency
+ Polygon, // TrianglesAdjacency
+ Polygon, // TriangleStripAdjacency
+ Polygon, // Patches
+};
+
+constexpr FixedPipelineState::Rasterizer GetRasterizerState(const Maxwell& regs) {
+ const std::array enabled_lut = {regs.polygon_offset_point_enable,
+ regs.polygon_offset_line_enable,
+ regs.polygon_offset_fill_enable};
+ const auto topology = static_cast<std::size_t>(regs.draw.topology.Value());
+ const bool depth_bias_enabled = enabled_lut[PolygonOffsetEnableLUT[topology]];
+
+ Maxwell::Cull::FrontFace front_face = regs.cull.front_face;
+ if (regs.screen_y_control.triangle_rast_flip != 0 &&
+ regs.viewport_transform[0].scale_y > 0.0f) {
+ if (front_face == Maxwell::Cull::FrontFace::CounterClockWise)
+ front_face = Maxwell::Cull::FrontFace::ClockWise;
+ else if (front_face == Maxwell::Cull::FrontFace::ClockWise)
+ front_face = Maxwell::Cull::FrontFace::CounterClockWise;
+ }
+
+ const bool gl_ndc = regs.depth_mode == Maxwell::DepthMode::MinusOneToOne;
+ return FixedPipelineState::Rasterizer(regs.cull.enabled, depth_bias_enabled, gl_ndc,
+ regs.cull.cull_face, front_face);
+}
+
+} // Anonymous namespace
+
+std::size_t FixedPipelineState::VertexBinding::Hash() const noexcept {
+ return (index << stride) ^ divisor;
+}
+
+bool FixedPipelineState::VertexBinding::operator==(const VertexBinding& rhs) const noexcept {
+ return std::tie(index, stride, divisor) == std::tie(rhs.index, rhs.stride, rhs.divisor);
+}
+
+std::size_t FixedPipelineState::VertexAttribute::Hash() const noexcept {
+ return static_cast<std::size_t>(index) ^ (static_cast<std::size_t>(buffer) << 13) ^
+ (static_cast<std::size_t>(type) << 22) ^ (static_cast<std::size_t>(size) << 31) ^
+ (static_cast<std::size_t>(offset) << 36);
+}
+
+bool FixedPipelineState::VertexAttribute::operator==(const VertexAttribute& rhs) const noexcept {
+ return std::tie(index, buffer, type, size, offset) ==
+ std::tie(rhs.index, rhs.buffer, rhs.type, rhs.size, rhs.offset);
+}
+
+std::size_t FixedPipelineState::StencilFace::Hash() const noexcept {
+ return static_cast<std::size_t>(action_stencil_fail) ^
+ (static_cast<std::size_t>(action_depth_fail) << 4) ^
+ (static_cast<std::size_t>(action_depth_fail) << 20) ^
+ (static_cast<std::size_t>(action_depth_pass) << 36);
+}
+
+bool FixedPipelineState::StencilFace::operator==(const StencilFace& rhs) const noexcept {
+ return std::tie(action_stencil_fail, action_depth_fail, action_depth_pass, test_func) ==
+ std::tie(rhs.action_stencil_fail, rhs.action_depth_fail, rhs.action_depth_pass,
+ rhs.test_func);
+}
+
+std::size_t FixedPipelineState::BlendingAttachment::Hash() const noexcept {
+ return static_cast<std::size_t>(enable) ^ (static_cast<std::size_t>(rgb_equation) << 5) ^
+ (static_cast<std::size_t>(src_rgb_func) << 10) ^
+ (static_cast<std::size_t>(dst_rgb_func) << 15) ^
+ (static_cast<std::size_t>(a_equation) << 20) ^
+ (static_cast<std::size_t>(src_a_func) << 25) ^
+ (static_cast<std::size_t>(dst_a_func) << 30) ^
+ (static_cast<std::size_t>(components[0]) << 35) ^
+ (static_cast<std::size_t>(components[1]) << 36) ^
+ (static_cast<std::size_t>(components[2]) << 37) ^
+ (static_cast<std::size_t>(components[3]) << 38);
+}
+
+bool FixedPipelineState::BlendingAttachment::operator==(const BlendingAttachment& rhs) const
+ noexcept {
+ return std::tie(enable, rgb_equation, src_rgb_func, dst_rgb_func, a_equation, src_a_func,
+ dst_a_func, components) ==
+ std::tie(rhs.enable, rhs.rgb_equation, rhs.src_rgb_func, rhs.dst_rgb_func,
+ rhs.a_equation, rhs.src_a_func, rhs.dst_a_func, rhs.components);
+}
+
+std::size_t FixedPipelineState::VertexInput::Hash() const noexcept {
+ std::size_t hash = num_bindings ^ (num_attributes << 32);
+ for (std::size_t i = 0; i < num_bindings; ++i) {
+ boost::hash_combine(hash, bindings[i].Hash());
+ }
+ for (std::size_t i = 0; i < num_attributes; ++i) {
+ boost::hash_combine(hash, attributes[i].Hash());
+ }
+ return hash;
+}
+
+bool FixedPipelineState::VertexInput::operator==(const VertexInput& rhs) const noexcept {
+ return std::equal(bindings.begin(), bindings.begin() + num_bindings, rhs.bindings.begin(),
+ rhs.bindings.begin() + rhs.num_bindings) &&
+ std::equal(attributes.begin(), attributes.begin() + num_attributes,
+ rhs.attributes.begin(), rhs.attributes.begin() + rhs.num_attributes);
+}
+
+std::size_t FixedPipelineState::InputAssembly::Hash() const noexcept {
+ std::size_t point_size_int = 0;
+ std::memcpy(&point_size_int, &point_size, sizeof(point_size));
+ return (static_cast<std::size_t>(topology) << 24) ^ (point_size_int << 32) ^
+ static_cast<std::size_t>(primitive_restart_enable);
+}
+
+bool FixedPipelineState::InputAssembly::operator==(const InputAssembly& rhs) const noexcept {
+ return std::tie(topology, primitive_restart_enable, point_size) ==
+ std::tie(rhs.topology, rhs.primitive_restart_enable, rhs.point_size);
+}
+
+std::size_t FixedPipelineState::Tessellation::Hash() const noexcept {
+ return static_cast<std::size_t>(patch_control_points) ^
+ (static_cast<std::size_t>(primitive) << 6) ^ (static_cast<std::size_t>(spacing) << 8) ^
+ (static_cast<std::size_t>(clockwise) << 10);
+}
+
+bool FixedPipelineState::Tessellation::operator==(const Tessellation& rhs) const noexcept {
+ return std::tie(patch_control_points, primitive, spacing, clockwise) ==
+ std::tie(rhs.patch_control_points, rhs.primitive, rhs.spacing, rhs.clockwise);
+}
+
+std::size_t FixedPipelineState::Rasterizer::Hash() const noexcept {
+ return static_cast<std::size_t>(cull_enable) ^
+ (static_cast<std::size_t>(depth_bias_enable) << 1) ^
+ (static_cast<std::size_t>(ndc_minus_one_to_one) << 2) ^
+ (static_cast<std::size_t>(cull_face) << 24) ^
+ (static_cast<std::size_t>(front_face) << 48);
+}
+
+bool FixedPipelineState::Rasterizer::operator==(const Rasterizer& rhs) const noexcept {
+ return std::tie(cull_enable, depth_bias_enable, ndc_minus_one_to_one, cull_face, front_face) ==
+ std::tie(rhs.cull_enable, rhs.depth_bias_enable, rhs.ndc_minus_one_to_one, rhs.cull_face,
+ rhs.front_face);
+}
+
+std::size_t FixedPipelineState::DepthStencil::Hash() const noexcept {
+ std::size_t hash = static_cast<std::size_t>(depth_test_enable) ^
+ (static_cast<std::size_t>(depth_write_enable) << 1) ^
+ (static_cast<std::size_t>(depth_bounds_enable) << 2) ^
+ (static_cast<std::size_t>(stencil_enable) << 3) ^
+ (static_cast<std::size_t>(depth_test_function) << 4);
+ boost::hash_combine(hash, front_stencil.Hash());
+ boost::hash_combine(hash, back_stencil.Hash());
+ return hash;
+}
+
+bool FixedPipelineState::DepthStencil::operator==(const DepthStencil& rhs) const noexcept {
+ return std::tie(depth_test_enable, depth_write_enable, depth_bounds_enable, depth_test_function,
+ stencil_enable, front_stencil, back_stencil) ==
+ std::tie(rhs.depth_test_enable, rhs.depth_write_enable, rhs.depth_bounds_enable,
+ rhs.depth_test_function, rhs.stencil_enable, rhs.front_stencil,
+ rhs.back_stencil);
+}
+
+std::size_t FixedPipelineState::ColorBlending::Hash() const noexcept {
+ std::size_t hash = attachments_count << 13;
+ for (std::size_t rt = 0; rt < static_cast<std::size_t>(attachments_count); ++rt) {
+ boost::hash_combine(hash, attachments[rt].Hash());
+ }
+ return hash;
+}
+
+bool FixedPipelineState::ColorBlending::operator==(const ColorBlending& rhs) const noexcept {
+ return std::equal(attachments.begin(), attachments.begin() + attachments_count,
+ rhs.attachments.begin(), rhs.attachments.begin() + rhs.attachments_count);
+}
+
+std::size_t FixedPipelineState::Hash() const noexcept {
+ std::size_t hash = 0;
+ boost::hash_combine(hash, vertex_input.Hash());
+ boost::hash_combine(hash, input_assembly.Hash());
+ boost::hash_combine(hash, tessellation.Hash());
+ boost::hash_combine(hash, rasterizer.Hash());
+ boost::hash_combine(hash, depth_stencil.Hash());
+ boost::hash_combine(hash, color_blending.Hash());
+ return hash;
+}
+
+bool FixedPipelineState::operator==(const FixedPipelineState& rhs) const noexcept {
+ return std::tie(vertex_input, input_assembly, tessellation, rasterizer, depth_stencil,
+ color_blending) == std::tie(rhs.vertex_input, rhs.input_assembly,
+ rhs.tessellation, rhs.rasterizer, rhs.depth_stencil,
+ rhs.color_blending);
+}
+
+FixedPipelineState GetFixedPipelineState(const Maxwell& regs) {
+ FixedPipelineState fixed_state;
+ fixed_state.input_assembly = GetInputAssemblyState(regs);
+ fixed_state.tessellation = GetTessellationState(regs);
+ fixed_state.rasterizer = GetRasterizerState(regs);
+ fixed_state.depth_stencil = GetDepthStencilState(regs);
+ fixed_state.color_blending = GetColorBlendingState(regs);
+ return fixed_state;
+}
+
+} // namespace Vulkan
diff --git a/src/video_core/renderer_vulkan/fixed_pipeline_state.h b/src/video_core/renderer_vulkan/fixed_pipeline_state.h
new file mode 100644
index 000000000..04152c0d4
--- /dev/null
+++ b/src/video_core/renderer_vulkan/fixed_pipeline_state.h
@@ -0,0 +1,282 @@
+// Copyright 2019 yuzu Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#pragma once
+
+#include <array>
+#include <type_traits>
+
+#include "common/common_types.h"
+
+#include "video_core/engines/maxwell_3d.h"
+#include "video_core/surface.h"
+
+namespace Vulkan {
+
+using Maxwell = Tegra::Engines::Maxwell3D::Regs;
+
+// TODO(Rodrigo): Optimize this structure.
+
+struct FixedPipelineState {
+ using PixelFormat = VideoCore::Surface::PixelFormat;
+
+ struct VertexBinding {
+ constexpr VertexBinding(u32 index, u32 stride, u32 divisor)
+ : index{index}, stride{stride}, divisor{divisor} {}
+ VertexBinding() = default;
+
+ u32 index;
+ u32 stride;
+ u32 divisor;
+
+ std::size_t Hash() const noexcept;
+
+ bool operator==(const VertexBinding& rhs) const noexcept;
+
+ bool operator!=(const VertexBinding& rhs) const noexcept {
+ return !operator==(rhs);
+ }
+ };
+
+ struct VertexAttribute {
+ constexpr VertexAttribute(u32 index, u32 buffer, Maxwell::VertexAttribute::Type type,
+ Maxwell::VertexAttribute::Size size, u32 offset)
+ : index{index}, buffer{buffer}, type{type}, size{size}, offset{offset} {}
+ VertexAttribute() = default;
+
+ u32 index;
+ u32 buffer;
+ Maxwell::VertexAttribute::Type type;
+ Maxwell::VertexAttribute::Size size;
+ u32 offset;
+
+ std::size_t Hash() const noexcept;
+
+ bool operator==(const VertexAttribute& rhs) const noexcept;
+
+ bool operator!=(const VertexAttribute& rhs) const noexcept {
+ return !operator==(rhs);
+ }
+ };
+
+ struct StencilFace {
+ constexpr StencilFace(Maxwell::StencilOp action_stencil_fail,
+ Maxwell::StencilOp action_depth_fail,
+ Maxwell::StencilOp action_depth_pass, Maxwell::ComparisonOp test_func)
+ : action_stencil_fail{action_stencil_fail}, action_depth_fail{action_depth_fail},
+ action_depth_pass{action_depth_pass}, test_func{test_func} {}
+ StencilFace() = default;
+
+ Maxwell::StencilOp action_stencil_fail;
+ Maxwell::StencilOp action_depth_fail;
+ Maxwell::StencilOp action_depth_pass;
+ Maxwell::ComparisonOp test_func;
+
+ std::size_t Hash() const noexcept;
+
+ bool operator==(const StencilFace& rhs) const noexcept;
+
+ bool operator!=(const StencilFace& rhs) const noexcept {
+ return !operator==(rhs);
+ }
+ };
+
+ struct BlendingAttachment {
+ constexpr BlendingAttachment(bool enable, Maxwell::Blend::Equation rgb_equation,
+ Maxwell::Blend::Factor src_rgb_func,
+ Maxwell::Blend::Factor dst_rgb_func,
+ Maxwell::Blend::Equation a_equation,
+ Maxwell::Blend::Factor src_a_func,
+ Maxwell::Blend::Factor dst_a_func,
+ std::array<bool, 4> components)
+ : enable{enable}, rgb_equation{rgb_equation}, src_rgb_func{src_rgb_func},
+ dst_rgb_func{dst_rgb_func}, a_equation{a_equation}, src_a_func{src_a_func},
+ dst_a_func{dst_a_func}, components{components} {}
+ BlendingAttachment() = default;
+
+ bool enable;
+ Maxwell::Blend::Equation rgb_equation;
+ Maxwell::Blend::Factor src_rgb_func;
+ Maxwell::Blend::Factor dst_rgb_func;
+ Maxwell::Blend::Equation a_equation;
+ Maxwell::Blend::Factor src_a_func;
+ Maxwell::Blend::Factor dst_a_func;
+ std::array<bool, 4> components;
+
+ std::size_t Hash() const noexcept;
+
+ bool operator==(const BlendingAttachment& rhs) const noexcept;
+
+ bool operator!=(const BlendingAttachment& rhs) const noexcept {
+ return !operator==(rhs);
+ }
+ };
+
+ struct VertexInput {
+ std::size_t num_bindings = 0;
+ std::size_t num_attributes = 0;
+ std::array<VertexBinding, Maxwell::NumVertexArrays> bindings;
+ std::array<VertexAttribute, Maxwell::NumVertexAttributes> attributes;
+
+ std::size_t Hash() const noexcept;
+
+ bool operator==(const VertexInput& rhs) const noexcept;
+
+ bool operator!=(const VertexInput& rhs) const noexcept {
+ return !operator==(rhs);
+ }
+ };
+
+ struct InputAssembly {
+ constexpr InputAssembly(Maxwell::PrimitiveTopology topology, bool primitive_restart_enable,
+ float point_size)
+ : topology{topology}, primitive_restart_enable{primitive_restart_enable},
+ point_size{point_size} {}
+ InputAssembly() = default;
+
+ Maxwell::PrimitiveTopology topology;
+ bool primitive_restart_enable;
+ float point_size;
+
+ std::size_t Hash() const noexcept;
+
+ bool operator==(const InputAssembly& rhs) const noexcept;
+
+ bool operator!=(const InputAssembly& rhs) const noexcept {
+ return !operator==(rhs);
+ }
+ };
+
+ struct Tessellation {
+ constexpr Tessellation(u32 patch_control_points, Maxwell::TessellationPrimitive primitive,
+ Maxwell::TessellationSpacing spacing, bool clockwise)
+ : patch_control_points{patch_control_points}, primitive{primitive}, spacing{spacing},
+ clockwise{clockwise} {}
+ Tessellation() = default;
+
+ u32 patch_control_points;
+ Maxwell::TessellationPrimitive primitive;
+ Maxwell::TessellationSpacing spacing;
+ bool clockwise;
+
+ std::size_t Hash() const noexcept;
+
+ bool operator==(const Tessellation& rhs) const noexcept;
+
+ bool operator!=(const Tessellation& rhs) const noexcept {
+ return !operator==(rhs);
+ }
+ };
+
+ struct Rasterizer {
+ constexpr Rasterizer(bool cull_enable, bool depth_bias_enable, bool ndc_minus_one_to_one,
+ Maxwell::Cull::CullFace cull_face, Maxwell::Cull::FrontFace front_face)
+ : cull_enable{cull_enable}, depth_bias_enable{depth_bias_enable},
+ ndc_minus_one_to_one{ndc_minus_one_to_one}, cull_face{cull_face}, front_face{
+ front_face} {}
+ Rasterizer() = default;
+
+ bool cull_enable;
+ bool depth_bias_enable;
+ bool ndc_minus_one_to_one;
+ Maxwell::Cull::CullFace cull_face;
+ Maxwell::Cull::FrontFace front_face;
+
+ std::size_t Hash() const noexcept;
+
+ bool operator==(const Rasterizer& rhs) const noexcept;
+
+ bool operator!=(const Rasterizer& rhs) const noexcept {
+ return !operator==(rhs);
+ }
+ };
+
+ struct DepthStencil {
+ constexpr DepthStencil(bool depth_test_enable, bool depth_write_enable,
+ bool depth_bounds_enable, bool stencil_enable,
+ Maxwell::ComparisonOp depth_test_function, StencilFace front_stencil,
+ StencilFace back_stencil)
+ : depth_test_enable{depth_test_enable}, depth_write_enable{depth_write_enable},
+ depth_bounds_enable{depth_bounds_enable}, stencil_enable{stencil_enable},
+ depth_test_function{depth_test_function}, front_stencil{front_stencil},
+ back_stencil{back_stencil} {}
+ DepthStencil() = default;
+
+ bool depth_test_enable;
+ bool depth_write_enable;
+ bool depth_bounds_enable;
+ bool stencil_enable;
+ Maxwell::ComparisonOp depth_test_function;
+ StencilFace front_stencil;
+ StencilFace back_stencil;
+
+ std::size_t Hash() const noexcept;
+
+ bool operator==(const DepthStencil& rhs) const noexcept;
+
+ bool operator!=(const DepthStencil& rhs) const noexcept {
+ return !operator==(rhs);
+ }
+ };
+
+ struct ColorBlending {
+ constexpr ColorBlending(
+ std::array<float, 4> blend_constants, std::size_t attachments_count,
+ std::array<BlendingAttachment, Maxwell::NumRenderTargets> attachments)
+ : attachments_count{attachments_count}, attachments{attachments} {}
+ ColorBlending() = default;
+
+ std::size_t attachments_count;
+ std::array<BlendingAttachment, Maxwell::NumRenderTargets> attachments;
+
+ std::size_t Hash() const noexcept;
+
+ bool operator==(const ColorBlending& rhs) const noexcept;
+
+ bool operator!=(const ColorBlending& rhs) const noexcept {
+ return !operator==(rhs);
+ }
+ };
+
+ std::size_t Hash() const noexcept;
+
+ bool operator==(const FixedPipelineState& rhs) const noexcept;
+
+ bool operator!=(const FixedPipelineState& rhs) const noexcept {
+ return !operator==(rhs);
+ }
+
+ VertexInput vertex_input;
+ InputAssembly input_assembly;
+ Tessellation tessellation;
+ Rasterizer rasterizer;
+ DepthStencil depth_stencil;
+ ColorBlending color_blending;
+};
+static_assert(std::is_trivially_copyable_v<FixedPipelineState::VertexBinding>);
+static_assert(std::is_trivially_copyable_v<FixedPipelineState::VertexAttribute>);
+static_assert(std::is_trivially_copyable_v<FixedPipelineState::StencilFace>);
+static_assert(std::is_trivially_copyable_v<FixedPipelineState::BlendingAttachment>);
+static_assert(std::is_trivially_copyable_v<FixedPipelineState::VertexInput>);
+static_assert(std::is_trivially_copyable_v<FixedPipelineState::InputAssembly>);
+static_assert(std::is_trivially_copyable_v<FixedPipelineState::Tessellation>);
+static_assert(std::is_trivially_copyable_v<FixedPipelineState::Rasterizer>);
+static_assert(std::is_trivially_copyable_v<FixedPipelineState::DepthStencil>);
+static_assert(std::is_trivially_copyable_v<FixedPipelineState::ColorBlending>);
+static_assert(std::is_trivially_copyable_v<FixedPipelineState>);
+
+FixedPipelineState GetFixedPipelineState(const Maxwell& regs);
+
+} // namespace Vulkan
+
+namespace std {
+
+template <>
+struct hash<Vulkan::FixedPipelineState> {
+ std::size_t operator()(const Vulkan::FixedPipelineState& k) const noexcept {
+ return k.Hash();
+ }
+};
+
+} // namespace std
diff --git a/src/video_core/renderer_vulkan/shaders/blit.frag b/src/video_core/renderer_vulkan/shaders/blit.frag
new file mode 100644
index 000000000..a06ecd24a
--- /dev/null
+++ b/src/video_core/renderer_vulkan/shaders/blit.frag
@@ -0,0 +1,24 @@
+// Copyright 2019 yuzu Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+/*
+ * Build instructions:
+ * $ glslangValidator -V $THIS_FILE -o output.spv
+ * $ spirv-opt -O --strip-debug output.spv -o optimized.spv
+ * $ xxd -i optimized.spv
+ *
+ * Then copy that bytecode to the C++ file
+ */
+
+#version 460 core
+
+layout (location = 0) in vec2 frag_tex_coord;
+
+layout (location = 0) out vec4 color;
+
+layout (binding = 1) uniform sampler2D color_texture;
+
+void main() {
+ color = texture(color_texture, frag_tex_coord);
+}
diff --git a/src/video_core/renderer_vulkan/shaders/blit.vert b/src/video_core/renderer_vulkan/shaders/blit.vert
new file mode 100644
index 000000000..c64d9235a
--- /dev/null
+++ b/src/video_core/renderer_vulkan/shaders/blit.vert
@@ -0,0 +1,28 @@
+// Copyright 2019 yuzu Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+/*
+ * Build instructions:
+ * $ glslangValidator -V $THIS_FILE -o output.spv
+ * $ spirv-opt -O --strip-debug output.spv -o optimized.spv
+ * $ xxd -i optimized.spv
+ *
+ * Then copy that bytecode to the C++ file
+ */
+
+#version 460 core
+
+layout (location = 0) in vec2 vert_position;
+layout (location = 1) in vec2 vert_tex_coord;
+
+layout (location = 0) out vec2 frag_tex_coord;
+
+layout (set = 0, binding = 0) uniform MatrixBlock {
+ mat4 modelview_matrix;
+};
+
+void main() {
+ gl_Position = modelview_matrix * vec4(vert_position, 0.0, 1.0);
+ frag_tex_coord = vert_tex_coord;
+}
diff --git a/src/video_core/renderer_vulkan/shaders/quad_array.comp b/src/video_core/renderer_vulkan/shaders/quad_array.comp
new file mode 100644
index 000000000..5a5703308
--- /dev/null
+++ b/src/video_core/renderer_vulkan/shaders/quad_array.comp
@@ -0,0 +1,37 @@
+// Copyright 2019 yuzu Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+/*
+ * Build instructions:
+ * $ glslangValidator -V $THIS_FILE -o output.spv
+ * $ spirv-opt -O --strip-debug output.spv -o optimized.spv
+ * $ xxd -i optimized.spv
+ *
+ * Then copy that bytecode to the C++ file
+ */
+
+#version 460 core
+
+layout (local_size_x = 1024) in;
+
+layout (std430, set = 0, binding = 0) buffer OutputBuffer {
+ uint output_indexes[];
+};
+
+layout (push_constant) uniform PushConstants {
+ uint first;
+};
+
+void main() {
+ uint primitive = gl_GlobalInvocationID.x;
+ if (primitive * 6 >= output_indexes.length()) {
+ return;
+ }
+
+ const uint quad_map[6] = uint[](0, 1, 2, 0, 2, 3);
+ for (uint vertex = 0; vertex < 6; ++vertex) {
+ uint index = first + primitive * 4 + quad_map[vertex];
+ output_indexes[primitive * 6 + vertex] = index;
+ }
+}
diff --git a/src/video_core/renderer_vulkan/shaders/uint8.comp b/src/video_core/renderer_vulkan/shaders/uint8.comp
new file mode 100644
index 000000000..a320f3ae0
--- /dev/null
+++ b/src/video_core/renderer_vulkan/shaders/uint8.comp
@@ -0,0 +1,33 @@
+// Copyright 2019 yuzu Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+/*
+ * Build instructions:
+ * $ glslangValidator -V $THIS_FILE -o output.spv
+ * $ spirv-opt -O --strip-debug output.spv -o optimized.spv
+ * $ xxd -i optimized.spv
+ *
+ * Then copy that bytecode to the C++ file
+ */
+
+#version 460 core
+#extension GL_EXT_shader_16bit_storage : require
+#extension GL_EXT_shader_8bit_storage : require
+
+layout (local_size_x = 1024) in;
+
+layout (std430, set = 0, binding = 0) readonly buffer InputBuffer {
+ uint8_t input_indexes[];
+};
+
+layout (std430, set = 0, binding = 1) writeonly buffer OutputBuffer {
+ uint16_t output_indexes[];
+};
+
+void main() {
+ uint id = gl_GlobalInvocationID.x;
+ if (id < input_indexes.length()) {
+ output_indexes[id] = uint16_t(input_indexes[id]);
+ }
+}
diff --git a/src/video_core/renderer_vulkan/vk_device.cpp b/src/video_core/renderer_vulkan/vk_device.cpp
index 92854a4b3..939eebe83 100644
--- a/src/video_core/renderer_vulkan/vk_device.cpp
+++ b/src/video_core/renderer_vulkan/vk_device.cpp
@@ -3,12 +3,15 @@
// Refer to the license.txt file included.
#include <bitset>
+#include <chrono>
#include <cstdlib>
#include <optional>
#include <set>
#include <string_view>
+#include <thread>
#include <vector>
#include "common/assert.h"
+#include "core/settings.h"
#include "video_core/renderer_vulkan/declarations.h"
#include "video_core/renderer_vulkan/vk_device.h"
@@ -201,6 +204,22 @@ vk::Format VKDevice::GetSupportedFormat(vk::Format wanted_format,
return wanted_format;
}
+void VKDevice::ReportLoss() const {
+ LOG_CRITICAL(Render_Vulkan, "Device loss occured!");
+
+ // Wait some time to let the log flush
+ std::this_thread::sleep_for(std::chrono::seconds{1});
+
+ if (!nv_device_diagnostic_checkpoints) {
+ return;
+ }
+
+ [[maybe_unused]] const std::vector data = graphics_queue.getCheckpointDataNV(dld);
+ // Catch here in debug builds (or with optimizations disabled) the last graphics pipeline to be
+ // executed. It can be done on a debugger by evaluating the expression:
+ // *(VKGraphicsPipeline*)data[0]
+}
+
bool VKDevice::IsOptimalAstcSupported(const vk::PhysicalDeviceFeatures& features,
const vk::DispatchLoaderDynamic& dldi) const {
// Disable for now to avoid converting ASTC twice.
@@ -381,6 +400,8 @@ std::vector<const char*> VKDevice::LoadExtensions(const vk::DispatchLoaderDynami
VK_EXT_SHADER_VIEWPORT_INDEX_LAYER_EXTENSION_NAME, true);
Test(extension, ext_subgroup_size_control, VK_EXT_SUBGROUP_SIZE_CONTROL_EXTENSION_NAME,
false);
+ Test(extension, nv_device_diagnostic_checkpoints,
+ VK_NV_DEVICE_DIAGNOSTIC_CHECKPOINTS_EXTENSION_NAME, true);
}
if (khr_shader_float16_int8) {
@@ -464,6 +485,7 @@ std::vector<vk::DeviceQueueCreateInfo> VKDevice::GetDeviceQueueCreateInfos() con
std::unordered_map<vk::Format, vk::FormatProperties> VKDevice::GetFormatProperties(
const vk::DispatchLoaderDynamic& dldi, vk::PhysicalDevice physical) {
static constexpr std::array formats{vk::Format::eA8B8G8R8UnormPack32,
+ vk::Format::eA8B8G8R8UintPack32,
vk::Format::eA8B8G8R8SnormPack32,
vk::Format::eA8B8G8R8SrgbPack32,
vk::Format::eB5G6R5UnormPack16,
diff --git a/src/video_core/renderer_vulkan/vk_device.h b/src/video_core/renderer_vulkan/vk_device.h
index a844c52df..72603f9f6 100644
--- a/src/video_core/renderer_vulkan/vk_device.h
+++ b/src/video_core/renderer_vulkan/vk_device.h
@@ -39,6 +39,9 @@ public:
vk::Format GetSupportedFormat(vk::Format wanted_format, vk::FormatFeatureFlags wanted_usage,
FormatType format_type) const;
+ /// Reports a device loss.
+ void ReportLoss() const;
+
/// Returns the dispatch loader with direct function pointers of the device.
const vk::DispatchLoaderDynamic& GetDispatchLoader() const {
return dld;
@@ -159,6 +162,11 @@ public:
return ext_shader_viewport_index_layer;
}
+ /// Returns true if the device supports VK_NV_device_diagnostic_checkpoints.
+ bool IsNvDeviceDiagnosticCheckpoints() const {
+ return nv_device_diagnostic_checkpoints;
+ }
+
/// Returns the vendor name reported from Vulkan.
std::string_view GetVendorName() const {
return vendor_name;
@@ -218,6 +226,7 @@ private:
bool ext_index_type_uint8{}; ///< Support for VK_EXT_index_type_uint8.
bool ext_depth_range_unrestricted{}; ///< Support for VK_EXT_depth_range_unrestricted.
bool ext_shader_viewport_index_layer{}; ///< Support for VK_EXT_shader_viewport_index_layer.
+ bool nv_device_diagnostic_checkpoints{}; ///< Support for VK_NV_device_diagnostic_checkpoints.
// Telemetry parameters
std::string vendor_name; ///< Device's driver name.
diff --git a/src/video_core/renderer_vulkan/vk_resource_manager.cpp b/src/video_core/renderer_vulkan/vk_resource_manager.cpp
index 13c46e5b8..525b4bb46 100644
--- a/src/video_core/renderer_vulkan/vk_resource_manager.cpp
+++ b/src/video_core/renderer_vulkan/vk_resource_manager.cpp
@@ -72,12 +72,22 @@ VKFence::VKFence(const VKDevice& device, UniqueFence handle)
VKFence::~VKFence() = default;
void VKFence::Wait() {
+ static constexpr u64 timeout = std::numeric_limits<u64>::max();
const auto dev = device.GetLogical();
const auto& dld = device.GetDispatchLoader();
- dev.waitForFences({*handle}, true, std::numeric_limits<u64>::max(), dld);
+ switch (const auto result = dev.waitForFences(1, &*handle, true, timeout, dld)) {
+ case vk::Result::eSuccess:
+ return;
+ case vk::Result::eErrorDeviceLost:
+ device.ReportLoss();
+ [[fallthrough]];
+ default:
+ vk::throwResultException(result, "vk::waitForFences");
+ }
}
void VKFence::Release() {
+ ASSERT(is_owned);
is_owned = false;
}
@@ -133,8 +143,32 @@ void VKFence::Unprotect(VKResource* resource) {
protected_resources.erase(it);
}
+void VKFence::RedirectProtection(VKResource* old_resource, VKResource* new_resource) noexcept {
+ std::replace(std::begin(protected_resources), std::end(protected_resources), old_resource,
+ new_resource);
+}
+
VKFenceWatch::VKFenceWatch() = default;
+VKFenceWatch::VKFenceWatch(VKFence& initial_fence) {
+ Watch(initial_fence);
+}
+
+VKFenceWatch::VKFenceWatch(VKFenceWatch&& rhs) noexcept {
+ fence = std::exchange(rhs.fence, nullptr);
+ if (fence) {
+ fence->RedirectProtection(&rhs, this);
+ }
+}
+
+VKFenceWatch& VKFenceWatch::operator=(VKFenceWatch&& rhs) noexcept {
+ fence = std::exchange(rhs.fence, nullptr);
+ if (fence) {
+ fence->RedirectProtection(&rhs, this);
+ }
+ return *this;
+}
+
VKFenceWatch::~VKFenceWatch() {
if (fence) {
fence->Unprotect(this);
diff --git a/src/video_core/renderer_vulkan/vk_resource_manager.h b/src/video_core/renderer_vulkan/vk_resource_manager.h
index 08ee86fa6..d4cbc95a5 100644
--- a/src/video_core/renderer_vulkan/vk_resource_manager.h
+++ b/src/video_core/renderer_vulkan/vk_resource_manager.h
@@ -65,6 +65,9 @@ public:
/// Removes protection for a resource.
void Unprotect(VKResource* resource);
+ /// Redirects one protected resource to a new address.
+ void RedirectProtection(VKResource* old_resource, VKResource* new_resource) noexcept;
+
/// Retreives the fence.
operator vk::Fence() const {
return *handle;
@@ -97,8 +100,13 @@ private:
class VKFenceWatch final : public VKResource {
public:
explicit VKFenceWatch();
+ VKFenceWatch(VKFence& initial_fence);
+ VKFenceWatch(VKFenceWatch&&) noexcept;
+ VKFenceWatch(const VKFenceWatch&) = delete;
~VKFenceWatch() override;
+ VKFenceWatch& operator=(VKFenceWatch&&) noexcept;
+
/// Waits for the fence to be released.
void Wait();
@@ -116,6 +124,14 @@ public:
void OnFenceRemoval(VKFence* signaling_fence) override;
+ /**
+ * Do not use it paired with Watch. Use TryWatch instead.
+ * Returns true when the watch is free.
+ */
+ bool IsUsed() const {
+ return fence != nullptr;
+ }
+
private:
VKFence* fence{}; ///< Fence watching this resource. nullptr when the watch is free.
};
diff --git a/src/video_core/renderer_vulkan/vk_scheduler.cpp b/src/video_core/renderer_vulkan/vk_scheduler.cpp
index 0f8116458..d66133ad1 100644
--- a/src/video_core/renderer_vulkan/vk_scheduler.cpp
+++ b/src/video_core/renderer_vulkan/vk_scheduler.cpp
@@ -3,7 +3,7 @@
// Refer to the license.txt file included.
#include "common/assert.h"
-#include "common/logging/log.h"
+#include "common/microprofile.h"
#include "video_core/renderer_vulkan/declarations.h"
#include "video_core/renderer_vulkan/vk_device.h"
#include "video_core/renderer_vulkan/vk_resource_manager.h"
@@ -11,46 +11,172 @@
namespace Vulkan {
+MICROPROFILE_DECLARE(Vulkan_WaitForWorker);
+
+void VKScheduler::CommandChunk::ExecuteAll(vk::CommandBuffer cmdbuf,
+ const vk::DispatchLoaderDynamic& dld) {
+ auto command = first;
+ while (command != nullptr) {
+ auto next = command->GetNext();
+ command->Execute(cmdbuf, dld);
+ command->~Command();
+ command = next;
+ }
+
+ command_offset = 0;
+ first = nullptr;
+ last = nullptr;
+}
+
VKScheduler::VKScheduler(const VKDevice& device, VKResourceManager& resource_manager)
- : device{device}, resource_manager{resource_manager} {
- next_fence = &resource_manager.CommitFence();
+ : device{device}, resource_manager{resource_manager}, next_fence{
+ &resource_manager.CommitFence()} {
+ AcquireNewChunk();
AllocateNewContext();
+ worker_thread = std::thread(&VKScheduler::WorkerThread, this);
}
-VKScheduler::~VKScheduler() = default;
+VKScheduler::~VKScheduler() {
+ quit = true;
+ cv.notify_all();
+ worker_thread.join();
+}
void VKScheduler::Flush(bool release_fence, vk::Semaphore semaphore) {
SubmitExecution(semaphore);
- if (release_fence)
+ if (release_fence) {
current_fence->Release();
+ }
AllocateNewContext();
}
void VKScheduler::Finish(bool release_fence, vk::Semaphore semaphore) {
SubmitExecution(semaphore);
current_fence->Wait();
- if (release_fence)
+ if (release_fence) {
current_fence->Release();
+ }
AllocateNewContext();
}
+void VKScheduler::WaitWorker() {
+ MICROPROFILE_SCOPE(Vulkan_WaitForWorker);
+ DispatchWork();
+
+ bool finished = false;
+ do {
+ cv.notify_all();
+ std::unique_lock lock{mutex};
+ finished = chunk_queue.Empty();
+ } while (!finished);
+}
+
+void VKScheduler::DispatchWork() {
+ if (chunk->Empty()) {
+ return;
+ }
+ chunk_queue.Push(std::move(chunk));
+ cv.notify_all();
+ AcquireNewChunk();
+}
+
+void VKScheduler::RequestRenderpass(const vk::RenderPassBeginInfo& renderpass_bi) {
+ if (state.renderpass && renderpass_bi == *state.renderpass) {
+ return;
+ }
+ const bool end_renderpass = state.renderpass.has_value();
+ state.renderpass = renderpass_bi;
+ Record([renderpass_bi, end_renderpass](auto cmdbuf, auto& dld) {
+ if (end_renderpass) {
+ cmdbuf.endRenderPass(dld);
+ }
+ cmdbuf.beginRenderPass(renderpass_bi, vk::SubpassContents::eInline, dld);
+ });
+}
+
+void VKScheduler::RequestOutsideRenderPassOperationContext() {
+ EndRenderPass();
+}
+
+void VKScheduler::BindGraphicsPipeline(vk::Pipeline pipeline) {
+ if (state.graphics_pipeline == pipeline) {
+ return;
+ }
+ state.graphics_pipeline = pipeline;
+ Record([pipeline](auto cmdbuf, auto& dld) {
+ cmdbuf.bindPipeline(vk::PipelineBindPoint::eGraphics, pipeline, dld);
+ });
+}
+
+void VKScheduler::WorkerThread() {
+ std::unique_lock lock{mutex};
+ do {
+ cv.wait(lock, [this] { return !chunk_queue.Empty() || quit; });
+ if (quit) {
+ continue;
+ }
+ auto extracted_chunk = std::move(chunk_queue.Front());
+ chunk_queue.Pop();
+ extracted_chunk->ExecuteAll(current_cmdbuf, device.GetDispatchLoader());
+ chunk_reserve.Push(std::move(extracted_chunk));
+ } while (!quit);
+}
+
void VKScheduler::SubmitExecution(vk::Semaphore semaphore) {
+ EndPendingOperations();
+ InvalidateState();
+ WaitWorker();
+
+ std::unique_lock lock{mutex};
+
+ const auto queue = device.GetGraphicsQueue();
const auto& dld = device.GetDispatchLoader();
current_cmdbuf.end(dld);
- const auto queue = device.GetGraphicsQueue();
- const vk::SubmitInfo submit_info(0, nullptr, nullptr, 1, &current_cmdbuf, semaphore ? 1u : 0u,
+ const vk::SubmitInfo submit_info(0, nullptr, nullptr, 1, &current_cmdbuf, semaphore ? 1U : 0U,
&semaphore);
- queue.submit({submit_info}, *current_fence, dld);
+ queue.submit({submit_info}, static_cast<vk::Fence>(*current_fence), dld);
}
void VKScheduler::AllocateNewContext() {
+ std::unique_lock lock{mutex};
current_fence = next_fence;
- current_cmdbuf = resource_manager.CommitCommandBuffer(*current_fence);
next_fence = &resource_manager.CommitFence();
- const auto& dld = device.GetDispatchLoader();
- current_cmdbuf.begin({vk::CommandBufferUsageFlagBits::eOneTimeSubmit}, dld);
+ current_cmdbuf = resource_manager.CommitCommandBuffer(*current_fence);
+ current_cmdbuf.begin({vk::CommandBufferUsageFlagBits::eOneTimeSubmit},
+ device.GetDispatchLoader());
+}
+
+void VKScheduler::InvalidateState() {
+ state.graphics_pipeline = nullptr;
+ state.viewports = false;
+ state.scissors = false;
+ state.depth_bias = false;
+ state.blend_constants = false;
+ state.depth_bounds = false;
+ state.stencil_values = false;
+}
+
+void VKScheduler::EndPendingOperations() {
+ EndRenderPass();
+}
+
+void VKScheduler::EndRenderPass() {
+ if (!state.renderpass) {
+ return;
+ }
+ state.renderpass = std::nullopt;
+ Record([](auto cmdbuf, auto& dld) { cmdbuf.endRenderPass(dld); });
+}
+
+void VKScheduler::AcquireNewChunk() {
+ if (chunk_reserve.Empty()) {
+ chunk = std::make_unique<CommandChunk>();
+ return;
+ }
+ chunk = std::move(chunk_reserve.Front());
+ chunk_reserve.Pop();
}
} // namespace Vulkan
diff --git a/src/video_core/renderer_vulkan/vk_scheduler.h b/src/video_core/renderer_vulkan/vk_scheduler.h
index 0e5b49c7f..bcdffbba0 100644
--- a/src/video_core/renderer_vulkan/vk_scheduler.h
+++ b/src/video_core/renderer_vulkan/vk_scheduler.h
@@ -4,7 +4,14 @@
#pragma once
+#include <condition_variable>
+#include <memory>
+#include <optional>
+#include <stack>
+#include <thread>
+#include <utility>
#include "common/common_types.h"
+#include "common/threadsafe_queue.h"
#include "video_core/renderer_vulkan/declarations.h"
namespace Vulkan {
@@ -30,56 +37,197 @@ private:
VKFence* const& fence;
};
-class VKCommandBufferView {
+/// The scheduler abstracts command buffer and fence management with an interface that's able to do
+/// OpenGL-like operations on Vulkan command buffers.
+class VKScheduler {
public:
- VKCommandBufferView() = default;
- VKCommandBufferView(const vk::CommandBuffer& cmdbuf) : cmdbuf{cmdbuf} {}
+ explicit VKScheduler(const VKDevice& device, VKResourceManager& resource_manager);
+ ~VKScheduler();
+
+ /// Sends the current execution context to the GPU.
+ void Flush(bool release_fence = true, vk::Semaphore semaphore = nullptr);
+
+ /// Sends the current execution context to the GPU and waits for it to complete.
+ void Finish(bool release_fence = true, vk::Semaphore semaphore = nullptr);
+
+ /// Waits for the worker thread to finish executing everything. After this function returns it's
+ /// safe to touch worker resources.
+ void WaitWorker();
+
+ /// Sends currently recorded work to the worker thread.
+ void DispatchWork();
+
+ /// Requests to begin a renderpass.
+ void RequestRenderpass(const vk::RenderPassBeginInfo& renderpass_bi);
+
+ /// Requests the current executino context to be able to execute operations only allowed outside
+ /// of a renderpass.
+ void RequestOutsideRenderPassOperationContext();
+
+ /// Binds a pipeline to the current execution context.
+ void BindGraphicsPipeline(vk::Pipeline pipeline);
- const vk::CommandBuffer* operator->() const noexcept {
- return &cmdbuf;
+ /// Returns true when viewports have been set in the current command buffer.
+ bool TouchViewports() {
+ return std::exchange(state.viewports, true);
}
- operator vk::CommandBuffer() const noexcept {
- return cmdbuf;
+ /// Returns true when scissors have been set in the current command buffer.
+ bool TouchScissors() {
+ return std::exchange(state.scissors, true);
}
-private:
- const vk::CommandBuffer& cmdbuf;
-};
+ /// Returns true when depth bias have been set in the current command buffer.
+ bool TouchDepthBias() {
+ return std::exchange(state.depth_bias, true);
+ }
-/// The scheduler abstracts command buffer and fence management with an interface that's able to do
-/// OpenGL-like operations on Vulkan command buffers.
-class VKScheduler {
-public:
- explicit VKScheduler(const VKDevice& device, VKResourceManager& resource_manager);
- ~VKScheduler();
+ /// Returns true when blend constants have been set in the current command buffer.
+ bool TouchBlendConstants() {
+ return std::exchange(state.blend_constants, true);
+ }
+
+ /// Returns true when depth bounds have been set in the current command buffer.
+ bool TouchDepthBounds() {
+ return std::exchange(state.depth_bounds, true);
+ }
+
+ /// Returns true when stencil values have been set in the current command buffer.
+ bool TouchStencilValues() {
+ return std::exchange(state.stencil_values, true);
+ }
+
+ /// Send work to a separate thread.
+ template <typename T>
+ void Record(T&& command) {
+ if (chunk->Record(command)) {
+ return;
+ }
+ DispatchWork();
+ (void)chunk->Record(command);
+ }
/// Gets a reference to the current fence.
VKFenceView GetFence() const {
return current_fence;
}
- /// Gets a reference to the current command buffer.
- VKCommandBufferView GetCommandBuffer() const {
- return current_cmdbuf;
- }
+private:
+ class Command {
+ public:
+ virtual ~Command() = default;
- /// Sends the current execution context to the GPU.
- void Flush(bool release_fence = true, vk::Semaphore semaphore = nullptr);
+ virtual void Execute(vk::CommandBuffer cmdbuf,
+ const vk::DispatchLoaderDynamic& dld) const = 0;
- /// Sends the current execution context to the GPU and waits for it to complete.
- void Finish(bool release_fence = true, vk::Semaphore semaphore = nullptr);
+ Command* GetNext() const {
+ return next;
+ }
+
+ void SetNext(Command* next_) {
+ next = next_;
+ }
+
+ private:
+ Command* next = nullptr;
+ };
+
+ template <typename T>
+ class TypedCommand final : public Command {
+ public:
+ explicit TypedCommand(T&& command) : command{std::move(command)} {}
+ ~TypedCommand() override = default;
+
+ TypedCommand(TypedCommand&&) = delete;
+ TypedCommand& operator=(TypedCommand&&) = delete;
+
+ void Execute(vk::CommandBuffer cmdbuf,
+ const vk::DispatchLoaderDynamic& dld) const override {
+ command(cmdbuf, dld);
+ }
+
+ private:
+ T command;
+ };
+
+ class CommandChunk final {
+ public:
+ void ExecuteAll(vk::CommandBuffer cmdbuf, const vk::DispatchLoaderDynamic& dld);
+
+ template <typename T>
+ bool Record(T& command) {
+ using FuncType = TypedCommand<T>;
+ static_assert(sizeof(FuncType) < sizeof(data), "Lambda is too large");
+
+ if (command_offset > sizeof(data) - sizeof(FuncType)) {
+ return false;
+ }
+
+ Command* current_last = last;
+
+ last = new (data.data() + command_offset) FuncType(std::move(command));
+
+ if (current_last) {
+ current_last->SetNext(last);
+ } else {
+ first = last;
+ }
+
+ command_offset += sizeof(FuncType);
+ return true;
+ }
+
+ bool Empty() const {
+ return command_offset == 0;
+ }
+
+ private:
+ Command* first = nullptr;
+ Command* last = nullptr;
+
+ std::size_t command_offset = 0;
+ std::array<u8, 0x8000> data{};
+ };
+
+ void WorkerThread();
-private:
void SubmitExecution(vk::Semaphore semaphore);
void AllocateNewContext();
+ void InvalidateState();
+
+ void EndPendingOperations();
+
+ void EndRenderPass();
+
+ void AcquireNewChunk();
+
const VKDevice& device;
VKResourceManager& resource_manager;
vk::CommandBuffer current_cmdbuf;
VKFence* current_fence = nullptr;
VKFence* next_fence = nullptr;
+
+ struct State {
+ std::optional<vk::RenderPassBeginInfo> renderpass;
+ vk::Pipeline graphics_pipeline;
+ bool viewports = false;
+ bool scissors = false;
+ bool depth_bias = false;
+ bool blend_constants = false;
+ bool depth_bounds = false;
+ bool stencil_values = false;
+ } state;
+
+ std::unique_ptr<CommandChunk> chunk;
+ std::thread worker_thread;
+
+ Common::SPSCQueue<std::unique_ptr<CommandChunk>> chunk_queue;
+ Common::SPSCQueue<std::unique_ptr<CommandChunk>> chunk_reserve;
+ std::mutex mutex;
+ std::condition_variable cv;
+ bool quit = false;
};
} // namespace Vulkan
diff --git a/src/video_core/renderer_vulkan/vk_shader_decompiler.cpp b/src/video_core/renderer_vulkan/vk_shader_decompiler.cpp
index 6227bc70b..a8baf91de 100644
--- a/src/video_core/renderer_vulkan/vk_shader_decompiler.cpp
+++ b/src/video_core/renderer_vulkan/vk_shader_decompiler.cpp
@@ -543,7 +543,7 @@ private:
}
for (u32 rt = 0; rt < static_cast<u32>(frag_colors.size()); ++rt) {
- if (!IsRenderTargetUsed(rt)) {
+ if (!specialization.enabled_rendertargets[rt]) {
continue;
}
@@ -1555,40 +1555,48 @@ private:
Expression Texture(Operation operation) {
const auto& meta = std::get<MetaTexture>(operation.GetMeta());
- UNIMPLEMENTED_IF(!meta.aoffi.empty());
const bool can_implicit = stage == ShaderType::Fragment;
const Id sampler = GetTextureSampler(operation);
const Id coords = GetCoordinates(operation, Type::Float);
+ std::vector<Id> operands;
+ spv::ImageOperandsMask mask{};
+ if (meta.bias) {
+ mask = mask | spv::ImageOperandsMask::Bias;
+ operands.push_back(AsFloat(Visit(meta.bias)));
+ }
+
+ if (!can_implicit) {
+ mask = mask | spv::ImageOperandsMask::Lod;
+ operands.push_back(v_float_zero);
+ }
+
+ if (!meta.aoffi.empty()) {
+ mask = mask | spv::ImageOperandsMask::Offset;
+ operands.push_back(GetOffsetCoordinates(operation));
+ }
+
if (meta.depth_compare) {
// Depth sampling
UNIMPLEMENTED_IF(meta.bias);
const Id dref = AsFloat(Visit(meta.depth_compare));
if (can_implicit) {
- return {OpImageSampleDrefImplicitLod(t_float, sampler, coords, dref, {}),
- Type::Float};
+ return {
+ OpImageSampleDrefImplicitLod(t_float, sampler, coords, dref, mask, operands),
+ Type::Float};
} else {
- return {OpImageSampleDrefExplicitLod(t_float, sampler, coords, dref,
- spv::ImageOperandsMask::Lod, v_float_zero),
- Type::Float};
+ return {
+ OpImageSampleDrefExplicitLod(t_float, sampler, coords, dref, mask, operands),
+ Type::Float};
}
}
- std::vector<Id> operands;
- spv::ImageOperandsMask mask{};
- if (meta.bias) {
- mask = mask | spv::ImageOperandsMask::Bias;
- operands.push_back(AsFloat(Visit(meta.bias)));
- }
-
Id texture;
if (can_implicit) {
texture = OpImageSampleImplicitLod(t_float4, sampler, coords, mask, operands);
} else {
- texture = OpImageSampleExplicitLod(t_float4, sampler, coords,
- mask | spv::ImageOperandsMask::Lod, v_float_zero,
- operands);
+ texture = OpImageSampleExplicitLod(t_float4, sampler, coords, mask, operands);
}
return GetTextureElement(operation, texture, Type::Float);
}
@@ -1601,7 +1609,8 @@ private:
const Id lod = AsFloat(Visit(meta.lod));
spv::ImageOperandsMask mask = spv::ImageOperandsMask::Lod;
- std::vector<Id> operands;
+ std::vector<Id> operands{lod};
+
if (!meta.aoffi.empty()) {
mask = mask | spv::ImageOperandsMask::Offset;
operands.push_back(GetOffsetCoordinates(operation));
@@ -1609,11 +1618,10 @@ private:
if (meta.sampler.IsShadow()) {
const Id dref = AsFloat(Visit(meta.depth_compare));
- return {
- OpImageSampleDrefExplicitLod(t_float, sampler, coords, dref, mask, lod, operands),
- Type::Float};
+ return {OpImageSampleDrefExplicitLod(t_float, sampler, coords, dref, mask, operands),
+ Type::Float};
}
- const Id texture = OpImageSampleExplicitLod(t_float4, sampler, coords, mask, lod, operands);
+ const Id texture = OpImageSampleExplicitLod(t_float4, sampler, coords, mask, operands);
return GetTextureElement(operation, texture, Type::Float);
}
@@ -1722,7 +1730,7 @@ private:
const std::vector grad = {dx, dy};
static constexpr auto mask = spv::ImageOperandsMask::Grad;
- const Id texture = OpImageSampleImplicitLod(t_float4, sampler, coords, mask, grad);
+ const Id texture = OpImageSampleExplicitLod(t_float4, sampler, coords, mask, grad);
return GetTextureElement(operation, texture, Type::Float);
}
@@ -1833,7 +1841,7 @@ private:
}
void PreExit() {
- if (stage == ShaderType::Vertex) {
+ if (stage == ShaderType::Vertex && specialization.ndc_minus_one_to_one) {
const u32 position_index = out_indices.position.value();
const Id z_pointer = AccessElement(t_out_float, out_vertex, position_index, 2U);
const Id w_pointer = AccessElement(t_out_float, out_vertex, position_index, 3U);
@@ -1860,12 +1868,18 @@ private:
// rendertargets/components are skipped in the register assignment.
u32 current_reg = 0;
for (u32 rt = 0; rt < Maxwell::NumRenderTargets; ++rt) {
+ if (!specialization.enabled_rendertargets[rt]) {
+ // Skip rendertargets that are not enabled
+ continue;
+ }
// TODO(Subv): Figure out how dual-source blending is configured in the Switch.
for (u32 component = 0; component < 4; ++component) {
+ const Id pointer = AccessElement(t_out_float, frag_colors.at(rt), component);
if (header.ps.IsColorComponentOutputEnabled(rt, component)) {
- OpStore(AccessElement(t_out_float, frag_colors.at(rt), component),
- SafeGetRegister(current_reg));
+ OpStore(pointer, SafeGetRegister(current_reg));
++current_reg;
+ } else {
+ OpStore(pointer, component == 3 ? v_float_one : v_float_zero);
}
}
}
@@ -1995,15 +2009,6 @@ private:
return DeclareBuiltIn(builtin, spv::StorageClass::Input, type, std::move(name));
}
- bool IsRenderTargetUsed(u32 rt) const {
- for (u32 component = 0; component < 4; ++component) {
- if (header.ps.IsColorComponentOutputEnabled(rt, component)) {
- return true;
- }
- }
- return false;
- }
-
template <typename... Args>
Id AccessElement(Id pointer_type, Id composite, Args... elements_) {
std::vector<Id> members;
@@ -2552,29 +2557,7 @@ public:
}
Id operator()(const ExprCondCode& expr) {
- const Node cc = decomp.ir.GetConditionCode(expr.cc);
- Id target;
-
- if (const auto pred = std::get_if<PredicateNode>(&*cc)) {
- const auto index = pred->GetIndex();
- switch (index) {
- case Tegra::Shader::Pred::NeverExecute:
- target = decomp.v_false;
- break;
- case Tegra::Shader::Pred::UnusedIndex:
- target = decomp.v_true;
- break;
- default:
- target = decomp.predicates.at(index);
- break;
- }
- } else if (const auto flag = std::get_if<InternalFlagNode>(&*cc)) {
- target = decomp.internal_flags.at(static_cast<u32>(flag->GetFlag()));
- } else {
- UNREACHABLE();
- }
-
- return decomp.OpLoad(decomp.t_bool, target);
+ return decomp.AsBool(decomp.Visit(decomp.ir.GetConditionCode(expr.cc)));
}
Id operator()(const ExprVar& expr) {
@@ -2589,7 +2572,7 @@ public:
const Id target = decomp.Constant(decomp.t_uint, expr.value);
Id gpr = decomp.OpLoad(decomp.t_float, decomp.registers.at(expr.gpr));
gpr = decomp.OpBitcast(decomp.t_uint, gpr);
- return decomp.OpLogicalEqual(decomp.t_uint, gpr, target);
+ return decomp.OpIEqual(decomp.t_bool, gpr, target);
}
Id Visit(const Expr& node) {
@@ -2659,11 +2642,11 @@ public:
const Id loop_label = decomp.OpLabel();
const Id endloop_label = decomp.OpLabel();
const Id loop_start_block = decomp.OpLabel();
- const Id loop_end_block = decomp.OpLabel();
+ const Id loop_continue_block = decomp.OpLabel();
current_loop_exit = endloop_label;
decomp.OpBranch(loop_label);
decomp.AddLabel(loop_label);
- decomp.OpLoopMerge(endloop_label, loop_end_block, spv::LoopControlMask::MaskNone);
+ decomp.OpLoopMerge(endloop_label, loop_continue_block, spv::LoopControlMask::MaskNone);
decomp.OpBranch(loop_start_block);
decomp.AddLabel(loop_start_block);
ASTNode current = ast.nodes.GetFirst();
@@ -2671,6 +2654,8 @@ public:
Visit(current);
current = current->GetNext();
}
+ decomp.OpBranch(loop_continue_block);
+ decomp.AddLabel(loop_continue_block);
ExprDecompiler expr_parser{decomp};
const Id condition = expr_parser.Visit(ast.condition);
decomp.OpBranchConditional(condition, loop_label, endloop_label);
diff --git a/src/video_core/renderer_vulkan/vk_shader_decompiler.h b/src/video_core/renderer_vulkan/vk_shader_decompiler.h
index 2b01321b6..10794be1c 100644
--- a/src/video_core/renderer_vulkan/vk_shader_decompiler.h
+++ b/src/video_core/renderer_vulkan/vk_shader_decompiler.h
@@ -94,6 +94,7 @@ struct Specialization final {
Maxwell::PrimitiveTopology primitive_topology{};
std::optional<float> point_size{};
std::array<Maxwell::VertexAttribute::Type, Maxwell::NumVertexAttributes> attribute_types{};
+ bool ndc_minus_one_to_one{};
// Tessellation specific
struct {
@@ -101,6 +102,9 @@ struct Specialization final {
Maxwell::TessellationSpacing spacing{};
bool clockwise{};
} tessellation;
+
+ // Fragment specific
+ std::bitset<8> enabled_rendertargets;
};
// Old gcc versions don't consider this trivially copyable.
// static_assert(std::is_trivially_copyable_v<Specialization>);
diff --git a/src/video_core/shader/decode/conversion.cpp b/src/video_core/shader/decode/conversion.cpp
index 32facd6ba..0eeb75559 100644
--- a/src/video_core/shader/decode/conversion.cpp
+++ b/src/video_core/shader/decode/conversion.cpp
@@ -63,12 +63,11 @@ u32 ShaderIR::DecodeConversion(NodeBlock& bb, u32 pc) {
case OpCode::Id::I2F_R:
case OpCode::Id::I2F_C:
case OpCode::Id::I2F_IMM: {
- UNIMPLEMENTED_IF(instr.conversion.int_src.selector != 0);
UNIMPLEMENTED_IF(instr.conversion.dst_size == Register::Size::Long);
UNIMPLEMENTED_IF_MSG(instr.generates_cc,
"Condition codes generation in I2F is not implemented");
- Node value = [&]() {
+ Node value = [&] {
switch (opcode->get().GetId()) {
case OpCode::Id::I2F_R:
return GetRegister(instr.gpr20);
@@ -81,7 +80,19 @@ u32 ShaderIR::DecodeConversion(NodeBlock& bb, u32 pc) {
return Immediate(0);
}
}();
+
const bool input_signed = instr.conversion.is_input_signed;
+
+ if (instr.conversion.src_size == Register::Size::Byte) {
+ const u32 offset = static_cast<u32>(instr.conversion.int_src.selector) * 8;
+ if (offset > 0) {
+ value = SignedOperation(OperationCode::ILogicalShiftRight, input_signed,
+ std::move(value), Immediate(offset));
+ }
+ } else {
+ UNIMPLEMENTED_IF(instr.conversion.int_src.selector != 0);
+ }
+
value = ConvertIntegerSize(value, instr.conversion.src_size, input_signed);
value = GetOperandAbsNegInteger(value, instr.conversion.abs_a, false, input_signed);
value = SignedOperation(OperationCode::FCastInteger, input_signed, PRECISE, value);
diff --git a/src/video_core/shader/decode/memory.cpp b/src/video_core/shader/decode/memory.cpp
index 78e92f52e..c934d0719 100644
--- a/src/video_core/shader/decode/memory.cpp
+++ b/src/video_core/shader/decode/memory.cpp
@@ -22,7 +22,23 @@ using Tegra::Shader::Register;
namespace {
-u32 GetUniformTypeElementsCount(Tegra::Shader::UniformType uniform_type) {
+u32 GetLdgMemorySize(Tegra::Shader::UniformType uniform_type) {
+ switch (uniform_type) {
+ case Tegra::Shader::UniformType::UnsignedByte:
+ case Tegra::Shader::UniformType::Single:
+ return 1;
+ case Tegra::Shader::UniformType::Double:
+ return 2;
+ case Tegra::Shader::UniformType::Quad:
+ case Tegra::Shader::UniformType::UnsignedQuad:
+ return 4;
+ default:
+ UNIMPLEMENTED_MSG("Unimplemented size={}!", static_cast<u32>(uniform_type));
+ return 1;
+ }
+}
+
+u32 GetStgMemorySize(Tegra::Shader::UniformType uniform_type) {
switch (uniform_type) {
case Tegra::Shader::UniformType::Single:
return 1;
@@ -170,7 +186,7 @@ u32 ShaderIR::DecodeMemory(NodeBlock& bb, u32 pc) {
const auto [real_address_base, base_address, descriptor] =
TrackGlobalMemory(bb, instr, false);
- const u32 count = GetUniformTypeElementsCount(type);
+ const u32 count = GetLdgMemorySize(type);
if (!real_address_base || !base_address) {
// Tracking failed, load zeroes.
for (u32 i = 0; i < count; ++i) {
@@ -181,12 +197,22 @@ u32 ShaderIR::DecodeMemory(NodeBlock& bb, u32 pc) {
for (u32 i = 0; i < count; ++i) {
const Node it_offset = Immediate(i * 4);
- const Node real_address =
- Operation(OperationCode::UAdd, NO_PRECISE, real_address_base, it_offset);
- const Node gmem = MakeNode<GmemNode>(real_address, base_address, descriptor);
+ const Node real_address = Operation(OperationCode::UAdd, real_address_base, it_offset);
+ Node gmem = MakeNode<GmemNode>(real_address, base_address, descriptor);
+
+ if (type == Tegra::Shader::UniformType::UnsignedByte) {
+ // To handle unaligned loads get the byte used to dereferenced global memory
+ // and extract that byte from the loaded uint32.
+ Node byte = Operation(OperationCode::UBitwiseAnd, real_address, Immediate(3));
+ byte = Operation(OperationCode::ULogicalShiftLeft, std::move(byte), Immediate(3));
+
+ gmem = Operation(OperationCode::UBitfieldExtract, std::move(gmem), std::move(byte),
+ Immediate(8));
+ }
SetTemporary(bb, i, gmem);
}
+
for (u32 i = 0; i < count; ++i) {
SetRegister(bb, instr.gpr0.Value() + i, GetTemporary(i));
}
@@ -276,7 +302,7 @@ u32 ShaderIR::DecodeMemory(NodeBlock& bb, u32 pc) {
break;
}
- const u32 count = GetUniformTypeElementsCount(type);
+ const u32 count = GetStgMemorySize(type);
for (u32 i = 0; i < count; ++i) {
const Node it_offset = Immediate(i * 4);
const Node real_address = Operation(OperationCode::UAdd, real_address_base, it_offset);
diff --git a/src/video_core/shader/decode/texture.cpp b/src/video_core/shader/decode/texture.cpp
index dd8ff851e..4b14cdf58 100644
--- a/src/video_core/shader/decode/texture.cpp
+++ b/src/video_core/shader/decode/texture.cpp
@@ -751,13 +751,18 @@ Node4 ShaderIR::GetTldsCode(Instruction instr, TextureType texture_type, bool is
// When lod is used always is in gpr20
const Node lod = lod_enabled ? GetRegister(instr.gpr20) : Immediate(0);
- // Fill empty entries from the guest sampler.
+ // Fill empty entries from the guest sampler
const std::size_t entry_coord_count = GetCoordCount(sampler.GetType());
if (type_coord_count != entry_coord_count) {
LOG_WARNING(HW_GPU, "Bound and built texture types mismatch");
- }
- for (std::size_t i = type_coord_count; i < entry_coord_count; ++i) {
- coords.push_back(GetRegister(Register::ZeroIndex));
+
+ // When the size is higher we insert zeroes
+ for (std::size_t i = type_coord_count; i < entry_coord_count; ++i) {
+ coords.push_back(GetRegister(Register::ZeroIndex));
+ }
+
+ // Then we ensure the size matches the number of entries (dropping unused values)
+ coords.resize(entry_coord_count);
}
Node4 values;
diff --git a/src/video_core/texture_cache/surface_params.cpp b/src/video_core/texture_cache/surface_params.cpp
index a4f1edd9a..38b3a4ba8 100644
--- a/src/video_core/texture_cache/surface_params.cpp
+++ b/src/video_core/texture_cache/surface_params.cpp
@@ -392,4 +392,42 @@ std::string SurfaceParams::TargetName() const {
}
}
+u32 SurfaceParams::GetBlockSize() const {
+ const u32 x = 64U << block_width;
+ const u32 y = 8U << block_height;
+ const u32 z = 1U << block_depth;
+ return x * y * z;
+}
+
+std::pair<u32, u32> SurfaceParams::GetBlockXY() const {
+ const u32 x_pixels = 64U / GetBytesPerPixel();
+ const u32 x = x_pixels << block_width;
+ const u32 y = 8U << block_height;
+ return {x, y};
+}
+
+std::tuple<u32, u32, u32> SurfaceParams::GetBlockOffsetXYZ(u32 offset) const {
+ const auto div_ceil = [](const u32 x, const u32 y) { return ((x + y - 1) / y); };
+ const u32 block_size = GetBlockSize();
+ const u32 block_index = offset / block_size;
+ const u32 gob_offset = offset % block_size;
+ const u32 gob_index = gob_offset / static_cast<u32>(Tegra::Texture::GetGOBSize());
+ const u32 x_gob_pixels = 64U / GetBytesPerPixel();
+ const u32 x_block_pixels = x_gob_pixels << block_width;
+ const u32 y_block_pixels = 8U << block_height;
+ const u32 z_block_pixels = 1U << block_depth;
+ const u32 x_blocks = div_ceil(width, x_block_pixels);
+ const u32 y_blocks = div_ceil(height, y_block_pixels);
+ const u32 z_blocks = div_ceil(depth, z_block_pixels);
+ const u32 base_x = block_index % x_blocks;
+ const u32 base_y = (block_index / x_blocks) % y_blocks;
+ const u32 base_z = (block_index / (x_blocks * y_blocks)) % z_blocks;
+ u32 x = base_x * x_block_pixels;
+ u32 y = base_y * y_block_pixels;
+ u32 z = base_z * z_block_pixels;
+ z += gob_index >> block_height;
+ y += (gob_index * 8U) % y_block_pixels;
+ return {x, y, z};
+}
+
} // namespace VideoCommon
diff --git a/src/video_core/texture_cache/surface_params.h b/src/video_core/texture_cache/surface_params.h
index 129817ad3..992b5c022 100644
--- a/src/video_core/texture_cache/surface_params.h
+++ b/src/video_core/texture_cache/surface_params.h
@@ -4,6 +4,8 @@
#pragma once
+#include <utility>
+
#include "common/alignment.h"
#include "common/bit_util.h"
#include "common/cityhash.h"
@@ -136,6 +138,15 @@ public:
std::size_t GetConvertedMipmapSize(u32 level) const;
+ /// Get this texture Tegra Block size in guest memory layout
+ u32 GetBlockSize() const;
+
+ /// Get X, Y coordinates max sizes of a single block.
+ std::pair<u32, u32> GetBlockXY() const;
+
+ /// Get the offset in x, y, z coordinates from a memory offset
+ std::tuple<u32, u32, u32> GetBlockOffsetXYZ(u32 offset) const;
+
/// Returns the size of a layer in bytes in guest memory.
std::size_t GetGuestLayerSize() const {
return GetLayerSize(false, false);
@@ -269,7 +280,8 @@ private:
/// Returns the size of all mipmap levels and aligns as needed.
std::size_t GetInnerMemorySize(bool as_host_size, bool layer_only, bool uncompressed) const {
- return GetLayerSize(as_host_size, uncompressed) * (layer_only ? 1U : depth);
+ return GetLayerSize(as_host_size, uncompressed) *
+ (layer_only ? 1U : (is_layered ? depth : 1U));
}
/// Returns the size of a layer
diff --git a/src/video_core/texture_cache/texture_cache.h b/src/video_core/texture_cache/texture_cache.h
index 02d2e9136..f4c015635 100644
--- a/src/video_core/texture_cache/texture_cache.h
+++ b/src/video_core/texture_cache/texture_cache.h
@@ -616,6 +616,86 @@ private:
}
/**
+ * Takes care of managing 3D textures and its slices. Does HLE methods for reconstructing the 3D
+ * textures within the GPU if possible. Falls back to LLE when it isn't possible to use any of
+ * the HLE methods.
+ *
+ * @param overlaps The overlapping surfaces registered in the cache.
+ * @param params The parameters on the new surface.
+ * @param gpu_addr The starting address of the new surface.
+ * @param cache_addr The starting address of the new surface on physical memory.
+ * @param preserve_contents Indicates that the new surface should be loaded from memory or
+ * left blank.
+ */
+ std::optional<std::pair<TSurface, TView>> Manage3DSurfaces(std::vector<TSurface>& overlaps,
+ const SurfaceParams& params,
+ const GPUVAddr gpu_addr,
+ const CacheAddr cache_addr,
+ bool preserve_contents) {
+ if (params.target == SurfaceTarget::Texture3D) {
+ bool failed = false;
+ if (params.num_levels > 1) {
+ // We can't handle mipmaps in 3D textures yet, better fallback to LLE approach
+ return std::nullopt;
+ }
+ TSurface new_surface = GetUncachedSurface(gpu_addr, params);
+ bool modified = false;
+ for (auto& surface : overlaps) {
+ const SurfaceParams& src_params = surface->GetSurfaceParams();
+ if (src_params.target != SurfaceTarget::Texture2D) {
+ failed = true;
+ break;
+ }
+ if (src_params.height != params.height) {
+ failed = true;
+ break;
+ }
+ if (src_params.block_depth != params.block_depth ||
+ src_params.block_height != params.block_height) {
+ failed = true;
+ break;
+ }
+ const u32 offset = static_cast<u32>(surface->GetCacheAddr() - cache_addr);
+ const auto [x, y, z] = params.GetBlockOffsetXYZ(offset);
+ modified |= surface->IsModified();
+ const CopyParams copy_params(0, 0, 0, 0, 0, z, 0, 0, params.width, params.height,
+ 1);
+ ImageCopy(surface, new_surface, copy_params);
+ }
+ if (failed) {
+ return std::nullopt;
+ }
+ for (const auto& surface : overlaps) {
+ Unregister(surface);
+ }
+ new_surface->MarkAsModified(modified, Tick());
+ Register(new_surface);
+ auto view = new_surface->GetMainView();
+ return {{std::move(new_surface), view}};
+ } else {
+ for (const auto& surface : overlaps) {
+ if (!surface->MatchTarget(params.target)) {
+ if (overlaps.size() == 1 && surface->GetCacheAddr() == cache_addr) {
+ if (Settings::values.use_accurate_gpu_emulation) {
+ return std::nullopt;
+ }
+ Unregister(surface);
+ return InitializeSurface(gpu_addr, params, preserve_contents);
+ }
+ return std::nullopt;
+ }
+ if (surface->GetCacheAddr() != cache_addr) {
+ continue;
+ }
+ if (surface->MatchesStructure(params) == MatchStructureResult::FullMatch) {
+ return {{surface, surface->GetMainView()}};
+ }
+ }
+ return InitializeSurface(gpu_addr, params, preserve_contents);
+ }
+ }
+
+ /**
* Gets the starting address and parameters of a candidate surface and tries
* to find a matching surface within the cache. This is done in 3 big steps:
*
@@ -687,6 +767,15 @@ private:
}
}
+ // Check if it's a 3D texture
+ if (params.block_depth > 0) {
+ auto surface =
+ Manage3DSurfaces(overlaps, params, gpu_addr, cache_addr, preserve_contents);
+ if (surface) {
+ return *surface;
+ }
+ }
+
// Split cases between 1 overlap or many.
if (overlaps.size() == 1) {
TSurface current_surface = overlaps[0];
diff --git a/src/video_core/textures/decoders.h b/src/video_core/textures/decoders.h
index f1e3952bc..e5eac3f3b 100644
--- a/src/video_core/textures/decoders.h
+++ b/src/video_core/textures/decoders.h
@@ -12,6 +12,10 @@ namespace Tegra::Texture {
// GOBSize constant. Calculated by 64 bytes in x multiplied by 8 y coords, represents
// an small rect of (64/bytes_per_pixel)X8.
+inline std::size_t GetGOBSize() {
+ return 512;
+}
+
inline std::size_t GetGOBSizeShift() {
return 9;
}