VideoBackends: Add AbstractStagingTexture class

Can be used for asynchronous readback or upload of textures.
This commit is contained in:
Stenzek
2017-10-22 00:49:40 +10:00
parent a584ccc7d8
commit f43d85921d
33 changed files with 1220 additions and 12 deletions

View File

@ -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