[Vulkan] Implement readback_resolve for vulkan
This commit is contained in:
committed by
Radosław Gliński
parent
f4b471d000
commit
4644657e83
@@ -50,14 +50,14 @@ DEFINE_bool(clear_memory_page_state, false,
|
||||
|
||||
DEFINE_bool(
|
||||
readback_resolve, false,
|
||||
"[D3D12 Only] Read render-to-texture results on the CPU. This may be "
|
||||
"Read render-to-texture results on the CPU. This may be "
|
||||
"needed in some games, for instance, for screenshots in saved games, but "
|
||||
"causes mid-frame synchronization, so it has a huge performance impact.",
|
||||
"GPU");
|
||||
|
||||
DEFINE_bool(
|
||||
readback_memexport, false,
|
||||
"[D3D12 Only] Read data written by memory export in shaders on the CPU. "
|
||||
"Read data written by memory export in shaders on the CPU. "
|
||||
"This may be needed in some games (but many only access exported data on "
|
||||
"the GPU, and this flag isn't needed to handle such behavior), but causes "
|
||||
"mid-frame synchronization, so it has a huge performance impact.",
|
||||
|
||||
@@ -160,6 +160,8 @@ class CommandProcessor {
|
||||
size_t length = 0;
|
||||
};
|
||||
|
||||
static constexpr uint32_t kReadbackBufferSizeIncrement = 16 * 1024 * 1024;
|
||||
|
||||
void WorkerThreadMain();
|
||||
virtual bool SetupContext() = 0;
|
||||
virtual void ShutdownContext() = 0;
|
||||
|
||||
@@ -692,7 +692,6 @@ class D3D12CommandProcessor final : public CommandProcessor {
|
||||
D3D12_RESOURCE_STATES scratch_buffer_state_;
|
||||
bool scratch_buffer_used_ = false;
|
||||
|
||||
static constexpr uint32_t kReadbackBufferSizeIncrement = 16 * 1024 * 1024;
|
||||
ID3D12Resource* readback_buffer_ = nullptr;
|
||||
uint32_t readback_buffer_size_ = 0;
|
||||
|
||||
|
||||
@@ -1087,6 +1087,13 @@ void VulkanCommandProcessor::ShutdownContext() {
|
||||
ui::vulkan::util::DestroyAndNullHandle(dfn.vkFreeMemory, device,
|
||||
gamma_ramp_buffer_memory_);
|
||||
|
||||
// Clean up readback buffer.
|
||||
ui::vulkan::util::DestroyAndNullHandle(dfn.vkDestroyBuffer, device,
|
||||
readback_buffer_);
|
||||
ui::vulkan::util::DestroyAndNullHandle(dfn.vkFreeMemory, device,
|
||||
readback_buffer_memory_);
|
||||
readback_buffer_size_ = 0;
|
||||
|
||||
ui::vulkan::util::DestroyAndNullHandle(
|
||||
dfn.vkDestroyDescriptorPool, device,
|
||||
shared_memory_and_edram_descriptor_pool_);
|
||||
@@ -2642,6 +2649,80 @@ bool VulkanCommandProcessor::IssueDraw(xenos::PrimitiveType prim_type,
|
||||
memexport_range.size_bytes, false);
|
||||
}
|
||||
|
||||
// CPU readback for memexport data (if enabled).
|
||||
if (GetGPUSetting(GPUSetting::ReadbackMemexport) &&
|
||||
!memexport_ranges_.empty()) {
|
||||
// Calculate total size of all memexport ranges.
|
||||
uint32_t memexport_total_size = 0;
|
||||
for (const draw_util::MemExportRange& memexport_range : memexport_ranges_) {
|
||||
memexport_total_size += memexport_range.size_bytes;
|
||||
}
|
||||
|
||||
if (memexport_total_size > 0) {
|
||||
VkBuffer readback_buffer = RequestReadbackBuffer(memexport_total_size);
|
||||
if (readback_buffer != VK_NULL_HANDLE) {
|
||||
const ui::vulkan::VulkanDevice* const vulkan_device = GetVulkanDevice();
|
||||
const ui::vulkan::VulkanDevice::Functions& dfn =
|
||||
vulkan_device->functions();
|
||||
const VkDevice device = vulkan_device->device();
|
||||
|
||||
VkBuffer shared_memory_buffer = shared_memory_->buffer();
|
||||
|
||||
// Ensure shared memory is ready for transfer.
|
||||
shared_memory_->Use(VulkanSharedMemory::Usage::kRead);
|
||||
|
||||
// Copy each memexport range to the readback buffer.
|
||||
uint32_t readback_buffer_offset = 0;
|
||||
for (const draw_util::MemExportRange& memexport_range :
|
||||
memexport_ranges_) {
|
||||
VkBufferCopy copy_region = {};
|
||||
copy_region.srcOffset = memexport_range.base_address_dwords << 2;
|
||||
copy_region.dstOffset = readback_buffer_offset;
|
||||
copy_region.size = memexport_range.size_bytes;
|
||||
|
||||
deferred_command_buffer_.CmdVkCopyBuffer(
|
||||
shared_memory_buffer, readback_buffer, 1, ©_region);
|
||||
|
||||
readback_buffer_offset += memexport_range.size_bytes;
|
||||
}
|
||||
|
||||
// Wait for GPU to finish (SYNCHRONIZATION STALL)
|
||||
if (AwaitAllQueueOperationsCompletion()) {
|
||||
// Map staging buffer and copy to guest memory.
|
||||
void* mapped_data;
|
||||
if (dfn.vkMapMemory(device, readback_buffer_memory_, 0,
|
||||
memexport_total_size, 0,
|
||||
&mapped_data) == VK_SUCCESS) {
|
||||
if (mapped_data) {
|
||||
const uint8_t* readback_bytes =
|
||||
static_cast<const uint8_t*>(mapped_data);
|
||||
for (const draw_util::MemExportRange& memexport_range :
|
||||
memexport_ranges_) {
|
||||
std::memcpy(memory_->TranslatePhysical(
|
||||
memexport_range.base_address_dwords << 2),
|
||||
readback_bytes, memexport_range.size_bytes);
|
||||
readback_bytes += memexport_range.size_bytes;
|
||||
}
|
||||
} else {
|
||||
XELOGE(
|
||||
"VulkanCommandProcessor: Failed to map readback buffer "
|
||||
"(mapped_data is null)");
|
||||
}
|
||||
dfn.vkUnmapMemory(device, readback_buffer_memory_);
|
||||
} else {
|
||||
XELOGE(
|
||||
"VulkanCommandProcessor: Failed to map readback buffer memory "
|
||||
"for memexport");
|
||||
}
|
||||
} else {
|
||||
XELOGE(
|
||||
"VulkanCommandProcessor: Failed to complete queue operations for "
|
||||
"memexport readback");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -2660,11 +2741,146 @@ bool VulkanCommandProcessor::IssueCopy() {
|
||||
return false;
|
||||
}
|
||||
|
||||
// TODO(Triang3l): CPU readback.
|
||||
// CPU readback resolve path (if enabled).
|
||||
if (GetGPUSetting(GPUSetting::ReadbackResolve) &&
|
||||
!texture_cache_->IsDrawResolutionScaled() && written_length > 0) {
|
||||
VkBuffer readback_buffer = RequestReadbackBuffer(written_length);
|
||||
if (readback_buffer != VK_NULL_HANDLE) {
|
||||
const ui::vulkan::VulkanDevice* const vulkan_device = GetVulkanDevice();
|
||||
const ui::vulkan::VulkanDevice::Functions& dfn =
|
||||
vulkan_device->functions();
|
||||
const VkDevice device = vulkan_device->device();
|
||||
|
||||
VkBuffer shared_memory_buffer = shared_memory_->buffer();
|
||||
|
||||
// Ensure shared memory is ready for transfer.
|
||||
shared_memory_->Use(VulkanSharedMemory::Usage::kRead);
|
||||
|
||||
// Copy GPU buffer → staging buffer.
|
||||
VkBufferCopy copy_region = {};
|
||||
copy_region.srcOffset = written_address;
|
||||
copy_region.dstOffset = 0;
|
||||
copy_region.size = written_length;
|
||||
|
||||
deferred_command_buffer_.CmdVkCopyBuffer(
|
||||
shared_memory_buffer, readback_buffer, 1, ©_region);
|
||||
|
||||
// Wait for GPU to finish (SYNCHRONIZATION STALL - major performance
|
||||
// hit!).
|
||||
if (AwaitAllQueueOperationsCompletion()) {
|
||||
// Map staging buffer and copy to guest memory.
|
||||
void* mapped_data;
|
||||
if (dfn.vkMapMemory(device, readback_buffer_memory_, 0, written_length,
|
||||
0, &mapped_data) == VK_SUCCESS) {
|
||||
if (mapped_data) {
|
||||
memory::vastcpy(memory_->TranslatePhysical(written_address),
|
||||
static_cast<uint8_t*>(mapped_data), written_length);
|
||||
} else {
|
||||
XELOGE(
|
||||
"VulkanCommandProcessor: Failed to map readback buffer "
|
||||
"(mapped_data is null)");
|
||||
}
|
||||
dfn.vkUnmapMemory(device, readback_buffer_memory_);
|
||||
} else {
|
||||
XELOGE(
|
||||
"VulkanCommandProcessor: Failed to map readback buffer memory "
|
||||
"for "
|
||||
"resolve");
|
||||
}
|
||||
} else {
|
||||
XELOGE(
|
||||
"VulkanCommandProcessor: Failed to complete queue operations for "
|
||||
"resolve readback");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
VkBuffer VulkanCommandProcessor::RequestReadbackBuffer(uint32_t size) {
|
||||
if (size == 0) {
|
||||
return VK_NULL_HANDLE;
|
||||
}
|
||||
|
||||
size = xe::align(size, kReadbackBufferSizeIncrement);
|
||||
|
||||
if (size > readback_buffer_size_) {
|
||||
const ui::vulkan::VulkanDevice* const vulkan_device = GetVulkanDevice();
|
||||
const ui::vulkan::VulkanDevice::Functions& dfn = vulkan_device->functions();
|
||||
const VkDevice device = vulkan_device->device();
|
||||
|
||||
// Create buffer with TRANSFER_DST usage for copying from GPU.
|
||||
VkBufferCreateInfo buffer_info = {};
|
||||
buffer_info.sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO;
|
||||
buffer_info.size = size;
|
||||
buffer_info.usage = VK_BUFFER_USAGE_TRANSFER_DST_BIT;
|
||||
buffer_info.sharingMode = VK_SHARING_MODE_EXCLUSIVE;
|
||||
|
||||
VkBuffer new_buffer;
|
||||
if (dfn.vkCreateBuffer(device, &buffer_info, nullptr, &new_buffer) !=
|
||||
VK_SUCCESS) {
|
||||
XELOGE(
|
||||
"VulkanCommandProcessor: Failed to create readback buffer of {} MB",
|
||||
size >> 20);
|
||||
return VK_NULL_HANDLE;
|
||||
}
|
||||
|
||||
// Get memory requirements.
|
||||
VkMemoryRequirements memory_requirements;
|
||||
dfn.vkGetBufferMemoryRequirements(device, new_buffer, &memory_requirements);
|
||||
|
||||
// Allocate HOST_VISIBLE | HOST_CACHED | HOST_COHERENT memory for readback.
|
||||
const uint32_t memory_type_index = ui::vulkan::util::ChooseMemoryType(
|
||||
vulkan_device->memory_types(), memory_requirements.memoryTypeBits,
|
||||
ui::vulkan::util::MemoryPurpose::kReadback);
|
||||
if (memory_type_index == UINT32_MAX) {
|
||||
XELOGE(
|
||||
"VulkanCommandProcessor: Failed to find suitable memory type for "
|
||||
"readback buffer");
|
||||
dfn.vkDestroyBuffer(device, new_buffer, nullptr);
|
||||
return VK_NULL_HANDLE;
|
||||
}
|
||||
|
||||
VkMemoryAllocateInfo alloc_info = {};
|
||||
alloc_info.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO;
|
||||
alloc_info.allocationSize = memory_requirements.size;
|
||||
alloc_info.memoryTypeIndex = memory_type_index;
|
||||
|
||||
VkDeviceMemory new_memory;
|
||||
if (dfn.vkAllocateMemory(device, &alloc_info, nullptr, &new_memory) !=
|
||||
VK_SUCCESS) {
|
||||
XELOGE(
|
||||
"VulkanCommandProcessor: Failed to allocate memory for readback "
|
||||
"buffer");
|
||||
dfn.vkDestroyBuffer(device, new_buffer, nullptr);
|
||||
return VK_NULL_HANDLE;
|
||||
}
|
||||
|
||||
// Bind memory to buffer.
|
||||
if (dfn.vkBindBufferMemory(device, new_buffer, new_memory, 0) !=
|
||||
VK_SUCCESS) {
|
||||
XELOGE(
|
||||
"VulkanCommandProcessor: Failed to bind memory to readback buffer");
|
||||
dfn.vkFreeMemory(device, new_memory, nullptr);
|
||||
dfn.vkDestroyBuffer(device, new_buffer, nullptr);
|
||||
return VK_NULL_HANDLE;
|
||||
}
|
||||
|
||||
// Destroy old buffer if it exists.
|
||||
if (readback_buffer_ != VK_NULL_HANDLE) {
|
||||
dfn.vkDestroyBuffer(device, readback_buffer_, nullptr);
|
||||
dfn.vkFreeMemory(device, readback_buffer_memory_, nullptr);
|
||||
}
|
||||
|
||||
readback_buffer_ = new_buffer;
|
||||
readback_buffer_memory_ = new_memory;
|
||||
readback_buffer_size_ = size;
|
||||
}
|
||||
|
||||
return readback_buffer_;
|
||||
}
|
||||
|
||||
void VulkanCommandProcessor::InitializeTrace() {
|
||||
CommandProcessor::InitializeTrace();
|
||||
|
||||
|
||||
@@ -436,6 +436,9 @@ class VulkanCommandProcessor final : public CommandProcessor {
|
||||
return !submission_open_ && submissions_in_flight_fences_.empty();
|
||||
}
|
||||
|
||||
// Requests a readback buffer for CPU access to GPU data.
|
||||
VkBuffer RequestReadbackBuffer(uint32_t size);
|
||||
|
||||
void ClearTransientDescriptorPools();
|
||||
|
||||
void SplitPendingBarrier();
|
||||
@@ -750,6 +753,11 @@ class VulkanCommandProcessor final : public CommandProcessor {
|
||||
|
||||
// Temporary storage for memexport stream constants used in the draw.
|
||||
std::vector<draw_util::MemExportRange> memexport_ranges_;
|
||||
|
||||
// Readback buffer for CPU access to resolved data
|
||||
VkBuffer readback_buffer_ = VK_NULL_HANDLE;
|
||||
VkDeviceMemory readback_buffer_memory_ = VK_NULL_HANDLE;
|
||||
uint32_t readback_buffer_size_ = 0;
|
||||
};
|
||||
|
||||
} // namespace vulkan
|
||||
|
||||
Reference in New Issue
Block a user