renderer: replace generation retries with staged resource binding

This commit is contained in:
nmzik
2026-07-23 22:32:30 +02:00
parent 85876388cf
commit 7638c25d76
12 changed files with 586 additions and 238 deletions
+40 -39
View File
@@ -636,24 +636,16 @@ BufferBinding BufferCache::ObtainBuffer(CommandBuffer& command, uint64_t vaddr,
const auto end = AlignUp(vaddr + size); const auto end = AlignUp(vaddr + size);
std::lock_guard transaction(m_resource_mutex); std::lock_guard transaction(m_resource_mutex);
const auto texture_region = m_texture_cache->QueryRegion(vaddr, size); const auto texture_region = m_texture_cache->QueryRegion(vaddr, size);
// Use the stream-buffer fast path before image/buffer alias handling. Clean image and metadata // Snapshot small CPU-current reads now and upload them only during the final non-resetting
// views may coexist with a small CPU-current read; Kyty's separate image trackers require // commit. This keeps unaligned guest addresses valid without tying preparation to a command-local
// GPU-dirty ownership guards here. Read physical backing so // stream allocation.
// host page protection is irrelevant.
if (is_read && !is_written && size <= CACHING_PAGE_SIZE && if (is_read && !is_written && size <= CACHING_PAGE_SIZE &&
!m_memory_tracker.IsRegionGpuModified(vaddr, size) && !m_memory_tracker.IsRegionGpuModified(vaddr, size) &&
m_memory_tracker.IsRegionCpuModified(vaddr, size) && !texture_region.gpu_image_bytes && m_memory_tracker.IsRegionCpuModified(vaddr, size) && !texture_region.gpu_image_bytes &&
!texture_region.gpu_metadata_bytes) { !texture_region.gpu_metadata_bytes) {
std::array<uint8_t, CACHING_PAGE_SIZE> guest_data; std::vector<uint8_t> guest_data(size);
if (Libs::LibKernel::Memory::TryReadBacking(vaddr, guest_data.data(), size)) { if (Libs::LibKernel::Memory::TryReadBacking(vaddr, guest_data.data(), size)) {
VulkanBuffer* stream_buffer = nullptr; return {nullptr, 0, std::move(guest_data)};
vk::DeviceSize stream_offset = 0;
vk::DeviceSize stream_range = 0;
const auto stream_alignment = std::max<uint64_t>(m_graphics.StorageMinAlignment(), 16);
if (UploadHostData(command, guest_data.data(), size, stream_alignment, stream_buffer,
stream_offset, stream_range)) {
return {*stream_buffer, stream_offset};
}
} }
} }
const auto texture_pages = begin == vaddr && end - begin == size const auto texture_pages = begin == vaddr && end - begin == size
@@ -829,8 +821,7 @@ BufferBinding BufferCache::ObtainBuffer(CommandBuffer& command, uint64_t vaddr,
if (is_written) { if (is_written) {
m_gpu_modified_ranges.Add(vaddr, size); m_gpu_modified_ranges.Add(vaddr, size);
} }
command.RetainResourceUntilFence(cached.buffer); return {cached.buffer, vaddr - cached.vaddr};
return {*cached.buffer, vaddr - cached.vaddr};
} }
bool BufferCache::UploadHostData(CommandBuffer& command, const void* src, uint64_t size, bool BufferCache::UploadHostData(CommandBuffer& command, const void* src, uint64_t size,
@@ -842,10 +833,7 @@ bool BufferCache::UploadHostData(CommandBuffer& command, const void* src, uint64
return command.m_host_stream.Copy(src, size, alignment, out_buffer, out_offset, out_range); return command.m_host_stream.Copy(src, size, alignment, out_buffer, out_offset, out_range);
} }
VulkanBuffer& BufferCache::ObtainNullBuffer(CommandBuffer& command) { std::shared_ptr<VulkanBuffer> BufferCache::ObtainNullBuffer() {
if (command.IsInvalid() || command.IsExecute()) {
EXIT("BufferCache: null buffer requires a graphics context\n");
}
FaultSafeCacheLock lock(this, m_mutex); FaultSafeCacheLock lock(this, m_mutex);
if (m_null_buffer == nullptr) { if (m_null_buffer == nullptr) {
// robustBufferAccess makes every fetch safe; TODO: Use a // robustBufferAccess makes every fetch safe; TODO: Use a
@@ -865,10 +853,7 @@ VulkanBuffer& BufferCache::ObtainNullBuffer(CommandBuffer& command) {
m_null_buffer = std::move(buffer); m_null_buffer = std::move(buffer);
} }
// The buffer remains cache-persistent, while every recorded consumer keeps the allocation return m_null_buffer;
// alive until its own fence even if an unrelated command processor resets the global cache.
command.RetainResourceUntilFence(m_null_buffer);
return *m_null_buffer;
} }
BufferImageCopySource BufferCache::ObtainBufferForImage(uint64_t vaddr, uint64_t size) { BufferImageCopySource BufferCache::ObtainBufferForImage(uint64_t vaddr, uint64_t size) {
@@ -1046,17 +1031,20 @@ void BufferCache::FillBuffer(CommandBuffer* command, uint64_t vaddr, uint64_t si
} }
} }
EXIT_IF(command == nullptr); EXIT_IF(command == nullptr);
const auto [dst, dst_offset] = ObtainBuffer(*command, vaddr, size, true, false); auto dst = ObtainBuffer(*command, vaddr, size, true, false);
const auto before = MakeDmaBarrier( EXIT_IF(dst.buffer == nullptr || !dst.host_data.empty());
dst, dst_offset, size, vk::AccessFlagBits::eMemoryRead | vk::AccessFlagBits::eMemoryWrite, command->RetainResourceUntilFence(dst.buffer);
const auto before = MakeDmaBarrier(
*dst.buffer, dst.offset, size,
vk::AccessFlagBits::eMemoryRead | vk::AccessFlagBits::eMemoryWrite,
vk::AccessFlagBits::eTransferWrite); vk::AccessFlagBits::eTransferWrite);
const auto vk_buffer = command->Handle(); const auto vk_buffer = command->Handle();
vk_buffer.pipelineBarrier( vk_buffer.pipelineBarrier(
vk::PipelineStageFlagBits::eAllCommands, vk::PipelineStageFlagBits::eTransfer, vk::PipelineStageFlagBits::eAllCommands, vk::PipelineStageFlagBits::eTransfer,
vk::DependencyFlagBits::eByRegion, 0, nullptr, 1, &before, 0, nullptr); vk::DependencyFlagBits::eByRegion, 0, nullptr, 1, &before, 0, nullptr);
vk_buffer.fillBuffer(dst.buffer, dst_offset, size, value); vk_buffer.fillBuffer(dst.buffer->buffer, dst.offset, size, value);
const auto after = const auto after =
MakeDmaBarrier(dst, dst_offset, size, vk::AccessFlagBits::eTransferWrite, MakeDmaBarrier(*dst.buffer, dst.offset, size, vk::AccessFlagBits::eTransferWrite,
vk::AccessFlagBits::eMemoryRead | vk::AccessFlagBits::eMemoryWrite); vk::AccessFlagBits::eMemoryRead | vk::AccessFlagBits::eMemoryWrite);
vk_buffer.pipelineBarrier(vk::PipelineStageFlagBits::eTransfer, vk_buffer.pipelineBarrier(vk::PipelineStageFlagBits::eTransfer,
vk::PipelineStageFlagBits::eAllCommands, vk::PipelineStageFlagBits::eAllCommands,
@@ -1128,31 +1116,44 @@ void BufferCache::CopyBuffer(CommandBuffer* command, uint64_t dst_vaddr, uint64_
} }
} }
EXIT_IF(command == nullptr); EXIT_IF(command == nullptr);
const auto [src, src_offset] = ObtainBuffer(*command, src_vaddr, size, false, true); auto src = ObtainBuffer(*command, src_vaddr, size, false, true);
const auto [dst, dst_offset] = auto dst = ObtainBuffer(*command, dst_vaddr, size, true, false, dst_image_transition);
ObtainBuffer(*command, dst_vaddr, size, true, false, dst_image_transition); EXIT_IF(dst.buffer == nullptr || !dst.host_data.empty());
if (&src == &dst && src_offset < dst_offset + size && dst_offset < src_offset + size) { VulkanBuffer* src_buffer = src.buffer.get();
vk::DeviceSize src_offset = src.offset;
if (!src.host_data.empty()) {
vk::DeviceSize range = 0;
EXIT_IF(!UploadHostData(*command, src.host_data.data(), src.host_data.size(), 16,
src_buffer, src_offset, range) ||
range != size);
} else {
command->RetainResourceUntilFence(src.buffer);
}
command->RetainResourceUntilFence(dst.buffer);
EXIT_IF(src_buffer == nullptr);
if (src.buffer == dst.buffer && src.offset < dst.offset + size &&
dst.offset < src.offset + size) {
EXIT("BufferCache: resolved Vulkan copy ranges overlap, src_offset=0x%016" PRIx64 EXIT("BufferCache: resolved Vulkan copy ranges overlap, src_offset=0x%016" PRIx64
" dst_offset=0x%016" PRIx64 " size=0x%016" PRIx64 "\n", " dst_offset=0x%016" PRIx64 " size=0x%016" PRIx64 "\n",
src_offset, dst_offset, size); src.offset, dst.offset, size);
} }
const vk::BufferMemoryBarrier before[] = { const vk::BufferMemoryBarrier before[] = {
MakeDmaBarrier(dst, dst_offset, size, MakeDmaBarrier(*dst.buffer, dst.offset, size,
vk::AccessFlagBits::eMemoryRead | vk::AccessFlagBits::eMemoryWrite, vk::AccessFlagBits::eMemoryRead | vk::AccessFlagBits::eMemoryWrite,
vk::AccessFlagBits::eTransferWrite), vk::AccessFlagBits::eTransferWrite),
MakeDmaBarrier(src, src_offset, size, vk::AccessFlagBits::eMemoryWrite, MakeDmaBarrier(*src_buffer, src_offset, size, vk::AccessFlagBits::eMemoryWrite,
vk::AccessFlagBits::eTransferRead), vk::AccessFlagBits::eTransferRead),
}; };
const auto vk_buffer = command->Handle(); const auto vk_buffer = command->Handle();
vk_buffer.pipelineBarrier(vk::PipelineStageFlagBits::eAllCommands, vk_buffer.pipelineBarrier(vk::PipelineStageFlagBits::eAllCommands,
vk::PipelineStageFlagBits::eTransfer, vk::PipelineStageFlagBits::eTransfer,
vk::DependencyFlagBits::eByRegion, 0, nullptr, 2, before, 0, nullptr); vk::DependencyFlagBits::eByRegion, 0, nullptr, 2, before, 0, nullptr);
const vk::BufferCopy copy {src_offset, dst_offset, size}; const vk::BufferCopy copy {src_offset, dst.offset, size};
vk_buffer.copyBuffer(src.buffer, dst.buffer, 1, &copy); vk_buffer.copyBuffer(src_buffer->buffer, dst.buffer->buffer, 1, &copy);
const vk::BufferMemoryBarrier after[] = { const vk::BufferMemoryBarrier after[] = {
MakeDmaBarrier(dst, dst_offset, size, vk::AccessFlagBits::eTransferWrite, MakeDmaBarrier(*dst.buffer, dst.offset, size, vk::AccessFlagBits::eTransferWrite,
vk::AccessFlagBits::eMemoryRead | vk::AccessFlagBits::eMemoryWrite), vk::AccessFlagBits::eMemoryRead | vk::AccessFlagBits::eMemoryWrite),
MakeDmaBarrier(src, src_offset, size, vk::AccessFlagBits::eTransferRead, MakeDmaBarrier(*src_buffer, src_offset, size, vk::AccessFlagBits::eTransferRead,
vk::AccessFlagBits::eMemoryWrite), vk::AccessFlagBits::eMemoryWrite),
}; };
vk_buffer.pipelineBarrier(vk::PipelineStageFlagBits::eTransfer, vk_buffer.pipelineBarrier(vk::PipelineStageFlagBits::eTransfer,
+5 -3
View File
@@ -10,6 +10,7 @@
#include <map> #include <map>
#include <memory> #include <memory>
#include <mutex> #include <mutex>
#include <vector>
namespace Libs::Graphics { namespace Libs::Graphics {
@@ -36,8 +37,9 @@ struct BufferCacheRange {
}; };
struct BufferBinding { struct BufferBinding {
VulkanBuffer& buffer; std::shared_ptr<VulkanBuffer> buffer;
uint64_t offset; uint64_t offset = 0;
std::vector<uint8_t> host_data;
}; };
[[nodiscard]] bool MergeOverlappingBufferCacheRange(BufferCacheRange& merged, [[nodiscard]] bool MergeOverlappingBufferCacheRange(BufferCacheRange& merged,
@@ -65,7 +67,7 @@ public:
[[nodiscard]] bool UploadHostData(CommandBuffer& command, const void* src, uint64_t size, [[nodiscard]] bool UploadHostData(CommandBuffer& command, const void* src, uint64_t size,
uint64_t alignment, VulkanBuffer*& out_buffer, uint64_t alignment, VulkanBuffer*& out_buffer,
uint64_t& out_offset, uint64_t& out_range); uint64_t& out_offset, uint64_t& out_range);
[[nodiscard]] VulkanBuffer& ObtainNullBuffer(CommandBuffer& command); [[nodiscard]] std::shared_ptr<VulkanBuffer> ObtainNullBuffer();
[[nodiscard]] BufferImageCopySource ObtainBufferForImage(uint64_t vaddr, uint64_t size); [[nodiscard]] BufferImageCopySource ObtainBufferForImage(uint64_t vaddr, uint64_t size);
void FillBuffer(CommandBuffer* command, uint64_t vaddr, uint64_t size, uint32_t value); void FillBuffer(CommandBuffer* command, uint64_t vaddr, uint64_t size, uint32_t value);
void CopyBuffer(CommandBuffer* command, uint64_t dst_vaddr, uint64_t src_vaddr, uint64_t size); void CopyBuffer(CommandBuffer* command, uint64_t dst_vaddr, uint64_t src_vaddr, uint64_t size);
@@ -358,7 +358,6 @@ void CommandBuffer::FinalizeFence(bool reset_recording) {
m_fence_waited = false; m_fence_waited = false;
if (reset_recording) { if (reset_recording) {
ResetNativeCommandBuffer(m_slot->buffer); ResetNativeCommandBuffer(m_slot->buffer);
m_recording_generation++;
} }
} }
if (reset_recording) { if (reset_recording) {
@@ -10,6 +10,8 @@
#include "graphics/shader/shaderBindings.h" #include "graphics/shader/shaderBindings.h"
#include <map> #include <map>
#include <memory>
#include <span>
#include <unordered_map> #include <unordered_map>
#include <vector> #include <vector>
@@ -17,6 +19,7 @@ namespace Libs::Graphics {
namespace ShaderRecompiler::IR { namespace ShaderRecompiler::IR {
struct Program; struct Program;
struct ResourceSnapshot;
} }
class CommandBuffer; class CommandBuffer;
@@ -29,9 +32,11 @@ struct VulkanDescriptorSet {
}; };
struct BufferView { struct BufferView {
VulkanBuffer* buffer = nullptr; std::shared_ptr<VulkanBuffer> owner;
vk::DeviceSize offset = 0; VulkanBuffer* buffer = nullptr;
vk::DeviceSize range = VK_WHOLE_SIZE; vk::DeviceSize offset = 0;
vk::DeviceSize range = VK_WHOLE_SIZE;
std::vector<uint8_t> host_data;
}; };
class DescriptorCache { class DescriptorCache {
@@ -42,6 +47,7 @@ public:
VulkanImage* image = nullptr; VulkanImage* image = nullptr;
int view = VulkanImage::VIEW_DEFAULT; int view = VulkanImage::VIEW_DEFAULT;
vk::ImageView image_view = nullptr; vk::ImageView image_view = nullptr;
std::shared_ptr<void> owner;
}; };
enum class TextureVariant : int { enum class TextureVariant : int {
@@ -63,6 +69,17 @@ public:
BufferView user_data; 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) { explicit DescriptorCache(GraphicContext& graphics): m_graphics(graphics) {
EXIT_NOT_IMPLEMENTED(!Common::Thread::IsMainThread()); EXIT_NOT_IMPLEMENTED(!Common::Thread::IsMainThread());
} }
@@ -95,10 +112,14 @@ private:
std::map<std::vector<uint32_t>, vk::DescriptorSetLayout> m_descriptor_set_layouts; std::map<std::vector<uint32_t>, vk::DescriptorSetLayout> m_descriptor_set_layouts;
}; };
void BindDescriptors(uint64_t submit_id, CommandBuffer& buffer, [[nodiscard]] DescriptorCache::PreparedBindings
vk::PipelineBindPoint pipeline_bind_point, vk::PipelineLayout layout, PrepareBindings(CommandBuffer& buffer, const ShaderStageRuntime& runtime,
const ShaderStageRuntime& runtime, vk::ShaderStageFlags vk_stage, vk::ShaderStageFlags shader_stage, DescriptorCache::Stage stage);
DescriptorCache::Stage stage); void RebindBuffers(CommandBuffer& buffer, DescriptorCache::PreparedBindings& bindings);
void RebindImages(CommandBuffer& buffer, DescriptorCache::PreparedBindings& bindings);
void ActivateImageWrites(std::span<DescriptorCache::PreparedBindings*> bindings);
void CommitBindings(CommandBuffer& buffer, vk::PipelineBindPoint pipeline_bind_point,
vk::PipelineLayout layout, DescriptorCache::PreparedBindings& bindings);
} // namespace Libs::Graphics } // namespace Libs::Graphics
+189 -49
View File
@@ -37,6 +37,7 @@
#include <fmt/format.h> #include <fmt/format.h>
#include <limits> #include <limits>
#include <span> #include <span>
#include <unordered_set>
#ifdef min #ifdef min
#undef min #undef min
@@ -49,8 +50,21 @@ namespace Libs::Graphics {
using TextureVariant = DescriptorCache::TextureVariant; using TextureVariant = DescriptorCache::TextureVariant;
static void BindNullStorageBuffer(CommandBuffer& cmd_buffer, BufferView& dst) { ImageWriteActivation ClassifyImageWriteActivation(VulkanImageType type) noexcept {
dst.buffer = &GetRenderContext().GetBufferCache().ObtainNullBuffer(cmd_buffer); switch (type) {
case VulkanImageType::StorageTexture: return ImageWriteActivation::Implicit;
case VulkanImageType::VideoOut:
case VulkanImageType::DepthStencil:
case VulkanImageType::RenderTexture: return ImageWriteActivation::Explicit;
case VulkanImageType::Unknown:
case VulkanImageType::Texture: return ImageWriteActivation::Unsupported;
}
return ImageWriteActivation::Unsupported;
}
static void BindNullStorageBuffer(BufferView& dst) {
dst.owner = GetRenderContext().GetBufferCache().ObtainNullBuffer();
dst.buffer = dst.owner.get();
dst.offset = 0; dst.offset = 0;
dst.range = 16; dst.range = 16;
} }
@@ -119,7 +133,7 @@ static void CopyNativeDescriptor(const ShaderRecompiler::IR::DescriptorValue& so
std::copy_n(source.dwords.begin(), destination.size(), destination.begin()); std::copy_n(source.dwords.begin(), destination.size(), destination.begin());
} }
static BufferView NativeStorageBuffer(uint64_t submit_id, CommandBuffer& command_buffer, static BufferView NativeStorageBuffer(CommandBuffer& command_buffer,
const ShaderBufferResource& descriptor, const ShaderBufferResource& descriptor,
const ShaderRecompiler::IR::BufferResource& resource) { const ShaderRecompiler::IR::BufferResource& resource) {
BufferView result; BufferView result;
@@ -132,7 +146,7 @@ static BufferView NativeStorageBuffer(uint64_t submit_id, CommandBuffer& command
} }
const auto size = stride != 0 ? static_cast<uint64_t>(stride) * records : records; const auto size = stride != 0 ? static_cast<uint64_t>(stride) * records : records;
if (address == 0 || size == 0) { if (address == 0 || size == 0) {
BindNullStorageBuffer(command_buffer, result); BindNullStorageBuffer(result);
return result; return result;
} }
const auto& graphics = GetRenderContext().GetGraphics(); const auto& graphics = GetRenderContext().GetGraphics();
@@ -142,25 +156,26 @@ static BufferView NativeStorageBuffer(uint64_t submit_id, CommandBuffer& command
BufferCache::CACHING_PAGE_SIZE % alignment != 0) { BufferCache::CACHING_PAGE_SIZE % alignment != 0) {
EXIT("storage buffer range or device alignment is unsupported\n"); EXIT("storage buffer range or device alignment is unsupported\n");
} }
(void)submit_id;
auto binding = GetRenderContext().GetBufferCache().ObtainBuffer( auto binding = GetRenderContext().GetBufferCache().ObtainBuffer(
command_buffer, address, size, resource.written, resource.read, resource.formatted); command_buffer, address, size, resource.written, resource.read, resource.formatted);
if (binding.offset % alignment != 0) { if (binding.host_data.empty() && binding.offset % alignment != 0) {
EXIT("storage buffer binding is not device-aligned\n"); EXIT("storage buffer binding is not device-aligned\n");
} }
result.buffer = &binding.buffer; result.owner = std::move(binding.buffer);
result.offset = binding.offset; result.buffer = result.owner.get();
result.range = static_cast<vk::DeviceSize>(size); result.offset = binding.offset;
result.range = static_cast<vk::DeviceSize>(size);
result.host_data = std::move(binding.host_data);
return result; return result;
} }
static BufferView static BufferView
NativeAddressBuffer(uint64_t submit_id, CommandBuffer& command_buffer, NativeAddressBuffer(CommandBuffer& command_buffer,
const ShaderRecompiler::IR::AddressResource& resource, const ShaderRecompiler::IR::AddressResource& resource,
const ShaderRecompiler::IR::ResourceSnapshot::Address& address) { const ShaderRecompiler::IR::ResourceSnapshot::Address& address) {
BufferView result; BufferView result;
if (address.binding_base == 0) { if (address.binding_base == 0) {
BindNullStorageBuffer(command_buffer, result); BindNullStorageBuffer(result);
return result; return result;
} }
if (resource.written) { if (resource.written) {
@@ -185,12 +200,13 @@ NativeAddressBuffer(uint64_t submit_id, CommandBuffer& command_buffer,
BufferCache::GetBufferOffset(address.binding_base) % alignment != 0) { BufferCache::GetBufferOffset(address.binding_base) % alignment != 0) {
EXIT("address resource range or alignment is unsupported\n"); EXIT("address resource range or alignment is unsupported\n");
} }
(void)submit_id;
auto binding = GetRenderContext().GetBufferCache().ObtainBuffer(command_buffer, auto binding = GetRenderContext().GetBufferCache().ObtainBuffer(command_buffer,
address.binding_base, size); address.binding_base, size);
result.buffer = &binding.buffer; result.owner = std::move(binding.buffer);
result.offset = binding.offset; result.buffer = result.owner.get();
result.range = static_cast<vk::DeviceSize>(size); result.offset = binding.offset;
result.range = static_cast<vk::DeviceSize>(size);
result.host_data = std::move(binding.host_data);
return result; return result;
} }
@@ -475,8 +491,7 @@ void ValidateMetadataReuseTexture(const ShaderRecompiler::IR::ImageResource& res
} }
static DescriptorCache::TextureBinding static DescriptorCache::TextureBinding
NativeTexture(uint64_t submit_id, CommandBuffer& command_buffer, NativeTexture(CommandBuffer& command_buffer, const ShaderRecompiler::IR::ImageResource& resource,
const ShaderRecompiler::IR::ImageResource& resource,
const ShaderRecompiler::IR::DescriptorValue& value) { const ShaderRecompiler::IR::DescriptorValue& value) {
ShaderTextureResource descriptor; ShaderTextureResource descriptor;
CopyNativeDescriptor(value, descriptor.fields); CopyNativeDescriptor(value, descriptor.fields);
@@ -536,6 +551,7 @@ NativeTexture(uint64_t submit_id, CommandBuffer& command_buffer,
VulkanImage* image = nullptr; VulkanImage* image = nullptr;
int view = VulkanImage::VIEW_DEFAULT; int view = VulkanImage::VIEW_DEFAULT;
vk::ImageView image_view = nullptr; vk::ImageView image_view = nullptr;
bool external = false;
const bool check_depth = static_cast<Prospero::TileMode>(tile) == Prospero::TileMode::kDepth || const bool check_depth = static_cast<Prospero::TileMode>(tile) == Prospero::TileMode::kDepth ||
descriptor.MsaaDepth(); descriptor.MsaaDepth();
if (image == nullptr) { if (image == nullptr) {
@@ -615,14 +631,12 @@ NativeTexture(uint64_t submit_id, CommandBuffer& command_buffer,
if (image != nullptr && image_view == nullptr && image->image_view[view] == nullptr) { if (image != nullptr && image_view == nullptr && image->image_view[view] == nullptr) {
EXIT("required cached texture image view is missing\n"); EXIT("required cached texture image view is missing\n");
} }
if (storage && image != nullptr) {
GetRenderContext().GetTextureCache().MarkGpuWritten(*image);
}
} }
} }
if (image == nullptr) { if (image == nullptr) {
const auto video = Presentation::DisplayBufferFind(address); const auto video = Presentation::DisplayBufferFind(address);
if (video.image != nullptr) { if (video.image != nullptr) {
external = true;
if (storage) { if (storage) {
const bool exact = const bool exact =
resource.kind == ShaderRecompiler::IR::ResourceKind::StorageImageUint && resource.kind == ShaderRecompiler::IR::ResourceKind::StorageImageUint &&
@@ -644,7 +658,6 @@ NativeTexture(uint64_t submit_id, CommandBuffer& command_buffer,
} }
image = video.image; image = video.image;
view = VulkanImage::VIEW_STORAGE; view = VulkanImage::VIEW_STORAGE;
GetRenderContext().GetTextureCache().MarkGpuWritten(*image);
} else { } else {
const bool exact = const bool exact =
IsSupportedSampledVideoOutView(resource, descriptor, *video.image) && IsSupportedSampledVideoOutView(resource, descriptor, *video.image) &&
@@ -678,7 +691,6 @@ NativeTexture(uint64_t submit_id, CommandBuffer& command_buffer,
if (!storage && metadata_read) { if (!storage && metadata_read) {
ValidateMetadataReuseTexture(resource, descriptor, size.size); ValidateMetadataReuseTexture(resource, descriptor, size.size);
} }
(void)submit_id;
(void)command_buffer; (void)command_buffer;
ImageInfo info {}; ImageInfo info {};
info.address = address; info.address = address;
@@ -714,7 +726,9 @@ NativeTexture(uint64_t submit_id, CommandBuffer& command_buffer,
image_view != nullptr)) { image_view != nullptr)) {
view = SelectSampledTextureArrayView(*image, view); view = SelectSampledTextureArrayView(*image, view);
} }
return {image, view, image_view}; return {image, view, image_view,
external ? std::shared_ptr<void> {}
: GetRenderContext().GetTextureCache().GetImageOwner(*image)};
} }
static vk::Sampler NativeSampler(const ShaderRecompiler::IR::Program& program, uint32_t index, static vk::Sampler NativeSampler(const ShaderRecompiler::IR::Program& program, uint32_t index,
@@ -742,10 +756,9 @@ static BufferView NativeUpload(CommandBuffer& command_buffer, std::span<const ui
return result; return result;
} }
void BindDescriptors(uint64_t submit_id, CommandBuffer& buffer, DescriptorCache::PreparedBindings
vk::PipelineBindPoint pipeline_bind_point, vk::PipelineLayout layout, PrepareBindings(CommandBuffer& buffer, const ShaderStageRuntime& runtime,
const ShaderStageRuntime& runtime, vk::ShaderStageFlags vk_stage, vk::ShaderStageFlags shader_stage, DescriptorCache::Stage stage) {
DescriptorCache::Stage stage) {
KYTY_PROFILER_FUNCTION(); KYTY_PROFILER_FUNCTION();
EXIT_IF(!runtime); EXIT_IF(!runtime);
const auto& program = *runtime.program; const auto& program = *runtime.program;
@@ -754,27 +767,30 @@ void BindDescriptors(uint64_t submit_id, CommandBuffer& buffer,
if (!ShaderRecompiler::IR::ValidateResourceSpecialization(program, snapshot, &error)) { if (!ShaderRecompiler::IR::ValidateResourceSpecialization(program, snapshot, &error)) {
EXIT("invalid native shader runtime snapshot: %s\n", error.c_str()); EXIT("invalid native shader runtime snapshot: %s\n", error.c_str());
} }
auto vk_buffer = buffer.Handle();
const auto shader_stages = ShaderPipelineStages(vk_stage);
DescriptorCache::NativeDescriptors descriptors; 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.buffers.reserve(program.info.buffers.size());
for (uint32_t i = 0; i < program.info.buffers.size(); i++) { for (uint32_t i = 0; i < program.info.buffers.size(); i++) {
ShaderBufferResource descriptor; ShaderBufferResource descriptor;
CopyNativeDescriptor(snapshot.buffers[i], descriptor.fields); CopyNativeDescriptor(snapshot.buffers[i], descriptor.fields);
descriptors.buffers.push_back( descriptors.buffers.push_back(
NativeStorageBuffer(submit_id, buffer, descriptor, program.info.buffers[i])); NativeStorageBuffer(buffer, descriptor, program.info.buffers[i]));
} }
descriptors.images.reserve(program.info.images.size()); descriptors.images.reserve(program.info.images.size());
for (uint32_t i = 0; i < program.info.images.size(); i++) { for (uint32_t i = 0; i < program.info.images.size(); i++) {
const auto kind = program.info.images[i].kind; const auto kind = program.info.images[i].kind;
if ((kind == ShaderRecompiler::IR::ResourceKind::StorageImage || if ((kind == ShaderRecompiler::IR::ResourceKind::StorageImage ||
kind == ShaderRecompiler::IR::ResourceKind::StorageImageUint) && kind == ShaderRecompiler::IR::ResourceKind::StorageImageUint) &&
vk_stage != vk::ShaderStageFlagBits::eCompute) { shader_stage != vk::ShaderStageFlagBits::eCompute) {
EXIT("storage images are unsupported outside compute shaders\n"); EXIT("storage images are unsupported outside compute shaders\n");
} }
descriptors.images.push_back( descriptors.images.push_back(
NativeTexture(submit_id, buffer, program.info.images[i], snapshot.images[i])); NativeTexture(buffer, program.info.images[i], snapshot.images[i]));
} }
descriptors.samplers.reserve(program.info.samplers.size()); descriptors.samplers.reserve(program.info.samplers.size());
for (uint32_t i = 0; i < program.info.samplers.size(); i++) { for (uint32_t i = 0; i < program.info.samplers.size(); i++) {
@@ -782,28 +798,150 @@ void BindDescriptors(uint64_t submit_id, CommandBuffer& buffer,
} }
descriptors.addresses.reserve(program.info.addresses.size()); descriptors.addresses.reserve(program.info.addresses.size());
for (uint32_t i = 0; i < program.info.addresses.size(); i++) { for (uint32_t i = 0; i < program.info.addresses.size(); i++) {
descriptors.addresses.push_back(NativeAddressBuffer( descriptors.addresses.push_back(
submit_id, buffer, program.info.addresses[i], snapshot.addresses[i])); NativeAddressBuffer(buffer, program.info.addresses[i], snapshot.addresses[i]));
} }
if (ShaderRecompiler::IR::FindBinding( if (ShaderRecompiler::IR::FindBinding(
program.bindings, ShaderRecompiler::IR::DescriptorBindingKind::FlattenedSrt) != program.bindings, ShaderRecompiler::IR::DescriptorBindingKind::FlattenedSrt) !=
nullptr) { nullptr) {
descriptors.flattened_srt = NativeUpload(buffer, snapshot.flattened_srt); prepared.flattened_srt.assign(snapshot.flattened_srt.begin(), snapshot.flattened_srt.end());
} }
std::vector<uint32_t> user_data; prepared.user_data.reserve(program.bindings.user_data_registers.size());
user_data.reserve(program.bindings.user_data_registers.size());
for (const auto reg: program.bindings.user_data_registers) { for (const auto reg: program.bindings.user_data_registers) {
user_data.push_back(snapshot.user_data[reg - program.user_data_base]); prepared.user_data.push_back(snapshot.user_data[reg - program.user_data_base]);
}
if (ShaderRecompiler::IR::FindBinding(
program.bindings, ShaderRecompiler::IR::DescriptorBindingKind::UserData) != nullptr) {
descriptors.user_data = NativeUpload(buffer, user_data);
} }
if (ShaderRecompiler::IR::FindBinding( if (ShaderRecompiler::IR::FindBinding(
program.bindings, ShaderRecompiler::IR::DescriptorBindingKind::Gds) != nullptr) { program.bindings, ShaderRecompiler::IR::DescriptorBindingKind::Gds) != nullptr) {
descriptors.gds.buffer = &GetRenderContext().GetGdsBuffer().GetBuffer(); descriptors.gds.buffer = &GetRenderContext().GetGdsBuffer().GetBuffer();
const auto barrier = MakeGdsDependency(*descriptors.gds.buffer); }
return prepared;
}
void 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;
resources.buffers.clear();
resources.buffers.reserve(program.info.buffers.size());
for (uint32_t i = 0; i < program.info.buffers.size(); i++) {
ShaderBufferResource descriptor;
CopyNativeDescriptor(snapshot.buffers[i], descriptor.fields);
resources.buffers.push_back(
NativeStorageBuffer(buffer, descriptor, program.info.buffers[i]));
}
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(buffer, program.info.addresses[i], snapshot.addresses[i]));
}
}
void 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;
images.clear();
images.reserve(program.info.images.size());
for (uint32_t i = 0; i < program.info.images.size(); i++) {
images.push_back(NativeTexture(buffer, program.info.images[i], snapshot.images[i]));
}
}
void ActivateImageWrites(std::span<DescriptorCache::PreparedBindings*> bindings) {
std::unordered_set<VulkanImage*> writable;
for (const auto* prepared: bindings) {
if (prepared == nullptr) {
continue;
}
EXIT_IF(prepared->program == nullptr ||
prepared->resources.images.size() != prepared->program->info.images.size());
for (uint32_t i = 0; i < prepared->program->info.images.size(); i++) {
const auto& binding = prepared->resources.images[i];
EXIT_IF(binding.image == nullptr);
if (binding.owner != nullptr) {
auto current = GetRenderContext().GetTextureCache().GetImageOwner(*binding.image);
EXIT_IF(current.get() != binding.owner.get());
}
if (prepared->program->info.images[i].written) {
switch (ClassifyImageWriteActivation(binding.image->type)) {
case ImageWriteActivation::Implicit: break;
case ImageWriteActivation::Explicit: writable.insert(binding.image); break;
case ImageWriteActivation::Unsupported:
EXIT("unsupported writable image type: %u\n",
static_cast<uint32_t>(binding.image->type));
}
}
}
}
for (auto* image: writable) {
GetRenderContext().GetTextureCache().MarkGpuWritten(*image);
}
}
static void RetainBindings(CommandBuffer& buffer,
const DescriptorCache::NativeDescriptors& resources) {
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);
}
}
for (const auto& image: resources.images) {
if (image.owner != nullptr) {
buffer.RetainResourceUntilFence(image.owner);
}
}
}
static void CommitHostBuffers(CommandBuffer& buffer, std::vector<BufferView>& views) {
const auto alignment = GetRenderContext().GetGraphics().StorageMinAlignment();
EXIT_IF(alignment == 0);
for (auto& view: views) {
if (view.host_data.empty()) {
continue;
}
EXIT_IF(view.owner != nullptr || view.buffer != nullptr);
vk::DeviceSize uploaded_range = 0;
EXIT_IF(!GetRenderContext().GetBufferCache().UploadHostData(
buffer, view.host_data.data(), view.host_data.size(), alignment, view.buffer,
view.offset, uploaded_range));
EXIT_IF(uploaded_range != view.range);
}
}
void 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;
CommitHostBuffers(buffer, descriptors.buffers);
CommitHostBuffers(buffer, descriptors.addresses);
if (!prepared.flattened_srt.empty()) {
descriptors.flattened_srt = NativeUpload(buffer, prepared.flattened_srt);
}
if (ShaderRecompiler::IR::FindBinding(
program.bindings, ShaderRecompiler::IR::DescriptorBindingKind::UserData) != nullptr) {
descriptors.user_data = NativeUpload(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) {
const auto barrier = MakeGdsDependency(*descriptors.gds.buffer);
vk_buffer.pipelineBarrier( vk_buffer.pipelineBarrier(
vk::PipelineStageFlagBits::eHost | vk::PipelineStageFlagBits::eTransfer | vk::PipelineStageFlagBits::eHost | vk::PipelineStageFlagBits::eTransfer |
vk::PipelineStageFlagBits::eAllGraphics | vk::PipelineStageFlagBits::eComputeShader, vk::PipelineStageFlagBits::eAllGraphics | vk::PipelineStageFlagBits::eComputeShader,
@@ -838,17 +976,19 @@ void BindDescriptors(uint64_t submit_id, CommandBuffer& buffer,
} }
if (!program.bindings.descriptors.empty()) { if (!program.bindings.descriptors.empty()) {
auto& set = auto& set = GetRenderContext().GetDescriptorCache().GetDescriptor(
GetRenderContext().GetDescriptorCache().GetDescriptor(stage, program, descriptors); prepared.stage, program, descriptors);
vk_buffer.bindDescriptorSets(pipeline_bind_point, layout, program.bindings.descriptor_set, vk_buffer.bindDescriptorSets(pipeline_bind_point, layout, program.bindings.descriptor_set,
1, &set.set, 0, nullptr); 1, &set.set, 0, nullptr);
buffer.RecycleDescriptorAfterFence(set); buffer.RecycleDescriptorAfterFence(set);
} }
if (program.bindings.push_constant_size != 0) { if (program.bindings.push_constant_size != 0) {
EXIT_IF(program.bindings.push_constant_size != user_data.size() * sizeof(uint32_t)); EXIT_IF(program.bindings.push_constant_size !=
vk_buffer.pushConstants(layout, vk_stage, program.bindings.push_constant_offset, prepared.user_data.size() * sizeof(uint32_t));
program.bindings.push_constant_size, user_data.data()); 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 } // namespace Libs::Graphics
@@ -13,6 +13,12 @@
namespace Libs::Graphics { namespace Libs::Graphics {
struct VulkanImage; struct VulkanImage;
enum class VulkanImageType;
enum class ImageWriteActivation { Unsupported, Implicit, Explicit };
[[nodiscard]] ImageWriteActivation
ClassifyImageWriteActivation(VulkanImageType type) noexcept;
template <typename T> template <typename T>
[[nodiscard]] T DecodeNativeDescriptor(const ShaderRecompiler::IR::DescriptorValue& value) { [[nodiscard]] T DecodeNativeDescriptor(const ShaderRecompiler::IR::DescriptorValue& value) {
-2
View File
@@ -95,7 +95,6 @@ public:
[[nodiscard]] vk::CommandBuffer Handle() const; [[nodiscard]] vk::CommandBuffer Handle() const;
[[nodiscard]] GraphicContext& GetGraphics() const noexcept { return m_graphics; } [[nodiscard]] GraphicContext& GetGraphics() const noexcept { return m_graphics; }
[[nodiscard]] bool IsExecute() const { return m_execute; } [[nodiscard]] bool IsExecute() const { return m_execute; }
[[nodiscard]] uint64_t GetRecordingGeneration() const { return m_recording_generation; }
private: private:
friend class BufferCache; friend class BufferCache;
@@ -113,7 +112,6 @@ private:
bool m_execute = false; bool m_execute = false;
bool m_fence_waited = false; bool m_fence_waited = false;
uint64_t m_submit_seq = 0; uint64_t m_submit_seq = 0;
uint64_t m_recording_generation = 0;
uint32_t m_debug_op = 0; uint32_t m_debug_op = 0;
uint64_t m_debug_submit_id = 0; uint64_t m_debug_submit_id = 0;
uint32_t m_debug_arg0 = 0; uint32_t m_debug_arg0 = 0;
@@ -501,37 +501,33 @@ void RenderDispatchDirect(uint64_t submit_id, RenderCommandBuffer& buffer, uint3
return; return;
} }
for (;;) { auto& pipeline = GetRenderContext().GetPipelineCache().CreateComputePipeline(
const auto recording_generation = buffer.GetRecordingGeneration(); input_info, sh_ctx.GetCs(), cs_shader);
auto vk_buffer = buffer.Handle(); auto bindings = PrepareBindings(buffer, input_info.stage,
auto& pipeline = GetRenderContext().GetPipelineCache().CreateComputePipeline( vk::ShaderStageFlagBits::eCompute,
input_info, sh_ctx.GetCs(), cs_shader); DescriptorCache::Stage::Compute);
RebindBuffers(buffer, bindings);
RebindImages(buffer, bindings);
DescriptorCache::PreparedBindings* binding_sets[] = {&bindings};
ActivateImageWrites(binding_sets);
vk_buffer.bindPipeline(vk::PipelineBindPoint::eCompute, pipeline.pipeline); auto vk_buffer = buffer.Handle();
CommitBindings(buffer, vk::PipelineBindPoint::eCompute, pipeline.pipeline_layout, bindings);
vk_buffer.bindPipeline(vk::PipelineBindPoint::eCompute, pipeline.pipeline);
vk_buffer.dispatch(thread_group_x, thread_group_y, thread_group_z);
BindDescriptors(submit_id, buffer, vk::PipelineBindPoint::eCompute, bool has_storage_writes = HasShaderBufferWrites(input_info.stage);
pipeline.pipeline_layout, input_info.stage, has_storage_writes =
vk::ShaderStageFlagBits::eCompute, DescriptorCache::Stage::Compute); std::any_of(
if (buffer.GetRecordingGeneration() != recording_generation) { program.info.images.begin(), program.info.images.end(),
continue; [](const auto& image) {
} return image.written &&
(image.kind == ShaderRecompiler::IR::ResourceKind::StorageImage ||
vk_buffer.dispatch(thread_group_x, thread_group_y, thread_group_z); image.kind == ShaderRecompiler::IR::ResourceKind::StorageImageUint);
}) ||
bool has_storage_writes = HasShaderBufferWrites(input_info.stage); has_storage_writes;
has_storage_writes = if (has_storage_writes) {
std::any_of( ShaderWriteBarrier(vk_buffer, vk::PipelineStageFlagBits::eComputeShader);
program.info.images.begin(), program.info.images.end(),
[](const auto& image) {
return image.written &&
(image.kind == ShaderRecompiler::IR::ResourceKind::StorageImage ||
image.kind == ShaderRecompiler::IR::ResourceKind::StorageImageUint);
}) ||
has_storage_writes;
if (has_storage_writes) {
ShaderWriteBarrier(vk_buffer, vk::PipelineStageFlagBits::eComputeShader);
}
break;
} }
} }
+226 -107
View File
@@ -39,6 +39,8 @@
#include <cmath> #include <cmath>
#include <cstring> #include <cstring>
#include <limits> #include <limits>
#include <memory>
#include <optional>
#include <span> #include <span>
#include <unordered_map> #include <unordered_map>
#include <vector> #include <vector>
@@ -487,11 +489,11 @@ struct DrawRenderState {
uint32_t color_count = 0; uint32_t color_count = 0;
bool ps_active = true; bool ps_active = true;
VulkanFramebuffer* framebuffer = nullptr; VulkanFramebuffer* framebuffer = nullptr;
vk::CommandBuffer vk_buffer = nullptr;
ShaderVertexInputInfo vs_input_info; ShaderVertexInputInfo vs_input_info;
ShaderPixelInputInfo ps_input_info; ShaderPixelInputInfo ps_input_info;
std::span<const uint32_t> vs_shader; std::span<const uint32_t> vs_shader;
std::span<const uint32_t> ps_shader; std::span<const uint32_t> ps_shader;
std::vector<std::shared_ptr<void>> target_owners;
}; };
struct DrawCallInfo { struct DrawCallInfo {
@@ -558,6 +560,16 @@ struct DrawIndexBufferSource {
vk::IndexType type = vk::IndexType::eUint16; vk::IndexType type = vk::IndexType::eUint16;
}; };
struct PreparedIndexBuffer {
std::shared_ptr<VulkanBuffer> buffer;
const void* host_data = nullptr;
std::vector<uint8_t> owned_data;
uint64_t address = 0;
uint64_t size = 0;
vk::DeviceSize offset = 0;
vk::IndexType type = vk::IndexType::eUint16;
};
static uint64_t VertexBufferDescriptorSize(const ShaderVertexInputBuffer& buffer) { static uint64_t VertexBufferDescriptorSize(const ShaderVertexInputBuffer& buffer) {
return (buffer.stride != 0 ? static_cast<uint64_t>(buffer.stride) * buffer.num_records return (buffer.stride != 0 ? static_cast<uint64_t>(buffer.stride) * buffer.num_records
: buffer.num_records); : buffer.num_records);
@@ -626,10 +638,12 @@ static bool PrepareDrawRenderState(uint64_t submit_id, RenderCommandBuffer& buff
ResolveRenderDepthTarget(submit_id, buffer, state.depth_info); ResolveRenderDepthTarget(submit_id, buffer, state.depth_info);
if (ResolveColorTargets(submit_id, buffer, render_target_slice_offset)) { if (ResolveColorTargets(submit_id, buffer, render_target_slice_offset)) {
MarkRenderTargetGpuWritten(state.depth_info);
return false; return false;
} }
MarkRenderTargetGpuWritten(state.depth_info); if (state.depth_info.vulkan_buffer != nullptr) {
state.target_owners.push_back(
GetRenderContext().GetTextureCache().GetImageOwner(*state.depth_info.vulkan_buffer));
}
if (log_setup_phases) { if (log_setup_phases) {
LogDrawPhase(draw.name, "ResolveRenderColorTarget"); LogDrawPhase(draw.name, "ResolveRenderColorTarget");
@@ -640,7 +654,12 @@ static bool PrepareDrawRenderState(uint64_t submit_id, RenderCommandBuffer& buff
ResolveRenderColorTarget(submit_id, buffer, state.color_info[state.color_count], ResolveRenderColorTarget(submit_id, buffer, state.color_info[state.color_count],
render_target_slice_offset, slot); render_target_slice_offset, slot);
if (state.color_info[state.color_count].vulkan_buffer != nullptr) { if (state.color_info[state.color_count].vulkan_buffer != nullptr) {
MarkRenderTargetGpuWritten(state.color_info[state.color_count]); if (state.color_info[state.color_count].type == RenderColorType::RenderTexture) {
state.target_owners.push_back(GetRenderContext()
.GetTextureCache()
.GetImageOwner(*state.color_info[state.color_count]
.vulkan_buffer));
}
state.color_count++; state.color_count++;
} }
} }
@@ -669,8 +688,6 @@ static bool PrepareDrawRenderState(uint64_t submit_id, RenderCommandBuffer& buff
EXIT_NOT_IMPLEMENTED(state.framebuffer == nullptr); EXIT_NOT_IMPLEMENTED(state.framebuffer == nullptr);
EXIT_NOT_IMPLEMENTED(state.framebuffer->render_pass == nullptr); EXIT_NOT_IMPLEMENTED(state.framebuffer->render_pass == nullptr);
state.vk_buffer = buffer.Handle();
return true; return true;
} }
@@ -714,56 +731,138 @@ static void RefreshShaders(RenderCommandBuffer& buffer, const DrawCallInfo& draw
} }
} }
static void BindDrawVertexBuffers(uint64_t submit_id, RenderCommandBuffer& buffer, static std::vector<BufferBinding>
const DrawCallInfo& draw, vk::CommandBuffer vk_buffer, PrepareVertexBuffers(uint64_t submit_id, RenderCommandBuffer& buffer, const DrawCallInfo& draw,
const ShaderVertexInputInfo& vs_input_info) { const ShaderVertexInputInfo& vs_input_info) {
EXIT_IF(draw.name == nullptr); EXIT_IF(draw.name == nullptr);
(void)submit_id; (void)submit_id;
LogDrawPhase(draw.name, "BindVertexBuffers"); LogDrawPhase(draw.name, "PrepareVertexBuffers");
std::vector<BufferBinding> bindings;
bindings.reserve(vs_input_info.buffers_num);
for (int i = 0; i < vs_input_info.buffers_num; i++) { for (int i = 0; i < vs_input_info.buffers_num; i++) {
const auto& b = vs_input_info.buffers[i]; const auto& b = vs_input_info.buffers[i];
uint64_t addr = b.addr; const auto size = VertexBufferDescriptorSize(b);
uint64_t size = VertexBufferDescriptorSize(b);
VulkanBuffer* vertices = nullptr;
vk::DeviceSize offset = 0;
if (size == 0) { if (size == 0) {
vertices = &GetRenderContext().GetBufferCache().ObtainNullBuffer(buffer); bindings.push_back({GetRenderContext().GetBufferCache().ObtainNullBuffer(), 0});
} else { } else {
auto binding = GetRenderContext().GetBufferCache().ObtainBuffer(buffer, addr, size); bindings.push_back(
vertices = &binding.buffer; GetRenderContext().GetBufferCache().ObtainBuffer(buffer, b.addr, size));
offset = binding.offset;
} }
EXIT_NOT_IMPLEMENTED(vertices == nullptr); }
return bindings;
}
vk_buffer.bindVertexBuffers(i, 1, &vertices->buffer, &offset); static void RebindVertexBuffers(RenderCommandBuffer& buffer,
const ShaderVertexInputInfo& vs_input_info,
std::vector<BufferBinding>& bindings) {
EXIT_IF(bindings.size() != static_cast<size_t>(vs_input_info.buffers_num));
for (int i = 0; i < vs_input_info.buffers_num; i++) {
const auto& vertex = vs_input_info.buffers[i];
const auto size = VertexBufferDescriptorSize(vertex);
bindings[i] = size == 0
? BufferBinding {GetRenderContext().GetBufferCache().ObtainNullBuffer(), 0}
: GetRenderContext().GetBufferCache().ObtainBuffer(buffer, vertex.addr, size);
} }
} }
static void BindDrawIndexBuffer(RenderCommandBuffer& buffer, vk::CommandBuffer vk_buffer, static PreparedIndexBuffer PrepareIndexBuffer(RenderCommandBuffer& buffer,
const DrawIndexBufferSource& source) { const DrawIndexBufferSource& source) {
PreparedIndexBuffer prepared;
if (!source.enabled) { if (!source.enabled) {
return; return prepared;
} }
EXIT_IF(source.size == 0); EXIT_IF(source.size == 0);
prepared.host_data = source.host_data;
VulkanBuffer* index_buffer = nullptr; prepared.address = source.address;
vk::DeviceSize index_offset = 0; prepared.size = source.size;
prepared.type = source.type;
if (source.host_data != nullptr) { if (source.host_data != nullptr) {
return prepared;
} else {
auto binding = GetRenderContext().GetBufferCache().ObtainBuffer(
buffer, source.address, source.size);
prepared.buffer = std::move(binding.buffer);
prepared.owned_data = std::move(binding.host_data);
prepared.offset = binding.offset;
}
return prepared;
}
static void RebindIndexBuffer(RenderCommandBuffer& buffer, PreparedIndexBuffer& prepared) {
if (prepared.size == 0 || prepared.host_data != nullptr) {
return;
}
auto binding = GetRenderContext().GetBufferCache().ObtainBuffer(
buffer, prepared.address, prepared.size);
prepared.buffer = std::move(binding.buffer);
prepared.owned_data = std::move(binding.host_data);
prepared.offset = binding.offset;
}
static void RevalidateDrawTargets(DrawRenderState& state) {
auto old_owners = std::move(state.target_owners);
std::vector<std::shared_ptr<void>> current_owners;
auto validate = [&](VulkanImage& image) {
auto owner = GetRenderContext().GetTextureCache().GetImageOwner(image);
const auto found = std::find_if(old_owners.begin(), old_owners.end(), [&](const auto& old) {
return old.get() == owner.get();
});
EXIT_IF(found == old_owners.end());
current_owners.push_back(std::move(owner));
};
if (state.depth_info.vulkan_buffer != nullptr) {
validate(*state.depth_info.vulkan_buffer);
}
for (uint32_t i = 0; i < state.color_count; i++) {
if (state.color_info[i].type == RenderColorType::RenderTexture) {
validate(*state.color_info[i].vulkan_buffer);
}
}
state.target_owners = std::move(current_owners);
}
static void CommitVertexBuffers(RenderCommandBuffer& buffer, vk::CommandBuffer vk_buffer,
std::vector<BufferBinding>& bindings) {
for (uint32_t slot = 0; slot < bindings.size(); slot++) {
auto& binding = bindings[slot];
VulkanBuffer* vertex_buffer = binding.buffer.get();
vk::DeviceSize vertex_offset = binding.offset;
if (!binding.host_data.empty()) {
vk::DeviceSize range = 0;
EXIT_IF(!GetRenderContext().GetBufferCache().UploadHostData(
buffer, binding.host_data.data(), binding.host_data.size(), 16, vertex_buffer,
vertex_offset, range));
} else {
buffer.RetainResourceUntilFence(binding.buffer);
}
EXIT_IF(vertex_buffer == nullptr);
vk_buffer.bindVertexBuffers(slot, 1, &vertex_buffer->buffer, &vertex_offset);
}
}
static void CommitIndexBuffer(RenderCommandBuffer& buffer, vk::CommandBuffer vk_buffer,
const PreparedIndexBuffer& prepared) {
if (prepared.size == 0) {
return;
}
VulkanBuffer* index_buffer = prepared.buffer.get();
vk::DeviceSize index_offset = prepared.offset;
const void* host_data = prepared.host_data;
if (host_data == nullptr && !prepared.owned_data.empty()) {
host_data = prepared.owned_data.data();
}
if (host_data != nullptr) {
vk::DeviceSize range = 0; vk::DeviceSize range = 0;
if (!GetRenderContext().GetBufferCache().UploadHostData( if (!GetRenderContext().GetBufferCache().UploadHostData(
buffer, source.host_data, source.size, 16, index_buffer, index_offset, range)) { buffer, host_data, prepared.size, 16, index_buffer, index_offset, range)) {
EXIT("failed to upload host index buffer\n"); EXIT("failed to upload host index buffer\n");
} }
} else { } else {
auto binding = buffer.RetainResourceUntilFence(prepared.buffer);
GetRenderContext().GetBufferCache().ObtainBuffer(buffer, source.address, source.size);
index_buffer = &binding.buffer;
index_offset = binding.offset;
} }
EXIT_IF(index_buffer == nullptr); EXIT_IF(index_buffer == nullptr);
vk_buffer.bindIndexBuffer(index_buffer->buffer, index_offset, source.type); vk_buffer.bindIndexBuffer(index_buffer->buffer, index_offset, prepared.type);
} }
static void LogDrawStateIfNeeded(const RenderCommandBuffer& buffer, const DrawCallInfo& draw, static void LogDrawStateIfNeeded(const RenderCommandBuffer& buffer, const DrawCallInfo& draw,
@@ -865,88 +964,108 @@ static void ExecutePreparedDraw(uint64_t submit_id, RenderCommandBuffer& buffer,
bool set_bind_debug, bool set_auto_debug) { bool set_bind_debug, bool set_auto_debug) {
EXIT_IF(draw.name == nullptr); EXIT_IF(draw.name == nullptr);
auto& ucfg = buffer.GetUserConfig(); auto& ucfg = buffer.GetUserConfig();
if (log_pipeline_phase) {
LogDrawPhase(draw.name, "CreatePipeline");
}
auto& pipeline = GetRenderContext().GetPipelineCache().CreateGraphicsPipeline(
*state.framebuffer, state.color_info, state.color_count, state.depth_info,
state.vs_input_info, buffer, &state.ps_input_info, topology, state.ps_active,
state.vs_shader, state.ps_shader);
for (;;) { LogDrawPhase(draw.name, "PrepareBindingsVS");
const auto recording_generation = buffer.GetRecordingGeneration(); auto vs_bindings = PrepareBindings(buffer, state.vs_input_info.stage,
if (log_pipeline_phase) { vk::ShaderStageFlagBits::eVertex,
LogDrawPhase(draw.name, "CreatePipeline"); DescriptorCache::Stage::Vertex);
} std::optional<DescriptorCache::PreparedBindings> ps_bindings;
auto& pipeline = GetRenderContext().GetPipelineCache().CreateGraphicsPipeline( if (state.ps_active) {
*state.framebuffer, state.color_info, state.color_count, state.depth_info, LogDrawPhase(draw.name, "PrepareBindingsPS");
state.vs_input_info, buffer, &state.ps_input_info, topology, state.ps_active, ps_bindings.emplace(PrepareBindings(buffer, state.ps_input_info.stage,
state.vs_shader, state.ps_shader); vk::ShaderStageFlagBits::eFragment,
DescriptorCache::Stage::Pixel));
}
auto vertex_bindings = PrepareVertexBuffers(submit_id, buffer, draw, state.vs_input_info);
auto index_binding = PrepareIndexBuffer(buffer, index_source);
if (set_bind_debug) { // The discovery pass above can merge cache allocations. Rebind every buffer only after the
SetDrawDebugPhase(buffer, submit_id, draw, 0x100u); // complete draw resource set is known.
} RebindBuffers(buffer, vs_bindings);
state.vk_buffer.bindPipeline(vk::PipelineBindPoint::eGraphics, pipeline.pipeline); if (ps_bindings.has_value()) {
const auto dynamic_params = BuildGraphicsDynamicParams(buffer, state.color_info, RebindBuffers(buffer, *ps_bindings);
state.color_count, state.depth_info); }
SetDynamicParams(buffer, state.vk_buffer, dynamic_params); RebindVertexBuffers(buffer, state.vs_input_info, vertex_bindings);
RebindIndexBuffer(buffer, index_binding);
RebindImages(buffer, vs_bindings);
if (ps_bindings.has_value()) {
RebindImages(buffer, *ps_bindings);
}
// EXIT_NOT_IMPLEMENTED(vs_input_info.buffers_num > 1); // Resource preparation above may synchronously finish and restart the scheduler. From this
BindDrawVertexBuffers(submit_id, buffer, draw, state.vk_buffer, state.vs_input_info); // point onward, every operation targets the current command buffer and cannot touch guest
// memory.
RevalidateDrawTargets(state);
DescriptorCache::PreparedBindings* binding_sets[] = {
&vs_bindings, ps_bindings.has_value() ? &*ps_bindings : nullptr};
ActivateImageWrites(binding_sets);
MarkRenderTargetGpuWritten(state.depth_info);
for (uint32_t i = 0; i < state.color_count; i++) {
MarkRenderTargetGpuWritten(state.color_info[i]);
}
for (const auto& owner: state.target_owners) {
buffer.RetainResourceUntilFence(owner);
}
LogDrawPhase(draw.name, "BindDescriptorsVS"); auto vk_buffer = buffer.Handle();
if (set_bind_debug) {
SetDrawDebugPhase(buffer, submit_id, draw, 0x100u);
}
if (set_auto_debug) {
SetDrawDebugPhase(buffer, submit_id, draw, 0x200u);
}
CommitVertexBuffers(buffer, vk_buffer, vertex_bindings);
CommitBindings(buffer, vk::PipelineBindPoint::eGraphics, pipeline.pipeline_layout,
vs_bindings);
if (ps_bindings.has_value()) {
if (set_auto_debug) { if (set_auto_debug) {
SetDrawDebugPhase(buffer, submit_id, draw, 0x200u); SetDrawDebugPhase(buffer, submit_id, draw, 0x300u);
} }
BindDescriptors(submit_id, buffer, vk::PipelineBindPoint::eGraphics, CommitBindings(buffer, vk::PipelineBindPoint::eGraphics, pipeline.pipeline_layout,
pipeline.pipeline_layout, state.vs_input_info.stage, *ps_bindings);
vk::ShaderStageFlagBits::eVertex, DescriptorCache::Stage::Vertex); }
CommitIndexBuffer(buffer, vk_buffer, index_binding);
if (state.ps_active) { const auto dynamic_params = BuildGraphicsDynamicParams(buffer, state.color_info,
LogDrawPhase(draw.name, "BindDescriptorsPS"); state.color_count, state.depth_info);
if (set_auto_debug) { SetDynamicParams(buffer, vk_buffer, dynamic_params);
SetDrawDebugPhase(buffer, submit_id, draw, 0x300u);
}
BindDescriptors(submit_id, buffer, vk::PipelineBindPoint::eGraphics,
pipeline.pipeline_layout, state.ps_input_info.stage,
vk::ShaderStageFlagBits::eFragment, DescriptorCache::Stage::Pixel);
}
if (buffer.GetRecordingGeneration() != recording_generation) {
continue;
}
// Index data may use this command buffer's host stream. Resolve it only after the other
// fault-capable bindings, and rebuild it whenever a fault reset changes the generation.
BindDrawIndexBuffer(buffer, state.vk_buffer, index_source);
if (buffer.GetRecordingGeneration() != recording_generation) {
continue;
}
LogDrawPhase(draw.name, "BeginRenderPass"); LogDrawPhase(draw.name, "BeginRenderPass");
if (set_auto_debug) { if (set_auto_debug) {
SetDrawDebugPhase(buffer, submit_id, draw, 0x400u); SetDrawDebugPhase(buffer, submit_id, draw, 0x400u);
} }
buffer.BeginRenderPass(*state.framebuffer, state.color_info, state.color_count, buffer.BeginRenderPass(*state.framebuffer, state.color_info, state.color_count,
state.depth_info); state.depth_info);
if (set_auto_debug) { vk_buffer.bindPipeline(vk::PipelineBindPoint::eGraphics, pipeline.pipeline);
SetDrawDebugPhase(buffer, submit_id, draw, 0x500u); if (set_auto_debug) {
} SetDrawDebugPhase(buffer, submit_id, draw, 0x500u);
}
EmitDrawPrimitives(ucfg, vk_buffer, state.vs_input_info, draw, emit);
EmitDrawPrimitives(ucfg, state.vk_buffer, state.vs_input_info, draw, emit); if (set_auto_debug) {
SetDrawDebugPhase(buffer, submit_id, draw, 0x600u);
if (set_auto_debug) { }
SetDrawDebugPhase(buffer, submit_id, draw, 0x600u); buffer.EndRenderPass();
} vk::PipelineStageFlags shader_write_stages = {};
buffer.EndRenderPass(); if (HasShaderBufferWrites(state.vs_input_info.stage)) {
vk::PipelineStageFlags shader_write_stages = {}; shader_write_stages |= vk::PipelineStageFlagBits::eVertexShader;
if (HasShaderBufferWrites(state.vs_input_info.stage)) { }
shader_write_stages |= vk::PipelineStageFlagBits::eVertexShader; if (state.ps_active && HasShaderBufferWrites(state.ps_input_info.stage)) {
} shader_write_stages |= vk::PipelineStageFlagBits::eFragmentShader;
if (state.ps_active) { }
if (HasShaderBufferWrites(state.ps_input_info.stage)) { if (shader_write_stages) {
shader_write_stages |= vk::PipelineStageFlagBits::eFragmentShader; ShaderWriteBarrier(vk_buffer, shader_write_stages);
} }
} LogDrawPhase(draw.name, "EndRenderPass");
if (shader_write_stages) { if (set_auto_debug) {
ShaderWriteBarrier(state.vk_buffer, shader_write_stages); SetDrawDebugPhase(buffer, submit_id, draw, 0x700u);
}
LogDrawPhase(draw.name, "EndRenderPass");
if (set_auto_debug) {
SetDrawDebugPhase(buffer, submit_id, draw, 0x700u);
}
break;
} }
} }
@@ -340,6 +340,17 @@ VulkanImage& TextureCache::PublishImage(CommandBuffer& command,
return result; return result;
} }
std::shared_ptr<void> TextureCache::GetImageOwner(const VulkanImage& image) {
FaultSafeTextureLock lock(this, m_lock);
const auto owner = std::ranges::find_if(
m_images, [&](const auto& cached) { return cached->image == &image; });
if (owner == m_images.end()) {
EXIT("TextureCache: image has no cache owner, image=%p\n",
static_cast<const void*>(&image));
}
return *owner;
}
std::vector<TextureCache::CachedImage*> std::vector<TextureCache::CachedImage*>
TextureCache::FindImagesInRegionLocked(uint64_t vaddr, uint64_t size, bool page_overlap) { TextureCache::FindImagesInRegionLocked(uint64_t vaddr, uint64_t size, bool page_overlap) {
return page_overlap ? m_image_owner_index.QueryCandidates(vaddr, size) return page_overlap ? m_image_owner_index.QueryCandidates(vaddr, size)
@@ -1484,7 +1495,7 @@ VulkanImage& TextureCache::FindTexture(CommandBuffer& command, const ImageInfo&
return *storage_match->image; return *storage_match->image;
} }
if (!storage_retire.empty()) { if (!storage_retire.empty()) {
// shadPS4 resolves a modified cached mip before replacing it with the containing image. // Resolve a modified cached mip before replacing it with the containing image.
// Kyty does not yet copy between those independently allocated Vulkan images, so use the // Kyty does not yet copy between those independently allocated Vulkan images, so use the
// existing synchronized tiled readback seam and rebuild the complete chain from coherent // existing synchronized tiled readback seam and rebuild the complete chain from coherent
// guest backing. This is an uncommon ownership transition, not a frame lookup fast path. // guest backing. This is an uncommon ownership transition, not a frame lookup fast path.
@@ -118,6 +118,7 @@ public:
VulkanImage& GetDummySampledTexture(bool uint_format, bool image_3d); VulkanImage& GetDummySampledTexture(bool uint_format, bool image_3d);
VulkanImage& GetDummyStorageTexture(bool uint_format, bool image_3d); VulkanImage& GetDummyStorageTexture(bool uint_format, bool image_3d);
[[nodiscard]] std::shared_ptr<void> GetImageOwner(const VulkanImage& image);
private: private:
struct CachedImage; struct CachedImage;
+54
View File
@@ -10557,6 +10557,56 @@ void CheckBufferCacheRangeMerge() {
std::printf("[host] %-32s ok\n", "BufferCacheRangeMerge"); std::printf("[host] %-32s ok\n", "BufferCacheRangeMerge");
} }
void CheckBufferBindingStabilization() {
constexpr std::array<BufferCacheRange, 4> requests{{
{0x10000, 0x4000},
{0x12000, 0x6000},
{0x17000, 0x5000},
{0x11000, 0x1000},
}};
BufferCacheRange allocation = requests.front();
for (const auto request : std::span{requests}.subspan(1)) {
Require("BufferBindingStabilization", "discovery",
MergeOverlappingBufferCacheRange(allocation, request),
"overlapping discovery request did not join the cache allocation");
}
Require("BufferBindingStabilization", "canonical range",
allocation.address == 0x10000 && allocation.size == 0xc000,
"discovery did not produce the complete cache allocation");
for (const auto request : requests) {
const auto before = allocation;
Require("BufferBindingStabilization", "second pass",
MergeOverlappingBufferCacheRange(allocation, request) &&
allocation.address == before.address &&
allocation.size == before.size,
"second-pass binding changed a fully discovered allocation");
}
std::printf("[host] %-32s ok\n", "BufferBindingStabilization");
}
void CheckImageWriteActivationPolicy() {
Require("ImageWriteActivationPolicy", "standalone storage",
ClassifyImageWriteActivation(VulkanImageType::StorageTexture) ==
ImageWriteActivation::Implicit,
"standalone storage image was sent through target write activation");
for (const auto type : {VulkanImageType::VideoOut,
VulkanImageType::DepthStencil,
VulkanImageType::RenderTexture}) {
Require("ImageWriteActivationPolicy", "cache target",
ClassifyImageWriteActivation(type) ==
ImageWriteActivation::Explicit,
"writable cache target skipped explicit write activation");
}
for (const auto type : {VulkanImageType::Unknown, VulkanImageType::Texture}) {
Require("ImageWriteActivationPolicy", "unsupported",
ClassifyImageWriteActivation(type) ==
ImageWriteActivation::Unsupported,
"unsupported writable image type received an activation policy");
}
std::printf("[host] %-32s ok\n", "ImageWriteActivationPolicy");
}
ShaderRecompiler::IR::ImageResource BasicStorageTextureResource() { ShaderRecompiler::IR::ImageResource BasicStorageTextureResource() {
ShaderRecompiler::IR::ImageResource resource{}; ShaderRecompiler::IR::ImageResource resource{};
resource.kind = ShaderRecompiler::IR::ResourceKind::StorageImage; resource.kind = ShaderRecompiler::IR::ResourceKind::StorageImage;
@@ -14245,6 +14295,8 @@ int main(int argc, char **argv) {
} }
if (argc == 2 && std::strcmp(argv[1], "--buffer-cache-range-only") == 0) { if (argc == 2 && std::strcmp(argv[1], "--buffer-cache-range-only") == 0) {
CheckBufferCacheRangeMerge(); CheckBufferCacheRangeMerge();
CheckBufferBindingStabilization();
CheckImageWriteActivationPolicy();
return 0; return 0;
} }
if (argc == 2 && std::strcmp(argv[1], "--storage-bgra-only") == 0) { if (argc == 2 && std::strcmp(argv[1], "--storage-bgra-only") == 0) {
@@ -14296,6 +14348,8 @@ int main(int argc, char **argv) {
CheckSampledDepthResource(); CheckSampledDepthResource();
CheckSampledDepthDescriptor(); CheckSampledDepthDescriptor();
CheckBufferCacheRangeMerge(); CheckBufferCacheRangeMerge();
CheckBufferBindingStabilization();
CheckImageWriteActivationPolicy();
CheckBasicStorageTextureDescriptor(); CheckBasicStorageTextureDescriptor();
CheckStorageTextureLinearUploadLayout(); CheckStorageTextureLinearUploadLayout();
CheckStorageTextureDepthTileUploadLayout(); CheckStorageTextureDepthTileUploadLayout();