mirror of
https://github.com/WinampDesktop/winamp.git
synced 2025-04-26 10:45:43 -04:00
535 lines
21 KiB
C++
535 lines
21 KiB
C++
// SPDX-FileCopyrightText: 2019-2022 Connor McLaughlin <stenzek@gmail.com>
|
|
// SPDX-License-Identifier: (GPL-3.0 OR CC-BY-NC-ND-4.0)
|
|
|
|
#include "texture.h"
|
|
#include "../align.h"
|
|
#include "../assert.h"
|
|
#include "../log.h"
|
|
#include "../string_util.h"
|
|
#include "context.h"
|
|
#include "util.h"
|
|
#include <algorithm>
|
|
Log_SetChannel(Texture);
|
|
|
|
static constexpr std::array<VkFormat, static_cast<u32>(GPUTexture::Format::Count)> s_vk_mapping = {
|
|
{VK_FORMAT_UNDEFINED, VK_FORMAT_R8G8B8A8_UNORM, VK_FORMAT_B8G8R8A8_UNORM, VK_FORMAT_R5G6B5_UNORM_PACK16,
|
|
VK_FORMAT_A1R5G5B5_UNORM_PACK16, VK_FORMAT_R8_UNORM, VK_FORMAT_D16_UNORM}};
|
|
|
|
static constexpr VkComponentMapping s_identity_swizzle{VK_COMPONENT_SWIZZLE_IDENTITY, VK_COMPONENT_SWIZZLE_IDENTITY,
|
|
VK_COMPONENT_SWIZZLE_IDENTITY, VK_COMPONENT_SWIZZLE_IDENTITY};
|
|
|
|
Vulkan::Texture::Texture() = default;
|
|
|
|
Vulkan::Texture::Texture(Texture&& move)
|
|
: m_view_type(move.m_view_type), m_layout(move.m_layout), m_image(move.m_image), m_allocation(move.m_allocation),
|
|
m_view(move.m_view)
|
|
{
|
|
m_width = move.m_width;
|
|
m_height = move.m_height;
|
|
m_layers = move.m_layers;
|
|
m_levels = move.m_levels;
|
|
m_samples = move.m_samples;
|
|
|
|
move.ClearBaseProperties();
|
|
move.m_view_type = VK_IMAGE_VIEW_TYPE_2D;
|
|
move.m_layout = VK_IMAGE_LAYOUT_UNDEFINED;
|
|
move.m_image = VK_NULL_HANDLE;
|
|
move.m_allocation = VK_NULL_HANDLE;
|
|
move.m_view = VK_NULL_HANDLE;
|
|
}
|
|
|
|
Vulkan::Texture::~Texture()
|
|
{
|
|
if (IsValid())
|
|
Destroy(true);
|
|
}
|
|
|
|
VkFormat Vulkan::Texture::GetVkFormat(Format format)
|
|
{
|
|
return s_vk_mapping[static_cast<u8>(format)];
|
|
}
|
|
|
|
GPUTexture::Format Vulkan::Texture::LookupBaseFormat(VkFormat vformat)
|
|
{
|
|
for (u32 i = 0; i < static_cast<u32>(s_vk_mapping.size()); i++)
|
|
{
|
|
if (s_vk_mapping[i] == vformat)
|
|
return static_cast<Format>(i);
|
|
}
|
|
return GPUTexture::Format::Unknown;
|
|
}
|
|
|
|
bool Vulkan::Texture::IsValid() const
|
|
{
|
|
return (m_image != VK_NULL_HANDLE);
|
|
}
|
|
|
|
Vulkan::Texture& Vulkan::Texture::operator=(Texture&& move)
|
|
{
|
|
if (IsValid())
|
|
Destroy(true);
|
|
|
|
std::swap(m_width, move.m_width);
|
|
std::swap(m_height, move.m_height);
|
|
std::swap(m_levels, move.m_levels);
|
|
std::swap(m_layers, move.m_layers);
|
|
std::swap(m_format, move.m_format);
|
|
std::swap(m_samples, move.m_samples);
|
|
std::swap(m_view_type, move.m_view_type);
|
|
std::swap(m_layout, move.m_layout);
|
|
std::swap(m_image, move.m_image);
|
|
std::swap(m_allocation, move.m_allocation);
|
|
std::swap(m_view, move.m_view);
|
|
|
|
return *this;
|
|
}
|
|
|
|
bool Vulkan::Texture::Create(u32 width, u32 height, u32 levels, u32 layers, VkFormat format,
|
|
VkSampleCountFlagBits samples, VkImageViewType view_type, VkImageTiling tiling,
|
|
VkImageUsageFlags usage, bool dedicated_memory /* = false */,
|
|
const VkComponentMapping* swizzle /* = nullptr */)
|
|
{
|
|
const VkImageCreateInfo image_info = {VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO,
|
|
nullptr,
|
|
0,
|
|
VK_IMAGE_TYPE_2D,
|
|
format,
|
|
{width, height, 1},
|
|
levels,
|
|
layers,
|
|
samples,
|
|
tiling,
|
|
usage,
|
|
VK_SHARING_MODE_EXCLUSIVE,
|
|
0,
|
|
nullptr,
|
|
VK_IMAGE_LAYOUT_UNDEFINED};
|
|
|
|
VmaAllocationCreateInfo aci = {};
|
|
aci.usage = VMA_MEMORY_USAGE_GPU_ONLY;
|
|
aci.flags = VMA_ALLOCATION_CREATE_WITHIN_BUDGET_BIT;
|
|
aci.requiredFlags = VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT;
|
|
if (dedicated_memory)
|
|
aci.flags |= VMA_ALLOCATION_CREATE_DEDICATED_MEMORY_BIT;
|
|
|
|
VkImage image = VK_NULL_HANDLE;
|
|
VmaAllocation allocation = VK_NULL_HANDLE;
|
|
VkResult res = vmaCreateImage(g_vulkan_context->GetAllocator(), &image_info, &aci, &image, &allocation, nullptr);
|
|
if (res != VK_SUCCESS && dedicated_memory)
|
|
{
|
|
// try without dedicated memory
|
|
aci.flags &= ~VMA_ALLOCATION_CREATE_DEDICATED_MEMORY_BIT;
|
|
res = vmaCreateImage(g_vulkan_context->GetAllocator(), &image_info, &aci, &image, &allocation, nullptr);
|
|
}
|
|
if (res == VK_ERROR_OUT_OF_DEVICE_MEMORY)
|
|
{
|
|
Log_WarningPrintf("Failed to allocate device memory for %ux%u texture", width, height);
|
|
return false;
|
|
}
|
|
else if (res != VK_SUCCESS)
|
|
{
|
|
LOG_VULKAN_ERROR(res, "vmaCreateImage failed: ");
|
|
return false;
|
|
}
|
|
|
|
const VkImageViewCreateInfo view_info = {VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO,
|
|
nullptr,
|
|
0,
|
|
image,
|
|
view_type,
|
|
format,
|
|
swizzle ? *swizzle : s_identity_swizzle,
|
|
{Util::IsDepthFormat(format) ?
|
|
static_cast<VkImageAspectFlags>(VK_IMAGE_ASPECT_DEPTH_BIT) :
|
|
static_cast<VkImageAspectFlags>(VK_IMAGE_ASPECT_COLOR_BIT),
|
|
0, levels, 0, layers}};
|
|
|
|
VkImageView view = VK_NULL_HANDLE;
|
|
res = vkCreateImageView(g_vulkan_context->GetDevice(), &view_info, nullptr, &view);
|
|
if (res != VK_SUCCESS)
|
|
{
|
|
LOG_VULKAN_ERROR(res, "vkCreateImageView failed: ");
|
|
vmaDestroyImage(g_vulkan_context->GetAllocator(), image, allocation);
|
|
return false;
|
|
}
|
|
|
|
if (IsValid())
|
|
Destroy(true);
|
|
|
|
m_width = static_cast<u16>(width);
|
|
m_height = static_cast<u16>(height);
|
|
m_levels = static_cast<u8>(levels);
|
|
m_layers = static_cast<u8>(layers);
|
|
m_samples = static_cast<u8>(samples);
|
|
m_format = LookupBaseFormat(format);
|
|
m_view_type = view_type;
|
|
m_layout = VK_IMAGE_LAYOUT_UNDEFINED;
|
|
m_image = image;
|
|
m_allocation = allocation;
|
|
m_view = view;
|
|
return true;
|
|
}
|
|
|
|
bool Vulkan::Texture::Adopt(VkImage existing_image, VkImageViewType view_type, u32 width, u32 height, u32 levels,
|
|
u32 layers, VkFormat format, VkSampleCountFlagBits samples, VkImageLayout layout,
|
|
const VkComponentMapping* swizzle /* = nullptr */)
|
|
{
|
|
// Only need to create the image view, this is mainly for swap chains.
|
|
const VkImageViewCreateInfo view_info = {VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO,
|
|
nullptr,
|
|
0,
|
|
existing_image,
|
|
view_type,
|
|
format,
|
|
swizzle ? *swizzle : s_identity_swizzle,
|
|
{Util::IsDepthFormat(format) ?
|
|
static_cast<VkImageAspectFlags>(VK_IMAGE_ASPECT_DEPTH_BIT) :
|
|
static_cast<VkImageAspectFlags>(VK_IMAGE_ASPECT_COLOR_BIT),
|
|
0, levels, 0, layers}};
|
|
|
|
// Memory is managed by the owner of the image.
|
|
VkImageView view = VK_NULL_HANDLE;
|
|
VkResult res = vkCreateImageView(g_vulkan_context->GetDevice(), &view_info, nullptr, &view);
|
|
if (res != VK_SUCCESS)
|
|
{
|
|
LOG_VULKAN_ERROR(res, "vkCreateImageView failed: ");
|
|
return false;
|
|
}
|
|
|
|
if (IsValid())
|
|
Destroy(true);
|
|
|
|
m_width = static_cast<u16>(width);
|
|
m_height = static_cast<u16>(height);
|
|
m_levels = static_cast<u8>(levels);
|
|
m_layers = static_cast<u8>(layers);
|
|
m_format = LookupBaseFormat(format);
|
|
m_samples = static_cast<u8>(samples);
|
|
m_view_type = view_type;
|
|
m_layout = layout;
|
|
m_image = existing_image;
|
|
m_view = view;
|
|
return true;
|
|
}
|
|
|
|
void Vulkan::Texture::Destroy(bool defer /* = true */)
|
|
{
|
|
if (m_view != VK_NULL_HANDLE)
|
|
{
|
|
if (defer)
|
|
g_vulkan_context->DeferImageViewDestruction(m_view);
|
|
else
|
|
vkDestroyImageView(g_vulkan_context->GetDevice(), m_view, nullptr);
|
|
m_view = VK_NULL_HANDLE;
|
|
}
|
|
|
|
// If we don't have device memory allocated, the image is not owned by us (e.g. swapchain)
|
|
if (m_allocation != VK_NULL_HANDLE)
|
|
{
|
|
Assert(m_image != VK_NULL_HANDLE);
|
|
if (defer)
|
|
g_vulkan_context->DeferImageDestruction(m_image, m_allocation);
|
|
else
|
|
vmaDestroyImage(g_vulkan_context->GetAllocator(), m_image, m_allocation);
|
|
m_image = VK_NULL_HANDLE;
|
|
m_allocation = VK_NULL_HANDLE;
|
|
}
|
|
|
|
ClearBaseProperties();
|
|
m_samples = VK_SAMPLE_COUNT_1_BIT;
|
|
m_view_type = VK_IMAGE_VIEW_TYPE_2D;
|
|
m_layout = VK_IMAGE_LAYOUT_UNDEFINED;
|
|
}
|
|
|
|
void Vulkan::Texture::OverrideImageLayout(VkImageLayout new_layout)
|
|
{
|
|
m_layout = new_layout;
|
|
}
|
|
|
|
void Vulkan::Texture::TransitionToLayout(VkCommandBuffer command_buffer, VkImageLayout new_layout)
|
|
{
|
|
if (m_layout == new_layout)
|
|
return;
|
|
const Vulkan::Util::DebugScope debugScope(command_buffer, "Texture::TransitionToLayout: %s",
|
|
Vulkan::Util::VkImageLayoutToString(new_layout));
|
|
|
|
TransitionSubresourcesToLayout(command_buffer, 0, m_levels, 0, m_layers, m_layout, new_layout);
|
|
|
|
m_layout = new_layout;
|
|
}
|
|
|
|
void Vulkan::Texture::TransitionSubresourcesToLayout(VkCommandBuffer command_buffer, u32 start_level, u32 num_levels,
|
|
u32 start_layer, u32 num_layers, VkImageLayout old_layout,
|
|
VkImageLayout new_layout)
|
|
{
|
|
const Vulkan::Util::DebugScope debugScope(
|
|
command_buffer, "Texture::TransitionSubresourcesToLayout: Lvl:[%u,%u) Lyr:[%u,%u) %s -> %s", start_level,
|
|
start_level + num_levels, start_layer, start_layer + num_layers, Vulkan::Util::VkImageLayoutToString(old_layout),
|
|
Vulkan::Util::VkImageLayoutToString(new_layout));
|
|
|
|
VkImageMemoryBarrier barrier = {
|
|
VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER, // VkStructureType sType
|
|
nullptr, // const void* pNext
|
|
0, // VkAccessFlags srcAccessMask
|
|
0, // VkAccessFlags dstAccessMask
|
|
old_layout, // VkImageLayout oldLayout
|
|
new_layout, // VkImageLayout newLayout
|
|
VK_QUEUE_FAMILY_IGNORED, // uint32_t srcQueueFamilyIndex
|
|
VK_QUEUE_FAMILY_IGNORED, // uint32_t dstQueueFamilyIndex
|
|
m_image, // VkImage image
|
|
{static_cast<VkImageAspectFlags>(IsDepthFormat(m_format) ? VK_IMAGE_ASPECT_DEPTH_BIT : VK_IMAGE_ASPECT_COLOR_BIT),
|
|
start_level, num_levels, start_layer, num_layers} // VkImageSubresourceRange subresourceRange
|
|
};
|
|
|
|
// srcStageMask -> Stages that must complete before the barrier
|
|
// dstStageMask -> Stages that must wait for after the barrier before beginning
|
|
VkPipelineStageFlags srcStageMask, dstStageMask;
|
|
switch (old_layout)
|
|
{
|
|
case VK_IMAGE_LAYOUT_UNDEFINED:
|
|
// Layout undefined therefore contents undefined, and we don't care what happens to it.
|
|
barrier.srcAccessMask = 0;
|
|
srcStageMask = VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT;
|
|
break;
|
|
|
|
case VK_IMAGE_LAYOUT_PREINITIALIZED:
|
|
// Image has been pre-initialized by the host, so ensure all writes have completed.
|
|
barrier.srcAccessMask = VK_ACCESS_HOST_WRITE_BIT;
|
|
srcStageMask = VK_PIPELINE_STAGE_HOST_BIT;
|
|
break;
|
|
|
|
case VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL:
|
|
// Image was being used as a color attachment, so ensure all writes have completed.
|
|
barrier.srcAccessMask = VK_ACCESS_COLOR_ATTACHMENT_READ_BIT | VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT;
|
|
srcStageMask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT;
|
|
break;
|
|
|
|
case VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL:
|
|
// Image was being used as a depthstencil attachment, so ensure all writes have completed.
|
|
barrier.srcAccessMask =
|
|
VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_READ_BIT | VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT;
|
|
srcStageMask = VK_PIPELINE_STAGE_EARLY_FRAGMENT_TESTS_BIT | VK_PIPELINE_STAGE_LATE_FRAGMENT_TESTS_BIT;
|
|
break;
|
|
|
|
case VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL:
|
|
// Image was being used as a shader resource, make sure all reads have finished.
|
|
barrier.srcAccessMask = VK_ACCESS_SHADER_READ_BIT;
|
|
srcStageMask = VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT;
|
|
break;
|
|
|
|
case VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL:
|
|
// Image was being used as a copy source, ensure all reads have finished.
|
|
barrier.srcAccessMask = VK_ACCESS_TRANSFER_READ_BIT;
|
|
srcStageMask = VK_PIPELINE_STAGE_TRANSFER_BIT;
|
|
break;
|
|
|
|
case VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL:
|
|
// Image was being used as a copy destination, ensure all writes have finished.
|
|
barrier.srcAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT;
|
|
srcStageMask = VK_PIPELINE_STAGE_TRANSFER_BIT;
|
|
break;
|
|
|
|
default:
|
|
srcStageMask = VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT;
|
|
break;
|
|
}
|
|
|
|
switch (new_layout)
|
|
{
|
|
case VK_IMAGE_LAYOUT_UNDEFINED:
|
|
barrier.dstAccessMask = 0;
|
|
dstStageMask = VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT;
|
|
break;
|
|
|
|
case VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL:
|
|
barrier.dstAccessMask = VK_ACCESS_COLOR_ATTACHMENT_READ_BIT | VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT;
|
|
dstStageMask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT;
|
|
break;
|
|
|
|
case VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL:
|
|
barrier.dstAccessMask =
|
|
VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_READ_BIT | VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT;
|
|
dstStageMask = VK_PIPELINE_STAGE_EARLY_FRAGMENT_TESTS_BIT | VK_PIPELINE_STAGE_LATE_FRAGMENT_TESTS_BIT;
|
|
break;
|
|
|
|
case VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL:
|
|
barrier.dstAccessMask = VK_ACCESS_SHADER_READ_BIT;
|
|
dstStageMask = VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT;
|
|
break;
|
|
|
|
case VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL:
|
|
barrier.dstAccessMask = VK_ACCESS_TRANSFER_READ_BIT;
|
|
dstStageMask = VK_PIPELINE_STAGE_TRANSFER_BIT;
|
|
break;
|
|
|
|
case VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL:
|
|
barrier.dstAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT;
|
|
dstStageMask = VK_PIPELINE_STAGE_TRANSFER_BIT;
|
|
break;
|
|
|
|
case VK_IMAGE_LAYOUT_PRESENT_SRC_KHR:
|
|
srcStageMask = VK_PIPELINE_STAGE_ALL_COMMANDS_BIT;
|
|
dstStageMask = VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT;
|
|
break;
|
|
|
|
default:
|
|
dstStageMask = VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT;
|
|
break;
|
|
}
|
|
vkCmdPipelineBarrier(command_buffer, srcStageMask, dstStageMask, 0, 0, nullptr, 0, nullptr, 1, &barrier);
|
|
}
|
|
|
|
VkFramebuffer Vulkan::Texture::CreateFramebuffer(VkRenderPass render_pass)
|
|
{
|
|
const VkFramebufferCreateInfo ci = {
|
|
VK_STRUCTURE_TYPE_FRAMEBUFFER_CREATE_INFO, nullptr, 0u, render_pass, 1, &m_view, m_width, m_height, m_layers};
|
|
VkFramebuffer fb = VK_NULL_HANDLE;
|
|
VkResult res = vkCreateFramebuffer(g_vulkan_context->GetDevice(), &ci, nullptr, &fb);
|
|
if (res != VK_SUCCESS)
|
|
{
|
|
LOG_VULKAN_ERROR(res, "vkCreateFramebuffer() failed: ");
|
|
return VK_NULL_HANDLE;
|
|
}
|
|
|
|
return fb;
|
|
}
|
|
|
|
void Vulkan::Texture::UpdateFromBuffer(VkCommandBuffer cmdbuf, u32 level, u32 layer, u32 x, u32 y, u32 width,
|
|
u32 height, VkBuffer buffer, u32 buffer_offset, u32 row_length)
|
|
{
|
|
// If we're previously undefined, don't leave any images in this layout.
|
|
const VkImageLayout old_layout = m_layout;
|
|
if (old_layout == VK_IMAGE_LAYOUT_UNDEFINED)
|
|
TransitionToLayout(cmdbuf, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL);
|
|
else if (old_layout != VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL)
|
|
TransitionSubresourcesToLayout(cmdbuf, level, 1, layer, 1, old_layout, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL);
|
|
|
|
const VkBufferImageCopy bic = {static_cast<VkDeviceSize>(buffer_offset),
|
|
row_length,
|
|
height,
|
|
{VK_IMAGE_ASPECT_COLOR_BIT, level, layer, 1u},
|
|
{static_cast<int32_t>(x), static_cast<int32_t>(y), 0},
|
|
{width, height, 1u}};
|
|
|
|
vkCmdCopyBufferToImage(cmdbuf, buffer, m_image, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, 1, &bic);
|
|
|
|
if (old_layout != VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL && old_layout != VK_IMAGE_LAYOUT_UNDEFINED)
|
|
TransitionSubresourcesToLayout(cmdbuf, level, 1, layer, 1, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, old_layout);
|
|
}
|
|
|
|
u32 Vulkan::Texture::CalcUpdatePitch(u32 width) const
|
|
{
|
|
return Common::AlignUp(width * GetPixelSize(), g_vulkan_context->GetBufferCopyRowPitchAlignment());
|
|
}
|
|
|
|
u32 Vulkan::Texture::CalcUpdateRowLength(u32 pitch) const
|
|
{
|
|
return pitch / GetPixelSize();
|
|
}
|
|
|
|
bool Vulkan::Texture::BeginUpdate(u32 width, u32 height, void** out_buffer, u32* out_pitch)
|
|
{
|
|
const u32 pitch = CalcUpdatePitch(width);
|
|
const u32 required_size = pitch * height;
|
|
StreamBuffer& buffer = g_vulkan_context->GetTextureUploadBuffer();
|
|
if (required_size > buffer.GetCurrentSize())
|
|
return false;
|
|
|
|
// TODO: allocate temporary buffer if this fails...
|
|
if (!buffer.ReserveMemory(required_size, g_vulkan_context->GetBufferCopyOffsetAlignment()))
|
|
{
|
|
g_vulkan_context->ExecuteCommandBuffer(false);
|
|
if (!buffer.ReserveMemory(required_size, g_vulkan_context->GetBufferCopyOffsetAlignment()))
|
|
return false;
|
|
}
|
|
|
|
*out_buffer = buffer.GetCurrentHostPointer();
|
|
*out_pitch = pitch;
|
|
return true;
|
|
}
|
|
|
|
void Vulkan::Texture::EndUpdate(u32 x, u32 y, u32 width, u32 height, u32 level, u32 layer)
|
|
{
|
|
const u32 pitch = CalcUpdatePitch(width);
|
|
const u32 required_size = pitch * height;
|
|
|
|
StreamBuffer& buffer = g_vulkan_context->GetTextureUploadBuffer();
|
|
const u32 buffer_offset = buffer.GetCurrentOffset();
|
|
buffer.CommitMemory(required_size);
|
|
|
|
UpdateFromBuffer(g_vulkan_context->GetCurrentCommandBuffer(), level, layer, x, y, width, height, buffer.GetBuffer(),
|
|
buffer_offset, CalcUpdateRowLength(pitch));
|
|
}
|
|
|
|
bool Vulkan::Texture::Update(u32 x, u32 y, u32 width, u32 height, u32 level, u32 layer, const void* data,
|
|
u32 data_pitch)
|
|
{
|
|
const u32 pitch = CalcUpdatePitch(width);
|
|
const u32 row_length = CalcUpdateRowLength(pitch);
|
|
const u32 required_size = pitch * height;
|
|
StreamBuffer& sbuffer = g_vulkan_context->GetTextureUploadBuffer();
|
|
|
|
// If the texture is larger than half our streaming buffer size, use a separate buffer.
|
|
// Otherwise allocation will either fail, or require lots of cmdbuffer submissions.
|
|
if (required_size > (g_vulkan_context->GetTextureUploadBuffer().GetCurrentSize() / 2))
|
|
{
|
|
const u32 size = data_pitch * height;
|
|
const VkBufferCreateInfo bci = {VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO,
|
|
nullptr,
|
|
0,
|
|
static_cast<VkDeviceSize>(size),
|
|
VK_BUFFER_USAGE_TRANSFER_SRC_BIT,
|
|
VK_SHARING_MODE_EXCLUSIVE,
|
|
0,
|
|
nullptr};
|
|
|
|
// Don't worry about setting the coherent bit for this upload, the main reason we had
|
|
// that set in StreamBuffer was for MoltenVK, which would upload the whole buffer on
|
|
// smaller uploads, but we're writing to the whole thing anyway.
|
|
VmaAllocationCreateInfo aci = {};
|
|
aci.flags = VMA_ALLOCATION_CREATE_MAPPED_BIT;
|
|
aci.usage = VMA_MEMORY_USAGE_CPU_TO_GPU;
|
|
|
|
VmaAllocationInfo ai;
|
|
VkBuffer buffer;
|
|
VmaAllocation allocation;
|
|
VkResult res = vmaCreateBuffer(g_vulkan_context->GetAllocator(), &bci, &aci, &buffer, &allocation, &ai);
|
|
if (res != VK_SUCCESS)
|
|
{
|
|
LOG_VULKAN_ERROR(res, "vmaCreateBuffer() failed: ");
|
|
return false;
|
|
}
|
|
|
|
// Immediately queue it for freeing after the command buffer finishes, since it's only needed for the copy.
|
|
g_vulkan_context->DeferBufferDestruction(buffer, allocation);
|
|
|
|
StringUtil::StrideMemCpy(ai.pMappedData, pitch, data, data_pitch, std::min(data_pitch, pitch), height);
|
|
vmaFlushAllocation(g_vulkan_context->GetAllocator(), allocation, 0, size);
|
|
|
|
UpdateFromBuffer(g_vulkan_context->GetCurrentCommandBuffer(), level, layer, x, y, width, height, buffer, 0,
|
|
row_length);
|
|
return true;
|
|
}
|
|
else
|
|
{
|
|
if (!sbuffer.ReserveMemory(required_size, g_vulkan_context->GetBufferCopyOffsetAlignment()))
|
|
{
|
|
g_vulkan_context->ExecuteCommandBuffer(false);
|
|
if (!sbuffer.ReserveMemory(required_size, g_vulkan_context->GetBufferCopyOffsetAlignment()))
|
|
{
|
|
Log_ErrorPrintf("Failed to reserve texture upload memory (%u bytes).", required_size);
|
|
return false;
|
|
}
|
|
}
|
|
|
|
const u32 buffer_offset = sbuffer.GetCurrentOffset();
|
|
StringUtil::StrideMemCpy(sbuffer.GetCurrentHostPointer(), pitch, data, data_pitch, std::min(data_pitch, pitch),
|
|
height);
|
|
sbuffer.CommitMemory(required_size);
|
|
|
|
UpdateFromBuffer(g_vulkan_context->GetCurrentCommandBuffer(), level, layer, x, y, width, height,
|
|
sbuffer.GetBuffer(), buffer_offset, row_length);
|
|
return true;
|
|
}
|
|
}
|