summaryrefslogtreecommitdiffstats
path: root/src/video_core
diff options
context:
space:
mode:
Diffstat (limited to 'src/video_core')
-rw-r--r--src/video_core/CMakeLists.txt13
-rw-r--r--src/video_core/dma_pusher.cpp4
-rw-r--r--src/video_core/engines/kepler_memory.cpp47
-rw-r--r--src/video_core/engines/kepler_memory.h24
-rw-r--r--src/video_core/engines/maxwell_3d.cpp43
-rw-r--r--src/video_core/engines/maxwell_3d.h6
-rw-r--r--src/video_core/engines/shader_bytecode.h85
-rw-r--r--src/video_core/gpu.h5
-rw-r--r--src/video_core/gpu_asynch.cpp6
-rw-r--r--src/video_core/gpu_asynch.h5
-rw-r--r--src/video_core/gpu_synch.cpp4
-rw-r--r--src/video_core/gpu_synch.h1
-rw-r--r--src/video_core/gpu_thread.cpp17
-rw-r--r--src/video_core/gpu_thread.h6
-rw-r--r--src/video_core/memory_manager.cpp64
-rw-r--r--src/video_core/memory_manager.h29
-rw-r--r--src/video_core/renderer_opengl/gl_global_cache.cpp42
-rw-r--r--src/video_core/renderer_opengl/gl_global_cache.h16
-rw-r--r--src/video_core/renderer_opengl/gl_rasterizer.cpp116
-rw-r--r--src/video_core/renderer_opengl/gl_rasterizer.h36
-rw-r--r--src/video_core/renderer_opengl/gl_rasterizer_cache.cpp41
-rw-r--r--src/video_core/renderer_opengl/gl_sampler_cache.cpp52
-rw-r--r--src/video_core/renderer_opengl/gl_sampler_cache.h25
-rw-r--r--src/video_core/renderer_opengl/gl_shader_cache.cpp13
-rw-r--r--src/video_core/renderer_opengl/gl_shader_decompiler.cpp162
-rw-r--r--src/video_core/renderer_opengl/gl_shader_decompiler.h18
-rw-r--r--src/video_core/renderer_opengl/gl_shader_disk_cache.cpp27
-rw-r--r--src/video_core/renderer_opengl/gl_shader_manager.cpp34
-rw-r--r--src/video_core/renderer_opengl/gl_shader_manager.h61
-rw-r--r--src/video_core/renderer_vulkan/vk_sampler_cache.cpp40
-rw-r--r--src/video_core/renderer_vulkan/vk_sampler_cache.h36
-rw-r--r--src/video_core/renderer_vulkan/vk_shader_decompiler.cpp1396
-rw-r--r--src/video_core/renderer_vulkan/vk_shader_decompiler.h80
-rw-r--r--src/video_core/sampler_cache.cpp21
-rw-r--r--src/video_core/sampler_cache.h60
-rw-r--r--src/video_core/shader/decode/arithmetic_half.cpp15
-rw-r--r--src/video_core/shader/decode/arithmetic_half_immediate.cpp17
-rw-r--r--src/video_core/shader/decode/conversion.cpp69
-rw-r--r--src/video_core/shader/decode/half_set.cpp16
-rw-r--r--src/video_core/shader/decode/half_set_predicate.cpp8
-rw-r--r--src/video_core/shader/decode/hfma2.cpp12
-rw-r--r--src/video_core/shader/decode/memory.cpp118
-rw-r--r--src/video_core/shader/decode/texture.cpp113
-rw-r--r--src/video_core/shader/shader_ir.cpp51
-rw-r--r--src/video_core/shader/shader_ir.h101
-rw-r--r--src/video_core/surface.cpp2
-rw-r--r--src/video_core/texture_cache.cpp386
-rw-r--r--src/video_core/texture_cache.h586
-rw-r--r--src/video_core/textures/decoders.cpp23
-rw-r--r--src/video_core/textures/decoders.h4
-rw-r--r--src/video_core/video_core.cpp10
-rw-r--r--src/video_core/video_core.h7
52 files changed, 3614 insertions, 559 deletions
diff --git a/src/video_core/CMakeLists.txt b/src/video_core/CMakeLists.txt
index 5de3eb686..6821f275d 100644
--- a/src/video_core/CMakeLists.txt
+++ b/src/video_core/CMakeLists.txt
@@ -48,6 +48,8 @@ add_library(video_core STATIC
renderer_opengl/gl_rasterizer_cache.h
renderer_opengl/gl_resource_manager.cpp
renderer_opengl/gl_resource_manager.h
+ renderer_opengl/gl_sampler_cache.cpp
+ renderer_opengl/gl_sampler_cache.h
renderer_opengl/gl_shader_cache.cpp
renderer_opengl/gl_shader_cache.h
renderer_opengl/gl_shader_decompiler.cpp
@@ -69,6 +71,8 @@ add_library(video_core STATIC
renderer_opengl/renderer_opengl.h
renderer_opengl/utils.cpp
renderer_opengl/utils.h
+ sampler_cache.cpp
+ sampler_cache.h
shader/decode/arithmetic.cpp
shader/decode/arithmetic_immediate.cpp
shader/decode/bfe.cpp
@@ -108,6 +112,8 @@ add_library(video_core STATIC
textures/decoders.cpp
textures/decoders.h
textures/texture.h
+ texture_cache.cpp
+ texture_cache.h
video_core.cpp
video_core.h
)
@@ -129,12 +135,14 @@ if (ENABLE_VULKAN)
renderer_vulkan/vk_sampler_cache.h
renderer_vulkan/vk_scheduler.cpp
renderer_vulkan/vk_scheduler.h
+ renderer_vulkan/vk_shader_decompiler.cpp
+ renderer_vulkan/vk_shader_decompiler.h
renderer_vulkan/vk_stream_buffer.cpp
renderer_vulkan/vk_stream_buffer.h
renderer_vulkan/vk_swapchain.cpp
renderer_vulkan/vk_swapchain.h)
- target_include_directories(video_core PRIVATE ../../externals/Vulkan-Headers/include)
+ target_include_directories(video_core PRIVATE sirit ../../externals/Vulkan-Headers/include)
target_compile_definitions(video_core PRIVATE HAS_VULKAN)
endif()
@@ -142,3 +150,6 @@ create_target_directory_groups(video_core)
target_link_libraries(video_core PUBLIC common core)
target_link_libraries(video_core PRIVATE glad)
+if (ENABLE_VULKAN)
+ target_link_libraries(video_core PRIVATE sirit)
+endif()
diff --git a/src/video_core/dma_pusher.cpp b/src/video_core/dma_pusher.cpp
index 046d047cb..6674d9405 100644
--- a/src/video_core/dma_pusher.cpp
+++ b/src/video_core/dma_pusher.cpp
@@ -57,8 +57,8 @@ bool DmaPusher::Step() {
// Push buffer non-empty, read a word
command_headers.resize(command_list_header.size);
- gpu.MemoryManager().ReadBlock(dma_get, command_headers.data(),
- command_list_header.size * sizeof(u32));
+ gpu.MemoryManager().ReadBlockUnsafe(dma_get, command_headers.data(),
+ command_list_header.size * sizeof(u32));
for (const CommandHeader& command_header : command_headers) {
diff --git a/src/video_core/engines/kepler_memory.cpp b/src/video_core/engines/kepler_memory.cpp
index cd51a31d7..7387886a3 100644
--- a/src/video_core/engines/kepler_memory.cpp
+++ b/src/video_core/engines/kepler_memory.cpp
@@ -10,6 +10,7 @@
#include "video_core/memory_manager.h"
#include "video_core/rasterizer_interface.h"
#include "video_core/renderer_base.h"
+#include "video_core/textures/decoders.h"
namespace Tegra::Engines {
@@ -27,30 +28,46 @@ void KeplerMemory::CallMethod(const GPU::MethodCall& method_call) {
switch (method_call.method) {
case KEPLERMEMORY_REG_INDEX(exec): {
- state.write_offset = 0;
+ ProcessExec();
break;
}
case KEPLERMEMORY_REG_INDEX(data): {
- ProcessData(method_call.argument);
+ ProcessData(method_call.argument, method_call.IsLastCall());
break;
}
}
}
-void KeplerMemory::ProcessData(u32 data) {
- ASSERT_MSG(regs.exec.linear, "Non-linear uploads are not supported");
- ASSERT(regs.dest.x == 0 && regs.dest.y == 0 && regs.dest.z == 0);
-
- // We have to invalidate the destination region to evict any outdated surfaces from the cache.
- // We do this before actually writing the new data because the destination address might
- // contain a dirty surface that will have to be written back to memory.
- const GPUVAddr address{regs.dest.Address() + state.write_offset * sizeof(u32)};
- rasterizer.InvalidateRegion(ToCacheAddr(memory_manager.GetPointer(address)), sizeof(u32));
- memory_manager.Write<u32>(address, data);
-
- system.GPU().Maxwell3D().dirty_flags.OnMemoryWrite();
+void KeplerMemory::ProcessExec() {
+ state.write_offset = 0;
+ state.copy_size = regs.line_length_in * regs.line_count;
+ state.inner_buffer.resize(state.copy_size);
+}
- state.write_offset++;
+void KeplerMemory::ProcessData(u32 data, bool is_last_call) {
+ const u32 sub_copy_size = std::min(4U, state.copy_size - state.write_offset);
+ std::memcpy(&state.inner_buffer[state.write_offset], &regs.data, sub_copy_size);
+ state.write_offset += sub_copy_size;
+ if (is_last_call) {
+ const GPUVAddr address{regs.dest.Address()};
+ if (regs.exec.linear != 0) {
+ memory_manager.WriteBlock(address, state.inner_buffer.data(), state.copy_size);
+ } else {
+ UNIMPLEMENTED_IF(regs.dest.z != 0);
+ UNIMPLEMENTED_IF(regs.dest.depth != 1);
+ UNIMPLEMENTED_IF(regs.dest.BlockWidth() != 1);
+ UNIMPLEMENTED_IF(regs.dest.BlockDepth() != 1);
+ const std::size_t dst_size = Tegra::Texture::CalculateSize(
+ true, 1, regs.dest.width, regs.dest.height, 1, regs.dest.BlockHeight(), 1);
+ std::vector<u8> tmp_buffer(dst_size);
+ memory_manager.ReadBlock(address, tmp_buffer.data(), dst_size);
+ Tegra::Texture::SwizzleKepler(regs.dest.width, regs.dest.height, regs.dest.x,
+ regs.dest.y, regs.dest.BlockHeight(), state.copy_size,
+ state.inner_buffer.data(), tmp_buffer.data());
+ memory_manager.WriteBlock(address, tmp_buffer.data(), dst_size);
+ }
+ system.GPU().Maxwell3D().dirty_flags.OnMemoryWrite();
+ }
}
} // namespace Tegra::Engines
diff --git a/src/video_core/engines/kepler_memory.h b/src/video_core/engines/kepler_memory.h
index 78b6c3e45..5f892ddad 100644
--- a/src/video_core/engines/kepler_memory.h
+++ b/src/video_core/engines/kepler_memory.h
@@ -6,6 +6,7 @@
#include <array>
#include <cstddef>
+#include <vector>
#include "common/bit_field.h"
#include "common/common_funcs.h"
#include "common/common_types.h"
@@ -51,7 +52,11 @@ public:
u32 address_high;
u32 address_low;
u32 pitch;
- u32 block_dimensions;
+ union {
+ BitField<0, 4, u32> block_width;
+ BitField<4, 4, u32> block_height;
+ BitField<8, 4, u32> block_depth;
+ };
u32 width;
u32 height;
u32 depth;
@@ -63,6 +68,18 @@ public:
return static_cast<GPUVAddr>((static_cast<GPUVAddr>(address_high) << 32) |
address_low);
}
+
+ u32 BlockWidth() const {
+ return 1U << block_width.Value();
+ }
+
+ u32 BlockHeight() const {
+ return 1U << block_height.Value();
+ }
+
+ u32 BlockDepth() const {
+ return 1U << block_depth.Value();
+ }
} dest;
struct {
@@ -81,6 +98,8 @@ public:
struct {
u32 write_offset = 0;
+ u32 copy_size = 0;
+ std::vector<u8> inner_buffer;
} state{};
private:
@@ -88,7 +107,8 @@ private:
VideoCore::RasterizerInterface& rasterizer;
MemoryManager& memory_manager;
- void ProcessData(u32 data);
+ void ProcessExec();
+ void ProcessData(u32 data, bool is_last_call);
};
#define ASSERT_REG_POSITION(field_name, position) \
diff --git a/src/video_core/engines/maxwell_3d.cpp b/src/video_core/engines/maxwell_3d.cpp
index 74403eed4..9780417f2 100644
--- a/src/video_core/engines/maxwell_3d.cpp
+++ b/src/video_core/engines/maxwell_3d.cpp
@@ -418,7 +418,7 @@ Texture::TICEntry Maxwell3D::GetTICEntry(u32 tic_index) const {
const GPUVAddr tic_address_gpu{regs.tic.TICAddress() + tic_index * sizeof(Texture::TICEntry)};
Texture::TICEntry tic_entry;
- memory_manager.ReadBlock(tic_address_gpu, &tic_entry, sizeof(Texture::TICEntry));
+ memory_manager.ReadBlockUnsafe(tic_address_gpu, &tic_entry, sizeof(Texture::TICEntry));
ASSERT_MSG(tic_entry.header_version == Texture::TICHeaderVersion::BlockLinear ||
tic_entry.header_version == Texture::TICHeaderVersion::Pitch,
@@ -439,7 +439,7 @@ Texture::TSCEntry Maxwell3D::GetTSCEntry(u32 tsc_index) const {
const GPUVAddr tsc_address_gpu{regs.tsc.TSCAddress() + tsc_index * sizeof(Texture::TSCEntry)};
Texture::TSCEntry tsc_entry;
- memory_manager.ReadBlock(tsc_address_gpu, &tsc_entry, sizeof(Texture::TSCEntry));
+ memory_manager.ReadBlockUnsafe(tsc_address_gpu, &tsc_entry, sizeof(Texture::TSCEntry));
return tsc_entry;
}
@@ -482,19 +482,8 @@ std::vector<Texture::FullTextureInfo> Maxwell3D::GetStageTextures(Regs::ShaderSt
return textures;
}
-Texture::FullTextureInfo Maxwell3D::GetStageTexture(Regs::ShaderStage stage,
- std::size_t offset) const {
- auto& shader = state.shader_stages[static_cast<std::size_t>(stage)];
- auto& tex_info_buffer = shader.const_buffers[regs.tex_cb_index];
- ASSERT(tex_info_buffer.enabled && tex_info_buffer.address != 0);
-
- const GPUVAddr tex_info_address =
- tex_info_buffer.address + offset * sizeof(Texture::TextureHandle);
-
- ASSERT(tex_info_address < tex_info_buffer.address + tex_info_buffer.size);
-
- const Texture::TextureHandle tex_handle{memory_manager.Read<u32>(tex_info_address)};
-
+Texture::FullTextureInfo Maxwell3D::GetTextureInfo(const Texture::TextureHandle tex_handle,
+ std::size_t offset) const {
Texture::FullTextureInfo tex_info{};
tex_info.index = static_cast<u32>(offset);
@@ -511,6 +500,22 @@ Texture::FullTextureInfo Maxwell3D::GetStageTexture(Regs::ShaderStage stage,
return tex_info;
}
+Texture::FullTextureInfo Maxwell3D::GetStageTexture(Regs::ShaderStage stage,
+ std::size_t offset) const {
+ const auto& shader = state.shader_stages[static_cast<std::size_t>(stage)];
+ const auto& tex_info_buffer = shader.const_buffers[regs.tex_cb_index];
+ ASSERT(tex_info_buffer.enabled && tex_info_buffer.address != 0);
+
+ const GPUVAddr tex_info_address =
+ tex_info_buffer.address + offset * sizeof(Texture::TextureHandle);
+
+ ASSERT(tex_info_address < tex_info_buffer.address + tex_info_buffer.size);
+
+ const Texture::TextureHandle tex_handle{memory_manager.Read<u32>(tex_info_address)};
+
+ return GetTextureInfo(tex_handle, offset);
+}
+
u32 Maxwell3D::GetRegisterValue(u32 method) const {
ASSERT_MSG(method < Regs::NUM_REGS, "Invalid Maxwell3D register");
return regs.reg_array[method];
@@ -524,4 +529,12 @@ void Maxwell3D::ProcessClearBuffers() {
rasterizer.Clear();
}
+u32 Maxwell3D::AccessConstBuffer32(Regs::ShaderStage stage, u64 const_buffer, u64 offset) const {
+ const auto& shader_stage = state.shader_stages[static_cast<std::size_t>(stage)];
+ const auto& buffer = shader_stage.const_buffers[const_buffer];
+ u32 result;
+ std::memcpy(&result, memory_manager.GetPointer(buffer.address + offset), sizeof(u32));
+ return result;
+}
+
} // namespace Tegra::Engines
diff --git a/src/video_core/engines/maxwell_3d.h b/src/video_core/engines/maxwell_3d.h
index 321af3297..cc2424d38 100644
--- a/src/video_core/engines/maxwell_3d.h
+++ b/src/video_core/engines/maxwell_3d.h
@@ -1131,12 +1131,18 @@ public:
/// Write the value to the register identified by method.
void CallMethod(const GPU::MethodCall& method_call);
+ /// Given a Texture Handle, returns the TSC and TIC entries.
+ Texture::FullTextureInfo GetTextureInfo(const Texture::TextureHandle tex_handle,
+ std::size_t offset) const;
+
/// Returns a list of enabled textures for the specified shader stage.
std::vector<Texture::FullTextureInfo> GetStageTextures(Regs::ShaderStage stage) const;
/// Returns the texture information for a specific texture in a specific shader stage.
Texture::FullTextureInfo GetStageTexture(Regs::ShaderStage stage, std::size_t offset) const;
+ u32 AccessConstBuffer32(Regs::ShaderStage stage, u64 const_buffer, u64 offset) const;
+
/// Memory for macro code - it's undetermined how big this is, however 1MB is much larger than
/// we've seen used.
using MacroMemory = std::array<u32, 0x40000>;
diff --git a/src/video_core/engines/shader_bytecode.h b/src/video_core/engines/shader_bytecode.h
index 2e1e96c81..e5b4eadea 100644
--- a/src/video_core/engines/shader_bytecode.h
+++ b/src/video_core/engines/shader_bytecode.h
@@ -387,6 +387,20 @@ enum class IpaSampleMode : u64 {
Offset = 2,
};
+enum class LmemLoadCacheManagement : u64 {
+ Default = 0,
+ LU = 1,
+ CI = 2,
+ CV = 3,
+};
+
+enum class LmemStoreCacheManagement : u64 {
+ Default = 0,
+ CG = 1,
+ CS = 2,
+ WT = 3,
+};
+
struct IpaMode {
IpaInterpMode interpolation_mode;
IpaSampleMode sampling_mode;
@@ -782,7 +796,7 @@ union Instruction {
} ld_l;
union {
- BitField<44, 2, u64> unknown;
+ BitField<44, 2, LmemStoreCacheManagement> cache_management;
} st_l;
union {
@@ -792,6 +806,12 @@ union Instruction {
} ldg;
union {
+ BitField<48, 3, UniformType> type;
+ BitField<46, 2, u64> cache_mode;
+ BitField<20, 24, s64> immediate_offset;
+ } stg;
+
+ union {
BitField<0, 3, u64> pred0;
BitField<3, 3, u64> pred3;
BitField<7, 1, u64> abs_a;
@@ -917,21 +937,34 @@ union Instruction {
} iset;
union {
- BitField<8, 2, Register::Size> dest_size;
- BitField<10, 2, Register::Size> src_size;
- BitField<12, 1, u64> is_output_signed;
- BitField<13, 1, u64> is_input_signed;
- BitField<41, 2, u64> selector;
+ BitField<41, 2, u64> selector; // i2i and i2f only
BitField<45, 1, u64> negate_a;
BitField<49, 1, u64> abs_a;
+ BitField<10, 2, Register::Size> src_size;
+ BitField<13, 1, u64> is_input_signed;
+ BitField<8, 2, Register::Size> dst_size;
+ BitField<12, 1, u64> is_output_signed;
+
+ union {
+ BitField<39, 2, u64> tab5cb8_2;
+ } i2f;
union {
BitField<39, 2, F2iRoundingOp> rounding;
} f2i;
union {
- BitField<39, 4, F2fRoundingOp> rounding;
+ BitField<8, 2, Register::Size> src_size;
+ BitField<10, 2, Register::Size> dst_size;
+ BitField<39, 4, u64> rounding;
+ // H0, H1 extract for F16 missing
+ BitField<41, 1, u64> selector; // Guessed as some games set it, TODO: reverse this value
+ F2fRoundingOp GetRoundingMode() const {
+ constexpr u64 rounding_mask = 0x0B;
+ return static_cast<F2fRoundingOp>(rounding.Value() & rounding_mask);
+ }
} f2f;
+
} conversion;
union {
@@ -967,6 +1000,38 @@ union Instruction {
} tex;
union {
+ BitField<28, 1, u64> array;
+ BitField<29, 2, TextureType> texture_type;
+ BitField<31, 4, u64> component_mask;
+ BitField<49, 1, u64> nodep_flag;
+ BitField<50, 1, u64> dc_flag;
+ BitField<36, 1, u64> aoffi_flag;
+ BitField<37, 3, TextureProcessMode> process_mode;
+
+ bool IsComponentEnabled(std::size_t component) const {
+ return ((1ULL << component) & component_mask) != 0;
+ }
+
+ TextureProcessMode GetTextureProcessMode() const {
+ return process_mode;
+ }
+
+ bool UsesMiscMode(TextureMiscMode mode) const {
+ switch (mode) {
+ case TextureMiscMode::DC:
+ return dc_flag != 0;
+ case TextureMiscMode::NODEP:
+ return nodep_flag != 0;
+ case TextureMiscMode::AOFFI:
+ return aoffi_flag != 0;
+ default:
+ break;
+ }
+ return false;
+ }
+ } tex_b;
+
+ union {
BitField<22, 6, TextureQueryType> query_type;
BitField<31, 4, u64> component_mask;
BitField<49, 1, u64> nodep_flag;
@@ -1312,7 +1377,9 @@ public:
LDG, // Load from global memory
STG, // Store in global memory
TEX,
+ TEX_B, // Texture Load Bindless
TXQ, // Texture Query
+ TXQ_B, // Texture Query Bindless
TEXS, // Texture Fetch with scalar/non-vec4 source/destinations
TLDS, // Texture Load with scalar/non-vec4 source/destinations
TLD4, // Texture Load 4
@@ -1580,7 +1647,9 @@ private:
INST("1110111011010---", Id::LDG, Type::Memory, "LDG"),
INST("1110111011011---", Id::STG, Type::Memory, "STG"),
INST("110000----111---", Id::TEX, Type::Texture, "TEX"),
+ INST("1101111010111---", Id::TEX_B, Type::Texture, "TEX_B"),
INST("1101111101001---", Id::TXQ, Type::Texture, "TXQ"),
+ INST("1101111101010---", Id::TXQ_B, Type::Texture, "TXQ_B"),
INST("1101-00---------", Id::TEXS, Type::Texture, "TEXS"),
INST("1101101---------", Id::TLDS, Type::Texture, "TLDS"),
INST("110010----111---", Id::TLD4, Type::Texture, "TLD4"),
@@ -1678,7 +1747,7 @@ private:
INST("0011100-00101---", Id::SHR_IMM, Type::Shift, "SHR_IMM"),
INST("0100110011100---", Id::I2I_C, Type::Conversion, "I2I_C"),
INST("0101110011100---", Id::I2I_R, Type::Conversion, "I2I_R"),
- INST("01110001-1000---", Id::I2I_IMM, Type::Conversion, "I2I_IMM"),
+ INST("0011101-11100---", Id::I2I_IMM, Type::Conversion, "I2I_IMM"),
INST("0100110010111---", Id::I2F_C, Type::Conversion, "I2F_C"),
INST("0101110010111---", Id::I2F_R, Type::Conversion, "I2F_R"),
INST("0011100-10111---", Id::I2F_IMM, Type::Conversion, "I2F_IMM"),
diff --git a/src/video_core/gpu.h b/src/video_core/gpu.h
index de30ea354..fe6628923 100644
--- a/src/video_core/gpu.h
+++ b/src/video_core/gpu.h
@@ -207,6 +207,11 @@ public:
};
} regs{};
+ /// Performs any additional setup necessary in order to begin GPU emulation.
+ /// This can be used to launch any necessary threads and register any necessary
+ /// core timing events.
+ virtual void Start() = 0;
+
/// Push GPU command entries to be processed
virtual void PushGPUEntries(Tegra::CommandList&& entries) = 0;
diff --git a/src/video_core/gpu_asynch.cpp b/src/video_core/gpu_asynch.cpp
index db507cf04..d4e2553a9 100644
--- a/src/video_core/gpu_asynch.cpp
+++ b/src/video_core/gpu_asynch.cpp
@@ -9,10 +9,14 @@
namespace VideoCommon {
GPUAsynch::GPUAsynch(Core::System& system, VideoCore::RendererBase& renderer)
- : Tegra::GPU(system, renderer), gpu_thread{system, renderer, *dma_pusher} {}
+ : GPU(system, renderer), gpu_thread{system} {}
GPUAsynch::~GPUAsynch() = default;
+void GPUAsynch::Start() {
+ gpu_thread.StartThread(renderer, *dma_pusher);
+}
+
void GPUAsynch::PushGPUEntries(Tegra::CommandList&& entries) {
gpu_thread.SubmitList(std::move(entries));
}
diff --git a/src/video_core/gpu_asynch.h b/src/video_core/gpu_asynch.h
index 1dcc61a6c..30be74cba 100644
--- a/src/video_core/gpu_asynch.h
+++ b/src/video_core/gpu_asynch.h
@@ -13,16 +13,13 @@ class RendererBase;
namespace VideoCommon {
-namespace GPUThread {
-class ThreadManager;
-} // namespace GPUThread
-
/// Implementation of GPU interface that runs the GPU asynchronously
class GPUAsynch : public Tegra::GPU {
public:
explicit GPUAsynch(Core::System& system, VideoCore::RendererBase& renderer);
~GPUAsynch() override;
+ void Start() override;
void PushGPUEntries(Tegra::CommandList&& entries) override;
void SwapBuffers(
std::optional<std::reference_wrapper<const Tegra::FramebufferConfig>> framebuffer) override;
diff --git a/src/video_core/gpu_synch.cpp b/src/video_core/gpu_synch.cpp
index 2cfc900ed..45e43b1dc 100644
--- a/src/video_core/gpu_synch.cpp
+++ b/src/video_core/gpu_synch.cpp
@@ -8,10 +8,12 @@
namespace VideoCommon {
GPUSynch::GPUSynch(Core::System& system, VideoCore::RendererBase& renderer)
- : Tegra::GPU(system, renderer) {}
+ : GPU(system, renderer) {}
GPUSynch::~GPUSynch() = default;
+void GPUSynch::Start() {}
+
void GPUSynch::PushGPUEntries(Tegra::CommandList&& entries) {
dma_pusher->Push(std::move(entries));
dma_pusher->DispatchCalls();
diff --git a/src/video_core/gpu_synch.h b/src/video_core/gpu_synch.h
index 766b5631c..3031fcf72 100644
--- a/src/video_core/gpu_synch.h
+++ b/src/video_core/gpu_synch.h
@@ -18,6 +18,7 @@ public:
explicit GPUSynch(Core::System& system, VideoCore::RendererBase& renderer);
~GPUSynch() override;
+ void Start() override;
void PushGPUEntries(Tegra::CommandList&& entries) override;
void SwapBuffers(
std::optional<std::reference_wrapper<const Tegra::FramebufferConfig>> framebuffer) override;
diff --git a/src/video_core/gpu_thread.cpp b/src/video_core/gpu_thread.cpp
index cc56cf467..c9a2077de 100644
--- a/src/video_core/gpu_thread.cpp
+++ b/src/video_core/gpu_thread.cpp
@@ -55,19 +55,24 @@ static void RunThread(VideoCore::RendererBase& renderer, Tegra::DmaPusher& dma_p
}
}
-ThreadManager::ThreadManager(Core::System& system, VideoCore::RendererBase& renderer,
- Tegra::DmaPusher& dma_pusher)
- : system{system}, thread{RunThread, std::ref(renderer), std::ref(dma_pusher), std::ref(state)} {
- synchronization_event = system.CoreTiming().RegisterEvent(
- "GPUThreadSynch", [this](u64 fence, s64) { state.WaitForSynchronization(fence); });
-}
+ThreadManager::ThreadManager(Core::System& system) : system{system} {}
ThreadManager::~ThreadManager() {
+ if (!thread.joinable()) {
+ return;
+ }
+
// Notify GPU thread that a shutdown is pending
PushCommand(EndProcessingCommand());
thread.join();
}
+void ThreadManager::StartThread(VideoCore::RendererBase& renderer, Tegra::DmaPusher& dma_pusher) {
+ thread = std::thread{RunThread, std::ref(renderer), std::ref(dma_pusher), std::ref(state)};
+ synchronization_event = system.CoreTiming().RegisterEvent(
+ "GPUThreadSynch", [this](u64 fence, s64) { state.WaitForSynchronization(fence); });
+}
+
void ThreadManager::SubmitList(Tegra::CommandList&& entries) {
const u64 fence{PushCommand(SubmitListCommand(std::move(entries)))};
const s64 synchronization_ticks{Core::Timing::usToCycles(9000)};
diff --git a/src/video_core/gpu_thread.h b/src/video_core/gpu_thread.h
index 62bcea5bb..cc14527c7 100644
--- a/src/video_core/gpu_thread.h
+++ b/src/video_core/gpu_thread.h
@@ -138,10 +138,12 @@ struct SynchState final {
/// Class used to manage the GPU thread
class ThreadManager final {
public:
- explicit ThreadManager(Core::System& system, VideoCore::RendererBase& renderer,
- Tegra::DmaPusher& dma_pusher);
+ explicit ThreadManager(Core::System& system);
~ThreadManager();
+ /// Creates and starts the GPU thread.
+ void StartThread(VideoCore::RendererBase& renderer, Tegra::DmaPusher& dma_pusher);
+
/// Push GPU command entries to be processed
void SubmitList(Tegra::CommandList&& entries);
diff --git a/src/video_core/memory_manager.cpp b/src/video_core/memory_manager.cpp
index 0f4e820aa..6c98c6701 100644
--- a/src/video_core/memory_manager.cpp
+++ b/src/video_core/memory_manager.cpp
@@ -199,7 +199,15 @@ const u8* MemoryManager::GetPointer(GPUVAddr addr) const {
return {};
}
-void MemoryManager::ReadBlock(GPUVAddr src_addr, void* dest_buffer, std::size_t size) const {
+bool MemoryManager::IsBlockContinous(const GPUVAddr start, const std::size_t size) {
+ const GPUVAddr end = start + size;
+ const auto host_ptr_start = reinterpret_cast<std::uintptr_t>(GetPointer(start));
+ const auto host_ptr_end = reinterpret_cast<std::uintptr_t>(GetPointer(end));
+ const std::size_t range = static_cast<std::size_t>(host_ptr_end - host_ptr_start);
+ return range == size;
+}
+
+void MemoryManager::ReadBlock(GPUVAddr src_addr, void* dest_buffer, const std::size_t size) const {
std::size_t remaining_size{size};
std::size_t page_index{src_addr >> page_bits};
std::size_t page_offset{src_addr & page_mask};
@@ -226,7 +234,30 @@ void MemoryManager::ReadBlock(GPUVAddr src_addr, void* dest_buffer, std::size_t
}
}
-void MemoryManager::WriteBlock(GPUVAddr dest_addr, const void* src_buffer, std::size_t size) {
+void MemoryManager::ReadBlockUnsafe(GPUVAddr src_addr, void* dest_buffer,
+ const std::size_t size) const {
+ std::size_t remaining_size{size};
+ std::size_t page_index{src_addr >> page_bits};
+ std::size_t page_offset{src_addr & page_mask};
+
+ while (remaining_size > 0) {
+ const std::size_t copy_amount{
+ std::min(static_cast<std::size_t>(page_size) - page_offset, remaining_size)};
+ const u8* page_pointer = page_table.pointers[page_index];
+ if (page_pointer) {
+ const u8* src_ptr{page_pointer + page_offset};
+ std::memcpy(dest_buffer, src_ptr, copy_amount);
+ } else {
+ std::memset(dest_buffer, 0, copy_amount);
+ }
+ page_index++;
+ page_offset = 0;
+ dest_buffer = static_cast<u8*>(dest_buffer) + copy_amount;
+ remaining_size -= copy_amount;
+ }
+}
+
+void MemoryManager::WriteBlock(GPUVAddr dest_addr, const void* src_buffer, const std::size_t size) {
std::size_t remaining_size{size};
std::size_t page_index{dest_addr >> page_bits};
std::size_t page_offset{dest_addr & page_mask};
@@ -253,7 +284,28 @@ void MemoryManager::WriteBlock(GPUVAddr dest_addr, const void* src_buffer, std::
}
}
-void MemoryManager::CopyBlock(GPUVAddr dest_addr, GPUVAddr src_addr, std::size_t size) {
+void MemoryManager::WriteBlockUnsafe(GPUVAddr dest_addr, const void* src_buffer,
+ const std::size_t size) {
+ std::size_t remaining_size{size};
+ std::size_t page_index{dest_addr >> page_bits};
+ std::size_t page_offset{dest_addr & page_mask};
+
+ while (remaining_size > 0) {
+ const std::size_t copy_amount{
+ std::min(static_cast<std::size_t>(page_size) - page_offset, remaining_size)};
+ u8* page_pointer = page_table.pointers[page_index];
+ if (page_pointer) {
+ u8* dest_ptr{page_pointer + page_offset};
+ std::memcpy(dest_ptr, src_buffer, copy_amount);
+ }
+ page_index++;
+ page_offset = 0;
+ src_buffer = static_cast<const u8*>(src_buffer) + copy_amount;
+ remaining_size -= copy_amount;
+ }
+}
+
+void MemoryManager::CopyBlock(GPUVAddr dest_addr, GPUVAddr src_addr, const std::size_t size) {
std::size_t remaining_size{size};
std::size_t page_index{src_addr >> page_bits};
std::size_t page_offset{src_addr & page_mask};
@@ -281,6 +333,12 @@ void MemoryManager::CopyBlock(GPUVAddr dest_addr, GPUVAddr src_addr, std::size_t
}
}
+void MemoryManager::CopyBlockUnsafe(GPUVAddr dest_addr, GPUVAddr src_addr, const std::size_t size) {
+ std::vector<u8> tmp_buffer(size);
+ ReadBlockUnsafe(src_addr, tmp_buffer.data(), size);
+ WriteBlockUnsafe(dest_addr, tmp_buffer.data(), size);
+}
+
void MemoryManager::MapPages(GPUVAddr base, u64 size, u8* memory, Common::PageType type,
VAddr backing_addr) {
LOG_DEBUG(HW_GPU, "Mapping {} onto {:016X}-{:016X}", fmt::ptr(memory), base * page_size,
diff --git a/src/video_core/memory_manager.h b/src/video_core/memory_manager.h
index 647cbf93a..e4f0c4bd6 100644
--- a/src/video_core/memory_manager.h
+++ b/src/video_core/memory_manager.h
@@ -65,9 +65,32 @@ public:
u8* GetPointer(GPUVAddr addr);
const u8* GetPointer(GPUVAddr addr) const;
- void ReadBlock(GPUVAddr src_addr, void* dest_buffer, std::size_t size) const;
- void WriteBlock(GPUVAddr dest_addr, const void* src_buffer, std::size_t size);
- void CopyBlock(GPUVAddr dest_addr, GPUVAddr src_addr, std::size_t size);
+ // Returns true if the block is continous in host memory, false otherwise
+ bool IsBlockContinous(const GPUVAddr start, const std::size_t size);
+
+ /**
+ * ReadBlock and WriteBlock are full read and write operations over virtual
+ * GPU Memory. It's important to use these when GPU memory may not be continous
+ * in the Host Memory counterpart. Note: This functions cause Host GPU Memory
+ * Flushes and Invalidations, respectively to each operation.
+ */
+ void ReadBlock(GPUVAddr src_addr, void* dest_buffer, const std::size_t size) const;
+ void WriteBlock(GPUVAddr dest_addr, const void* src_buffer, const std::size_t size);
+ void CopyBlock(GPUVAddr dest_addr, GPUVAddr src_addr, const std::size_t size);
+
+ /**
+ * ReadBlockUnsafe and WriteBlockUnsafe are special versions of ReadBlock and
+ * WriteBlock respectively. In this versions, no flushing or invalidation is actually
+ * done and their performance is similar to a memcpy. This functions can be used
+ * on either of this 2 scenarios instead of their safe counterpart:
+ * - Memory which is sure to never be represented in the Host GPU.
+ * - Memory Managed by a Cache Manager. Example: Texture Flushing should use
+ * WriteBlockUnsafe instead of WriteBlock since it shouldn't invalidate the texture
+ * being flushed.
+ */
+ void ReadBlockUnsafe(GPUVAddr src_addr, void* dest_buffer, const std::size_t size) const;
+ void WriteBlockUnsafe(GPUVAddr dest_addr, const void* src_buffer, const std::size_t size);
+ void CopyBlockUnsafe(GPUVAddr dest_addr, GPUVAddr src_addr, const std::size_t size);
private:
using VMAMap = std::map<GPUVAddr, VirtualMemoryArea>;
diff --git a/src/video_core/renderer_opengl/gl_global_cache.cpp b/src/video_core/renderer_opengl/gl_global_cache.cpp
index 8d9ee81f1..ea4a593af 100644
--- a/src/video_core/renderer_opengl/gl_global_cache.cpp
+++ b/src/video_core/renderer_opengl/gl_global_cache.cpp
@@ -14,28 +14,28 @@
namespace OpenGL {
-CachedGlobalRegion::CachedGlobalRegion(VAddr cpu_addr, u32 size, u8* host_ptr)
- : RasterizerCacheObject{host_ptr}, cpu_addr{cpu_addr}, size{size} {
+CachedGlobalRegion::CachedGlobalRegion(VAddr cpu_addr, u8* host_ptr, u32 size, u32 max_size)
+ : RasterizerCacheObject{host_ptr}, cpu_addr{cpu_addr}, host_ptr{host_ptr}, size{size},
+ max_size{max_size} {
buffer.Create();
- // Bind and unbind the buffer so it gets allocated by the driver
- glBindBuffer(GL_SHADER_STORAGE_BUFFER, buffer.handle);
- glBindBuffer(GL_SHADER_STORAGE_BUFFER, 0);
LabelGLObject(GL_BUFFER, buffer.handle, cpu_addr, "GlobalMemory");
}
-void CachedGlobalRegion::Reload(u32 size_) {
- constexpr auto max_size = static_cast<u32>(RasterizerOpenGL::MaxGlobalMemorySize);
+CachedGlobalRegion::~CachedGlobalRegion() = default;
+void CachedGlobalRegion::Reload(u32 size_) {
size = size_;
if (size > max_size) {
size = max_size;
- LOG_CRITICAL(HW_GPU, "Global region size {} exceeded the expected size {}!", size_,
+ LOG_CRITICAL(HW_GPU, "Global region size {} exceeded the supported size {}!", size_,
max_size);
}
+ glNamedBufferData(buffer.handle, size, host_ptr, GL_STREAM_DRAW);
+}
- // TODO(Rodrigo): Get rid of Memory::GetPointer with a staging buffer
- glBindBuffer(GL_SHADER_STORAGE_BUFFER, buffer.handle);
- glBufferData(GL_SHADER_STORAGE_BUFFER, size, GetHostPtr(), GL_DYNAMIC_DRAW);
+void CachedGlobalRegion::Flush() {
+ LOG_DEBUG(Render_OpenGL, "Flushing {} bytes to CPU memory address 0x{:16}", size, cpu_addr);
+ glGetNamedBufferSubData(buffer.handle, 0, static_cast<GLsizeiptr>(size), host_ptr);
}
GlobalRegion GlobalRegionCacheOpenGL::TryGetReservedGlobalRegion(CacheAddr addr, u32 size) const {
@@ -46,14 +46,16 @@ GlobalRegion GlobalRegionCacheOpenGL::TryGetReservedGlobalRegion(CacheAddr addr,
return search->second;
}
-GlobalRegion GlobalRegionCacheOpenGL::GetUncachedGlobalRegion(GPUVAddr addr, u32 size,
- u8* host_ptr) {
+GlobalRegion GlobalRegionCacheOpenGL::GetUncachedGlobalRegion(GPUVAddr addr, u8* host_ptr,
+ u32 size) {
GlobalRegion region{TryGetReservedGlobalRegion(ToCacheAddr(host_ptr), size)};
if (!region) {
// No reserved surface available, create a new one and reserve it
auto& memory_manager{Core::System::GetInstance().GPU().MemoryManager()};
- const auto cpu_addr = *memory_manager.GpuToCpuAddress(addr);
- region = std::make_shared<CachedGlobalRegion>(cpu_addr, size, host_ptr);
+ const auto cpu_addr{memory_manager.GpuToCpuAddress(addr)};
+ ASSERT(cpu_addr);
+
+ region = std::make_shared<CachedGlobalRegion>(*cpu_addr, host_ptr, size, max_ssbo_size);
ReserveGlobalRegion(region);
}
region->Reload(size);
@@ -65,7 +67,11 @@ void GlobalRegionCacheOpenGL::ReserveGlobalRegion(GlobalRegion region) {
}
GlobalRegionCacheOpenGL::GlobalRegionCacheOpenGL(RasterizerOpenGL& rasterizer)
- : RasterizerCache{rasterizer} {}
+ : RasterizerCache{rasterizer} {
+ GLint max_ssbo_size_;
+ glGetIntegerv(GL_MAX_SHADER_STORAGE_BLOCK_SIZE, &max_ssbo_size_);
+ max_ssbo_size = static_cast<u32>(max_ssbo_size_);
+}
GlobalRegion GlobalRegionCacheOpenGL::GetGlobalRegion(
const GLShader::GlobalMemoryEntry& global_region,
@@ -73,7 +79,7 @@ GlobalRegion GlobalRegionCacheOpenGL::GetGlobalRegion(
auto& gpu{Core::System::GetInstance().GPU()};
auto& memory_manager{gpu.MemoryManager()};
- const auto cbufs{gpu.Maxwell3D().state.shader_stages[static_cast<u64>(stage)]};
+ const auto cbufs{gpu.Maxwell3D().state.shader_stages[static_cast<std::size_t>(stage)]};
const auto addr{cbufs.const_buffers[global_region.GetCbufIndex()].address +
global_region.GetCbufOffset()};
const auto actual_addr{memory_manager.Read<u64>(addr)};
@@ -85,7 +91,7 @@ GlobalRegion GlobalRegionCacheOpenGL::GetGlobalRegion(
if (!region) {
// No global region found - create a new one
- region = GetUncachedGlobalRegion(actual_addr, size, host_ptr);
+ region = GetUncachedGlobalRegion(actual_addr, host_ptr, size);
Register(region);
}
diff --git a/src/video_core/renderer_opengl/gl_global_cache.h b/src/video_core/renderer_opengl/gl_global_cache.h
index 5a21ab66f..196e6e278 100644
--- a/src/video_core/renderer_opengl/gl_global_cache.h
+++ b/src/video_core/renderer_opengl/gl_global_cache.h
@@ -19,7 +19,7 @@ namespace OpenGL {
namespace GLShader {
class GlobalMemoryEntry;
-} // namespace GLShader
+}
class RasterizerOpenGL;
class CachedGlobalRegion;
@@ -27,7 +27,8 @@ using GlobalRegion = std::shared_ptr<CachedGlobalRegion>;
class CachedGlobalRegion final : public RasterizerCacheObject {
public:
- explicit CachedGlobalRegion(VAddr cpu_addr, u32 size, u8* host_ptr);
+ explicit CachedGlobalRegion(VAddr cpu_addr, u8* host_ptr, u32 size, u32 max_size);
+ ~CachedGlobalRegion();
VAddr GetCpuAddr() const override {
return cpu_addr;
@@ -45,14 +46,14 @@ public:
/// Reloads the global region from guest memory
void Reload(u32 size_);
- // TODO(Rodrigo): When global memory is written (STG), implement flushing
- void Flush() override {
- UNIMPLEMENTED();
- }
+ void Flush() override;
private:
VAddr cpu_addr{};
+ u8* host_ptr{};
u32 size{};
+ u32 max_size{};
+
OGLBuffer buffer;
};
@@ -66,10 +67,11 @@ public:
private:
GlobalRegion TryGetReservedGlobalRegion(CacheAddr addr, u32 size) const;
- GlobalRegion GetUncachedGlobalRegion(GPUVAddr addr, u32 size, u8* host_ptr);
+ GlobalRegion GetUncachedGlobalRegion(GPUVAddr addr, u8* host_ptr, u32 size);
void ReserveGlobalRegion(GlobalRegion region);
std::unordered_map<CacheAddr, GlobalRegion> reserve;
+ u32 max_ssbo_size{};
};
} // namespace OpenGL
diff --git a/src/video_core/renderer_opengl/gl_rasterizer.cpp b/src/video_core/renderer_opengl/gl_rasterizer.cpp
index ff5d05ced..9a088a503 100644
--- a/src/video_core/renderer_opengl/gl_rasterizer.cpp
+++ b/src/video_core/renderer_opengl/gl_rasterizer.cpp
@@ -101,12 +101,6 @@ struct FramebufferCacheKey {
RasterizerOpenGL::RasterizerOpenGL(Core::System& system, ScreenInfo& info)
: res_cache{*this}, shader_cache{*this, system, device}, global_cache{*this}, system{system},
screen_info{info}, buffer_cache(*this, STREAM_BUFFER_SIZE) {
- // Create sampler objects
- for (std::size_t i = 0; i < texture_samplers.size(); ++i) {
- texture_samplers[i].Create();
- state.texture_units[i].sampler = texture_samplers[i].sampler.handle;
- }
-
OpenGLState::ApplyDefaultState();
shader_program_manager = std::make_unique<GLShader::ProgramManager>();
@@ -580,9 +574,6 @@ std::pair<bool, bool> RasterizerOpenGL::ConfigureFramebuffers(
}
void RasterizerOpenGL::Clear() {
- const auto prev_state{state};
- SCOPE_EXIT({ prev_state.Apply(); });
-
const auto& regs = system.GPU().Maxwell3D().regs;
bool use_color{};
bool use_depth{};
@@ -654,7 +645,10 @@ void RasterizerOpenGL::Clear() {
clear_state.EmulateViewportWithScissor();
}
- clear_state.Apply();
+ clear_state.ApplyColorMask();
+ clear_state.ApplyDepth();
+ clear_state.ApplyStencilTest();
+ clear_state.ApplyViewport();
if (use_color) {
glClearBufferfv(GL_COLOR, regs.clear_buffers.RT, regs.clear_color);
@@ -755,6 +749,7 @@ void RasterizerOpenGL::FlushRegion(CacheAddr addr, u64 size) {
return;
}
res_cache.FlushRegion(addr, size);
+ global_cache.FlushRegion(addr, size);
}
void RasterizerOpenGL::InvalidateRegion(CacheAddr addr, u64 size) {
@@ -811,92 +806,6 @@ bool RasterizerOpenGL::AccelerateDisplay(const Tegra::FramebufferConfig& config,
return true;
}
-void RasterizerOpenGL::SamplerInfo::Create() {
- sampler.Create();
- mag_filter = Tegra::Texture::TextureFilter::Linear;
- min_filter = Tegra::Texture::TextureFilter::Linear;
- wrap_u = Tegra::Texture::WrapMode::Wrap;
- wrap_v = Tegra::Texture::WrapMode::Wrap;
- wrap_p = Tegra::Texture::WrapMode::Wrap;
- use_depth_compare = false;
- depth_compare_func = Tegra::Texture::DepthCompareFunc::Never;
-
- // OpenGL's default is GL_LINEAR_MIPMAP_LINEAR
- glSamplerParameteri(sampler.handle, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
- glSamplerParameteri(sampler.handle, GL_TEXTURE_COMPARE_FUNC, GL_NEVER);
-
- // Other attributes have correct defaults
-}
-
-void RasterizerOpenGL::SamplerInfo::SyncWithConfig(const Tegra::Texture::TSCEntry& config) {
- const GLuint sampler_id = sampler.handle;
- if (mag_filter != config.mag_filter) {
- mag_filter = config.mag_filter;
- glSamplerParameteri(
- sampler_id, GL_TEXTURE_MAG_FILTER,
- MaxwellToGL::TextureFilterMode(mag_filter, Tegra::Texture::TextureMipmapFilter::None));
- }
- if (min_filter != config.min_filter || mipmap_filter != config.mipmap_filter) {
- min_filter = config.min_filter;
- mipmap_filter = config.mipmap_filter;
- glSamplerParameteri(sampler_id, GL_TEXTURE_MIN_FILTER,
- MaxwellToGL::TextureFilterMode(min_filter, mipmap_filter));
- }
-
- if (wrap_u != config.wrap_u) {
- wrap_u = config.wrap_u;
- glSamplerParameteri(sampler_id, GL_TEXTURE_WRAP_S, MaxwellToGL::WrapMode(wrap_u));
- }
- if (wrap_v != config.wrap_v) {
- wrap_v = config.wrap_v;
- glSamplerParameteri(sampler_id, GL_TEXTURE_WRAP_T, MaxwellToGL::WrapMode(wrap_v));
- }
- if (wrap_p != config.wrap_p) {
- wrap_p = config.wrap_p;
- glSamplerParameteri(sampler_id, GL_TEXTURE_WRAP_R, MaxwellToGL::WrapMode(wrap_p));
- }
-
- if (const bool enabled = config.depth_compare_enabled == 1; use_depth_compare != enabled) {
- use_depth_compare = enabled;
- glSamplerParameteri(sampler_id, GL_TEXTURE_COMPARE_MODE,
- use_depth_compare ? GL_COMPARE_REF_TO_TEXTURE : GL_NONE);
- }
-
- if (depth_compare_func != config.depth_compare_func) {
- depth_compare_func = config.depth_compare_func;
- glSamplerParameteri(sampler_id, GL_TEXTURE_COMPARE_FUNC,
- MaxwellToGL::DepthCompareFunc(depth_compare_func));
- }
-
- if (const auto new_border_color = config.GetBorderColor(); border_color != new_border_color) {
- border_color = new_border_color;
- glSamplerParameterfv(sampler_id, GL_TEXTURE_BORDER_COLOR, border_color.data());
- }
-
- if (const float anisotropic = config.GetMaxAnisotropy(); max_anisotropic != anisotropic) {
- max_anisotropic = anisotropic;
- if (GLAD_GL_ARB_texture_filter_anisotropic) {
- glSamplerParameterf(sampler_id, GL_TEXTURE_MAX_ANISOTROPY, max_anisotropic);
- } else if (GLAD_GL_EXT_texture_filter_anisotropic) {
- glSamplerParameterf(sampler_id, GL_TEXTURE_MAX_ANISOTROPY_EXT, max_anisotropic);
- }
- }
-
- if (const float min = config.GetMinLod(); min_lod != min) {
- min_lod = min;
- glSamplerParameterf(sampler_id, GL_TEXTURE_MIN_LOD, min_lod);
- }
- if (const float max = config.GetMaxLod(); max_lod != max) {
- max_lod = max;
- glSamplerParameterf(sampler_id, GL_TEXTURE_MAX_LOD, max_lod);
- }
-
- if (const float bias = config.GetLodBias(); lod_bias != bias) {
- lod_bias = bias;
- glSamplerParameterf(sampler_id, GL_TEXTURE_LOD_BIAS, lod_bias);
- }
-}
-
void RasterizerOpenGL::SetupConstBuffers(Tegra::Engines::Maxwell3D::Regs::ShaderStage stage,
const Shader& shader, GLuint program_handle,
BaseBindings base_bindings) {
@@ -952,6 +861,9 @@ void RasterizerOpenGL::SetupGlobalRegions(Tegra::Engines::Maxwell3D::Regs::Shade
for (std::size_t bindpoint = 0; bindpoint < entries.size(); ++bindpoint) {
const auto& entry{entries[bindpoint]};
const auto& region{global_cache.GetGlobalRegion(entry, stage)};
+ if (entry.IsWritten()) {
+ region->MarkAsModified(true, global_cache);
+ }
bind_ssbo_pushbuffer.Push(region->GetBufferHandle(), 0,
static_cast<GLsizeiptr>(region->GetSizeInBytes()));
}
@@ -969,10 +881,18 @@ void RasterizerOpenGL::SetupTextures(Maxwell::ShaderStage stage, const Shader& s
for (u32 bindpoint = 0; bindpoint < entries.size(); ++bindpoint) {
const auto& entry = entries[bindpoint];
- const auto texture = maxwell3d.GetStageTexture(stage, entry.GetOffset());
+ Tegra::Texture::FullTextureInfo texture;
+ if (entry.IsBindless()) {
+ const auto cbuf = entry.GetBindlessCBuf();
+ Tegra::Texture::TextureHandle tex_handle;
+ tex_handle.raw = maxwell3d.AccessConstBuffer32(stage, cbuf.first, cbuf.second);
+ texture = maxwell3d.GetTextureInfo(tex_handle, entry.GetOffset());
+ } else {
+ texture = maxwell3d.GetStageTexture(stage, entry.GetOffset());
+ }
const u32 current_bindpoint = base_bindings.sampler + bindpoint;
- texture_samplers[current_bindpoint].SyncWithConfig(texture.tsc);
+ state.texture_units[current_bindpoint].sampler = sampler_cache.GetSampler(texture.tsc);
if (Surface surface = res_cache.GetTextureSurface(texture, entry); surface) {
state.texture_units[current_bindpoint].texture =
diff --git a/src/video_core/renderer_opengl/gl_rasterizer.h b/src/video_core/renderer_opengl/gl_rasterizer.h
index cbcce8597..71b9c5ead 100644
--- a/src/video_core/renderer_opengl/gl_rasterizer.h
+++ b/src/video_core/renderer_opengl/gl_rasterizer.h
@@ -26,6 +26,7 @@
#include "video_core/renderer_opengl/gl_primitive_assembler.h"
#include "video_core/renderer_opengl/gl_rasterizer_cache.h"
#include "video_core/renderer_opengl/gl_resource_manager.h"
+#include "video_core/renderer_opengl/gl_sampler_cache.h"
#include "video_core/renderer_opengl/gl_shader_cache.h"
#include "video_core/renderer_opengl/gl_shader_manager.h"
#include "video_core/renderer_opengl/gl_state.h"
@@ -72,39 +73,7 @@ public:
static_assert(MaxConstbufferSize % sizeof(GLvec4) == 0,
"The maximum size of a constbuffer must be a multiple of the size of GLvec4");
- static constexpr std::size_t MaxGlobalMemorySize = 0x10000;
- static_assert(MaxGlobalMemorySize % sizeof(float) == 0,
- "The maximum size of a global memory must be a multiple of the size of float");
-
private:
- class SamplerInfo {
- public:
- OGLSampler sampler;
-
- /// Creates the sampler object, initializing its state so that it's in sync with the
- /// SamplerInfo struct.
- void Create();
- /// Syncs the sampler object with the config, updating any necessary state.
- void SyncWithConfig(const Tegra::Texture::TSCEntry& info);
-
- private:
- Tegra::Texture::TextureFilter mag_filter = Tegra::Texture::TextureFilter::Nearest;
- Tegra::Texture::TextureFilter min_filter = Tegra::Texture::TextureFilter::Nearest;
- Tegra::Texture::TextureMipmapFilter mipmap_filter =
- Tegra::Texture::TextureMipmapFilter::None;
- Tegra::Texture::WrapMode wrap_u = Tegra::Texture::WrapMode::ClampToEdge;
- Tegra::Texture::WrapMode wrap_v = Tegra::Texture::WrapMode::ClampToEdge;
- Tegra::Texture::WrapMode wrap_p = Tegra::Texture::WrapMode::ClampToEdge;
- bool use_depth_compare = false;
- Tegra::Texture::DepthCompareFunc depth_compare_func =
- Tegra::Texture::DepthCompareFunc::Always;
- GLvec4 border_color = {};
- float min_lod = 0.0f;
- float max_lod = 16.0f;
- float lod_bias = 0.0f;
- float max_anisotropic = 1.0f;
- };
-
struct FramebufferConfigState {
bool using_color_fb{};
bool using_depth_fb{};
@@ -210,6 +179,7 @@ private:
RasterizerCacheOpenGL res_cache;
ShaderCacheOpenGL shader_cache;
GlobalRegionCacheOpenGL global_cache;
+ SamplerCacheOpenGL sampler_cache;
Core::System& system;
ScreenInfo& screen_info;
@@ -224,8 +194,6 @@ private:
FramebufferConfigState current_framebuffer_config_state;
std::pair<bool, bool> current_depth_stencil_usage{};
- std::array<SamplerInfo, Tegra::Engines::Maxwell3D::Regs::NumTextureSamplers> texture_samplers;
-
static constexpr std::size_t STREAM_BUFFER_SIZE = 128 * 1024 * 1024;
OGLBufferCache buffer_cache;
PrimitiveAssembler primitive_assembler{buffer_cache};
diff --git a/src/video_core/renderer_opengl/gl_rasterizer_cache.cpp b/src/video_core/renderer_opengl/gl_rasterizer_cache.cpp
index aa6da1944..5a25f5b37 100644
--- a/src/video_core/renderer_opengl/gl_rasterizer_cache.cpp
+++ b/src/video_core/renderer_opengl/gl_rasterizer_cache.cpp
@@ -112,11 +112,26 @@ std::size_t SurfaceParams::InnerMemorySize(bool force_gl, bool layer_only,
params.pixel_format = PixelFormatFromTextureFormat(config.tic.format, config.tic.r_type.Value(),
params.srgb_conversion);
- if (params.pixel_format == PixelFormat::R16U && config.tsc.depth_compare_enabled) {
+ if (config.tsc.depth_compare_enabled) {
// Some titles create a 'R16U' (normalized 16-bit) texture with depth_compare enabled,
// then attempt to sample from it via a shadow sampler. Convert format to Z16 (which also
// causes GetFormatType to properly return 'Depth' below).
- params.pixel_format = PixelFormat::Z16;
+ if (GetFormatType(params.pixel_format) == SurfaceType::ColorTexture) {
+ switch (params.pixel_format) {
+ case PixelFormat::R16S:
+ case PixelFormat::R16U:
+ case PixelFormat::R16F:
+ params.pixel_format = PixelFormat::Z16;
+ break;
+ case PixelFormat::R32F:
+ params.pixel_format = PixelFormat::Z32F;
+ break;
+ default:
+ LOG_WARNING(HW_GPU, "Color texture format being used with depth compare: {}",
+ static_cast<u32>(params.pixel_format));
+ break;
+ }
+ }
}
params.component_type = ComponentTypeFromTexture(config.tic.r_type.Value());
@@ -266,6 +281,7 @@ std::size_t SurfaceParams::InnerMemorySize(bool force_gl, bool layer_only,
params.component_type = ComponentTypeFromRenderTarget(config.format);
params.type = GetFormatType(params.pixel_format);
params.width = config.width;
+ params.pitch = config.pitch;
params.height = config.height;
params.unaligned_height = config.height;
params.target = SurfaceTarget::Texture2D;
@@ -624,13 +640,16 @@ void CachedSurface::LoadGLBuffer() {
SwizzleFunc(MortonSwizzleMode::MortonToLinear, params, gl_buffer[i], i);
} else {
const u32 bpp = params.GetFormatBpp() / 8;
- const u32 copy_size = params.width * bpp;
+ const u32 copy_size = (params.width * bpp + GetDefaultBlockWidth(params.pixel_format) - 1) /
+ GetDefaultBlockWidth(params.pixel_format);
if (params.pitch == copy_size) {
std::memcpy(gl_buffer[0].data(), params.host_ptr, params.size_in_bytes_gl);
} else {
+ const u32 height = (params.height + GetDefaultBlockHeight(params.pixel_format) - 1) /
+ GetDefaultBlockHeight(params.pixel_format);
const u8* start{params.host_ptr};
u8* write_to = gl_buffer[0].data();
- for (u32 h = params.height; h > 0; h--) {
+ for (u32 h = height; h > 0; h--) {
std::memcpy(write_to, start, copy_size);
start += params.pitch;
write_to += copy_size;
@@ -1175,10 +1194,16 @@ Surface RasterizerCacheOpenGL::RecreateSurface(const Surface& old_surface,
return new_surface;
}
+ const bool old_compressed =
+ GetFormatTuple(old_params.pixel_format, old_params.component_type).compressed;
+ const bool new_compressed =
+ GetFormatTuple(new_params.pixel_format, new_params.component_type).compressed;
+ const bool compatible_formats =
+ GetFormatBpp(old_params.pixel_format) == GetFormatBpp(new_params.pixel_format) &&
+ !(old_compressed || new_compressed);
// For compatible surfaces, we can just do fast glCopyImageSubData based copy
- if (old_params.target == new_params.target && old_params.type == new_params.type &&
- old_params.depth == new_params.depth && old_params.depth == 1 &&
- GetFormatBpp(old_params.pixel_format) == GetFormatBpp(new_params.pixel_format)) {
+ if (old_params.target == new_params.target && old_params.depth == new_params.depth &&
+ old_params.depth == 1 && compatible_formats) {
FastCopySurface(old_surface, new_surface);
return new_surface;
}
@@ -1193,7 +1218,7 @@ Surface RasterizerCacheOpenGL::RecreateSurface(const Surface& old_surface,
case SurfaceTarget::TextureCubemap:
case SurfaceTarget::Texture2DArray:
case SurfaceTarget::TextureCubeArray:
- if (old_params.pixel_format == new_params.pixel_format)
+ if (compatible_formats)
FastLayeredCopySurface(old_surface, new_surface);
else {
AccurateCopySurface(old_surface, new_surface);
diff --git a/src/video_core/renderer_opengl/gl_sampler_cache.cpp b/src/video_core/renderer_opengl/gl_sampler_cache.cpp
new file mode 100644
index 000000000..3ded5ecea
--- /dev/null
+++ b/src/video_core/renderer_opengl/gl_sampler_cache.cpp
@@ -0,0 +1,52 @@
+// Copyright 2019 yuzu Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#include "common/logging/log.h"
+#include "video_core/renderer_opengl/gl_resource_manager.h"
+#include "video_core/renderer_opengl/gl_sampler_cache.h"
+#include "video_core/renderer_opengl/maxwell_to_gl.h"
+
+namespace OpenGL {
+
+SamplerCacheOpenGL::SamplerCacheOpenGL() = default;
+
+SamplerCacheOpenGL::~SamplerCacheOpenGL() = default;
+
+OGLSampler SamplerCacheOpenGL::CreateSampler(const Tegra::Texture::TSCEntry& tsc) const {
+ OGLSampler sampler;
+ sampler.Create();
+
+ const GLuint sampler_id{sampler.handle};
+ glSamplerParameteri(
+ sampler_id, GL_TEXTURE_MAG_FILTER,
+ MaxwellToGL::TextureFilterMode(tsc.mag_filter, Tegra::Texture::TextureMipmapFilter::None));
+ glSamplerParameteri(sampler_id, GL_TEXTURE_MIN_FILTER,
+ MaxwellToGL::TextureFilterMode(tsc.min_filter, tsc.mipmap_filter));
+ glSamplerParameteri(sampler_id, GL_TEXTURE_WRAP_S, MaxwellToGL::WrapMode(tsc.wrap_u));
+ glSamplerParameteri(sampler_id, GL_TEXTURE_WRAP_T, MaxwellToGL::WrapMode(tsc.wrap_v));
+ glSamplerParameteri(sampler_id, GL_TEXTURE_WRAP_R, MaxwellToGL::WrapMode(tsc.wrap_p));
+ glSamplerParameteri(sampler_id, GL_TEXTURE_COMPARE_MODE,
+ tsc.depth_compare_enabled == 1 ? GL_COMPARE_REF_TO_TEXTURE : GL_NONE);
+ glSamplerParameteri(sampler_id, GL_TEXTURE_COMPARE_FUNC,
+ MaxwellToGL::DepthCompareFunc(tsc.depth_compare_func));
+ glSamplerParameterfv(sampler_id, GL_TEXTURE_BORDER_COLOR, tsc.GetBorderColor().data());
+ glSamplerParameterf(sampler_id, GL_TEXTURE_MIN_LOD, tsc.GetMinLod());
+ glSamplerParameterf(sampler_id, GL_TEXTURE_MAX_LOD, tsc.GetMaxLod());
+ glSamplerParameterf(sampler_id, GL_TEXTURE_LOD_BIAS, tsc.GetLodBias());
+ if (GLAD_GL_ARB_texture_filter_anisotropic) {
+ glSamplerParameterf(sampler_id, GL_TEXTURE_MAX_ANISOTROPY, tsc.GetMaxAnisotropy());
+ } else if (GLAD_GL_EXT_texture_filter_anisotropic) {
+ glSamplerParameterf(sampler_id, GL_TEXTURE_MAX_ANISOTROPY_EXT, tsc.GetMaxAnisotropy());
+ } else if (tsc.GetMaxAnisotropy() != 1) {
+ LOG_WARNING(Render_OpenGL, "Anisotropy not supported by host GPU driver");
+ }
+
+ return sampler;
+}
+
+GLuint SamplerCacheOpenGL::ToSamplerType(const OGLSampler& sampler) const {
+ return sampler.handle;
+}
+
+} // namespace OpenGL
diff --git a/src/video_core/renderer_opengl/gl_sampler_cache.h b/src/video_core/renderer_opengl/gl_sampler_cache.h
new file mode 100644
index 000000000..defbc2d81
--- /dev/null
+++ b/src/video_core/renderer_opengl/gl_sampler_cache.h
@@ -0,0 +1,25 @@
+// Copyright 2019 yuzu Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#pragma once
+
+#include <glad/glad.h>
+
+#include "video_core/renderer_opengl/gl_resource_manager.h"
+#include "video_core/sampler_cache.h"
+
+namespace OpenGL {
+
+class SamplerCacheOpenGL final : public VideoCommon::SamplerCache<GLuint, OGLSampler> {
+public:
+ explicit SamplerCacheOpenGL();
+ ~SamplerCacheOpenGL();
+
+protected:
+ OGLSampler CreateSampler(const Tegra::Texture::TSCEntry& tsc) const;
+
+ GLuint ToSamplerType(const OGLSampler& sampler) const;
+};
+
+} // namespace OpenGL
diff --git a/src/video_core/renderer_opengl/gl_shader_cache.cpp b/src/video_core/renderer_opengl/gl_shader_cache.cpp
index 4efeb9c3e..2a81b1169 100644
--- a/src/video_core/renderer_opengl/gl_shader_cache.cpp
+++ b/src/video_core/renderer_opengl/gl_shader_cache.cpp
@@ -38,13 +38,15 @@ GPUVAddr GetShaderAddress(Maxwell::ShaderProgram program) {
}
/// Gets the shader program code from memory for the specified address
-ProgramCode GetShaderCode(const u8* host_ptr) {
+ProgramCode GetShaderCode(Tegra::MemoryManager& memory_manager, const GPUVAddr gpu_addr,
+ const u8* host_ptr) {
ProgramCode program_code(VideoCommon::Shader::MAX_PROGRAM_LENGTH);
ASSERT_OR_EXECUTE(host_ptr != nullptr, {
std::fill(program_code.begin(), program_code.end(), 0);
return program_code;
});
- std::memcpy(program_code.data(), host_ptr, program_code.size() * sizeof(u64));
+ memory_manager.ReadBlockUnsafe(gpu_addr, program_code.data(),
+ program_code.size() * sizeof(u64));
return program_code;
}
@@ -497,11 +499,12 @@ Shader ShaderCacheOpenGL::GetStageProgram(Maxwell::ShaderProgram program) {
if (!shader) {
// No shader found - create a new one
- ProgramCode program_code{GetShaderCode(host_ptr)};
+ ProgramCode program_code{GetShaderCode(memory_manager, program_addr, host_ptr)};
ProgramCode program_code_b;
if (program == Maxwell::ShaderProgram::VertexA) {
- program_code_b = GetShaderCode(
- memory_manager.GetPointer(GetShaderAddress(Maxwell::ShaderProgram::VertexB)));
+ const GPUVAddr program_addr_b{GetShaderAddress(Maxwell::ShaderProgram::VertexB)};
+ program_code_b = GetShaderCode(memory_manager, program_addr_b,
+ memory_manager.GetPointer(program_addr_b));
}
const u64 unique_identifier = GetUniqueIdentifier(program, program_code, program_code_b);
const VAddr cpu_addr{*memory_manager.GpuToCpuAddress(program_addr)};
diff --git a/src/video_core/renderer_opengl/gl_shader_decompiler.cpp b/src/video_core/renderer_opengl/gl_shader_decompiler.cpp
index aee9939db..ef1a1995f 100644
--- a/src/video_core/renderer_opengl/gl_shader_decompiler.cpp
+++ b/src/video_core/renderer_opengl/gl_shader_decompiler.cpp
@@ -46,8 +46,6 @@ using TextureIR = std::variant<TextureAoffi, TextureArgument>;
enum : u32 { POSITION_VARYING_LOCATION = 0, GENERIC_VARYING_START_LOCATION = 1 };
constexpr u32 MAX_CONSTBUFFER_ELEMENTS =
static_cast<u32>(RasterizerOpenGL::MaxConstbufferSize) / (4 * sizeof(float));
-constexpr u32 MAX_GLOBALMEMORY_ELEMENTS =
- static_cast<u32>(RasterizerOpenGL::MaxGlobalMemorySize) / sizeof(float);
class ShaderWriter {
public:
@@ -122,14 +120,10 @@ std::string GetTopologyName(Tegra::Shader::OutputTopology topology) {
/// Returns true if an object has to be treated as precise
bool IsPrecise(Operation operand) {
- const auto& meta = operand.GetMeta();
-
+ const auto& meta{operand.GetMeta()};
if (const auto arithmetic = std::get_if<MetaArithmetic>(&meta)) {
return arithmetic->precise;
}
- if (const auto half_arithmetic = std::get_if<MetaHalfArithmetic>(&meta)) {
- return half_arithmetic->precise;
- }
return false;
}
@@ -210,8 +204,10 @@ public:
for (const auto& sampler : ir.GetSamplers()) {
entries.samplers.emplace_back(sampler);
}
- for (const auto& gmem : ir.GetGlobalMemoryBases()) {
- entries.global_memory_entries.emplace_back(gmem.cbuf_index, gmem.cbuf_offset);
+ for (const auto& gmem_pair : ir.GetGlobalMemory()) {
+ const auto& [base, usage] = gmem_pair;
+ entries.global_memory_entries.emplace_back(base.cbuf_index, base.cbuf_offset,
+ usage.is_read, usage.is_written);
}
entries.clip_distances = ir.GetClipDistances();
entries.shader_length = ir.GetLength();
@@ -382,12 +378,22 @@ private:
}
void DeclareGlobalMemory() {
- for (const auto& entry : ir.GetGlobalMemoryBases()) {
+ for (const auto& gmem : ir.GetGlobalMemory()) {
+ const auto& [base, usage] = gmem;
+
+ // Since we don't know how the shader will use the shader, hint the driver to disable as
+ // much optimizations as possible
+ std::string qualifier = "coherent volatile";
+ if (usage.is_read && !usage.is_written)
+ qualifier += " readonly";
+ else if (usage.is_written && !usage.is_read)
+ qualifier += " writeonly";
+
const std::string binding =
- fmt::format("GMEM_BINDING_{}_{}", entry.cbuf_index, entry.cbuf_offset);
- code.AddLine("layout (std430, binding = " + binding + ") buffer " +
- GetGlobalMemoryBlock(entry) + " {");
- code.AddLine(" float " + GetGlobalMemory(entry) + "[MAX_GLOBALMEMORY_ELEMENTS];");
+ fmt::format("GMEM_BINDING_{}_{}", base.cbuf_index, base.cbuf_offset);
+ code.AddLine("layout (std430, binding = " + binding + ") " + qualifier + " buffer " +
+ GetGlobalMemoryBlock(base) + " {");
+ code.AddLine(" float " + GetGlobalMemory(base) + "[];");
code.AddLine("};");
code.AddNewLine();
}
@@ -619,28 +625,7 @@ private:
}
std::string VisitOperand(Operation operation, std::size_t operand_index, Type type) {
- std::string value = VisitOperand(operation, operand_index);
- switch (type) {
- case Type::HalfFloat: {
- const auto half_meta = std::get_if<MetaHalfArithmetic>(&operation.GetMeta());
- if (!half_meta) {
- value = "toHalf2(" + value + ')';
- }
-
- switch (half_meta->types.at(operand_index)) {
- case Tegra::Shader::HalfType::H0_H1:
- return "toHalf2(" + value + ')';
- case Tegra::Shader::HalfType::F32:
- return "vec2(" + value + ')';
- case Tegra::Shader::HalfType::H0_H0:
- return "vec2(toHalf2(" + value + ")[0])";
- case Tegra::Shader::HalfType::H1_H1:
- return "vec2(toHalf2(" + value + ")[1])";
- }
- }
- default:
- return CastOperand(value, type);
- }
+ return CastOperand(VisitOperand(operation, operand_index), type);
}
std::string CastOperand(const std::string& value, Type type) const {
@@ -654,9 +639,7 @@ private:
case Type::Uint:
return "ftou(" + value + ')';
case Type::HalfFloat:
- // Can't be handled as a stand-alone value
- UNREACHABLE();
- return value;
+ return "toHalf2(" + value + ')';
}
UNREACHABLE();
return value;
@@ -874,6 +857,12 @@ private:
} else if (const auto lmem = std::get_if<LmemNode>(dest)) {
target = GetLocalMemory() + "[ftou(" + Visit(lmem->GetAddress()) + ") / 4]";
+ } else if (const auto gmem = std::get_if<GmemNode>(dest)) {
+ const std::string real = Visit(gmem->GetRealAddress());
+ const std::string base = Visit(gmem->GetBaseAddress());
+ const std::string final_offset = "(ftou(" + real + ") - ftou(" + base + ")) / 4";
+ target = fmt::format("{}[{}]", GetGlobalMemory(gmem->GetDescriptor()), final_offset);
+
} else {
UNREACHABLE_MSG("Assign called without a proper target");
}
@@ -1073,13 +1062,40 @@ private:
return BitwiseCastResult(value, Type::HalfFloat);
}
+ std::string HClamp(Operation operation) {
+ const std::string value = VisitOperand(operation, 0, Type::HalfFloat);
+ const std::string min = VisitOperand(operation, 1, Type::Float);
+ const std::string max = VisitOperand(operation, 2, Type::Float);
+ const std::string clamped = "clamp(" + value + ", vec2(" + min + "), vec2(" + max + "))";
+ return ApplyPrecise(operation, BitwiseCastResult(clamped, Type::HalfFloat));
+ }
+
+ std::string HUnpack(Operation operation) {
+ const std::string operand{VisitOperand(operation, 0, Type::HalfFloat)};
+ const auto value = [&]() -> std::string {
+ switch (std::get<Tegra::Shader::HalfType>(operation.GetMeta())) {
+ case Tegra::Shader::HalfType::H0_H1:
+ return operand;
+ case Tegra::Shader::HalfType::F32:
+ return "vec2(fromHalf2(" + operand + "))";
+ case Tegra::Shader::HalfType::H0_H0:
+ return "vec2(" + operand + "[0])";
+ case Tegra::Shader::HalfType::H1_H1:
+ return "vec2(" + operand + "[1])";
+ }
+ UNREACHABLE();
+ return "0";
+ }();
+ return "fromHalf2(" + value + ')';
+ }
+
std::string HMergeF32(Operation operation) {
return "float(toHalf2(" + Visit(operation[0]) + ")[0])";
}
std::string HMergeH0(Operation operation) {
- return "fromHalf2(vec2(toHalf2(" + Visit(operation[0]) + ")[1], toHalf2(" +
- Visit(operation[1]) + ")[0]))";
+ return "fromHalf2(vec2(toHalf2(" + Visit(operation[1]) + ")[0], toHalf2(" +
+ Visit(operation[0]) + ")[1]))";
}
std::string HMergeH1(Operation operation) {
@@ -1179,34 +1195,46 @@ private:
return GenerateUnary(operation, "any", Type::Bool, Type::Bool2);
}
+ template <bool with_nan>
+ std::string GenerateHalfComparison(Operation operation, std::string compare_op) {
+ std::string comparison{GenerateBinaryCall(operation, compare_op, Type::Bool2,
+ Type::HalfFloat, Type::HalfFloat)};
+ if constexpr (!with_nan) {
+ return comparison;
+ }
+ return "halfFloatNanComparison(" + comparison + ", " +
+ VisitOperand(operation, 0, Type::HalfFloat) + ", " +
+ VisitOperand(operation, 1, Type::HalfFloat) + ')';
+ }
+
+ template <bool with_nan>
std::string Logical2HLessThan(Operation operation) {
- return GenerateBinaryCall(operation, "lessThan", Type::Bool2, Type::HalfFloat,
- Type::HalfFloat);
+ return GenerateHalfComparison<with_nan>(operation, "lessThan");
}
+ template <bool with_nan>
std::string Logical2HEqual(Operation operation) {
- return GenerateBinaryCall(operation, "equal", Type::Bool2, Type::HalfFloat,
- Type::HalfFloat);
+ return GenerateHalfComparison<with_nan>(operation, "equal");
}
+ template <bool with_nan>
std::string Logical2HLessEqual(Operation operation) {
- return GenerateBinaryCall(operation, "lessThanEqual", Type::Bool2, Type::HalfFloat,
- Type::HalfFloat);
+ return GenerateHalfComparison<with_nan>(operation, "lessThanEqual");
}
+ template <bool with_nan>
std::string Logical2HGreaterThan(Operation operation) {
- return GenerateBinaryCall(operation, "greaterThan", Type::Bool2, Type::HalfFloat,
- Type::HalfFloat);
+ return GenerateHalfComparison<with_nan>(operation, "greaterThan");
}
+ template <bool with_nan>
std::string Logical2HNotEqual(Operation operation) {
- return GenerateBinaryCall(operation, "notEqual", Type::Bool2, Type::HalfFloat,
- Type::HalfFloat);
+ return GenerateHalfComparison<with_nan>(operation, "notEqual");
}
+ template <bool with_nan>
std::string Logical2HGreaterEqual(Operation operation) {
- return GenerateBinaryCall(operation, "greaterThanEqual", Type::Bool2, Type::HalfFloat,
- Type::HalfFloat);
+ return GenerateHalfComparison<with_nan>(operation, "greaterThanEqual");
}
std::string Texture(Operation operation) {
@@ -1495,6 +1523,8 @@ private:
&GLSLDecompiler::Fma<Type::HalfFloat>,
&GLSLDecompiler::Absolute<Type::HalfFloat>,
&GLSLDecompiler::HNegate,
+ &GLSLDecompiler::HClamp,
+ &GLSLDecompiler::HUnpack,
&GLSLDecompiler::HMergeF32,
&GLSLDecompiler::HMergeH0,
&GLSLDecompiler::HMergeH1,
@@ -1531,12 +1561,18 @@ private:
&GLSLDecompiler::LogicalNotEqual<Type::Uint>,
&GLSLDecompiler::LogicalGreaterEqual<Type::Uint>,
- &GLSLDecompiler::Logical2HLessThan,
- &GLSLDecompiler::Logical2HEqual,
- &GLSLDecompiler::Logical2HLessEqual,
- &GLSLDecompiler::Logical2HGreaterThan,
- &GLSLDecompiler::Logical2HNotEqual,
- &GLSLDecompiler::Logical2HGreaterEqual,
+ &GLSLDecompiler::Logical2HLessThan<false>,
+ &GLSLDecompiler::Logical2HEqual<false>,
+ &GLSLDecompiler::Logical2HLessEqual<false>,
+ &GLSLDecompiler::Logical2HGreaterThan<false>,
+ &GLSLDecompiler::Logical2HNotEqual<false>,
+ &GLSLDecompiler::Logical2HGreaterEqual<false>,
+ &GLSLDecompiler::Logical2HLessThan<true>,
+ &GLSLDecompiler::Logical2HEqual<true>,
+ &GLSLDecompiler::Logical2HLessEqual<true>,
+ &GLSLDecompiler::Logical2HGreaterThan<true>,
+ &GLSLDecompiler::Logical2HNotEqual<true>,
+ &GLSLDecompiler::Logical2HGreaterEqual<true>,
&GLSLDecompiler::Texture,
&GLSLDecompiler::TextureLod,
@@ -1628,9 +1664,7 @@ private:
std::string GetCommonDeclarations() {
const auto cbuf = std::to_string(MAX_CONSTBUFFER_ELEMENTS);
- const auto gmem = std::to_string(MAX_GLOBALMEMORY_ELEMENTS);
return "#define MAX_CONSTBUFFER_ELEMENTS " + cbuf + "\n" +
- "#define MAX_GLOBALMEMORY_ELEMENTS " + gmem + "\n" +
"#define ftoi floatBitsToInt\n"
"#define ftou floatBitsToUint\n"
"#define itof intBitsToFloat\n"
@@ -1640,6 +1674,12 @@ std::string GetCommonDeclarations() {
"}\n\n"
"vec2 toHalf2(float value) {\n"
" return unpackHalf2x16(ftou(value));\n"
+ "}\n\n"
+ "bvec2 halfFloatNanComparison(bvec2 comparison, vec2 pair1, vec2 pair2) {\n"
+ " bvec2 is_nan1 = isnan(pair1);\n"
+ " bvec2 is_nan2 = isnan(pair2);\n"
+ " return bvec2(comparison.x || is_nan1.x || is_nan2.x, comparison.y || is_nan1.y || "
+ "is_nan2.y);\n"
"}\n";
}
diff --git a/src/video_core/renderer_opengl/gl_shader_decompiler.h b/src/video_core/renderer_opengl/gl_shader_decompiler.h
index fa6b0a10f..c1569e737 100644
--- a/src/video_core/renderer_opengl/gl_shader_decompiler.h
+++ b/src/video_core/renderer_opengl/gl_shader_decompiler.h
@@ -43,8 +43,9 @@ private:
class GlobalMemoryEntry {
public:
- explicit GlobalMemoryEntry(u32 cbuf_index, u32 cbuf_offset)
- : cbuf_index{cbuf_index}, cbuf_offset{cbuf_offset} {}
+ explicit GlobalMemoryEntry(u32 cbuf_index, u32 cbuf_offset, bool is_read, bool is_written)
+ : cbuf_index{cbuf_index}, cbuf_offset{cbuf_offset}, is_read{is_read}, is_written{
+ is_written} {}
u32 GetCbufIndex() const {
return cbuf_index;
@@ -54,14 +55,25 @@ public:
return cbuf_offset;
}
+ bool IsRead() const {
+ return is_read;
+ }
+
+ bool IsWritten() const {
+ return is_written;
+ }
+
private:
u32 cbuf_index{};
u32 cbuf_offset{};
+ bool is_read{};
+ bool is_written{};
};
struct ShaderEntries {
std::vector<ConstBufferEntry> const_buffers;
std::vector<SamplerEntry> samplers;
+ std::vector<SamplerEntry> bindless_samplers;
std::vector<GlobalMemoryEntry> global_memory_entries;
std::array<bool, Maxwell::NumClipDistances> clip_distances{};
std::size_t shader_length{};
@@ -72,4 +84,4 @@ std::string GetCommonDeclarations();
ProgramResult Decompile(const Device& device, const VideoCommon::Shader::ShaderIR& ir,
Maxwell::ShaderStage stage, const std::string& suffix);
-} // namespace OpenGL::GLShader \ No newline at end of file
+} // namespace OpenGL::GLShader
diff --git a/src/video_core/renderer_opengl/gl_shader_disk_cache.cpp b/src/video_core/renderer_opengl/gl_shader_disk_cache.cpp
index 8a43eb157..53752b38d 100644
--- a/src/video_core/renderer_opengl/gl_shader_disk_cache.cpp
+++ b/src/video_core/renderer_opengl/gl_shader_disk_cache.cpp
@@ -319,16 +319,19 @@ std::optional<ShaderDiskCacheDecompiled> ShaderDiskCacheOpenGL::LoadDecompiledEn
u32 type{};
u8 is_array{};
u8 is_shadow{};
+ u8 is_bindless{};
if (file.ReadBytes(&offset, sizeof(u64)) != sizeof(u64) ||
file.ReadBytes(&index, sizeof(u64)) != sizeof(u64) ||
file.ReadBytes(&type, sizeof(u32)) != sizeof(u32) ||
file.ReadBytes(&is_array, sizeof(u8)) != sizeof(u8) ||
- file.ReadBytes(&is_shadow, sizeof(u8)) != sizeof(u8)) {
+ file.ReadBytes(&is_shadow, sizeof(u8)) != sizeof(u8) ||
+ file.ReadBytes(&is_bindless, sizeof(u8)) != sizeof(u8)) {
return {};
}
- entry.entries.samplers.emplace_back(
- static_cast<std::size_t>(offset), static_cast<std::size_t>(index),
- static_cast<Tegra::Shader::TextureType>(type), is_array != 0, is_shadow != 0);
+ entry.entries.samplers.emplace_back(static_cast<std::size_t>(offset),
+ static_cast<std::size_t>(index),
+ static_cast<Tegra::Shader::TextureType>(type),
+ is_array != 0, is_shadow != 0, is_bindless != 0);
}
u32 global_memory_count{};
@@ -337,11 +340,16 @@ std::optional<ShaderDiskCacheDecompiled> ShaderDiskCacheOpenGL::LoadDecompiledEn
for (u32 i = 0; i < global_memory_count; ++i) {
u32 cbuf_index{};
u32 cbuf_offset{};
+ u8 is_read{};
+ u8 is_written{};
if (file.ReadBytes(&cbuf_index, sizeof(u32)) != sizeof(u32) ||
- file.ReadBytes(&cbuf_offset, sizeof(u32)) != sizeof(u32)) {
+ file.ReadBytes(&cbuf_offset, sizeof(u32)) != sizeof(u32) ||
+ file.ReadBytes(&is_read, sizeof(u8)) != sizeof(u8) ||
+ file.ReadBytes(&is_written, sizeof(u8)) != sizeof(u8)) {
return {};
}
- entry.entries.global_memory_entries.emplace_back(cbuf_index, cbuf_offset);
+ entry.entries.global_memory_entries.emplace_back(cbuf_index, cbuf_offset, is_read != 0,
+ is_written != 0);
}
for (auto& clip_distance : entry.entries.clip_distances) {
@@ -388,7 +396,8 @@ bool ShaderDiskCacheOpenGL::SaveDecompiledFile(FileUtil::IOFile& file, u64 uniqu
file.WriteObject(static_cast<u64>(sampler.GetIndex())) != 1 ||
file.WriteObject(static_cast<u32>(sampler.GetType())) != 1 ||
file.WriteObject(static_cast<u8>(sampler.IsArray() ? 1 : 0)) != 1 ||
- file.WriteObject(static_cast<u8>(sampler.IsShadow() ? 1 : 0)) != 1) {
+ file.WriteObject(static_cast<u8>(sampler.IsShadow() ? 1 : 0)) != 1 ||
+ file.WriteObject(static_cast<u8>(sampler.IsBindless() ? 1 : 0)) != 1) {
return false;
}
}
@@ -397,7 +406,9 @@ bool ShaderDiskCacheOpenGL::SaveDecompiledFile(FileUtil::IOFile& file, u64 uniqu
return false;
for (const auto& gmem : entries.global_memory_entries) {
if (file.WriteObject(static_cast<u32>(gmem.GetCbufIndex())) != 1 ||
- file.WriteObject(static_cast<u32>(gmem.GetCbufOffset())) != 1) {
+ file.WriteObject(static_cast<u32>(gmem.GetCbufOffset())) != 1 ||
+ file.WriteObject(static_cast<u8>(gmem.IsRead() ? 1 : 0)) != 1 ||
+ file.WriteObject(static_cast<u8>(gmem.IsWritten() ? 1 : 0)) != 1) {
return false;
}
}
diff --git a/src/video_core/renderer_opengl/gl_shader_manager.cpp b/src/video_core/renderer_opengl/gl_shader_manager.cpp
index eaf3e03a0..05ab01dcb 100644
--- a/src/video_core/renderer_opengl/gl_shader_manager.cpp
+++ b/src/video_core/renderer_opengl/gl_shader_manager.cpp
@@ -2,12 +2,44 @@
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
+#include "common/common_types.h"
+#include "video_core/engines/maxwell_3d.h"
#include "video_core/renderer_opengl/gl_shader_manager.h"
namespace OpenGL::GLShader {
using Tegra::Engines::Maxwell3D;
+ProgramManager::ProgramManager() {
+ pipeline.Create();
+}
+
+ProgramManager::~ProgramManager() = default;
+
+void ProgramManager::ApplyTo(OpenGLState& state) {
+ UpdatePipeline();
+ state.draw.shader_program = 0;
+ state.draw.program_pipeline = pipeline.handle;
+}
+
+void ProgramManager::UpdatePipeline() {
+ // Avoid updating the pipeline when values have no changed
+ if (old_state == current_state) {
+ return;
+ }
+
+ // Workaround for AMD bug
+ constexpr GLenum all_used_stages{GL_VERTEX_SHADER_BIT | GL_GEOMETRY_SHADER_BIT |
+ GL_FRAGMENT_SHADER_BIT};
+ glUseProgramStages(pipeline.handle, all_used_stages, 0);
+
+ glUseProgramStages(pipeline.handle, GL_VERTEX_SHADER_BIT, current_state.vertex_shader);
+ glUseProgramStages(pipeline.handle, GL_GEOMETRY_SHADER_BIT, current_state.geometry_shader);
+ glUseProgramStages(pipeline.handle, GL_FRAGMENT_SHADER_BIT, current_state.fragment_shader);
+
+ old_state = current_state;
+}
+
void MaxwellUniformData::SetFromRegs(const Maxwell3D& maxwell, std::size_t shader_stage) {
const auto& regs = maxwell.regs;
const auto& state = maxwell.state;
@@ -16,7 +48,7 @@ void MaxwellUniformData::SetFromRegs(const Maxwell3D& maxwell, std::size_t shade
viewport_flip[0] = regs.viewport_transform[0].scale_x < 0.0 ? -1.0f : 1.0f;
viewport_flip[1] = regs.viewport_transform[0].scale_y < 0.0 ? -1.0f : 1.0f;
- u32 func = static_cast<u32>(regs.alpha_test_func);
+ auto func{static_cast<u32>(regs.alpha_test_func)};
// Normalize the gl variants of opCompare to be the same as the normal variants
const u32 op_gl_variant_base = static_cast<u32>(Maxwell3D::Regs::ComparisonOp::Never);
if (func >= op_gl_variant_base) {
diff --git a/src/video_core/renderer_opengl/gl_shader_manager.h b/src/video_core/renderer_opengl/gl_shader_manager.h
index 37dcfefdb..cec18a832 100644
--- a/src/video_core/renderer_opengl/gl_shader_manager.h
+++ b/src/video_core/renderer_opengl/gl_shader_manager.h
@@ -4,6 +4,8 @@
#pragma once
+#include <cstddef>
+
#include <glad/glad.h>
#include "video_core/renderer_opengl/gl_resource_manager.h"
@@ -38,55 +40,48 @@ static_assert(sizeof(MaxwellUniformData) < 16384,
class ProgramManager {
public:
- ProgramManager() {
- pipeline.Create();
- }
+ explicit ProgramManager();
+ ~ProgramManager();
+
+ void ApplyTo(OpenGLState& state);
void UseProgrammableVertexShader(GLuint program) {
- vs = program;
+ current_state.vertex_shader = program;
}
void UseProgrammableGeometryShader(GLuint program) {
- gs = program;
+ current_state.geometry_shader = program;
}
void UseProgrammableFragmentShader(GLuint program) {
- fs = program;
+ current_state.fragment_shader = program;
}
void UseTrivialGeometryShader() {
- gs = 0;
- }
-
- void ApplyTo(OpenGLState& state) {
- UpdatePipeline();
- state.draw.shader_program = 0;
- state.draw.program_pipeline = pipeline.handle;
+ current_state.geometry_shader = 0;
}
private:
- void UpdatePipeline() {
- // Avoid updating the pipeline when values have no changed
- if (old_vs == vs && old_fs == fs && old_gs == gs)
- return;
- // Workaround for AMD bug
- glUseProgramStages(pipeline.handle,
- GL_VERTEX_SHADER_BIT | GL_GEOMETRY_SHADER_BIT | GL_FRAGMENT_SHADER_BIT,
- 0);
-
- glUseProgramStages(pipeline.handle, GL_VERTEX_SHADER_BIT, vs);
- glUseProgramStages(pipeline.handle, GL_GEOMETRY_SHADER_BIT, gs);
- glUseProgramStages(pipeline.handle, GL_FRAGMENT_SHADER_BIT, fs);
-
- // Update the old values
- old_vs = vs;
- old_fs = fs;
- old_gs = gs;
- }
+ struct PipelineState {
+ bool operator==(const PipelineState& rhs) const {
+ return vertex_shader == rhs.vertex_shader && fragment_shader == rhs.fragment_shader &&
+ geometry_shader == rhs.geometry_shader;
+ }
+
+ bool operator!=(const PipelineState& rhs) const {
+ return !operator==(rhs);
+ }
+
+ GLuint vertex_shader{};
+ GLuint fragment_shader{};
+ GLuint geometry_shader{};
+ };
+
+ void UpdatePipeline();
OGLPipeline pipeline;
- GLuint vs{}, fs{}, gs{};
- GLuint old_vs{}, old_fs{}, old_gs{};
+ PipelineState current_state;
+ PipelineState old_state;
};
} // namespace OpenGL::GLShader
diff --git a/src/video_core/renderer_vulkan/vk_sampler_cache.cpp b/src/video_core/renderer_vulkan/vk_sampler_cache.cpp
index ed3178f09..801826d3d 100644
--- a/src/video_core/renderer_vulkan/vk_sampler_cache.cpp
+++ b/src/video_core/renderer_vulkan/vk_sampler_cache.cpp
@@ -7,7 +7,6 @@
#include <unordered_map>
#include "common/assert.h"
-#include "common/cityhash.h"
#include "video_core/renderer_vulkan/declarations.h"
#include "video_core/renderer_vulkan/maxwell_to_vk.h"
#include "video_core/renderer_vulkan/vk_sampler_cache.h"
@@ -28,39 +27,20 @@ static std::optional<vk::BorderColor> TryConvertBorderColor(std::array<float, 4>
}
}
-std::size_t SamplerCacheKey::Hash() const {
- static_assert(sizeof(raw) % sizeof(u64) == 0);
- return static_cast<std::size_t>(
- Common::CityHash64(reinterpret_cast<const char*>(raw.data()), sizeof(raw) / sizeof(u64)));
-}
-
-bool SamplerCacheKey::operator==(const SamplerCacheKey& rhs) const {
- return raw == rhs.raw;
-}
-
VKSamplerCache::VKSamplerCache(const VKDevice& device) : device{device} {}
VKSamplerCache::~VKSamplerCache() = default;
-vk::Sampler VKSamplerCache::GetSampler(const Tegra::Texture::TSCEntry& tsc) {
- const auto [entry, is_cache_miss] = cache.try_emplace(SamplerCacheKey{tsc});
- auto& sampler = entry->second;
- if (is_cache_miss) {
- sampler = CreateSampler(tsc);
- }
- return *sampler;
-}
-
-UniqueSampler VKSamplerCache::CreateSampler(const Tegra::Texture::TSCEntry& tsc) {
- const float max_anisotropy = tsc.GetMaxAnisotropy();
- const bool has_anisotropy = max_anisotropy > 1.0f;
+UniqueSampler VKSamplerCache::CreateSampler(const Tegra::Texture::TSCEntry& tsc) const {
+ const float max_anisotropy{tsc.GetMaxAnisotropy()};
+ const bool has_anisotropy{max_anisotropy > 1.0f};
- const auto border_color = tsc.GetBorderColor();
- const auto vk_border_color = TryConvertBorderColor(border_color);
+ const auto border_color{tsc.GetBorderColor()};
+ const auto vk_border_color{TryConvertBorderColor(border_color)};
UNIMPLEMENTED_IF_MSG(!vk_border_color, "Unimplemented border color {} {} {} {}",
border_color[0], border_color[1], border_color[2], border_color[3]);
- constexpr bool unnormalized_coords = false;
+ constexpr bool unnormalized_coords{false};
const vk::SamplerCreateInfo sampler_ci(
{}, MaxwellToVK::Sampler::Filter(tsc.mag_filter),
@@ -73,9 +53,13 @@ UniqueSampler VKSamplerCache::CreateSampler(const Tegra::Texture::TSCEntry& tsc)
tsc.GetMaxLod(), vk_border_color.value_or(vk::BorderColor::eFloatTransparentBlack),
unnormalized_coords);
- const auto& dld = device.GetDispatchLoader();
- const auto dev = device.GetLogical();
+ const auto& dld{device.GetDispatchLoader()};
+ const auto dev{device.GetLogical()};
return dev.createSamplerUnique(sampler_ci, nullptr, dld);
}
+vk::Sampler VKSamplerCache::ToSamplerType(const UniqueSampler& sampler) const {
+ return *sampler;
+}
+
} // namespace Vulkan
diff --git a/src/video_core/renderer_vulkan/vk_sampler_cache.h b/src/video_core/renderer_vulkan/vk_sampler_cache.h
index c6394dc87..771b05c73 100644
--- a/src/video_core/renderer_vulkan/vk_sampler_cache.h
+++ b/src/video_core/renderer_vulkan/vk_sampler_cache.h
@@ -8,49 +8,25 @@
#include "common/common_types.h"
#include "video_core/renderer_vulkan/declarations.h"
+#include "video_core/sampler_cache.h"
#include "video_core/textures/texture.h"
namespace Vulkan {
class VKDevice;
-struct SamplerCacheKey final : public Tegra::Texture::TSCEntry {
- std::size_t Hash() const;
-
- bool operator==(const SamplerCacheKey& rhs) const;
-
- bool operator!=(const SamplerCacheKey& rhs) const {
- return !operator==(rhs);
- }
-};
-
-} // namespace Vulkan
-
-namespace std {
-
-template <>
-struct hash<Vulkan::SamplerCacheKey> {
- std::size_t operator()(const Vulkan::SamplerCacheKey& k) const noexcept {
- return k.Hash();
- }
-};
-
-} // namespace std
-
-namespace Vulkan {
-
-class VKSamplerCache {
+class VKSamplerCache final : public VideoCommon::SamplerCache<vk::Sampler, UniqueSampler> {
public:
explicit VKSamplerCache(const VKDevice& device);
~VKSamplerCache();
- vk::Sampler GetSampler(const Tegra::Texture::TSCEntry& tsc);
+protected:
+ UniqueSampler CreateSampler(const Tegra::Texture::TSCEntry& tsc) const;
-private:
- UniqueSampler CreateSampler(const Tegra::Texture::TSCEntry& tsc);
+ vk::Sampler ToSamplerType(const UniqueSampler& sampler) const;
+private:
const VKDevice& device;
- std::unordered_map<SamplerCacheKey, UniqueSampler> cache;
};
} // namespace Vulkan
diff --git a/src/video_core/renderer_vulkan/vk_shader_decompiler.cpp b/src/video_core/renderer_vulkan/vk_shader_decompiler.cpp
new file mode 100644
index 000000000..23d9b10db
--- /dev/null
+++ b/src/video_core/renderer_vulkan/vk_shader_decompiler.cpp
@@ -0,0 +1,1396 @@
+// Copyright 2019 yuzu Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#include <functional>
+#include <map>
+#include <set>
+
+#include <fmt/format.h>
+
+#include <sirit/sirit.h>
+
+#include "common/alignment.h"
+#include "common/assert.h"
+#include "common/common_types.h"
+#include "common/logging/log.h"
+#include "video_core/engines/maxwell_3d.h"
+#include "video_core/engines/shader_bytecode.h"
+#include "video_core/engines/shader_header.h"
+#include "video_core/renderer_vulkan/vk_shader_decompiler.h"
+#include "video_core/shader/shader_ir.h"
+
+namespace Vulkan::VKShader {
+
+using Sirit::Id;
+using Tegra::Shader::Attribute;
+using Tegra::Shader::AttributeUse;
+using Tegra::Shader::Register;
+using namespace VideoCommon::Shader;
+
+using Maxwell = Tegra::Engines::Maxwell3D::Regs;
+using ShaderStage = Tegra::Engines::Maxwell3D::Regs::ShaderStage;
+using Operation = const OperationNode&;
+
+// TODO(Rodrigo): Use rasterizer's value
+constexpr u32 MAX_CONSTBUFFER_ELEMENTS = 0x1000;
+constexpr u32 STAGE_BINDING_STRIDE = 0x100;
+
+enum class Type { Bool, Bool2, Float, Int, Uint, HalfFloat };
+
+struct SamplerImage {
+ Id image_type;
+ Id sampled_image_type;
+ Id sampler;
+};
+
+namespace {
+
+spv::Dim GetSamplerDim(const Sampler& sampler) {
+ switch (sampler.GetType()) {
+ case Tegra::Shader::TextureType::Texture1D:
+ return spv::Dim::Dim1D;
+ case Tegra::Shader::TextureType::Texture2D:
+ return spv::Dim::Dim2D;
+ case Tegra::Shader::TextureType::Texture3D:
+ return spv::Dim::Dim3D;
+ case Tegra::Shader::TextureType::TextureCube:
+ return spv::Dim::Cube;
+ default:
+ UNIMPLEMENTED_MSG("Unimplemented sampler type={}", static_cast<u32>(sampler.GetType()));
+ return spv::Dim::Dim2D;
+ }
+}
+
+/// Returns true if an attribute index is one of the 32 generic attributes
+constexpr bool IsGenericAttribute(Attribute::Index attribute) {
+ return attribute >= Attribute::Index::Attribute_0 &&
+ attribute <= Attribute::Index::Attribute_31;
+}
+
+/// Returns the location of a generic attribute
+constexpr u32 GetGenericAttributeLocation(Attribute::Index attribute) {
+ ASSERT(IsGenericAttribute(attribute));
+ return static_cast<u32>(attribute) - static_cast<u32>(Attribute::Index::Attribute_0);
+}
+
+/// Returns true if an object has to be treated as precise
+bool IsPrecise(Operation operand) {
+ const auto& meta{operand.GetMeta()};
+ if (std::holds_alternative<MetaArithmetic>(meta)) {
+ return std::get<MetaArithmetic>(meta).precise;
+ }
+ return false;
+}
+
+} // namespace
+
+class SPIRVDecompiler : public Sirit::Module {
+public:
+ explicit SPIRVDecompiler(const ShaderIR& ir, ShaderStage stage)
+ : Module(0x00010300), ir{ir}, stage{stage}, header{ir.GetHeader()} {
+ AddCapability(spv::Capability::Shader);
+ AddExtension("SPV_KHR_storage_buffer_storage_class");
+ AddExtension("SPV_KHR_variable_pointers");
+ }
+
+ void Decompile() {
+ AllocateBindings();
+ AllocateLabels();
+
+ DeclareVertex();
+ DeclareGeometry();
+ DeclareFragment();
+ DeclareRegisters();
+ DeclarePredicates();
+ DeclareLocalMemory();
+ DeclareInternalFlags();
+ DeclareInputAttributes();
+ DeclareOutputAttributes();
+ DeclareConstantBuffers();
+ DeclareGlobalBuffers();
+ DeclareSamplers();
+
+ execute_function =
+ Emit(OpFunction(t_void, spv::FunctionControlMask::Inline, TypeFunction(t_void)));
+ Emit(OpLabel());
+
+ const u32 first_address = ir.GetBasicBlocks().begin()->first;
+ const Id loop_label = OpLabel("loop");
+ const Id merge_label = OpLabel("merge");
+ const Id dummy_label = OpLabel();
+ const Id jump_label = OpLabel();
+ continue_label = OpLabel("continue");
+
+ std::vector<Sirit::Literal> literals;
+ std::vector<Id> branch_labels;
+ for (const auto& pair : labels) {
+ const auto [literal, label] = pair;
+ literals.push_back(literal);
+ branch_labels.push_back(label);
+ }
+
+ // TODO(Rodrigo): Figure out the actual depth of the flow stack, for now it seems unlikely
+ // that shaders will use 20 nested SSYs and PBKs.
+ constexpr u32 FLOW_STACK_SIZE = 20;
+ const Id flow_stack_type = TypeArray(t_uint, Constant(t_uint, FLOW_STACK_SIZE));
+ jmp_to = Emit(OpVariable(TypePointer(spv::StorageClass::Function, t_uint),
+ spv::StorageClass::Function, Constant(t_uint, first_address)));
+ flow_stack = Emit(OpVariable(TypePointer(spv::StorageClass::Function, flow_stack_type),
+ spv::StorageClass::Function, ConstantNull(flow_stack_type)));
+ flow_stack_top =
+ Emit(OpVariable(t_func_uint, spv::StorageClass::Function, Constant(t_uint, 0)));
+
+ Name(jmp_to, "jmp_to");
+ Name(flow_stack, "flow_stack");
+ Name(flow_stack_top, "flow_stack_top");
+
+ Emit(OpBranch(loop_label));
+ Emit(loop_label);
+ Emit(OpLoopMerge(merge_label, continue_label, spv::LoopControlMask::Unroll));
+ Emit(OpBranch(dummy_label));
+
+ Emit(dummy_label);
+ const Id default_branch = OpLabel();
+ const Id jmp_to_load = Emit(OpLoad(t_uint, jmp_to));
+ Emit(OpSelectionMerge(jump_label, spv::SelectionControlMask::MaskNone));
+ Emit(OpSwitch(jmp_to_load, default_branch, literals, branch_labels));
+
+ Emit(default_branch);
+ Emit(OpReturn());
+
+ for (const auto& pair : ir.GetBasicBlocks()) {
+ const auto& [address, bb] = pair;
+ Emit(labels.at(address));
+
+ VisitBasicBlock(bb);
+
+ const auto next_it = labels.lower_bound(address + 1);
+ const Id next_label = next_it != labels.end() ? next_it->second : default_branch;
+ Emit(OpBranch(next_label));
+ }
+
+ Emit(jump_label);
+ Emit(OpBranch(continue_label));
+ Emit(continue_label);
+ Emit(OpBranch(loop_label));
+ Emit(merge_label);
+ Emit(OpReturn());
+ Emit(OpFunctionEnd());
+ }
+
+ ShaderEntries GetShaderEntries() const {
+ ShaderEntries entries;
+ entries.const_buffers_base_binding = const_buffers_base_binding;
+ entries.global_buffers_base_binding = global_buffers_base_binding;
+ entries.samplers_base_binding = samplers_base_binding;
+ for (const auto& cbuf : ir.GetConstantBuffers()) {
+ entries.const_buffers.emplace_back(cbuf.second, cbuf.first);
+ }
+ for (const auto& gmem_pair : ir.GetGlobalMemory()) {
+ const auto& [base, usage] = gmem_pair;
+ entries.global_buffers.emplace_back(base.cbuf_index, base.cbuf_offset);
+ }
+ for (const auto& sampler : ir.GetSamplers()) {
+ entries.samplers.emplace_back(sampler);
+ }
+ for (const auto& attr : ir.GetInputAttributes()) {
+ entries.attributes.insert(GetGenericAttributeLocation(attr.first));
+ }
+ entries.clip_distances = ir.GetClipDistances();
+ entries.shader_length = ir.GetLength();
+ entries.entry_function = execute_function;
+ entries.interfaces = interfaces;
+ return entries;
+ }
+
+private:
+ using OperationDecompilerFn = Id (SPIRVDecompiler::*)(Operation);
+ using OperationDecompilersArray =
+ std::array<OperationDecompilerFn, static_cast<std::size_t>(OperationCode::Amount)>;
+
+ static constexpr auto INTERNAL_FLAGS_COUNT = static_cast<std::size_t>(InternalFlag::Amount);
+ static constexpr u32 CBUF_STRIDE = 16;
+
+ void AllocateBindings() {
+ const u32 binding_base = static_cast<u32>(stage) * STAGE_BINDING_STRIDE;
+ u32 binding_iterator = binding_base;
+
+ const auto Allocate = [&binding_iterator](std::size_t count) {
+ const u32 current_binding = binding_iterator;
+ binding_iterator += static_cast<u32>(count);
+ return current_binding;
+ };
+ const_buffers_base_binding = Allocate(ir.GetConstantBuffers().size());
+ global_buffers_base_binding = Allocate(ir.GetGlobalMemory().size());
+ samplers_base_binding = Allocate(ir.GetSamplers().size());
+
+ ASSERT_MSG(binding_iterator - binding_base < STAGE_BINDING_STRIDE,
+ "Stage binding stride is too small");
+ }
+
+ void AllocateLabels() {
+ for (const auto& pair : ir.GetBasicBlocks()) {
+ const u32 address = pair.first;
+ labels.emplace(address, OpLabel(fmt::format("label_0x{:x}", address)));
+ }
+ }
+
+ void DeclareVertex() {
+ if (stage != ShaderStage::Vertex)
+ return;
+
+ DeclareVertexRedeclarations();
+ }
+
+ void DeclareGeometry() {
+ if (stage != ShaderStage::Geometry)
+ return;
+
+ UNIMPLEMENTED();
+ }
+
+ void DeclareFragment() {
+ if (stage != ShaderStage::Fragment)
+ return;
+
+ for (u32 rt = 0; rt < static_cast<u32>(frag_colors.size()); ++rt) {
+ if (!IsRenderTargetUsed(rt)) {
+ continue;
+ }
+
+ const Id id = AddGlobalVariable(OpVariable(t_out_float4, spv::StorageClass::Output));
+ Name(id, fmt::format("frag_color{}", rt));
+ Decorate(id, spv::Decoration::Location, rt);
+
+ frag_colors[rt] = id;
+ interfaces.push_back(id);
+ }
+
+ if (header.ps.omap.depth) {
+ frag_depth = AddGlobalVariable(OpVariable(t_out_float, spv::StorageClass::Output));
+ Name(frag_depth, "frag_depth");
+ Decorate(frag_depth, spv::Decoration::BuiltIn,
+ static_cast<u32>(spv::BuiltIn::FragDepth));
+
+ interfaces.push_back(frag_depth);
+ }
+
+ frag_coord = DeclareBuiltIn(spv::BuiltIn::FragCoord, spv::StorageClass::Input, t_in_float4,
+ "frag_coord");
+ front_facing = DeclareBuiltIn(spv::BuiltIn::FrontFacing, spv::StorageClass::Input,
+ t_in_bool, "front_facing");
+ }
+
+ void DeclareRegisters() {
+ for (const u32 gpr : ir.GetRegisters()) {
+ const Id id = OpVariable(t_prv_float, spv::StorageClass::Private, v_float_zero);
+ Name(id, fmt::format("gpr_{}", gpr));
+ registers.emplace(gpr, AddGlobalVariable(id));
+ }
+ }
+
+ void DeclarePredicates() {
+ for (const auto pred : ir.GetPredicates()) {
+ const Id id = OpVariable(t_prv_bool, spv::StorageClass::Private, v_false);
+ Name(id, fmt::format("pred_{}", static_cast<u32>(pred)));
+ predicates.emplace(pred, AddGlobalVariable(id));
+ }
+ }
+
+ void DeclareLocalMemory() {
+ if (const u64 local_memory_size = header.GetLocalMemorySize(); local_memory_size > 0) {
+ const auto element_count = static_cast<u32>(Common::AlignUp(local_memory_size, 4) / 4);
+ const Id type_array = TypeArray(t_float, Constant(t_uint, element_count));
+ const Id type_pointer = TypePointer(spv::StorageClass::Private, type_array);
+ Name(type_pointer, "LocalMemory");
+
+ local_memory =
+ OpVariable(type_pointer, spv::StorageClass::Private, ConstantNull(type_array));
+ AddGlobalVariable(Name(local_memory, "local_memory"));
+ }
+ }
+
+ void DeclareInternalFlags() {
+ constexpr std::array<const char*, INTERNAL_FLAGS_COUNT> names = {"zero", "sign", "carry",
+ "overflow"};
+ for (std::size_t flag = 0; flag < INTERNAL_FLAGS_COUNT; ++flag) {
+ const auto flag_code = static_cast<InternalFlag>(flag);
+ const Id id = OpVariable(t_prv_bool, spv::StorageClass::Private, v_false);
+ internal_flags[flag] = AddGlobalVariable(Name(id, names[flag]));
+ }
+ }
+
+ void DeclareInputAttributes() {
+ for (const auto element : ir.GetInputAttributes()) {
+ const Attribute::Index index = element.first;
+ if (!IsGenericAttribute(index)) {
+ continue;
+ }
+
+ UNIMPLEMENTED_IF(stage == ShaderStage::Geometry);
+
+ const u32 location = GetGenericAttributeLocation(index);
+ const Id id = OpVariable(t_in_float4, spv::StorageClass::Input);
+ Name(AddGlobalVariable(id), fmt::format("in_attr{}", location));
+ input_attributes.emplace(index, id);
+ interfaces.push_back(id);
+
+ Decorate(id, spv::Decoration::Location, location);
+
+ if (stage != ShaderStage::Fragment) {
+ continue;
+ }
+ switch (header.ps.GetAttributeUse(location)) {
+ case AttributeUse::Constant:
+ Decorate(id, spv::Decoration::Flat);
+ break;
+ case AttributeUse::ScreenLinear:
+ Decorate(id, spv::Decoration::NoPerspective);
+ break;
+ case AttributeUse::Perspective:
+ // Default
+ break;
+ default:
+ UNREACHABLE_MSG("Unused attribute being fetched");
+ }
+ }
+ }
+
+ void DeclareOutputAttributes() {
+ for (const auto index : ir.GetOutputAttributes()) {
+ if (!IsGenericAttribute(index)) {
+ continue;
+ }
+ const auto location = GetGenericAttributeLocation(index);
+ const Id id = OpVariable(t_out_float4, spv::StorageClass::Output);
+ Name(AddGlobalVariable(id), fmt::format("out_attr{}", location));
+ output_attributes.emplace(index, id);
+ interfaces.push_back(id);
+
+ Decorate(id, spv::Decoration::Location, location);
+ }
+ }
+
+ void DeclareConstantBuffers() {
+ u32 binding = const_buffers_base_binding;
+ for (const auto& entry : ir.GetConstantBuffers()) {
+ const auto [index, size] = entry;
+ const Id id = OpVariable(t_cbuf_ubo, spv::StorageClass::Uniform);
+ AddGlobalVariable(Name(id, fmt::format("cbuf_{}", index)));
+
+ Decorate(id, spv::Decoration::Binding, binding++);
+ Decorate(id, spv::Decoration::DescriptorSet, DESCRIPTOR_SET);
+ constant_buffers.emplace(index, id);
+ }
+ }
+
+ void DeclareGlobalBuffers() {
+ u32 binding = global_buffers_base_binding;
+ for (const auto& entry : ir.GetGlobalMemory()) {
+ const auto [base, usage] = entry;
+ const Id id = OpVariable(t_gmem_ssbo, spv::StorageClass::StorageBuffer);
+ AddGlobalVariable(
+ Name(id, fmt::format("gmem_{}_{}", base.cbuf_index, base.cbuf_offset)));
+
+ Decorate(id, spv::Decoration::Binding, binding++);
+ Decorate(id, spv::Decoration::DescriptorSet, DESCRIPTOR_SET);
+ global_buffers.emplace(base, id);
+ }
+ }
+
+ void DeclareSamplers() {
+ u32 binding = samplers_base_binding;
+ for (const auto& sampler : ir.GetSamplers()) {
+ const auto dim = GetSamplerDim(sampler);
+ const int depth = sampler.IsShadow() ? 1 : 0;
+ const int arrayed = sampler.IsArray() ? 1 : 0;
+ // TODO(Rodrigo): Sampled 1 indicates that the image will be used with a sampler. When
+ // SULD and SUST instructions are implemented, replace this value.
+ const int sampled = 1;
+ const Id image_type =
+ TypeImage(t_float, dim, depth, arrayed, false, sampled, spv::ImageFormat::Unknown);
+ const Id sampled_image_type = TypeSampledImage(image_type);
+ const Id pointer_type =
+ TypePointer(spv::StorageClass::UniformConstant, sampled_image_type);
+ const Id id = OpVariable(pointer_type, spv::StorageClass::UniformConstant);
+ AddGlobalVariable(Name(id, fmt::format("sampler_{}", sampler.GetIndex())));
+
+ sampler_images.insert(
+ {static_cast<u32>(sampler.GetIndex()), {image_type, sampled_image_type, id}});
+
+ Decorate(id, spv::Decoration::Binding, binding++);
+ Decorate(id, spv::Decoration::DescriptorSet, DESCRIPTOR_SET);
+ }
+ }
+
+ void DeclareVertexRedeclarations() {
+ vertex_index = DeclareBuiltIn(spv::BuiltIn::VertexIndex, spv::StorageClass::Input,
+ t_in_uint, "vertex_index");
+ instance_index = DeclareBuiltIn(spv::BuiltIn::InstanceIndex, spv::StorageClass::Input,
+ t_in_uint, "instance_index");
+
+ bool is_point_size_declared = false;
+ bool is_clip_distances_declared = false;
+ for (const auto index : ir.GetOutputAttributes()) {
+ if (index == Attribute::Index::PointSize) {
+ is_point_size_declared = true;
+ } else if (index == Attribute::Index::ClipDistances0123 ||
+ index == Attribute::Index::ClipDistances4567) {
+ is_clip_distances_declared = true;
+ }
+ }
+
+ std::vector<Id> members;
+ members.push_back(t_float4);
+ if (is_point_size_declared) {
+ members.push_back(t_float);
+ }
+ if (is_clip_distances_declared) {
+ members.push_back(TypeArray(t_float, Constant(t_uint, 8)));
+ }
+
+ const Id gl_per_vertex_struct = Name(TypeStruct(members), "PerVertex");
+ Decorate(gl_per_vertex_struct, spv::Decoration::Block);
+
+ u32 declaration_index = 0;
+ const auto MemberDecorateBuiltIn = [&](spv::BuiltIn builtin, std::string name,
+ bool condition) {
+ if (!condition)
+ return u32{};
+ MemberName(gl_per_vertex_struct, declaration_index, name);
+ MemberDecorate(gl_per_vertex_struct, declaration_index, spv::Decoration::BuiltIn,
+ static_cast<u32>(builtin));
+ return declaration_index++;
+ };
+
+ position_index = MemberDecorateBuiltIn(spv::BuiltIn::Position, "position", true);
+ point_size_index =
+ MemberDecorateBuiltIn(spv::BuiltIn::PointSize, "point_size", is_point_size_declared);
+ clip_distances_index = MemberDecorateBuiltIn(spv::BuiltIn::ClipDistance, "clip_distances",
+ is_clip_distances_declared);
+
+ const Id type_pointer = TypePointer(spv::StorageClass::Output, gl_per_vertex_struct);
+ per_vertex = OpVariable(type_pointer, spv::StorageClass::Output);
+ AddGlobalVariable(Name(per_vertex, "per_vertex"));
+ interfaces.push_back(per_vertex);
+ }
+
+ void VisitBasicBlock(const NodeBlock& bb) {
+ for (const Node node : bb) {
+ static_cast<void>(Visit(node));
+ }
+ }
+
+ Id Visit(Node node) {
+ if (const auto operation = std::get_if<OperationNode>(node)) {
+ const auto operation_index = static_cast<std::size_t>(operation->GetCode());
+ const auto decompiler = operation_decompilers[operation_index];
+ if (decompiler == nullptr) {
+ UNREACHABLE_MSG("Operation decompiler {} not defined", operation_index);
+ }
+ return (this->*decompiler)(*operation);
+
+ } else if (const auto gpr = std::get_if<GprNode>(node)) {
+ const u32 index = gpr->GetIndex();
+ if (index == Register::ZeroIndex) {
+ return Constant(t_float, 0.0f);
+ }
+ return Emit(OpLoad(t_float, registers.at(index)));
+
+ } else if (const auto immediate = std::get_if<ImmediateNode>(node)) {
+ return BitcastTo<Type::Float>(Constant(t_uint, immediate->GetValue()));
+
+ } else if (const auto predicate = std::get_if<PredicateNode>(node)) {
+ const auto value = [&]() -> Id {
+ switch (const auto index = predicate->GetIndex(); index) {
+ case Tegra::Shader::Pred::UnusedIndex:
+ return v_true;
+ case Tegra::Shader::Pred::NeverExecute:
+ return v_false;
+ default:
+ return Emit(OpLoad(t_bool, predicates.at(index)));
+ }
+ }();
+ if (predicate->IsNegated()) {
+ return Emit(OpLogicalNot(t_bool, value));
+ }
+ return value;
+
+ } else if (const auto abuf = std::get_if<AbufNode>(node)) {
+ const auto attribute = abuf->GetIndex();
+ const auto element = abuf->GetElement();
+
+ switch (attribute) {
+ case Attribute::Index::Position:
+ if (stage != ShaderStage::Fragment) {
+ UNIMPLEMENTED();
+ break;
+ } else {
+ if (element == 3) {
+ return Constant(t_float, 1.0f);
+ }
+ return Emit(OpLoad(t_float, AccessElement(t_in_float, frag_coord, element)));
+ }
+ case Attribute::Index::TessCoordInstanceIDVertexID:
+ // TODO(Subv): Find out what the values are for the first two elements when inside a
+ // vertex shader, and what's the value of the fourth element when inside a Tess Eval
+ // shader.
+ ASSERT(stage == ShaderStage::Vertex);
+ switch (element) {
+ case 2:
+ return BitcastFrom<Type::Uint>(Emit(OpLoad(t_uint, instance_index)));
+ case 3:
+ return BitcastFrom<Type::Uint>(Emit(OpLoad(t_uint, vertex_index)));
+ }
+ UNIMPLEMENTED_MSG("Unmanaged TessCoordInstanceIDVertexID element={}", element);
+ return Constant(t_float, 0);
+ case Attribute::Index::FrontFacing:
+ // TODO(Subv): Find out what the values are for the other elements.
+ ASSERT(stage == ShaderStage::Fragment);
+ if (element == 3) {
+ const Id is_front_facing = Emit(OpLoad(t_bool, front_facing));
+ const Id true_value =
+ BitcastTo<Type::Float>(Constant(t_int, static_cast<s32>(-1)));
+ const Id false_value = BitcastTo<Type::Float>(Constant(t_int, 0));
+ return Emit(OpSelect(t_float, is_front_facing, true_value, false_value));
+ }
+ UNIMPLEMENTED_MSG("Unmanaged FrontFacing element={}", element);
+ return Constant(t_float, 0.0f);
+ default:
+ if (IsGenericAttribute(attribute)) {
+ const Id pointer =
+ AccessElement(t_in_float, input_attributes.at(attribute), element);
+ return Emit(OpLoad(t_float, pointer));
+ }
+ break;
+ }
+ UNIMPLEMENTED_MSG("Unhandled input attribute: {}", static_cast<u32>(attribute));
+
+ } else if (const auto cbuf = std::get_if<CbufNode>(node)) {
+ const Node offset = cbuf->GetOffset();
+ const Id buffer_id = constant_buffers.at(cbuf->GetIndex());
+
+ Id buffer_index{};
+ Id buffer_element{};
+
+ if (const auto immediate = std::get_if<ImmediateNode>(offset)) {
+ // Direct access
+ const u32 offset_imm = immediate->GetValue();
+ ASSERT(offset_imm % 4 == 0);
+ buffer_index = Constant(t_uint, offset_imm / 16);
+ buffer_element = Constant(t_uint, (offset_imm / 4) % 4);
+
+ } else if (std::holds_alternative<OperationNode>(*offset)) {
+ // Indirect access
+ // TODO(Rodrigo): Use a uniform buffer stride of 4 and drop this slow math (which
+ // emits sub-optimal code on GLSL from my testing).
+ const Id offset_id = BitcastTo<Type::Uint>(Visit(offset));
+ const Id unsafe_offset = Emit(OpUDiv(t_uint, offset_id, Constant(t_uint, 4)));
+ const Id final_offset = Emit(
+ OpUMod(t_uint, unsafe_offset, Constant(t_uint, MAX_CONSTBUFFER_ELEMENTS - 1)));
+ buffer_index = Emit(OpUDiv(t_uint, final_offset, Constant(t_uint, 4)));
+ buffer_element = Emit(OpUMod(t_uint, final_offset, Constant(t_uint, 4)));
+
+ } else {
+ UNREACHABLE_MSG("Unmanaged offset node type");
+ }
+
+ const Id pointer = Emit(OpAccessChain(t_cbuf_float, buffer_id, Constant(t_uint, 0),
+ buffer_index, buffer_element));
+ return Emit(OpLoad(t_float, pointer));
+
+ } else if (const auto gmem = std::get_if<GmemNode>(node)) {
+ const Id gmem_buffer = global_buffers.at(gmem->GetDescriptor());
+ const Id real = BitcastTo<Type::Uint>(Visit(gmem->GetRealAddress()));
+ const Id base = BitcastTo<Type::Uint>(Visit(gmem->GetBaseAddress()));
+
+ Id offset = Emit(OpISub(t_uint, real, base));
+ offset = Emit(OpUDiv(t_uint, offset, Constant(t_uint, 4u)));
+ return Emit(OpLoad(t_float, Emit(OpAccessChain(t_gmem_float, gmem_buffer,
+ Constant(t_uint, 0u), offset))));
+
+ } else if (const auto conditional = std::get_if<ConditionalNode>(node)) {
+ // It's invalid to call conditional on nested nodes, use an operation instead
+ const Id true_label = OpLabel();
+ const Id skip_label = OpLabel();
+ Emit(OpBranchConditional(Visit(conditional->GetCondition()), true_label, skip_label));
+ Emit(true_label);
+
+ VisitBasicBlock(conditional->GetCode());
+
+ Emit(OpBranch(skip_label));
+ Emit(skip_label);
+ return {};
+
+ } else if (const auto comment = std::get_if<CommentNode>(node)) {
+ Name(Emit(OpUndef(t_void)), comment->GetText());
+ return {};
+ }
+
+ UNREACHABLE();
+ return {};
+ }
+
+ template <Id (Module::*func)(Id, Id), Type result_type, Type type_a = result_type>
+ Id Unary(Operation operation) {
+ const Id type_def = GetTypeDefinition(result_type);
+ const Id op_a = VisitOperand<type_a>(operation, 0);
+
+ const Id value = BitcastFrom<result_type>(Emit((this->*func)(type_def, op_a)));
+ if (IsPrecise(operation)) {
+ Decorate(value, spv::Decoration::NoContraction);
+ }
+ return value;
+ }
+
+ template <Id (Module::*func)(Id, Id, Id), Type result_type, Type type_a = result_type,
+ Type type_b = type_a>
+ Id Binary(Operation operation) {
+ const Id type_def = GetTypeDefinition(result_type);
+ const Id op_a = VisitOperand<type_a>(operation, 0);
+ const Id op_b = VisitOperand<type_b>(operation, 1);
+
+ const Id value = BitcastFrom<result_type>(Emit((this->*func)(type_def, op_a, op_b)));
+ if (IsPrecise(operation)) {
+ Decorate(value, spv::Decoration::NoContraction);
+ }
+ return value;
+ }
+
+ template <Id (Module::*func)(Id, Id, Id, Id), Type result_type, Type type_a = result_type,
+ Type type_b = type_a, Type type_c = type_b>
+ Id Ternary(Operation operation) {
+ const Id type_def = GetTypeDefinition(result_type);
+ const Id op_a = VisitOperand<type_a>(operation, 0);
+ const Id op_b = VisitOperand<type_b>(operation, 1);
+ const Id op_c = VisitOperand<type_c>(operation, 2);
+
+ const Id value = BitcastFrom<result_type>(Emit((this->*func)(type_def, op_a, op_b, op_c)));
+ if (IsPrecise(operation)) {
+ Decorate(value, spv::Decoration::NoContraction);
+ }
+ return value;
+ }
+
+ template <Id (Module::*func)(Id, Id, Id, Id, Id), Type result_type, Type type_a = result_type,
+ Type type_b = type_a, Type type_c = type_b, Type type_d = type_c>
+ Id Quaternary(Operation operation) {
+ const Id type_def = GetTypeDefinition(result_type);
+ const Id op_a = VisitOperand<type_a>(operation, 0);
+ const Id op_b = VisitOperand<type_b>(operation, 1);
+ const Id op_c = VisitOperand<type_c>(operation, 2);
+ const Id op_d = VisitOperand<type_d>(operation, 3);
+
+ const Id value =
+ BitcastFrom<result_type>(Emit((this->*func)(type_def, op_a, op_b, op_c, op_d)));
+ if (IsPrecise(operation)) {
+ Decorate(value, spv::Decoration::NoContraction);
+ }
+ return value;
+ }
+
+ Id Assign(Operation operation) {
+ const Node dest = operation[0];
+ const Node src = operation[1];
+
+ Id target{};
+ if (const auto gpr = std::get_if<GprNode>(dest)) {
+ if (gpr->GetIndex() == Register::ZeroIndex) {
+ // Writing to Register::ZeroIndex is a no op
+ return {};
+ }
+ target = registers.at(gpr->GetIndex());
+
+ } else if (const auto abuf = std::get_if<AbufNode>(dest)) {
+ target = [&]() -> Id {
+ switch (const auto attribute = abuf->GetIndex(); attribute) {
+ case Attribute::Index::Position:
+ return AccessElement(t_out_float, per_vertex, position_index,
+ abuf->GetElement());
+ case Attribute::Index::PointSize:
+ return AccessElement(t_out_float, per_vertex, point_size_index);
+ case Attribute::Index::ClipDistances0123:
+ return AccessElement(t_out_float, per_vertex, clip_distances_index,
+ abuf->GetElement());
+ case Attribute::Index::ClipDistances4567:
+ return AccessElement(t_out_float, per_vertex, clip_distances_index,
+ abuf->GetElement() + 4);
+ default:
+ if (IsGenericAttribute(attribute)) {
+ return AccessElement(t_out_float, output_attributes.at(attribute),
+ abuf->GetElement());
+ }
+ UNIMPLEMENTED_MSG("Unhandled output attribute: {}",
+ static_cast<u32>(attribute));
+ return {};
+ }
+ }();
+
+ } else if (const auto lmem = std::get_if<LmemNode>(dest)) {
+ Id address = BitcastTo<Type::Uint>(Visit(lmem->GetAddress()));
+ address = Emit(OpUDiv(t_uint, address, Constant(t_uint, 4)));
+ target = Emit(OpAccessChain(t_prv_float, local_memory, {address}));
+ }
+
+ Emit(OpStore(target, Visit(src)));
+ return {};
+ }
+
+ Id HNegate(Operation operation) {
+ UNIMPLEMENTED();
+ return {};
+ }
+
+ Id HClamp(Operation operation) {
+ UNIMPLEMENTED();
+ return {};
+ }
+
+ Id HUnpack(Operation operation) {
+ UNIMPLEMENTED();
+ return {};
+ }
+
+ Id HMergeF32(Operation operation) {
+ UNIMPLEMENTED();
+ return {};
+ }
+
+ Id HMergeH0(Operation operation) {
+ UNIMPLEMENTED();
+ return {};
+ }
+
+ Id HMergeH1(Operation operation) {
+ UNIMPLEMENTED();
+ return {};
+ }
+
+ Id HPack2(Operation operation) {
+ UNIMPLEMENTED();
+ return {};
+ }
+
+ Id LogicalAssign(Operation operation) {
+ const Node dest = operation[0];
+ const Node src = operation[1];
+
+ Id target{};
+ if (const auto pred = std::get_if<PredicateNode>(dest)) {
+ ASSERT_MSG(!pred->IsNegated(), "Negating logical assignment");
+
+ const auto index = pred->GetIndex();
+ switch (index) {
+ case Tegra::Shader::Pred::NeverExecute:
+ case Tegra::Shader::Pred::UnusedIndex:
+ // Writing to these predicates is a no-op
+ return {};
+ }
+ target = predicates.at(index);
+
+ } else if (const auto flag = std::get_if<InternalFlagNode>(dest)) {
+ target = internal_flags.at(static_cast<u32>(flag->GetFlag()));
+ }
+
+ Emit(OpStore(target, Visit(src)));
+ return {};
+ }
+
+ Id LogicalPick2(Operation operation) {
+ UNIMPLEMENTED();
+ return {};
+ }
+
+ Id LogicalAll2(Operation operation) {
+ UNIMPLEMENTED();
+ return {};
+ }
+
+ Id LogicalAny2(Operation operation) {
+ UNIMPLEMENTED();
+ return {};
+ }
+
+ Id GetTextureSampler(Operation operation) {
+ const auto meta = std::get_if<MetaTexture>(&operation.GetMeta());
+ const auto entry = sampler_images.at(static_cast<u32>(meta->sampler.GetIndex()));
+ return Emit(OpLoad(entry.sampled_image_type, entry.sampler));
+ }
+
+ Id GetTextureImage(Operation operation) {
+ const auto meta = std::get_if<MetaTexture>(&operation.GetMeta());
+ const auto entry = sampler_images.at(static_cast<u32>(meta->sampler.GetIndex()));
+ return Emit(OpImage(entry.image_type, GetTextureSampler(operation)));
+ }
+
+ Id GetTextureCoordinates(Operation operation) {
+ const auto meta = std::get_if<MetaTexture>(&operation.GetMeta());
+ std::vector<Id> coords;
+ for (std::size_t i = 0; i < operation.GetOperandsCount(); ++i) {
+ coords.push_back(Visit(operation[i]));
+ }
+ if (meta->sampler.IsArray()) {
+ const Id array_integer = BitcastTo<Type::Int>(Visit(meta->array));
+ coords.push_back(Emit(OpConvertSToF(t_float, array_integer)));
+ }
+ if (meta->sampler.IsShadow()) {
+ coords.push_back(Visit(meta->depth_compare));
+ }
+
+ const std::array<Id, 4> t_float_lut = {nullptr, t_float2, t_float3, t_float4};
+ return coords.size() == 1
+ ? coords[0]
+ : Emit(OpCompositeConstruct(t_float_lut.at(coords.size() - 1), coords));
+ }
+
+ Id GetTextureElement(Operation operation, Id sample_value) {
+ const auto meta = std::get_if<MetaTexture>(&operation.GetMeta());
+ ASSERT(meta);
+ return Emit(OpCompositeExtract(t_float, sample_value, meta->element));
+ }
+
+ Id Texture(Operation operation) {
+ const Id texture = Emit(OpImageSampleImplicitLod(t_float4, GetTextureSampler(operation),
+ GetTextureCoordinates(operation)));
+ return GetTextureElement(operation, texture);
+ }
+
+ Id TextureLod(Operation operation) {
+ const auto meta = std::get_if<MetaTexture>(&operation.GetMeta());
+ const Id texture = Emit(OpImageSampleExplicitLod(
+ t_float4, GetTextureSampler(operation), GetTextureCoordinates(operation),
+ spv::ImageOperandsMask::Lod, Visit(meta->lod)));
+ return GetTextureElement(operation, texture);
+ }
+
+ Id TextureGather(Operation operation) {
+ const auto meta = std::get_if<MetaTexture>(&operation.GetMeta());
+ const auto coords = GetTextureCoordinates(operation);
+
+ Id texture;
+ if (meta->sampler.IsShadow()) {
+ texture = Emit(OpImageDrefGather(t_float4, GetTextureSampler(operation), coords,
+ Visit(meta->component)));
+ } else {
+ u32 component_value = 0;
+ if (meta->component) {
+ const auto component = std::get_if<ImmediateNode>(meta->component);
+ ASSERT_MSG(component, "Component is not an immediate value");
+ component_value = component->GetValue();
+ }
+ texture = Emit(OpImageGather(t_float4, GetTextureSampler(operation), coords,
+ Constant(t_uint, component_value)));
+ }
+
+ return GetTextureElement(operation, texture);
+ }
+
+ Id TextureQueryDimensions(Operation operation) {
+ const auto meta = std::get_if<MetaTexture>(&operation.GetMeta());
+ const auto image_id = GetTextureImage(operation);
+ AddCapability(spv::Capability::ImageQuery);
+
+ if (meta->element == 3) {
+ return BitcastTo<Type::Float>(Emit(OpImageQueryLevels(t_int, image_id)));
+ }
+
+ const Id lod = VisitOperand<Type::Uint>(operation, 0);
+ const std::size_t coords_count = [&]() {
+ switch (const auto type = meta->sampler.GetType(); type) {
+ case Tegra::Shader::TextureType::Texture1D:
+ return 1;
+ case Tegra::Shader::TextureType::Texture2D:
+ case Tegra::Shader::TextureType::TextureCube:
+ return 2;
+ case Tegra::Shader::TextureType::Texture3D:
+ return 3;
+ default:
+ UNREACHABLE_MSG("Invalid texture type={}", static_cast<u32>(type));
+ return 2;
+ }
+ }();
+
+ if (meta->element >= coords_count) {
+ return Constant(t_float, 0.0f);
+ }
+
+ const std::array<Id, 3> types = {t_int, t_int2, t_int3};
+ const Id sizes = Emit(OpImageQuerySizeLod(types.at(coords_count - 1), image_id, lod));
+ const Id size = Emit(OpCompositeExtract(t_int, sizes, meta->element));
+ return BitcastTo<Type::Float>(size);
+ }
+
+ Id TextureQueryLod(Operation operation) {
+ UNIMPLEMENTED();
+ return {};
+ }
+
+ Id TexelFetch(Operation operation) {
+ UNIMPLEMENTED();
+ return {};
+ }
+
+ Id Branch(Operation operation) {
+ const auto target = std::get_if<ImmediateNode>(operation[0]);
+ UNIMPLEMENTED_IF(!target);
+
+ Emit(OpStore(jmp_to, Constant(t_uint, target->GetValue())));
+ BranchingOp([&]() { Emit(OpBranch(continue_label)); });
+ return {};
+ }
+
+ Id PushFlowStack(Operation operation) {
+ const auto target = std::get_if<ImmediateNode>(operation[0]);
+ ASSERT(target);
+
+ const Id current = Emit(OpLoad(t_uint, flow_stack_top));
+ const Id next = Emit(OpIAdd(t_uint, current, Constant(t_uint, 1)));
+ const Id access = Emit(OpAccessChain(t_func_uint, flow_stack, current));
+
+ Emit(OpStore(access, Constant(t_uint, target->GetValue())));
+ Emit(OpStore(flow_stack_top, next));
+ return {};
+ }
+
+ Id PopFlowStack(Operation operation) {
+ const Id current = Emit(OpLoad(t_uint, flow_stack_top));
+ const Id previous = Emit(OpISub(t_uint, current, Constant(t_uint, 1)));
+ const Id access = Emit(OpAccessChain(t_func_uint, flow_stack, previous));
+ const Id target = Emit(OpLoad(t_uint, access));
+
+ Emit(OpStore(flow_stack_top, previous));
+ Emit(OpStore(jmp_to, target));
+ BranchingOp([&]() { Emit(OpBranch(continue_label)); });
+ return {};
+ }
+
+ Id Exit(Operation operation) {
+ switch (stage) {
+ case ShaderStage::Vertex: {
+ // TODO(Rodrigo): We should use VK_EXT_depth_range_unrestricted instead, but it doesn't
+ // seem to be working on Nvidia's drivers and Intel (mesa and blob) doesn't support it.
+ const Id position = AccessElement(t_float4, per_vertex, position_index);
+ Id depth = Emit(OpLoad(t_float, AccessElement(t_out_float, position, 2)));
+ depth = Emit(OpFAdd(t_float, depth, Constant(t_float, 1.0f)));
+ depth = Emit(OpFMul(t_float, depth, Constant(t_float, 0.5f)));
+ Emit(OpStore(AccessElement(t_out_float, position, 2), depth));
+ break;
+ }
+ case ShaderStage::Fragment: {
+ const auto SafeGetRegister = [&](u32 reg) {
+ // TODO(Rodrigo): Replace with contains once C++20 releases
+ if (const auto it = registers.find(reg); it != registers.end()) {
+ return Emit(OpLoad(t_float, it->second));
+ }
+ return Constant(t_float, 0.0f);
+ };
+
+ UNIMPLEMENTED_IF_MSG(header.ps.omap.sample_mask != 0,
+ "Sample mask write is unimplemented");
+
+ // TODO(Rodrigo): Alpha testing
+
+ // Write the color outputs using the data in the shader registers, disabled
+ // rendertargets/components are skipped in the register assignment.
+ u32 current_reg = 0;
+ for (u32 rt = 0; rt < Maxwell::NumRenderTargets; ++rt) {
+ // TODO(Subv): Figure out how dual-source blending is configured in the Switch.
+ for (u32 component = 0; component < 4; ++component) {
+ if (header.ps.IsColorComponentOutputEnabled(rt, component)) {
+ Emit(OpStore(AccessElement(t_out_float, frag_colors.at(rt), component),
+ SafeGetRegister(current_reg)));
+ ++current_reg;
+ }
+ }
+ }
+ if (header.ps.omap.depth) {
+ // The depth output is always 2 registers after the last color output, and
+ // current_reg already contains one past the last color register.
+ Emit(OpStore(frag_depth, SafeGetRegister(current_reg + 1)));
+ }
+ break;
+ }
+ }
+
+ BranchingOp([&]() { Emit(OpReturn()); });
+ return {};
+ }
+
+ Id Discard(Operation operation) {
+ BranchingOp([&]() { Emit(OpKill()); });
+ return {};
+ }
+
+ Id EmitVertex(Operation operation) {
+ UNIMPLEMENTED();
+ return {};
+ }
+
+ Id EndPrimitive(Operation operation) {
+ UNIMPLEMENTED();
+ return {};
+ }
+
+ Id YNegate(Operation operation) {
+ UNIMPLEMENTED();
+ return {};
+ }
+
+ Id DeclareBuiltIn(spv::BuiltIn builtin, spv::StorageClass storage, Id type,
+ const std::string& name) {
+ const Id id = OpVariable(type, storage);
+ Decorate(id, spv::Decoration::BuiltIn, static_cast<u32>(builtin));
+ AddGlobalVariable(Name(id, name));
+ interfaces.push_back(id);
+ return id;
+ }
+
+ 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;
+ auto elements = {elements_...};
+ for (const auto element : elements) {
+ members.push_back(Constant(t_uint, element));
+ }
+
+ return Emit(OpAccessChain(pointer_type, composite, members));
+ }
+
+ template <Type type>
+ Id VisitOperand(Operation operation, std::size_t operand_index) {
+ const Id value = Visit(operation[operand_index]);
+
+ switch (type) {
+ case Type::Bool:
+ case Type::Bool2:
+ case Type::Float:
+ return value;
+ case Type::Int:
+ return Emit(OpBitcast(t_int, value));
+ case Type::Uint:
+ return Emit(OpBitcast(t_uint, value));
+ case Type::HalfFloat:
+ UNIMPLEMENTED();
+ }
+ UNREACHABLE();
+ return value;
+ }
+
+ template <Type type>
+ Id BitcastFrom(Id value) {
+ switch (type) {
+ case Type::Bool:
+ case Type::Bool2:
+ case Type::Float:
+ return value;
+ case Type::Int:
+ case Type::Uint:
+ return Emit(OpBitcast(t_float, value));
+ case Type::HalfFloat:
+ UNIMPLEMENTED();
+ }
+ UNREACHABLE();
+ return value;
+ }
+
+ template <Type type>
+ Id BitcastTo(Id value) {
+ switch (type) {
+ case Type::Bool:
+ case Type::Bool2:
+ UNREACHABLE();
+ case Type::Float:
+ return Emit(OpBitcast(t_float, value));
+ case Type::Int:
+ return Emit(OpBitcast(t_int, value));
+ case Type::Uint:
+ return Emit(OpBitcast(t_uint, value));
+ case Type::HalfFloat:
+ UNIMPLEMENTED();
+ }
+ UNREACHABLE();
+ return value;
+ }
+
+ Id GetTypeDefinition(Type type) {
+ switch (type) {
+ case Type::Bool:
+ return t_bool;
+ case Type::Bool2:
+ return t_bool2;
+ case Type::Float:
+ return t_float;
+ case Type::Int:
+ return t_int;
+ case Type::Uint:
+ return t_uint;
+ case Type::HalfFloat:
+ UNIMPLEMENTED();
+ }
+ UNREACHABLE();
+ return {};
+ }
+
+ void BranchingOp(std::function<void()> call) {
+ const Id true_label = OpLabel();
+ const Id skip_label = OpLabel();
+ Emit(OpSelectionMerge(skip_label, spv::SelectionControlMask::Flatten));
+ Emit(OpBranchConditional(v_true, true_label, skip_label, 1, 0));
+ Emit(true_label);
+ call();
+
+ Emit(skip_label);
+ }
+
+ static constexpr OperationDecompilersArray operation_decompilers = {
+ &SPIRVDecompiler::Assign,
+
+ &SPIRVDecompiler::Ternary<&Module::OpSelect, Type::Float, Type::Bool, Type::Float,
+ Type::Float>,
+
+ &SPIRVDecompiler::Binary<&Module::OpFAdd, Type::Float>,
+ &SPIRVDecompiler::Binary<&Module::OpFMul, Type::Float>,
+ &SPIRVDecompiler::Binary<&Module::OpFDiv, Type::Float>,
+ &SPIRVDecompiler::Ternary<&Module::OpFma, Type::Float>,
+ &SPIRVDecompiler::Unary<&Module::OpFNegate, Type::Float>,
+ &SPIRVDecompiler::Unary<&Module::OpFAbs, Type::Float>,
+ &SPIRVDecompiler::Ternary<&Module::OpFClamp, Type::Float>,
+ &SPIRVDecompiler::Binary<&Module::OpFMin, Type::Float>,
+ &SPIRVDecompiler::Binary<&Module::OpFMax, Type::Float>,
+ &SPIRVDecompiler::Unary<&Module::OpCos, Type::Float>,
+ &SPIRVDecompiler::Unary<&Module::OpSin, Type::Float>,
+ &SPIRVDecompiler::Unary<&Module::OpExp2, Type::Float>,
+ &SPIRVDecompiler::Unary<&Module::OpLog2, Type::Float>,
+ &SPIRVDecompiler::Unary<&Module::OpInverseSqrt, Type::Float>,
+ &SPIRVDecompiler::Unary<&Module::OpSqrt, Type::Float>,
+ &SPIRVDecompiler::Unary<&Module::OpRoundEven, Type::Float>,
+ &SPIRVDecompiler::Unary<&Module::OpFloor, Type::Float>,
+ &SPIRVDecompiler::Unary<&Module::OpCeil, Type::Float>,
+ &SPIRVDecompiler::Unary<&Module::OpTrunc, Type::Float>,
+ &SPIRVDecompiler::Unary<&Module::OpConvertSToF, Type::Float, Type::Int>,
+ &SPIRVDecompiler::Unary<&Module::OpConvertUToF, Type::Float, Type::Uint>,
+
+ &SPIRVDecompiler::Binary<&Module::OpIAdd, Type::Int>,
+ &SPIRVDecompiler::Binary<&Module::OpIMul, Type::Int>,
+ &SPIRVDecompiler::Binary<&Module::OpSDiv, Type::Int>,
+ &SPIRVDecompiler::Unary<&Module::OpSNegate, Type::Int>,
+ &SPIRVDecompiler::Unary<&Module::OpSAbs, Type::Int>,
+ &SPIRVDecompiler::Binary<&Module::OpSMin, Type::Int>,
+ &SPIRVDecompiler::Binary<&Module::OpSMax, Type::Int>,
+
+ &SPIRVDecompiler::Unary<&Module::OpConvertFToS, Type::Int, Type::Float>,
+ &SPIRVDecompiler::Unary<&Module::OpBitcast, Type::Int, Type::Uint>,
+ &SPIRVDecompiler::Binary<&Module::OpShiftLeftLogical, Type::Int, Type::Int, Type::Uint>,
+ &SPIRVDecompiler::Binary<&Module::OpShiftRightLogical, Type::Int, Type::Int, Type::Uint>,
+ &SPIRVDecompiler::Binary<&Module::OpShiftRightArithmetic, Type::Int, Type::Int, Type::Uint>,
+ &SPIRVDecompiler::Binary<&Module::OpBitwiseAnd, Type::Int>,
+ &SPIRVDecompiler::Binary<&Module::OpBitwiseOr, Type::Int>,
+ &SPIRVDecompiler::Binary<&Module::OpBitwiseXor, Type::Int>,
+ &SPIRVDecompiler::Unary<&Module::OpNot, Type::Int>,
+ &SPIRVDecompiler::Quaternary<&Module::OpBitFieldInsert, Type::Int>,
+ &SPIRVDecompiler::Ternary<&Module::OpBitFieldSExtract, Type::Int>,
+ &SPIRVDecompiler::Unary<&Module::OpBitCount, Type::Int>,
+
+ &SPIRVDecompiler::Binary<&Module::OpIAdd, Type::Uint>,
+ &SPIRVDecompiler::Binary<&Module::OpIMul, Type::Uint>,
+ &SPIRVDecompiler::Binary<&Module::OpUDiv, Type::Uint>,
+ &SPIRVDecompiler::Binary<&Module::OpUMin, Type::Uint>,
+ &SPIRVDecompiler::Binary<&Module::OpUMax, Type::Uint>,
+ &SPIRVDecompiler::Unary<&Module::OpConvertFToU, Type::Uint, Type::Float>,
+ &SPIRVDecompiler::Unary<&Module::OpBitcast, Type::Uint, Type::Int>,
+ &SPIRVDecompiler::Binary<&Module::OpShiftLeftLogical, Type::Uint>,
+ &SPIRVDecompiler::Binary<&Module::OpShiftRightLogical, Type::Uint>,
+ &SPIRVDecompiler::Binary<&Module::OpShiftRightArithmetic, Type::Uint>,
+ &SPIRVDecompiler::Binary<&Module::OpBitwiseAnd, Type::Uint>,
+ &SPIRVDecompiler::Binary<&Module::OpBitwiseOr, Type::Uint>,
+ &SPIRVDecompiler::Binary<&Module::OpBitwiseXor, Type::Uint>,
+ &SPIRVDecompiler::Unary<&Module::OpNot, Type::Uint>,
+ &SPIRVDecompiler::Quaternary<&Module::OpBitFieldInsert, Type::Uint>,
+ &SPIRVDecompiler::Ternary<&Module::OpBitFieldUExtract, Type::Uint>,
+ &SPIRVDecompiler::Unary<&Module::OpBitCount, Type::Uint>,
+
+ &SPIRVDecompiler::Binary<&Module::OpFAdd, Type::HalfFloat>,
+ &SPIRVDecompiler::Binary<&Module::OpFMul, Type::HalfFloat>,
+ &SPIRVDecompiler::Ternary<&Module::OpFma, Type::HalfFloat>,
+ &SPIRVDecompiler::Unary<&Module::OpFAbs, Type::HalfFloat>,
+ &SPIRVDecompiler::HNegate,
+ &SPIRVDecompiler::HClamp,
+ &SPIRVDecompiler::HUnpack,
+ &SPIRVDecompiler::HMergeF32,
+ &SPIRVDecompiler::HMergeH0,
+ &SPIRVDecompiler::HMergeH1,
+ &SPIRVDecompiler::HPack2,
+
+ &SPIRVDecompiler::LogicalAssign,
+ &SPIRVDecompiler::Binary<&Module::OpLogicalAnd, Type::Bool>,
+ &SPIRVDecompiler::Binary<&Module::OpLogicalOr, Type::Bool>,
+ &SPIRVDecompiler::Binary<&Module::OpLogicalNotEqual, Type::Bool>,
+ &SPIRVDecompiler::Unary<&Module::OpLogicalNot, Type::Bool>,
+ &SPIRVDecompiler::LogicalPick2,
+ &SPIRVDecompiler::LogicalAll2,
+ &SPIRVDecompiler::LogicalAny2,
+
+ &SPIRVDecompiler::Binary<&Module::OpFOrdLessThan, Type::Bool, Type::Float>,
+ &SPIRVDecompiler::Binary<&Module::OpFOrdEqual, Type::Bool, Type::Float>,
+ &SPIRVDecompiler::Binary<&Module::OpFOrdLessThanEqual, Type::Bool, Type::Float>,
+ &SPIRVDecompiler::Binary<&Module::OpFOrdGreaterThan, Type::Bool, Type::Float>,
+ &SPIRVDecompiler::Binary<&Module::OpFOrdNotEqual, Type::Bool, Type::Float>,
+ &SPIRVDecompiler::Binary<&Module::OpFOrdGreaterThanEqual, Type::Bool, Type::Float>,
+ &SPIRVDecompiler::Unary<&Module::OpIsNan, Type::Bool>,
+
+ &SPIRVDecompiler::Binary<&Module::OpSLessThan, Type::Bool, Type::Int>,
+ &SPIRVDecompiler::Binary<&Module::OpIEqual, Type::Bool, Type::Int>,
+ &SPIRVDecompiler::Binary<&Module::OpSLessThanEqual, Type::Bool, Type::Int>,
+ &SPIRVDecompiler::Binary<&Module::OpSGreaterThan, Type::Bool, Type::Int>,
+ &SPIRVDecompiler::Binary<&Module::OpINotEqual, Type::Bool, Type::Int>,
+ &SPIRVDecompiler::Binary<&Module::OpSGreaterThanEqual, Type::Bool, Type::Int>,
+
+ &SPIRVDecompiler::Binary<&Module::OpULessThan, Type::Bool, Type::Uint>,
+ &SPIRVDecompiler::Binary<&Module::OpIEqual, Type::Bool, Type::Uint>,
+ &SPIRVDecompiler::Binary<&Module::OpULessThanEqual, Type::Bool, Type::Uint>,
+ &SPIRVDecompiler::Binary<&Module::OpUGreaterThan, Type::Bool, Type::Uint>,
+ &SPIRVDecompiler::Binary<&Module::OpINotEqual, Type::Bool, Type::Uint>,
+ &SPIRVDecompiler::Binary<&Module::OpUGreaterThanEqual, Type::Bool, Type::Uint>,
+
+ &SPIRVDecompiler::Binary<&Module::OpFOrdLessThan, Type::Bool, Type::HalfFloat>,
+ &SPIRVDecompiler::Binary<&Module::OpFOrdEqual, Type::Bool, Type::HalfFloat>,
+ &SPIRVDecompiler::Binary<&Module::OpFOrdLessThanEqual, Type::Bool, Type::HalfFloat>,
+ &SPIRVDecompiler::Binary<&Module::OpFOrdGreaterThan, Type::Bool, Type::HalfFloat>,
+ &SPIRVDecompiler::Binary<&Module::OpFOrdNotEqual, Type::Bool, Type::HalfFloat>,
+ &SPIRVDecompiler::Binary<&Module::OpFOrdGreaterThanEqual, Type::Bool, Type::HalfFloat>,
+ // TODO(Rodrigo): Should these use the OpFUnord* variants?
+ &SPIRVDecompiler::Binary<&Module::OpFOrdLessThan, Type::Bool, Type::HalfFloat>,
+ &SPIRVDecompiler::Binary<&Module::OpFOrdEqual, Type::Bool, Type::HalfFloat>,
+ &SPIRVDecompiler::Binary<&Module::OpFOrdLessThanEqual, Type::Bool, Type::HalfFloat>,
+ &SPIRVDecompiler::Binary<&Module::OpFOrdGreaterThan, Type::Bool, Type::HalfFloat>,
+ &SPIRVDecompiler::Binary<&Module::OpFOrdNotEqual, Type::Bool, Type::HalfFloat>,
+ &SPIRVDecompiler::Binary<&Module::OpFOrdGreaterThanEqual, Type::Bool, Type::HalfFloat>,
+
+ &SPIRVDecompiler::Texture,
+ &SPIRVDecompiler::TextureLod,
+ &SPIRVDecompiler::TextureGather,
+ &SPIRVDecompiler::TextureQueryDimensions,
+ &SPIRVDecompiler::TextureQueryLod,
+ &SPIRVDecompiler::TexelFetch,
+
+ &SPIRVDecompiler::Branch,
+ &SPIRVDecompiler::PushFlowStack,
+ &SPIRVDecompiler::PopFlowStack,
+ &SPIRVDecompiler::Exit,
+ &SPIRVDecompiler::Discard,
+
+ &SPIRVDecompiler::EmitVertex,
+ &SPIRVDecompiler::EndPrimitive,
+
+ &SPIRVDecompiler::YNegate,
+ };
+
+ const ShaderIR& ir;
+ const ShaderStage stage;
+ const Tegra::Shader::Header header;
+
+ const Id t_void = Name(TypeVoid(), "void");
+
+ const Id t_bool = Name(TypeBool(), "bool");
+ const Id t_bool2 = Name(TypeVector(t_bool, 2), "bool2");
+
+ const Id t_int = Name(TypeInt(32, true), "int");
+ const Id t_int2 = Name(TypeVector(t_int, 2), "int2");
+ const Id t_int3 = Name(TypeVector(t_int, 3), "int3");
+ const Id t_int4 = Name(TypeVector(t_int, 4), "int4");
+
+ const Id t_uint = Name(TypeInt(32, false), "uint");
+ const Id t_uint2 = Name(TypeVector(t_uint, 2), "uint2");
+ const Id t_uint3 = Name(TypeVector(t_uint, 3), "uint3");
+ const Id t_uint4 = Name(TypeVector(t_uint, 4), "uint4");
+
+ const Id t_float = Name(TypeFloat(32), "float");
+ const Id t_float2 = Name(TypeVector(t_float, 2), "float2");
+ const Id t_float3 = Name(TypeVector(t_float, 3), "float3");
+ const Id t_float4 = Name(TypeVector(t_float, 4), "float4");
+
+ const Id t_prv_bool = Name(TypePointer(spv::StorageClass::Private, t_bool), "prv_bool");
+ const Id t_prv_float = Name(TypePointer(spv::StorageClass::Private, t_float), "prv_float");
+
+ const Id t_func_uint = Name(TypePointer(spv::StorageClass::Function, t_uint), "func_uint");
+
+ const Id t_in_bool = Name(TypePointer(spv::StorageClass::Input, t_bool), "in_bool");
+ const Id t_in_uint = Name(TypePointer(spv::StorageClass::Input, t_uint), "in_uint");
+ const Id t_in_float = Name(TypePointer(spv::StorageClass::Input, t_float), "in_float");
+ const Id t_in_float4 = Name(TypePointer(spv::StorageClass::Input, t_float4), "in_float4");
+
+ const Id t_out_float = Name(TypePointer(spv::StorageClass::Output, t_float), "out_float");
+ const Id t_out_float4 = Name(TypePointer(spv::StorageClass::Output, t_float4), "out_float4");
+
+ const Id t_cbuf_float = TypePointer(spv::StorageClass::Uniform, t_float);
+ const Id t_cbuf_array =
+ Decorate(Name(TypeArray(t_float4, Constant(t_uint, MAX_CONSTBUFFER_ELEMENTS)), "CbufArray"),
+ spv::Decoration::ArrayStride, CBUF_STRIDE);
+ const Id t_cbuf_struct = MemberDecorate(
+ Decorate(TypeStruct(t_cbuf_array), spv::Decoration::Block), 0, spv::Decoration::Offset, 0);
+ const Id t_cbuf_ubo = TypePointer(spv::StorageClass::Uniform, t_cbuf_struct);
+
+ const Id t_gmem_float = TypePointer(spv::StorageClass::StorageBuffer, t_float);
+ const Id t_gmem_array =
+ Name(Decorate(TypeRuntimeArray(t_float), spv::Decoration::ArrayStride, 4u), "GmemArray");
+ const Id t_gmem_struct = MemberDecorate(
+ Decorate(TypeStruct(t_gmem_array), spv::Decoration::Block), 0, spv::Decoration::Offset, 0);
+ const Id t_gmem_ssbo = TypePointer(spv::StorageClass::StorageBuffer, t_gmem_struct);
+
+ const Id v_float_zero = Constant(t_float, 0.0f);
+ const Id v_true = ConstantTrue(t_bool);
+ const Id v_false = ConstantFalse(t_bool);
+
+ Id per_vertex{};
+ std::map<u32, Id> registers;
+ std::map<Tegra::Shader::Pred, Id> predicates;
+ Id local_memory{};
+ std::array<Id, INTERNAL_FLAGS_COUNT> internal_flags{};
+ std::map<Attribute::Index, Id> input_attributes;
+ std::map<Attribute::Index, Id> output_attributes;
+ std::map<u32, Id> constant_buffers;
+ std::map<GlobalMemoryBase, Id> global_buffers;
+ std::map<u32, SamplerImage> sampler_images;
+
+ Id instance_index{};
+ Id vertex_index{};
+ std::array<Id, Maxwell::NumRenderTargets> frag_colors{};
+ Id frag_depth{};
+ Id frag_coord{};
+ Id front_facing{};
+
+ u32 position_index{};
+ u32 point_size_index{};
+ u32 clip_distances_index{};
+
+ std::vector<Id> interfaces;
+
+ u32 const_buffers_base_binding{};
+ u32 global_buffers_base_binding{};
+ u32 samplers_base_binding{};
+
+ Id execute_function{};
+ Id jmp_to{};
+ Id flow_stack_top{};
+ Id flow_stack{};
+ Id continue_label{};
+ std::map<u32, Id> labels;
+};
+
+DecompilerResult Decompile(const VideoCommon::Shader::ShaderIR& ir, Maxwell::ShaderStage stage) {
+ auto decompiler = std::make_unique<SPIRVDecompiler>(ir, stage);
+ decompiler->Decompile();
+ return {std::move(decompiler), decompiler->GetShaderEntries()};
+}
+
+} // namespace Vulkan::VKShader
diff --git a/src/video_core/renderer_vulkan/vk_shader_decompiler.h b/src/video_core/renderer_vulkan/vk_shader_decompiler.h
new file mode 100644
index 000000000..329d8fa38
--- /dev/null
+++ b/src/video_core/renderer_vulkan/vk_shader_decompiler.h
@@ -0,0 +1,80 @@
+// Copyright 2019 yuzu Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#pragma once
+
+#include <array>
+#include <memory>
+#include <set>
+#include <utility>
+#include <vector>
+
+#include <sirit/sirit.h>
+
+#include "common/common_types.h"
+#include "video_core/engines/maxwell_3d.h"
+#include "video_core/shader/shader_ir.h"
+
+namespace VideoCommon::Shader {
+class ShaderIR;
+}
+
+namespace Vulkan::VKShader {
+
+using Maxwell = Tegra::Engines::Maxwell3D::Regs;
+
+using SamplerEntry = VideoCommon::Shader::Sampler;
+
+constexpr u32 DESCRIPTOR_SET = 0;
+
+class ConstBufferEntry : public VideoCommon::Shader::ConstBuffer {
+public:
+ explicit constexpr ConstBufferEntry(const VideoCommon::Shader::ConstBuffer& entry, u32 index)
+ : VideoCommon::Shader::ConstBuffer{entry}, index{index} {}
+
+ constexpr u32 GetIndex() const {
+ return index;
+ }
+
+private:
+ u32 index{};
+};
+
+class GlobalBufferEntry {
+public:
+ explicit GlobalBufferEntry(u32 cbuf_index, u32 cbuf_offset)
+ : cbuf_index{cbuf_index}, cbuf_offset{cbuf_offset} {}
+
+ u32 GetCbufIndex() const {
+ return cbuf_index;
+ }
+
+ u32 GetCbufOffset() const {
+ return cbuf_offset;
+ }
+
+private:
+ u32 cbuf_index{};
+ u32 cbuf_offset{};
+};
+
+struct ShaderEntries {
+ u32 const_buffers_base_binding{};
+ u32 global_buffers_base_binding{};
+ u32 samplers_base_binding{};
+ std::vector<ConstBufferEntry> const_buffers;
+ std::vector<GlobalBufferEntry> global_buffers;
+ std::vector<SamplerEntry> samplers;
+ std::set<u32> attributes;
+ std::array<bool, Maxwell::NumClipDistances> clip_distances{};
+ std::size_t shader_length{};
+ Sirit::Id entry_function{};
+ std::vector<Sirit::Id> interfaces;
+};
+
+using DecompilerResult = std::pair<std::unique_ptr<Sirit::Module>, ShaderEntries>;
+
+DecompilerResult Decompile(const VideoCommon::Shader::ShaderIR& ir, Maxwell::ShaderStage stage);
+
+} // namespace Vulkan::VKShader
diff --git a/src/video_core/sampler_cache.cpp b/src/video_core/sampler_cache.cpp
new file mode 100644
index 000000000..53c7ef12d
--- /dev/null
+++ b/src/video_core/sampler_cache.cpp
@@ -0,0 +1,21 @@
+// Copyright 2019 yuzu Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#include "common/cityhash.h"
+#include "common/common_types.h"
+#include "video_core/sampler_cache.h"
+
+namespace VideoCommon {
+
+std::size_t SamplerCacheKey::Hash() const {
+ static_assert(sizeof(raw) % sizeof(u64) == 0);
+ return static_cast<std::size_t>(
+ Common::CityHash64(reinterpret_cast<const char*>(raw.data()), sizeof(raw) / sizeof(u64)));
+}
+
+bool SamplerCacheKey::operator==(const SamplerCacheKey& rhs) const {
+ return raw == rhs.raw;
+}
+
+} // namespace VideoCommon
diff --git a/src/video_core/sampler_cache.h b/src/video_core/sampler_cache.h
new file mode 100644
index 000000000..cbe3ad071
--- /dev/null
+++ b/src/video_core/sampler_cache.h
@@ -0,0 +1,60 @@
+// Copyright 2019 yuzu Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#pragma once
+
+#include <cstddef>
+#include <unordered_map>
+
+#include "video_core/textures/texture.h"
+
+namespace VideoCommon {
+
+struct SamplerCacheKey final : public Tegra::Texture::TSCEntry {
+ std::size_t Hash() const;
+
+ bool operator==(const SamplerCacheKey& rhs) const;
+
+ bool operator!=(const SamplerCacheKey& rhs) const {
+ return !operator==(rhs);
+ }
+};
+
+} // namespace VideoCommon
+
+namespace std {
+
+template <>
+struct hash<VideoCommon::SamplerCacheKey> {
+ std::size_t operator()(const VideoCommon::SamplerCacheKey& k) const noexcept {
+ return k.Hash();
+ }
+};
+
+} // namespace std
+
+namespace VideoCommon {
+
+template <typename SamplerType, typename SamplerStorageType>
+class SamplerCache {
+public:
+ SamplerType GetSampler(const Tegra::Texture::TSCEntry& tsc) {
+ const auto [entry, is_cache_miss] = cache.try_emplace(SamplerCacheKey{tsc});
+ auto& sampler = entry->second;
+ if (is_cache_miss) {
+ sampler = CreateSampler(tsc);
+ }
+ return ToSamplerType(sampler);
+ }
+
+protected:
+ virtual SamplerStorageType CreateSampler(const Tegra::Texture::TSCEntry& tsc) const = 0;
+
+ virtual SamplerType ToSamplerType(const SamplerStorageType& sampler) const = 0;
+
+private:
+ std::unordered_map<SamplerCacheKey, SamplerStorageType> cache;
+};
+
+} // namespace VideoCommon \ No newline at end of file
diff --git a/src/video_core/shader/decode/arithmetic_half.cpp b/src/video_core/shader/decode/arithmetic_half.cpp
index baee89107..9467f9417 100644
--- a/src/video_core/shader/decode/arithmetic_half.cpp
+++ b/src/video_core/shader/decode/arithmetic_half.cpp
@@ -18,7 +18,9 @@ u32 ShaderIR::DecodeArithmeticHalf(NodeBlock& bb, u32 pc) {
if (opcode->get().GetId() == OpCode::Id::HADD2_C ||
opcode->get().GetId() == OpCode::Id::HADD2_R) {
- UNIMPLEMENTED_IF(instr.alu_half.ftz != 0);
+ if (instr.alu_half.ftz != 0) {
+ LOG_WARNING(HW_GPU, "{} FTZ not implemented", opcode->get().GetName());
+ }
}
UNIMPLEMENTED_IF_MSG(instr.alu_half.saturate != 0, "Half float saturation not implemented");
@@ -27,9 +29,8 @@ u32 ShaderIR::DecodeArithmeticHalf(NodeBlock& bb, u32 pc) {
const bool negate_b =
opcode->get().GetId() != OpCode::Id::HMUL2_C && instr.alu_half.negate_b != 0;
- const Node op_a = GetOperandAbsNegHalf(GetRegister(instr.gpr8), instr.alu_half.abs_a, negate_a);
-
- // instr.alu_half.type_a
+ Node op_a = UnpackHalfFloat(GetRegister(instr.gpr8), instr.alu_half.type_a);
+ op_a = GetOperandAbsNegHalf(op_a, instr.alu_half.abs_a, negate_a);
Node op_b = [&]() {
switch (opcode->get().GetId()) {
@@ -44,17 +45,17 @@ u32 ShaderIR::DecodeArithmeticHalf(NodeBlock& bb, u32 pc) {
return Immediate(0);
}
}();
+ op_b = UnpackHalfFloat(op_b, instr.alu_half.type_b);
op_b = GetOperandAbsNegHalf(op_b, instr.alu_half.abs_b, negate_b);
Node value = [&]() {
- MetaHalfArithmetic meta{true, {instr.alu_half_imm.type_a, instr.alu_half.type_b}};
switch (opcode->get().GetId()) {
case OpCode::Id::HADD2_C:
case OpCode::Id::HADD2_R:
- return Operation(OperationCode::HAdd, meta, op_a, op_b);
+ return Operation(OperationCode::HAdd, PRECISE, op_a, op_b);
case OpCode::Id::HMUL2_C:
case OpCode::Id::HMUL2_R:
- return Operation(OperationCode::HMul, meta, op_a, op_b);
+ return Operation(OperationCode::HMul, PRECISE, op_a, op_b);
default:
UNIMPLEMENTED_MSG("Unhandled half float instruction: {}", opcode->get().GetName());
return Immediate(0);
diff --git a/src/video_core/shader/decode/arithmetic_half_immediate.cpp b/src/video_core/shader/decode/arithmetic_half_immediate.cpp
index c2164ba50..fbcd35b18 100644
--- a/src/video_core/shader/decode/arithmetic_half_immediate.cpp
+++ b/src/video_core/shader/decode/arithmetic_half_immediate.cpp
@@ -17,34 +17,33 @@ u32 ShaderIR::DecodeArithmeticHalfImmediate(NodeBlock& bb, u32 pc) {
const auto opcode = OpCode::Decode(instr);
if (opcode->get().GetId() == OpCode::Id::HADD2_IMM) {
- UNIMPLEMENTED_IF(instr.alu_half_imm.ftz != 0);
+ if (instr.alu_half_imm.ftz != 0) {
+ LOG_WARNING(HW_GPU, "{} FTZ not implemented", opcode->get().GetName());
+ }
} else {
UNIMPLEMENTED_IF(instr.alu_half_imm.precision != Tegra::Shader::HalfPrecision::None);
}
- UNIMPLEMENTED_IF_MSG(instr.alu_half_imm.saturate != 0,
- "Half float immediate saturation not implemented");
- Node op_a = GetRegister(instr.gpr8);
+ Node op_a = UnpackHalfFloat(GetRegister(instr.gpr8), instr.alu_half_imm.type_a);
op_a = GetOperandAbsNegHalf(op_a, instr.alu_half_imm.abs_a, instr.alu_half_imm.negate_a);
const Node op_b = UnpackHalfImmediate(instr, true);
Node value = [&]() {
- MetaHalfArithmetic meta{true, {instr.alu_half_imm.type_a}};
switch (opcode->get().GetId()) {
case OpCode::Id::HADD2_IMM:
- return Operation(OperationCode::HAdd, meta, op_a, op_b);
+ return Operation(OperationCode::HAdd, PRECISE, op_a, op_b);
case OpCode::Id::HMUL2_IMM:
- return Operation(OperationCode::HMul, meta, op_a, op_b);
+ return Operation(OperationCode::HMul, PRECISE, op_a, op_b);
default:
UNREACHABLE();
return Immediate(0);
}
}();
- value = HalfMerge(GetRegister(instr.gpr0), value, instr.alu_half_imm.merge);
+ value = GetSaturatedHalfFloat(value, instr.alu_half_imm.saturate);
+ value = HalfMerge(GetRegister(instr.gpr0), value, instr.alu_half_imm.merge);
SetRegister(bb, instr.gpr0, value);
-
return pc;
}
diff --git a/src/video_core/shader/decode/conversion.cpp b/src/video_core/shader/decode/conversion.cpp
index 55a6fbbf2..ba15b1115 100644
--- a/src/video_core/shader/decode/conversion.cpp
+++ b/src/video_core/shader/decode/conversion.cpp
@@ -18,13 +18,29 @@ u32 ShaderIR::DecodeConversion(NodeBlock& bb, u32 pc) {
const auto opcode = OpCode::Decode(instr);
switch (opcode->get().GetId()) {
- case OpCode::Id::I2I_R: {
+ case OpCode::Id::I2I_R:
+ case OpCode::Id::I2I_C:
+ case OpCode::Id::I2I_IMM: {
UNIMPLEMENTED_IF(instr.conversion.selector);
+ UNIMPLEMENTED_IF(instr.conversion.dst_size != Register::Size::Word);
+ UNIMPLEMENTED_IF(instr.alu.saturate_d);
const bool input_signed = instr.conversion.is_input_signed;
const bool output_signed = instr.conversion.is_output_signed;
- Node value = GetRegister(instr.gpr20);
+ Node value = [&]() {
+ switch (opcode->get().GetId()) {
+ case OpCode::Id::I2I_R:
+ return GetRegister(instr.gpr20);
+ case OpCode::Id::I2I_C:
+ return GetConstBuffer(instr.cbuf34.index, instr.cbuf34.GetOffset());
+ case OpCode::Id::I2I_IMM:
+ return Immediate(instr.alu.GetSignedImm20_20());
+ default:
+ UNREACHABLE();
+ return Immediate(0);
+ }
+ }();
value = ConvertIntegerSize(value, instr.conversion.src_size, input_signed);
value = GetOperandAbsNegInteger(value, instr.conversion.abs_a, instr.conversion.negate_a,
@@ -38,17 +54,24 @@ u32 ShaderIR::DecodeConversion(NodeBlock& bb, u32 pc) {
break;
}
case OpCode::Id::I2F_R:
- case OpCode::Id::I2F_C: {
- UNIMPLEMENTED_IF(instr.conversion.dest_size != Register::Size::Word);
+ case OpCode::Id::I2F_C:
+ case OpCode::Id::I2F_IMM: {
+ UNIMPLEMENTED_IF(instr.conversion.dst_size != Register::Size::Word);
UNIMPLEMENTED_IF(instr.conversion.selector);
UNIMPLEMENTED_IF_MSG(instr.generates_cc,
"Condition codes generation in I2F is not implemented");
Node value = [&]() {
- if (instr.is_b_gpr) {
+ switch (opcode->get().GetId()) {
+ case OpCode::Id::I2F_R:
return GetRegister(instr.gpr20);
- } else {
+ case OpCode::Id::I2F_C:
return GetConstBuffer(instr.cbuf34.index, instr.cbuf34.GetOffset());
+ case OpCode::Id::I2F_IMM:
+ return Immediate(instr.alu.GetSignedImm20_20());
+ default:
+ UNREACHABLE();
+ return Immediate(0);
}
}();
const bool input_signed = instr.conversion.is_input_signed;
@@ -62,24 +85,31 @@ u32 ShaderIR::DecodeConversion(NodeBlock& bb, u32 pc) {
break;
}
case OpCode::Id::F2F_R:
- case OpCode::Id::F2F_C: {
- UNIMPLEMENTED_IF(instr.conversion.dest_size != Register::Size::Word);
- UNIMPLEMENTED_IF(instr.conversion.src_size != Register::Size::Word);
+ case OpCode::Id::F2F_C:
+ case OpCode::Id::F2F_IMM: {
+ UNIMPLEMENTED_IF(instr.conversion.f2f.dst_size != Register::Size::Word);
+ UNIMPLEMENTED_IF(instr.conversion.f2f.src_size != Register::Size::Word);
UNIMPLEMENTED_IF_MSG(instr.generates_cc,
"Condition codes generation in F2F is not implemented");
Node value = [&]() {
- if (instr.is_b_gpr) {
+ switch (opcode->get().GetId()) {
+ case OpCode::Id::F2F_R:
return GetRegister(instr.gpr20);
- } else {
+ case OpCode::Id::F2F_C:
return GetConstBuffer(instr.cbuf34.index, instr.cbuf34.GetOffset());
+ case OpCode::Id::F2F_IMM:
+ return GetImmediate19(instr);
+ default:
+ UNREACHABLE();
+ return Immediate(0);
}
}();
value = GetOperandAbsNegFloat(value, instr.conversion.abs_a, instr.conversion.negate_a);
value = [&]() {
- switch (instr.conversion.f2f.rounding) {
+ switch (instr.conversion.f2f.GetRoundingMode()) {
case Tegra::Shader::F2fRoundingOp::None:
return value;
case Tegra::Shader::F2fRoundingOp::Round:
@@ -102,15 +132,22 @@ u32 ShaderIR::DecodeConversion(NodeBlock& bb, u32 pc) {
break;
}
case OpCode::Id::F2I_R:
- case OpCode::Id::F2I_C: {
+ case OpCode::Id::F2I_C:
+ case OpCode::Id::F2I_IMM: {
UNIMPLEMENTED_IF(instr.conversion.src_size != Register::Size::Word);
UNIMPLEMENTED_IF_MSG(instr.generates_cc,
"Condition codes generation in F2I is not implemented");
Node value = [&]() {
- if (instr.is_b_gpr) {
+ switch (opcode->get().GetId()) {
+ case OpCode::Id::F2I_R:
return GetRegister(instr.gpr20);
- } else {
+ case OpCode::Id::F2I_C:
return GetConstBuffer(instr.cbuf34.index, instr.cbuf34.GetOffset());
+ case OpCode::Id::F2I_IMM:
+ return GetImmediate19(instr);
+ default:
+ UNREACHABLE();
+ return Immediate(0);
}
}();
@@ -134,7 +171,7 @@ u32 ShaderIR::DecodeConversion(NodeBlock& bb, u32 pc) {
}();
const bool is_signed = instr.conversion.is_output_signed;
value = SignedOperation(OperationCode::ICastFloat, is_signed, PRECISE, value);
- value = ConvertIntegerSize(value, instr.conversion.dest_size, is_signed);
+ value = ConvertIntegerSize(value, instr.conversion.dst_size, is_signed);
SetRegister(bb, instr.gpr0, value);
break;
diff --git a/src/video_core/shader/decode/half_set.cpp b/src/video_core/shader/decode/half_set.cpp
index 748368555..1dd94bf9d 100644
--- a/src/video_core/shader/decode/half_set.cpp
+++ b/src/video_core/shader/decode/half_set.cpp
@@ -18,11 +18,13 @@ u32 ShaderIR::DecodeHalfSet(NodeBlock& bb, u32 pc) {
const Instruction instr = {program_code[pc]};
const auto opcode = OpCode::Decode(instr);
- UNIMPLEMENTED_IF(instr.hset2.ftz != 0);
+ if (instr.hset2.ftz != 0) {
+ LOG_WARNING(HW_GPU, "{} FTZ not implemented", opcode->get().GetName());
+ }
+
+ Node op_a = UnpackHalfFloat(GetRegister(instr.gpr8), instr.hset2.type_a);
+ op_a = GetOperandAbsNegHalf(op_a, instr.hset2.abs_a, instr.hset2.negate_a);
- // instr.hset2.type_a
- // instr.hset2.type_b
- Node op_a = GetRegister(instr.gpr8);
Node op_b = [&]() {
switch (opcode->get().GetId()) {
case OpCode::Id::HSET2_R:
@@ -32,14 +34,12 @@ u32 ShaderIR::DecodeHalfSet(NodeBlock& bb, u32 pc) {
return Immediate(0);
}
}();
-
- op_a = GetOperandAbsNegHalf(op_a, instr.hset2.abs_a, instr.hset2.negate_a);
+ op_b = UnpackHalfFloat(op_b, instr.hset2.type_b);
op_b = GetOperandAbsNegHalf(op_b, instr.hset2.abs_b, instr.hset2.negate_b);
const Node second_pred = GetPredicate(instr.hset2.pred39, instr.hset2.neg_pred);
- MetaHalfArithmetic meta{false, {instr.hset2.type_a, instr.hset2.type_b}};
- const Node comparison_pair = GetPredicateComparisonHalf(instr.hset2.cond, meta, op_a, op_b);
+ const Node comparison_pair = GetPredicateComparisonHalf(instr.hset2.cond, op_a, op_b);
const OperationCode combiner = GetPredicateCombiner(instr.hset2.op);
diff --git a/src/video_core/shader/decode/half_set_predicate.cpp b/src/video_core/shader/decode/half_set_predicate.cpp
index e68512692..6e59eb650 100644
--- a/src/video_core/shader/decode/half_set_predicate.cpp
+++ b/src/video_core/shader/decode/half_set_predicate.cpp
@@ -19,10 +19,10 @@ u32 ShaderIR::DecodeHalfSetPredicate(NodeBlock& bb, u32 pc) {
UNIMPLEMENTED_IF(instr.hsetp2.ftz != 0);
- Node op_a = GetRegister(instr.gpr8);
+ Node op_a = UnpackHalfFloat(GetRegister(instr.gpr8), instr.hsetp2.type_a);
op_a = GetOperandAbsNegHalf(op_a, instr.hsetp2.abs_a, instr.hsetp2.negate_a);
- const Node op_b = [&]() {
+ Node op_b = [&]() {
switch (opcode->get().GetId()) {
case OpCode::Id::HSETP2_R:
return GetOperandAbsNegHalf(GetRegister(instr.gpr20), instr.hsetp2.abs_a,
@@ -32,6 +32,7 @@ u32 ShaderIR::DecodeHalfSetPredicate(NodeBlock& bb, u32 pc) {
return Immediate(0);
}
}();
+ op_b = UnpackHalfFloat(op_b, instr.hsetp2.type_b);
// We can't use the constant predicate as destination.
ASSERT(instr.hsetp2.pred3 != static_cast<u64>(Pred::UnusedIndex));
@@ -42,8 +43,7 @@ u32 ShaderIR::DecodeHalfSetPredicate(NodeBlock& bb, u32 pc) {
const OperationCode pair_combiner =
instr.hsetp2.h_and ? OperationCode::LogicalAll2 : OperationCode::LogicalAny2;
- MetaHalfArithmetic meta = {false, {instr.hsetp2.type_a, instr.hsetp2.type_b}};
- const Node comparison = GetPredicateComparisonHalf(instr.hsetp2.cond, meta, op_a, op_b);
+ const Node comparison = GetPredicateComparisonHalf(instr.hsetp2.cond, op_a, op_b);
const Node first_pred = Operation(pair_combiner, comparison);
// Set the primary predicate to the result of Predicate OP SecondPredicate
diff --git a/src/video_core/shader/decode/hfma2.cpp b/src/video_core/shader/decode/hfma2.cpp
index 7a07c5ec6..5c1becce5 100644
--- a/src/video_core/shader/decode/hfma2.cpp
+++ b/src/video_core/shader/decode/hfma2.cpp
@@ -27,10 +27,6 @@ u32 ShaderIR::DecodeHfma2(NodeBlock& bb, u32 pc) {
}
constexpr auto identity = HalfType::H0_H1;
-
- const HalfType type_a = instr.hfma2.type_a;
- const Node op_a = GetRegister(instr.gpr8);
-
bool neg_b{}, neg_c{};
auto [saturate, type_b, op_b, type_c,
op_c] = [&]() -> std::tuple<bool, HalfType, Node, HalfType, Node> {
@@ -62,11 +58,11 @@ u32 ShaderIR::DecodeHfma2(NodeBlock& bb, u32 pc) {
}();
UNIMPLEMENTED_IF_MSG(saturate, "HFMA2 saturation is not implemented");
- op_b = GetOperandAbsNegHalf(op_b, false, neg_b);
- op_c = GetOperandAbsNegHalf(op_c, false, neg_c);
+ const Node op_a = UnpackHalfFloat(GetRegister(instr.gpr8), instr.hfma2.type_a);
+ op_b = GetOperandAbsNegHalf(UnpackHalfFloat(op_b, type_b), false, neg_b);
+ op_c = GetOperandAbsNegHalf(UnpackHalfFloat(op_c, type_c), false, neg_c);
- MetaHalfArithmetic meta{true, {type_a, type_b, type_c}};
- Node value = Operation(OperationCode::HFma, meta, op_a, op_b, op_c);
+ Node value = Operation(OperationCode::HFma, PRECISE, op_a, op_b, op_c);
value = HalfMerge(GetRegister(instr.gpr0), value, instr.hfma2.merge);
SetRegister(bb, instr.gpr0, value);
diff --git a/src/video_core/shader/decode/memory.cpp b/src/video_core/shader/decode/memory.cpp
index ea3c71eed..ea1092db1 100644
--- a/src/video_core/shader/decode/memory.cpp
+++ b/src/video_core/shader/decode/memory.cpp
@@ -8,6 +8,7 @@
#include "common/assert.h"
#include "common/common_types.h"
+#include "common/logging/log.h"
#include "video_core/engines/shader_bytecode.h"
#include "video_core/shader/shader_ir.h"
@@ -18,6 +19,23 @@ using Tegra::Shader::Instruction;
using Tegra::Shader::OpCode;
using Tegra::Shader::Register;
+namespace {
+u32 GetUniformTypeElementsCount(Tegra::Shader::UniformType uniform_type) {
+ switch (uniform_type) {
+ 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;
+ }
+}
+} // namespace
+
u32 ShaderIR::DecodeMemory(NodeBlock& bb, u32 pc) {
const Instruction instr = {program_code[pc]};
const auto opcode = OpCode::Decode(instr);
@@ -85,8 +103,8 @@ u32 ShaderIR::DecodeMemory(NodeBlock& bb, u32 pc) {
break;
}
case OpCode::Id::LD_L: {
- UNIMPLEMENTED_IF_MSG(instr.ld_l.unknown == 1, "LD_L Unhandled mode: {}",
- static_cast<u32>(instr.ld_l.unknown.Value()));
+ LOG_DEBUG(HW_GPU, "LD_L cache management mode: {}",
+ static_cast<u64>(instr.ld_l.unknown.Value()));
const auto GetLmem = [&](s32 offset) {
ASSERT(offset % 4 == 0);
@@ -126,45 +144,15 @@ u32 ShaderIR::DecodeMemory(NodeBlock& bb, u32 pc) {
break;
}
case OpCode::Id::LDG: {
- const u32 count = [&]() {
- switch (instr.ldg.type) {
- 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 LDG size!");
- return 1;
- }
- }();
-
- const Node addr_register = GetRegister(instr.gpr8);
- const Node base_address =
- TrackCbuf(addr_register, global_code, static_cast<s64>(global_code.size()));
- const auto cbuf = std::get_if<CbufNode>(base_address);
- ASSERT(cbuf != nullptr);
- const auto cbuf_offset_imm = std::get_if<ImmediateNode>(cbuf->GetOffset());
- ASSERT(cbuf_offset_imm != nullptr);
- const auto cbuf_offset = cbuf_offset_imm->GetValue();
-
- bb.push_back(Comment(
- fmt::format("Base address is c[0x{:x}][0x{:x}]", cbuf->GetIndex(), cbuf_offset)));
-
- const GlobalMemoryBase descriptor{cbuf->GetIndex(), cbuf_offset};
- used_global_memory_bases.insert(descriptor);
-
- const Node immediate_offset =
- Immediate(static_cast<u32>(instr.ldg.immediate_offset.Value()));
- const Node base_real_address =
- Operation(OperationCode::UAdd, NO_PRECISE, immediate_offset, addr_register);
+ const auto [real_address_base, base_address, descriptor] =
+ TrackAndGetGlobalMemory(bb, GetRegister(instr.gpr8),
+ static_cast<u32>(instr.ldg.immediate_offset.Value()), false);
+ const u32 count = GetUniformTypeElementsCount(instr.ldg.type);
for (u32 i = 0; i < count; ++i) {
const Node it_offset = Immediate(i * 4);
const Node real_address =
- Operation(OperationCode::UAdd, NO_PRECISE, base_real_address, it_offset);
+ Operation(OperationCode::UAdd, NO_PRECISE, real_address_base, it_offset);
const Node gmem = StoreNode(GmemNode(real_address, base_address, descriptor));
SetTemporal(bb, i, gmem);
@@ -174,6 +162,28 @@ u32 ShaderIR::DecodeMemory(NodeBlock& bb, u32 pc) {
}
break;
}
+ case OpCode::Id::STG: {
+ const auto [real_address_base, base_address, descriptor] =
+ TrackAndGetGlobalMemory(bb, GetRegister(instr.gpr8),
+ static_cast<u32>(instr.stg.immediate_offset.Value()), true);
+
+ // Encode in temporary registers like this: real_base_address, {registers_to_be_written...}
+ SetTemporal(bb, 0, real_address_base);
+
+ const u32 count = GetUniformTypeElementsCount(instr.stg.type);
+ for (u32 i = 0; i < count; ++i) {
+ SetTemporal(bb, i + 1, GetRegister(instr.gpr0.Value() + i));
+ }
+ 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 = StoreNode(GmemNode(real_address, base_address, descriptor));
+
+ bb.push_back(Operation(OperationCode::Assign, gmem, GetTemporal(i + 1)));
+ }
+ break;
+ }
case OpCode::Id::ST_A: {
UNIMPLEMENTED_IF_MSG(instr.gpr8.Value() != Register::ZeroIndex,
"Indirect attribute loads are not supported");
@@ -205,8 +215,8 @@ u32 ShaderIR::DecodeMemory(NodeBlock& bb, u32 pc) {
break;
}
case OpCode::Id::ST_L: {
- UNIMPLEMENTED_IF_MSG(instr.st_l.unknown == 0, "ST_L Unhandled mode: {}",
- static_cast<u32>(instr.st_l.unknown.Value()));
+ LOG_DEBUG(HW_GPU, "ST_L cache management mode: {}",
+ static_cast<u64>(instr.st_l.cache_management.Value()));
const auto GetLmemAddr = [&](s32 offset) {
ASSERT(offset % 4 == 0);
@@ -236,4 +246,34 @@ u32 ShaderIR::DecodeMemory(NodeBlock& bb, u32 pc) {
return pc;
}
+std::tuple<Node, Node, GlobalMemoryBase> ShaderIR::TrackAndGetGlobalMemory(NodeBlock& bb,
+ Node addr_register,
+ u32 immediate_offset,
+ bool is_write) {
+ const Node base_address{
+ TrackCbuf(addr_register, global_code, static_cast<s64>(global_code.size()))};
+ const auto cbuf = std::get_if<CbufNode>(base_address);
+ ASSERT(cbuf != nullptr);
+ const auto cbuf_offset_imm = std::get_if<ImmediateNode>(cbuf->GetOffset());
+ ASSERT(cbuf_offset_imm != nullptr);
+ const auto cbuf_offset = cbuf_offset_imm->GetValue();
+
+ bb.push_back(
+ Comment(fmt::format("Base address is c[0x{:x}][0x{:x}]", cbuf->GetIndex(), cbuf_offset)));
+
+ const GlobalMemoryBase descriptor{cbuf->GetIndex(), cbuf_offset};
+ const auto& [entry, is_new] = used_global_memory.try_emplace(descriptor);
+ auto& usage = entry->second;
+ if (is_write) {
+ usage.is_written = true;
+ } else {
+ usage.is_read = true;
+ }
+
+ const auto real_address =
+ Operation(OperationCode::UAdd, NO_PRECISE, Immediate(immediate_offset), addr_register);
+
+ return {real_address, base_address, descriptor};
+}
+
} // namespace VideoCommon::Shader
diff --git a/src/video_core/shader/decode/texture.cpp b/src/video_core/shader/decode/texture.cpp
index a775b402b..fa65ac9a9 100644
--- a/src/video_core/shader/decode/texture.cpp
+++ b/src/video_core/shader/decode/texture.cpp
@@ -40,7 +40,7 @@ static std::size_t GetCoordCount(TextureType texture_type) {
u32 ShaderIR::DecodeTexture(NodeBlock& bb, u32 pc) {
const Instruction instr = {program_code[pc]};
const auto opcode = OpCode::Decode(instr);
-
+ bool is_bindless = false;
switch (opcode->get().GetId()) {
case OpCode::Id::TEX: {
if (instr.tex.UsesMiscMode(TextureMiscMode::NODEP)) {
@@ -54,7 +54,25 @@ u32 ShaderIR::DecodeTexture(NodeBlock& bb, u32 pc) {
const auto process_mode = instr.tex.GetTextureProcessMode();
WriteTexInstructionFloat(
bb, instr,
- GetTexCode(instr, texture_type, process_mode, depth_compare, is_array, is_aoffi));
+ GetTexCode(instr, texture_type, process_mode, depth_compare, is_array, is_aoffi, {}));
+ break;
+ }
+ case OpCode::Id::TEX_B: {
+ UNIMPLEMENTED_IF_MSG(instr.tex.UsesMiscMode(TextureMiscMode::AOFFI),
+ "AOFFI is not implemented");
+
+ if (instr.tex.UsesMiscMode(TextureMiscMode::NODEP)) {
+ LOG_WARNING(HW_GPU, "TEX.NODEP implementation is incomplete");
+ }
+
+ const TextureType texture_type{instr.tex_b.texture_type};
+ const bool is_array = instr.tex_b.array != 0;
+ const bool is_aoffi = instr.tex.UsesMiscMode(TextureMiscMode::AOFFI);
+ const bool depth_compare = instr.tex_b.UsesMiscMode(TextureMiscMode::DC);
+ const auto process_mode = instr.tex_b.GetTextureProcessMode();
+ WriteTexInstructionFloat(bb, instr,
+ GetTexCode(instr, texture_type, process_mode, depth_compare,
+ is_array, is_aoffi, {instr.gpr20}));
break;
}
case OpCode::Id::TEXS: {
@@ -134,6 +152,9 @@ u32 ShaderIR::DecodeTexture(NodeBlock& bb, u32 pc) {
WriteTexsInstructionFloat(bb, instr, values);
break;
}
+ case OpCode::Id::TXQ_B:
+ is_bindless = true;
+ [[fallthrough]];
case OpCode::Id::TXQ: {
if (instr.txq.UsesMiscMode(TextureMiscMode::NODEP)) {
LOG_WARNING(HW_GPU, "TXQ.NODEP implementation is incomplete");
@@ -143,7 +164,10 @@ u32 ShaderIR::DecodeTexture(NodeBlock& bb, u32 pc) {
// Sadly, not all texture instructions specify the type of texture their sampler
// uses. This must be fixed at a later instance.
const auto& sampler =
- GetSampler(instr.sampler, Tegra::Shader::TextureType::Texture2D, false, false);
+ is_bindless
+ ? GetBindlessSampler(instr.gpr8, Tegra::Shader::TextureType::Texture2D, false,
+ false)
+ : GetSampler(instr.sampler, Tegra::Shader::TextureType::Texture2D, false, false);
u32 indexer = 0;
switch (instr.txq.query_type) {
@@ -154,7 +178,8 @@ u32 ShaderIR::DecodeTexture(NodeBlock& bb, u32 pc) {
}
MetaTexture meta{sampler, {}, {}, {}, {}, {}, {}, element};
const Node value =
- Operation(OperationCode::TextureQueryDimensions, meta, GetRegister(instr.gpr8));
+ Operation(OperationCode::TextureQueryDimensions, meta,
+ GetRegister(instr.gpr8.Value() + (is_bindless ? 1 : 0)));
SetTemporal(bb, indexer++, value);
}
for (u32 i = 0; i < indexer; ++i) {
@@ -168,6 +193,9 @@ u32 ShaderIR::DecodeTexture(NodeBlock& bb, u32 pc) {
}
break;
}
+ case OpCode::Id::TMML_B:
+ is_bindless = true;
+ [[fallthrough]];
case OpCode::Id::TMML: {
UNIMPLEMENTED_IF_MSG(instr.tmml.UsesMiscMode(Tegra::Shader::TextureMiscMode::NDV),
"NDV is not implemented");
@@ -178,7 +206,9 @@ u32 ShaderIR::DecodeTexture(NodeBlock& bb, u32 pc) {
auto texture_type = instr.tmml.texture_type.Value();
const bool is_array = instr.tmml.array != 0;
- const auto& sampler = GetSampler(instr.sampler, texture_type, is_array, false);
+ const auto& sampler = is_bindless
+ ? GetBindlessSampler(instr.gpr20, texture_type, is_array, false)
+ : GetSampler(instr.sampler, texture_type, is_array, false);
std::vector<Node> coords;
@@ -199,17 +229,19 @@ u32 ShaderIR::DecodeTexture(NodeBlock& bb, u32 pc) {
coords.push_back(GetRegister(instr.gpr8.Value() + 1));
texture_type = TextureType::Texture2D;
}
-
+ u32 indexer = 0;
for (u32 element = 0; element < 2; ++element) {
+ if (!instr.tmml.IsComponentEnabled(element)) {
+ continue;
+ }
auto params = coords;
MetaTexture meta{sampler, {}, {}, {}, {}, {}, {}, element};
const Node value = Operation(OperationCode::TextureQueryLod, meta, std::move(params));
- SetTemporal(bb, element, value);
+ SetTemporal(bb, indexer++, value);
}
- for (u32 element = 0; element < 2; ++element) {
- SetRegister(bb, instr.gpr0.Value() + element, GetTemporal(element));
+ for (u32 i = 0; i < indexer; ++i) {
+ SetRegister(bb, instr.gpr0.Value() + i, GetTemporal(i));
}
-
break;
}
case OpCode::Id::TLDS: {
@@ -254,6 +286,34 @@ const Sampler& ShaderIR::GetSampler(const Tegra::Shader::Sampler& sampler, Textu
return *used_samplers.emplace(entry).first;
}
+const Sampler& ShaderIR::GetBindlessSampler(const Tegra::Shader::Register& reg, TextureType type,
+ bool is_array, bool is_shadow) {
+ const Node sampler_register = GetRegister(reg);
+ const Node base_sampler =
+ TrackCbuf(sampler_register, global_code, static_cast<s64>(global_code.size()));
+ const auto cbuf = std::get_if<CbufNode>(base_sampler);
+ const auto cbuf_offset_imm = std::get_if<ImmediateNode>(cbuf->GetOffset());
+ ASSERT(cbuf_offset_imm != nullptr);
+ const auto cbuf_offset = cbuf_offset_imm->GetValue();
+ const auto cbuf_index = cbuf->GetIndex();
+ const u64 cbuf_key = (cbuf_index << 32) | cbuf_offset;
+
+ // If this sampler has already been used, return the existing mapping.
+ const auto itr =
+ std::find_if(used_samplers.begin(), used_samplers.end(),
+ [&](const Sampler& entry) { return entry.GetOffset() == cbuf_key; });
+ if (itr != used_samplers.end()) {
+ ASSERT(itr->GetType() == type && itr->IsArray() == is_array &&
+ itr->IsShadow() == is_shadow);
+ return *itr;
+ }
+
+ // Otherwise create a new mapping for this sampler
+ const std::size_t next_index = used_samplers.size();
+ const Sampler entry{cbuf_index, cbuf_offset, next_index, type, is_array, is_shadow};
+ return *used_samplers.emplace(entry).first;
+}
+
void ShaderIR::WriteTexInstructionFloat(NodeBlock& bb, Instruction instr, const Node4& components) {
u32 dest_elem = 0;
for (u32 elem = 0; elem < 4; ++elem) {
@@ -326,22 +386,27 @@ void ShaderIR::WriteTexsInstructionHalfFloat(NodeBlock& bb, Instruction instr,
Node4 ShaderIR::GetTextureCode(Instruction instr, TextureType texture_type,
TextureProcessMode process_mode, std::vector<Node> coords,
Node array, Node depth_compare, u32 bias_offset,
- std::vector<Node> aoffi) {
+ std::vector<Node> aoffi,
+ std::optional<Tegra::Shader::Register> bindless_reg) {
const bool is_array = array;
const bool is_shadow = depth_compare;
+ const bool is_bindless = bindless_reg.has_value();
UNIMPLEMENTED_IF_MSG((texture_type == TextureType::Texture3D && (is_array || is_shadow)) ||
(texture_type == TextureType::TextureCube && is_array && is_shadow),
"This method is not supported.");
- const auto& sampler = GetSampler(instr.sampler, texture_type, is_array, is_shadow);
+ const auto& sampler = is_bindless
+ ? GetBindlessSampler(*bindless_reg, texture_type, is_array, is_shadow)
+ : GetSampler(instr.sampler, texture_type, is_array, is_shadow);
const bool lod_needed = process_mode == TextureProcessMode::LZ ||
process_mode == TextureProcessMode::LL ||
process_mode == TextureProcessMode::LLA;
- // LOD selection (either via bias or explicit textureLod) not supported in GL for
- // sampler2DArrayShadow and samplerCubeArrayShadow.
+ // LOD selection (either via bias or explicit textureLod) not
+ // supported in GL for sampler2DArrayShadow and
+ // samplerCubeArrayShadow.
const bool gl_lod_supported =
!((texture_type == Tegra::Shader::TextureType::Texture2D && is_array && is_shadow) ||
(texture_type == Tegra::Shader::TextureType::TextureCube && is_array && is_shadow));
@@ -359,8 +424,9 @@ Node4 ShaderIR::GetTextureCode(Instruction instr, TextureType texture_type,
lod = Immediate(0.0f);
break;
case TextureProcessMode::LB:
- // If present, lod or bias are always stored in the register indexed by the gpr20
- // field with an offset depending on the usage of the other registers
+ // If present, lod or bias are always stored in the register
+ // indexed by the gpr20 field with an offset depending on the
+ // usage of the other registers
bias = GetRegister(instr.gpr20.Value() + bias_offset);
break;
case TextureProcessMode::LL:
@@ -384,11 +450,18 @@ Node4 ShaderIR::GetTextureCode(Instruction instr, TextureType texture_type,
Node4 ShaderIR::GetTexCode(Instruction instr, TextureType texture_type,
TextureProcessMode process_mode, bool depth_compare, bool is_array,
- bool is_aoffi) {
+ bool is_aoffi, std::optional<Tegra::Shader::Register> bindless_reg) {
const bool lod_bias_enabled{
(process_mode != TextureProcessMode::None && process_mode != TextureProcessMode::LZ)};
+ const bool is_bindless = bindless_reg.has_value();
+
u64 parameter_register = instr.gpr20.Value();
+ if (is_bindless) {
+ ++parameter_register;
+ }
+
+ const u32 bias_lod_offset = (is_bindless ? 1 : 0);
if (lod_bias_enabled) {
++parameter_register;
}
@@ -423,7 +496,8 @@ Node4 ShaderIR::GetTexCode(Instruction instr, TextureType texture_type,
dc = GetRegister(parameter_register++);
}
- return GetTextureCode(instr, texture_type, process_mode, coords, array, dc, 0, aoffi);
+ return GetTextureCode(instr, texture_type, process_mode, coords, array, dc, bias_lod_offset,
+ aoffi, bindless_reg);
}
Node4 ShaderIR::GetTexsCode(Instruction instr, TextureType texture_type,
@@ -459,7 +533,8 @@ Node4 ShaderIR::GetTexsCode(Instruction instr, TextureType texture_type,
dc = GetRegister(depth_register);
}
- return GetTextureCode(instr, texture_type, process_mode, coords, array, dc, bias_offset, {});
+ return GetTextureCode(instr, texture_type, process_mode, coords, array, dc, bias_offset, {},
+ {});
}
Node4 ShaderIR::GetTld4Code(Instruction instr, TextureType texture_type, bool depth_compare,
diff --git a/src/video_core/shader/shader_ir.cpp b/src/video_core/shader/shader_ir.cpp
index ac5112d78..17f2f711c 100644
--- a/src/video_core/shader/shader_ir.cpp
+++ b/src/video_core/shader/shader_ir.cpp
@@ -189,7 +189,11 @@ Node ShaderIR::UnpackHalfImmediate(Instruction instr, bool has_negation) {
const Node first_negate = GetPredicate(instr.half_imm.first_negate != 0);
const Node second_negate = GetPredicate(instr.half_imm.second_negate != 0);
- return Operation(OperationCode::HNegate, HALF_NO_PRECISE, value, first_negate, second_negate);
+ return Operation(OperationCode::HNegate, NO_PRECISE, value, first_negate, second_negate);
+}
+
+Node ShaderIR::UnpackHalfFloat(Node value, Tegra::Shader::HalfType type) {
+ return Operation(OperationCode::HUnpack, type, value);
}
Node ShaderIR::HalfMerge(Node dest, Node src, Tegra::Shader::HalfMerge merge) {
@@ -209,17 +213,26 @@ Node ShaderIR::HalfMerge(Node dest, Node src, Tegra::Shader::HalfMerge merge) {
Node ShaderIR::GetOperandAbsNegHalf(Node value, bool absolute, bool negate) {
if (absolute) {
- value = Operation(OperationCode::HAbsolute, HALF_NO_PRECISE, value);
+ value = Operation(OperationCode::HAbsolute, NO_PRECISE, value);
}
if (negate) {
- value = Operation(OperationCode::HNegate, HALF_NO_PRECISE, value, GetPredicate(true),
+ value = Operation(OperationCode::HNegate, NO_PRECISE, value, GetPredicate(true),
GetPredicate(true));
}
return value;
}
+Node ShaderIR::GetSaturatedHalfFloat(Node value, bool saturate) {
+ if (!saturate) {
+ return value;
+ }
+ const Node positive_zero = Immediate(std::copysignf(0, 1));
+ const Node positive_one = Immediate(1.0f);
+ return Operation(OperationCode::HClamp, NO_PRECISE, value, positive_zero, positive_one);
+}
+
Node ShaderIR::GetPredicateComparisonFloat(PredCondition condition, Node op_a, Node op_b) {
- static const std::unordered_map<PredCondition, OperationCode> PredicateComparisonTable = {
+ const std::unordered_map<PredCondition, OperationCode> PredicateComparisonTable = {
{PredCondition::LessThan, OperationCode::LogicalFLessThan},
{PredCondition::Equal, OperationCode::LogicalFEqual},
{PredCondition::LessEqual, OperationCode::LogicalFLessEqual},
@@ -255,7 +268,7 @@ Node ShaderIR::GetPredicateComparisonFloat(PredCondition condition, Node op_a, N
Node ShaderIR::GetPredicateComparisonInteger(PredCondition condition, bool is_signed, Node op_a,
Node op_b) {
- static const std::unordered_map<PredCondition, OperationCode> PredicateComparisonTable = {
+ const std::unordered_map<PredCondition, OperationCode> PredicateComparisonTable = {
{PredCondition::LessThan, OperationCode::LogicalILessThan},
{PredCondition::Equal, OperationCode::LogicalIEqual},
{PredCondition::LessEqual, OperationCode::LogicalILessEqual},
@@ -283,40 +296,32 @@ Node ShaderIR::GetPredicateComparisonInteger(PredCondition condition, bool is_si
return predicate;
}
-Node ShaderIR::GetPredicateComparisonHalf(Tegra::Shader::PredCondition condition,
- const MetaHalfArithmetic& meta, Node op_a, Node op_b) {
-
- UNIMPLEMENTED_IF_MSG(condition == PredCondition::LessThanWithNan ||
- condition == PredCondition::NotEqualWithNan ||
- condition == PredCondition::LessEqualWithNan ||
- condition == PredCondition::GreaterThanWithNan ||
- condition == PredCondition::GreaterEqualWithNan,
- "Unimplemented NaN comparison for half floats");
-
- static const std::unordered_map<PredCondition, OperationCode> PredicateComparisonTable = {
+Node ShaderIR::GetPredicateComparisonHalf(Tegra::Shader::PredCondition condition, Node op_a,
+ Node op_b) {
+ const std::unordered_map<PredCondition, OperationCode> PredicateComparisonTable = {
{PredCondition::LessThan, OperationCode::Logical2HLessThan},
{PredCondition::Equal, OperationCode::Logical2HEqual},
{PredCondition::LessEqual, OperationCode::Logical2HLessEqual},
{PredCondition::GreaterThan, OperationCode::Logical2HGreaterThan},
{PredCondition::NotEqual, OperationCode::Logical2HNotEqual},
{PredCondition::GreaterEqual, OperationCode::Logical2HGreaterEqual},
- {PredCondition::LessThanWithNan, OperationCode::Logical2HLessThan},
- {PredCondition::NotEqualWithNan, OperationCode::Logical2HNotEqual},
- {PredCondition::LessEqualWithNan, OperationCode::Logical2HLessEqual},
- {PredCondition::GreaterThanWithNan, OperationCode::Logical2HGreaterThan},
- {PredCondition::GreaterEqualWithNan, OperationCode::Logical2HGreaterEqual}};
+ {PredCondition::LessThanWithNan, OperationCode::Logical2HLessThanWithNan},
+ {PredCondition::NotEqualWithNan, OperationCode::Logical2HNotEqualWithNan},
+ {PredCondition::LessEqualWithNan, OperationCode::Logical2HLessEqualWithNan},
+ {PredCondition::GreaterThanWithNan, OperationCode::Logical2HGreaterThanWithNan},
+ {PredCondition::GreaterEqualWithNan, OperationCode::Logical2HGreaterEqualWithNan}};
const auto comparison{PredicateComparisonTable.find(condition)};
UNIMPLEMENTED_IF_MSG(comparison == PredicateComparisonTable.end(),
"Unknown predicate comparison operation");
- const Node predicate = Operation(comparison->second, meta, op_a, op_b);
+ const Node predicate = Operation(comparison->second, NO_PRECISE, op_a, op_b);
return predicate;
}
OperationCode ShaderIR::GetPredicateCombiner(PredOperation operation) {
- static const std::unordered_map<PredOperation, OperationCode> PredicateOperationTable = {
+ const std::unordered_map<PredOperation, OperationCode> PredicateOperationTable = {
{PredOperation::And, OperationCode::LogicalAnd},
{PredOperation::Or, OperationCode::LogicalOr},
{PredOperation::Xor, OperationCode::LogicalXor},
diff --git a/src/video_core/shader/shader_ir.h b/src/video_core/shader/shader_ir.h
index 4888998d3..81278fb33 100644
--- a/src/video_core/shader/shader_ir.h
+++ b/src/video_core/shader/shader_ir.h
@@ -109,11 +109,13 @@ enum class OperationCode {
UBitfieldExtract, /// (MetaArithmetic, uint value, int offset, int offset) -> uint
UBitCount, /// (MetaArithmetic, uint) -> uint
- HAdd, /// (MetaHalfArithmetic, f16vec2 a, f16vec2 b) -> f16vec2
- HMul, /// (MetaHalfArithmetic, f16vec2 a, f16vec2 b) -> f16vec2
- HFma, /// (MetaHalfArithmetic, f16vec2 a, f16vec2 b, f16vec2 c) -> f16vec2
+ HAdd, /// (MetaArithmetic, f16vec2 a, f16vec2 b) -> f16vec2
+ HMul, /// (MetaArithmetic, f16vec2 a, f16vec2 b) -> f16vec2
+ HFma, /// (MetaArithmetic, f16vec2 a, f16vec2 b, f16vec2 c) -> f16vec2
HAbsolute, /// (f16vec2 a) -> f16vec2
HNegate, /// (f16vec2 a, bool first, bool second) -> f16vec2
+ HClamp, /// (f16vec2 src, float min, float max) -> f16vec2
+ HUnpack, /// (Tegra::Shader::HalfType, T value) -> f16vec2
HMergeF32, /// (f16vec2 src) -> float
HMergeH0, /// (f16vec2 dest, f16vec2 src) -> f16vec2
HMergeH1, /// (f16vec2 dest, f16vec2 src) -> f16vec2
@@ -150,12 +152,18 @@ enum class OperationCode {
LogicalUNotEqual, /// (uint a, uint b) -> bool
LogicalUGreaterEqual, /// (uint a, uint b) -> bool
- Logical2HLessThan, /// (MetaHalfArithmetic, f16vec2 a, f16vec2) -> bool2
- Logical2HEqual, /// (MetaHalfArithmetic, f16vec2 a, f16vec2) -> bool2
- Logical2HLessEqual, /// (MetaHalfArithmetic, f16vec2 a, f16vec2) -> bool2
- Logical2HGreaterThan, /// (MetaHalfArithmetic, f16vec2 a, f16vec2) -> bool2
- Logical2HNotEqual, /// (MetaHalfArithmetic, f16vec2 a, f16vec2) -> bool2
- Logical2HGreaterEqual, /// (MetaHalfArithmetic, f16vec2 a, f16vec2) -> bool2
+ Logical2HLessThan, /// (MetaHalfArithmetic, f16vec2 a, f16vec2) -> bool2
+ Logical2HEqual, /// (MetaHalfArithmetic, f16vec2 a, f16vec2) -> bool2
+ Logical2HLessEqual, /// (MetaHalfArithmetic, f16vec2 a, f16vec2) -> bool2
+ Logical2HGreaterThan, /// (MetaHalfArithmetic, f16vec2 a, f16vec2) -> bool2
+ Logical2HNotEqual, /// (MetaHalfArithmetic, f16vec2 a, f16vec2) -> bool2
+ Logical2HGreaterEqual, /// (MetaHalfArithmetic, f16vec2 a, f16vec2) -> bool2
+ Logical2HLessThanWithNan, /// (MetaHalfArithmetic, f16vec2 a, f16vec2) -> bool2
+ Logical2HEqualWithNan, /// (MetaHalfArithmetic, f16vec2 a, f16vec2) -> bool2
+ Logical2HLessEqualWithNan, /// (MetaHalfArithmetic, f16vec2 a, f16vec2) -> bool2
+ Logical2HGreaterThanWithNan, /// (MetaHalfArithmetic, f16vec2 a, f16vec2) -> bool2
+ Logical2HNotEqualWithNan, /// (MetaHalfArithmetic, f16vec2 a, f16vec2) -> bool2
+ Logical2HGreaterEqualWithNan, /// (MetaHalfArithmetic, f16vec2 a, f16vec2) -> bool2
Texture, /// (MetaTexture, float[N] coords) -> float4
TextureLod, /// (MetaTexture, float[N] coords) -> float4
@@ -196,9 +204,23 @@ enum class ExitMethod {
class Sampler {
public:
+ // Use this constructor for bounded Samplers
explicit Sampler(std::size_t offset, std::size_t index, Tegra::Shader::TextureType type,
bool is_array, bool is_shadow)
- : offset{offset}, index{index}, type{type}, is_array{is_array}, is_shadow{is_shadow} {}
+ : offset{offset}, index{index}, type{type}, is_array{is_array}, is_shadow{is_shadow},
+ is_bindless{false} {}
+
+ // Use this constructor for bindless Samplers
+ explicit Sampler(u32 cbuf_index, u32 cbuf_offset, std::size_t index,
+ Tegra::Shader::TextureType type, bool is_array, bool is_shadow)
+ : offset{(static_cast<u64>(cbuf_index) << 32) | cbuf_offset}, index{index}, type{type},
+ is_array{is_array}, is_shadow{is_shadow}, is_bindless{true} {}
+
+ // Use this only for serialization/deserialization
+ explicit Sampler(std::size_t offset, std::size_t index, Tegra::Shader::TextureType type,
+ bool is_array, bool is_shadow, bool is_bindless)
+ : offset{offset}, index{index}, type{type}, is_array{is_array}, is_shadow{is_shadow},
+ is_bindless{is_bindless} {}
std::size_t GetOffset() const {
return offset;
@@ -220,6 +242,14 @@ public:
return is_shadow;
}
+ bool IsBindless() const {
+ return is_bindless;
+ }
+
+ std::pair<u32, u32> GetBindlessCBuf() const {
+ return {static_cast<u32>(offset >> 32), static_cast<u32>(offset)};
+ }
+
bool operator<(const Sampler& rhs) const {
return std::tie(offset, index, type, is_array, is_shadow) <
std::tie(rhs.offset, rhs.index, rhs.type, rhs.is_array, rhs.is_shadow);
@@ -231,8 +261,9 @@ private:
std::size_t offset{};
std::size_t index{}; ///< Value used to index into the generated GLSL sampler array.
Tegra::Shader::TextureType type{}; ///< The type used to sample this texture (Texture2D, etc)
- bool is_array{}; ///< Whether the texture is being sampled as an array texture or not.
- bool is_shadow{}; ///< Whether the texture is being sampled as a depth texture or not.
+ bool is_array{}; ///< Whether the texture is being sampled as an array texture or not.
+ bool is_shadow{}; ///< Whether the texture is being sampled as a depth texture or not.
+ bool is_bindless{}; ///< Whether this sampler belongs to a bindless texture or not.
};
class ConstBuffer {
@@ -276,15 +307,13 @@ struct GlobalMemoryBase {
}
};
-struct MetaArithmetic {
- bool precise{};
+struct GlobalMemoryUsage {
+ bool is_read{};
+ bool is_written{};
};
-struct MetaHalfArithmetic {
+struct MetaArithmetic {
bool precise{};
- std::array<Tegra::Shader::HalfType, 3> types = {Tegra::Shader::HalfType::H0_H1,
- Tegra::Shader::HalfType::H0_H1,
- Tegra::Shader::HalfType::H0_H1};
};
struct MetaTexture {
@@ -298,11 +327,10 @@ struct MetaTexture {
u32 element{};
};
-constexpr MetaArithmetic PRECISE = {true};
-constexpr MetaArithmetic NO_PRECISE = {false};
-constexpr MetaHalfArithmetic HALF_NO_PRECISE = {false};
+inline constexpr MetaArithmetic PRECISE = {true};
+inline constexpr MetaArithmetic NO_PRECISE = {false};
-using Meta = std::variant<MetaArithmetic, MetaHalfArithmetic, MetaTexture>;
+using Meta = std::variant<MetaArithmetic, MetaTexture, Tegra::Shader::HalfType>;
/// Holds any kind of operation that can be done in the IR
class OperationNode final {
@@ -578,8 +606,8 @@ public:
return used_clip_distances;
}
- const std::set<GlobalMemoryBase>& GetGlobalMemoryBases() const {
- return used_global_memory_bases;
+ const std::map<GlobalMemoryBase, GlobalMemoryUsage>& GetGlobalMemory() const {
+ return used_global_memory;
}
std::size_t GetLength() const {
@@ -706,10 +734,14 @@ private:
/// Unpacks a half immediate from an instruction
Node UnpackHalfImmediate(Tegra::Shader::Instruction instr, bool has_negation);
+ /// Unpacks a binary value into a half float pair with a type format
+ Node UnpackHalfFloat(Node value, Tegra::Shader::HalfType type);
/// Merges a half pair into another value
Node HalfMerge(Node dest, Node src, Tegra::Shader::HalfMerge merge);
/// Conditionally absolute/negated half float pair. Absolute is applied first
Node GetOperandAbsNegHalf(Node value, bool absolute, bool negate);
+ /// Conditionally saturates a half float pair
+ Node GetSaturatedHalfFloat(Node value, bool saturate = true);
/// Returns a predicate comparing two floats
Node GetPredicateComparisonFloat(Tegra::Shader::PredCondition condition, Node op_a, Node op_b);
@@ -717,8 +749,7 @@ private:
Node GetPredicateComparisonInteger(Tegra::Shader::PredCondition condition, bool is_signed,
Node op_a, Node op_b);
/// Returns a predicate comparing two half floats. meta consumes how both pairs will be compared
- Node GetPredicateComparisonHalf(Tegra::Shader::PredCondition condition,
- const MetaHalfArithmetic& meta, Node op_a, Node op_b);
+ Node GetPredicateComparisonHalf(Tegra::Shader::PredCondition condition, Node op_a, Node op_b);
/// Returns a predicate combiner operation
OperationCode GetPredicateCombiner(Tegra::Shader::PredOperation operation);
@@ -730,6 +761,11 @@ private:
const Sampler& GetSampler(const Tegra::Shader::Sampler& sampler,
Tegra::Shader::TextureType type, bool is_array, bool is_shadow);
+ // Accesses a texture sampler for a bindless texture.
+ const Sampler& GetBindlessSampler(const Tegra::Shader::Register& reg,
+ Tegra::Shader::TextureType type, bool is_array,
+ bool is_shadow);
+
/// Extracts a sequence of bits from a node
Node BitfieldExtract(Node value, u32 offset, u32 bits);
@@ -743,7 +779,8 @@ private:
Node4 GetTexCode(Tegra::Shader::Instruction instr, Tegra::Shader::TextureType texture_type,
Tegra::Shader::TextureProcessMode process_mode, bool depth_compare,
- bool is_array, bool is_aoffi);
+ bool is_array, bool is_aoffi,
+ std::optional<Tegra::Shader::Register> bindless_reg);
Node4 GetTexsCode(Tegra::Shader::Instruction instr, Tegra::Shader::TextureType texture_type,
Tegra::Shader::TextureProcessMode process_mode, bool depth_compare,
@@ -763,7 +800,8 @@ private:
Node4 GetTextureCode(Tegra::Shader::Instruction instr, Tegra::Shader::TextureType texture_type,
Tegra::Shader::TextureProcessMode process_mode, std::vector<Node> coords,
- Node array, Node depth_compare, u32 bias_offset, std::vector<Node> aoffi);
+ Node array, Node depth_compare, u32 bias_offset, std::vector<Node> aoffi,
+ std::optional<Tegra::Shader::Register> bindless_reg);
Node GetVideoOperand(Node op, bool is_chunk, bool is_signed, Tegra::Shader::VideoType type,
u64 byte_height);
@@ -781,6 +819,11 @@ private:
std::pair<Node, s64> TrackRegister(const GprNode* tracked, const NodeBlock& code, s64 cursor);
+ std::tuple<Node, Node, GlobalMemoryBase> TrackAndGetGlobalMemory(NodeBlock& bb,
+ Node addr_register,
+ u32 immediate_offset,
+ bool is_write);
+
template <typename... T>
Node Operation(OperationCode code, const T*... operands) {
return StoreNode(OperationNode(code, operands...));
@@ -834,7 +877,7 @@ private:
std::map<u32, ConstBuffer> used_cbufs;
std::set<Sampler> used_samplers;
std::array<bool, Tegra::Engines::Maxwell3D::Regs::NumClipDistances> used_clip_distances{};
- std::set<GlobalMemoryBase> used_global_memory_bases;
+ std::map<GlobalMemoryBase, GlobalMemoryUsage> used_global_memory;
Tegra::Shader::Header header;
};
diff --git a/src/video_core/surface.cpp b/src/video_core/surface.cpp
index a7ac26d71..3b022a456 100644
--- a/src/video_core/surface.cpp
+++ b/src/video_core/surface.cpp
@@ -294,6 +294,8 @@ PixelFormat PixelFormatFromTextureFormat(Tegra::Texture::TextureFormat format,
return PixelFormat::Z16;
case Tegra::Texture::TextureFormat::Z24S8:
return PixelFormat::Z24S8;
+ case Tegra::Texture::TextureFormat::ZF32_X24S8:
+ return PixelFormat::Z32FS8;
case Tegra::Texture::TextureFormat::DXT1:
return is_srgb ? PixelFormat::DXT1_SRGB : PixelFormat::DXT1;
case Tegra::Texture::TextureFormat::DXT23:
diff --git a/src/video_core/texture_cache.cpp b/src/video_core/texture_cache.cpp
new file mode 100644
index 000000000..e96eba7cc
--- /dev/null
+++ b/src/video_core/texture_cache.cpp
@@ -0,0 +1,386 @@
+// Copyright 2019 yuzu Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#include "common/alignment.h"
+#include "common/assert.h"
+#include "common/cityhash.h"
+#include "common/common_types.h"
+#include "core/core.h"
+#include "video_core/surface.h"
+#include "video_core/texture_cache.h"
+#include "video_core/textures/decoders.h"
+#include "video_core/textures/texture.h"
+
+namespace VideoCommon {
+
+using VideoCore::Surface::SurfaceTarget;
+
+using VideoCore::Surface::ComponentTypeFromDepthFormat;
+using VideoCore::Surface::ComponentTypeFromRenderTarget;
+using VideoCore::Surface::ComponentTypeFromTexture;
+using VideoCore::Surface::PixelFormatFromDepthFormat;
+using VideoCore::Surface::PixelFormatFromRenderTargetFormat;
+using VideoCore::Surface::PixelFormatFromTextureFormat;
+using VideoCore::Surface::SurfaceTargetFromTextureType;
+
+constexpr u32 GetMipmapSize(bool uncompressed, u32 mip_size, u32 tile) {
+ return uncompressed ? mip_size : std::max(1U, (mip_size + tile - 1) / tile);
+}
+
+SurfaceParams SurfaceParams::CreateForTexture(Core::System& system,
+ const Tegra::Texture::FullTextureInfo& config) {
+ SurfaceParams params;
+ params.is_tiled = config.tic.IsTiled();
+ params.block_width = params.is_tiled ? config.tic.BlockWidth() : 0,
+ params.block_height = params.is_tiled ? config.tic.BlockHeight() : 0,
+ params.block_depth = params.is_tiled ? config.tic.BlockDepth() : 0,
+ params.tile_width_spacing = params.is_tiled ? (1 << config.tic.tile_width_spacing.Value()) : 1;
+ params.pixel_format =
+ PixelFormatFromTextureFormat(config.tic.format, config.tic.r_type.Value(), false);
+ params.component_type = ComponentTypeFromTexture(config.tic.r_type.Value());
+ params.type = GetFormatType(params.pixel_format);
+ params.target = SurfaceTargetFromTextureType(config.tic.texture_type);
+ params.width = Common::AlignUp(config.tic.Width(), GetCompressionFactor(params.pixel_format));
+ params.height = Common::AlignUp(config.tic.Height(), GetCompressionFactor(params.pixel_format));
+ params.depth = config.tic.Depth();
+ if (params.target == SurfaceTarget::TextureCubemap ||
+ params.target == SurfaceTarget::TextureCubeArray) {
+ params.depth *= 6;
+ }
+ params.pitch = params.is_tiled ? 0 : config.tic.Pitch();
+ params.unaligned_height = config.tic.Height();
+ params.num_levels = config.tic.max_mip_level + 1;
+
+ params.CalculateCachedValues();
+ return params;
+}
+
+SurfaceParams SurfaceParams::CreateForDepthBuffer(
+ Core::System& system, u32 zeta_width, u32 zeta_height, Tegra::DepthFormat format,
+ u32 block_width, u32 block_height, u32 block_depth,
+ Tegra::Engines::Maxwell3D::Regs::InvMemoryLayout type) {
+ SurfaceParams params;
+ params.is_tiled = type == Tegra::Engines::Maxwell3D::Regs::InvMemoryLayout::BlockLinear;
+ params.block_width = 1 << std::min(block_width, 5U);
+ params.block_height = 1 << std::min(block_height, 5U);
+ params.block_depth = 1 << std::min(block_depth, 5U);
+ params.tile_width_spacing = 1;
+ params.pixel_format = PixelFormatFromDepthFormat(format);
+ params.component_type = ComponentTypeFromDepthFormat(format);
+ params.type = GetFormatType(params.pixel_format);
+ params.width = zeta_width;
+ params.height = zeta_height;
+ params.unaligned_height = zeta_height;
+ params.target = SurfaceTarget::Texture2D;
+ params.depth = 1;
+ params.num_levels = 1;
+
+ params.CalculateCachedValues();
+ return params;
+}
+
+SurfaceParams SurfaceParams::CreateForFramebuffer(Core::System& system, std::size_t index) {
+ const auto& config{system.GPU().Maxwell3D().regs.rt[index]};
+ SurfaceParams params;
+ params.is_tiled =
+ config.memory_layout.type == Tegra::Engines::Maxwell3D::Regs::InvMemoryLayout::BlockLinear;
+ params.block_width = 1 << config.memory_layout.block_width;
+ params.block_height = 1 << config.memory_layout.block_height;
+ params.block_depth = 1 << config.memory_layout.block_depth;
+ params.tile_width_spacing = 1;
+ params.pixel_format = PixelFormatFromRenderTargetFormat(config.format);
+ params.component_type = ComponentTypeFromRenderTarget(config.format);
+ params.type = GetFormatType(params.pixel_format);
+ if (params.is_tiled) {
+ params.width = config.width;
+ } else {
+ const u32 bpp = GetFormatBpp(params.pixel_format) / CHAR_BIT;
+ params.pitch = config.width;
+ params.width = params.pitch / bpp;
+ }
+ params.height = config.height;
+ params.depth = 1;
+ params.unaligned_height = config.height;
+ params.target = SurfaceTarget::Texture2D;
+ params.num_levels = 1;
+
+ params.CalculateCachedValues();
+ return params;
+}
+
+SurfaceParams SurfaceParams::CreateForFermiCopySurface(
+ const Tegra::Engines::Fermi2D::Regs::Surface& config) {
+ SurfaceParams params{};
+ params.is_tiled = !config.linear;
+ params.block_width = params.is_tiled ? std::min(config.BlockWidth(), 32U) : 0,
+ params.block_height = params.is_tiled ? std::min(config.BlockHeight(), 32U) : 0,
+ params.block_depth = params.is_tiled ? std::min(config.BlockDepth(), 32U) : 0,
+ params.tile_width_spacing = 1;
+ params.pixel_format = PixelFormatFromRenderTargetFormat(config.format);
+ params.component_type = ComponentTypeFromRenderTarget(config.format);
+ params.type = GetFormatType(params.pixel_format);
+ params.width = config.width;
+ params.height = config.height;
+ params.unaligned_height = config.height;
+ // TODO(Rodrigo): Try to guess the surface target from depth and layer parameters
+ params.target = SurfaceTarget::Texture2D;
+ params.depth = 1;
+ params.num_levels = 1;
+
+ params.CalculateCachedValues();
+ return params;
+}
+
+u32 SurfaceParams::GetMipWidth(u32 level) const {
+ return std::max(1U, width >> level);
+}
+
+u32 SurfaceParams::GetMipHeight(u32 level) const {
+ return std::max(1U, height >> level);
+}
+
+u32 SurfaceParams::GetMipDepth(u32 level) const {
+ return IsLayered() ? depth : std::max(1U, depth >> level);
+}
+
+bool SurfaceParams::IsLayered() const {
+ switch (target) {
+ case SurfaceTarget::Texture1DArray:
+ case SurfaceTarget::Texture2DArray:
+ case SurfaceTarget::TextureCubeArray:
+ case SurfaceTarget::TextureCubemap:
+ return true;
+ default:
+ return false;
+ }
+}
+
+u32 SurfaceParams::GetMipBlockHeight(u32 level) const {
+ // Auto block resizing algorithm from:
+ // https://cgit.freedesktop.org/mesa/mesa/tree/src/gallium/drivers/nouveau/nv50/nv50_miptree.c
+ if (level == 0) {
+ return block_height;
+ }
+ const u32 height{GetMipHeight(level)};
+ const u32 default_block_height{GetDefaultBlockHeight(pixel_format)};
+ const u32 blocks_in_y{(height + default_block_height - 1) / default_block_height};
+ u32 block_height = 16;
+ while (block_height > 1 && blocks_in_y <= block_height * 4) {
+ block_height >>= 1;
+ }
+ return block_height;
+}
+
+u32 SurfaceParams::GetMipBlockDepth(u32 level) const {
+ if (level == 0)
+ return block_depth;
+ if (target != SurfaceTarget::Texture3D)
+ return 1;
+
+ const u32 depth{GetMipDepth(level)};
+ u32 block_depth = 32;
+ while (block_depth > 1 && depth * 2 <= block_depth) {
+ block_depth >>= 1;
+ }
+ if (block_depth == 32 && GetMipBlockHeight(level) >= 4) {
+ return 16;
+ }
+ return block_depth;
+}
+
+std::size_t SurfaceParams::GetGuestMipmapLevelOffset(u32 level) const {
+ std::size_t offset = 0;
+ for (u32 i = 0; i < level; i++) {
+ offset += GetInnerMipmapMemorySize(i, false, IsLayered(), false);
+ }
+ return offset;
+}
+
+std::size_t SurfaceParams::GetHostMipmapLevelOffset(u32 level) const {
+ std::size_t offset = 0;
+ for (u32 i = 0; i < level; i++) {
+ offset += GetInnerMipmapMemorySize(i, true, false, false);
+ }
+ return offset;
+}
+
+std::size_t SurfaceParams::GetGuestLayerSize() const {
+ return GetInnerMemorySize(false, true, false);
+}
+
+std::size_t SurfaceParams::GetHostLayerSize(u32 level) const {
+ return GetInnerMipmapMemorySize(level, true, IsLayered(), false);
+}
+
+bool SurfaceParams::IsFamiliar(const SurfaceParams& view_params) const {
+ if (std::tie(is_tiled, tile_width_spacing, pixel_format, component_type, type) !=
+ std::tie(view_params.is_tiled, view_params.tile_width_spacing, view_params.pixel_format,
+ view_params.component_type, view_params.type)) {
+ return false;
+ }
+
+ const SurfaceTarget view_target{view_params.target};
+ if (view_target == target) {
+ return true;
+ }
+
+ switch (target) {
+ case SurfaceTarget::Texture1D:
+ case SurfaceTarget::Texture2D:
+ case SurfaceTarget::Texture3D:
+ return false;
+ case SurfaceTarget::Texture1DArray:
+ return view_target == SurfaceTarget::Texture1D;
+ case SurfaceTarget::Texture2DArray:
+ return view_target == SurfaceTarget::Texture2D;
+ case SurfaceTarget::TextureCubemap:
+ return view_target == SurfaceTarget::Texture2D ||
+ view_target == SurfaceTarget::Texture2DArray;
+ case SurfaceTarget::TextureCubeArray:
+ return view_target == SurfaceTarget::Texture2D ||
+ view_target == SurfaceTarget::Texture2DArray ||
+ view_target == SurfaceTarget::TextureCubemap;
+ default:
+ UNIMPLEMENTED_MSG("Unimplemented texture family={}", static_cast<u32>(target));
+ return false;
+ }
+}
+
+bool SurfaceParams::IsPixelFormatZeta() const {
+ return pixel_format >= VideoCore::Surface::PixelFormat::MaxColorFormat &&
+ pixel_format < VideoCore::Surface::PixelFormat::MaxDepthStencilFormat;
+}
+
+void SurfaceParams::CalculateCachedValues() {
+ guest_size_in_bytes = GetInnerMemorySize(false, false, false);
+
+ // ASTC is uncompressed in software, in emulated as RGBA8
+ if (IsPixelFormatASTC(pixel_format)) {
+ host_size_in_bytes = width * height * depth * 4;
+ } else {
+ host_size_in_bytes = GetInnerMemorySize(true, false, false);
+ }
+
+ switch (target) {
+ case SurfaceTarget::Texture1D:
+ case SurfaceTarget::Texture2D:
+ case SurfaceTarget::Texture3D:
+ num_layers = 1;
+ break;
+ case SurfaceTarget::Texture1DArray:
+ case SurfaceTarget::Texture2DArray:
+ case SurfaceTarget::TextureCubemap:
+ case SurfaceTarget::TextureCubeArray:
+ num_layers = depth;
+ break;
+ default:
+ UNREACHABLE();
+ }
+}
+
+std::size_t SurfaceParams::GetInnerMipmapMemorySize(u32 level, bool as_host_size, bool layer_only,
+ bool uncompressed) const {
+ const bool tiled{as_host_size ? false : is_tiled};
+ const u32 tile_x{GetDefaultBlockWidth(pixel_format)};
+ const u32 tile_y{GetDefaultBlockHeight(pixel_format)};
+ const u32 width{GetMipmapSize(uncompressed, GetMipWidth(level), tile_x)};
+ const u32 height{GetMipmapSize(uncompressed, GetMipHeight(level), tile_y)};
+ const u32 depth{layer_only ? 1U : GetMipDepth(level)};
+ return Tegra::Texture::CalculateSize(tiled, GetBytesPerPixel(pixel_format), width, height,
+ depth, GetMipBlockHeight(level), GetMipBlockDepth(level));
+}
+
+std::size_t SurfaceParams::GetInnerMemorySize(bool as_host_size, bool layer_only,
+ bool uncompressed) const {
+ std::size_t size = 0;
+ for (u32 level = 0; level < num_levels; ++level) {
+ size += GetInnerMipmapMemorySize(level, as_host_size, layer_only, uncompressed);
+ }
+ if (!as_host_size && is_tiled) {
+ size = Common::AlignUp(size, Tegra::Texture::GetGOBSize() * block_height * block_depth);
+ }
+ return size;
+}
+
+std::map<u64, std::pair<u32, u32>> SurfaceParams::CreateViewOffsetMap() const {
+ std::map<u64, std::pair<u32, u32>> view_offset_map;
+ switch (target) {
+ case SurfaceTarget::Texture1D:
+ case SurfaceTarget::Texture2D:
+ case SurfaceTarget::Texture3D: {
+ constexpr u32 layer = 0;
+ for (u32 level = 0; level < num_levels; ++level) {
+ const std::size_t offset{GetGuestMipmapLevelOffset(level)};
+ view_offset_map.insert({offset, {layer, level}});
+ }
+ break;
+ }
+ case SurfaceTarget::Texture1DArray:
+ case SurfaceTarget::Texture2DArray:
+ case SurfaceTarget::TextureCubemap:
+ case SurfaceTarget::TextureCubeArray: {
+ const std::size_t layer_size{GetGuestLayerSize()};
+ for (u32 level = 0; level < num_levels; ++level) {
+ const std::size_t level_offset{GetGuestMipmapLevelOffset(level)};
+ for (u32 layer = 0; layer < num_layers; ++layer) {
+ const auto layer_offset{static_cast<std::size_t>(layer_size * layer)};
+ const std::size_t offset{level_offset + layer_offset};
+ view_offset_map.insert({offset, {layer, level}});
+ }
+ }
+ break;
+ }
+ default:
+ UNIMPLEMENTED_MSG("Unimplemented surface target {}", static_cast<u32>(target));
+ }
+ return view_offset_map;
+}
+
+bool SurfaceParams::IsViewValid(const SurfaceParams& view_params, u32 layer, u32 level) const {
+ return IsDimensionValid(view_params, level) && IsDepthValid(view_params, level) &&
+ IsInBounds(view_params, layer, level);
+}
+
+bool SurfaceParams::IsDimensionValid(const SurfaceParams& view_params, u32 level) const {
+ return view_params.width == GetMipWidth(level) && view_params.height == GetMipHeight(level);
+}
+
+bool SurfaceParams::IsDepthValid(const SurfaceParams& view_params, u32 level) const {
+ if (view_params.target != SurfaceTarget::Texture3D) {
+ return true;
+ }
+ return view_params.depth == GetMipDepth(level);
+}
+
+bool SurfaceParams::IsInBounds(const SurfaceParams& view_params, u32 layer, u32 level) const {
+ return layer + view_params.num_layers <= num_layers &&
+ level + view_params.num_levels <= num_levels;
+}
+
+std::size_t HasheableSurfaceParams::Hash() const {
+ return static_cast<std::size_t>(
+ Common::CityHash64(reinterpret_cast<const char*>(this), sizeof(*this)));
+}
+
+bool HasheableSurfaceParams::operator==(const HasheableSurfaceParams& rhs) const {
+ return std::tie(is_tiled, block_width, block_height, block_depth, tile_width_spacing, width,
+ height, depth, pitch, unaligned_height, num_levels, pixel_format,
+ component_type, type, target) ==
+ std::tie(rhs.is_tiled, rhs.block_width, rhs.block_height, rhs.block_depth,
+ rhs.tile_width_spacing, rhs.width, rhs.height, rhs.depth, rhs.pitch,
+ rhs.unaligned_height, rhs.num_levels, rhs.pixel_format, rhs.component_type,
+ rhs.type, rhs.target);
+}
+
+std::size_t ViewKey::Hash() const {
+ return static_cast<std::size_t>(
+ Common::CityHash64(reinterpret_cast<const char*>(this), sizeof(*this)));
+}
+
+bool ViewKey::operator==(const ViewKey& rhs) const {
+ return std::tie(base_layer, num_layers, base_level, num_levels) ==
+ std::tie(rhs.base_layer, rhs.num_layers, rhs.base_level, rhs.num_levels);
+}
+
+} // namespace VideoCommon
diff --git a/src/video_core/texture_cache.h b/src/video_core/texture_cache.h
new file mode 100644
index 000000000..041551691
--- /dev/null
+++ b/src/video_core/texture_cache.h
@@ -0,0 +1,586 @@
+// Copyright 2019 yuzu Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#pragma once
+
+#include <list>
+#include <memory>
+#include <set>
+#include <tuple>
+#include <type_traits>
+#include <unordered_map>
+
+#include <boost/icl/interval_map.hpp>
+#include <boost/range/iterator_range.hpp>
+
+#include "common/assert.h"
+#include "common/common_types.h"
+#include "core/memory.h"
+#include "video_core/engines/fermi_2d.h"
+#include "video_core/engines/maxwell_3d.h"
+#include "video_core/gpu.h"
+#include "video_core/rasterizer_interface.h"
+#include "video_core/surface.h"
+
+namespace Core {
+class System;
+}
+
+namespace Tegra::Texture {
+struct FullTextureInfo;
+}
+
+namespace VideoCore {
+class RasterizerInterface;
+}
+
+namespace VideoCommon {
+
+class HasheableSurfaceParams {
+public:
+ std::size_t Hash() const;
+
+ bool operator==(const HasheableSurfaceParams& rhs) const;
+
+protected:
+ // Avoid creation outside of a managed environment.
+ HasheableSurfaceParams() = default;
+
+ bool is_tiled;
+ u32 block_width;
+ u32 block_height;
+ u32 block_depth;
+ u32 tile_width_spacing;
+ u32 width;
+ u32 height;
+ u32 depth;
+ u32 pitch;
+ u32 unaligned_height;
+ u32 num_levels;
+ VideoCore::Surface::PixelFormat pixel_format;
+ VideoCore::Surface::ComponentType component_type;
+ VideoCore::Surface::SurfaceType type;
+ VideoCore::Surface::SurfaceTarget target;
+};
+
+class SurfaceParams final : public HasheableSurfaceParams {
+public:
+ /// Creates SurfaceCachedParams from a texture configuration.
+ static SurfaceParams CreateForTexture(Core::System& system,
+ const Tegra::Texture::FullTextureInfo& config);
+
+ /// Creates SurfaceCachedParams for a depth buffer configuration.
+ static SurfaceParams CreateForDepthBuffer(
+ Core::System& system, u32 zeta_width, u32 zeta_height, Tegra::DepthFormat format,
+ u32 block_width, u32 block_height, u32 block_depth,
+ Tegra::Engines::Maxwell3D::Regs::InvMemoryLayout type);
+
+ /// Creates SurfaceCachedParams from a framebuffer configuration.
+ static SurfaceParams CreateForFramebuffer(Core::System& system, std::size_t index);
+
+ /// Creates SurfaceCachedParams from a Fermi2D surface configuration.
+ static SurfaceParams CreateForFermiCopySurface(
+ const Tegra::Engines::Fermi2D::Regs::Surface& config);
+
+ bool IsTiled() const {
+ return is_tiled;
+ }
+
+ u32 GetBlockWidth() const {
+ return block_width;
+ }
+
+ u32 GetTileWidthSpacing() const {
+ return tile_width_spacing;
+ }
+
+ u32 GetWidth() const {
+ return width;
+ }
+
+ u32 GetHeight() const {
+ return height;
+ }
+
+ u32 GetDepth() const {
+ return depth;
+ }
+
+ u32 GetPitch() const {
+ return pitch;
+ }
+
+ u32 GetNumLevels() const {
+ return num_levels;
+ }
+
+ VideoCore::Surface::PixelFormat GetPixelFormat() const {
+ return pixel_format;
+ }
+
+ VideoCore::Surface::ComponentType GetComponentType() const {
+ return component_type;
+ }
+
+ VideoCore::Surface::SurfaceTarget GetTarget() const {
+ return target;
+ }
+
+ VideoCore::Surface::SurfaceType GetType() const {
+ return type;
+ }
+
+ std::size_t GetGuestSizeInBytes() const {
+ return guest_size_in_bytes;
+ }
+
+ std::size_t GetHostSizeInBytes() const {
+ return host_size_in_bytes;
+ }
+
+ u32 GetNumLayers() const {
+ return num_layers;
+ }
+
+ /// Returns the width of a given mipmap level.
+ u32 GetMipWidth(u32 level) const;
+
+ /// Returns the height of a given mipmap level.
+ u32 GetMipHeight(u32 level) const;
+
+ /// Returns the depth of a given mipmap level.
+ u32 GetMipDepth(u32 level) const;
+
+ /// Returns true if these parameters are from a layered surface.
+ bool IsLayered() const;
+
+ /// Returns the block height of a given mipmap level.
+ u32 GetMipBlockHeight(u32 level) const;
+
+ /// Returns the block depth of a given mipmap level.
+ u32 GetMipBlockDepth(u32 level) const;
+
+ /// Returns the offset in bytes in guest memory of a given mipmap level.
+ std::size_t GetGuestMipmapLevelOffset(u32 level) const;
+
+ /// Returns the offset in bytes in host memory (linear) of a given mipmap level.
+ std::size_t GetHostMipmapLevelOffset(u32 level) const;
+
+ /// Returns the size of a layer in bytes in guest memory.
+ std::size_t GetGuestLayerSize() const;
+
+ /// Returns the size of a layer in bytes in host memory for a given mipmap level.
+ std::size_t GetHostLayerSize(u32 level) const;
+
+ /// Returns true if another surface can be familiar with this. This is a loosely defined term
+ /// that reflects the possibility of these two surface parameters potentially being part of a
+ /// bigger superset.
+ bool IsFamiliar(const SurfaceParams& view_params) const;
+
+ /// Returns true if the pixel format is a depth and/or stencil format.
+ bool IsPixelFormatZeta() const;
+
+ /// Creates a map that redirects an address difference to a layer and mipmap level.
+ std::map<u64, std::pair<u32, u32>> CreateViewOffsetMap() const;
+
+ /// Returns true if the passed surface view parameters is equal or a valid subset of this.
+ bool IsViewValid(const SurfaceParams& view_params, u32 layer, u32 level) const;
+
+private:
+ /// Calculates values that can be deduced from HasheableSurfaceParams.
+ void CalculateCachedValues();
+
+ /// Returns the size of a given mipmap level.
+ std::size_t GetInnerMipmapMemorySize(u32 level, bool as_host_size, bool layer_only,
+ bool uncompressed) const;
+
+ /// 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;
+
+ /// Returns true if the passed view width and height match the size of this params in a given
+ /// mipmap level.
+ bool IsDimensionValid(const SurfaceParams& view_params, u32 level) const;
+
+ /// Returns true if the passed view depth match the size of this params in a given mipmap level.
+ bool IsDepthValid(const SurfaceParams& view_params, u32 level) const;
+
+ /// Returns true if the passed view layers and mipmap levels are in bounds.
+ bool IsInBounds(const SurfaceParams& view_params, u32 layer, u32 level) const;
+
+ std::size_t guest_size_in_bytes;
+ std::size_t host_size_in_bytes;
+ u32 num_layers;
+};
+
+struct ViewKey {
+ std::size_t Hash() const;
+
+ bool operator==(const ViewKey& rhs) const;
+
+ u32 base_layer{};
+ u32 num_layers{};
+ u32 base_level{};
+ u32 num_levels{};
+};
+
+} // namespace VideoCommon
+
+namespace std {
+
+template <>
+struct hash<VideoCommon::SurfaceParams> {
+ std::size_t operator()(const VideoCommon::SurfaceParams& k) const noexcept {
+ return k.Hash();
+ }
+};
+
+template <>
+struct hash<VideoCommon::ViewKey> {
+ std::size_t operator()(const VideoCommon::ViewKey& k) const noexcept {
+ return k.Hash();
+ }
+};
+
+} // namespace std
+
+namespace VideoCommon {
+
+template <typename TView, typename TExecutionContext>
+class SurfaceBase {
+ static_assert(std::is_trivially_copyable_v<TExecutionContext>);
+
+public:
+ virtual void LoadBuffer() = 0;
+
+ virtual TExecutionContext FlushBuffer(TExecutionContext exctx) = 0;
+
+ virtual TExecutionContext UploadTexture(TExecutionContext exctx) = 0;
+
+ TView* TryGetView(VAddr view_addr, const SurfaceParams& view_params) {
+ if (view_addr < cpu_addr || !params.IsFamiliar(view_params)) {
+ // It can't be a view if it's in a prior address.
+ return {};
+ }
+
+ const auto relative_offset{static_cast<u64>(view_addr - cpu_addr)};
+ const auto it{view_offset_map.find(relative_offset)};
+ if (it == view_offset_map.end()) {
+ // Couldn't find an aligned view.
+ return {};
+ }
+ const auto [layer, level] = it->second;
+
+ if (!params.IsViewValid(view_params, layer, level)) {
+ return {};
+ }
+
+ return GetView(layer, view_params.GetNumLayers(), level, view_params.GetNumLevels());
+ }
+
+ VAddr GetCpuAddr() const {
+ ASSERT(is_registered);
+ return cpu_addr;
+ }
+
+ u8* GetHostPtr() const {
+ ASSERT(is_registered);
+ return host_ptr;
+ }
+
+ CacheAddr GetCacheAddr() const {
+ ASSERT(is_registered);
+ return cache_addr;
+ }
+
+ std::size_t GetSizeInBytes() const {
+ return params.GetGuestSizeInBytes();
+ }
+
+ void MarkAsModified(bool is_modified_) {
+ is_modified = is_modified_;
+ }
+
+ const SurfaceParams& GetSurfaceParams() const {
+ return params;
+ }
+
+ TView* GetView(VAddr view_addr, const SurfaceParams& view_params) {
+ TView* view{TryGetView(view_addr, view_params)};
+ ASSERT(view != nullptr);
+ return view;
+ }
+
+ void Register(VAddr cpu_addr_, u8* host_ptr_) {
+ ASSERT(!is_registered);
+ is_registered = true;
+ cpu_addr = cpu_addr_;
+ host_ptr = host_ptr_;
+ cache_addr = ToCacheAddr(host_ptr_);
+ }
+
+ void Register(VAddr cpu_addr_) {
+ Register(cpu_addr_, Memory::GetPointer(cpu_addr_));
+ }
+
+ void Unregister() {
+ ASSERT(is_registered);
+ is_registered = false;
+ }
+
+ bool IsRegistered() const {
+ return is_registered;
+ }
+
+protected:
+ explicit SurfaceBase(const SurfaceParams& params)
+ : params{params}, view_offset_map{params.CreateViewOffsetMap()} {}
+
+ ~SurfaceBase() = default;
+
+ virtual std::unique_ptr<TView> CreateView(const ViewKey& view_key) = 0;
+
+ bool IsModified() const {
+ return is_modified;
+ }
+
+ const SurfaceParams params;
+
+private:
+ TView* GetView(u32 base_layer, u32 num_layers, u32 base_level, u32 num_levels) {
+ const ViewKey key{base_layer, num_layers, base_level, num_levels};
+ const auto [entry, is_cache_miss] = views.try_emplace(key);
+ auto& view{entry->second};
+ if (is_cache_miss) {
+ view = CreateView(key);
+ }
+ return view.get();
+ }
+
+ const std::map<u64, std::pair<u32, u32>> view_offset_map;
+
+ VAddr cpu_addr{};
+ u8* host_ptr{};
+ CacheAddr cache_addr{};
+ bool is_modified{};
+ bool is_registered{};
+ std::unordered_map<ViewKey, std::unique_ptr<TView>> views;
+};
+
+template <typename TSurface, typename TView, typename TExecutionContext>
+class TextureCache {
+ static_assert(std::is_trivially_copyable_v<TExecutionContext>);
+ using ResultType = std::tuple<TView*, TExecutionContext>;
+ using IntervalMap = boost::icl::interval_map<CacheAddr, std::set<TSurface*>>;
+ using IntervalType = typename IntervalMap::interval_type;
+
+public:
+ void InvalidateRegion(CacheAddr addr, std::size_t size) {
+ for (TSurface* surface : GetSurfacesInRegion(addr, size)) {
+ if (!surface->IsRegistered()) {
+ // Skip duplicates
+ continue;
+ }
+ Unregister(surface);
+ }
+ }
+
+ ResultType GetTextureSurface(TExecutionContext exctx,
+ const Tegra::Texture::FullTextureInfo& config) {
+ auto& memory_manager{system.GPU().MemoryManager()};
+ const auto cpu_addr{memory_manager.GpuToCpuAddress(config.tic.Address())};
+ if (!cpu_addr) {
+ return {{}, exctx};
+ }
+ const auto params{SurfaceParams::CreateForTexture(system, config)};
+ return GetSurfaceView(exctx, *cpu_addr, params, true);
+ }
+
+ ResultType GetDepthBufferSurface(TExecutionContext exctx, bool preserve_contents) {
+ const auto& regs{system.GPU().Maxwell3D().regs};
+ if (!regs.zeta.Address() || !regs.zeta_enable) {
+ return {{}, exctx};
+ }
+
+ auto& memory_manager{system.GPU().MemoryManager()};
+ const auto cpu_addr{memory_manager.GpuToCpuAddress(regs.zeta.Address())};
+ if (!cpu_addr) {
+ return {{}, exctx};
+ }
+
+ const auto depth_params{SurfaceParams::CreateForDepthBuffer(
+ system, regs.zeta_width, regs.zeta_height, regs.zeta.format,
+ regs.zeta.memory_layout.block_width, regs.zeta.memory_layout.block_height,
+ regs.zeta.memory_layout.block_depth, regs.zeta.memory_layout.type)};
+ return GetSurfaceView(exctx, *cpu_addr, depth_params, preserve_contents);
+ }
+
+ ResultType GetColorBufferSurface(TExecutionContext exctx, std::size_t index,
+ bool preserve_contents) {
+ ASSERT(index < Tegra::Engines::Maxwell3D::Regs::NumRenderTargets);
+
+ const auto& regs{system.GPU().Maxwell3D().regs};
+ if (index >= regs.rt_control.count || regs.rt[index].Address() == 0 ||
+ regs.rt[index].format == Tegra::RenderTargetFormat::NONE) {
+ return {{}, exctx};
+ }
+
+ auto& memory_manager{system.GPU().MemoryManager()};
+ const auto& config{system.GPU().Maxwell3D().regs.rt[index]};
+ const auto cpu_addr{memory_manager.GpuToCpuAddress(
+ config.Address() + config.base_layer * config.layer_stride * sizeof(u32))};
+ if (!cpu_addr) {
+ return {{}, exctx};
+ }
+
+ return GetSurfaceView(exctx, *cpu_addr, SurfaceParams::CreateForFramebuffer(system, index),
+ preserve_contents);
+ }
+
+ ResultType GetFermiSurface(TExecutionContext exctx,
+ const Tegra::Engines::Fermi2D::Regs::Surface& config) {
+ const auto cpu_addr{system.GPU().MemoryManager().GpuToCpuAddress(config.Address())};
+ ASSERT(cpu_addr);
+ return GetSurfaceView(exctx, *cpu_addr, SurfaceParams::CreateForFermiCopySurface(config),
+ true);
+ }
+
+ TSurface* TryFindFramebufferSurface(const u8* host_ptr) const {
+ const auto it{registered_surfaces.find(ToCacheAddr(host_ptr))};
+ return it != registered_surfaces.end() ? *it->second.begin() : nullptr;
+ }
+
+protected:
+ TextureCache(Core::System& system, VideoCore::RasterizerInterface& rasterizer)
+ : system{system}, rasterizer{rasterizer} {}
+
+ ~TextureCache() = default;
+
+ virtual ResultType TryFastGetSurfaceView(TExecutionContext exctx, VAddr cpu_addr, u8* host_ptr,
+ const SurfaceParams& params, bool preserve_contents,
+ const std::vector<TSurface*>& overlaps) = 0;
+
+ virtual std::unique_ptr<TSurface> CreateSurface(const SurfaceParams& params) = 0;
+
+ void Register(TSurface* surface, VAddr cpu_addr, u8* host_ptr) {
+ surface->Register(cpu_addr, host_ptr);
+ registered_surfaces.add({GetSurfaceInterval(surface), {surface}});
+ rasterizer.UpdatePagesCachedCount(surface->GetCpuAddr(), surface->GetSizeInBytes(), 1);
+ }
+
+ void Unregister(TSurface* surface) {
+ registered_surfaces.subtract({GetSurfaceInterval(surface), {surface}});
+ rasterizer.UpdatePagesCachedCount(surface->GetCpuAddr(), surface->GetSizeInBytes(), -1);
+ surface->Unregister();
+ }
+
+ TSurface* GetUncachedSurface(const SurfaceParams& params) {
+ if (TSurface* surface = TryGetReservedSurface(params); surface)
+ return surface;
+ // No reserved surface available, create a new one and reserve it
+ auto new_surface{CreateSurface(params)};
+ TSurface* surface{new_surface.get()};
+ ReserveSurface(params, std::move(new_surface));
+ return surface;
+ }
+
+ Core::System& system;
+
+private:
+ ResultType GetSurfaceView(TExecutionContext exctx, VAddr cpu_addr, const SurfaceParams& params,
+ bool preserve_contents) {
+ const auto host_ptr{Memory::GetPointer(cpu_addr)};
+ const auto cache_addr{ToCacheAddr(host_ptr)};
+ const auto overlaps{GetSurfacesInRegion(cache_addr, params.GetGuestSizeInBytes())};
+ if (overlaps.empty()) {
+ return LoadSurfaceView(exctx, cpu_addr, host_ptr, params, preserve_contents);
+ }
+
+ if (overlaps.size() == 1) {
+ if (TView* view = overlaps[0]->TryGetView(cpu_addr, params); view)
+ return {view, exctx};
+ }
+
+ TView* fast_view;
+ std::tie(fast_view, exctx) =
+ TryFastGetSurfaceView(exctx, cpu_addr, host_ptr, params, preserve_contents, overlaps);
+
+ for (TSurface* surface : overlaps) {
+ if (!fast_view) {
+ // Flush even when we don't care about the contents, to preserve memory not written
+ // by the new surface.
+ exctx = surface->FlushBuffer(exctx);
+ }
+ Unregister(surface);
+ }
+
+ if (fast_view) {
+ return {fast_view, exctx};
+ }
+
+ return LoadSurfaceView(exctx, cpu_addr, host_ptr, params, preserve_contents);
+ }
+
+ ResultType LoadSurfaceView(TExecutionContext exctx, VAddr cpu_addr, u8* host_ptr,
+ const SurfaceParams& params, bool preserve_contents) {
+ TSurface* new_surface{GetUncachedSurface(params)};
+ Register(new_surface, cpu_addr, host_ptr);
+ if (preserve_contents) {
+ exctx = LoadSurface(exctx, new_surface);
+ }
+ return {new_surface->GetView(cpu_addr, params), exctx};
+ }
+
+ TExecutionContext LoadSurface(TExecutionContext exctx, TSurface* surface) {
+ surface->LoadBuffer();
+ exctx = surface->UploadTexture(exctx);
+ surface->MarkAsModified(false);
+ return exctx;
+ }
+
+ std::vector<TSurface*> GetSurfacesInRegion(CacheAddr cache_addr, std::size_t size) const {
+ if (size == 0) {
+ return {};
+ }
+ const IntervalType interval{cache_addr, cache_addr + size};
+
+ std::vector<TSurface*> surfaces;
+ for (auto& pair : boost::make_iterator_range(registered_surfaces.equal_range(interval))) {
+ surfaces.push_back(*pair.second.begin());
+ }
+ return surfaces;
+ }
+
+ void ReserveSurface(const SurfaceParams& params, std::unique_ptr<TSurface> surface) {
+ surface_reserve[params].push_back(std::move(surface));
+ }
+
+ TSurface* TryGetReservedSurface(const SurfaceParams& params) {
+ auto search{surface_reserve.find(params)};
+ if (search == surface_reserve.end()) {
+ return {};
+ }
+ for (auto& surface : search->second) {
+ if (!surface->IsRegistered()) {
+ return surface.get();
+ }
+ }
+ return {};
+ }
+
+ IntervalType GetSurfaceInterval(TSurface* surface) const {
+ return IntervalType::right_open(surface->GetCacheAddr(),
+ surface->GetCacheAddr() + surface->GetSizeInBytes());
+ }
+
+ VideoCore::RasterizerInterface& rasterizer;
+
+ IntervalMap registered_surfaces;
+
+ /// The surface reserve is a "backup" cache, this is where we put unique surfaces that have
+ /// previously been used. This is to prevent surfaces from being constantly created and
+ /// destroyed when used with different surface parameters.
+ std::unordered_map<SurfaceParams, std::list<std::unique_ptr<TSurface>>> surface_reserve;
+};
+
+} // namespace VideoCommon
diff --git a/src/video_core/textures/decoders.cpp b/src/video_core/textures/decoders.cpp
index 995d0e068..217805386 100644
--- a/src/video_core/textures/decoders.cpp
+++ b/src/video_core/textures/decoders.cpp
@@ -288,6 +288,29 @@ void UnswizzleSubrect(u32 subrect_width, u32 subrect_height, u32 dest_pitch, u32
}
}
+void SwizzleKepler(const u32 width, const u32 height, const u32 dst_x, const u32 dst_y,
+ const u32 block_height, const std::size_t copy_size, const u8* source_data,
+ u8* swizzle_data) {
+ const u32 image_width_in_gobs{(width + gob_size_x - 1) / gob_size_x};
+ std::size_t count = 0;
+ for (std::size_t y = dst_y; y < height && count < copy_size; ++y) {
+ const std::size_t gob_address_y =
+ (y / (gob_size_y * block_height)) * gob_size * block_height * image_width_in_gobs +
+ ((y % (gob_size_y * block_height)) / gob_size_y) * gob_size;
+ const auto& table = legacy_swizzle_table[y % gob_size_y];
+ for (std::size_t x = dst_x; x < width && count < copy_size; ++x) {
+ const std::size_t gob_address =
+ gob_address_y + (x / gob_size_x) * gob_size * block_height;
+ const std::size_t swizzled_offset = gob_address + table[x % gob_size_x];
+ const u8* source_line = source_data + count;
+ u8* dest_addr = swizzle_data + swizzled_offset;
+ count++;
+
+ std::memcpy(dest_addr, source_line, 1);
+ }
+ }
+}
+
std::vector<u8> DecodeTexture(const std::vector<u8>& texture_data, TextureFormat format, u32 width,
u32 height) {
std::vector<u8> rgba_data;
diff --git a/src/video_core/textures/decoders.h b/src/video_core/textures/decoders.h
index e078fa274..e072d8401 100644
--- a/src/video_core/textures/decoders.h
+++ b/src/video_core/textures/decoders.h
@@ -51,4 +51,8 @@ void UnswizzleSubrect(u32 subrect_width, u32 subrect_height, u32 dest_pitch, u32
u32 bytes_per_pixel, u8* swizzled_data, u8* unswizzled_data, u32 block_height,
u32 offset_x, u32 offset_y);
+void SwizzleKepler(const u32 width, const u32 height, const u32 dst_x, const u32 dst_y,
+ const u32 block_height, const std::size_t copy_size, const u8* source_data,
+ u8* swizzle_data);
+
} // namespace Tegra::Texture
diff --git a/src/video_core/video_core.cpp b/src/video_core/video_core.cpp
index cb82ecf3f..60cda0ca3 100644
--- a/src/video_core/video_core.cpp
+++ b/src/video_core/video_core.cpp
@@ -5,6 +5,8 @@
#include <memory>
#include "core/core.h"
#include "core/settings.h"
+#include "video_core/gpu_asynch.h"
+#include "video_core/gpu_synch.h"
#include "video_core/renderer_base.h"
#include "video_core/renderer_opengl/renderer_opengl.h"
#include "video_core/video_core.h"
@@ -16,6 +18,14 @@ std::unique_ptr<RendererBase> CreateRenderer(Core::Frontend::EmuWindow& emu_wind
return std::make_unique<OpenGL::RendererOpenGL>(emu_window, system);
}
+std::unique_ptr<Tegra::GPU> CreateGPU(Core::System& system) {
+ if (Settings::values.use_asynchronous_gpu_emulation) {
+ return std::make_unique<VideoCommon::GPUAsynch>(system, system.Renderer());
+ }
+
+ return std::make_unique<VideoCommon::GPUSynch>(system, system.Renderer());
+}
+
u16 GetResolutionScaleFactor(const RendererBase& renderer) {
return static_cast<u16>(
Settings::values.resolution_factor
diff --git a/src/video_core/video_core.h b/src/video_core/video_core.h
index 3c583f195..b8e0ac372 100644
--- a/src/video_core/video_core.h
+++ b/src/video_core/video_core.h
@@ -14,6 +14,10 @@ namespace Core::Frontend {
class EmuWindow;
}
+namespace Tegra {
+class GPU;
+}
+
namespace VideoCore {
class RendererBase;
@@ -27,6 +31,9 @@ class RendererBase;
std::unique_ptr<RendererBase> CreateRenderer(Core::Frontend::EmuWindow& emu_window,
Core::System& system);
+/// Creates an emulated GPU instance using the given system context.
+std::unique_ptr<Tegra::GPU> CreateGPU(Core::System& system);
+
u16 GetResolutionScaleFactor(const RendererBase& renderer);
} // namespace VideoCore