refactor(graphics): split renderer and recompiler into focused modules

This commit is contained in:
nmzik
2026-07-27 22:08:04 +02:00
parent 2b9cba457e
commit a6b61d7aa0
108 changed files with 245 additions and 237 deletions
@@ -0,0 +1,296 @@
#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::Sampled3D:
case BindingKind::SampledUint1D:
case BindingKind::SampledUint1DArray:
case BindingKind::SampledUint2D:
case BindingKind::SampledUint2DArray:
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: 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) {
EXIT_IF(!texture.image_id || texture.image_view == nullptr ||
texture.layout == vk::ImageLayout::eUndefined);
return {nullptr, texture.image_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);
const auto descriptor_count = program.info.buffers.size() + program.info.images.size() +
program.info.samplers.size() + program.info.addresses.size() + 3u;
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());
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));
}
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);
}
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
@@ -0,0 +1,114 @@
#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/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 {
std::shared_ptr<void> owner;
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;
};
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<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);
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,953 @@
#include "graphics/host_gpu/renderer/pipeline/descriptors.h"
#include "common/assert.h"
#include "common/common.h"
#include "common/file.h"
#include "common/logging/log.h"
#include "common/profiler.h"
#include "common/stringUtils.h"
#include "common/threads.h"
#include "graphics/guest_gpu/gpu_defs.h"
#include "graphics/guest_gpu/gpu_format.h"
#include "graphics/guest_gpu/graphicsRun.h"
#include "graphics/guest_gpu/hardwareContext.h"
#include "graphics/guest_gpu/tile.h"
#include "graphics/host_gpu/graphicContext.h"
#include "graphics/host_gpu/hostMemory.h"
#include "graphics/host_gpu/renderer/image/textureCommon.h"
#include "graphics/host_gpu/renderer/debug.h"
#include "graphics/host_gpu/renderer/pipeline/descriptorCache.h"
#include "graphics/host_gpu/renderer/image/imageView.h"
#include "graphics/host_gpu/renderer/render.h"
#include "graphics/host_gpu/renderer/renderContext.h"
#include "graphics/host_gpu/renderer/pipeline/shaderResourceBarrier.h"
#include "graphics/host_gpu/vma.h"
#include "graphics/host_gpu/vulkanCommon.h"
#include "graphics/shader/recompiler/ir/BindingLayout.h"
#include "graphics/shader/recompiler/ir/ResourceMaterialization.h"
#include "graphics/shader/recompiler/ir/ShaderIR.h"
#include "graphics/shader/shader.h"
#include <algorithm>
#include <atomic>
#include <fmt/format.h>
#include <limits>
#include <span>
#include <vector>
#ifdef min
#undef min
#endif
#ifdef max
#undef max
#endif
namespace Libs::Graphics {
static void BindNullStorageBuffer(RenderContext& context, BufferView& dst) {
auto owner = context.GetBufferCache().ObtainNullBuffer();
dst.buffer = owner->Handle();
dst.owner = std::move(owner);
dst.offset = 0;
dst.range = 16;
}
static void CopyNativeDescriptor(const ShaderRecompiler::IR::DescriptorValue& source,
std::span<uint32_t> destination) {
EXIT_IF(source.dword_count != destination.size());
std::copy_n(source.dwords.begin(), destination.size(), destination.begin());
}
static Prospero::ImageType TextureType(const ShaderTextureResource& descriptor) {
const auto type = static_cast<Prospero::ImageType>(descriptor.Type());
return type == Prospero::ImageType::kCube ? Prospero::ImageType::kColor2DArray : type;
}
static Prospero::ImageType TextureBaseType(Prospero::ImageType type) {
switch (type) {
case Prospero::ImageType::kColor1DArray: return Prospero::ImageType::kColor1D;
case Prospero::ImageType::kColor2DArray:
case Prospero::ImageType::kColor2DMsaa:
case Prospero::ImageType::kColor2DMsaaArray: return Prospero::ImageType::kColor2D;
default: return type;
}
}
static BufferView NativeStorageBuffer(RenderContext& context, CommandBuffer& command_buffer,
const ShaderBufferResource& descriptor,
const ShaderRecompiler::IR::BufferResource& resource,
uint32_t& buffer_offset) {
BufferView result;
buffer_offset = 0;
const auto address = descriptor.Base48();
const auto stride = descriptor.Stride();
const auto records = descriptor.NumRecords();
if (stride != 0 && records > UINT64_MAX / stride) {
EXIT("storage buffer descriptor footprint overflow\n");
}
const auto size = stride != 0 ? static_cast<uint64_t>(stride) * records : records;
if (address == 0 || size == 0) {
BindNullStorageBuffer(context, result);
return result;
}
const auto& graphics = context.GetGraphics();
const auto alignment = graphics.StorageMinAlignment();
if (alignment == 0 ||
size > graphics.GetPhysicalDeviceProperties().limits.maxStorageBufferRange) {
EXIT("storage buffer range or device alignment is unsupported\n");
}
auto binding = context.GetBufferCache().ObtainBuffer(
command_buffer, address, size, resource.written, resource.read, resource.formatted);
const auto aligned_offset = binding.offset - binding.offset % alignment;
const auto adjustment = binding.offset - aligned_offset;
const auto max_range = graphics.GetPhysicalDeviceProperties().limits.maxStorageBufferRange;
if (adjustment % sizeof(uint32_t) != 0 || adjustment >= 256 || size > max_range - adjustment) {
EXIT("storage buffer offset adjustment is unsupported\n");
}
buffer_offset = static_cast<uint32_t>(adjustment);
result.owner = std::move(binding.owner);
result.buffer = binding.buffer;
result.offset = aligned_offset;
result.range = static_cast<vk::DeviceSize>(size + adjustment);
return result;
}
static BufferView
NativeAddressBuffer(RenderContext& context, CommandBuffer& command_buffer,
const ShaderRecompiler::IR::AddressResource& resource,
const ShaderRecompiler::IR::ResourceSnapshot::Address& address) {
BufferView result;
if (address.binding_base == 0) {
BindNullStorageBuffer(context, result);
return result;
}
if (resource.written) {
EXIT("writable address resources are unsupported\n");
}
const auto limit =
resource.kind == ShaderRecompiler::IR::ResourceKind::Flat
? ShaderRecompiler::IR::FlatAddressWindowSize
: static_cast<uint64_t>(
context.GetGraphics().GetPhysicalDeviceProperties().limits.maxStorageBufferRange);
uint64_t size = 0;
const auto access = HostMemoryAccess::Mapped;
if (!HostMemoryQueryRange(address.binding_base, limit, access, size)) {
EXIT("address resource is not host-accessible: base=0x%016" PRIx64 "\n",
address.binding_base);
}
const auto& graphics = context.GetGraphics();
const auto alignment = graphics.StorageMinAlignment();
if (alignment == 0 ||
size > graphics.GetPhysicalDeviceProperties().limits.maxStorageBufferRange ||
BufferCache::GetBufferOffset(address.binding_base) % alignment != 0) {
EXIT("address resource range or alignment is unsupported\n");
}
auto binding =
context.GetBufferCache().ObtainBuffer(command_buffer, address.binding_base, size);
result.owner = std::move(binding.owner);
result.buffer = binding.buffer;
result.offset = binding.offset;
result.range = static_cast<vk::DeviceSize>(size);
return result;
}
static bool IsSupportedSampledColorResource(const ShaderRecompiler::IR::ImageResource& resource) {
bool supported_dimension = false;
switch (resource.dimension) {
case ShaderRecompiler::Decoder::ImageDimension::Dim1D:
case ShaderRecompiler::Decoder::ImageDimension::Dim1DArray:
case ShaderRecompiler::Decoder::ImageDimension::Dim2D:
case ShaderRecompiler::Decoder::ImageDimension::Dim2DArray:
supported_dimension = true;
break;
default: break;
}
const bool sampled_kind = resource.kind == ShaderRecompiler::IR::ResourceKind::Image ||
resource.kind == ShaderRecompiler::IR::ResourceKind::ImageUint;
return sampled_kind && supported_dimension &&
resource.mip_mode == ShaderRecompiler::IR::ImageMipMode::None && resource.read &&
!resource.written && !resource.atomic && !resource.depth_compare;
}
TargetTextureViewInfo ResolveTargetTextureView(const ShaderRecompiler::IR::ImageResource& resource,
Prospero::ImageType type, uint32_t base_layer,
uint32_t image_layers) {
switch (type) {
case Prospero::ImageType::kColor2D:
return resource.dimension == ShaderRecompiler::Decoder::ImageDimension::Dim2D &&
base_layer == 0 && image_layers == 1
? TargetTextureViewInfo {vk::ImageViewType::e2D, 0, 1}
: TargetTextureViewInfo {};
case Prospero::ImageType::kCube:
if (resource.dimension != ShaderRecompiler::Decoder::ImageDimension::Dim2DArray ||
base_layer >= image_layers || (image_layers - base_layer) % 6u != 0) {
return {};
}
return {vk::ImageViewType::e2DArray, base_layer, image_layers - base_layer};
case Prospero::ImageType::kColor2DArray:
if (resource.dimension == ShaderRecompiler::Decoder::ImageDimension::Dim2D &&
base_layer == 0 && image_layers == 1) {
return {vk::ImageViewType::e2D, 0, 1};
}
return resource.dimension == ShaderRecompiler::Decoder::ImageDimension::Dim2DArray &&
base_layer < image_layers
? TargetTextureViewInfo {vk::ImageViewType::e2DArray, base_layer,
image_layers - base_layer}
: TargetTextureViewInfo {};
default: return {};
}
}
bool IsSupportedSampledVideoOutView(const ShaderRecompiler::IR::ImageResource& resource,
const ShaderTextureResource& descriptor, const Image& image) {
return image.usage.video_out && image.info.resources.layers == 1 &&
IsSupportedSampledColorResource(resource) &&
resource.dimension == ShaderRecompiler::Decoder::ImageDimension::Dim2D &&
descriptor.Type() == Prospero::GpuEnumValue(Prospero::ImageType::kColor2D) &&
descriptor.Depth() == 0 && descriptor.BaseArray5() == 0;
}
bool IsSupportedDepthTargetDescriptor(const ShaderTextureResource& descriptor, const Image& image) {
const auto width = static_cast<uint32_t>(descriptor.Width5()) + 1u;
const auto height = static_cast<uint32_t>(descriptor.Height5()) + 1u;
const auto pitch = TileGetTexturePitch(descriptor.Format(), width, 1, descriptor.TileMode());
const auto type = static_cast<Prospero::ImageType>(descriptor.Type());
const bool supported_single_layer =
image.info.resources.layers == 1 && descriptor.Depth() == 0 &&
descriptor.BaseArray5() == 0 &&
(type == Prospero::ImageType::kColor2D || type == Prospero::ImageType::kColor2DArray);
const bool supported_cube =
type == Prospero::ImageType::kCube && width == height && image.info.resources.layers >= 6 &&
image.info.resources.layers % 6u == 0 &&
static_cast<uint32_t>(descriptor.Depth()) + 1u == image.info.resources.layers &&
descriptor.BaseArray5() == 0;
return image.info.IsDepth() && width == image.info.extent.width &&
height == image.info.extent.height && (supported_single_layer || supported_cube) &&
descriptor.BaseLevel() == 0 && descriptor.LastLevel() == 0 && descriptor.MaxMip() == 0 &&
descriptor.MinLod() == 0 && descriptor.BaseArray5() == 0 &&
descriptor.TileMode() == Prospero::GpuEnumValue(Prospero::TileMode::kDepth) &&
descriptor.BCSwizzle() == 0 && !descriptor.MsaaDepth() && pitch >= width &&
pitch == image.info.pitch;
}
bool IsSupportedDepthTextureEncoding(const ShaderTextureResource& descriptor, const Image& image) {
constexpr uint32_t field1_reserved_mask = 0x200fff00u;
constexpr uint32_t field2_reserved_mask = 0xf0003000u;
constexpr uint32_t field3_common = 0x01800000u;
constexpr uint32_t field5_expected = 0x00700000u;
const uint32_t field3_expected =
(descriptor.Type() << 28u) | field3_common | descriptor.DstSelXYZW();
const uint32_t field4_expected = descriptor.Depth() | (descriptor.BaseArray5() << 16u);
const bool common = (descriptor.fields[1] & field1_reserved_mask) == 0 &&
(descriptor.fields[2] & field2_reserved_mask) == 0 &&
descriptor.fields[3] == field3_expected &&
descriptor.fields[4] == field4_expected &&
descriptor.fields[5] == field5_expected;
if (!common || (descriptor.fields[6] == 0 && descriptor.fields[7] != 0)) {
return false;
}
if (descriptor.fields[6] == 0) {
return true;
}
constexpr uint32_t htile_control = 0x00280000u;
const auto metadata_addr = descriptor.MetaAddr() << 8u;
return (descriptor.fields[6] & 0x00ffffffu) == htile_control && metadata_addr != 0 &&
descriptor.TileMode() == Prospero::GpuEnumValue(Prospero::TileMode::kDepth) &&
image.info.tile_mode == Prospero::GpuEnumValue(Prospero::TileMode::kDepth) &&
image.info.metadata.kind == ImageMetadataKind::Htile &&
image.info.metadata.range.Valid() && image.info.metadata.range.address == metadata_addr;
}
static void ValidateDepthTargetBinding(const ShaderRecompiler::IR::ImageResource& resource,
const ShaderTextureResource& descriptor, const Image* image,
vk::Format view_format, uint64_t size) {
const bool resource_ok = IsSupportedSampledDepthResource(resource);
const bool descriptor_ok =
image != nullptr && IsSupportedDepthTargetDescriptor(descriptor, *image);
const bool encoding_ok =
image != nullptr && IsSupportedDepthTextureEncoding(descriptor, *image);
const bool format_ok =
image != nullptr &&
IsSupportedSampledDepthFormat(image->info.pixel_format, descriptor.Format(), view_format);
if (resource_ok && descriptor_ok && encoding_ok && format_ok && size != 0) {
return;
}
const auto descriptor_pitch =
TileGetTexturePitch(descriptor.Format(), static_cast<uint32_t>(descriptor.Width5()) + 1u, 1,
descriptor.TileMode());
EXIT("unsupported sampled depth target: resource=%d descriptor=%d encoding=%d format=%d "
"kind=%u dimension=%u mip_mode=%u read=%d written=%d atomic=%d compare=%d "
"guest_format=%u swizzle=0x%03x image_format=%d view_format=%d image_layers=%u "
"descriptor_type=%u base_array=%u depth=%u descriptor_pitch=%u target_pitch=%u "
"addr=0x%016" PRIx64 " size=0x%016" PRIx64
" dwords=%08x,%08x,%08x,%08x,%08x,%08x,%08x,%08x\n",
resource_ok, descriptor_ok, encoding_ok, format_ok, static_cast<uint32_t>(resource.kind),
static_cast<uint32_t>(resource.dimension), static_cast<uint32_t>(resource.mip_mode),
resource.read, resource.written, resource.atomic, resource.depth_compare,
descriptor.Format(), descriptor.DstSelXYZW(),
image == nullptr ? static_cast<int>(vk::Format::eUndefined)
: static_cast<int>(image->info.pixel_format),
static_cast<int>(view_format), image == nullptr ? 0u : image->info.resources.layers,
descriptor.Type(), descriptor.BaseArray5(), descriptor.Depth(), descriptor_pitch,
image == nullptr ? 0u : image->info.pitch, descriptor.Base40(), size, descriptor.fields[0],
descriptor.fields[1], descriptor.fields[2], descriptor.fields[3], descriptor.fields[4],
descriptor.fields[5], descriptor.fields[6], descriptor.fields[7]);
}
static bool IsSupportedStorageTextureDescriptor(const ShaderRecompiler::IR::ImageResource& resource,
const ShaderTextureResource& descriptor) {
const auto tile = descriptor.TileMode();
const bool is_color_1d =
descriptor.Type() == Prospero::GpuEnumValue(Prospero::ImageType::kColor1D);
const bool is_color_1d_array =
descriptor.Type() == Prospero::GpuEnumValue(Prospero::ImageType::kColor1DArray);
const bool valid_1d_slice =
(is_color_1d && descriptor.Depth() == 0 && descriptor.BaseArray5() == 0) ||
(is_color_1d_array && descriptor.BaseArray5() <= descriptor.Depth());
const bool is_1d = resource.dimension == ShaderRecompiler::Decoder::ImageDimension::Dim1D &&
descriptor.Height5() == 0 && valid_1d_slice;
const bool is_1d_array =
resource.dimension == ShaderRecompiler::Decoder::ImageDimension::Dim1DArray &&
is_color_1d_array && descriptor.Height5() == 0 &&
descriptor.BaseArray5() <= descriptor.Depth();
const bool is_color_2d =
descriptor.Type() == Prospero::GpuEnumValue(Prospero::ImageType::kColor2D);
const bool is_color_2d_array =
descriptor.Type() == Prospero::GpuEnumValue(Prospero::ImageType::kColor2DArray);
const bool valid_2d_slice =
(is_color_2d && descriptor.Depth() == 0 && descriptor.BaseArray5() == 0) ||
(is_color_2d_array && descriptor.BaseArray5() <= descriptor.Depth());
const bool is_2d = resource.dimension == ShaderRecompiler::Decoder::ImageDimension::Dim2D &&
valid_2d_slice;
const bool is_2d_array =
resource.dimension == ShaderRecompiler::Decoder::ImageDimension::Dim2DArray &&
is_color_2d_array && descriptor.BaseArray5() <= descriptor.Depth();
const bool is_3d = resource.dimension == ShaderRecompiler::Decoder::ImageDimension::Dim3D &&
descriptor.Type() == Prospero::GpuEnumValue(Prospero::ImageType::kColor3D) &&
descriptor.BaseArray5() == 0;
TileBlockLayout depth_block {};
const auto depth_bpe = Prospero::RenderTargetBytesPerElement(descriptor.Format());
const bool supported_depth_tile =
tile == Prospero::GpuEnumValue(Prospero::TileMode::kDepth) && !resource.read &&
!Prospero::IsFmaskTextureFormat(descriptor.Format()) && (is_2d || is_2d_array) &&
TileGetBlockLayout(TileBlockFamily::Depth64KB, depth_bpe, depth_block);
const bool supported_standard_tile =
tile == Prospero::GpuEnumValue(Prospero::TileMode::kStandard4KB) &&
TileIsStandard4KBTextureSupported(descriptor.Format());
const bool supported_tile = tile == Prospero::GpuEnumValue(Prospero::TileMode::kLinear) ||
tile == Prospero::GpuEnumValue(Prospero::TileMode::kRenderTarget) ||
supported_depth_tile || supported_standard_tile;
const bool supported_swizzle =
IsValidImageSwizzle(descriptor.DstSelXYZW()) &&
(descriptor.DstSelXYZW() == DstSel(4, 5, 6, 7) || !resource.read);
const bool supported_mip_view = descriptor.BaseLevel() == 0 || is_1d || is_2d;
return (is_1d || is_1d_array || is_2d || is_2d_array || is_3d) && supported_tile &&
supported_mip_view &&
descriptor.BaseLevel() == descriptor.LastLevel() &&
descriptor.LastLevel() <= descriptor.MaxMip() && descriptor.MinLod() == 0 &&
supported_swizzle && descriptor.BCSwizzle() == 0 && !descriptor.MsaaDepth();
}
static bool IsSupportedStorageTextureEncoding(const ShaderTextureResource& descriptor) {
constexpr uint32_t field1_reserved_mask = 0x200fff00u;
constexpr uint32_t field2_reserved_mask = 0xf0003000u;
constexpr uint32_t field5_expected = 0x00700000u;
constexpr uint32_t field5_max_mip_mask = 0x000000f0u;
const uint32_t expected_field3 = descriptor.DstSelXYZW() |
(static_cast<uint32_t>(descriptor.BaseLevel()) << 12u) |
(static_cast<uint32_t>(descriptor.LastLevel()) << 16u) |
(static_cast<uint32_t>(descriptor.TileMode()) << 20u) |
(static_cast<uint32_t>(descriptor.Type()) << 28u);
const uint32_t expected_field4 =
descriptor.Depth() | (static_cast<uint32_t>(descriptor.BaseArray5()) << 16u);
return (descriptor.fields[1] & field1_reserved_mask) == 0 &&
(descriptor.fields[2] & field2_reserved_mask) == 0 &&
descriptor.fields[3] == expected_field3 && descriptor.fields[4] == expected_field4 &&
(descriptor.fields[5] & ~field5_max_mip_mask) == field5_expected;
}
void ValidateStorageTexture(const ShaderRecompiler::IR::ImageResource& resource,
const ShaderTextureResource& descriptor, uint64_t size) {
const auto format = descriptor.Format();
const bool resource_ok = IsSupportedStorageImageResource(resource);
const bool descriptor_ok = IsSupportedStorageTextureDescriptor(resource, descriptor);
const bool encoding_ok = IsSupportedStorageTextureEncoding(descriptor);
const bool uint_resource =
resource.kind == ShaderRecompiler::IR::ResourceKind::StorageImageUint;
const bool format_ok = Prospero::IsSupportedTextureFormat(format) &&
uint_resource == Prospero::IsUintTextureFormat(format);
if (resource_ok && descriptor_ok && encoding_ok && format_ok && size != 0) {
return;
}
EXIT("unsupported storage texture: resource=%d descriptor=%d encoding=%d format=%d "
"kind=%u dimension=%u mip_mode=%u atomic=%d compare=%d "
"base_level=%u last_level=%u max_mip=%u min_lod=%u base_array=%u bc=%u msaa=%d "
"depth_tile_bpe=%u swizzle_ok=%d "
"addr=0x%016" PRIx64 " size=0x%016" PRIx64
" extent=%ux%ux%u type=%u format=%u tile=%u swizzle=0x%03x read=%d written=%d "
"dwords=%08x,%08x,%08x,%08x,%08x,%08x,%08x,%08x\n",
resource_ok, descriptor_ok, encoding_ok, format_ok, static_cast<uint32_t>(resource.kind),
static_cast<uint32_t>(resource.dimension), static_cast<uint32_t>(resource.mip_mode),
resource.atomic, resource.depth_compare, descriptor.BaseLevel(), descriptor.LastLevel(),
descriptor.MaxMip(), descriptor.MinLod(), descriptor.BaseArray5(), descriptor.BCSwizzle(),
descriptor.MsaaDepth(), Prospero::RenderTargetBytesPerElement(format),
IsValidImageSwizzle(descriptor.DstSelXYZW()), descriptor.Base40(), size,
static_cast<uint32_t>(descriptor.Width5()) + 1u,
static_cast<uint32_t>(descriptor.Height5()) + 1u,
static_cast<uint32_t>(descriptor.Depth()) + 1u, descriptor.Type(), format,
descriptor.TileMode(), descriptor.DstSelXYZW(), resource.read, resource.written,
descriptor.fields[0], descriptor.fields[1], descriptor.fields[2], descriptor.fields[3],
descriptor.fields[4], descriptor.fields[5], descriptor.fields[6], descriptor.fields[7]);
}
struct NullImageSpec {
vk::Format format;
uint32_t guest_format;
};
static NullImageSpec NullTextureSpec(const ShaderRecompiler::IR::ImageResource& resource) {
const bool uint_image = resource.kind == ShaderRecompiler::IR::ResourceKind::ImageUint ||
resource.kind == ShaderRecompiler::IR::ResourceKind::StorageImageUint;
return uint_image ? NullImageSpec {vk::Format::eR32Uint,
Prospero::GpuEnumValue(Prospero::BufferFormat::k32UInt)}
: NullImageSpec {vk::Format::eR32Sfloat,
Prospero::GpuEnumValue(Prospero::BufferFormat::k32Float)};
}
static TextureCache::ImageDesc NullTextureDesc(const ShaderRecompiler::IR::ImageResource& resource,
TextureCache::BindingType binding) {
const auto spec = NullTextureSpec(resource);
TextureCache::ImageDesc desc {};
desc.info.pixel_format = spec.format;
desc.info.guest_format = spec.guest_format;
desc.info.type = Prospero::ImageType::kColor2D;
desc.info.extent = {1, 1, 1};
desc.info.resources = {1, 1};
desc.info.bytes_per_block = 4;
desc.info.samples = 1;
desc.info.mip_layout[0] = {0, 0, 1, 1};
desc.view_info.format = desc.info.pixel_format;
desc.view_info.type = vk::ImageViewType::e2D;
desc.view_info.aspect = vk::ImageAspectFlagBits::eColor;
desc.view_info.usage = binding == TextureCache::BindingType::Storage
? vk::ImageUsageFlagBits::eStorage
: vk::ImageUsageFlagBits::eSampled;
desc.type = binding;
return desc;
}
static void PopulateTextureMipLayout(ImageInfo& info) {
if (info.IsVolume() && info.tile_mode != Prospero::GpuEnumValue(Prospero::TileMode::kLinear)) {
TileVolumeLayout volume {};
if (!TileGetTextureVolumeLayout(info.guest_format, info.extent.width, info.extent.height,
info.extent.depth, info.resources.levels, info.tile_mode,
volume)) {
EXIT("unsupported normalized volume texture layout\n");
}
for (uint32_t level = 0; level < info.resources.levels; level++) {
info.mip_layout[level] = {
volume.level_offsets[level],
volume.level_sizes[level],
volume.level_widths[level],
volume.level_heights[level],
};
}
return;
}
TileSizeOffset levels[16] {};
TilePaddedSize padded[16] {};
TileGetTextureSize(info.guest_format, info.extent.width, info.extent.height, info.pitch,
info.resources.levels, info.tile_mode, nullptr, levels, padded);
for (uint32_t level = 0; level < info.resources.levels; level++) {
const auto offset =
levels[level].src_size != 0 ? levels[level].src_offset : levels[level].offset;
auto size = static_cast<uint64_t>(levels[level].src_size != 0 ? levels[level].src_size
: levels[level].size);
if (info.IsVolume()) {
size *= std::max(info.extent.depth >> level, 1u);
} else {
size *= info.resources.layers;
}
info.mip_layout[level] = {
offset,
size,
padded[level].width != 0 ? padded[level].width : std::max(info.pitch >> level, 1u),
padded[level].height != 0 ? padded[level].height
: std::max(info.extent.height >> level, 1u),
};
}
}
static ImageViewInfo TextureViewInfo(const ShaderRecompiler::IR::ImageResource& resource,
const ShaderTextureResource& descriptor, vk::Format format,
bool storage, uint32_t view_levels, uint32_t image_layers) {
ImageViewInfo view {};
view.format = format;
view.aspect = vk::ImageAspectFlagBits::eColor;
view.base_level = descriptor.BaseLevel();
view.level_count = view_levels;
view.usage = storage ? vk::ImageUsageFlagBits::eStorage : vk::ImageUsageFlagBits::eSampled;
view.mapping =
storage ? vk::ComponentMapping {} : TextureGetComponentMapping(descriptor.DstSelXYZW());
switch (resource.dimension) {
case ShaderRecompiler::Decoder::ImageDimension::Dim1D:
view.type = vk::ImageViewType::e1D;
view.base_layer = descriptor.BaseArray5();
if (view.base_layer >= image_layers) {
EXIT("texture base layer is out of bounds\n");
}
view.layer_count = 1;
break;
case ShaderRecompiler::Decoder::ImageDimension::Dim1DArray:
view.type = vk::ImageViewType::e1DArray;
view.base_layer = descriptor.BaseArray5();
if (view.base_layer >= image_layers) {
EXIT("texture array base layer is out of bounds\n");
}
view.layer_count = image_layers - view.base_layer;
break;
case ShaderRecompiler::Decoder::ImageDimension::Dim3D:
view.type = vk::ImageViewType::e3D;
view.base_layer = 0;
view.layer_count = 1;
break;
case ShaderRecompiler::Decoder::ImageDimension::Dim2DArray:
view.type = vk::ImageViewType::e2DArray;
view.base_layer = descriptor.BaseArray5();
if (view.base_layer >= image_layers) {
EXIT("texture array base layer is out of bounds\n");
}
view.layer_count = image_layers - view.base_layer;
break;
case ShaderRecompiler::Decoder::ImageDimension::Dim2D:
view.type = vk::ImageViewType::e2D;
view.base_layer = descriptor.BaseArray5();
if (view.base_layer >= image_layers) {
EXIT("texture base layer is out of bounds\n");
}
view.layer_count = 1;
break;
default: EXIT("unsupported texture view dimension\n");
}
return view;
}
DescriptorCache::TextureBinding
RenderExecutor::ResolveTexture(const ShaderRecompiler::IR::ImageResource& resource,
const ShaderRecompiler::IR::DescriptorValue& value) {
ShaderTextureResource descriptor;
CopyNativeDescriptor(value, descriptor.fields);
const bool storage = resource.written;
if (storage) {
ValidateStorageImageResource(resource);
}
auto& texture_cache = m_context.GetTextureCache();
if (descriptor.IsNull()) {
auto desc = NullTextureDesc(resource, storage ? TextureCache::BindingType::Storage
: TextureCache::BindingType::Texture);
const auto id = texture_cache.FindImage(desc);
return {id, nullptr, std::move(desc)};
}
const auto address = descriptor.Base40();
const auto width = static_cast<uint32_t>(descriptor.Width5()) + 1u;
const auto height = static_cast<uint32_t>(descriptor.Height5()) + 1u;
const auto base_level = descriptor.BaseLevel();
const auto last_level = descriptor.LastLevel();
const auto type = TextureType(descriptor);
const bool multisampled =
type == Prospero::ImageType::kColor2DMsaa || type == Prospero::ImageType::kColor2DMsaaArray;
const auto levels = multisampled ? 1u : static_cast<uint32_t>(descriptor.MaxMip()) + 1u;
const auto tile = descriptor.TileMode();
const bool msaa_tile = tile == Prospero::GpuEnumValue(Prospero::TileMode::kRenderTarget);
const bool msaa_array = type == Prospero::ImageType::kColor2DMsaaArray;
if ((!multisampled && (base_level > last_level || last_level >= levels)) ||
(multisampled &&
(base_level != 0 || last_level == 0 || last_level > 3 ||
descriptor.MaxMip() != last_level || !msaa_tile || descriptor.MsaaDepth() ||
(!msaa_array && (descriptor.Depth() != 0 || descriptor.BaseArray5() != 0))))) {
EXIT("unsupported texture mip view: base=%u last=%u levels=%u\n", base_level, last_level,
levels);
}
const auto samples = multisampled ? 1u << last_level : 1u;
const auto view_levels =
multisampled ? 1u : static_cast<uint32_t>(last_level - base_level) + 1u;
const auto depth = static_cast<uint32_t>(descriptor.Depth()) + 1u;
const auto format = descriptor.Format();
const bool sampled_numeric_class =
storage || ((resource.kind == ShaderRecompiler::IR::ResourceKind::ImageUint) ==
Prospero::IsUintTextureFormat(format));
if (!storage &&
(resource.kind == ShaderRecompiler::IR::ResourceKind::Image ||
resource.kind == ShaderRecompiler::IR::ResourceKind::ImageUint) &&
!sampled_numeric_class) {
EXIT("sampled image numeric class mismatch: kind=%u format=%u addr=0x%016" PRIx64 "\n",
static_cast<uint32_t>(resource.kind), format, address);
}
const bool volume = type == Prospero::ImageType::kColor3D;
const bool layered = type == Prospero::ImageType::kColor1DArray ||
type == Prospero::ImageType::kColor2DArray ||
type == Prospero::ImageType::kColor2DMsaaArray;
const auto image_layers = layered ? depth : 1u;
uint32_t pitch = 0;
TileSizeAlign size {};
if (multisampled) {
const auto bytes = Prospero::NumBytesPerElement(format);
pitch = TileGetRenderTargetPitch(width, bytes, last_level);
if (pitch == 0 || !TileGetRenderTargetSize(width, height, pitch, bytes, size, last_level) ||
size.size > UINT32_MAX / image_layers) {
EXIT("unsupported multisample texture layout\n");
}
size.size *= image_layers;
} else {
pitch = TileGetTexturePitch(format, width, levels, tile);
TileGetTextureTotalSize(format, width, height, volume ? depth : image_layers, pitch, levels,
tile, volume, size);
}
EXIT_NOT_IMPLEMENTED(size.size == 0 || size.align == 0 ||
(address & (static_cast<uint64_t>(size.align) - 1u)) != 0);
if (storage) {
ValidateStorageTexture(resource, descriptor, size.size);
m_context.GetBufferCache().ValidateGpuAccess(address, size.size, resource.read,
resource.written);
}
const auto pixel_format = TextureGetFormat(format);
const auto storage_view_format = SrgbStorageViewFormat(pixel_format);
const auto view_format =
storage && storage_view_format != vk::Format::eUndefined ? storage_view_format
: pixel_format;
const auto block_bytes = Prospero::BlockCompressedBytesPerBlock(format);
TextureCache::ImageDesc desc {};
desc.info.data = {address, size.size};
desc.info.pixel_format = pixel_format;
desc.info.guest_format = format;
desc.info.type = TextureBaseType(type);
desc.info.extent = {width, height, volume ? depth : 1u};
desc.info.resources = {levels, image_layers};
desc.info.pitch = pitch;
desc.info.bytes_per_block =
block_bytes != 0 ? block_bytes : Prospero::NumBytesPerElement(format);
desc.info.samples = samples;
desc.info.tile_mode = tile;
if (samples > 1) {
desc.info.mip_layout[0] = {0, size.size, pitch, height};
} else {
PopulateTextureMipLayout(desc.info);
}
desc.view_info = TextureViewInfo(resource, descriptor, view_format, storage, view_levels,
desc.info.resources.layers);
desc.type = storage ? TextureCache::BindingType::Storage : TextureCache::BindingType::Texture;
auto id = texture_cache.FindImage(desc);
auto* image = &texture_cache.GetImage(id);
const bool stencil_association = static_cast<bool>(image->depth_id);
if (stencil_association) {
id = image->depth_id;
image = &texture_cache.GetImage(id);
} else if (image->info.IsDepth()) {
if (storage) {
EXIT("depth target cannot be bound as a storage image\n");
}
ValidateDepthTargetBinding(resource, descriptor, image, pixel_format, size.size);
(void)SelectSampledDepthView(image->info.pixel_format, pixel_format,
descriptor.DstSelXYZW());
} else if (storage) {
ValidateStorageColorView(image->info.pixel_format, view_format, descriptor.DstSelXYZW());
} else {
(void)SelectSampledColorView(image->info.pixel_format, pixel_format,
descriptor.DstSelXYZW());
}
return {id, nullptr, std::move(desc)};
}
static vk::Sampler NativeSampler(RenderContext& context,
const ShaderRecompiler::IR::Program& program, uint32_t index,
const ShaderRecompiler::IR::DescriptorValue& value) {
ShaderSamplerResource descriptor;
CopyNativeDescriptor(value, descriptor.fields);
const bool depth_compare = std::any_of(program.info.sampled_pairs.begin(),
program.info.sampled_pairs.end(), [&](const auto& pair) {
return pair.sampler == index &&
pair.image < program.info.images.size() &&
program.info.images[pair.image].depth_compare;
});
if (!depth_compare) {
descriptor.fields[0] &= ~(0x7u << 12u);
}
return context.GetSamplerCache().GetSampler(descriptor);
}
static BufferView NativeUpload(RenderContext& context, CommandBuffer& command_buffer,
std::span<const uint32_t> data) {
EXIT_IF(data.empty());
EXIT_IF(command_buffer.IsInvalid() || command_buffer.IsExecute());
auto binding = context.GetBufferCache().UploadTransient(data.data(), data.size_bytes(), 256);
return {.owner = std::move(binding.owner),
.buffer = binding.buffer,
.offset = binding.offset,
.range = data.size_bytes()};
}
void RenderExecutor::TrackImageBinding(ImageId id) {
auto image = m_context.GetTextureCache().ResolveOwner(id);
EXIT_IF(image == nullptr);
if (std::ranges::find(m_bound_images, image) == m_bound_images.end()) {
m_bound_images.push_back(std::move(image));
}
}
void RenderExecutor::BindImage(ImageId id, bool storage) {
auto& image = m_context.GetTextureCache().GetImage(id);
if (image.info.data.Empty()) {
return;
}
if (image.binding.is_bound) {
image.binding.force_general |= storage;
}
image.binding.is_bound = true;
TrackImageBinding(id);
}
void RenderExecutor::BindRenderTarget(ImageId id) {
auto& image = m_context.GetTextureCache().GetImage(id);
image.binding.is_target = true;
TrackImageBinding(id);
}
void RenderExecutor::ResetBindings() {
for (const auto& image: m_bound_images) {
image->binding = {};
}
m_bound_images.clear();
}
DescriptorCache::PreparedBindings RenderExecutor::PrepareBindings(CommandBuffer& buffer,
const ShaderStageRuntime& runtime,
vk::ShaderStageFlags shader_stage,
DescriptorCache::Stage stage) {
KYTY_PROFILER_FUNCTION();
EXIT_IF(!runtime);
const auto& program = *runtime.program;
const auto& snapshot = *runtime.resources;
std::string error;
if (!ShaderRecompiler::IR::ValidateResourceSpecialization(program, snapshot, &error)) {
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;
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++) {
auto binding = ResolveTexture(program.info.images[i], snapshot.images[i]);
BindImage(binding.image_id, binding.desc.type == TextureCache::BindingType::Storage);
descriptors.images.push_back(binding);
}
descriptors.samplers.reserve(program.info.samplers.size());
for (uint32_t i = 0; i < program.info.samplers.size(); i++) {
descriptors.samplers.push_back(NativeSampler(m_context, program, i, snapshot.samplers[i]));
}
if (ShaderRecompiler::IR::FindBinding(
program.bindings, ShaderRecompiler::IR::DescriptorBindingKind::FlattenedSrt) !=
nullptr) {
prepared.flattened_srt.assign(snapshot.flattened_srt.begin(), snapshot.flattened_srt.end());
}
prepared.user_data.reserve(program.bindings.ShaderDataDwords());
for (const auto reg: program.bindings.user_data_registers) {
prepared.user_data.push_back(snapshot.user_data[reg - program.user_data_base]);
}
prepared.user_data.resize(program.bindings.ShaderDataDwords());
if (ShaderRecompiler::IR::FindBinding(
program.bindings, ShaderRecompiler::IR::DescriptorBindingKind::Gds) != nullptr) {
descriptors.gds.buffer = m_context.GetBufferCache().GetGdsBuffer().Handle();
}
return prepared;
}
void RenderExecutor::RebindBuffers(CommandBuffer& buffer,
DescriptorCache::PreparedBindings& prepared) {
KYTY_PROFILER_FUNCTION();
EXIT_IF(prepared.program == nullptr || prepared.snapshot == nullptr);
const auto& program = *prepared.program;
const auto& snapshot = *prepared.snapshot;
auto& resources = prepared.resources;
const auto& layout = program.bindings;
resources.buffers.clear();
resources.buffers.reserve(program.info.buffers.size());
EXIT_IF(prepared.user_data.size() != layout.ShaderDataDwords());
std::fill(prepared.user_data.begin() + layout.buffer_offset_dword, prepared.user_data.end(), 0);
for (uint32_t i = 0; i < program.info.buffers.size(); i++) {
ShaderBufferResource descriptor;
CopyNativeDescriptor(snapshot.buffers[i], descriptor.fields);
uint32_t buffer_offset = 0;
resources.buffers.push_back(NativeStorageBuffer(m_context, buffer, descriptor,
program.info.buffers[i], buffer_offset));
const auto dword = layout.buffer_offset_dword + i / 4u;
const auto shift = (i % 4u) * 8u;
prepared.user_data[dword] |= buffer_offset << shift;
}
resources.addresses.clear();
resources.addresses.reserve(program.info.addresses.size());
for (uint32_t i = 0; i < program.info.addresses.size(); i++) {
resources.addresses.push_back(NativeAddressBuffer(
m_context, buffer, program.info.addresses[i], snapshot.addresses[i]));
}
}
void RenderExecutor::RebindImages(CommandBuffer& buffer,
DescriptorCache::PreparedBindings& prepared) {
KYTY_PROFILER_FUNCTION();
EXIT_IF(prepared.program == nullptr || prepared.snapshot == nullptr);
const auto& program = *prepared.program;
const auto& snapshot = *prepared.snapshot;
auto& images = prepared.resources.images;
EXIT_IF(images.size() != program.info.images.size());
auto& texture_cache = m_context.GetTextureCache();
for (uint32_t i = 0; i < program.info.images.size(); i++) {
const auto old_image = texture_cache.ResolveOwner(images[i].image_id);
if (old_image == nullptr || (!old_image->registered && !old_image->info.data.Empty()) ||
old_image->binding.needs_rebind) {
if (old_image != nullptr) {
old_image->binding = {};
}
images[i] = ResolveTexture(program.info.images[i], snapshot.images[i]);
BindImage(images[i].image_id,
images[i].desc.type == TextureCache::BindingType::Storage);
}
auto& binding = images[i];
binding.image_view = texture_cache.FindTexture(binding.image_id, binding.desc);
auto& image = texture_cache.GetImage(binding.image_id);
const bool storage = binding.desc.type == TextureCache::BindingType::Storage;
image.usage.storage |= storage;
image.usage.texture |= !storage;
}
}
RenderExecutor::GraphicsBindings
RenderExecutor::PrepareGraphicsBindings(CommandBuffer& buffer, const ShaderStageRuntime& vertex,
const ShaderStageRuntime& pixel, bool pixel_active) {
GraphicsBindings bindings {
.vertex = PrepareBindings(buffer, vertex, vk::ShaderStageFlagBits::eVertex,
DescriptorCache::Stage::Vertex),
};
RebindBuffers(buffer, bindings.vertex);
RebindImages(buffer, bindings.vertex);
if (pixel_active) {
bindings.pixel.emplace(PrepareBindings(buffer, pixel, vk::ShaderStageFlagBits::eFragment,
DescriptorCache::Stage::Pixel));
RebindBuffers(buffer, *bindings.pixel);
RebindImages(buffer, *bindings.pixel);
}
return bindings;
}
static void RetainBindings(CommandBuffer& buffer,
const DescriptorCache::NativeDescriptors& resources) {
if (resources.flattened_srt.owner != nullptr) {
buffer.RetainResourceUntilFence(resources.flattened_srt.owner);
}
if (resources.user_data.owner != nullptr) {
buffer.RetainResourceUntilFence(resources.user_data.owner);
}
for (const auto& view: resources.buffers) {
if (view.owner != nullptr) {
buffer.RetainResourceUntilFence(view.owner);
}
}
for (const auto& view: resources.addresses) {
if (view.owner != nullptr) {
buffer.RetainResourceUntilFence(view.owner);
}
}
}
void RenderExecutor::CommitBindings(CommandBuffer& buffer,
vk::PipelineBindPoint pipeline_bind_point,
vk::PipelineLayout layout,
DescriptorCache::PreparedBindings& prepared) {
KYTY_PROFILER_FUNCTION();
EXIT_IF(prepared.program == nullptr || prepared.stage == DescriptorCache::Stage::Unknown ||
prepared.committed);
const auto& program = *prepared.program;
auto& descriptors = prepared.resources;
if (!prepared.flattened_srt.empty()) {
descriptors.flattened_srt = NativeUpload(m_context, buffer, prepared.flattened_srt);
}
if (ShaderRecompiler::IR::FindBinding(
program.bindings, ShaderRecompiler::IR::DescriptorBindingKind::UserData) != nullptr) {
descriptors.user_data = NativeUpload(m_context, buffer, prepared.user_data);
}
RetainBindings(buffer, descriptors);
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()) {
image.Transit(vk::ImageLayout::eGeneral,
vk::AccessFlagBits2::eShaderRead |
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;
}
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);
}
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());
}
prepared.committed = true;
}
} // namespace Libs::Graphics
@@ -0,0 +1,48 @@
#ifndef EMULATOR_SRC_GRAPHICS_HOST_GPU_RENDERER_DESCRIPTORS_H_
#define EMULATOR_SRC_GRAPHICS_HOST_GPU_RENDERER_DESCRIPTORS_H_
#include "common/assert.h"
#include "graphics/host_gpu/renderer/image/image.h"
#include "graphics/host_gpu/vulkanCommon.h"
#include "graphics/shader/recompiler/ir/ShaderIR.h"
#include "graphics/shader/shaderBindings.h"
#include <cstdint>
#include <cstring>
#include <type_traits>
namespace Libs::Graphics {
template <typename T>
[[nodiscard]] T DecodeNativeDescriptor(const ShaderRecompiler::IR::DescriptorValue& value) {
static_assert(std::is_trivially_copyable_v<T>);
static_assert(sizeof(T) % sizeof(uint32_t) == 0);
T result {};
EXIT_IF(value.dword_count < sizeof(result) / sizeof(uint32_t));
std::memcpy(&result, value.dwords.data(), sizeof(result));
return result;
}
struct TargetTextureViewInfo {
vk::ImageViewType type = static_cast<vk::ImageViewType>(VK_IMAGE_VIEW_TYPE_MAX_ENUM);
uint32_t base_layer = 0;
uint32_t layer_count = 0;
};
[[nodiscard]] TargetTextureViewInfo
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);
[[nodiscard]] bool IsSupportedDepthTextureEncoding(const ShaderTextureResource& descriptor,
const Image& image);
[[nodiscard]] bool
IsSupportedSampledVideoOutView(const ShaderRecompiler::IR::ImageResource& resource,
const ShaderTextureResource& descriptor, const Image& image);
void ValidateStorageTexture(const ShaderRecompiler::IR::ImageResource& resource,
const ShaderTextureResource& descriptor, uint64_t size);
} // namespace Libs::Graphics
#endif // EMULATOR_SRC_GRAPHICS_HOST_GPU_RENDERER_DESCRIPTORS_H_
@@ -0,0 +1,258 @@
#include "graphics/host_gpu/renderer/pipeline/pipelineCache.h"
#include "common/assert.h"
#include "common/logging/log.h"
#include "common/profiler.h"
#include "graphics/guest_gpu/hardwareContext.h"
#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/image/imageView.h"
#include "graphics/host_gpu/renderer/render.h"
#include "graphics/host_gpu/renderer/renderContext.h"
#include <atomic>
#include <cstring>
#include <span>
#include <utility>
namespace Libs::Graphics {
namespace {
void NormalizeStaticParamsForDynamicState(PipelineStaticParameters& static_params) {
static_params.viewport_scale[0] = 0.5f;
static_params.viewport_scale[1] = 0.5f;
static_params.viewport_scale[2] = 1.0f;
static_params.viewport_offset[0] = 0.5f;
static_params.viewport_offset[1] = 0.5f;
static_params.viewport_offset[2] = 0.0f;
static_params.scissor_ltrb[0] = 0;
static_params.scissor_ltrb[1] = 0;
static_params.scissor_ltrb[2] = 1;
static_params.scissor_ltrb[3] = 1;
}
} // namespace
PipelineCache::~PipelineCache() {
auto destroy = [this](const auto& pipelines) {
for (const auto& [key, pipeline]: pipelines) {
(void)key;
m_graphics.device.destroyPipeline(pipeline->pipeline, nullptr);
m_graphics.device.destroyPipelineLayout(pipeline->pipeline_layout, nullptr);
}
};
destroy(m_graphics_pipelines);
destroy(m_compute_pipelines);
}
bool PipelineStaticParameters::operator==(const PipelineStaticParameters& other) const noexcept {
return std::memcmp(this, &other, sizeof(*this)) == 0;
}
PipelineCache::GraphicsPipeline& PipelineCache::CreateGraphicsPipeline(
RenderColorInfo* colors, uint32_t color_count, RenderDepthInfo& depth,
ShaderVertexInputInfo& vs_input_info, RenderCommandBuffer& command,
ShaderPixelInputInfo* ps_input_info, vk::PrimitiveTopology topology, bool ps_active,
std::span<const uint32_t> vs_spirv, std::span<const uint32_t> ps_spirv) {
KYTY_PROFILER_BLOCK("PipelineCache::CreatePipeline(Gfx)", profiler::colors::DeepOrangeA200);
EXIT_IF(colors == nullptr);
EXIT_IF(color_count > RENDER_COLOR_ATTACHMENTS_MAX);
EXIT_IF(vs_spirv.empty());
EXIT_IF(ps_active && ps_spirv.empty());
Common::LockGuard lock(m_mutex);
auto& ctx = command.GetRegisters();
auto& sh_ctx = command.GetShaders();
const auto& vertex_info = sh_ctx.GetVs();
const auto& ps_regs = sh_ctx.GetPs();
const HW::BlendColor& bclr = ctx.GetBlendColor();
uint32_t color_mask[RENDER_COLOR_ATTACHMENTS_MAX] = {};
for (uint32_t i = 0; i < color_count; i++) {
color_mask[i] =
(colors[i].image_id ? colors[i].export_mapping.ApplyMask(render_target_mask_slot(
ctx.GetRenderTargetMask(), colors[i].target_slot))
: 0);
}
const HW::ModeControl& mc = ctx.GetModeControl();
auto vs_id = ShaderGetIdVS(vertex_info, vs_input_info, true);
ShaderId ps_id {};
if (ps_active) {
ps_id = ShaderGetIdPS(ps_regs, *ps_input_info, true);
}
PipelineStaticParameters static_params {};
GraphicsPipeline p {};
p.ps_shader_id = ps_id;
p.vs_shader_id = vs_id;
static_params.color_count = color_count;
PipelineRenderingState rendering {};
rendering.color_count = color_count;
uint32_t attachment_samples = 0;
for (uint32_t i = 0; i < color_count; i++) {
EXIT_IF(!colors[i].image_id || colors[i].format == vk::Format::eUndefined);
rendering.color_formats[i] = colors[i].format;
if (attachment_samples == 0) {
attachment_samples = colors[i].samples;
} else if (attachment_samples != colors[i].samples) {
EXIT("mixed color attachment sample counts are unsupported: %u and %u\n",
attachment_samples, colors[i].samples);
}
}
const bool with_depth =
depth.format != vk::Format::eUndefined && static_cast<bool>(depth.image_id);
if (with_depth) {
const auto aspects = ImageViewOps::DepthAspectMask(depth.format);
rendering.depth_format =
aspects & vk::ImageAspectFlagBits::eDepth ? depth.format : vk::Format::eUndefined;
rendering.stencil_format =
aspects & vk::ImageAspectFlagBits::eStencil ? depth.format : vk::Format::eUndefined;
if (attachment_samples == 0) {
attachment_samples = depth.samples;
} else if (attachment_samples != depth.samples) {
EXIT("mixed color/depth sample counts are unsupported: %u and %u\n",
attachment_samples, depth.samples);
}
}
EXIT_IF(attachment_samples == 0 ||
vulkan_sample_count(attachment_samples) == vk::SampleCountFlagBits {});
if (ps_active && depth.depth_test_enable && ps_input_info->ps_execute_on_noop) {
static std::atomic<uint32_t> log_count {0};
if (log_count.fetch_add(1, std::memory_order_relaxed) < 16) {
LOGF("Pipeline: temporary: accepting EXEC_ON_NOOP with depth test enabled\n");
}
}
const auto& clip_control = ctx.GetClipControl();
EXIT_NOT_IMPLEMENTED(!clip_control.IsZClipModeRepresentable());
static_params.negative_one_to_one = !clip_control.dx_clip_space;
static_params.depth_clip_enable = clip_control.IsZClipEnabled();
static_params.topology = topology;
static_params.samples = attachment_samples;
static_params.sample_shading_enable =
ps_active && attachment_samples > 1 && ps_input_info->ps_sample_shading;
if (static_params.sample_shading_enable && !m_graphics.sample_rate_shading_enabled) {
EXIT("Pipeline: sample-rate shading is required but unsupported by the host\n");
}
static_params.with_depth = with_depth;
static_params.depth_test_enable = depth.depth_test_enable;
static_params.depth_write_enable = (depth.depth_write_enable && !depth.depth_clear_enable);
static_params.depth_compare_op = depth.depth_compare_op;
static_params.depth_bounds_test_enable = depth.depth_bounds_test_enable;
static_params.depth_min_bounds = depth.depth_min_bounds;
static_params.depth_max_bounds = depth.depth_max_bounds;
static_params.stencil_test_enable = depth.stencil_test_enable;
static_params.stencil_front = depth.stencil_static_front;
static_params.stencil_back = depth.stencil_static_back;
for (uint32_t i = 0; i < RENDER_COLOR_ATTACHMENTS_MAX; i++) {
static_params.color_mask[i] = color_mask[i];
}
static_params.cull_back = mc.cull_back;
static_params.cull_front = mc.cull_front;
static_params.face = mc.face;
for (uint32_t i = 0; i < color_count; i++) {
const auto& rt = ctx.GetRenderTarget(colors[i].target_slot);
const auto& bc = ctx.GetBlendControl(colors[i].target_slot);
static_params.color_srcblend[i] = bc.color_srcblend;
static_params.color_comb_fcn[i] = bc.color_comb_fcn;
static_params.color_destblend[i] = bc.color_destblend;
static_params.alpha_srcblend[i] = bc.alpha_srcblend;
static_params.alpha_comb_fcn[i] = bc.alpha_comb_fcn;
static_params.alpha_destblend[i] = bc.alpha_destblend;
static_params.separate_alpha_blend[i] = bc.separate_alpha_blend;
static_params.blend_enable[i] = bc.enable;
static_params.blend_bypass[i] = rt.info.blend_bypass;
}
static_params.blend_color_red = bclr.red;
static_params.blend_color_green = bclr.green;
static_params.blend_color_blue = bclr.blue;
static_params.blend_color_alpha = bclr.alpha;
NormalizeStaticParamsForDynamicState(static_params);
GraphicsPipelineKey key {};
key.rendering = rendering;
key.vs_shader_id = p.vs_shader_id;
key.ps_shader_id = p.ps_shader_id;
key.static_params = static_params;
if (auto iter = m_graphics_pipelines.find(key); iter != m_graphics_pipelines.end()) {
return *iter->second;
}
if (graphics_debug_dump_enabled()) {
ShaderDbgDumpInputInfo(vs_input_info);
if (ps_active) {
ShaderDbgDumpInputInfo(*ps_input_info);
}
LOGF("PipelineTrace: shader binaries VS=0x%08" PRIx32 "/0x%08" PRIx32 " words=%" PRIu64
" PS=0x%08" PRIx32 "/0x%08" PRIx32 " words=%" PRIu64 "\n",
vs_id.hash0, vs_id.crc32, static_cast<uint64_t>(vs_spirv.size()), ps_id.hash0,
ps_id.crc32, static_cast<uint64_t>(ps_spirv.size()));
}
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);
LogPipelineTrace("CreatePipelineInternal done", vs_id.hash0, vs_id.crc32, ps_id.hash0,
ps_id.crc32);
EXIT_NOT_IMPLEMENTED(cached->pipeline == nullptr);
EXIT_NOT_IMPLEMENTED(cached->pipeline_layout == nullptr);
auto [iter, inserted] = m_graphics_pipelines.emplace(std::move(key), std::move(cached));
EXIT_IF(!inserted);
return *iter->second;
}
PipelineCache::ComputePipeline&
PipelineCache::CreateComputePipeline(ShaderComputeInputInfo& input_info,
const HW::ComputeShaderInfo& cs_regs,
std::span<const uint32_t> cs_spirv) {
KYTY_PROFILER_BLOCK("PipelineCache::CreatePipeline(Compute)", profiler::colors::RedA100);
EXIT_IF(cs_spirv.empty());
Common::LockGuard lock(m_mutex);
auto cs_id = ShaderGetIdCS(cs_regs, input_info, true);
ComputePipeline p {};
p.cs_shader_id = cs_id;
ComputePipelineKey key {};
key.cs_shader_id = p.cs_shader_id;
if (auto iter = m_compute_pipelines.find(key); iter != m_compute_pipelines.end()) {
return *iter->second;
}
if (graphics_debug_dump_enabled()) {
ShaderDbgDumpInputInfo(input_info);
}
auto cached = std::make_unique<ComputePipeline>(p);
CreatePipelineInternal(m_graphics, m_descriptor_cache, *cached, input_info, cs_spirv);
EXIT_NOT_IMPLEMENTED(cached->pipeline == nullptr);
EXIT_NOT_IMPLEMENTED(cached->pipeline_layout == nullptr);
auto [iter, inserted] = m_compute_pipelines.emplace(std::move(key), std::move(cached));
EXIT_IF(!inserted);
return *iter->second;
}
} // namespace Libs::Graphics
@@ -0,0 +1,231 @@
#ifndef EMULATOR_SRC_GRAPHICS_HOST_GPU_RENDERER_PIPELINECACHE_H_
#define EMULATOR_SRC_GRAPHICS_HOST_GPU_RENDERER_PIPELINECACHE_H_
#include "common/abi.h"
#include "common/assert.h"
#include "common/common.h"
#include "common/threads.h"
#include "graphics/host_gpu/renderer/renderTarget.h"
#include "graphics/host_gpu/vulkanCommon.h"
#include "graphics/shader/shader.h"
#include <cstddef>
#include <memory>
#include <span>
#include <type_traits>
#include <unordered_map>
namespace Libs::Graphics {
struct GraphicContext;
struct RenderColorInfo;
struct RenderDepthInfo;
class RenderCommandBuffer;
class DescriptorCache;
namespace HW {
class Context;
class Shader;
struct ComputeShaderInfo;
} // namespace HW
#pragma pack(push, 1)
struct PipelineStaticParameters {
float viewport_scale[3] = {};
float viewport_offset[3] = {};
bool negative_one_to_one = false;
bool depth_clip_enable = true;
int scissor_ltrb[4] = {0};
vk::PrimitiveTopology topology = vk::PrimitiveTopology::ePointList;
uint32_t samples = 1;
bool sample_shading_enable = false;
bool with_depth = false;
bool depth_test_enable = false;
bool depth_write_enable = false;
vk::CompareOp depth_compare_op = vk::CompareOp::eNever;
bool depth_bounds_test_enable = false;
float depth_min_bounds = 0.0f;
float depth_max_bounds = 0.0f;
bool stencil_test_enable = false;
PipelineStencilStaticState stencil_front;
PipelineStencilStaticState stencil_back;
uint32_t color_count = 1;
uint32_t color_mask[RENDER_COLOR_ATTACHMENTS_MAX] = {};
bool cull_front = false;
bool cull_back = false;
bool face = false;
uint8_t color_srcblend[RENDER_COLOR_ATTACHMENTS_MAX] = {};
uint8_t color_comb_fcn[RENDER_COLOR_ATTACHMENTS_MAX] = {};
uint8_t color_destblend[RENDER_COLOR_ATTACHMENTS_MAX] = {};
uint8_t alpha_srcblend[RENDER_COLOR_ATTACHMENTS_MAX] = {};
uint8_t alpha_comb_fcn[RENDER_COLOR_ATTACHMENTS_MAX] = {};
uint8_t alpha_destblend[RENDER_COLOR_ATTACHMENTS_MAX] = {};
bool separate_alpha_blend[RENDER_COLOR_ATTACHMENTS_MAX] = {};
bool blend_enable[RENDER_COLOR_ATTACHMENTS_MAX] = {};
bool blend_bypass[RENDER_COLOR_ATTACHMENTS_MAX] = {};
float blend_color_red = 0.0f;
float blend_color_green = 0.0f;
float blend_color_blue = 0.0f;
float blend_color_alpha = 0.0f;
bool operator==(const PipelineStaticParameters& other) const noexcept;
};
#pragma pack(pop)
static_assert(std::is_trivially_copyable_v<PipelineStaticParameters>);
static_assert(std::is_standard_layout_v<PipelineStaticParameters>);
static_assert(alignof(PipelineStaticParameters) == 1);
static_assert(sizeof(PipelineStaticParameters) ==
sizeof(float[3]) + sizeof(float[3]) + sizeof(bool) * 2 + sizeof(int[4]) +
sizeof(vk::PrimitiveTopology) + sizeof(uint32_t) + sizeof(bool) * 4 +
sizeof(vk::CompareOp) + sizeof(bool) + sizeof(float) * 2 + sizeof(bool) +
sizeof(PipelineStencilStaticState) * 2 + sizeof(uint32_t) +
sizeof(uint32_t[RENDER_COLOR_ATTACHMENTS_MAX]) + sizeof(bool) * 3 +
sizeof(uint8_t[RENDER_COLOR_ATTACHMENTS_MAX]) * 6 +
sizeof(bool[RENDER_COLOR_ATTACHMENTS_MAX]) * 3 + sizeof(float) * 4);
struct PipelineRenderingState {
std::array<vk::Format, RENDER_COLOR_ATTACHMENTS_MAX> color_formats {};
vk::Format depth_format = vk::Format::eUndefined;
vk::Format stencil_format = vk::Format::eUndefined;
uint32_t color_count = 0;
bool operator==(const PipelineRenderingState&) const = default;
};
class PipelineCache {
public:
PipelineCache(GraphicContext& graphics, DescriptorCache& descriptor_cache)
: m_graphics(graphics), m_descriptor_cache(descriptor_cache) {
EXIT_NOT_IMPLEMENTED(!Common::Thread::IsMainThread());
}
~PipelineCache();
KYTY_CLASS_NO_COPY(PipelineCache);
struct Pipeline {
vk::PipelineLayout pipeline_layout = nullptr;
vk::Pipeline pipeline = nullptr;
};
struct GraphicsPipeline: Pipeline {
ShaderId vs_shader_id;
ShaderId ps_shader_id;
};
struct ComputePipeline: Pipeline {
ShaderId cs_shader_id;
};
GraphicsPipeline& CreateGraphicsPipeline(
RenderColorInfo* colors, uint32_t color_count, RenderDepthInfo& depth,
ShaderVertexInputInfo& vs_input_info, RenderCommandBuffer& command,
ShaderPixelInputInfo* ps_input_info, vk::PrimitiveTopology topology, bool ps_active,
std::span<const uint32_t> vs_spirv, std::span<const uint32_t> ps_spirv);
ComputePipeline& CreateComputePipeline(ShaderComputeInputInfo& input_info,
const HW::ComputeShaderInfo& cs_regs,
std::span<const uint32_t> cs_spirv);
private:
struct GraphicsPipelineKey {
PipelineRenderingState rendering;
ShaderId vs_shader_id;
ShaderId ps_shader_id;
PipelineStaticParameters static_params;
bool operator==(const GraphicsPipelineKey& other) const {
return rendering == other.rendering && vs_shader_id == other.vs_shader_id &&
ps_shader_id == other.ps_shader_id && static_params == other.static_params;
}
};
struct ComputePipelineKey {
ShaderId cs_shader_id;
bool operator==(const ComputePipelineKey& other) const {
return cs_shader_id == other.cs_shader_id;
}
};
struct PipelineKeyHash {
static void Mix(std::size_t& hash, std::size_t value) {
hash ^= value + static_cast<std::size_t>(0x9e3779b97f4a7c15ull) + (hash << 6u) +
(hash >> 2u);
}
static void MixShaderId(std::size_t& hash, const ShaderId& id) {
Mix(hash, id.hash0);
Mix(hash, id.crc32);
Mix(hash, id.ids.size());
for (auto value: id.ids) {
Mix(hash, value);
}
}
static void MixStaticParams(std::size_t& hash, const PipelineStaticParameters& params) {
const auto* bytes = reinterpret_cast<const uint8_t*>(&params);
for (std::size_t i = 0; i < sizeof(params); i++) {
Mix(hash, bytes[i]);
}
}
static void MixRendering(std::size_t& hash, const PipelineRenderingState& rendering) {
Mix(hash, rendering.color_count);
for (uint32_t i = 0; i < rendering.color_count; i++) {
Mix(hash, static_cast<uint32_t>(rendering.color_formats[i]));
}
Mix(hash, static_cast<uint32_t>(rendering.depth_format));
Mix(hash, static_cast<uint32_t>(rendering.stencil_format));
}
};
struct GraphicsPipelineKeyHash {
std::size_t operator()(const GraphicsPipelineKey& key) const {
std::size_t hash = 0;
PipelineKeyHash::MixRendering(hash, key.rendering);
PipelineKeyHash::MixShaderId(hash, key.vs_shader_id);
PipelineKeyHash::MixShaderId(hash, key.ps_shader_id);
PipelineKeyHash::MixStaticParams(hash, key.static_params);
return hash;
}
};
struct ComputePipelineKeyHash {
std::size_t operator()(const ComputePipelineKey& key) const {
std::size_t hash = 0;
PipelineKeyHash::MixShaderId(hash, key.cs_shader_id);
return hash;
}
};
GraphicContext& m_graphics;
DescriptorCache& m_descriptor_cache;
std::unordered_map<GraphicsPipelineKey, std::unique_ptr<GraphicsPipeline>,
GraphicsPipelineKeyHash>
m_graphics_pipelines;
std::unordered_map<ComputePipelineKey, std::unique_ptr<ComputePipeline>, ComputePipelineKeyHash>
m_compute_pipelines;
Common::Mutex m_mutex;
};
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);
} // namespace Libs::Graphics
#endif // EMULATOR_SRC_GRAPHICS_HOST_GPU_RENDERER_PIPELINECACHE_H_
@@ -0,0 +1,99 @@
#include "graphics/host_gpu/renderer/pipeline/shaderResourceBarrier.h"
#include "common/assert.h"
#include "graphics/shader/shader.h"
#include "graphics/shader/shaderBindings.h"
#include <cstring>
namespace Libs::Graphics {
vk::PipelineStageFlags ShaderPipelineStages(vk::ShaderStageFlags stages) {
vk::PipelineStageFlags result = {};
if (stages & vk::ShaderStageFlagBits::eVertex) {
result |= vk::PipelineStageFlagBits::eVertexShader;
}
if (stages & vk::ShaderStageFlagBits::eFragment) {
result |= vk::PipelineStageFlagBits::eFragmentShader;
}
if (stages & vk::ShaderStageFlagBits::eCompute) {
result |= vk::PipelineStageFlagBits::eComputeShader;
}
EXIT_IF(!result);
return result;
}
VulkanMemoryBarrier MakeShaderWriteDependency() {
VulkanMemoryBarrier barrier {};
barrier.sType = vk::StructureType::eMemoryBarrier;
barrier.srcAccessMask = vk::AccessFlagBits::eShaderWrite;
barrier.dstAccessMask = vk::AccessFlagBits::eShaderRead | vk::AccessFlagBits::eShaderWrite |
vk::AccessFlagBits::eVertexAttributeRead |
vk::AccessFlagBits::eIndexRead | vk::AccessFlagBits::eUniformRead |
vk::AccessFlagBits::eTransferRead | vk::AccessFlagBits::eTransferWrite |
vk::AccessFlagBits::eColorAttachmentRead |
vk::AccessFlagBits::eColorAttachmentWrite;
return barrier;
}
vk::BufferMemoryBarrier MakeGdsDependency(vk::Buffer buffer) {
EXIT_IF(buffer == nullptr);
vk::BufferMemoryBarrier barrier {};
barrier.sType = vk::StructureType::eBufferMemoryBarrier;
barrier.srcAccessMask = vk::AccessFlagBits::eHostWrite | vk::AccessFlagBits::eTransferWrite |
vk::AccessFlagBits::eShaderWrite;
barrier.dstAccessMask = vk::AccessFlagBits::eShaderRead | vk::AccessFlagBits::eShaderWrite;
barrier.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
barrier.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
barrier.buffer = buffer;
barrier.offset = 0;
barrier.size = VK_WHOLE_SIZE;
return barrier;
}
std::vector<ShaderBufferWriteRange>
CollectShaderBufferWrites(const ShaderRecompiler::IR::Program& program,
const ShaderRecompiler::IR::ResourceSnapshot& resources) {
EXIT_IF(resources.buffers.size() != program.info.buffers.size());
std::vector<ShaderBufferWriteRange> writes;
for (uint32_t i = 0; i < program.info.buffers.size(); i++) {
if (!program.info.buffers[i].written) {
continue;
}
const auto& value = resources.buffers[i];
EXIT_IF(value.dword_count < 4);
ShaderBufferResource descriptor;
std::memcpy(descriptor.fields, value.dwords.data(), sizeof(descriptor.fields));
const auto address = descriptor.Base48();
const auto records = static_cast<uint64_t>(descriptor.NumRecords());
const auto stride = static_cast<uint64_t>(descriptor.Stride());
if (stride != 0 && records > UINT64_MAX / stride) {
EXIT("shader resource barrier buffer footprint overflow\n");
}
const auto size = stride == 0 ? records : stride * records;
if (address != 0 && size != 0) {
writes.push_back({address, size});
}
}
return writes;
}
bool HasShaderBufferWrites(const ShaderStageRuntime& runtime) {
EXIT_IF(!runtime);
return !CollectShaderBufferWrites(*runtime.program, *runtime.resources).empty();
}
void ShaderWriteBarrier(vk::CommandBuffer vk_buffer, vk::PipelineStageFlags source_stages) {
EXIT_IF(vk_buffer == nullptr || !source_stages);
const auto barrier = MakeShaderWriteDependency();
vk_buffer.pipelineBarrier(
source_stages,
vk::PipelineStageFlagBits::eComputeShader | vk::PipelineStageFlagBits::eVertexInput |
vk::PipelineStageFlagBits::eVertexShader | vk::PipelineStageFlagBits::eFragmentShader |
vk::PipelineStageFlagBits::eTransfer |
vk::PipelineStageFlagBits::eColorAttachmentOutput,
vk::DependencyFlags {}, 1, &barrier, 0, nullptr, 0, nullptr);
}
} // namespace Libs::Graphics
@@ -0,0 +1,31 @@
#ifndef EMULATOR_SRC_GRAPHICS_HOST_GPU_RENDERER_SHADERRESOURCEBARRIER_H_
#define EMULATOR_SRC_GRAPHICS_HOST_GPU_RENDERER_SHADERRESOURCEBARRIER_H_
#include "graphics/host_gpu/graphicContext.h"
#include "graphics/shader/recompiler/ir/ResourceMaterialization.h"
#include <vector>
namespace Libs::Graphics {
struct ShaderStageRuntime;
struct ShaderBufferWriteRange {
uint64_t address = 0;
uint64_t size = 0;
bool operator==(const ShaderBufferWriteRange& other) const = default;
};
vk::PipelineStageFlags ShaderPipelineStages(vk::ShaderStageFlags stages);
VulkanMemoryBarrier MakeShaderWriteDependency();
vk::BufferMemoryBarrier MakeGdsDependency(vk::Buffer buffer);
std::vector<ShaderBufferWriteRange>
CollectShaderBufferWrites(const ShaderRecompiler::IR::Program& program,
const ShaderRecompiler::IR::ResourceSnapshot& resources);
bool HasShaderBufferWrites(const ShaderStageRuntime& runtime);
void ShaderWriteBarrier(vk::CommandBuffer vk_buffer, vk::PipelineStageFlags source_stages);
} // namespace Libs::Graphics
#endif // EMULATOR_SRC_GRAPHICS_HOST_GPU_RENDERER_SHADERRESOURCEBARRIER_H_
@@ -0,0 +1,60 @@
#include "graphics/host_gpu/renderer/pipeline/shaderSubgroup.h"
#include "graphics/shader/recompiler/emitter/SpirvEmitter.h"
namespace Libs::Graphics {
ShaderSubgroupCapabilities::ShaderSubgroupCapabilities(const GraphicContext& graphics)
: subgroup_size(graphics.subgroup_size), min_subgroup_size(graphics.min_subgroup_size),
max_subgroup_size(graphics.max_subgroup_size),
required_subgroup_size_stages(graphics.required_subgroup_size_stages),
subgroup_size_control_enabled(graphics.subgroup_size_control_enabled) {}
ShaderLaneMaskMode SelectGraphicsLaneMaskMode(uint32_t guest_wave_size) {
// A 64-wide guest wave can't assume the host GPU's lane layout matches
// GCN's. AMD RDNA also reports a 64-wide subgroup but uses a different
// lane/pixel layout, which corrupted rendering under "native wave" mode.
// Per-invocation emulation works regardless of the host's lane layout.
if (guest_wave_size == 64u) {
return ShaderLaneMaskMode::PerInvocation;
}
return ShaderLaneMaskMode::NativeWave;
}
ShaderSubgroupConfiguration ConfigureShaderSubgroup(const ShaderSubgroupCapabilities& capabilities,
vk::ShaderStageFlagBits stage,
const ShaderRecompiler::IR::Program& program) {
const auto guest_wave_size = program.wave_size;
if (guest_wave_size != 32u && guest_wave_size != 64u) {
return {};
}
if (stage != vk::ShaderStageFlagBits::eCompute) {
const auto expected = SelectGraphicsLaneMaskMode(guest_wave_size);
if (program.lane_mask_mode != expected || (capabilities.subgroup_size != guest_wave_size &&
expected != ShaderLaneMaskMode::PerInvocation)) {
return {};
}
return {expected == ShaderLaneMaskMode::NativeWave
? ShaderSubgroupMode::Natural
: ShaderSubgroupMode::PerInvocationGraphics,
0};
}
if (program.lane_mask_mode != ShaderLaneMaskMode::NativeWave) {
return {};
}
if (capabilities.subgroup_size == guest_wave_size) {
return {ShaderSubgroupMode::Natural, 0};
}
if (capabilities.subgroup_size_control_enabled &&
(capabilities.required_subgroup_size_stages & stage) &&
guest_wave_size >= capabilities.min_subgroup_size &&
guest_wave_size <= capabilities.max_subgroup_size) {
return {ShaderSubgroupMode::Controlled, guest_wave_size};
}
if (!ShaderRecompiler::Spirv::ProgramRequiresExactSubgroupSize(program)) {
return {ShaderSubgroupMode::FlattenedMasks, 0};
}
return {};
}
} // namespace Libs::Graphics
@@ -0,0 +1,40 @@
#ifndef EMULATOR_SRC_GRAPHICS_HOST_GPU_RENDERER_SHADERSUBGROUP_H_
#define EMULATOR_SRC_GRAPHICS_HOST_GPU_RENDERER_SHADERSUBGROUP_H_
#include "graphics/host_gpu/graphicContext.h"
#include "graphics/shader/recompiler/ir/ShaderIR.h"
namespace Libs::Graphics {
enum class ShaderSubgroupMode {
Natural,
Controlled,
PerInvocationGraphics,
FlattenedMasks,
Unsupported
};
struct ShaderSubgroupConfiguration {
ShaderSubgroupMode mode = ShaderSubgroupMode::Unsupported;
uint32_t required_size = 0;
};
struct ShaderSubgroupCapabilities {
explicit ShaderSubgroupCapabilities(const GraphicContext& graphics);
uint32_t subgroup_size = 0;
uint32_t min_subgroup_size = 0;
uint32_t max_subgroup_size = 0;
vk::ShaderStageFlags required_subgroup_size_stages;
bool subgroup_size_control_enabled = false;
};
ShaderLaneMaskMode SelectGraphicsLaneMaskMode(uint32_t guest_wave_size);
ShaderSubgroupConfiguration ConfigureShaderSubgroup(const ShaderSubgroupCapabilities& capabilities,
vk::ShaderStageFlagBits stage,
const ShaderRecompiler::IR::Program& program);
} // namespace Libs::Graphics
#endif // EMULATOR_SRC_GRAPHICS_HOST_GPU_RENDERER_SHADERSUBGROUP_H_
File diff suppressed because it is too large Load Diff