mirror of
https://github.com/dolphin-emu/dolphin.git
synced 2025-07-22 22:00:39 -06:00
VideoBackends: Add AbstractStagingTexture class
Can be used for asynchronous readback or upload of textures.
This commit is contained in:
@ -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
|
||||
|
Reference in New Issue
Block a user