summaryrefslogtreecommitdiff
path: root/Source/Core/VideoBackends/Vulkan/VKTexture.cpp
diff options
context:
space:
mode:
authorStenzek <stenzek@gmail.com>2017-10-22 00:49:40 +1000
committerStenzek <stenzek@gmail.com>2017-11-22 18:47:04 +1000
commitf43d85921dc85ae586d69b21d28a7578835a55bd (patch)
treeda734d7c52a1cbaeae276c601b99dd426fcf3d43 /Source/Core/VideoBackends/Vulkan/VKTexture.cpp
parenta584ccc7d882213d93586cae9063d9865311aaad (diff)
VideoBackends: Add AbstractStagingTexture class
Can be used for asynchronous readback or upload of textures.
Diffstat (limited to 'Source/Core/VideoBackends/Vulkan/VKTexture.cpp')
-rw-r--r--Source/Core/VideoBackends/Vulkan/VKTexture.cpp222
1 files changed, 221 insertions, 1 deletions
diff --git a/Source/Core/VideoBackends/Vulkan/VKTexture.cpp b/Source/Core/VideoBackends/Vulkan/VKTexture.cpp
index cbb6e0c063..cfea34ff8e 100644
--- a/Source/Core/VideoBackends/Vulkan/VKTexture.cpp
+++ b/Source/Core/VideoBackends/Vulkan/VKTexture.cpp
@@ -291,7 +291,7 @@ void VKTexture::Load(u32 level, u32 width, u32 height, u32 row_length, const u8*
u32 upload_alignment = static_cast<u32>(g_vulkan_context->GetBufferImageGranularity());
u32 block_size = Util::GetBlockSize(m_texture->GetFormat());
u32 num_rows = Common::AlignUp(height, block_size) / block_size;
- size_t source_pitch = CalculateHostTextureLevelPitch(m_config.format, row_length);
+ size_t source_pitch = CalculateStrideForFormat(m_config.format, row_length);
size_t upload_size = source_pitch * num_rows;
std::unique_ptr<StagingBuffer> temp_buffer;
VkBuffer upload_buffer;
@@ -356,4 +356,224 @@ void VKTexture::Load(u32 level, u32 width, u32 height, u32 row_length, const u8*
}
}
+VKStagingTexture::VKStagingTexture(StagingTextureType type, const TextureConfig& config,
+ std::unique_ptr<StagingBuffer> buffer)
+ : AbstractStagingTexture(type, config), m_staging_buffer(std::move(buffer))
+{
+}
+
+VKStagingTexture::~VKStagingTexture()
+{
+ if (m_needs_flush)
+ VKStagingTexture::Flush();
+}
+
+std::unique_ptr<VKStagingTexture> VKStagingTexture::Create(StagingTextureType type,
+ const TextureConfig& config)
+{
+ size_t stride = config.GetStride();
+ size_t buffer_size = stride * static_cast<size_t>(config.height);
+
+ STAGING_BUFFER_TYPE buffer_type;
+ VkImageUsageFlags buffer_usage;
+ if (type == StagingTextureType::Readback)
+ {
+ buffer_type = STAGING_BUFFER_TYPE_READBACK;
+ buffer_usage = VK_IMAGE_USAGE_TRANSFER_DST_BIT;
+ }
+ else if (type == StagingTextureType::Upload)
+ {
+ buffer_type = STAGING_BUFFER_TYPE_UPLOAD;
+ buffer_usage = VK_IMAGE_USAGE_TRANSFER_SRC_BIT;
+ }
+ else
+ {
+ buffer_type = STAGING_BUFFER_TYPE_READBACK;
+ buffer_usage = VK_IMAGE_USAGE_TRANSFER_SRC_BIT | VK_IMAGE_USAGE_TRANSFER_DST_BIT;
+ }
+
+ VkBuffer buffer;
+ VkDeviceMemory memory;
+ bool coherent;
+ if (!StagingBuffer::AllocateBuffer(buffer_type, buffer_size, buffer_usage, &buffer, &memory,
+ &coherent))
+ {
+ return nullptr;
+ }
+
+ std::unique_ptr<StagingBuffer> staging_buffer =
+ std::make_unique<StagingBuffer>(buffer_type, buffer, memory, buffer_size, coherent);
+ std::unique_ptr<VKStagingTexture> staging_tex = std::unique_ptr<VKStagingTexture>(
+ new VKStagingTexture(type, config, std::move(staging_buffer)));
+
+ // Use persistent mapping.
+ if (!staging_tex->m_staging_buffer->Map())
+ return nullptr;
+ staging_tex->m_map_pointer = staging_tex->m_staging_buffer->GetMapPointer();
+ staging_tex->m_map_stride = stride;
+ return staging_tex;
+}
+
+void VKStagingTexture::CopyFromTexture(const AbstractTexture* src,
+ const MathUtil::Rectangle<int>& src_rect, u32 src_layer,
+ u32 src_level, const MathUtil::Rectangle<int>& dst_rect)
+{
+ _assert_(m_type == StagingTextureType::Readback);
+ _assert_(src_rect.GetWidth() == dst_rect.GetWidth() &&
+ src_rect.GetHeight() == dst_rect.GetHeight());
+ _assert_(src_rect.left >= 0 && static_cast<u32>(src_rect.right) <= src->GetConfig().width &&
+ src_rect.top >= 0 && static_cast<u32>(src_rect.bottom) <= src->GetConfig().height);
+ _assert_(dst_rect.left >= 0 && static_cast<u32>(dst_rect.right) <= m_config.width &&
+ dst_rect.top >= 0 && static_cast<u32>(dst_rect.bottom) <= m_config.height);
+
+ Texture2D* src_tex = static_cast<const VKTexture*>(src)->GetRawTexIdentifier();
+ CopyFromTexture(src_tex, src_rect, src_layer, src_level, dst_rect);
+}
+
+void VKStagingTexture::CopyFromTexture(Texture2D* src, const MathUtil::Rectangle<int>& src_rect,
+ u32 src_layer, u32 src_level,
+ const MathUtil::Rectangle<int>& dst_rect)
+{
+ if (m_needs_flush)
+ {
+ // Drop copy before reusing it.
+ g_command_buffer_mgr->RemoveFencePointCallback(this);
+ m_flush_fence = VK_NULL_HANDLE;
+ m_needs_flush = false;
+ }
+
+ VkImageLayout old_layout = src->GetLayout();
+ src->TransitionToLayout(g_command_buffer_mgr->GetCurrentCommandBuffer(),
+ VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL);
+
+ // Issue the image->buffer copy, but delay it for now.
+ VkBufferImageCopy image_copy = {};
+ VkImageAspectFlags aspect =
+ Util::IsDepthFormat(src->GetFormat()) ? VK_IMAGE_ASPECT_DEPTH_BIT : VK_IMAGE_ASPECT_COLOR_BIT;
+ image_copy.bufferOffset =
+ static_cast<VkDeviceSize>(static_cast<size_t>(dst_rect.top) * m_config.GetStride() +
+ static_cast<size_t>(dst_rect.left) * m_texel_size);
+ image_copy.bufferRowLength = static_cast<u32>(m_config.width);
+ image_copy.bufferImageHeight = 0;
+ image_copy.imageSubresource = {aspect, src_level, src_layer, 1};
+ image_copy.imageOffset = {src_rect.left, src_rect.top, 0};
+ image_copy.imageExtent = {static_cast<u32>(src_rect.GetWidth()),
+ static_cast<u32>(src_rect.GetHeight()), 1u};
+ vkCmdCopyImageToBuffer(g_command_buffer_mgr->GetCurrentCommandBuffer(), src->GetImage(),
+ VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, m_staging_buffer->GetBuffer(), 1,
+ &image_copy);
+
+ // Restore old source texture layout.
+ src->TransitionToLayout(g_command_buffer_mgr->GetCurrentCommandBuffer(), old_layout);
+
+ m_needs_flush = true;
+ g_command_buffer_mgr->AddFencePointCallback(this,
+ [this](VkCommandBuffer buf, VkFence fence) {
+ _assert_(m_needs_flush);
+ m_flush_fence = fence;
+ },
+ [this](VkFence fence) {
+ m_flush_fence = VK_NULL_HANDLE;
+ m_needs_flush = false;
+ g_command_buffer_mgr->RemoveFencePointCallback(
+ this);
+ });
+}
+
+void VKStagingTexture::CopyToTexture(const MathUtil::Rectangle<int>& src_rect, AbstractTexture* dst,
+ const MathUtil::Rectangle<int>& dst_rect, u32 dst_layer,
+ u32 dst_level)
+{
+ _assert_(m_type == StagingTextureType::Upload);
+ _assert_(src_rect.GetWidth() == dst_rect.GetWidth() &&
+ src_rect.GetHeight() == dst_rect.GetHeight());
+ _assert_(src_rect.left >= 0 && static_cast<u32>(src_rect.right) <= m_config.width &&
+ src_rect.top >= 0 && static_cast<u32>(src_rect.bottom) <= m_config.height);
+ _assert_(dst_rect.left >= 0 && static_cast<u32>(dst_rect.right) <= dst->GetConfig().width &&
+ dst_rect.top >= 0 && static_cast<u32>(dst_rect.bottom) <= dst->GetConfig().height);
+
+ if (m_needs_flush)
+ {
+ // Drop copy before reusing it.
+ g_command_buffer_mgr->RemoveFencePointCallback(this);
+ m_flush_fence = VK_NULL_HANDLE;
+ m_needs_flush = false;
+ }
+
+ // Flush caches before copying.
+ m_staging_buffer->FlushCPUCache();
+
+ Texture2D* dst_tex = static_cast<const VKTexture*>(dst)->GetRawTexIdentifier();
+ VkImageLayout old_layout = dst_tex->GetLayout();
+ dst_tex->TransitionToLayout(g_command_buffer_mgr->GetCurrentCommandBuffer(),
+ VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL);
+
+ // Issue the image->buffer copy, but delay it for now.
+ VkBufferImageCopy image_copy = {};
+ image_copy.bufferOffset =
+ static_cast<VkDeviceSize>(static_cast<size_t>(src_rect.top) * m_config.GetStride() +
+ static_cast<size_t>(src_rect.left) * m_texel_size);
+ image_copy.bufferRowLength = static_cast<u32>(m_config.width);
+ image_copy.bufferImageHeight = 0;
+ image_copy.imageSubresource = {VK_IMAGE_ASPECT_COLOR_BIT, dst_level, dst_layer, 1};
+ image_copy.imageOffset = {dst_rect.left, dst_rect.top, 0};
+ image_copy.imageExtent = {static_cast<u32>(dst_rect.GetWidth()),
+ static_cast<u32>(dst_rect.GetHeight()), 1u};
+ vkCmdCopyBufferToImage(g_command_buffer_mgr->GetCurrentCommandBuffer(),
+ m_staging_buffer->GetBuffer(), dst_tex->GetImage(),
+ VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, 1, &image_copy);
+
+ // Restore old source texture layout.
+ dst_tex->TransitionToLayout(g_command_buffer_mgr->GetCurrentCommandBuffer(), old_layout);
+
+ m_needs_flush = true;
+ g_command_buffer_mgr->AddFencePointCallback(this,
+ [this](VkCommandBuffer buf, VkFence fence) {
+ _assert_(m_needs_flush);
+ m_flush_fence = fence;
+ },
+ [this](VkFence fence) {
+ m_flush_fence = VK_NULL_HANDLE;
+ m_needs_flush = false;
+ g_command_buffer_mgr->RemoveFencePointCallback(
+ this);
+ });
+}
+
+bool VKStagingTexture::Map()
+{
+ // Always mapped.
+ return true;
+}
+
+void VKStagingTexture::Unmap()
+{
+ // Always mapped.
+}
+
+void VKStagingTexture::Flush()
+{
+ if (!m_needs_flush)
+ return;
+
+ // Either of the below two calls will cause the callback to fire.
+ g_command_buffer_mgr->RemoveFencePointCallback(this);
+ if (m_flush_fence != VK_NULL_HANDLE)
+ {
+ // WaitForFence should fire the callback.
+ g_command_buffer_mgr->WaitForFence(m_flush_fence);
+ }
+ else
+ {
+ // We don't have a fence, and are pending. That means the readback is in the current
+ // command buffer, and must execute it to populate the staging texture.
+ Util::ExecuteCurrentCommandsAndRestoreState(false, true);
+ }
+ m_needs_flush = false;
+
+ // For readback textures, invalidate the CPU cache as there is new data there.
+ if (m_type == StagingTextureType::Readback)
+ m_staging_buffer->InvalidateCPUCache();
+}
+
} // namespace Vulkan