diff --git a/src/shader_recompiler/environment.h b/src/shader_recompiler/environment.h
index 20381cf467..d46d1c890e 100644
--- a/src/shader_recompiler/environment.h
+++ b/src/shader_recompiler/environment.h
@@ -78,6 +78,11 @@ public:
[[nodiscard]] virtual TextureType ReadTextureType(u32 raw_handle) = 0;
+ [[nodiscard]] virtual u32 ResolveBindlessHandle(u32 cbuf_index, u32 cbuf_offset,
+ u32 raw_handle) {
+ return raw_handle;
+ }
+
[[nodiscard]] virtual TexturePixelFormat ReadTexturePixelFormat(u32 raw_handle) = 0;
[[nodiscard]] virtual bool IsTexturePixelFormatInteger(u32 raw_handle) = 0;
diff --git a/src/shader_recompiler/ir_opt/texture_pass.cpp b/src/shader_recompiler/ir_opt/texture_pass.cpp
index 0d0144fcab..cd306038ff 100644
--- a/src/shader_recompiler/ir_opt/texture_pass.cpp
+++ b/src/shader_recompiler/ir_opt/texture_pass.cpp
@@ -502,7 +502,7 @@ u32 GetTextureHandle(Environment& env, const ConstBufferAddr& cbuf) {
const u32 lhs_raw{env.ReadCbufValue(cbuf.index, cbuf.offset) << cbuf.shift_left};
const u32 rhs_raw{env.ReadCbufValue(secondary_index, secondary_offset)
<< cbuf.secondary_shift_left};
- return lhs_raw | rhs_raw;
+ return env.ResolveBindlessHandle(cbuf.index, cbuf.offset, lhs_raw | rhs_raw);
}
[[maybe_unused]] TextureType ReadTextureType(Environment& env, const ConstBufferAddr& cbuf) {
diff --git a/src/video_core/buffer_cache/buffer_cache.h b/src/video_core/buffer_cache/buffer_cache.h
index 80c6d0948f..33b49b7245 100644
--- a/src/video_core/buffer_cache/buffer_cache.h
+++ b/src/video_core/buffer_cache/buffer_cache.h
@@ -1504,8 +1504,9 @@ void BufferCache
::UpdateComputeUniformBuffers() {
Binding& binding = channel_state->compute_uniform_buffers[index];
binding = NULL_BINDING;
const auto& launch_desc = kepler_compute->launch_description;
- if (((launch_desc.const_buffer_enable_mask >> index) & 1) != 0) {
- const auto& cbuf = launch_desc.const_buffer_config[index];
+ const u32 resolved_index = launch_desc.ResolveConstBufferIndex(index);
+ if (((launch_desc.const_buffer_enable_mask >> resolved_index) & 1) != 0) {
+ const auto& cbuf = launch_desc.const_buffer_config[resolved_index];
const std::optional device_addr = gpu_memory->GpuToCpuAddress(cbuf.Address());
if (device_addr) {
binding.device_addr = *device_addr;
diff --git a/src/video_core/engines/kepler_compute.h b/src/video_core/engines/kepler_compute.h
index 4f7242f804..f35de64ae9 100644
--- a/src/video_core/engines/kepler_compute.h
+++ b/src/video_core/engines/kepler_compute.h
@@ -171,6 +171,22 @@ public:
};
std::array const_buffer_config;
+ static constexpr u32 NvnShaderSlotBias = 2;
+
+ u32 ResolveConstBufferIndex(u32 shader_index) const {
+ const u32 mask = const_buffer_enable_mask.Value();
+ if (((mask >> shader_index) & 1) != 0) {
+ return shader_index;
+ }
+ if (shader_index >= NvnShaderSlotBias) {
+ const u32 biased = shader_index - NvnShaderSlotBias;
+ if (((mask >> biased) & 1) != 0) {
+ return biased;
+ }
+ }
+ return shader_index;
+ }
+
union {
BitField<0, 20, u32> local_pos_alloc;
BitField<27, 5, u32> barrier_alloc;
diff --git a/src/video_core/renderer_opengl/gl_compute_pipeline.cpp b/src/video_core/renderer_opengl/gl_compute_pipeline.cpp
index f0a7baf9aa..bef7825635 100644
--- a/src/video_core/renderer_opengl/gl_compute_pipeline.cpp
+++ b/src/video_core/renderer_opengl/gl_compute_pipeline.cpp
@@ -10,6 +10,7 @@
#include "common/cityhash.h"
#include "common/settings.h"
#include "video_core/renderer_opengl/gl_compute_pipeline.h"
+#include "video_core/shader_environment.h"
#include "video_core/renderer_opengl/gl_shader_manager.h"
#include "video_core/renderer_opengl/gl_shader_util.h"
@@ -104,25 +105,36 @@ void ComputePipeline::Configure() {
const auto& qmd{kepler_compute->launch_description};
const auto& cbufs{qmd.const_buffer_config};
const bool via_header_index{qmd.linked_tsc != 0};
+ const u32 tic_limit{kepler_compute->regs.tic.limit};
const auto read_handle{[&](const auto& desc, u32 index) {
- ASSERT(((qmd.const_buffer_enable_mask >> desc.cbuf_index) & 1) != 0);
+ const u32 resolved_index{qmd.ResolveConstBufferIndex(desc.cbuf_index)};
+ ASSERT(((qmd.const_buffer_enable_mask >> resolved_index) & 1) != 0);
const u32 index_offset{index << desc.size_shift};
const u32 offset{desc.cbuf_offset + index_offset};
- const GPUVAddr addr{cbufs[desc.cbuf_index].Address() + offset};
+ const GPUVAddr addr{cbufs[resolved_index].Address() + offset};
if constexpr (std::is_same_v ||
std::is_same_v) {
if (desc.has_secondary) {
- ASSERT(((qmd.const_buffer_enable_mask >> desc.secondary_cbuf_index) & 1) != 0);
+ const u32 resolved_secondary{
+ qmd.ResolveConstBufferIndex(desc.secondary_cbuf_index)};
+ ASSERT(((qmd.const_buffer_enable_mask >> resolved_secondary) & 1) != 0);
const u32 secondary_offset{desc.secondary_cbuf_offset + index_offset};
- const GPUVAddr separate_addr{cbufs[desc.secondary_cbuf_index].Address() +
+ const GPUVAddr separate_addr{cbufs[resolved_secondary].Address() +
secondary_offset};
const u32 lhs_raw{gpu_memory->Read(addr) << desc.shift_left};
const u32 rhs_raw{gpu_memory->Read(separate_addr)
<< desc.secondary_shift_left};
- return TexturePair(lhs_raw | rhs_raw, via_header_index);
+ const u32 combined{VideoCommon::ResolveBindlessHandleTable(
+ *gpu_memory, addr, lhs_raw | rhs_raw,
+ TexturePair(lhs_raw | rhs_raw, via_header_index).first, tic_limit)};
+ return TexturePair(combined, via_header_index);
}
}
- return TexturePair(gpu_memory->Read(addr), via_header_index);
+ const u32 single_raw{gpu_memory->Read(addr)};
+ return TexturePair(VideoCommon::ResolveBindlessHandleTable(
+ *gpu_memory, addr, single_raw,
+ TexturePair(single_raw, via_header_index).first, tic_limit),
+ via_header_index);
}};
const auto add_image{[&](const auto& desc, bool blacklist) {
for (u32 index = 0; index < desc.count; ++index) {
diff --git a/src/video_core/renderer_vulkan/vk_compute_pipeline.cpp b/src/video_core/renderer_vulkan/vk_compute_pipeline.cpp
index 006bfc0a09..5d48ff8417 100644
--- a/src/video_core/renderer_vulkan/vk_compute_pipeline.cpp
+++ b/src/video_core/renderer_vulkan/vk_compute_pipeline.cpp
@@ -14,6 +14,7 @@
#include "video_core/renderer_vulkan/pipeline_statistics.h"
#include "video_core/renderer_vulkan/vk_buffer_cache.h"
#include "video_core/renderer_vulkan/vk_compute_pipeline.h"
+#include "video_core/shader_environment.h"
#include "video_core/renderer_vulkan/vk_descriptor_pool.h"
#include "video_core/renderer_vulkan/vk_pipeline_cache.h"
#include "video_core/renderer_vulkan/vk_scheduler.h"
@@ -148,24 +149,35 @@ void ComputePipeline::Configure(Tegra::Engines::KeplerCompute& kepler_compute,
const auto& qmd{kepler_compute.launch_description};
const auto& cbufs{qmd.const_buffer_config};
const bool via_header_index{qmd.linked_tsc != 0};
+ const u32 tic_limit{kepler_compute.regs.tic.limit};
const auto read_handle{[&](const auto& desc, u32 index) {
- ASSERT(((qmd.const_buffer_enable_mask >> desc.cbuf_index) & 1) != 0);
+ const u32 resolved_index{qmd.ResolveConstBufferIndex(desc.cbuf_index)};
+ ASSERT(((qmd.const_buffer_enable_mask >> resolved_index) & 1) != 0);
const u32 index_offset{index << desc.size_shift};
const u32 offset{desc.cbuf_offset + index_offset};
- const GPUVAddr addr{cbufs[desc.cbuf_index].Address() + offset};
+ const GPUVAddr addr{cbufs[resolved_index].Address() + offset};
if constexpr (std::is_same_v ||
std::is_same_v) {
if (desc.has_secondary) {
- ASSERT(((qmd.const_buffer_enable_mask >> desc.secondary_cbuf_index) & 1) != 0);
+ const u32 resolved_secondary{
+ qmd.ResolveConstBufferIndex(desc.secondary_cbuf_index)};
+ ASSERT(((qmd.const_buffer_enable_mask >> resolved_secondary) & 1) != 0);
const u32 secondary_offset{desc.secondary_cbuf_offset + index_offset};
- const GPUVAddr separate_addr{cbufs[desc.secondary_cbuf_index].Address() +
+ const GPUVAddr separate_addr{cbufs[resolved_secondary].Address() +
secondary_offset};
const u32 lhs_raw{gpu_memory.Read(addr) << desc.shift_left};
const u32 rhs_raw{gpu_memory.Read(separate_addr) << desc.secondary_shift_left};
- return TexturePair(lhs_raw | rhs_raw, via_header_index);
+ const u32 combined{VideoCommon::ResolveBindlessHandleTable(
+ gpu_memory, addr, lhs_raw | rhs_raw,
+ TexturePair(lhs_raw | rhs_raw, via_header_index).first, tic_limit)};
+ return TexturePair(combined, via_header_index);
}
}
- return TexturePair(gpu_memory.Read(addr), via_header_index);
+ const u32 single_raw{gpu_memory.Read(addr)};
+ return TexturePair(VideoCommon::ResolveBindlessHandleTable(
+ gpu_memory, addr, single_raw,
+ TexturePair(single_raw, via_header_index).first, tic_limit),
+ via_header_index);
}};
const auto add_image{[&](const auto& desc, bool blacklist) {
for (u32 index = 0; index < desc.count; ++index) {
diff --git a/src/video_core/shader_environment.cpp b/src/video_core/shader_environment.cpp
index 4d5db7e511..1d075ca8aa 100644
--- a/src/video_core/shader_environment.cpp
+++ b/src/video_core/shader_environment.cpp
@@ -281,6 +281,34 @@ std::optional GenericEnvironment::TryFindSize() {
return std::nullopt;
}
+u32 ResolveBindlessHandleTable(Tegra::MemoryManager& gpu_memory, GPUVAddr record_addr,
+ u32 raw_handle, u32 tic_index, u32 tic_limit) {
+ static constexpr u32 HandleRecordSize = 16;
+ static constexpr u32 MaxHandleTableBytes = 64 * 1024;
+
+ if (tic_index <= tic_limit) {
+ return raw_handle;
+ }
+ const u32 addr_low{gpu_memory.Read(record_addr)};
+ const u32 addr_high{gpu_memory.Read(record_addr + 4)};
+ const u32 table_size{gpu_memory.Read(record_addr + 8)};
+ const GPUVAddr table_addr{(static_cast(addr_high) << 32) |
+ static_cast(addr_low)};
+ if (table_addr == 0 || table_size < HandleRecordSize || table_size > MaxHandleTableBytes) {
+ return raw_handle;
+ }
+ const u32 count{table_size / HandleRecordSize};
+ for (u32 i = 0; i < count; ++i) {
+ const GPUVAddr entry_addr{table_addr + static_cast(i) * HandleRecordSize};
+ const u32 handle{gpu_memory.Read(entry_addr)};
+ const u32 live{gpu_memory.Read(entry_addr + 4)};
+ if (live != 0 && handle != 0 && handle <= tic_limit) {
+ return handle;
+ }
+ }
+ return raw_handle;
+}
+
Tegra::Texture::TICEntry GenericEnvironment::ReadTextureInfo(GPUVAddr tic_addr, u32 tic_limit,
bool via_header_index, u32 raw) {
const auto handle{Tegra::Texture::TexturePair(raw, via_header_index)};
@@ -421,8 +449,9 @@ ComputeEnvironment::ComputeEnvironment(Tegra::Engines::KeplerCompute& kepler_com
u32 ComputeEnvironment::ReadCbufValue(u32 cbuf_index, u32 cbuf_offset) {
const auto& qmd{kepler_compute->launch_description};
- ASSERT(((qmd.const_buffer_enable_mask.Value() >> cbuf_index) & 1) != 0);
- const auto& cbuf{qmd.const_buffer_config[cbuf_index]};
+ const u32 resolved_index{qmd.ResolveConstBufferIndex(cbuf_index)};
+ ASSERT(((qmd.const_buffer_enable_mask.Value() >> resolved_index) & 1) != 0);
+ const auto& cbuf{qmd.const_buffer_config[resolved_index]};
u32 value{};
if (cbuf_offset < cbuf.size) {
value = gpu_memory->Read(cbuf.Address() + cbuf_offset);
@@ -431,6 +460,25 @@ u32 ComputeEnvironment::ReadCbufValue(u32 cbuf_index, u32 cbuf_offset) {
return value;
}
+u32 ComputeEnvironment::ResolveBindlessHandle(u32 cbuf_index, u32 cbuf_offset, u32 raw_handle) {
+ const auto& regs{kepler_compute->regs};
+ const auto& qmd{kepler_compute->launch_description};
+ const auto pair{Tegra::Texture::TexturePair(raw_handle, qmd.linked_tsc != 0)};
+ if (pair.first <= regs.tic.limit) {
+ return raw_handle;
+ }
+ const u32 resolved_index{qmd.ResolveConstBufferIndex(cbuf_index)};
+ if (((qmd.const_buffer_enable_mask.Value() >> resolved_index) & 1) == 0) {
+ return raw_handle;
+ }
+ const auto& cbuf{qmd.const_buffer_config[resolved_index]};
+ if (cbuf_offset + 12 > cbuf.size) {
+ return raw_handle;
+ }
+ return ResolveBindlessHandleTable(*gpu_memory, cbuf.Address() + cbuf_offset, raw_handle,
+ pair.first, regs.tic.limit);
+}
+
Shader::TextureType ComputeEnvironment::ReadTextureType(u32 handle) {
const auto& regs{kepler_compute->regs};
const auto& qmd{kepler_compute->launch_description};
diff --git a/src/video_core/shader_environment.h b/src/video_core/shader_environment.h
index 95c6d72f89..d0b897c1a4 100644
--- a/src/video_core/shader_environment.h
+++ b/src/video_core/shader_environment.h
@@ -29,6 +29,9 @@ class Memorymanager;
namespace VideoCommon {
+[[nodiscard]] u32 ResolveBindlessHandleTable(Tegra::MemoryManager& gpu_memory, GPUVAddr record_addr,
+ u32 raw_handle, u32 tic_index, u32 tic_limit);
+
class GenericEnvironment : public Shader::Environment {
public:
explicit GenericEnvironment() = default;
@@ -140,6 +143,8 @@ public:
u32 ReadCbufValue(u32 cbuf_index, u32 cbuf_offset) override;
+ u32 ResolveBindlessHandle(u32 cbuf_index, u32 cbuf_offset, u32 raw_handle) override;
+
Shader::TextureType ReadTextureType(u32 handle) override;
Shader::TexturePixelFormat ReadTexturePixelFormat(u32 handle) override;