mirror of
https://github.com/KytyPS5/KytyPS5.git
synced 2026-08-03 11:23:49 +00:00
renderer: replace generation retries with staged resource binding
This commit is contained in:
@@ -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<uint8_t, CACHING_PAGE_SIZE> guest_data;
|
||||
std::vector<uint8_t> 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<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};
|
||||
}
|
||||
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<VulkanBuffer> 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,
|
||||
|
||||
@@ -10,6 +10,7 @@
|
||||
#include <map>
|
||||
#include <memory>
|
||||
#include <mutex>
|
||||
#include <vector>
|
||||
|
||||
namespace Libs::Graphics {
|
||||
|
||||
@@ -36,8 +37,9 @@ struct BufferCacheRange {
|
||||
};
|
||||
|
||||
struct BufferBinding {
|
||||
VulkanBuffer& buffer;
|
||||
uint64_t offset;
|
||||
std::shared_ptr<VulkanBuffer> buffer;
|
||||
uint64_t offset = 0;
|
||||
std::vector<uint8_t> 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<VulkanBuffer> 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);
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -10,6 +10,8 @@
|
||||
#include "graphics/shader/shaderBindings.h"
|
||||
|
||||
#include <map>
|
||||
#include <memory>
|
||||
#include <span>
|
||||
#include <unordered_map>
|
||||
#include <vector>
|
||||
|
||||
@@ -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<VulkanBuffer> owner;
|
||||
VulkanBuffer* buffer = nullptr;
|
||||
vk::DeviceSize offset = 0;
|
||||
vk::DeviceSize range = VK_WHOLE_SIZE;
|
||||
std::vector<uint8_t> 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<void> owner;
|
||||
};
|
||||
|
||||
enum class TextureVariant : int {
|
||||
@@ -63,6 +69,17 @@ public:
|
||||
BufferView user_data;
|
||||
};
|
||||
|
||||
struct PreparedBindings {
|
||||
std::shared_ptr<const ShaderRecompiler::IR::Program> program;
|
||||
std::shared_ptr<const ShaderRecompiler::IR::ResourceSnapshot> snapshot;
|
||||
NativeDescriptors resources;
|
||||
std::vector<uint32_t> flattened_srt;
|
||||
std::vector<uint32_t> user_data;
|
||||
vk::ShaderStageFlags shader_stage;
|
||||
Stage stage = Stage::Unknown;
|
||||
bool committed = false;
|
||||
};
|
||||
|
||||
explicit DescriptorCache(GraphicContext& graphics): m_graphics(graphics) {
|
||||
EXIT_NOT_IMPLEMENTED(!Common::Thread::IsMainThread());
|
||||
}
|
||||
@@ -95,10 +112,14 @@ private:
|
||||
std::map<std::vector<uint32_t>, 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<DescriptorCache::PreparedBindings*> bindings);
|
||||
void CommitBindings(CommandBuffer& buffer, vk::PipelineBindPoint pipeline_bind_point,
|
||||
vk::PipelineLayout layout, DescriptorCache::PreparedBindings& bindings);
|
||||
|
||||
} // namespace Libs::Graphics
|
||||
|
||||
|
||||
@@ -37,6 +37,7 @@
|
||||
#include <fmt/format.h>
|
||||
#include <limits>
|
||||
#include <span>
|
||||
#include <unordered_set>
|
||||
|
||||
#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<uint64_t>(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<vk::DeviceSize>(size);
|
||||
result.owner = std::move(binding.buffer);
|
||||
result.buffer = result.owner.get();
|
||||
result.offset = binding.offset;
|
||||
result.range = static_cast<vk::DeviceSize>(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<vk::DeviceSize>(size);
|
||||
result.owner = std::move(binding.buffer);
|
||||
result.buffer = result.owner.get();
|
||||
result.offset = binding.offset;
|
||||
result.range = static_cast<vk::DeviceSize>(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<Prospero::TileMode>(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<void> {}
|
||||
: 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<const ui
|
||||
return result;
|
||||
}
|
||||
|
||||
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) {
|
||||
DescriptorCache::PreparedBindings
|
||||
PrepareBindings(CommandBuffer& buffer, const ShaderStageRuntime& runtime,
|
||||
vk::ShaderStageFlags shader_stage, DescriptorCache::Stage stage) {
|
||||
KYTY_PROFILER_FUNCTION();
|
||||
EXIT_IF(!runtime);
|
||||
const auto& program = *runtime.program;
|
||||
@@ -754,27 +767,30 @@ void BindDescriptors(uint64_t submit_id, CommandBuffer& buffer,
|
||||
if (!ShaderRecompiler::IR::ValidateResourceSpecialization(program, snapshot, &error)) {
|
||||
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());
|
||||
for (uint32_t i = 0; i < program.info.buffers.size(); i++) {
|
||||
ShaderBufferResource descriptor;
|
||||
CopyNativeDescriptor(snapshot.buffers[i], descriptor.fields);
|
||||
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());
|
||||
for (uint32_t i = 0; i < program.info.images.size(); i++) {
|
||||
const auto kind = program.info.images[i].kind;
|
||||
if ((kind == ShaderRecompiler::IR::ResourceKind::StorageImage ||
|
||||
kind == ShaderRecompiler::IR::ResourceKind::StorageImageUint) &&
|
||||
vk_stage != vk::ShaderStageFlagBits::eCompute) {
|
||||
shader_stage != vk::ShaderStageFlagBits::eCompute) {
|
||||
EXIT("storage images are unsupported outside compute shaders\n");
|
||||
}
|
||||
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());
|
||||
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());
|
||||
for (uint32_t i = 0; i < program.info.addresses.size(); i++) {
|
||||
descriptors.addresses.push_back(NativeAddressBuffer(
|
||||
submit_id, buffer, program.info.addresses[i], snapshot.addresses[i]));
|
||||
descriptors.addresses.push_back(
|
||||
NativeAddressBuffer(buffer, program.info.addresses[i], snapshot.addresses[i]));
|
||||
}
|
||||
if (ShaderRecompiler::IR::FindBinding(
|
||||
program.bindings, ShaderRecompiler::IR::DescriptorBindingKind::FlattenedSrt) !=
|
||||
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;
|
||||
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<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::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
|
||||
|
||||
@@ -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 <typename T>
|
||||
[[nodiscard]] T DecodeNativeDescriptor(const ShaderRecompiler::IR::DescriptorValue& value) {
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -39,6 +39,8 @@
|
||||
#include <cmath>
|
||||
#include <cstring>
|
||||
#include <limits>
|
||||
#include <memory>
|
||||
#include <optional>
|
||||
#include <span>
|
||||
#include <unordered_map>
|
||||
#include <vector>
|
||||
@@ -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<const uint32_t> vs_shader;
|
||||
std::span<const uint32_t> ps_shader;
|
||||
std::vector<std::shared_ptr<void>> target_owners;
|
||||
};
|
||||
|
||||
struct DrawCallInfo {
|
||||
@@ -558,6 +560,16 @@ struct DrawIndexBufferSource {
|
||||
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) {
|
||||
return (buffer.stride != 0 ? static_cast<uint64_t>(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<BufferBinding>
|
||||
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<BufferBinding> 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<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,
|
||||
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<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;
|
||||
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<DescriptorCache::PreparedBindings> 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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -340,6 +340,17 @@ VulkanImage& TextureCache::PublishImage(CommandBuffer& command,
|
||||
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 == ℑ });
|
||||
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*>
|
||||
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.
|
||||
|
||||
@@ -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<void> GetImageOwner(const VulkanImage& image);
|
||||
|
||||
private:
|
||||
struct CachedImage;
|
||||
|
||||
@@ -10557,6 +10557,56 @@ void CheckBufferCacheRangeMerge() {
|
||||
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 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();
|
||||
|
||||
Reference in New Issue
Block a user