Rewrite descriptor bindings and unify graphics push constants

This commit is contained in:
nmzik
2026-08-21 22:28:10 +02:00
parent 26c4168160
commit bad6726f5e
33 changed files with 1501 additions and 1255 deletions
@@ -9,7 +9,6 @@
#include "graphics/host_gpu/graphicContext.h"
#include "graphics/host_gpu/renderer/debug.h"
#include "graphics/host_gpu/renderer/image/textureCommon.h"
#include "graphics/host_gpu/renderer/pipeline/descriptorCache.h"
#include "graphics/host_gpu/renderer/render.h"
#include "graphics/host_gpu/renderer/renderContext.h"
#include "graphics/host_gpu/vulkanCommon.h"
@@ -55,13 +55,14 @@ public:
void DeferPriorityOperation(Common::UniqueFunction<void>&& operation);
[[nodiscard]] static bool InDeferredOperation() noexcept;
[[nodiscard]] bool Active() const noexcept { return m_current >= 0; }
void CheckActive() const;
RenderCommandBuffer& Current() const;
[[nodiscard]] uint64_t CurrentTick() const noexcept { return m_master.CurrentTick(); }
[[nodiscard]] bool IsFree(uint64_t tick);
[[nodiscard]] RenderContext& Context() const noexcept { return m_context; }
[[nodiscard]] GraphicContext& Graphics() const noexcept { return m_graphics; }
[[nodiscard]] bool Active() const noexcept { return m_current >= 0; }
void CheckActive() const;
RenderCommandBuffer& Current() const;
[[nodiscard]] uint64_t CurrentTick() const noexcept { return m_master.CurrentTick(); }
[[nodiscard]] bool IsFree(uint64_t tick);
[[nodiscard]] MasterSemaphore& GetMasterSemaphore() noexcept { return m_master; }
[[nodiscard]] RenderContext& Context() const noexcept { return m_context; }
[[nodiscard]] GraphicContext& Graphics() const noexcept { return m_graphics; }
private:
static constexpr size_t CommandBufferGrowStep = 4;
+2 -18
View File
@@ -9,7 +9,6 @@
#include "graphics/host_gpu/renderer/debug.h"
#include "graphics/host_gpu/renderer/depthRenderTarget.h"
#include "graphics/host_gpu/renderer/image/imageView.h"
#include "graphics/host_gpu/renderer/pipeline/descriptorCache.h"
#include "graphics/host_gpu/renderer/render.h"
#include "graphics/host_gpu/renderer/renderContext.h"
#include "graphics/host_gpu/vma.h"
@@ -30,8 +29,8 @@ void ReportVulkanFatal(const char* what, vk::Result result, uint32_t slot, uint6
" args=%u,%u,%u,%u,0x%016" PRIx64 "\n",
what, VulkanToString(result).c_str(), static_cast<int>(result), slot, submit_seq, debug_op,
debug_submit, arg0, arg1, arg2, arg3, arg4);
std::printf("%s failed: %s (%d), slot=%u submit_seq=%" PRIu64 " debug_op=%u debug_submit=%" PRIu64
" args=%u,%u,%u,%u,0x%016" PRIx64 "\n",
std::printf("%s failed: %s (%d), slot=%u submit_seq=%" PRIu64
" debug_op=%u debug_submit=%" PRIu64 " args=%u,%u,%u,%u,0x%016" PRIx64 "\n",
what, VulkanToString(result).c_str(), static_cast<int>(result), slot, submit_seq,
debug_op, debug_submit, arg0, arg1, arg2, arg3, arg4);
std::fflush(stdout);
@@ -68,23 +67,11 @@ void CommandBuffer::Release() {
m_slot->busy = false;
m_slot->Reset();
RecycleDescriptorsAfterFence();
m_slot = nullptr;
EXIT_NOT_IMPLEMENTED(!IsInvalid());
}
void CommandBuffer::RecycleDescriptorAfterFence(VulkanDescriptorSet& set) {
m_descriptor_sets_after_fence.push_back(&set);
}
void CommandBuffer::RecycleDescriptorsAfterFence() {
for (auto* set: m_descriptor_sets_after_fence) {
m_context.GetDescriptorCache().Recycle(*set);
}
m_descriptor_sets_after_fence.clear();
}
void CommandBuffer::Begin() const {
EXIT_IF(m_rendering);
auto buffer = Handle();
@@ -218,9 +205,6 @@ void CommandBuffer::FinalizeFence(bool reset_recording) {
m_slot->Reset();
}
}
if (was_executed) {
RecycleDescriptorsAfterFence();
}
}
void CommandBuffer::BeginRendering(const RenderState& state) const {
@@ -13,7 +13,6 @@
#include "graphics/host_gpu/renderer/debug.h"
#include "graphics/host_gpu/renderer/image/imageView.h"
#include "graphics/host_gpu/renderer/image/textureCommon.h"
#include "graphics/host_gpu/renderer/pipeline/descriptorCache.h"
#include "graphics/host_gpu/renderer/render.h"
#include "graphics/host_gpu/renderer/renderContext.h"
#include "graphics/host_gpu/vulkanCommon.h"
@@ -290,8 +289,8 @@ void RenderExecutor::ResolveRenderDepthTarget(uint64_t submit_id, RenderCommandB
r.stencil_clear_enable =
has_stencil && rc.stencil_clear_enable && !z.depth_view.stencil_write_disable;
r.stencil_clear_value = hw.GetStencilClearValue();
r.stencil_test_enable = has_stencil && dc.stencil_enable;
r.stencil_clear_value = hw.GetStencilClearValue();
r.stencil_test_enable = has_stencil && dc.stencil_enable;
if (r.stencil_test_enable) {
const bool stencil_ops_disabled =
rc.stencil_clear_enable || z.depth_view.stencil_write_disable;
@@ -1,318 +0,0 @@
#include "graphics/host_gpu/renderer/pipeline/descriptorCache.h"
#include "common/assert.h"
#include "common/profiler.h"
#include "graphics/shader/recompiler/ir/ShaderIR.h"
#include <array>
namespace Libs::Graphics {
namespace {
using BindingKind = ShaderRecompiler::IR::DescriptorBindingKind;
vk::ShaderStageFlags StageFlags(DescriptorCache::Stage stage) {
switch (stage) {
case DescriptorCache::Stage::Vertex: return vk::ShaderStageFlagBits::eVertex;
case DescriptorCache::Stage::Pixel: return vk::ShaderStageFlagBits::eFragment;
case DescriptorCache::Stage::Compute: return vk::ShaderStageFlagBits::eCompute;
default: EXIT("unknown descriptor stage\n");
}
}
bool IsSampledImage(BindingKind kind) {
switch (kind) {
case BindingKind::Sampled1D:
case BindingKind::Sampled1DArray:
case BindingKind::Sampled2D:
case BindingKind::Sampled2DArray:
case BindingKind::Sampled2DMsaa:
case BindingKind::Sampled2DMsaaArray:
case BindingKind::Sampled3D:
case BindingKind::SampledUint1D:
case BindingKind::SampledUint1DArray:
case BindingKind::SampledUint2D:
case BindingKind::SampledUint2DArray:
case BindingKind::SampledUint2DMsaa:
case BindingKind::SampledUint2DMsaaArray:
case BindingKind::SampledUint3D: return true;
default: return false;
}
}
bool IsStorageImage(BindingKind kind) {
switch (kind) {
case BindingKind::Storage1D:
case BindingKind::Storage1DArray:
case BindingKind::Storage2D:
case BindingKind::Storage2DArray:
case BindingKind::Storage3D:
case BindingKind::StorageUint1D:
case BindingKind::StorageUint1DArray:
case BindingKind::StorageUint2D:
case BindingKind::StorageUint2DArray:
case BindingKind::StorageUint3D:
case BindingKind::StorageAtomic1D:
case BindingKind::StorageAtomic1DArray:
case BindingKind::StorageAtomic2D:
case BindingKind::StorageAtomic2DArray:
case BindingKind::StorageAtomic3D: return true;
default: return false;
}
}
vk::DescriptorType DescriptorType(BindingKind kind) {
if (kind == BindingKind::Samplers) {
return vk::DescriptorType::eSampler;
}
if (IsSampledImage(kind)) {
return vk::DescriptorType::eSampledImage;
}
if (IsStorageImage(kind)) {
return vk::DescriptorType::eStorageImage;
}
return vk::DescriptorType::eStorageBuffer;
}
uint32_t DescriptorCount(const ShaderRecompiler::IR::DescriptorBinding& binding) {
return binding.resources.empty() ? 1u : static_cast<uint32_t>(binding.resources.size());
}
std::vector<uint32_t> LayoutKey(DescriptorCache::Stage stage,
const ShaderRecompiler::IR::Program& program) {
std::vector<uint32_t> key;
key.reserve(1u + program.bindings.descriptors.size() * 4u);
key.push_back(static_cast<uint32_t>(stage));
for (const auto& binding: program.bindings.descriptors) {
key.push_back(static_cast<uint32_t>(binding.kind));
key.push_back(binding.binding);
key.push_back(DescriptorCount(binding));
key.push_back(static_cast<uint32_t>(DescriptorType(binding.kind)));
}
return key;
}
vk::DescriptorBufferInfo BufferInfo(const BufferView& view) {
EXIT_IF(view.buffer == nullptr);
return {view.buffer, view.offset, view.range};
}
} // namespace
vk::DescriptorImageInfo DescriptorCache::MakeImageInfo(const TextureBinding& texture,
uint32_t element) {
const auto view =
texture.mip_views.empty()
? (element == 0u ? texture.image_view : vk::ImageView {})
: (element < texture.mip_views.size() ? texture.mip_views[element] : vk::ImageView {});
EXIT_IF(!texture.image_id || view == nullptr || texture.layout == vk::ImageLayout::eUndefined);
return {nullptr, view, texture.layout};
}
DescriptorCache::~DescriptorCache() {
for (auto& [layout, sets]: m_free_sets_by_layout) {
(void)layout;
for (auto* set: sets) {
delete set;
}
}
for (const auto& [key, layout]: m_descriptor_set_layouts) {
(void)key;
m_graphics.device.destroyDescriptorSetLayout(layout, nullptr);
}
for (const auto& pool: m_pools) {
m_graphics.device.destroyDescriptorPool(pool.pool, nullptr);
}
}
vk::DescriptorSetLayout
DescriptorCache::GetDescriptorSetLayoutInternal(Stage stage,
const ShaderRecompiler::IR::Program& program) {
const auto key = LayoutKey(stage, program);
if (const auto found = m_descriptor_set_layouts.find(key);
found != m_descriptor_set_layouts.end()) {
return found->second;
}
std::vector<vk::DescriptorSetLayoutBinding> bindings;
bindings.reserve(program.bindings.descriptors.size());
for (const auto& descriptor: program.bindings.descriptors) {
bindings.push_back({descriptor.binding, DescriptorType(descriptor.kind),
DescriptorCount(descriptor), StageFlags(stage), nullptr});
}
if (bindings.empty()) {
return nullptr;
}
vk::DescriptorSetLayoutCreateInfo info {};
info.sType = vk::StructureType::eDescriptorSetLayoutCreateInfo;
info.bindingCount = static_cast<uint32_t>(bindings.size());
info.pBindings = bindings.data();
vk::DescriptorSetLayout layout = nullptr;
const auto result = m_graphics.device.createDescriptorSetLayout(&info, nullptr, &layout);
EXIT_NOT_IMPLEMENTED(result != vk::Result::eSuccess || layout == nullptr);
m_descriptor_set_layouts.emplace(key, layout);
return layout;
}
void DescriptorCache::CreatePool() {
KYTY_PROFILER_FUNCTION();
constexpr uint32_t MaxSets = 512;
const vk::DescriptorPoolSize sizes[] = {
{vk::DescriptorType::eStorageBuffer,
MaxSets * (ShaderRecompiler::IR::ShaderInfo::MaxBuffers +
ShaderRecompiler::IR::ShaderInfo::MaxAddresses + 3u)},
{vk::DescriptorType::eSampledImage, MaxSets * ShaderRecompiler::IR::ShaderInfo::MaxImages},
{vk::DescriptorType::eStorageImage, MaxSets * ShaderRecompiler::IR::ShaderInfo::MaxImages},
{vk::DescriptorType::eSampler, MaxSets * ShaderRecompiler::IR::ShaderInfo::MaxSamplers},
};
vk::DescriptorPoolCreateInfo info {};
info.sType = vk::StructureType::eDescriptorPoolCreateInfo;
info.poolSizeCount = static_cast<uint32_t>(std::size(sizes));
info.pPoolSizes = sizes;
info.maxSets = MaxSets;
const auto pool_id = static_cast<int>(m_pools.size());
auto& pool = m_pools.emplace_back();
const auto result = m_graphics.device.createDescriptorPool(&info, nullptr, &pool.pool);
EXIT_NOT_IMPLEMENTED(result != vk::Result::eSuccess || pool.pool == nullptr);
pool.next_free_pool = m_first_free_pool;
m_first_free_pool = pool_id;
}
VulkanDescriptorSet* DescriptorCache::Allocate(Stage stage,
const ShaderRecompiler::IR::Program& program) {
KYTY_PROFILER_FUNCTION();
Common::LockGuard lock(m_mutex);
const auto layout = GetDescriptorSetLayoutInternal(stage, program);
EXIT_IF(layout == nullptr);
auto& free_sets = m_free_sets_by_layout[layout];
if (!free_sets.empty()) {
auto* result = free_sets.back();
free_sets.pop_back();
return result;
}
constexpr uint32_t Batch = 32;
for (int attempt = 0; attempt < 2; attempt++) {
for (int pool_id = m_first_free_pool; pool_id != -1;
pool_id = m_pools[pool_id].next_free_pool) {
auto& pool = m_pools[pool_id];
std::array<vk::DescriptorSetLayout, Batch> layouts;
layouts.fill(layout);
std::array<vk::DescriptorSet, Batch> sets {};
vk::DescriptorSetAllocateInfo info {};
info.sType = vk::StructureType::eDescriptorSetAllocateInfo;
info.descriptorPool = pool.pool;
info.descriptorSetCount = Batch;
info.pSetLayouts = layouts.data();
if (m_graphics.device.allocateDescriptorSets(&info, sets.data()) ==
vk::Result::eSuccess) {
free_sets.reserve(free_sets.size() + Batch - 1u);
for (uint32_t i = 0; i + 1u < Batch; i++) {
free_sets.push_back(new VulkanDescriptorSet {sets[i], layout, pool_id});
}
return new VulkanDescriptorSet {sets.back(), layout, pool_id};
}
m_first_free_pool = pool.next_free_pool;
break;
}
CreatePool();
}
return nullptr;
}
void DescriptorCache::Recycle(VulkanDescriptorSet& set) {
EXIT_IF(set.set == nullptr || set.layout == nullptr);
Common::LockGuard lock(m_mutex);
m_free_sets_by_layout[set.layout].push_back(&set);
}
VulkanDescriptorSet& DescriptorCache::GetDescriptor(Stage stage,
const ShaderRecompiler::IR::Program& program,
const NativeDescriptors& data) {
KYTY_PROFILER_FUNCTION();
EXIT_IF(data.buffers.size() != program.info.buffers.size() ||
data.images.size() != program.info.images.size() ||
data.samplers.size() != program.info.samplers.size() ||
data.addresses.size() != program.info.addresses.size());
auto* set = Allocate(stage, program);
EXIT_NOT_IMPLEMENTED(set == nullptr);
size_t descriptor_count = 0;
for (const auto& binding: program.bindings.descriptors) {
descriptor_count += DescriptorCount(binding);
}
std::vector<vk::DescriptorBufferInfo> buffer_infos;
std::vector<vk::DescriptorImageInfo> image_infos;
std::vector<vk::WriteDescriptorSet> writes;
buffer_infos.reserve(descriptor_count);
image_infos.reserve(descriptor_count);
writes.reserve(program.bindings.descriptors.size());
std::vector<uint32_t> image_occurrences(data.images.size());
for (const auto& binding: program.bindings.descriptors) {
vk::WriteDescriptorSet write {};
write.sType = vk::StructureType::eWriteDescriptorSet;
write.dstSet = set->set;
write.dstBinding = binding.binding;
write.descriptorType = DescriptorType(binding.kind);
write.descriptorCount = DescriptorCount(binding);
const auto buffer_start = buffer_infos.size();
const auto image_start = image_infos.size();
switch (binding.kind) {
case BindingKind::Buffers:
for (const auto resource: binding.resources) {
buffer_infos.push_back(BufferInfo(data.buffers.at(resource)));
}
break;
case BindingKind::AddressMemory:
for (const auto resource: binding.resources) {
buffer_infos.push_back(BufferInfo(data.addresses.at(resource)));
}
break;
case BindingKind::FlattenedSrt:
buffer_infos.push_back(BufferInfo(data.flattened_srt));
break;
case BindingKind::UserData: buffer_infos.push_back(BufferInfo(data.user_data)); break;
case BindingKind::Gds: buffer_infos.push_back(BufferInfo(data.gds)); break;
case BindingKind::Samplers:
for (const auto resource: binding.resources) {
const auto sampler = data.samplers.at(resource);
EXIT_IF(sampler == nullptr);
image_infos.push_back({sampler, nullptr, vk::ImageLayout::eUndefined});
}
break;
default: {
for (const auto resource: binding.resources) {
const auto& texture = data.images.at(resource);
image_infos.push_back(MakeImageInfo(texture, image_occurrences.at(resource)++));
}
break;
}
}
if (buffer_infos.size() != buffer_start) {
write.pBufferInfo = buffer_infos.data() + buffer_start;
}
if (image_infos.size() != image_start) {
write.pImageInfo = image_infos.data() + image_start;
}
writes.push_back(write);
}
for (uint32_t i = 0; i < data.images.size(); i++) {
const auto expected = data.images[i].mip_views.empty()
? 1u
: static_cast<uint32_t>(data.images[i].mip_views.size());
EXIT_IF(image_occurrences[i] != expected);
}
m_graphics.device.updateDescriptorSets(static_cast<uint32_t>(writes.size()), writes.data(), 0,
nullptr);
return *set;
}
vk::DescriptorSetLayout
DescriptorCache::GetDescriptorSetLayout(Stage stage, const ShaderRecompiler::IR::Program& program) {
Common::LockGuard lock(m_mutex);
return GetDescriptorSetLayoutInternal(stage, program);
}
} // namespace Libs::Graphics
@@ -1,118 +0,0 @@
#ifndef EMULATOR_SRC_GRAPHICS_HOST_GPU_RENDERER_DESCRIPTORCACHE_H_
#define EMULATOR_SRC_GRAPHICS_HOST_GPU_RENDERER_DESCRIPTORCACHE_H_
#include "common/abi.h"
#include "common/assert.h"
#include "common/common.h"
#include "common/threads.h"
#include "graphics/host_gpu/graphicContext.h"
#include "graphics/host_gpu/renderer/cache/bufferCache.h"
#include "graphics/host_gpu/renderer/cache/textureCache.h"
#include "graphics/host_gpu/vulkanCommon.h"
#include "graphics/shader/shaderBindings.h"
#include <map>
#include <memory>
#include <span>
#include <unordered_map>
#include <vector>
namespace Libs::Graphics {
namespace ShaderRecompiler::IR {
struct DescriptorValue;
struct ImageResource;
struct Program;
struct ResourceSnapshot;
} // namespace ShaderRecompiler::IR
class CommandBuffer;
struct DescriptorCacheTestAccess;
struct ShaderStageRuntime;
struct VulkanDescriptorSet {
vk::DescriptorSet set = nullptr;
vk::DescriptorSetLayout layout = nullptr;
int pool_id = -1;
};
struct BufferView {
vk::Buffer buffer = nullptr;
vk::DeviceSize offset = 0;
vk::DeviceSize range = VK_WHOLE_SIZE;
};
class DescriptorCache {
public:
enum class Stage { Unknown, Vertex, Pixel, Compute };
struct TextureBinding {
ImageId image_id;
vk::ImageView image_view = nullptr;
TextureCache::ImageDesc desc;
vk::ImageLayout layout = vk::ImageLayout::eUndefined;
std::vector<vk::ImageView> mip_views;
};
struct NativeDescriptors {
std::vector<BufferView> buffers;
std::vector<TextureBinding> images;
std::vector<vk::Sampler> samplers;
std::vector<BufferView> addresses;
BufferView gds;
BufferView flattened_srt;
BufferView user_data;
};
struct PreparedBindings {
std::shared_ptr<const ShaderRecompiler::IR::Program> program;
std::shared_ptr<const ShaderRecompiler::IR::ResourceSnapshot> snapshot;
NativeDescriptors resources;
std::vector<BufferId> buffer_ids;
std::vector<BufferId> address_ids;
std::vector<uint32_t> flattened_srt;
std::vector<uint32_t> user_data;
vk::ShaderStageFlags shader_stage;
Stage stage = Stage::Unknown;
bool committed = false;
};
explicit DescriptorCache(GraphicContext& graphics): m_graphics(graphics) {
EXIT_NOT_IMPLEMENTED(!Common::Thread::IsMainThread());
}
~DescriptorCache();
KYTY_CLASS_NO_COPY(DescriptorCache);
vk::DescriptorSetLayout GetDescriptorSetLayout(Stage stage,
const ShaderRecompiler::IR::Program& program);
void Recycle(VulkanDescriptorSet& set);
VulkanDescriptorSet& GetDescriptor(Stage stage, const ShaderRecompiler::IR::Program& program,
const NativeDescriptors& descriptors);
private:
friend struct DescriptorCacheTestAccess;
struct Pool {
vk::DescriptorPool pool = nullptr;
int next_free_pool = -1;
};
static vk::DescriptorImageInfo MakeImageInfo(const TextureBinding& texture,
uint32_t element = 0);
void CreatePool();
VulkanDescriptorSet* Allocate(Stage stage, const ShaderRecompiler::IR::Program& program);
vk::DescriptorSetLayout
GetDescriptorSetLayoutInternal(Stage stage, const ShaderRecompiler::IR::Program& program);
GraphicContext& m_graphics;
Common::Mutex m_mutex;
std::vector<Pool> m_pools;
int m_first_free_pool = -1;
std::unordered_map<vk::DescriptorSetLayout, std::vector<VulkanDescriptorSet*>>
m_free_sets_by_layout;
std::map<std::vector<uint32_t>, vk::DescriptorSetLayout> m_descriptor_set_layouts;
};
} // namespace Libs::Graphics
#endif // EMULATOR_SRC_GRAPHICS_HOST_GPU_RENDERER_DESCRIPTORCACHE_H_
@@ -0,0 +1,97 @@
#include "graphics/host_gpu/renderer/pipeline/descriptorHeap.h"
#include "common/assert.h"
#include "common/profiler.h"
#include "graphics/host_gpu/graphicContext.h"
#include "graphics/host_gpu/renderer/masterSemaphore.h"
namespace Libs::Graphics {
namespace {
constexpr uint32_t DescriptorHeapCount = 1024;
constexpr std::array DescriptorPoolSizes = {
vk::DescriptorPoolSize {vk::DescriptorType::eStorageBuffer, 8192},
vk::DescriptorPoolSize {vk::DescriptorType::eSampledImage, 8192},
vk::DescriptorPoolSize {vk::DescriptorType::eStorageImage, 1024},
vk::DescriptorPoolSize {vk::DescriptorType::eSampler, 1024},
};
} // namespace
DescriptorHeap::DescriptorHeap(GraphicContext& graphics, MasterSemaphore& master_semaphore)
: m_graphics(graphics), m_master_semaphore(master_semaphore) {
CreatePool();
}
DescriptorHeap::~DescriptorHeap() {
m_graphics.device.destroyDescriptorPool(m_current_pool, nullptr);
for (const auto& [pool, tick]: m_pending_pools) {
m_master_semaphore.Wait(tick);
m_graphics.device.destroyDescriptorPool(pool, nullptr);
}
}
vk::DescriptorSet DescriptorHeap::Commit(vk::DescriptorSetLayout layout) {
KYTY_PROFILER_FUNCTION();
EXIT_IF(layout == nullptr);
auto& batch = m_sets[layout];
if (batch.size != 0) {
return batch.sets[--batch.size];
}
if (Allocate(layout, batch)) {
return batch.sets[--batch.size];
}
m_pending_pools.emplace_back(m_current_pool, m_master_semaphore.CurrentTick());
if (const auto& [pool, tick] = m_pending_pools.front(); m_master_semaphore.IsFree(tick)) {
m_current_pool = pool;
m_pending_pools.pop_front();
EXIT_IF(m_graphics.device.resetDescriptorPool(m_current_pool, {}) != vk::Result::eSuccess);
} else {
CreatePool();
}
m_sets.clear();
auto& fresh_batch = m_sets[layout];
EXIT_IF(!Allocate(layout, fresh_batch));
return fresh_batch.sets[--fresh_batch.size];
}
bool DescriptorHeap::Allocate(vk::DescriptorSetLayout layout, Batch& batch) {
std::array<vk::DescriptorSetLayout, DescriptorSetBatch> layouts;
layouts.fill(layout);
vk::DescriptorSetAllocateInfo allocate {};
allocate.sType = vk::StructureType::eDescriptorSetAllocateInfo;
allocate.descriptorPool = m_current_pool;
allocate.pSetLayouts = layouts.data();
for (;;) {
allocate.descriptorSetCount = batch.allocation;
const auto result = m_graphics.device.allocateDescriptorSets(&allocate, batch.sets.data());
if (result == vk::Result::eSuccess) {
batch.size = batch.allocation;
return true;
}
EXIT_IF(result != vk::Result::eErrorOutOfPoolMemory &&
result != vk::Result::eErrorFragmentedPool);
if (batch.allocation == 1) {
return false;
}
batch.allocation /= 2;
}
}
void DescriptorHeap::CreatePool() {
vk::DescriptorPoolCreateInfo create {};
create.sType = vk::StructureType::eDescriptorPoolCreateInfo;
create.flags = {};
create.maxSets = DescriptorHeapCount;
create.poolSizeCount = static_cast<uint32_t>(DescriptorPoolSizes.size());
create.pPoolSizes = DescriptorPoolSizes.data();
EXIT_IF(m_graphics.device.createDescriptorPool(&create, nullptr, &m_current_pool) !=
vk::Result::eSuccess);
}
} // namespace Libs::Graphics
@@ -0,0 +1,45 @@
#ifndef EMULATOR_SRC_GRAPHICS_HOST_GPU_RENDERER_DESCRIPTORHEAP_H_
#define EMULATOR_SRC_GRAPHICS_HOST_GPU_RENDERER_DESCRIPTORHEAP_H_
#include "common/common.h"
#include "graphics/host_gpu/vulkanCommon.h"
#include <array>
#include <deque>
#include <unordered_map>
namespace Libs::Graphics {
struct GraphicContext;
class MasterSemaphore;
class DescriptorHeap {
public:
DescriptorHeap(GraphicContext& graphics, MasterSemaphore& master_semaphore);
~DescriptorHeap();
KYTY_CLASS_NO_COPY(DescriptorHeap);
[[nodiscard]] vk::DescriptorSet Commit(vk::DescriptorSetLayout layout);
private:
static constexpr uint32_t DescriptorSetBatch = 32;
struct Batch {
std::array<vk::DescriptorSet, DescriptorSetBatch> sets {};
uint32_t size = 0;
uint32_t allocation = DescriptorSetBatch;
};
[[nodiscard]] bool Allocate(vk::DescriptorSetLayout layout, Batch& batch);
void CreatePool();
GraphicContext& m_graphics;
MasterSemaphore& m_master_semaphore;
vk::DescriptorPool m_current_pool = nullptr;
std::deque<std::pair<vk::DescriptorPool, uint64_t>> m_pending_pools;
std::unordered_map<vk::DescriptorSetLayout, Batch> m_sets;
};
} // namespace Libs::Graphics
#endif // EMULATOR_SRC_GRAPHICS_HOST_GPU_RENDERER_DESCRIPTORHEAP_H_
@@ -17,7 +17,6 @@
#include "graphics/host_gpu/renderer/debug.h"
#include "graphics/host_gpu/renderer/image/imageView.h"
#include "graphics/host_gpu/renderer/image/textureCommon.h"
#include "graphics/host_gpu/renderer/pipeline/descriptorCache.h"
#include "graphics/host_gpu/renderer/pipeline/shaderResourceBarrier.h"
#include "graphics/host_gpu/renderer/render.h"
#include "graphics/host_gpu/renderer/renderContext.h"
@@ -45,14 +44,95 @@
namespace Libs::Graphics {
static const char* ShaderStageResourceName(DescriptorCache::Stage stage) {
switch (stage) {
case DescriptorCache::Stage::Vertex: return "Vertex";
case DescriptorCache::Stage::Pixel: return "Pixel";
case DescriptorCache::Stage::Compute: return "Compute";
case DescriptorCache::Stage::Unknown: return "Unknown";
namespace {
using BindingKind = ShaderRecompiler::IR::DescriptorBindingKind;
bool IsSampledImage(BindingKind kind) {
switch (kind) {
case BindingKind::Sampled1D:
case BindingKind::Sampled1DArray:
case BindingKind::Sampled2D:
case BindingKind::Sampled2DArray:
case BindingKind::Sampled2DMsaa:
case BindingKind::Sampled2DMsaaArray:
case BindingKind::Sampled3D:
case BindingKind::SampledUint1D:
case BindingKind::SampledUint1DArray:
case BindingKind::SampledUint2D:
case BindingKind::SampledUint2DArray:
case BindingKind::SampledUint2DMsaa:
case BindingKind::SampledUint2DMsaaArray:
case BindingKind::SampledUint3D: return true;
default: return false;
}
}
bool IsStorageImage(BindingKind kind) {
switch (kind) {
case BindingKind::Storage1D:
case BindingKind::Storage1DArray:
case BindingKind::Storage2D:
case BindingKind::Storage2DArray:
case BindingKind::Storage3D:
case BindingKind::StorageUint1D:
case BindingKind::StorageUint1DArray:
case BindingKind::StorageUint2D:
case BindingKind::StorageUint2DArray:
case BindingKind::StorageUint3D:
case BindingKind::StorageAtomic1D:
case BindingKind::StorageAtomic1DArray:
case BindingKind::StorageAtomic2D:
case BindingKind::StorageAtomic2DArray:
case BindingKind::StorageAtomic3D: return true;
default: return false;
}
}
} // namespace
vk::DescriptorType NativeDescriptorType(BindingKind kind) {
if (kind == BindingKind::Samplers) {
return vk::DescriptorType::eSampler;
}
if (IsSampledImage(kind)) {
return vk::DescriptorType::eSampledImage;
}
if (IsStorageImage(kind)) {
return vk::DescriptorType::eStorageImage;
}
return vk::DescriptorType::eStorageBuffer;
}
uint32_t NativeDescriptorCount(const ShaderRecompiler::IR::DescriptorBinding& binding) {
return binding.resources.empty() ? 1u : static_cast<uint32_t>(binding.resources.size());
}
vk::DescriptorImageInfo MakeImageInfo(const TextureBinding& texture, uint32_t element) {
const auto view =
texture.mip_views.empty()
? (element == 0u ? texture.image_view : vk::ImageView {})
: (element < texture.mip_views.size() ? texture.mip_views[element] : vk::ImageView {});
EXIT_IF(!texture.image_id || view == nullptr || texture.layout == vk::ImageLayout::eUndefined);
return {nullptr, view, texture.layout};
}
static const char* ShaderStageResourceName(ShaderType stage) {
switch (stage) {
case ShaderType::Vertex: return "Vertex";
case ShaderType::Pixel: return "Pixel";
case ShaderType::Compute: return "Compute";
default: return "Unknown";
}
}
static vk::ShaderStageFlags NativeShaderStage(ShaderType stage) {
switch (stage) {
case ShaderType::Vertex: return vk::ShaderStageFlagBits::eVertex;
case ShaderType::Pixel: return vk::ShaderStageFlagBits::eFragment;
case ShaderType::Compute: return vk::ShaderStageFlagBits::eCompute;
default: EXIT("unknown native shader stage\n");
}
return "Unknown";
}
static void BindNullStorageBuffer(RenderContext& context, BufferView& dst) {
@@ -87,11 +167,11 @@ static bool IsMultisampledTexture(Prospero::ImageType type) {
type == Prospero::ImageType::kColor2DMsaaArray;
}
static BufferView
NativeStorageBuffer(RenderContext& context, const ShaderBufferResource& descriptor,
const ShaderRecompiler::IR::BufferResource& resource,
DescriptorCache::Stage stage, uint32_t slot, uint32_t& buffer_offset,
BufferId id) {
static BufferView NativeStorageBuffer(RenderContext& context,
const ShaderBufferResource& descriptor,
const ShaderRecompiler::IR::BufferResource& resource,
ShaderType stage, uint32_t slot, uint32_t& buffer_offset,
BufferId id) {
BufferView result;
buffer_offset = 0;
@@ -113,8 +193,8 @@ NativeStorageBuffer(RenderContext& context, const ShaderBufferResource& descript
size > graphics.GetPhysicalDeviceProperties().limits.maxStorageBufferRange) {
EXIT("storage buffer range or device alignment is unsupported\n");
}
auto [buffer, offset] = context.GetBufferCache().ObtainBuffer(
address, size, resource.written, resource.formatted, id);
auto [buffer, offset] = context.GetBufferCache().ObtainBuffer(address, size, resource.written,
resource.formatted, id);
const auto aligned_offset = offset - offset % alignment;
const auto adjustment = offset - aligned_offset;
const auto max_range = graphics.GetPhysicalDeviceProperties().limits.maxStorageBufferRange;
@@ -136,10 +216,10 @@ NativeStorageBuffer(RenderContext& context, const ShaderBufferResource& descript
return result;
}
static BufferView NativeAddressBuffer(
RenderContext& context, const ShaderRecompiler::IR::AddressResource& resource,
const ShaderRecompiler::IR::ResourceSnapshot::Address& address,
DescriptorCache::Stage stage, uint32_t slot, uint32_t& address_offset, BufferId id) {
static BufferView
NativeAddressBuffer(RenderContext& context, const ShaderRecompiler::IR::AddressResource& resource,
const ShaderRecompiler::IR::ResourceSnapshot::Address& address,
ShaderType stage, uint32_t slot, uint32_t& address_offset, BufferId id) {
BufferView result;
address_offset = 0;
if (address.binding_base == 0) {
@@ -640,9 +720,8 @@ static ImageViewInfo TextureViewInfo(const ShaderRecompiler::IR::ImageResource&
return view;
}
DescriptorCache::TextureBinding
RenderExecutor::ResolveTexture(const ShaderRecompiler::IR::ImageResource& resource,
const ShaderRecompiler::IR::DescriptorValue& value) {
TextureBinding RenderExecutor::ResolveTexture(const ShaderRecompiler::IR::ImageResource& resource,
const ShaderRecompiler::IR::DescriptorValue& value) {
ShaderTextureResource descriptor;
CopyNativeDescriptor(value, descriptor.fields);
const bool storage = resource.written;
@@ -813,7 +892,7 @@ static BufferView NativeUpload(RenderContext& context, std::span<const uint32_t>
EXIT_IF(data.empty());
auto& command_buffer = context.GetCommandScheduler().Current();
EXIT_IF(command_buffer.IsInvalid() || command_buffer.IsExecute());
auto& buffer = context.GetBufferCache().GetUtilityBuffer(MemoryUsage::Stream);
auto& buffer = context.GetBufferCache().GetUtilityBuffer(MemoryUsage::Stream);
const auto offset = buffer.Copy(data.data(), data.size_bytes(), 256);
return {.buffer = buffer.Handle(), .offset = offset, .range = data.size_bytes()};
}
@@ -853,9 +932,7 @@ void RenderExecutor::ResetBindings() {
m_bound_images.clear();
}
DescriptorCache::PreparedBindings
RenderExecutor::PrepareBindings(const ShaderStageRuntime& runtime,
vk::ShaderStageFlags shader_stage, DescriptorCache::Stage stage) {
PreparedBindings RenderExecutor::PrepareBindings(const ShaderStageRuntime& runtime) {
KYTY_PROFILER_FUNCTION();
EXIT_IF(!runtime);
const auto& program = *runtime.program;
@@ -865,12 +942,10 @@ RenderExecutor::PrepareBindings(const ShaderStageRuntime& runtime,
EXIT("invalid native shader runtime snapshot: %s\n", error.c_str());
}
DescriptorCache::PreparedBindings prepared;
prepared.program = runtime.program;
prepared.snapshot = runtime.resources;
prepared.shader_stage = shader_stage;
prepared.stage = stage;
auto& descriptors = prepared.resources;
PreparedBindings prepared;
prepared.program = runtime.program.get();
prepared.snapshot = runtime.resources.get();
auto& descriptors = prepared.resources;
descriptors.buffers.reserve(program.info.buffers.size());
descriptors.images.reserve(program.info.images.size());
for (uint32_t i = 0; i < program.info.images.size(); i++) {
@@ -900,7 +975,7 @@ RenderExecutor::PrepareBindings(const ShaderStageRuntime& runtime,
return prepared;
}
void RenderExecutor::FindBuffers(DescriptorCache::PreparedBindings& prepared) {
void RenderExecutor::FindBuffers(PreparedBindings& prepared) {
KYTY_PROFILER_FUNCTION();
EXIT_IF(prepared.program == nullptr || prepared.snapshot == nullptr);
const auto& program = *prepared.program;
@@ -916,8 +991,7 @@ void RenderExecutor::FindBuffers(DescriptorCache::PreparedBindings& prepared) {
const auto stride = descriptor.Stride();
const auto records = descriptor.NumRecords();
EXIT_IF(stride != 0 && records > UINT64_MAX / stride);
const auto requested_size =
stride != 0 ? static_cast<uint64_t>(stride) * records : records;
const auto requested_size = stride != 0 ? static_cast<uint64_t>(stride) * records : records;
if (address == 0 || requested_size == 0) {
prepared.buffer_ids.emplace_back();
continue;
@@ -936,19 +1010,18 @@ void RenderExecutor::FindBuffers(DescriptorCache::PreparedBindings& prepared) {
continue;
}
EXIT_IF(resource.written);
const auto limit =
resource.kind == ShaderRecompiler::IR::ResourceKind::Flat
? ShaderRecompiler::IR::FlatAddressWindowSize
: static_cast<uint64_t>(m_context.GetGraphics()
.GetPhysicalDeviceProperties()
.limits.maxStorageBufferRange);
uint64_t size = 0;
const auto limit = resource.kind == ShaderRecompiler::IR::ResourceKind::Flat
? ShaderRecompiler::IR::FlatAddressWindowSize
: static_cast<uint64_t>(m_context.GetGraphics()
.GetPhysicalDeviceProperties()
.limits.maxStorageBufferRange);
uint64_t size = 0;
EXIT_IF(!HostMemoryQueryRange(address.binding_base, limit, HostMemoryAccess::Mapped, size));
prepared.address_ids.push_back(cache.FindBuffer(address.binding_base, size));
}
}
void RenderExecutor::RebindBuffers(DescriptorCache::PreparedBindings& prepared) {
void RenderExecutor::RebindBuffers(PreparedBindings& prepared) {
KYTY_PROFILER_FUNCTION();
EXIT_IF(prepared.program == nullptr || prepared.snapshot == nullptr);
const auto& program = *prepared.program;
@@ -972,7 +1045,7 @@ void RenderExecutor::RebindBuffers(DescriptorCache::PreparedBindings& prepared)
CopyNativeDescriptor(snapshot.buffers[i], descriptor.fields);
uint32_t buffer_offset = 0;
resources.buffers.push_back(NativeStorageBuffer(m_context, descriptor,
program.info.buffers[i], prepared.stage, i,
program.info.buffers[i], program.stage, i,
buffer_offset, prepared.buffer_ids[i]));
pack_memory_offset(i, buffer_offset);
}
@@ -980,9 +1053,9 @@ void RenderExecutor::RebindBuffers(DescriptorCache::PreparedBindings& prepared)
resources.addresses.reserve(program.info.addresses.size());
for (uint32_t i = 0; i < program.info.addresses.size(); i++) {
uint32_t address_offset = 0;
resources.addresses.push_back(
NativeAddressBuffer(m_context, program.info.addresses[i], snapshot.addresses[i],
prepared.stage, i, address_offset, prepared.address_ids[i]));
resources.addresses.push_back(NativeAddressBuffer(m_context, program.info.addresses[i],
snapshot.addresses[i], program.stage, i,
address_offset, prepared.address_ids[i]));
pack_memory_offset(static_cast<uint32_t>(program.info.buffers.size()) + i, address_offset);
}
if (!prepared.flattened_srt.empty()) {
@@ -994,7 +1067,7 @@ void RenderExecutor::RebindBuffers(DescriptorCache::PreparedBindings& prepared)
}
}
void RenderExecutor::RebindImages(DescriptorCache::PreparedBindings& prepared) {
void RenderExecutor::RebindImages(PreparedBindings& prepared) {
KYTY_PROFILER_FUNCTION();
EXIT_IF(prepared.program == nullptr || prepared.snapshot == nullptr);
const auto& program = *prepared.program;
@@ -1047,12 +1120,10 @@ RenderExecutor::GraphicsBindings
RenderExecutor::PrepareGraphicsBindings(const ShaderStageRuntime& vertex,
const ShaderStageRuntime& pixel, bool pixel_active) {
GraphicsBindings bindings {
.vertex = PrepareBindings(vertex, vk::ShaderStageFlagBits::eVertex,
DescriptorCache::Stage::Vertex),
.vertex = PrepareBindings(vertex),
};
if (pixel_active) {
bindings.pixel.emplace(PrepareBindings(pixel, vk::ShaderStageFlagBits::eFragment,
DescriptorCache::Stage::Pixel));
bindings.pixel.emplace(PrepareBindings(pixel));
}
FindBuffers(bindings.vertex);
if (bindings.pixel) {
@@ -1071,74 +1142,190 @@ RenderExecutor::PrepareGraphicsBindings(const ShaderStageRuntime& vertex,
void RenderExecutor::CommitBindings(CommandBuffer& buffer,
vk::PipelineBindPoint pipeline_bind_point,
vk::PipelineLayout layout,
DescriptorCache::PreparedBindings& prepared) {
const PipelineCache::Pipeline& pipeline,
std::span<PreparedBindings* const> prepared_bindings) {
KYTY_PROFILER_FUNCTION();
EXIT_IF(prepared.program == nullptr || prepared.stage == DescriptorCache::Stage::Unknown ||
prepared.committed);
const auto& program = *prepared.program;
auto& descriptors = prepared.resources;
auto vk_buffer = buffer.Handle();
const auto shader_stages = ShaderPipelineStages(prepared.shader_stage);
if (descriptors.gds.buffer != nullptr) {
buffer.EndRendering();
const auto barrier = MakeGdsDependency(descriptors.gds.buffer);
vk_buffer.pipelineBarrier(
vk::PipelineStageFlagBits::eHost | vk::PipelineStageFlagBits::eTransfer |
vk::PipelineStageFlagBits::eAllGraphics | vk::PipelineStageFlagBits::eComputeShader,
shader_stages, vk::DependencyFlags {}, 0, nullptr, 1, &barrier, 0, nullptr);
}
for (uint32_t i = 0; i < program.info.images.size(); i++) {
auto& image = m_context.GetTextureCache().GetImage(descriptors.images[i].image_id);
auto& binding = descriptors.images[i];
const auto& view = binding.desc.view_info;
const ImageSubresourceRange range {view.base_level, view.level_count, view.base_layer,
view.layer_count};
const bool storage = binding.desc.type == TextureCache::BindingType::Storage;
if (image.info.data.Empty()) {
image.Transit(vk::ImageLayout::eGeneral,
storage
? vk::AccessFlagBits2::eShaderRead | vk::AccessFlagBits2::eShaderWrite
: vk::AccessFlagBits2::eShaderRead,
range, vk_buffer);
} else if ((image.binding.force_general || image.binding.is_target) &&
!image.info.IsDepth()) {
const vk::AccessFlags2 storage_access = image.binding.shader_write
? vk::AccessFlagBits2::eShaderWrite
: vk::AccessFlags2 {};
image.Transit(vk::ImageLayout::eGeneral,
vk::AccessFlagBits2::eShaderRead | storage_access |
vk::AccessFlagBits2::eColorAttachmentRead |
vk::AccessFlagBits2::eColorAttachmentWrite,
{}, vk_buffer);
} else if (storage) {
image.Transit(vk::ImageLayout::eGeneral,
vk::AccessFlagBits2::eShaderRead | vk::AccessFlagBits2::eShaderWrite,
range, vk_buffer);
} else {
image.Transit(image.info.IsDepth() ? vk::ImageLayout::eDepthStencilReadOnlyOptimal
: vk::ImageLayout::eShaderReadOnlyOptimal,
vk::AccessFlagBits2::eShaderRead, range, vk_buffer);
auto vk_buffer = buffer.Handle();
size_t descriptor_count = 0;
size_t write_count = 0;
constexpr auto GraphicsStages =
vk::ShaderStageFlagBits::eVertex | vk::ShaderStageFlagBits::eFragment;
const auto push_constant_stages = pipeline_bind_point == vk::PipelineBindPoint::eGraphics
? vk::ShaderStageFlags {GraphicsStages}
: vk::ShaderStageFlags {vk::ShaderStageFlagBits::eCompute};
for (const auto* prepared: prepared_bindings) {
EXIT_IF(prepared == nullptr || prepared->program == nullptr ||
prepared->snapshot == nullptr || prepared->committed);
write_count += prepared->program->bindings.descriptors.size();
for (const auto& binding: prepared->program->bindings.descriptors) {
descriptor_count += NativeDescriptorCount(binding);
}
binding.layout = image.backing.state.layout;
const auto shader_stage = NativeShaderStage(prepared->program->stage);
EXIT_IF((pipeline_bind_point == vk::PipelineBindPoint::eGraphics &&
(shader_stage & GraphicsStages) == vk::ShaderStageFlags {}) ||
(pipeline_bind_point == vk::PipelineBindPoint::eCompute &&
shader_stage != vk::ShaderStageFlagBits::eCompute));
}
m_descriptor_buffers.clear();
m_descriptor_images.clear();
m_descriptor_writes.clear();
m_push_constants.fill(0);
m_descriptor_buffers.reserve(descriptor_count);
m_descriptor_images.reserve(descriptor_count);
m_descriptor_writes.reserve(write_count);
if (!program.bindings.descriptors.empty()) {
auto& set =
m_context.GetDescriptorCache().GetDescriptor(prepared.stage, program, descriptors);
vk_buffer.bindDescriptorSets(pipeline_bind_point, layout, program.bindings.descriptor_set,
1, &set.set, 0, nullptr);
buffer.RecycleDescriptorAfterFence(set);
for (auto* prepared: prepared_bindings) {
const auto& program = *prepared->program;
auto& descriptors = prepared->resources;
const auto shader_stage = NativeShaderStage(program.stage);
const auto shader_stages = ShaderPipelineStages(shader_stage);
if (descriptors.gds.buffer != nullptr) {
buffer.EndRendering();
const auto barrier = MakeGdsDependency(descriptors.gds.buffer);
vk_buffer.pipelineBarrier(
vk::PipelineStageFlagBits::eHost | vk::PipelineStageFlagBits::eTransfer |
vk::PipelineStageFlagBits::eAllGraphics |
vk::PipelineStageFlagBits::eComputeShader,
shader_stages, vk::DependencyFlags {}, 0, nullptr, 1, &barrier, 0, nullptr);
}
for (uint32_t i = 0; i < program.info.images.size(); i++) {
auto& image = m_context.GetTextureCache().GetImage(descriptors.images[i].image_id);
auto& binding = descriptors.images[i];
const auto& view = binding.desc.view_info;
const ImageSubresourceRange range {view.base_level, view.level_count, view.base_layer,
view.layer_count};
const bool storage = binding.desc.type == TextureCache::BindingType::Storage;
if (image.info.data.Empty()) {
image.Transit(vk::ImageLayout::eGeneral,
storage ? vk::AccessFlagBits2::eShaderRead |
vk::AccessFlagBits2::eShaderWrite
: vk::AccessFlagBits2::eShaderRead,
range, vk_buffer);
} else if ((image.binding.force_general || image.binding.is_target) &&
!image.info.IsDepth()) {
const vk::AccessFlags2 storage_access = image.binding.shader_write
? vk::AccessFlagBits2::eShaderWrite
: vk::AccessFlags2 {};
image.Transit(vk::ImageLayout::eGeneral,
vk::AccessFlagBits2::eShaderRead | storage_access |
vk::AccessFlagBits2::eColorAttachmentRead |
vk::AccessFlagBits2::eColorAttachmentWrite,
{}, vk_buffer);
} else if (storage) {
image.Transit(vk::ImageLayout::eGeneral,
vk::AccessFlagBits2::eShaderRead | vk::AccessFlagBits2::eShaderWrite,
range, vk_buffer);
} else {
image.Transit(image.info.IsDepth() ? vk::ImageLayout::eDepthStencilReadOnlyOptimal
: vk::ImageLayout::eShaderReadOnlyOptimal,
vk::AccessFlagBits2::eShaderRead, range, vk_buffer);
}
binding.layout = image.backing.state.layout;
}
m_image_occurrences.assign(descriptors.images.size(), 0);
for (const auto& binding: program.bindings.descriptors) {
vk::WriteDescriptorSet write {};
write.sType = vk::StructureType::eWriteDescriptorSet;
write.dstBinding = ShaderRecompiler::IR::NativeBinding(program.stage, binding.kind);
write.descriptorType = NativeDescriptorType(binding.kind);
write.descriptorCount = NativeDescriptorCount(binding);
const auto buffer_start = m_descriptor_buffers.size();
const auto image_start = m_descriptor_images.size();
switch (binding.kind) {
case BindingKind::Buffers:
for (const auto resource: binding.resources) {
const auto& view = descriptors.buffers.at(resource);
EXIT_IF(view.buffer == nullptr);
m_descriptor_buffers.emplace_back(view.buffer, view.offset, view.range);
}
break;
case BindingKind::AddressMemory:
for (const auto resource: binding.resources) {
const auto& view = descriptors.addresses.at(resource);
EXIT_IF(view.buffer == nullptr);
m_descriptor_buffers.emplace_back(view.buffer, view.offset, view.range);
}
break;
case BindingKind::FlattenedSrt:
case BindingKind::UserData:
case BindingKind::Gds: {
const auto& view =
binding.kind == BindingKind::FlattenedSrt ? descriptors.flattened_srt
: binding.kind == BindingKind::UserData ? descriptors.user_data
: descriptors.gds;
EXIT_IF(view.buffer == nullptr);
m_descriptor_buffers.emplace_back(view.buffer, view.offset, view.range);
break;
}
case BindingKind::Samplers:
for (const auto resource: binding.resources) {
const auto sampler = descriptors.samplers.at(resource);
EXIT_IF(sampler == nullptr);
m_descriptor_images.emplace_back(sampler, nullptr,
vk::ImageLayout::eUndefined);
}
break;
default:
for (const auto resource: binding.resources) {
m_descriptor_images.push_back(MakeImageInfo(
descriptors.images.at(resource), m_image_occurrences.at(resource)++));
}
break;
}
if (m_descriptor_buffers.size() != buffer_start) {
write.pBufferInfo = m_descriptor_buffers.data() + buffer_start;
}
if (m_descriptor_images.size() != image_start) {
write.pImageInfo = m_descriptor_images.data() + image_start;
}
m_descriptor_writes.push_back(write);
}
for (uint32_t i = 0; i < descriptors.images.size(); i++) {
const auto expected =
descriptors.images[i].mip_views.empty()
? 1u
: static_cast<uint32_t>(descriptors.images[i].mip_views.size());
EXIT_IF(m_image_occurrences[i] != expected);
}
if (program.bindings.push_constant_size != 0) {
const auto offset = program.bindings.push_constant_offset;
EXIT_IF(offset % sizeof(uint32_t) != 0 ||
program.bindings.push_constant_size !=
prepared->user_data.size() * sizeof(uint32_t) ||
offset + program.bindings.push_constant_size >
ShaderRecompiler::IR::NativePushConstantSize);
std::copy(prepared->user_data.begin(), prepared->user_data.end(),
m_push_constants.begin() + offset / sizeof(uint32_t));
}
}
if (program.bindings.push_constant_size != 0) {
EXIT_IF(program.bindings.push_constant_size !=
prepared.user_data.size() * sizeof(uint32_t));
vk_buffer.pushConstants(layout, prepared.shader_stage,
program.bindings.push_constant_offset,
program.bindings.push_constant_size, prepared.user_data.data());
vk_buffer.pushConstants(pipeline.pipeline_layout, push_constant_stages, 0,
ShaderRecompiler::IR::NativePushConstantSize,
m_push_constants.data());
if (!m_descriptor_writes.empty()) {
EXIT_IF(pipeline.descriptor_set_layout == nullptr);
if (pipeline.uses_push_descriptors) {
vk_buffer.pushDescriptorSetKHR(pipeline_bind_point, pipeline.pipeline_layout, 0,
static_cast<uint32_t>(m_descriptor_writes.size()),
m_descriptor_writes.data());
} else {
const auto set = m_context.GetDescriptorHeap().Commit(pipeline.descriptor_set_layout);
for (auto& write: m_descriptor_writes) {
write.dstSet = set;
}
m_context.GetGraphics().device.updateDescriptorSets(
static_cast<uint32_t>(m_descriptor_writes.size()), m_descriptor_writes.data(), 0,
nullptr);
vk_buffer.bindDescriptorSets(pipeline_bind_point, pipeline.pipeline_layout, 0, 1, &set,
0, nullptr);
}
}
for (auto* prepared: prepared_bindings) {
prepared->committed = true;
}
prepared.committed = true;
}
} // namespace Libs::Graphics
@@ -2,6 +2,8 @@
#define EMULATOR_SRC_GRAPHICS_HOST_GPU_RENDERER_DESCRIPTORS_H_
#include "common/assert.h"
#include "graphics/host_gpu/renderer/cache/bufferCache.h"
#include "graphics/host_gpu/renderer/cache/textureCache.h"
#include "graphics/host_gpu/renderer/image/image.h"
#include "graphics/host_gpu/vulkanCommon.h"
#include "graphics/shader/recompiler/ir/ShaderIR.h"
@@ -10,9 +12,56 @@
#include <cstdint>
#include <cstring>
#include <type_traits>
#include <vector>
namespace Libs::Graphics {
namespace ShaderRecompiler::IR {
struct ResourceSnapshot;
}
struct BufferView {
vk::Buffer buffer = nullptr;
vk::DeviceSize offset = 0;
vk::DeviceSize range = VK_WHOLE_SIZE;
};
struct TextureBinding {
ImageId image_id;
vk::ImageView image_view = nullptr;
TextureCache::ImageDesc desc;
vk::ImageLayout layout = vk::ImageLayout::eUndefined;
std::vector<vk::ImageView> mip_views;
};
struct NativeDescriptors {
std::vector<BufferView> buffers;
std::vector<TextureBinding> images;
std::vector<vk::Sampler> samplers;
std::vector<BufferView> addresses;
BufferView gds;
BufferView flattened_srt;
BufferView user_data;
};
struct PreparedBindings {
const ShaderRecompiler::IR::Program* program = nullptr;
const ShaderRecompiler::IR::ResourceSnapshot* snapshot = nullptr;
NativeDescriptors resources;
std::vector<BufferId> buffer_ids;
std::vector<BufferId> address_ids;
std::vector<uint32_t> flattened_srt;
std::vector<uint32_t> user_data;
bool committed = false;
};
[[nodiscard]] vk::DescriptorType
NativeDescriptorType(ShaderRecompiler::IR::DescriptorBindingKind kind);
[[nodiscard]] uint32_t
NativeDescriptorCount(const ShaderRecompiler::IR::DescriptorBinding& binding);
[[nodiscard]] vk::DescriptorImageInfo MakeImageInfo(const TextureBinding& texture,
uint32_t element = 0);
template <typename T>
[[nodiscard]] T DecodeNativeDescriptor(const ShaderRecompiler::IR::DescriptorValue& value) {
static_assert(std::is_trivially_copyable_v<T>);
@@ -34,11 +83,9 @@ ResolveTargetTextureView(const ShaderRecompiler::IR::ImageResource& resource,
Prospero::ImageType type, uint32_t base_layer, uint32_t image_layers);
[[nodiscard]] bool IsSupportedDepthTargetDescriptor(const ShaderTextureResource& descriptor,
const Image& image,
bool r128 = false);
const Image& image, bool r128 = false);
[[nodiscard]] bool IsSupportedDepthTextureEncoding(const ShaderTextureResource& descriptor,
const Image& image,
bool r128 = false);
const Image& image, bool r128 = false);
[[nodiscard]] bool
IsSupportedSampledVideoOutView(const ShaderRecompiler::IR::ImageResource& resource,
const ShaderTextureResource& descriptor, const Image& image);
@@ -42,6 +42,7 @@ PipelineCache::~PipelineCache() {
(void)key;
m_graphics.device.destroyPipeline(pipeline->pipeline, nullptr);
m_graphics.device.destroyPipelineLayout(pipeline->pipeline_layout, nullptr);
m_graphics.device.destroyDescriptorSetLayout(pipeline->descriptor_set_layout, nullptr);
}
};
destroy(m_graphics_pipelines);
@@ -210,9 +211,9 @@ PipelineCache::GraphicsPipeline& PipelineCache::CreateGraphicsPipeline(
auto cached = std::make_unique<GraphicsPipeline>(p);
LogPipelineTrace("CreatePipelineInternal begin", vs_id.hash0, vs_id.crc32, ps_id.hash0,
ps_id.crc32);
CreatePipelineInternal(m_graphics, m_descriptor_cache, *cached, rendering, vs_input_info,
vs_spirv, ps_input_info, ps_spirv, static_params, vs_id.hash0,
vs_id.crc32, ps_id.hash0, ps_id.crc32, ps_active);
CreatePipelineInternal(m_graphics, *cached, rendering, vs_input_info, vs_spirv, ps_input_info,
ps_spirv, static_params, vs_id.hash0, vs_id.crc32, ps_id.hash0,
ps_id.crc32, ps_active);
LogPipelineTrace("CreatePipelineInternal done", vs_id.hash0, vs_id.crc32, ps_id.hash0,
ps_id.crc32);
@@ -252,7 +253,7 @@ PipelineCache::CreateComputePipeline(ShaderComputeInputInfo& input_info,
}
auto cached = std::make_unique<ComputePipeline>(p);
CreatePipelineInternal(m_graphics, m_descriptor_cache, *cached, input_info, cs_spirv);
CreatePipelineInternal(m_graphics, *cached, input_info, cs_spirv);
EXIT_NOT_IMPLEMENTED(cached->pipeline == nullptr);
EXIT_NOT_IMPLEMENTED(cached->pipeline_layout == nullptr);
@@ -21,7 +21,6 @@ struct GraphicContext;
struct RenderColorInfo;
struct RenderDepthInfo;
class RenderCommandBuffer;
class DescriptorCache;
namespace HW {
class Context;
@@ -98,16 +97,17 @@ struct PipelineRenderingState {
class PipelineCache {
public:
PipelineCache(GraphicContext& graphics, DescriptorCache& descriptor_cache)
: m_graphics(graphics), m_descriptor_cache(descriptor_cache) {
explicit PipelineCache(GraphicContext& graphics): m_graphics(graphics) {
EXIT_NOT_IMPLEMENTED(!Common::Thread::IsMainThread());
}
~PipelineCache();
KYTY_CLASS_NO_COPY(PipelineCache);
struct Pipeline {
vk::PipelineLayout pipeline_layout = nullptr;
vk::Pipeline pipeline = nullptr;
vk::PipelineLayout pipeline_layout = nullptr;
vk::Pipeline pipeline = nullptr;
vk::DescriptorSetLayout descriptor_set_layout = nullptr;
bool uses_push_descriptors = false;
};
struct GraphicsPipeline: Pipeline {
@@ -201,8 +201,7 @@ private:
}
};
GraphicContext& m_graphics;
DescriptorCache& m_descriptor_cache;
GraphicContext& m_graphics;
std::unordered_map<GraphicsPipelineKey, std::unique_ptr<GraphicsPipeline>,
GraphicsPipelineKeyHash>
m_graphics_pipelines;
@@ -214,16 +213,14 @@ private:
void LogPipelineTrace(const char* phase, uint32_t vs_hash0, uint32_t vs_crc32, uint32_t ps_hash0,
uint32_t ps_crc32);
void CreatePipelineInternal(
GraphicContext& graphics, DescriptorCache& descriptor_cache,
PipelineCache::GraphicsPipeline& pipeline, const PipelineRenderingState& rendering,
const ShaderVertexInputInfo& vs_input_info, std::span<const uint32_t> vs_shader,
const ShaderPixelInputInfo* ps_input_info, std::span<const uint32_t> ps_shader,
const PipelineStaticParameters& static_params, uint32_t vs_hash0, uint32_t vs_crc32,
uint32_t ps_hash0, uint32_t ps_crc32, bool ps_active);
void CreatePipelineInternal(GraphicContext& graphics, DescriptorCache& descriptor_cache,
PipelineCache::ComputePipeline& pipeline,
const ShaderComputeInputInfo& input_info,
std::span<const uint32_t> cs_shader);
GraphicContext& graphics, PipelineCache::GraphicsPipeline& pipeline,
const PipelineRenderingState& rendering, const ShaderVertexInputInfo& vs_input_info,
std::span<const uint32_t> vs_shader, const ShaderPixelInputInfo* ps_input_info,
std::span<const uint32_t> ps_shader, const PipelineStaticParameters& static_params,
uint32_t vs_hash0, uint32_t vs_crc32, uint32_t ps_hash0, uint32_t ps_crc32, bool ps_active);
void CreatePipelineInternal(GraphicContext& graphics, PipelineCache::ComputePipeline& pipeline,
const ShaderComputeInputInfo& input_info,
std::span<const uint32_t> cs_shader);
} // namespace Libs::Graphics
@@ -6,7 +6,7 @@
#include "graphics/guest_gpu/gpu_defs.h"
#include "graphics/host_gpu/graphicContext.h"
#include "graphics/host_gpu/renderer/debug.h"
#include "graphics/host_gpu/renderer/pipeline/descriptorCache.h"
#include "graphics/host_gpu/renderer/pipeline/descriptors.h"
#include "graphics/host_gpu/renderer/pipeline/pipelineCache.h"
#include "graphics/host_gpu/renderer/render.h"
#include "graphics/host_gpu/renderer/renderContext.h"
@@ -19,6 +19,7 @@
#include <algorithm>
#include <limits>
#include <span>
#include <vector>
namespace Libs::Graphics {
@@ -388,40 +389,42 @@ static vk::BlendOp GetBlendOp(uint32_t op) {
return vk::BlendOp::eAdd;
}
static void CreateLayout(DescriptorCache& descriptor_cache,
std::span<vk::DescriptorSetLayout> set_layouts, uint32_t& set_layouts_num,
std::span<vk::PushConstantRange> push_constant_info,
uint32_t& push_constant_info_num,
const ShaderRecompiler::IR::Program& program,
vk::ShaderStageFlags vk_stage, DescriptorCache::Stage stage) {
const auto& bindings = program.bindings;
const bool need_descriptor = !bindings.descriptors.empty();
if (bindings.push_constant_size != 0) {
auto index = push_constant_info_num;
push_constant_info[index].stageFlags = vk_stage;
push_constant_info[index].offset = bindings.push_constant_offset;
push_constant_info[index].size = bindings.push_constant_size;
push_constant_info_num++;
static void AddLayoutBindings(std::vector<vk::DescriptorSetLayoutBinding>& descriptor_bindings,
const ShaderRecompiler::IR::Program& program,
vk::ShaderStageFlagBits stage) {
for (const auto& binding: program.bindings.descriptors) {
descriptor_bindings.push_back(
{ShaderRecompiler::IR::NativeBinding(program.stage, binding.kind),
NativeDescriptorType(binding.kind), NativeDescriptorCount(binding), stage, nullptr});
}
}
if (need_descriptor) {
EXIT_IF(bindings.descriptor_set != set_layouts_num);
set_layouts[set_layouts_num] = descriptor_cache.GetDescriptorSetLayout(stage, program);
set_layouts_num++;
static void CreateDescriptorLayout(GraphicContext& graphics, PipelineCache::Pipeline& pipeline,
std::span<const vk::DescriptorSetLayoutBinding> bindings) {
uint32_t descriptor_count = 0;
for (const auto& binding: bindings) {
descriptor_count += binding.descriptorCount;
}
pipeline.uses_push_descriptors = descriptor_count <= graphics.max_push_descriptors;
vk::DescriptorSetLayoutCreateInfo create {};
create.sType = vk::StructureType::eDescriptorSetLayoutCreateInfo;
create.flags = pipeline.uses_push_descriptors
? vk::DescriptorSetLayoutCreateFlagBits::ePushDescriptorKHR
: vk::DescriptorSetLayoutCreateFlags {};
create.bindingCount = static_cast<uint32_t>(bindings.size());
create.pBindings = bindings.data();
EXIT_IF(graphics.device.createDescriptorSetLayout(
&create, nullptr, &pipeline.descriptor_set_layout) != vk::Result::eSuccess);
}
// NOLINTNEXTLINE(readability-function-cognitive-complexity)
void CreatePipelineInternal(
GraphicContext& graphics, DescriptorCache& descriptor_cache,
PipelineCache::GraphicsPipeline& pipeline, const PipelineRenderingState& rendering,
const ShaderVertexInputInfo& vs_input_info, std::span<const uint32_t> vs_shader,
const ShaderPixelInputInfo* ps_input_info, std::span<const uint32_t> ps_shader,
const PipelineStaticParameters& static_params, uint32_t vs_hash0, uint32_t vs_crc32,
uint32_t ps_hash0, uint32_t ps_crc32, bool ps_active) {
GraphicContext& graphics, PipelineCache::GraphicsPipeline& pipeline,
const PipelineRenderingState& rendering, const ShaderVertexInputInfo& vs_input_info,
std::span<const uint32_t> vs_shader, const ShaderPixelInputInfo* ps_input_info,
std::span<const uint32_t> ps_shader, const PipelineStaticParameters& static_params,
uint32_t vs_hash0, uint32_t vs_crc32, uint32_t ps_hash0, uint32_t ps_crc32, bool ps_active) {
EXIT_IF(ps_active && ps_input_info == nullptr);
const bool rect_list = static_params.topology == vk::PrimitiveTopology::ePatchList;
@@ -823,40 +826,36 @@ void CreatePipelineInternal(
color_blending.blendConstants[2] = static_params.blend_color_blue;
color_blending.blendConstants[3] = static_params.blend_color_alpha;
vk::DescriptorSetLayout set_layouts[2] = {};
uint32_t set_layouts_num = 0;
vk::PushConstantRange push_constant_info[2];
uint32_t push_constant_info_num = 0;
EXIT_IF(!vs_input_info.stage);
CreateLayout(descriptor_cache, set_layouts, set_layouts_num, push_constant_info,
push_constant_info_num, *vs_input_info.stage.program,
vk::ShaderStageFlagBits::eVertex, DescriptorCache::Stage::Vertex);
std::vector<vk::DescriptorSetLayoutBinding> descriptor_bindings;
AddLayoutBindings(descriptor_bindings, *vs_input_info.stage.program,
vk::ShaderStageFlagBits::eVertex);
if (ps_active) {
EXIT_IF(!ps_input_info->stage);
CreateLayout(descriptor_cache, set_layouts, set_layouts_num, push_constant_info,
push_constant_info_num, *ps_input_info->stage.program,
vk::ShaderStageFlagBits::eFragment, DescriptorCache::Stage::Pixel);
AddLayoutBindings(descriptor_bindings, *ps_input_info->stage.program,
vk::ShaderStageFlagBits::eFragment);
}
CreateDescriptorLayout(graphics, pipeline, descriptor_bindings);
constexpr auto GraphicsStages =
vk::ShaderStageFlagBits::eVertex | vk::ShaderStageFlagBits::eFragment;
const vk::PushConstantRange push_constants {
GraphicsStages, 0, ShaderRecompiler::IR::NativePushConstantSize};
vk::PipelineLayoutCreateInfo pipeline_layout_info {};
pipeline_layout_info.sType = vk::StructureType::ePipelineLayoutCreateInfo;
pipeline_layout_info.pNext = nullptr;
pipeline_layout_info.flags = {};
pipeline_layout_info.setLayoutCount = set_layouts_num;
pipeline_layout_info.pSetLayouts = (set_layouts_num > 0 ? set_layouts : nullptr);
pipeline_layout_info.pushConstantRangeCount = push_constant_info_num;
pipeline_layout_info.pPushConstantRanges =
push_constant_info_num > 0 ? push_constant_info : nullptr;
pipeline_layout_info.setLayoutCount = 1;
pipeline_layout_info.pSetLayouts = &pipeline.descriptor_set_layout;
pipeline_layout_info.pushConstantRangeCount = 1;
pipeline_layout_info.pPushConstantRanges = &push_constants;
EXIT_IF(pipeline.pipeline_layout != nullptr);
if (graphics_debug_dump_enabled()) {
LOGF("PipelineTrace: vkCreatePipelineLayout begin VS=0x%08" PRIx32 "/0x%08" PRIx32
" PS=0x%08" PRIx32 "/0x%08" PRIx32 " set_layouts=%" PRIu32 " push_constants=%" PRIu32
"\n",
vs_hash0, vs_crc32, ps_hash0, ps_crc32, set_layouts_num, push_constant_info_num);
" PS=0x%08" PRIx32 "/0x%08" PRIx32 " set_layouts=1 push_constants=%" PRIu32 "\n",
vs_hash0, vs_crc32, ps_hash0, ps_crc32, 1u);
}
result = graphics.device.createPipelineLayout(&pipeline_layout_info, nullptr,
&pipeline.pipeline_layout);
@@ -988,10 +987,9 @@ void CreatePipelineInternal(
}
// NOLINTNEXTLINE(readability-function-cognitive-complexity)
void CreatePipelineInternal(GraphicContext& graphics, DescriptorCache& descriptor_cache,
PipelineCache::ComputePipeline& pipeline,
const ShaderComputeInputInfo& input_info,
std::span<const uint32_t> cs_shader) {
void CreatePipelineInternal(GraphicContext& graphics, PipelineCache::ComputePipeline& pipeline,
const ShaderComputeInputInfo& input_info,
std::span<const uint32_t> cs_shader) {
vk::ShaderModule comp_shader_module = nullptr;
vk::ShaderModuleCreateInfo create_info {};
@@ -1030,30 +1028,26 @@ void CreatePipelineInternal(GraphicContext& graphics, DescriptorCache& descripto
comp_shader_stage_info.pNext = &comp_subgroup_size;
}
vk::DescriptorSetLayout set_layouts[1] = {};
uint32_t set_layouts_num = 0;
vk::PushConstantRange push_constant_info[1];
uint32_t push_constant_info_num = 0;
CreateLayout(descriptor_cache, set_layouts, set_layouts_num, push_constant_info,
push_constant_info_num, *input_info.stage.program,
vk::ShaderStageFlagBits::eCompute, DescriptorCache::Stage::Compute);
std::vector<vk::DescriptorSetLayoutBinding> descriptor_bindings;
AddLayoutBindings(descriptor_bindings, *input_info.stage.program,
vk::ShaderStageFlagBits::eCompute);
CreateDescriptorLayout(graphics, pipeline, descriptor_bindings);
const vk::PushConstantRange push_constants {vk::ShaderStageFlagBits::eCompute, 0,
ShaderRecompiler::IR::NativePushConstantSize};
vk::PipelineLayoutCreateInfo pipeline_layout_info {};
pipeline_layout_info.sType = vk::StructureType::ePipelineLayoutCreateInfo;
pipeline_layout_info.pNext = nullptr;
pipeline_layout_info.flags = {};
pipeline_layout_info.setLayoutCount = set_layouts_num;
pipeline_layout_info.pSetLayouts = (set_layouts_num > 0 ? set_layouts : nullptr);
pipeline_layout_info.pushConstantRangeCount = push_constant_info_num;
pipeline_layout_info.pPushConstantRanges =
push_constant_info_num > 0 ? push_constant_info : nullptr;
pipeline_layout_info.setLayoutCount = 1;
pipeline_layout_info.pSetLayouts = &pipeline.descriptor_set_layout;
pipeline_layout_info.pushConstantRangeCount = 1;
pipeline_layout_info.pPushConstantRanges = &push_constants;
EXIT_IF(pipeline.pipeline_layout != nullptr);
LOGF("PipelineTrace: vkCreatePipelineLayout CS begin set_layouts=%u push_constants=%u\n",
set_layouts_num, push_constant_info_num);
LOGF("PipelineTrace: vkCreatePipelineLayout CS begin set_layouts=1 push_constants=%u\n",
1u);
result = graphics.device.createPipelineLayout(&pipeline_layout_info, nullptr,
&pipeline.pipeline_layout);
LOGF("PipelineTrace: vkCreatePipelineLayout CS done result=%s layout=%p\n",
+38 -35
View File
@@ -4,11 +4,14 @@
#include "common/abi.h"
#include "common/assert.h"
#include "common/common.h"
#include "graphics/host_gpu/renderer/pipeline/descriptorCache.h"
#include "graphics/host_gpu/renderer/pipeline/descriptors.h"
#include "graphics/host_gpu/renderer/pipeline/pipelineCache.h"
#include "graphics/host_gpu/renderer/renderTarget.h"
#include "graphics/host_gpu/vulkanCommon.h"
#include <array>
#include <optional>
#include <span>
#include <vector>
namespace Libs::Graphics {
@@ -23,7 +26,6 @@ struct GraphicContext;
struct ShaderBufferResource;
struct ShaderComputeInputInfo;
struct CommandSlot;
struct VulkanDescriptorSet;
struct RenderDepthInfo;
struct RenderColorInfo;
struct DrawCallInfo;
@@ -92,7 +94,6 @@ public:
void WaitForFenceOnly();
void WaitForFence();
void WaitForFenceAndReset();
void RecycleDescriptorAfterFence(VulkanDescriptorSet& set);
[[nodiscard]] vk::CommandBuffer Handle() const;
[[nodiscard]] GraphicContext& GetGraphics() const noexcept { return m_graphics; }
@@ -102,25 +103,23 @@ public:
private:
void Release();
void FinalizeFence(bool reset_recording);
void RecycleDescriptorsAfterFence();
RenderContext& m_context;
CommandScheduler& m_scheduler;
GraphicContext& m_graphics;
CommandSlot* m_slot = nullptr;
bool m_execute = false;
bool m_fence_waited = false;
uint64_t m_submit_seq = 0;
uint32_t m_debug_op = 0;
uint64_t m_debug_submit_id = 0;
uint32_t m_debug_arg0 = 0;
uint32_t m_debug_arg1 = 0;
uint32_t m_debug_arg2 = 0;
uint32_t m_debug_arg3 = 0;
uint64_t m_debug_arg4 = 0;
std::vector<VulkanDescriptorSet*> m_descriptor_sets_after_fence;
mutable RenderState m_render_state;
mutable bool m_rendering = false;
RenderContext& m_context;
CommandScheduler& m_scheduler;
GraphicContext& m_graphics;
CommandSlot* m_slot = nullptr;
bool m_execute = false;
bool m_fence_waited = false;
uint64_t m_submit_seq = 0;
uint32_t m_debug_op = 0;
uint64_t m_debug_submit_id = 0;
uint32_t m_debug_arg0 = 0;
uint32_t m_debug_arg1 = 0;
uint32_t m_debug_arg2 = 0;
uint32_t m_debug_arg3 = 0;
uint64_t m_debug_arg4 = 0;
mutable RenderState m_render_state;
mutable bool m_rendering = false;
};
class RenderCommandBuffer final: public CommandBuffer {
@@ -159,24 +158,22 @@ public:
void DispatchDirect(uint64_t submit_id, RenderCommandBuffer& buffer, uint32_t thread_group_x,
uint32_t thread_group_y, uint32_t thread_group_z, uint32_t mode);
[[nodiscard]] DescriptorCache::PreparedBindings
PrepareBindings(const ShaderStageRuntime& runtime, vk::ShaderStageFlags shader_stage,
DescriptorCache::Stage stage);
void FindBuffers(DescriptorCache::PreparedBindings& bindings);
void RebindBuffers(DescriptorCache::PreparedBindings& bindings);
void RebindImages(DescriptorCache::PreparedBindings& bindings);
[[nodiscard]] PreparedBindings PrepareBindings(const ShaderStageRuntime& runtime);
void FindBuffers(PreparedBindings& bindings);
void RebindBuffers(PreparedBindings& bindings);
void RebindImages(PreparedBindings& bindings);
void CommitBindings(CommandBuffer& buffer, vk::PipelineBindPoint pipeline_bind_point,
vk::PipelineLayout layout, DescriptorCache::PreparedBindings& bindings);
const PipelineCache::Pipeline& pipeline,
std::span<PreparedBindings* const> bindings);
private:
struct GraphicsBindings {
DescriptorCache::PreparedBindings vertex;
std::optional<DescriptorCache::PreparedBindings> pixel;
PreparedBindings vertex;
std::optional<PreparedBindings> pixel;
};
[[nodiscard]] DescriptorCache::TextureBinding
ResolveTexture(const ShaderRecompiler::IR::ImageResource& resource,
const ShaderRecompiler::IR::DescriptorValue& value);
[[nodiscard]] TextureBinding ResolveTexture(const ShaderRecompiler::IR::ImageResource& resource,
const ShaderRecompiler::IR::DescriptorValue& value);
[[nodiscard]] GraphicsBindings PrepareGraphicsBindings(const ShaderStageRuntime& vertex,
const ShaderStageRuntime& pixel,
bool pixel_active);
@@ -207,8 +204,14 @@ private:
[[nodiscard]] bool TryConsumeComputeMetaClear(const ShaderComputeInputInfo& input,
const RenderCommandBuffer& buffer);
RenderContext& m_context;
std::vector<ImageId> m_bound_images;
RenderContext& m_context;
std::vector<ImageId> m_bound_images;
std::vector<vk::DescriptorBufferInfo> m_descriptor_buffers;
std::vector<vk::DescriptorImageInfo> m_descriptor_images;
std::vector<vk::WriteDescriptorSet> m_descriptor_writes;
std::vector<uint32_t> m_image_occurrences;
std::array<uint32_t, ShaderRecompiler::IR::NativePushConstantSize / sizeof(uint32_t)>
m_push_constants {};
friend struct RenderExecutorTestAccess;
};
@@ -11,7 +11,6 @@
#include "graphics/guest_gpu/hardwareContext.h"
#include "graphics/host_gpu/graphicContext.h"
#include "graphics/host_gpu/renderer/image/imageInfo.h"
#include "graphics/host_gpu/renderer/pipeline/descriptorCache.h"
#include "graphics/host_gpu/renderer/pipeline/descriptors.h"
#include "graphics/host_gpu/renderer/pipeline/pipelineCache.h"
#include "graphics/host_gpu/renderer/pipeline/shaderResourceBarrier.h"
@@ -334,14 +333,15 @@ void RenderExecutor::DispatchDirect(uint64_t submit_id, RenderCommandBuffer& buf
buffer.EndRendering();
auto& pipeline =
m_context.GetPipelineCache().CreateComputePipeline(input_info, sh_ctx.GetCs(), cs_shader);
auto bindings = PrepareBindings(input_info.stage, vk::ShaderStageFlagBits::eCompute,
DescriptorCache::Stage::Compute);
auto bindings = PrepareBindings(input_info.stage);
FindBuffers(bindings);
RebindBuffers(bindings);
RebindImages(bindings);
auto vk_buffer = buffer.Handle();
CommitBindings(buffer, vk::PipelineBindPoint::eCompute, pipeline.pipeline_layout, bindings);
auto vk_buffer = buffer.Handle();
PreparedBindings* descriptor_stage = &bindings;
CommitBindings(buffer, vk::PipelineBindPoint::eCompute, pipeline,
std::span {&descriptor_stage, 1u});
bool has_storage_writes = HasShaderBufferWrites(input_info.stage);
has_storage_writes =
std::any_of(program.info.images.begin(), program.info.images.end(),
@@ -13,8 +13,9 @@ namespace Libs::Graphics {
RenderContext::RenderContext(GraphicContext& graphics)
: m_graphics(graphics), m_render_executor(*this), m_command_scheduler(*this, graphics),
m_descriptor_cache(graphics), m_pipeline_cache(graphics, m_descriptor_cache),
m_sampler_cache(graphics), m_gpu_resources(graphics, m_command_scheduler) {
m_descriptor_heap(graphics, m_command_scheduler.GetMasterSemaphore()),
m_pipeline_cache(graphics), m_sampler_cache(graphics),
m_gpu_resources(graphics, m_command_scheduler) {
EXIT_NOT_IMPLEMENTED(!Common::Thread::IsMainThread());
}
@@ -10,7 +10,7 @@
#include "graphics/host_gpu/renderer/cache/samplerCache.h"
#include "graphics/host_gpu/renderer/cache/textureCache.h"
#include "graphics/host_gpu/renderer/commandScheduler.h"
#include "graphics/host_gpu/renderer/pipeline/descriptorCache.h"
#include "graphics/host_gpu/renderer/pipeline/descriptorHeap.h"
#include "graphics/host_gpu/renderer/pipeline/pipelineCache.h"
#include "kernel/eventQueue.h"
@@ -41,7 +41,7 @@ public:
Common::Mutex& GetMutex() { return m_mutex; }
CommandScheduler& GetCommandScheduler() { return m_command_scheduler; }
PipelineCache& GetPipelineCache() { return m_pipeline_cache; }
DescriptorCache& GetDescriptorCache() { return m_descriptor_cache; }
DescriptorHeap& GetDescriptorHeap() { return m_descriptor_heap; }
SamplerCache& GetSamplerCache() { return m_sampler_cache; }
GpuResourceManager& GetGpuResources() { return m_gpu_resources; }
BufferCache& GetBufferCache() { return m_gpu_resources.GetBufferCache(); }
@@ -63,7 +63,7 @@ private:
Common::Mutex m_mutex;
RenderExecutor m_render_executor;
CommandScheduler m_command_scheduler;
DescriptorCache m_descriptor_cache;
DescriptorHeap m_descriptor_heap;
PipelineCache m_pipeline_cache;
SamplerCache m_sampler_cache;
GpuResourceManager m_gpu_resources;
+20 -17
View File
@@ -15,7 +15,6 @@
#include "graphics/host_gpu/renderer/colorRenderTarget.h"
#include "graphics/host_gpu/renderer/debug.h"
#include "graphics/host_gpu/renderer/depthRenderTarget.h"
#include "graphics/host_gpu/renderer/pipeline/descriptorCache.h"
#include "graphics/host_gpu/renderer/pipeline/pipelineCache.h"
#include "graphics/host_gpu/renderer/pipeline/shaderResourceBarrier.h"
#include "graphics/host_gpu/renderer/render.h"
@@ -798,9 +797,9 @@ static uint64_t VertexBufferDescriptorSize(const ShaderVertexInputBuffer& buffer
}
struct VertexBufferRange {
uint64_t base_address = 0;
uint64_t requested_end = 0;
uint64_t acquired_end = 0;
uint64_t base_address = 0;
uint64_t requested_end = 0;
uint64_t acquired_end = 0;
std::pair<Buffer*, uint64_t> binding;
[[nodiscard]] uint64_t RequestedSize() const { return requested_end - base_address; }
@@ -862,15 +861,14 @@ static PreparedVertexBuffers AcquireVertexBuffers(RenderCommandBuffer& b
Libs::LibKernel::Memory::ClampRangeSize(range.base_address, range.RequestedSize());
range.acquired_end = range.base_address + size;
range.binding = cache.ObtainBuffer(range.base_address, size, false);
SetVulkanObjectNameF(buffer.GetContext().GetGraphics().device,
range.binding.first->Handle(),
"Kyty.VertexBufferRange[guest=0x{:016x} size=0x{:x}]",
range.base_address, size);
SetVulkanObjectNameF(
buffer.GetContext().GetGraphics().device, range.binding.first->Handle(),
"Kyty.VertexBufferRange[guest=0x{:016x} size=0x{:x}]", range.base_address, size);
}
// Rebuild slot bindings, offsetting non-empty slots into their acquired merged range.
PreparedVertexBuffers prepared;
prepared.count = static_cast<uint32_t>(vs_input_info.buffers_num);
prepared.count = static_cast<uint32_t>(vs_input_info.buffers_num);
vk::Buffer null_buffer = nullptr;
for (int i = 0; i < vs_input_info.buffers_num; i++) {
const auto& vertex = vs_input_info.buffers[i];
@@ -1062,7 +1060,8 @@ static void RefreshShaders(RenderCommandBuffer& buffer, const DrawCallInfo& draw
LogDrawPhase(draw.name, "ShaderCompileInfoPS");
}
if (!ShaderCompileInfoPS(pixel_shader_info, shader_regs, state.vs_input_info,
target_export_mapping, state.ps_input_info, state.ps_shader)) {
target_export_mapping,
state.ps_input_info, state.ps_shader)) {
EXIT("ShaderCompileInfoPS failed for draw %s\n", draw.name);
}
}
@@ -1091,8 +1090,8 @@ static PreparedIndexBuffer PrepareIndexBuffer(RenderCommandBuffer& buffe
prepared.offset = stream.Copy(source.host_data, source.size, 16);
prepared.buffer = stream.Handle();
} else {
auto [buffer_ptr, offset] = buffer.GetContext().GetBufferCache().ObtainBuffer(
source.address, source.size, false);
auto [buffer_ptr, offset] =
buffer.GetContext().GetBufferCache().ObtainBuffer(source.address, source.size, false);
prepared.buffer = buffer_ptr->Handle();
prepared.offset = offset;
}
@@ -1108,7 +1107,8 @@ static PreparedIndexBuffer PrepareIndexBuffer(RenderCommandBuffer& buffe
return prepared;
}
static void CommitVertexBuffers(vk::CommandBuffer vk_buffer, const PreparedVertexBuffers& prepared) {
static void CommitVertexBuffers(vk::CommandBuffer vk_buffer,
const PreparedVertexBuffers& prepared) {
for (uint32_t i = 0; i < prepared.count; i++) {
EXIT_IF(prepared.buffers[i] == nullptr);
}
@@ -1230,15 +1230,18 @@ void RenderExecutor::ExecutePreparedDraw(uint64_t submit_id, RenderCommandBuffer
SetDrawDebugPhase(buffer, submit_id, draw, 0x200u);
}
CommitVertexBuffers(vk_buffer, vertex_bindings);
CommitBindings(buffer, vk::PipelineBindPoint::eGraphics, pipeline.pipeline_layout,
bindings.vertex);
if (bindings.pixel.has_value()) {
if (set_auto_debug) {
SetDrawDebugPhase(buffer, submit_id, draw, 0x300u);
}
CommitBindings(buffer, vk::PipelineBindPoint::eGraphics, pipeline.pipeline_layout,
*bindings.pixel);
}
std::array<PreparedBindings*, 2> descriptor_stages {&bindings.vertex, nullptr};
const size_t descriptor_stage_count = bindings.pixel.has_value() ? 2u : 1u;
if (bindings.pixel) {
descriptor_stages[1] = &*bindings.pixel;
}
CommitBindings(buffer, vk::PipelineBindPoint::eGraphics, pipeline,
std::span {descriptor_stages.data(), descriptor_stage_count});
CommitIndexBuffer(vk_buffer, index_binding);
const auto dynamic_params =
+1
View File
@@ -30,6 +30,7 @@ struct VulkanInstance {
uint32_t subgroup_size = 0;
uint32_t min_subgroup_size = 0;
uint32_t max_subgroup_size = 0;
uint32_t max_push_descriptors = 0;
vk::ShaderStageFlags required_subgroup_size_stages = {};
Common::Mutex queue_mutex;
uint32_t queue_family = static_cast<uint32_t>(-1);
@@ -583,13 +583,13 @@ static vk::Device VulkanCreateDevice(vk::PhysicalDevice physical_device, const V
robustness2.nullDescriptor = supported_robustness2.nullDescriptor;
}
const bool subgroup_size_control_enabled =
graphics.subgroup_size_control_enabled && supported_features13.subgroupSizeControl == VK_TRUE;
const bool subgroup_size_control_enabled = graphics.subgroup_size_control_enabled &&
supported_features13.subgroupSizeControl == VK_TRUE;
auto features13 = required_features13;
features13.pNext = robustness2_ext_enabled ? static_cast<void*>(&robustness2)
: static_cast<void*>(&features12);
features13.robustImageAccess = supported_features13.robustImageAccess;
auto features13 = required_features13;
features13.pNext = robustness2_ext_enabled ? static_cast<void*>(&robustness2)
: static_cast<void*>(&features12);
features13.robustImageAccess = supported_features13.robustImageAccess;
features13.subgroupSizeControl = subgroup_size_control_enabled ? VK_TRUE : VK_FALSE;
LOGF("Vulkan robustness: robustImageAccess=%s robustImageAccess2=%s\n",
@@ -834,7 +834,7 @@ void WindowContext::CreateVulkan() {
EXIT("--spirv-debug-printf and --gpu-assisted-validation are mutually exclusive\n");
}
vk::ValidationFeatureEnableEXT enabled_features[3] = {};
vk::ValidationFeatureEnableEXT enabled_features[3] = {};
uint32_t enabled_features_count = 0;
#ifdef KYTY_ENABLE_BEST_PRACTICES
enabled_features[enabled_features_count++] = vk::ValidationFeatureEnableEXT::eBestPractices;
@@ -939,7 +939,15 @@ void WindowContext::CreateVulkan() {
EXIT("Could not find suitable device");
}
graphic_ctx.physical_device.getProperties(&graphic_ctx.physical_device_properties);
vk::PhysicalDevicePushDescriptorProperties push_descriptor_properties {};
vk::PhysicalDeviceProperties2 physical_device_properties {};
push_descriptor_properties.sType = vk::StructureType::ePhysicalDevicePushDescriptorProperties;
push_descriptor_properties.pNext = nullptr;
physical_device_properties.sType = vk::StructureType::ePhysicalDeviceProperties2;
physical_device_properties.pNext = &push_descriptor_properties;
graphic_ctx.physical_device.getProperties2(&physical_device_properties);
graphic_ctx.physical_device_properties = physical_device_properties.properties;
graphic_ctx.max_push_descriptors = push_descriptor_properties.maxPushDescriptors;
graphic_ctx.physical_device.getMemoryProperties(&graphic_ctx.physical_device_memory_properties);
const auto& device_properties = graphic_ctx.GetPhysicalDeviceProperties();
@@ -798,10 +798,7 @@ bool TryRecompile(std::span<const uint32_t> code, const CompileOptions& options,
if (!IR::CollectShaderInfo(ir, info_options, error)) {
return false;
}
IR::BindingLayoutOptions layout_options;
layout_options.descriptor_set = options.descriptor_set;
layout_options.push_constant_offset = options.push_constant_offset;
if (!IR::AllocateBindings(ir, layout_options, error)) {
if (!IR::AllocateBindings(ir, options.push_constant_offset, error)) {
return false;
}
if (!Spirv::AnalyzeProgramRequirements(ir, error)) {
@@ -21,8 +21,7 @@ struct CompileOptions {
uint64_t shader_hash = 0;
uint64_t shader_base = 0;
std::optional<uint64_t> flat_memory_base;
uint32_t descriptor_set = 0;
uint32_t push_constant_offset = 0;
uint32_t push_constant_offset = 0;
bool dump_ir = true;
bool early_dump = false;
const char* dump_label = nullptr;
@@ -89,18 +89,12 @@ bool ValidateNativeProgram(const IR::Program& program, std::string* error) {
}
std::array<bool, KindCount> seen {};
for (uint32_t i = 0; i < program.bindings.descriptors.size(); i++) {
const auto& binding = program.bindings.descriptors[i];
const auto kind = static_cast<size_t>(binding.kind);
for (const auto& binding: program.bindings.descriptors) {
const auto kind = static_cast<size_t>(binding.kind);
if (kind >= KindCount || seen[kind] || !present[kind] ||
binding.resources != expected[kind]) {
return Fail(error, "native descriptor groups do not match shader topology");
}
for (uint32_t j = 0; j < i; j++) {
if (program.bindings.descriptors[j].binding == binding.binding) {
return Fail(error, "native descriptor binding numbers are not unique");
}
}
seen[kind] = true;
}
for (size_t i = 0; i < KindCount; i++) {
@@ -109,7 +103,10 @@ bool ValidateNativeProgram(const IR::Program& program, std::string* error) {
}
}
const auto has_user_storage = present[static_cast<size_t>(Kind::UserData)];
if (program.bindings.memory_offset_dword != program.bindings.user_data_registers.size() ||
if (program.bindings.push_constant_offset % sizeof(uint32_t) != 0 ||
program.bindings.push_constant_offset + program.bindings.push_constant_size >
IR::NativePushConstantSize ||
program.bindings.memory_offset_dword != program.bindings.user_data_registers.size() ||
program.bindings.memory_offset_count !=
program.info.buffers.size() + program.info.addresses.size() ||
(!has_user_storage &&
@@ -667,8 +667,8 @@ void AddInputAnnotationsAndNames(EmitterState& state);
void AddOutputAnnotationsAndNames(EmitterState& state);
void DecorateDescriptor(EmitterState& state, uint32_t variable, const char* name, uint32_t set,
uint32_t binding);
void DecorateDescriptor(EmitterState& state, uint32_t variable, const char* name,
IR::DescriptorBindingKind kind);
void AddDescriptorAnnotationsAndNames(EmitterState& state);
@@ -510,23 +510,20 @@ void AddOutputAnnotationsAndNames(EmitterState& state) {
}
}
void DecorateDescriptor(EmitterState& state, uint32_t variable, const char* name, uint32_t set,
uint32_t binding) {
void DecorateDescriptor(EmitterState& state, uint32_t variable, const char* name,
IR::DescriptorBindingKind kind) {
if (variable == 0) {
return;
}
state.builder.AddName(variable, name);
state.builder.AddAnnotation({OpDecorate, variable, DecorationDescriptorSet, set});
state.builder.AddAnnotation({OpDecorate, variable, DecorationBinding, binding});
state.builder.AddAnnotation({OpDecorate, variable, DecorationDescriptorSet, 0});
state.builder.AddAnnotation(
{OpDecorate, variable, DecorationBinding, IR::NativeBinding(state.program.stage, kind)});
}
void AddDescriptorAnnotationsAndNames(EmitterState& state) {
const auto set = state.program.bindings.descriptor_set;
auto Decorate = [&](uint32_t variable, const char* name, IR::DescriptorBindingKind kind) {
const auto* binding = DescriptorBinding(state, kind);
if (binding != nullptr) {
DecorateDescriptor(state, variable, name, set, binding->binding);
}
auto Decorate = [&](uint32_t variable, const char* name, IR::DescriptorBindingKind kind) {
DecorateDescriptor(state, variable, name, kind);
};
if (state.storage_buffer_variable != 0) {
Decorate(state.storage_buffer_variable, "buffers", IR::DescriptorBindingKind::Buffers);
@@ -570,7 +567,7 @@ void AddDescriptorAnnotationsAndNames(EmitterState& state) {
"storage_atomic_2d_array",
"storage_atomic_3d"};
for (uint32_t i = 0; i < state.storage_image_variables.size(); i++) {
const auto view = static_cast<ImageViewKind>(i % StorageImageViewKindCount);
const auto view = static_cast<ImageViewKind>(i % StorageImageViewKindCount);
const auto image_class = static_cast<StorageImageClass>(i / StorageImageViewKindCount);
Decorate(state.storage_image_variables[i], StorageNames[i],
StorageBindingKind(image_class, view));
@@ -588,17 +585,13 @@ void AddDescriptorAnnotationsAndNames(EmitterState& state) {
}
void AddVsharpAnnotationsAndNames(EmitterState& state) {
const auto& bind = state.program.bindings;
if (state.push_constant_variable != 0) {
state.builder.AddName(PushConstantBlockType(state), "BufferResource");
state.builder.AddName(state.push_constant_variable, "vsharp");
}
if (state.vsharp_storage_variable != 0) {
const auto* storage = DescriptorBinding(state, IR::DescriptorBindingKind::UserData);
if (storage != nullptr) {
DecorateDescriptor(state, state.vsharp_storage_variable, "user_data",
bind.descriptor_set, storage->binding);
}
DecorateDescriptor(state, state.vsharp_storage_variable, "user_data",
IR::DescriptorBindingKind::UserData);
}
}
+11 -6
View File
@@ -338,6 +338,13 @@ enum class DescriptorBindingKind {
Count,
};
constexpr uint32_t NativePushConstantSize = 128;
[[nodiscard]] constexpr uint32_t NativeBinding(ShaderType stage, DescriptorBindingKind kind) {
return static_cast<uint32_t>(kind) +
(stage == ShaderType::Pixel ? static_cast<uint32_t>(DescriptorBindingKind::Count) : 0u);
}
[[nodiscard]] inline std::optional<DescriptorBindingKind>
DescriptorBindingForImage(const ImageResource& image) {
using Dimension = Decoder::ImageDimension;
@@ -412,19 +419,17 @@ DescriptorBindingForImage(const ImageResource& image) {
}
struct DescriptorBinding {
DescriptorBindingKind kind = DescriptorBindingKind::Buffers;
uint32_t binding = 0;
DescriptorBindingKind kind = DescriptorBindingKind::Buffers;
std::vector<uint32_t> resources;
bool operator==(const DescriptorBinding& other) const = default;
};
struct BindingLayout {
uint32_t descriptor_set = 0;
uint32_t push_constant_offset = 0;
uint32_t push_constant_size = 0;
uint32_t memory_offset_dword = 0;
uint32_t memory_offset_count = 0;
uint32_t push_constant_size = 0;
uint32_t memory_offset_dword = 0;
uint32_t memory_offset_count = 0;
std::vector<uint32_t> user_data_registers;
std::vector<DescriptorBinding> descriptors;
@@ -4,48 +4,18 @@
#include <algorithm>
#include <array>
#include <set>
#include <utility>
namespace Libs::Graphics::ShaderRecompiler::IR {
namespace {
constexpr uint32_t MaxPushConstantBytes = 128;
constexpr std::array ImageBindingKinds = {
DescriptorBindingKind::Sampled1D,
DescriptorBindingKind::Sampled1DArray,
DescriptorBindingKind::Sampled2D,
DescriptorBindingKind::Sampled2DArray,
DescriptorBindingKind::Sampled2DMsaa,
DescriptorBindingKind::Sampled2DMsaaArray,
DescriptorBindingKind::Sampled3D,
DescriptorBindingKind::SampledUint1D,
DescriptorBindingKind::SampledUint1DArray,
DescriptorBindingKind::SampledUint2D,
DescriptorBindingKind::SampledUint2DArray,
DescriptorBindingKind::SampledUint2DMsaa,
DescriptorBindingKind::SampledUint2DMsaaArray,
DescriptorBindingKind::SampledUint3D,
DescriptorBindingKind::Storage1D,
DescriptorBindingKind::Storage1DArray,
DescriptorBindingKind::Storage2D,
DescriptorBindingKind::Storage2DArray,
DescriptorBindingKind::Storage3D,
DescriptorBindingKind::StorageUint1D,
DescriptorBindingKind::StorageUint1DArray,
DescriptorBindingKind::StorageUint2D,
DescriptorBindingKind::StorageUint2DArray,
DescriptorBindingKind::StorageUint3D,
DescriptorBindingKind::StorageAtomic1D,
DescriptorBindingKind::StorageAtomic1DArray,
DescriptorBindingKind::StorageAtomic2D,
DescriptorBindingKind::StorageAtomic2DArray,
DescriptorBindingKind::StorageAtomic3D,
};
constexpr auto FirstImageBinding = DescriptorBindingKind::Sampled1D;
constexpr uint32_t ImageBindingCount =
static_cast<uint32_t>(DescriptorBindingKind::StorageAtomic3D) -
static_cast<uint32_t>(FirstImageBinding) + 1u;
bool CollectUserData(const Program& program, std::vector<uint32_t>& result) {
std::set<uint32_t> registers;
std::array<bool, NumScalarRegs> registers {};
for (const auto* block: program.values->blocks) {
for (const auto& inst: *block) {
if (inst.GetOpcode() != ValueOpcode::GetUserData || !inst.HasUses()) {
@@ -58,17 +28,21 @@ bool CollectUserData(const Program& program, std::vector<uint32_t>& result) {
if (index >= NumScalarRegs) {
return false;
}
registers.insert(index);
registers[index] = true;
}
}
result.clear();
for (uint32_t index = 0; index < registers.size(); index++) {
if (registers[index]) {
result.push_back(index);
}
}
result.assign(registers.begin(), registers.end());
return true;
}
void AddBinding(BindingLayout& layout, DescriptorBindingKind kind,
std::vector<uint32_t> resources = {}) {
layout.descriptors.push_back(
{kind, static_cast<uint32_t>(layout.descriptors.size()), std::move(resources)});
layout.descriptors.push_back({kind, std::move(resources)});
}
bool UsesGds(const Program& program, bool& uses_gds) {
@@ -94,7 +68,7 @@ bool UsesGds(const Program& program, bool& uses_gds) {
} // namespace
bool AllocateBindings(Program& program, const BindingLayoutOptions& options, std::string* error) {
bool AllocateBindings(Program& program, uint32_t push_constant_offset, std::string* error) {
if (!program.shader_info_complete || program.binding_layout_complete) {
if (error != nullptr) {
*error = !program.shader_info_complete ? "shader info is not ready"
@@ -102,28 +76,22 @@ bool AllocateBindings(Program& program, const BindingLayoutOptions& options, std
}
return false;
}
if (options.push_constant_offset > MaxPushConstantBytes) {
if (error != nullptr) {
*error = "push-constant offset exceeds the Vulkan minimum guarantee";
}
return false;
}
if (options.push_constant_offset % 4u != 0) {
if (error != nullptr) {
*error = "push-constant offset is not dword aligned";
}
return false;
}
if (program.values == nullptr) {
if (error != nullptr) {
*error = "typed value program is not available";
}
return false;
}
if (push_constant_offset > NativePushConstantSize ||
push_constant_offset % sizeof(uint32_t) != 0) {
if (error != nullptr) {
*error = "push-constant offset exceeds the Vulkan minimum guarantee or is unaligned";
}
return false;
}
BindingLayout next;
next.descriptor_set = options.descriptor_set;
next.push_constant_offset = options.push_constant_offset;
next.push_constant_offset = push_constant_offset;
if (!CollectUserData(program, next.user_data_registers)) {
if (error != nullptr) {
*error = "typed shader contains an invalid user-data register";
@@ -142,7 +110,7 @@ bool AllocateBindings(Program& program, const BindingLayoutOptions& options, std
AddBinding(next, DescriptorBindingKind::Buffers, std::move(resources));
}
std::array<std::vector<uint32_t>, ImageBindingKinds.size()> image_groups;
std::array<std::vector<uint32_t>, ImageBindingCount> image_groups;
for (uint32_t i = 0; i < program.info.images.size(); i++) {
const auto kind = DescriptorBindingForImage(program.info.images[i]);
if (!kind.has_value()) {
@@ -151,14 +119,14 @@ bool AllocateBindings(Program& program, const BindingLayoutOptions& options, std
}
return false;
}
const auto group = std::find(ImageBindingKinds.begin(), ImageBindingKinds.end(), *kind);
if (group == ImageBindingKinds.end()) {
const auto group = static_cast<uint32_t>(*kind) - static_cast<uint32_t>(FirstImageBinding);
if (group >= image_groups.size()) {
if (error != nullptr) {
*error = "shader info contains an unmapped image binding class";
}
return false;
}
auto& resources = image_groups[static_cast<size_t>(group - ImageBindingKinds.begin())];
auto& resources = image_groups[group];
const auto dynamic = program.info.images[i].mip_mode == ImageMipMode::DynamicStorage;
const auto count = dynamic ? program.info.images[i].mip_count : 1u;
if (count == 0u || (!dynamic && program.info.images[i].mip_count != 1u)) {
@@ -171,7 +139,10 @@ bool AllocateBindings(Program& program, const BindingLayoutOptions& options, std
}
for (uint32_t i = 0; i < image_groups.size(); i++) {
if (!image_groups[i].empty()) {
AddBinding(next, ImageBindingKinds[i], std::move(image_groups[i]));
AddBinding(
next,
static_cast<DescriptorBindingKind>(static_cast<uint32_t>(FirstImageBinding) + i),
std::move(image_groups[i]));
}
}
@@ -208,9 +179,8 @@ bool AllocateBindings(Program& program, const BindingLayoutOptions& options, std
AddBinding(next, DescriptorBindingKind::FlattenedSrt);
}
const auto available_push_dwords = (MaxPushConstantBytes - options.push_constant_offset) / 4u;
const auto push_limit = std::min(options.max_push_dwords, available_push_dwords);
if (next.ShaderDataDwords() <= push_limit) {
const auto push_dwords = (NativePushConstantSize - push_constant_offset) / sizeof(uint32_t);
if (next.ShaderDataDwords() <= push_dwords) {
next.push_constant_size = next.ShaderDataDwords() * sizeof(uint32_t);
} else {
AddBinding(next, DescriptorBindingKind::UserData);
@@ -5,13 +5,7 @@
namespace Libs::Graphics::ShaderRecompiler::IR {
struct BindingLayoutOptions {
uint32_t descriptor_set = 0;
uint32_t push_constant_offset = 0;
uint32_t max_push_dwords = 32;
};
bool AllocateBindings(Program& program, const BindingLayoutOptions& options, std::string* error);
bool AllocateBindings(Program& program, uint32_t push_constant_offset, std::string* error);
const DescriptorBinding* FindBinding(const BindingLayout& layout, DescriptorBindingKind kind);
+12 -28
View File
@@ -846,6 +846,11 @@ static void ShaderGetStaticInputInfoPS(
regs.ps_regs.data_addr);
}
ps_info.scratch_size_dwords = data.scratch_size_dwords;
ps_info.push_constant_offset =
vs_info.stage.program != nullptr
? vs_info.stage.program->bindings.push_constant_offset +
vs_info.stage.program->bindings.push_constant_size
: 0;
// SPI_PS_IN_CONTROL.NUM_INTERP occupies bits 5:0. Keep the remaining control
// flags in the hardware state and extract only the input count here.
@@ -873,15 +878,6 @@ static void ShaderGetStaticInputInfoPS(
ps_info.interpolator_settings[i] = sh.ps_interpolator_settings[i];
}
ps_info.descriptor_set =
vs_info.stage.program != nullptr && !vs_info.stage.program->bindings.descriptors.empty()
? 1
: 0;
ps_info.push_constant_offset = vs_info.stage.program != nullptr
? vs_info.stage.program->bindings.push_constant_offset +
vs_info.stage.program->bindings.push_constant_size
: 0;
for (int i = 0; i < 8; i++) {
ps_info.target_output_mode[i] = sh.target_output_mode[i];
ps_info.target_export_mapping[i] = sh.target_output_mode[i] != 0
@@ -1020,11 +1016,10 @@ static void LogShaderProgramCacheHit(const char* stage, uint64_t shader_hash, ui
static std::string ShaderDescribeSpecialization(const ShaderRecompiler::IR::Program& program) {
std::string ret = fmt::format(
"set={} push={} groups={} user={} buffers={} images={} samplers={} addresses={} srt={}",
program.bindings.descriptor_set, program.bindings.push_constant_size,
program.bindings.descriptors.size(), program.bindings.user_data_registers.size(),
program.info.buffers.size(), program.info.images.size(), program.info.samplers.size(),
program.info.addresses.size(),
"push={} groups={} user={} buffers={} images={} samplers={} addresses={} srt={}",
program.bindings.push_constant_size, program.bindings.descriptors.size(),
program.bindings.user_data_registers.size(), program.info.buffers.size(),
program.info.images.size(), program.info.samplers.size(), program.info.addresses.size(),
program.values != nullptr ? program.values->srt_reads.size() : 0u);
for (uint32_t i = 0; i < program.info.buffers.size(); i++) {
const auto& buffer = program.info.buffers[i];
@@ -1050,8 +1045,6 @@ static std::string ShaderDescribeSpecialization(const ShaderRecompiler::IR::Prog
static void ShaderAppendNativeSpecialization(std::vector<uint32_t>& ids,
const ShaderRecompiler::IR::Program& program) {
EXIT_IF(!program.binding_layout_complete);
ids.push_back(program.bindings.descriptor_set);
ids.push_back(program.bindings.push_constant_offset);
ids.push_back(program.bindings.push_constant_size);
ids.push_back(program.bindings.memory_offset_dword);
ids.push_back(program.bindings.memory_offset_count);
@@ -1061,7 +1054,6 @@ static void ShaderAppendNativeSpecialization(std::vector<uint32_t>&
ids.push_back(static_cast<uint32_t>(program.bindings.descriptors.size()));
for (const auto& binding: program.bindings.descriptors) {
ids.push_back(static_cast<uint32_t>(binding.kind));
ids.push_back(binding.binding);
ids.push_back(static_cast<uint32_t>(binding.resources.size()));
ids.insert(ids.end(), binding.resources.begin(), binding.resources.end());
}
@@ -1456,8 +1448,6 @@ bool ShaderCompileSpirvVS(const HW::VertexShaderInfo& regs, const HW::ShaderRegi
options.scratch_dwords = input_info.scratch_size_dwords;
options.user_data = regs.gs_user_sgpr.value;
options.read_specialization_memory = ReadShaderGuestMemory;
options.descriptor_set = 0;
options.push_constant_offset = 0;
options.input_info.vertex = &input_info;
options.dump_ir = ShaderRecompilerTextDumpEnabled();
options.early_dump = options.dump_ir;
@@ -1507,10 +1497,9 @@ bool ShaderCompileSpirvPS(const HW::PixelShaderInfo& regs, const HW::ShaderRegis
options.shader_base = shader_addr;
options.user_data_count = regs.ps_regs.rsrc2.user_sgpr;
options.scratch_dwords = input_info.scratch_size_dwords;
options.push_constant_offset = input_info.push_constant_offset;
options.user_data = regs.ps_user_sgpr.value;
options.read_specialization_memory = ReadShaderGuestMemory;
options.descriptor_set = input_info.descriptor_set;
options.push_constant_offset = input_info.push_constant_offset;
options.input_info.pixel = &input_info;
options.dump_ir = ShaderRecompilerTextDumpEnabled();
options.early_dump = options.dump_ir;
@@ -1560,8 +1549,6 @@ bool ShaderCompileSpirvCS(const HW::ComputeShaderInfo& regs, const HW::ShaderReg
options.scratch_dwords = input_info.scratch_size_dwords;
options.user_data = regs.cs_user_sgpr.value;
options.read_specialization_memory = ReadShaderGuestMemory;
options.descriptor_set = 0;
options.push_constant_offset = 0;
options.input_info.compute = &input_info;
options.wave_size = input_info.wave_size;
options.dump_ir = ShaderRecompilerTextDumpEnabled();
@@ -1666,7 +1653,6 @@ ShaderId ShaderGetIdPS(const HW::PixelShaderInfo& regs, const ShaderPixelInputIn
ret.hash0 = (regs.ps_regs.chksum >> 32u) & 0xffffffffu;
ret.crc32 = regs.ps_regs.chksum & 0xffffffffu;
ret.ids.push_back(input_info.descriptor_set);
ret.ids.push_back(input_info.push_constant_offset);
ret.ids.push_back(input_info.scratch_size_dwords);
ret.ids.push_back(input_info.input_num);
@@ -1678,6 +1664,7 @@ ShaderId ShaderGetIdPS(const HW::PixelShaderInfo& regs, const ShaderPixelInputIn
ret.ids.push_back(static_cast<uint32_t>(input_info.ps_front_face));
ret.ids.push_back(static_cast<uint32_t>(input_info.ps_no_perspective));
ret.ids.push_back(static_cast<uint32_t>(input_info.ps_pixel_kill_enable));
ret.ids.push_back(static_cast<uint32_t>(input_info.ps_depth_export_enable));
ret.ids.push_back(static_cast<uint32_t>(input_info.ps_sample_mask_export_enable));
ret.ids.push_back(static_cast<uint32_t>(input_info.ps_early_z));
ret.ids.push_back(static_cast<uint32_t>(input_info.ps_execute_on_noop));
@@ -1738,10 +1725,7 @@ ShaderId ShaderGetIdCS(const HW::ComputeShaderInfo& regs, const ShaderComputeInp
ret.ids.push_back(input_info.threads_num[i]);
ret.ids.push_back(static_cast<uint32_t>(input_info.group_id[i]));
}
ret.ids.push_back(static_cast<uint32_t>(input_info.dispatch_thread_dimensions));
for (uint32_t dim: input_info.dispatch_threads_num) {
ret.ids.push_back(dim);
}
ret.ids.push_back(static_cast<uint32_t>(input_info.tg_size_en));
if (include_bind_specialization) {
EXIT_IF(!input_info.stage);
-1
View File
@@ -120,7 +120,6 @@ struct ShaderPixelInputInfo {
uint8_t target_output_mode[8] = {};
std::array<Prospero::ColorComponentMapping, 8> target_export_mapping = {};
uint32_t mrt_output_mask = 0;
uint32_t descriptor_set = 0;
uint32_t push_constant_offset = 0;
uint32_t scratch_size_dwords = 0;
bool ps_pos_x = false;
+102 -41
View File
@@ -636,24 +636,22 @@ void TestImagesSamplersAndAliases() {
void TestSampleAdjustSamplerScratch() {
Fixture fixture(ShaderType::Pixel);
const auto active = fixture.Emit(ValueOpcode::WqmMask, {Value(true)});
const auto lane = fixture.Emit(ValueOpcode::SelectU32,
{active, Value(1u), Value(0u)});
const auto low = fixture.Emit(ValueOpcode::BitwiseAnd32,
{lane, Value(0xffu)});
const auto high = fixture.Emit(ValueOpcode::BitwiseAnd32,
{lane, Value(0xffu)});
const auto lane =
fixture.Emit(ValueOpcode::SelectU32, {active, Value(1u), Value(0u)});
const auto low =
fixture.Emit(ValueOpcode::BitwiseAnd32, {lane, Value(0xffu)});
const auto high =
fixture.Emit(ValueOpcode::BitwiseAnd32, {lane, Value(0xffu)});
const auto quads = fixture.Emit(
ValueOpcode::BitwiseOr32,
{low, fixture.Emit(ValueOpcode::ShiftLeftLogical32,
{high, Value(8u)})});
const auto scratch = fixture.Emit(ValueOpcode::ShiftLeftLogical32,
{quads, Value(12u)});
const auto word3 = fixture.Emit(ValueOpcode::BitwiseOr32,
{fixture.UserData(3), scratch});
const auto image = fixture.Image(
{Value(0u), Value(0u), Value(0u), Value(0u), Value(0u), Value(0u),
Value(0u), Value(0u)},
0x1ec);
{low, fixture.Emit(ValueOpcode::ShiftLeftLogical32, {high, Value(8u)})});
const auto scratch =
fixture.Emit(ValueOpcode::ShiftLeftLogical32, {quads, Value(12u)});
const auto word3 =
fixture.Emit(ValueOpcode::BitwiseOr32, {fixture.UserData(3), scratch});
const auto image = fixture.Image({Value(0u), Value(0u), Value(0u), Value(0u),
Value(0u), Value(0u), Value(0u), Value(0u)},
0x1ec);
const auto sampler = fixture.Sampler(
{fixture.UserData(0), fixture.UserData(1), fixture.UserData(2), word3},
0x1ec);
@@ -685,22 +683,21 @@ void TestSampleAdjustSamplerScratch() {
const auto CheckRejected = [](uint32_t flags, uint32_t shift,
const char *message) {
Fixture rejected(ShaderType::Pixel);
const auto condition =
rejected.Emit(ValueOpcode::WqmMask, {Value(true)});
const auto condition = rejected.Emit(ValueOpcode::WqmMask, {Value(true)});
const auto bit = rejected.Emit(ValueOpcode::SelectU32,
{condition, Value(1u), Value(0u)});
const auto dynamic = rejected.Emit(ValueOpcode::ShiftLeftLogical32,
{bit, Value(shift)});
const auto dynamic_word3 = rejected.Emit(
ValueOpcode::BitwiseOr32, {rejected.UserData(3), dynamic});
const auto rejected_image = rejected.Image(
{Value(0u), Value(0u), Value(0u), Value(0u), Value(0u), Value(0u),
Value(0u), Value(0u)},
0x200);
const auto rejected_sampler = rejected.Sampler(
{rejected.UserData(0), rejected.UserData(1), rejected.UserData(2),
dynamic_word3},
0x200);
const auto dynamic =
rejected.Emit(ValueOpcode::ShiftLeftLogical32, {bit, Value(shift)});
const auto dynamic_word3 = rejected.Emit(ValueOpcode::BitwiseOr32,
{rejected.UserData(3), dynamic});
const auto rejected_image =
rejected.Image({Value(0u), Value(0u), Value(0u), Value(0u), Value(0u),
Value(0u), Value(0u), Value(0u)},
0x200);
const auto rejected_sampler =
rejected.Sampler({rejected.UserData(0), rejected.UserData(1),
rejected.UserData(2), dynamic_word3},
0x200);
MemoryInfo rejected_memory;
rejected_memory.kind = ResourceKind::Image;
rejected_memory.image_dimension = Decoder::ImageDimension::Dim2D;
@@ -789,7 +786,7 @@ void TestDynamicStorageMipTracking() {
"base-1 through last-3 dynamic storage range was not specialized");
ShaderComputeInputInfo compute{};
Check(CollectShaderInfo(fixture.program, {.compute = &compute}, &error) &&
AllocateBindings(fixture.program, {}, &error),
AllocateBindings(fixture.program, 0, &error),
"dynamic storage mip bindings were not allocated");
const auto *storage_binding =
FindBinding(fixture.program.bindings, DescriptorBindingKind::Storage2D);
@@ -888,7 +885,7 @@ void TestSrtFlatteningAndRuntimeMemoization() {
ShaderComputeInputInfo compute{};
Check(CollectShaderInfo(fixture.program, {.compute = &compute}, &error) &&
AllocateBindings(fixture.program, {}, &error) &&
AllocateBindings(fixture.program, 0, &error) &&
FindBinding(fixture.program.bindings,
DescriptorBindingKind::FlattenedSrt) != nullptr,
"flattened typed SRT reads did not receive a binding");
@@ -933,7 +930,7 @@ void TestDynamicSrtReadRemainsExplicit() {
ShaderComputeInputInfo compute{};
Check(CollectShaderInfo(fixture.program, {.compute = &compute}, &error) &&
AllocateBindings(fixture.program, {}, &error) &&
AllocateBindings(fixture.program, 0, &error) &&
FindBinding(fixture.program.bindings,
DescriptorBindingKind::FlattenedSrt) == nullptr &&
FindBinding(fixture.program.bindings,
@@ -1198,24 +1195,87 @@ void TestShaderInfoAndBindingLayout() {
StageInputKind::GlobalInvocationId,
"typed shader values were not reflected in shader info");
BindingLayoutOptions options;
options.descriptor_set = 2;
options.max_push_dwords = 1;
Check(AllocateBindings(fixture.program, options, &error),
Check(AllocateBindings(fixture.program, 0, &error),
"typed binding allocation failed");
Check(fixture.program.bindings.descriptor_set == 2 &&
FindBinding(fixture.program.bindings,
DescriptorBindingKind::Buffers) != nullptr &&
Check(FindBinding(fixture.program.bindings, DescriptorBindingKind::Buffers) !=
nullptr &&
FindBinding(fixture.program.bindings, DescriptorBindingKind::Gds) !=
nullptr &&
FindBinding(fixture.program.bindings,
DescriptorBindingKind::UserData) != nullptr,
DescriptorBindingKind::UserData) == nullptr &&
fixture.program.bindings.push_constant_size ==
fixture.program.bindings.ShaderDataDwords() * sizeof(uint32_t),
"typed resources were not assigned native bindings");
Check(NativeBinding(ShaderType::Compute, DescriptorBindingKind::Buffers) ==
static_cast<uint32_t>(DescriptorBindingKind::Buffers) &&
NativeBinding(ShaderType::Vertex, DescriptorBindingKind::Buffers) ==
static_cast<uint32_t>(DescriptorBindingKind::Buffers) &&
NativeBinding(ShaderType::Pixel, DescriptorBindingKind::Buffers) ==
static_cast<uint32_t>(DescriptorBindingKind::Count) +
static_cast<uint32_t>(DescriptorBindingKind::Buffers),
"fixed stage binding ranges are inconsistent");
Check(fixture.program.bindings.user_data_registers ==
std::vector<uint32_t>({3u, 4u}),
"binding layout did not collect live typed user-data values");
}
void TestGraphicsPushConstantLayout() {
const auto AddUserData = [](Fixture &fixture, uint32_t count) {
for (uint32_t index = 0; index < count; index++) {
fixture.Emit(ValueOpcode::ReferenceU32, {fixture.UserData(index)});
}
fixture.program.shader_info_complete = true;
};
std::string error;
Fixture vertex(ShaderType::Vertex);
AddUserData(vertex, 9);
Check(AllocateBindings(vertex.program, 0, &error) &&
vertex.program.bindings.push_constant_offset == 0 &&
vertex.program.bindings.push_constant_size == 9 * sizeof(uint32_t),
"vertex shader was not placed at the start of the graphics push bank");
const auto pixel_offset = vertex.program.bindings.push_constant_size;
Fixture pixel(ShaderType::Pixel);
AddUserData(pixel, 4);
Check(AllocateBindings(pixel.program, pixel_offset, &error) &&
pixel.program.bindings.push_constant_offset == pixel_offset &&
pixel.program.bindings.push_constant_size == 4 * sizeof(uint32_t) &&
FindBinding(pixel.program.bindings, DescriptorBindingKind::UserData) ==
nullptr,
"pixel shader did not follow the vertex data in the graphics push bank");
Fixture edge(ShaderType::Pixel);
AddUserData(edge, 1);
Check(AllocateBindings(edge.program, NativePushConstantSize - sizeof(uint32_t),
&error) &&
edge.program.bindings.push_constant_size == sizeof(uint32_t),
"last aligned push-constant dword did not fit in the graphics bank");
Fixture spill(ShaderType::Pixel);
AddUserData(spill, 32);
Check(AllocateBindings(spill.program, pixel_offset, &error) &&
spill.program.bindings.push_constant_size == 0 &&
FindBinding(spill.program.bindings, DescriptorBindingKind::UserData) !=
nullptr,
"pixel shader overlapping the vertex push data did not spill to storage");
Fixture full(ShaderType::Pixel);
AddUserData(full, 1);
Check(AllocateBindings(full.program, NativePushConstantSize, &error) &&
full.program.bindings.push_constant_size == 0 &&
FindBinding(full.program.bindings, DescriptorBindingKind::UserData) !=
nullptr,
"full graphics push bank did not spill pixel user data to storage");
Fixture invalid(ShaderType::Pixel);
AddUserData(invalid, 1);
Check(!AllocateBindings(invalid.program,
NativePushConstantSize + sizeof(uint32_t), &error) &&
!invalid.program.binding_layout_complete,
"push-constant placement beyond the graphics bank was accepted");
}
void TestResourceLimitIsTransactional() {
Fixture fixture;
MemoryInfo memory;
@@ -1339,6 +1399,7 @@ int main() {
Run("exec-masked FLAT address", TestExecMaskedFlatAddressProvenance);
Run("buffer swizzle specialization", TestBufferSwizzleSpecialization);
Run("shader info and bindings", TestShaderInfoAndBindingLayout);
Run("graphics push constants", TestGraphicsPushConstantLayout);
Run("resource limit", TestResourceLimitIsTransactional);
Run("malformed memory kinds", TestMalformedMemoryKindsRejected);
} catch (const std::exception &exception) {
File diff suppressed because it is too large Load Diff
+195 -46
View File
@@ -690,6 +690,24 @@ bool SpirvHasDecorationValue(const std::vector<uint32_t> &binary,
return SpirvDecorationValueCount(binary, decoration, value) != 0;
}
bool SpirvHasMemberDecorationValue(std::span<const uint32_t> binary,
uint32_t decoration, uint32_t value) {
for (size_t i = 5; i < binary.size();) {
const uint32_t word = binary[i];
const uint32_t opcode = word & 0xffffu;
const uint32_t word_count = word >> 16u;
if (word_count == 0 || i + word_count > binary.size()) {
return false;
}
if (opcode == 72u && word_count >= 5u && binary[i + 3] == decoration &&
binary[i + 4] == value) {
return true;
}
i += word_count;
}
return false;
}
std::vector<uint32_t>
SpirvDecorationValueTargets(const std::vector<uint32_t> &binary,
uint32_t decoration, uint32_t value) {
@@ -9868,22 +9886,23 @@ void TestRenderTargetReverseExportMapping() {
mapped.code_size_bytes = sizeof(shader);
ShaderMapUserData(regs.ps_regs.data_addr, mapped);
HW::ShaderRegisters sh{};
ShaderVertexInputInfo vs_info{};
vs_info.stage.program = std::make_shared<ShaderRecompiler::IR::Program>();
std::array<Prospero::ColorComponentMapping, 8> mappings{};
mappings[0] = gr32.export_mapping;
ShaderPixelInputInfo compiled_info{};
ShaderVertexInputInfo vertex_info{};
std::span<const uint32_t> compiled_spirv;
Check(ShaderCompileInfoPS(regs, sh, vs_info, mappings, compiled_info,
compiled_spirv) &&
compiled_info.target_export_mapping[0].IsIdentity(),
"inactive reverse MRT mapping was not normalized out of the shader "
"cache key");
Check(
ShaderCompileInfoPS(regs, sh, vertex_info, mappings, compiled_info,
compiled_spirv) &&
compiled_info.target_export_mapping[0].IsIdentity(),
"inactive reverse MRT mapping was not normalized out of the shader "
"cache key");
sh.target_output_mode[0] = 4;
Check(ShaderCompileInfoPS(regs, sh, vs_info, mappings, compiled_info,
compiled_spirv) &&
compiled_info.target_export_mapping[0] == gr32.export_mapping,
"active reverse MRT mapping was lost before shader specialization");
Check(
ShaderCompileInfoPS(regs, sh, vertex_info, mappings, compiled_info,
compiled_spirv) &&
compiled_info.target_export_mapping[0] == gr32.export_mapping,
"active reverse MRT mapping was lost before shader specialization");
}
void TestNewShaderRecompilerEarlyZDisabledWhenPixelKillEnabled() {
@@ -9988,15 +10007,21 @@ void TestNewShaderRecompilerNativeBindingPlan() {
"native binding plan did not allocate the sampler");
Check(SpirvContainsOpcode(result.spirv, 86),
"SPIR-V binary does not combine separate image/sampler descriptors");
Check(SpirvHasDecorationValue(result.spirv, 33u, buffers->binding),
Check(SpirvHasDecorationValue(result.spirv, 33u,
ShaderRecompiler::IR::NativeBinding(
result.program.stage, buffers->kind)),
"SPIR-V lacks storage-buffer Binding decoration");
Check(SpirvHasDecorationValue(result.spirv, 33u, sampled->binding),
Check(SpirvHasDecorationValue(result.spirv, 33u,
ShaderRecompiler::IR::NativeBinding(
result.program.stage, sampled->kind)),
"SPIR-V lacks sampled-image Binding decoration");
Check(SpirvHasDecorationValue(result.spirv, 33u, samplers->binding),
Check(SpirvHasDecorationValue(result.spirv, 33u,
ShaderRecompiler::IR::NativeBinding(
result.program.stage, samplers->kind)),
"SPIR-V lacks sampler Binding decoration");
Check(SpirvHasDecorationValue(result.spirv, 34u,
result.program.bindings.descriptor_set),
"SPIR-V lacks DescriptorSet decoration");
Check(SpirvDecorationValueCount(result.spirv, 34u, 0u) ==
result.program.bindings.descriptors.size(),
"SPIR-V resources do not all use descriptor set zero");
CheckSpirvBinaryValidates(result.spirv);
auto malformed = result.program;
@@ -10945,6 +10970,31 @@ void TestComputeLdsAllocationIdentity() {
ShaderGetIdCS(regs, lds_896, true),
"compute pipeline identity omitted the LDS allocation");
auto tg_size_disabled = lds_896;
auto tg_size_enabled = lds_896;
tg_size_enabled.tg_size_en = true;
Check(ShaderGetIdCS(regs, tg_size_disabled, false) !=
ShaderGetIdCS(regs, tg_size_enabled, false),
"compute shader identity omitted TG_SIZE semantics");
auto dispatch_disabled = lds_896;
dispatch_disabled.dispatch_threads_num[0] = 64;
dispatch_disabled.dispatch_threads_num[1] = 32;
dispatch_disabled.dispatch_threads_num[2] = 1;
auto dispatch_enabled = dispatch_disabled;
dispatch_enabled.dispatch_thread_dimensions = true;
Check(ShaderGetIdCS(regs, dispatch_disabled, false) ==
ShaderGetIdCS(regs, dispatch_enabled, false),
"runtime dispatch enable created a compute shader variant");
auto second_extent = dispatch_enabled;
second_extent.dispatch_threads_num[0] = 1920;
second_extent.dispatch_threads_num[1] = 1080;
second_extent.dispatch_threads_num[2] = 4;
Check(ShaderGetIdCS(regs, dispatch_enabled, false) ==
ShaderGetIdCS(regs, second_extent, false),
"runtime dispatch extent created a compute shader variant");
mapped.scratch_size_dwords = 7;
ShaderMapUserData(regs.cs_regs.data_addr, mapped);
regs.cs_regs.lds_size = decode_lds_field(lds_896_rsrc2);
@@ -10981,14 +11031,25 @@ void TestComputeLdsAllocationIdentity() {
CheckSpirvBinaryValidates(append_result.spirv);
}
void TestPixelProgramCacheDescriptorSetIdentity() {
void TestPixelProgramCacheBindingIdentity() {
const uint32_t shader_01[] = {0xbf810000u};
const uint32_t shader_10[] = {0xbf810000u};
HW::ShaderRegisters sh{};
auto check_transition = [&](const uint32_t *shader, uint64_t checksum,
bool first_has_vs_descriptors,
bool second_has_vs_descriptors) {
HW::PixelShaderInfo identity_regs{};
ShaderPixelInputInfo no_depth_export{};
auto depth_export = no_depth_export;
depth_export.ps_depth_export_enable = true;
Check(ShaderGetIdPS(identity_regs, no_depth_export, false) !=
ShaderGetIdPS(identity_regs, depth_export, false),
"pixel shader identity omitted depth-export semantics");
auto shifted_push = no_depth_export;
shifted_push.push_constant_offset = 36;
Check(ShaderGetIdPS(identity_regs, no_depth_export, false) !=
ShaderGetIdPS(identity_regs, shifted_push, false),
"pixel shader identity omitted graphics push-bank placement");
auto check_cache_identity = [&](const uint32_t *shader, uint64_t checksum) {
HW::PixelShaderInfo regs{};
regs.ps_regs.data_addr = reinterpret_cast<uint64_t>(shader);
regs.ps_regs.chksum = checksum;
@@ -10996,33 +11057,120 @@ void TestPixelProgramCacheDescriptorSetIdentity() {
mapped.code_size_bytes = sizeof(uint32_t);
ShaderMapUserData(regs.ps_regs.data_addr, mapped);
auto compile = [&](bool has_vs_descriptors) {
auto vs_program = std::make_shared<ShaderRecompiler::IR::Program>();
if (has_vs_descriptors) {
vs_program->bindings.descriptors.emplace_back();
}
ShaderVertexInputInfo vs_info{};
vs_info.stage.program = std::move(vs_program);
const std::array<Prospero::ColorComponentMapping, 8> identity_mappings{};
ShaderPixelInputInfo ps_info{};
std::span<const uint32_t> spirv;
Check(ShaderCompileInfoPS(regs, sh, vs_info, identity_mappings, ps_info,
spirv),
"pixel program-cache transition failed to compile");
const auto expected_set = has_vs_descriptors ? 1u : 0u;
Check(ps_info.descriptor_set == expected_set &&
ps_info.stage.program != nullptr &&
ps_info.stage.program->bindings.descriptor_set == expected_set,
"pixel program cache reused SPIR-V with the paired VS descriptor "
"set");
};
const std::array<Prospero::ColorComponentMapping, 8> identity_mappings{};
ShaderVertexInputInfo vertex_info{};
ShaderPixelInputInfo first_info{};
std::span<const uint32_t> first_spirv;
Check(ShaderCompileInfoPS(regs, sh, vertex_info, identity_mappings, first_info,
first_spirv),
"pixel program-cache fixture failed to compile");
compile(first_has_vs_descriptors);
compile(second_has_vs_descriptors);
ShaderPixelInputInfo second_info{};
std::span<const uint32_t> second_spirv;
Check(ShaderCompileInfoPS(regs, sh, vertex_info, identity_mappings, second_info,
second_spirv),
"pixel program-cache lookup failed");
Check(
first_info.stage.program != nullptr &&
second_info.stage.program == first_info.stage.program &&
second_spirv.data() == first_spirv.data() &&
second_spirv.size() == first_spirv.size(),
"pixel program cache did not reuse an identical graphics placement");
};
check_transition(shader_01, 0x91a27e6300000001ull, false, true);
check_transition(shader_10, 0x91a27e6300000002ull, true, false);
check_cache_identity(shader_01, 0x91a27e6300000001ull);
check_cache_identity(shader_10, 0x91a27e6300000002ull);
const uint32_t push_shader[] = {
EncodeVop1(0x01, 0, 0), EncodeVop1(0x01, 1, 1),
EncodeVop1(0x01, 2, 2), EncodeVop1(0x01, 3, 3),
EncodeExp0(0x00, 0xf), EncodeExp1(0, 1, 2, 3), 0xbf810000u,
};
HW::PixelShaderInfo push_regs{};
push_regs.ps_regs.data_addr = reinterpret_cast<uint64_t>(push_shader);
push_regs.ps_regs.chksum = 0x91a27e6300000003ull;
push_regs.ps_regs.rsrc2.user_sgpr = 4;
ShaderMappedData push_mapped{};
push_mapped.code_size_bytes = sizeof(push_shader);
ShaderMapUserData(push_regs.ps_regs.data_addr, push_mapped);
HW::ShaderRegisters push_sh{};
push_sh.target_output_mode[0] = 4;
const std::array<Prospero::ColorComponentMapping, 8> mappings{};
const auto MakeVertex = [](uint32_t push_size) {
ShaderVertexInputInfo info{};
auto program = std::make_shared<ShaderRecompiler::IR::Program>();
program->stage = ShaderType::Vertex;
program->bindings.push_constant_size = push_size;
info.stage.program = std::move(program);
return info;
};
const auto vertex_at_zero = MakeVertex(0);
const auto vertex_at_36 = MakeVertex(36);
ShaderPixelInputInfo at_zero{};
ShaderPixelInputInfo at_36{};
ShaderPixelInputInfo at_zero_again{};
std::span<const uint32_t> spirv_at_zero;
std::span<const uint32_t> spirv_at_36;
std::span<const uint32_t> spirv_at_zero_again;
Check(ShaderCompileInfoPS(push_regs, push_sh, vertex_at_zero, mappings,
at_zero, spirv_at_zero) &&
ShaderCompileInfoPS(push_regs, push_sh, vertex_at_36, mappings,
at_36, spirv_at_36) &&
ShaderCompileInfoPS(push_regs, push_sh, vertex_at_zero, mappings,
at_zero_again, spirv_at_zero_again),
"pixel program-cache placement fixture failed to compile");
Check(at_zero.push_constant_offset == 0 &&
at_36.push_constant_offset == 36 &&
at_zero.stage.program != at_36.stage.program &&
spirv_at_zero.data() != spirv_at_36.data() &&
at_zero_again.stage.program == at_zero.stage.program &&
spirv_at_zero_again.data() == spirv_at_zero.data() &&
SpirvHasMemberDecorationValue(spirv_at_36, 35u, 36u),
"pixel program cache did not partition and reuse graphics push placement");
}
void TestGraphicsPushConstantPlacement() {
using BindingKind = ShaderRecompiler::IR::DescriptorBindingKind;
constexpr uint32_t OffsetDecoration = 35;
const uint32_t shader[] = {
EncodeVop1(0x01, 0, 0), EncodeVop1(0x01, 1, 1),
EncodeVop1(0x01, 2, 2), EncodeVop1(0x01, 3, 3),
EncodeExp0(0x00, 0xf), EncodeExp1(0, 1, 2, 3), 0xbf810000u,
};
const std::array<uint32_t, 4> user_data = {
0x3e800000u, 0x3f000000u, 0x3f400000u, 0x3f800000u};
ShaderPixelInputInfo pixel_info{};
auto options = MakeCompileOptions(ShaderType::Pixel);
options.input_info.pixel = &pixel_info;
options.user_data = user_data.data();
options.user_data_count = static_cast<uint32_t>(user_data.size());
options.push_constant_offset = 36;
ShaderRecompiler::CompileResult placed;
std::string error;
Check(ShaderRecompiler::TryRecompile(shader, options, placed, &error),
error.c_str());
Check(placed.program.bindings.push_constant_offset == 36 &&
placed.program.bindings.push_constant_size == sizeof(user_data) &&
ShaderRecompiler::IR::FindBinding(placed.program.bindings,
BindingKind::UserData) == nullptr,
"pixel push constants did not follow the vertex payload");
Check(SpirvHasMemberDecorationValue(placed.spirv, OffsetDecoration, 36),
"SPIR-V push block omitted the graphics-bank member offset");
CheckSpirvBinaryValidates(placed.spirv);
options.push_constant_offset = 124;
ShaderRecompiler::CompileResult spilled;
error.clear();
Check(ShaderRecompiler::TryRecompile(shader, options, spilled, &error),
error.c_str());
Check(spilled.program.bindings.push_constant_offset == 124 &&
spilled.program.bindings.push_constant_size == 0 &&
ShaderRecompiler::IR::FindBinding(spilled.program.bindings,
BindingKind::UserData) != nullptr,
"pixel data that crossed the graphics push bank did not spill");
CheckSpirvBinaryValidates(spilled.spirv);
}
void TestNewShaderRecompilerUnsupportedMemoryDecode() {
@@ -11478,7 +11626,8 @@ int main() {
TestGraphicsCreateInterpolantMapping();
TestNewShaderRecompilerPixelPipelineEntry();
TestComputeLdsAllocationIdentity();
TestPixelProgramCacheDescriptorSetIdentity();
TestPixelProgramCacheBindingIdentity();
TestGraphicsPushConstantPlacement();
TestNewShaderRecompilerUnsupportedMemoryDecode();
return 0;