diff --git a/src/graphics/host_gpu/renderer/bufferCache.cpp b/src/graphics/host_gpu/renderer/bufferCache.cpp index d355ef222..5465f2b1e 100644 --- a/src/graphics/host_gpu/renderer/bufferCache.cpp +++ b/src/graphics/host_gpu/renderer/bufferCache.cpp @@ -636,24 +636,16 @@ BufferBinding BufferCache::ObtainBuffer(CommandBuffer& command, uint64_t vaddr, const auto end = AlignUp(vaddr + size); std::lock_guard transaction(m_resource_mutex); 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 - // views may coexist with a small CPU-current read; Kyty's separate image trackers require - // GPU-dirty ownership guards here. Read physical backing so - // host page protection is irrelevant. + // Snapshot small CPU-current reads now and upload them only during the final non-resetting + // commit. This keeps unaligned guest addresses valid without tying preparation to a command-local + // stream allocation. if (is_read && !is_written && size <= CACHING_PAGE_SIZE && !m_memory_tracker.IsRegionGpuModified(vaddr, size) && m_memory_tracker.IsRegionCpuModified(vaddr, size) && !texture_region.gpu_image_bytes && !texture_region.gpu_metadata_bytes) { - std::array guest_data; + std::vector guest_data(size); if (Libs::LibKernel::Memory::TryReadBacking(vaddr, guest_data.data(), size)) { - VulkanBuffer* stream_buffer = nullptr; - vk::DeviceSize stream_offset = 0; - vk::DeviceSize stream_range = 0; - const auto stream_alignment = std::max(m_graphics.StorageMinAlignment(), 16); - if (UploadHostData(command, guest_data.data(), size, stream_alignment, stream_buffer, - stream_offset, stream_range)) { - return {*stream_buffer, stream_offset}; - } + return {nullptr, 0, std::move(guest_data)}; } } const auto texture_pages = begin == vaddr && end - begin == size @@ -829,8 +821,7 @@ BufferBinding BufferCache::ObtainBuffer(CommandBuffer& command, uint64_t vaddr, if (is_written) { 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, @@ -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); } -VulkanBuffer& BufferCache::ObtainNullBuffer(CommandBuffer& command) { - if (command.IsInvalid() || command.IsExecute()) { - EXIT("BufferCache: null buffer requires a graphics context\n"); - } +std::shared_ptr BufferCache::ObtainNullBuffer() { FaultSafeCacheLock lock(this, m_mutex); if (m_null_buffer == nullptr) { // robustBufferAccess makes every fetch safe; TODO: Use a @@ -865,10 +853,7 @@ VulkanBuffer& BufferCache::ObtainNullBuffer(CommandBuffer& command) { m_null_buffer = std::move(buffer); } - // The buffer remains cache-persistent, while every recorded consumer keeps the allocation - // alive until its own fence even if an unrelated command processor resets the global cache. - command.RetainResourceUntilFence(m_null_buffer); - return *m_null_buffer; + return m_null_buffer; } 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); - const auto [dst, dst_offset] = ObtainBuffer(*command, vaddr, size, true, false); - const auto before = MakeDmaBarrier( - dst, dst_offset, size, vk::AccessFlagBits::eMemoryRead | vk::AccessFlagBits::eMemoryWrite, + auto dst = ObtainBuffer(*command, vaddr, size, true, false); + EXIT_IF(dst.buffer == nullptr || !dst.host_data.empty()); + command->RetainResourceUntilFence(dst.buffer); + const auto before = MakeDmaBarrier( + *dst.buffer, dst.offset, size, + vk::AccessFlagBits::eMemoryRead | vk::AccessFlagBits::eMemoryWrite, vk::AccessFlagBits::eTransferWrite); const auto vk_buffer = command->Handle(); vk_buffer.pipelineBarrier( vk::PipelineStageFlagBits::eAllCommands, vk::PipelineStageFlagBits::eTransfer, 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 = - MakeDmaBarrier(dst, dst_offset, size, vk::AccessFlagBits::eTransferWrite, + MakeDmaBarrier(*dst.buffer, dst.offset, size, vk::AccessFlagBits::eTransferWrite, vk::AccessFlagBits::eMemoryRead | vk::AccessFlagBits::eMemoryWrite); vk_buffer.pipelineBarrier(vk::PipelineStageFlagBits::eTransfer, vk::PipelineStageFlagBits::eAllCommands, @@ -1128,31 +1116,44 @@ void BufferCache::CopyBuffer(CommandBuffer* command, uint64_t dst_vaddr, uint64_ } } EXIT_IF(command == nullptr); - const auto [src, src_offset] = ObtainBuffer(*command, src_vaddr, size, false, true); - const auto [dst, dst_offset] = - ObtainBuffer(*command, dst_vaddr, size, true, false, dst_image_transition); - if (&src == &dst && src_offset < dst_offset + size && dst_offset < src_offset + size) { + auto src = ObtainBuffer(*command, src_vaddr, size, false, true); + auto dst = ObtainBuffer(*command, dst_vaddr, size, true, false, dst_image_transition); + EXIT_IF(dst.buffer == nullptr || !dst.host_data.empty()); + 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 " dst_offset=0x%016" PRIx64 " size=0x%016" PRIx64 "\n", - src_offset, dst_offset, size); + src.offset, dst.offset, size); } const vk::BufferMemoryBarrier before[] = { - MakeDmaBarrier(dst, dst_offset, size, + MakeDmaBarrier(*dst.buffer, dst.offset, size, vk::AccessFlagBits::eMemoryRead | vk::AccessFlagBits::eMemoryWrite, vk::AccessFlagBits::eTransferWrite), - MakeDmaBarrier(src, src_offset, size, vk::AccessFlagBits::eMemoryWrite, + MakeDmaBarrier(*src_buffer, src_offset, size, vk::AccessFlagBits::eMemoryWrite, vk::AccessFlagBits::eTransferRead), }; const auto vk_buffer = command->Handle(); vk_buffer.pipelineBarrier(vk::PipelineStageFlagBits::eAllCommands, vk::PipelineStageFlagBits::eTransfer, vk::DependencyFlagBits::eByRegion, 0, nullptr, 2, before, 0, nullptr); - const vk::BufferCopy copy {src_offset, dst_offset, size}; - vk_buffer.copyBuffer(src.buffer, dst.buffer, 1, ©); + const vk::BufferCopy copy {src_offset, dst.offset, size}; + vk_buffer.copyBuffer(src_buffer->buffer, dst.buffer->buffer, 1, ©); 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), - MakeDmaBarrier(src, src_offset, size, vk::AccessFlagBits::eTransferRead, + MakeDmaBarrier(*src_buffer, src_offset, size, vk::AccessFlagBits::eTransferRead, vk::AccessFlagBits::eMemoryWrite), }; vk_buffer.pipelineBarrier(vk::PipelineStageFlagBits::eTransfer, diff --git a/src/graphics/host_gpu/renderer/bufferCache.h b/src/graphics/host_gpu/renderer/bufferCache.h index 99c98c1bb..b887c467c 100644 --- a/src/graphics/host_gpu/renderer/bufferCache.h +++ b/src/graphics/host_gpu/renderer/bufferCache.h @@ -10,6 +10,7 @@ #include #include #include +#include namespace Libs::Graphics { @@ -36,8 +37,9 @@ struct BufferCacheRange { }; struct BufferBinding { - VulkanBuffer& buffer; - uint64_t offset; + std::shared_ptr buffer; + uint64_t offset = 0; + std::vector host_data; }; [[nodiscard]] bool MergeOverlappingBufferCacheRange(BufferCacheRange& merged, @@ -65,7 +67,7 @@ public: [[nodiscard]] bool UploadHostData(CommandBuffer& command, const void* src, uint64_t size, uint64_t alignment, VulkanBuffer*& out_buffer, uint64_t& out_offset, uint64_t& out_range); - [[nodiscard]] VulkanBuffer& ObtainNullBuffer(CommandBuffer& command); + [[nodiscard]] std::shared_ptr ObtainNullBuffer(); [[nodiscard]] BufferImageCopySource ObtainBufferForImage(uint64_t vaddr, uint64_t size); 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); diff --git a/src/graphics/host_gpu/renderer/context.cpp b/src/graphics/host_gpu/renderer/context.cpp index 6c6501ff3..dbf55ce76 100644 --- a/src/graphics/host_gpu/renderer/context.cpp +++ b/src/graphics/host_gpu/renderer/context.cpp @@ -358,7 +358,6 @@ void CommandBuffer::FinalizeFence(bool reset_recording) { m_fence_waited = false; if (reset_recording) { ResetNativeCommandBuffer(m_slot->buffer); - m_recording_generation++; } } if (reset_recording) { diff --git a/src/graphics/host_gpu/renderer/descriptorCache.h b/src/graphics/host_gpu/renderer/descriptorCache.h index 73a7cf4f4..65fa6d49e 100644 --- a/src/graphics/host_gpu/renderer/descriptorCache.h +++ b/src/graphics/host_gpu/renderer/descriptorCache.h @@ -10,6 +10,8 @@ #include "graphics/shader/shaderBindings.h" #include +#include +#include #include #include @@ -17,6 +19,7 @@ namespace Libs::Graphics { namespace ShaderRecompiler::IR { struct Program; +struct ResourceSnapshot; } class CommandBuffer; @@ -29,9 +32,11 @@ struct VulkanDescriptorSet { }; struct BufferView { - VulkanBuffer* buffer = nullptr; - vk::DeviceSize offset = 0; - vk::DeviceSize range = VK_WHOLE_SIZE; + std::shared_ptr owner; + VulkanBuffer* buffer = nullptr; + vk::DeviceSize offset = 0; + vk::DeviceSize range = VK_WHOLE_SIZE; + std::vector host_data; }; class DescriptorCache { @@ -42,6 +47,7 @@ public: VulkanImage* image = nullptr; int view = VulkanImage::VIEW_DEFAULT; vk::ImageView image_view = nullptr; + std::shared_ptr owner; }; enum class TextureVariant : int { @@ -63,6 +69,17 @@ public: BufferView user_data; }; + struct PreparedBindings { + std::shared_ptr program; + std::shared_ptr snapshot; + NativeDescriptors resources; + std::vector flattened_srt; + std::vector 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()); } @@ -95,10 +112,14 @@ private: std::map, vk::DescriptorSetLayout> m_descriptor_set_layouts; }; -void BindDescriptors(uint64_t submit_id, CommandBuffer& buffer, - vk::PipelineBindPoint pipeline_bind_point, vk::PipelineLayout layout, - const ShaderStageRuntime& runtime, vk::ShaderStageFlags vk_stage, - DescriptorCache::Stage stage); +[[nodiscard]] DescriptorCache::PreparedBindings +PrepareBindings(CommandBuffer& buffer, const ShaderStageRuntime& runtime, + vk::ShaderStageFlags shader_stage, DescriptorCache::Stage stage); +void RebindBuffers(CommandBuffer& buffer, DescriptorCache::PreparedBindings& bindings); +void RebindImages(CommandBuffer& buffer, DescriptorCache::PreparedBindings& bindings); +void ActivateImageWrites(std::span bindings); +void CommitBindings(CommandBuffer& buffer, vk::PipelineBindPoint pipeline_bind_point, + vk::PipelineLayout layout, DescriptorCache::PreparedBindings& bindings); } // namespace Libs::Graphics diff --git a/src/graphics/host_gpu/renderer/descriptors.cpp b/src/graphics/host_gpu/renderer/descriptors.cpp index 13e67ebcd..c81dacec9 100644 --- a/src/graphics/host_gpu/renderer/descriptors.cpp +++ b/src/graphics/host_gpu/renderer/descriptors.cpp @@ -37,6 +37,7 @@ #include #include #include +#include #ifdef min #undef min @@ -49,8 +50,21 @@ namespace Libs::Graphics { using TextureVariant = DescriptorCache::TextureVariant; -static void BindNullStorageBuffer(CommandBuffer& cmd_buffer, BufferView& dst) { - dst.buffer = &GetRenderContext().GetBufferCache().ObtainNullBuffer(cmd_buffer); +ImageWriteActivation ClassifyImageWriteActivation(VulkanImageType type) noexcept { + 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.range = 16; } @@ -119,7 +133,7 @@ static void CopyNativeDescriptor(const ShaderRecompiler::IR::DescriptorValue& so 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 ShaderRecompiler::IR::BufferResource& resource) { BufferView result; @@ -132,7 +146,7 @@ static BufferView NativeStorageBuffer(uint64_t submit_id, CommandBuffer& command } const auto size = stride != 0 ? static_cast(stride) * records : records; if (address == 0 || size == 0) { - BindNullStorageBuffer(command_buffer, result); + BindNullStorageBuffer(result); return result; } const auto& graphics = GetRenderContext().GetGraphics(); @@ -142,25 +156,26 @@ static BufferView NativeStorageBuffer(uint64_t submit_id, CommandBuffer& command BufferCache::CACHING_PAGE_SIZE % alignment != 0) { EXIT("storage buffer range or device alignment is unsupported\n"); } - (void)submit_id; auto binding = GetRenderContext().GetBufferCache().ObtainBuffer( 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"); } - result.buffer = &binding.buffer; - result.offset = binding.offset; - result.range = static_cast(size); + result.owner = std::move(binding.buffer); + result.buffer = result.owner.get(); + result.offset = binding.offset; + result.range = static_cast(size); + result.host_data = std::move(binding.host_data); return result; } static BufferView -NativeAddressBuffer(uint64_t submit_id, CommandBuffer& command_buffer, +NativeAddressBuffer(CommandBuffer& command_buffer, const ShaderRecompiler::IR::AddressResource& resource, const ShaderRecompiler::IR::ResourceSnapshot::Address& address) { BufferView result; if (address.binding_base == 0) { - BindNullStorageBuffer(command_buffer, result); + BindNullStorageBuffer(result); return result; } if (resource.written) { @@ -185,12 +200,13 @@ NativeAddressBuffer(uint64_t submit_id, CommandBuffer& command_buffer, BufferCache::GetBufferOffset(address.binding_base) % alignment != 0) { EXIT("address resource range or alignment is unsupported\n"); } - (void)submit_id; auto binding = GetRenderContext().GetBufferCache().ObtainBuffer(command_buffer, address.binding_base, size); - result.buffer = &binding.buffer; - result.offset = binding.offset; - result.range = static_cast(size); + result.owner = std::move(binding.buffer); + result.buffer = result.owner.get(); + result.offset = binding.offset; + result.range = static_cast(size); + result.host_data = std::move(binding.host_data); return result; } @@ -475,8 +491,7 @@ void ValidateMetadataReuseTexture(const ShaderRecompiler::IR::ImageResource& res } static DescriptorCache::TextureBinding -NativeTexture(uint64_t submit_id, CommandBuffer& command_buffer, - const ShaderRecompiler::IR::ImageResource& resource, +NativeTexture(CommandBuffer& command_buffer, const ShaderRecompiler::IR::ImageResource& resource, const ShaderRecompiler::IR::DescriptorValue& value) { ShaderTextureResource descriptor; CopyNativeDescriptor(value, descriptor.fields); @@ -536,6 +551,7 @@ NativeTexture(uint64_t submit_id, CommandBuffer& command_buffer, VulkanImage* image = nullptr; int view = VulkanImage::VIEW_DEFAULT; vk::ImageView image_view = nullptr; + bool external = false; const bool check_depth = static_cast(tile) == Prospero::TileMode::kDepth || descriptor.MsaaDepth(); 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) { EXIT("required cached texture image view is missing\n"); } - if (storage && image != nullptr) { - GetRenderContext().GetTextureCache().MarkGpuWritten(*image); - } } } if (image == nullptr) { const auto video = Presentation::DisplayBufferFind(address); if (video.image != nullptr) { + external = true; if (storage) { const bool exact = resource.kind == ShaderRecompiler::IR::ResourceKind::StorageImageUint && @@ -644,7 +658,6 @@ NativeTexture(uint64_t submit_id, CommandBuffer& command_buffer, } image = video.image; view = VulkanImage::VIEW_STORAGE; - GetRenderContext().GetTextureCache().MarkGpuWritten(*image); } else { const bool exact = IsSupportedSampledVideoOutView(resource, descriptor, *video.image) && @@ -678,7 +691,6 @@ NativeTexture(uint64_t submit_id, CommandBuffer& command_buffer, if (!storage && metadata_read) { ValidateMetadataReuseTexture(resource, descriptor, size.size); } - (void)submit_id; (void)command_buffer; ImageInfo info {}; info.address = address; @@ -714,7 +726,9 @@ NativeTexture(uint64_t submit_id, CommandBuffer& command_buffer, image_view != nullptr)) { view = SelectSampledTextureArrayView(*image, view); } - return {image, view, image_view}; + return {image, view, image_view, + external ? std::shared_ptr {} + : GetRenderContext().GetTextureCache().GetImageOwner(*image)}; } static vk::Sampler NativeSampler(const ShaderRecompiler::IR::Program& program, uint32_t index, @@ -742,10 +756,9 @@ static BufferView NativeUpload(CommandBuffer& command_buffer, std::span user_data; - user_data.reserve(program.bindings.user_data_registers.size()); + prepared.user_data.reserve(program.bindings.user_data_registers.size()); for (const auto reg: program.bindings.user_data_registers) { - 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); + prepared.user_data.push_back(snapshot.user_data[reg - program.user_data_base]); } if (ShaderRecompiler::IR::FindBinding( program.bindings, ShaderRecompiler::IR::DescriptorBindingKind::Gds) != nullptr) { 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 bindings) { + std::unordered_set 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(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& 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::PipelineStageFlagBits::eHost | vk::PipelineStageFlagBits::eTransfer | vk::PipelineStageFlagBits::eAllGraphics | vk::PipelineStageFlagBits::eComputeShader, @@ -838,17 +976,19 @@ void BindDescriptors(uint64_t submit_id, CommandBuffer& buffer, } if (!program.bindings.descriptors.empty()) { - auto& set = - GetRenderContext().GetDescriptorCache().GetDescriptor(stage, program, descriptors); + auto& set = GetRenderContext().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 != user_data.size() * sizeof(uint32_t)); - vk_buffer.pushConstants(layout, vk_stage, program.bindings.push_constant_offset, - program.bindings.push_constant_size, user_data.data()); + 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 diff --git a/src/graphics/host_gpu/renderer/descriptors.h b/src/graphics/host_gpu/renderer/descriptors.h index 5f175e70f..830378ccc 100644 --- a/src/graphics/host_gpu/renderer/descriptors.h +++ b/src/graphics/host_gpu/renderer/descriptors.h @@ -13,6 +13,12 @@ namespace Libs::Graphics { struct VulkanImage; +enum class VulkanImageType; + +enum class ImageWriteActivation { Unsupported, Implicit, Explicit }; + +[[nodiscard]] ImageWriteActivation +ClassifyImageWriteActivation(VulkanImageType type) noexcept; template [[nodiscard]] T DecodeNativeDescriptor(const ShaderRecompiler::IR::DescriptorValue& value) { diff --git a/src/graphics/host_gpu/renderer/render.h b/src/graphics/host_gpu/renderer/render.h index f536bb513..a62802415 100644 --- a/src/graphics/host_gpu/renderer/render.h +++ b/src/graphics/host_gpu/renderer/render.h @@ -95,7 +95,6 @@ public: [[nodiscard]] vk::CommandBuffer Handle() const; [[nodiscard]] GraphicContext& GetGraphics() const noexcept { return m_graphics; } [[nodiscard]] bool IsExecute() const { return m_execute; } - [[nodiscard]] uint64_t GetRecordingGeneration() const { return m_recording_generation; } private: friend class BufferCache; @@ -113,7 +112,6 @@ private: bool m_execute = false; bool m_fence_waited = false; uint64_t m_submit_seq = 0; - uint64_t m_recording_generation = 0; uint32_t m_debug_op = 0; uint64_t m_debug_submit_id = 0; uint32_t m_debug_arg0 = 0; diff --git a/src/graphics/host_gpu/renderer/renderCompute.cpp b/src/graphics/host_gpu/renderer/renderCompute.cpp index e370ae4df..0c361f584 100644 --- a/src/graphics/host_gpu/renderer/renderCompute.cpp +++ b/src/graphics/host_gpu/renderer/renderCompute.cpp @@ -501,37 +501,33 @@ void RenderDispatchDirect(uint64_t submit_id, RenderCommandBuffer& buffer, uint3 return; } - for (;;) { - const auto recording_generation = buffer.GetRecordingGeneration(); - auto vk_buffer = buffer.Handle(); - auto& pipeline = GetRenderContext().GetPipelineCache().CreateComputePipeline( - input_info, sh_ctx.GetCs(), cs_shader); + auto& pipeline = GetRenderContext().GetPipelineCache().CreateComputePipeline( + input_info, sh_ctx.GetCs(), cs_shader); + auto bindings = PrepareBindings(buffer, input_info.stage, + vk::ShaderStageFlagBits::eCompute, + 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, - pipeline.pipeline_layout, input_info.stage, - vk::ShaderStageFlagBits::eCompute, DescriptorCache::Stage::Compute); - if (buffer.GetRecordingGeneration() != recording_generation) { - continue; - } - - vk_buffer.dispatch(thread_group_x, thread_group_y, thread_group_z); - - bool has_storage_writes = HasShaderBufferWrites(input_info.stage); - has_storage_writes = - std::any_of( - 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; + bool has_storage_writes = HasShaderBufferWrites(input_info.stage); + has_storage_writes = + std::any_of( + 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); } } diff --git a/src/graphics/host_gpu/renderer/renderDraw.cpp b/src/graphics/host_gpu/renderer/renderDraw.cpp index d4dc455d1..b57a344df 100644 --- a/src/graphics/host_gpu/renderer/renderDraw.cpp +++ b/src/graphics/host_gpu/renderer/renderDraw.cpp @@ -39,6 +39,8 @@ #include #include #include +#include +#include #include #include #include @@ -487,11 +489,11 @@ struct DrawRenderState { uint32_t color_count = 0; bool ps_active = true; VulkanFramebuffer* framebuffer = nullptr; - vk::CommandBuffer vk_buffer = nullptr; ShaderVertexInputInfo vs_input_info; ShaderPixelInputInfo ps_input_info; std::span vs_shader; std::span ps_shader; + std::vector> target_owners; }; struct DrawCallInfo { @@ -558,6 +560,16 @@ struct DrawIndexBufferSource { vk::IndexType type = vk::IndexType::eUint16; }; +struct PreparedIndexBuffer { + std::shared_ptr buffer; + const void* host_data = nullptr; + std::vector 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) { return (buffer.stride != 0 ? static_cast(buffer.stride) * 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); if (ResolveColorTargets(submit_id, buffer, render_target_slice_offset)) { - MarkRenderTargetGpuWritten(state.depth_info); 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) { 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], render_target_slice_offset, slot); 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++; } } @@ -669,8 +688,6 @@ static bool PrepareDrawRenderState(uint64_t submit_id, RenderCommandBuffer& buff EXIT_NOT_IMPLEMENTED(state.framebuffer == nullptr); EXIT_NOT_IMPLEMENTED(state.framebuffer->render_pass == nullptr); - state.vk_buffer = buffer.Handle(); - return true; } @@ -714,56 +731,138 @@ static void RefreshShaders(RenderCommandBuffer& buffer, const DrawCallInfo& draw } } -static void BindDrawVertexBuffers(uint64_t submit_id, RenderCommandBuffer& buffer, - const DrawCallInfo& draw, vk::CommandBuffer vk_buffer, - const ShaderVertexInputInfo& vs_input_info) { +static std::vector +PrepareVertexBuffers(uint64_t submit_id, RenderCommandBuffer& buffer, const DrawCallInfo& draw, + const ShaderVertexInputInfo& vs_input_info) { EXIT_IF(draw.name == nullptr); (void)submit_id; - LogDrawPhase(draw.name, "BindVertexBuffers"); + LogDrawPhase(draw.name, "PrepareVertexBuffers"); + std::vector bindings; + bindings.reserve(vs_input_info.buffers_num); for (int i = 0; i < vs_input_info.buffers_num; i++) { - const auto& b = vs_input_info.buffers[i]; - uint64_t addr = b.addr; - uint64_t size = VertexBufferDescriptorSize(b); - VulkanBuffer* vertices = nullptr; - vk::DeviceSize offset = 0; - + const auto& b = vs_input_info.buffers[i]; + const auto size = VertexBufferDescriptorSize(b); if (size == 0) { - vertices = &GetRenderContext().GetBufferCache().ObtainNullBuffer(buffer); + bindings.push_back({GetRenderContext().GetBufferCache().ObtainNullBuffer(), 0}); } else { - auto binding = GetRenderContext().GetBufferCache().ObtainBuffer(buffer, addr, size); - vertices = &binding.buffer; - offset = binding.offset; + bindings.push_back( + GetRenderContext().GetBufferCache().ObtainBuffer(buffer, b.addr, size)); } - 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& bindings) { + EXIT_IF(bindings.size() != static_cast(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, - const DrawIndexBufferSource& source) { +static PreparedIndexBuffer PrepareIndexBuffer(RenderCommandBuffer& buffer, + const DrawIndexBufferSource& source) { + PreparedIndexBuffer prepared; if (!source.enabled) { - return; + return prepared; } EXIT_IF(source.size == 0); - - VulkanBuffer* index_buffer = nullptr; - vk::DeviceSize index_offset = 0; + prepared.host_data = source.host_data; + prepared.address = source.address; + prepared.size = source.size; + prepared.type = source.type; 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> 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& 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; 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"); } } else { - auto binding = - GetRenderContext().GetBufferCache().ObtainBuffer(buffer, source.address, source.size); - index_buffer = &binding.buffer; - index_offset = binding.offset; + buffer.RetainResourceUntilFence(prepared.buffer); } 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, @@ -865,88 +964,108 @@ static void ExecutePreparedDraw(uint64_t submit_id, RenderCommandBuffer& buffer, bool set_bind_debug, bool set_auto_debug) { EXIT_IF(draw.name == nullptr); 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 (;;) { - const auto recording_generation = buffer.GetRecordingGeneration(); - 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); + LogDrawPhase(draw.name, "PrepareBindingsVS"); + auto vs_bindings = PrepareBindings(buffer, state.vs_input_info.stage, + vk::ShaderStageFlagBits::eVertex, + DescriptorCache::Stage::Vertex); + std::optional ps_bindings; + if (state.ps_active) { + LogDrawPhase(draw.name, "PrepareBindingsPS"); + ps_bindings.emplace(PrepareBindings(buffer, state.ps_input_info.stage, + 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) { - SetDrawDebugPhase(buffer, submit_id, draw, 0x100u); - } - state.vk_buffer.bindPipeline(vk::PipelineBindPoint::eGraphics, pipeline.pipeline); - const auto dynamic_params = BuildGraphicsDynamicParams(buffer, state.color_info, - state.color_count, state.depth_info); - SetDynamicParams(buffer, state.vk_buffer, dynamic_params); + // The discovery pass above can merge cache allocations. Rebind every buffer only after the + // complete draw resource set is known. + RebindBuffers(buffer, vs_bindings); + if (ps_bindings.has_value()) { + RebindBuffers(buffer, *ps_bindings); + } + 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); - BindDrawVertexBuffers(submit_id, buffer, draw, state.vk_buffer, state.vs_input_info); + // Resource preparation above may synchronously finish and restart the scheduler. From this + // 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) { - SetDrawDebugPhase(buffer, submit_id, draw, 0x200u); + SetDrawDebugPhase(buffer, submit_id, draw, 0x300u); } - BindDescriptors(submit_id, buffer, vk::PipelineBindPoint::eGraphics, - pipeline.pipeline_layout, state.vs_input_info.stage, - vk::ShaderStageFlagBits::eVertex, DescriptorCache::Stage::Vertex); + CommitBindings(buffer, vk::PipelineBindPoint::eGraphics, pipeline.pipeline_layout, + *ps_bindings); + } + CommitIndexBuffer(buffer, vk_buffer, index_binding); - if (state.ps_active) { - LogDrawPhase(draw.name, "BindDescriptorsPS"); - if (set_auto_debug) { - 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; - } + const auto dynamic_params = BuildGraphicsDynamicParams(buffer, state.color_info, + state.color_count, state.depth_info); + SetDynamicParams(buffer, vk_buffer, dynamic_params); - LogDrawPhase(draw.name, "BeginRenderPass"); - if (set_auto_debug) { - SetDrawDebugPhase(buffer, submit_id, draw, 0x400u); - } - buffer.BeginRenderPass(*state.framebuffer, state.color_info, state.color_count, - state.depth_info); - if (set_auto_debug) { - SetDrawDebugPhase(buffer, submit_id, draw, 0x500u); - } + LogDrawPhase(draw.name, "BeginRenderPass"); + if (set_auto_debug) { + SetDrawDebugPhase(buffer, submit_id, draw, 0x400u); + } + buffer.BeginRenderPass(*state.framebuffer, state.color_info, state.color_count, + state.depth_info); + vk_buffer.bindPipeline(vk::PipelineBindPoint::eGraphics, pipeline.pipeline); + 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); - } - buffer.EndRenderPass(); - vk::PipelineStageFlags shader_write_stages = {}; - if (HasShaderBufferWrites(state.vs_input_info.stage)) { - shader_write_stages |= vk::PipelineStageFlagBits::eVertexShader; - } - if (state.ps_active) { - if (HasShaderBufferWrites(state.ps_input_info.stage)) { - shader_write_stages |= vk::PipelineStageFlagBits::eFragmentShader; - } - } - if (shader_write_stages) { - ShaderWriteBarrier(state.vk_buffer, shader_write_stages); - } - LogDrawPhase(draw.name, "EndRenderPass"); - if (set_auto_debug) { - SetDrawDebugPhase(buffer, submit_id, draw, 0x700u); - } - break; + if (set_auto_debug) { + SetDrawDebugPhase(buffer, submit_id, draw, 0x600u); + } + buffer.EndRenderPass(); + vk::PipelineStageFlags shader_write_stages = {}; + 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 (shader_write_stages) { + ShaderWriteBarrier(vk_buffer, shader_write_stages); + } + LogDrawPhase(draw.name, "EndRenderPass"); + if (set_auto_debug) { + SetDrawDebugPhase(buffer, submit_id, draw, 0x700u); } } diff --git a/src/graphics/host_gpu/renderer/textureCache.cpp b/src/graphics/host_gpu/renderer/textureCache.cpp index f78f4a0b5..010acaef3 100644 --- a/src/graphics/host_gpu/renderer/textureCache.cpp +++ b/src/graphics/host_gpu/renderer/textureCache.cpp @@ -340,6 +340,17 @@ VulkanImage& TextureCache::PublishImage(CommandBuffer& command, return result; } +std::shared_ptr 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 == ℑ }); + if (owner == m_images.end()) { + EXIT("TextureCache: image has no cache owner, image=%p\n", + static_cast(&image)); + } + return *owner; +} + std::vector TextureCache::FindImagesInRegionLocked(uint64_t vaddr, uint64_t size, bool page_overlap) { 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; } 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 // 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. diff --git a/src/graphics/host_gpu/renderer/textureCache.h b/src/graphics/host_gpu/renderer/textureCache.h index d79bacfcf..53ce44140 100644 --- a/src/graphics/host_gpu/renderer/textureCache.h +++ b/src/graphics/host_gpu/renderer/textureCache.h @@ -118,6 +118,7 @@ public: VulkanImage& GetDummySampledTexture(bool uint_format, bool image_3d); VulkanImage& GetDummyStorageTexture(bool uint_format, bool image_3d); + [[nodiscard]] std::shared_ptr GetImageOwner(const VulkanImage& image); private: struct CachedImage; diff --git a/tests/ShaderRecompilerComputeTests.cpp b/tests/ShaderRecompilerComputeTests.cpp index 81fb12301..e36190ed6 100644 --- a/tests/ShaderRecompilerComputeTests.cpp +++ b/tests/ShaderRecompilerComputeTests.cpp @@ -10557,6 +10557,56 @@ void CheckBufferCacheRangeMerge() { std::printf("[host] %-32s ok\n", "BufferCacheRangeMerge"); } +void CheckBufferBindingStabilization() { + constexpr std::array 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 resource{}; 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) { CheckBufferCacheRangeMerge(); + CheckBufferBindingStabilization(); + CheckImageWriteActivationPolicy(); return 0; } if (argc == 2 && std::strcmp(argv[1], "--storage-bgra-only") == 0) { @@ -14296,6 +14348,8 @@ int main(int argc, char **argv) { CheckSampledDepthResource(); CheckSampledDepthDescriptor(); CheckBufferCacheRangeMerge(); + CheckBufferBindingStabilization(); + CheckImageWriteActivationPolicy(); CheckBasicStorageTextureDescriptor(); CheckStorageTextureLinearUploadLayout(); CheckStorageTextureDepthTileUploadLayout();