123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348 | /**
* Copyright (c) 2006-2026 LOVE Development Team
*
* This software is provided 'as-is', without any express or implied
* warranty. In no event will the authors be held liable for any damages
* arising from the use of this software.
*
* Permission is granted to anyone to use this software for any purpose,
* including commercial applications, and to alter it and redistribute it
* freely, subject to the following restrictions:
*
* 1. The origin of this software must not be misrepresented; you must not
* claim that you wrote the original software. If you use this software
* in a product, an acknowledgment in the product documentation would be
* appreciated but is not required.
* 2. Altered source versions must be plainly marked as such, and must not be
* misrepresented as being the original software.
* 3. This notice may not be removed or altered from any source distribution.
**/
#include "Buffer.h"
#include "Graphics.h"
namespace love
{
namespace graphics
{
namespace vulkan
{
static VkBufferUsageFlags getUsageBit(BufferUsage mode)
{
switch (mode)
{
case BUFFERUSAGE_VERTEX: return VK_BUFFER_USAGE_VERTEX_BUFFER_BIT;
case BUFFERUSAGE_INDEX: return VK_BUFFER_USAGE_INDEX_BUFFER_BIT;
case BUFFERUSAGE_UNIFORM: return VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT;
case BUFFERUSAGE_TEXEL: return VK_BUFFER_USAGE_UNIFORM_TEXEL_BUFFER_BIT;
case BUFFERUSAGE_SHADER_STORAGE: return VK_BUFFER_USAGE_STORAGE_BUFFER_BIT;
case BUFFERUSAGE_INDIRECT_ARGUMENTS: return VK_BUFFER_USAGE_INDIRECT_BUFFER_BIT;
default:
throw love::Exception("Unsupported BufferUsage mode: %d", mode);
}
}
static VkBufferUsageFlags getVulkanUsageFlags(BufferUsageFlags flags)
{
VkBufferUsageFlags vkFlags = 0;
for (int i = 0; i < BUFFERUSAGE_MAX_ENUM; i++)
{
BufferUsageFlags flag = static_cast<BufferUsageFlags>(1u << i);
if (flags & flag)
vkFlags |= getUsageBit((BufferUsage)i);
}
return vkFlags;
}
Buffer::Buffer(love::graphics::Graphics *gfx, const Settings &settings, const std::vector<DataDeclaration> &format, const void *data, size_t size, size_t arraylength)
: love::graphics::Buffer(gfx, settings, format, size, arraylength)
, zeroInitialize(settings.zeroInitialize)
, initialData(data)
, vgfx(dynamic_cast<Graphics*>(gfx))
, usageFlags(settings.usageFlags)
{
// All buffers can be copied to and from.
barrierDstAccessFlags = VK_ACCESS_TRANSFER_READ_BIT | VK_ACCESS_TRANSFER_WRITE_BIT;
barrierDstStageFlags = VK_PIPELINE_STAGE_TRANSFER_BIT;
if (usageFlags & BUFFERUSAGEFLAG_VERTEX)
{
barrierDstAccessFlags |= VK_ACCESS_VERTEX_ATTRIBUTE_READ_BIT;
barrierDstStageFlags |= VK_PIPELINE_STAGE_VERTEX_INPUT_BIT;
}
if (usageFlags & BUFFERUSAGEFLAG_INDEX)
{
barrierDstAccessFlags |= VK_ACCESS_INDEX_READ_BIT;
barrierDstStageFlags |= VK_PIPELINE_STAGE_VERTEX_INPUT_BIT;
}
if (usageFlags & BUFFERUSAGEFLAG_TEXEL)
{
barrierDstAccessFlags |= VK_ACCESS_SHADER_READ_BIT;
barrierDstStageFlags |= VK_PIPELINE_STAGE_VERTEX_SHADER_BIT | VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT | VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT;
}
if (usageFlags & BUFFERUSAGEFLAG_SHADER_STORAGE)
{
barrierDstAccessFlags |= VK_ACCESS_SHADER_READ_BIT | VK_ACCESS_SHADER_WRITE_BIT;
barrierDstStageFlags |= VK_PIPELINE_STAGE_VERTEX_SHADER_BIT | VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT | VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT;
}
if (usageFlags & BUFFERUSAGEFLAG_INDIRECT_ARGUMENTS)
{
barrierDstAccessFlags |= VK_ACCESS_INDIRECT_COMMAND_READ_BIT;
barrierDstStageFlags |= VK_PIPELINE_STAGE_DRAW_INDIRECT_BIT;
}
loadVolatile();
}
bool Buffer::loadVolatile()
{
if (buffer != VK_NULL_HANDLE)
return true;
allocator = vgfx->getVmaAllocator();
VkBufferCreateInfo bufferInfo{};
bufferInfo.sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO;
bufferInfo.size = getSize();
bufferInfo.usage = VK_BUFFER_USAGE_TRANSFER_DST_BIT | VK_BUFFER_USAGE_TRANSFER_SRC_BIT | getVulkanUsageFlags(usageFlags);
VmaAllocationCreateInfo allocCreateInfo{};
allocCreateInfo.usage = VMA_MEMORY_USAGE_AUTO;
if (dataUsage == BUFFERDATAUSAGE_READBACK)
allocCreateInfo.flags = VMA_ALLOCATION_CREATE_HOST_ACCESS_RANDOM_BIT | VMA_ALLOCATION_CREATE_MAPPED_BIT;
auto result = vmaCreateBuffer(allocator, &bufferInfo, &allocCreateInfo, &buffer, &allocation, &allocInfo);
if (result != VK_SUCCESS)
throw love::Exception("Failed to create Vulkan buffer: %s", Vulkan::getErrorString(result));
if (zeroInitialize)
{
auto cmd = vgfx->getCommandBufferForDataTransfer();
vkCmdFillBuffer(cmd, buffer, 0, VK_WHOLE_SIZE, 0);
postGPUWriteBarrier(cmd);
}
if (initialData)
fill(0, size, initialData);
if (usageFlags & BUFFERUSAGEFLAG_TEXEL)
{
VkBufferViewCreateInfo bufferViewInfo{};
bufferViewInfo.buffer = buffer;
bufferViewInfo.sType = VK_STRUCTURE_TYPE_BUFFER_VIEW_CREATE_INFO;
bufferViewInfo.format = Vulkan::getVulkanVertexFormat(getDataMember(0).decl.format);
bufferViewInfo.range = VK_WHOLE_SIZE;
result = vkCreateBufferView(vgfx->getDevice(), &bufferViewInfo, nullptr, &bufferView);
if (result != VK_SUCCESS)
throw love::Exception("Failed to create Vulkan texel buffer view: %s", Vulkan::getErrorString(result));
}
VkMemoryPropertyFlags memoryProperties;
vmaGetAllocationMemoryProperties(allocator, allocation, &memoryProperties);
if (memoryProperties & VK_MEMORY_PROPERTY_HOST_COHERENT_BIT)
coherent = true;
else
coherent = false;
if (!debugName.empty() && vgfx->getEnabledOptionalInstanceExtensions().debugInfo)
{
auto device = vgfx->getDevice();
VkDebugUtilsObjectNameInfoEXT nameInfo{};
nameInfo.sType = VK_STRUCTURE_TYPE_DEBUG_UTILS_OBJECT_NAME_INFO_EXT;
nameInfo.objectType = VK_OBJECT_TYPE_BUFFER;
nameInfo.objectHandle = (uint64_t)buffer;
nameInfo.pObjectName = debugName.c_str();
vkSetDebugUtilsObjectNameEXT(device, &nameInfo);
}
return true;
}
void Buffer::unloadVolatile()
{
if (buffer == VK_NULL_HANDLE)
return;
auto device = vgfx->getDevice();
vgfx->queueCleanUp(
[device=device, allocator=allocator, buffer=buffer, allocation=allocation, bufferView=bufferView](){
vkDeviceWaitIdle(device);
vmaDestroyBuffer(allocator, buffer, allocation);
if (bufferView)
vkDestroyBufferView(device, bufferView, nullptr);
});
buffer = VK_NULL_HANDLE;
bufferView = VK_NULL_HANDLE;
}
Buffer::~Buffer()
{
unloadVolatile();
}
ptrdiff_t Buffer::getHandle() const
{
return (ptrdiff_t) buffer;
}
ptrdiff_t Buffer::getTexelBufferHandle() const
{
return (ptrdiff_t) bufferView;
}
void *Buffer::map(MapType map, size_t offset, size_t size)
{
if (size == 0)
return nullptr;
if (map == MAP_WRITE_INVALIDATE && (isImmutable() || dataUsage == BUFFERDATAUSAGE_READBACK))
return nullptr;
if (map == MAP_READ_ONLY && dataUsage != BUFFERDATAUSAGE_READBACK)
return nullptr;
mappedRange = Range(offset, size);
if (!Range(0, getSize()).contains(mappedRange))
return nullptr;
if (dataUsage == BUFFERDATAUSAGE_READBACK)
{
if (!coherent)
vmaInvalidateAllocation(allocator, allocation, offset, size);
char *data = (char*)allocInfo.pMappedData;
return (void*) (data + offset);
}
else
{
VkBufferCreateInfo bufferInfo{};
bufferInfo.sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO;
bufferInfo.size = size;
bufferInfo.usage = VK_BUFFER_USAGE_TRANSFER_SRC_BIT;
VmaAllocationCreateInfo allocInfo{};
allocInfo.usage = VMA_MEMORY_USAGE_AUTO;
allocInfo.flags = VMA_ALLOCATION_CREATE_HOST_ACCESS_RANDOM_BIT | VMA_ALLOCATION_CREATE_MAPPED_BIT;
VkResult result = vmaCreateBuffer(allocator, &bufferInfo, &allocInfo, &stagingBuffer, &stagingAllocation, &stagingAllocInfo);
if (result != VK_SUCCESS)
throw love::Exception("Failed to create Vulkan staging buffer: %s", Vulkan::getErrorString(result));
return stagingAllocInfo.pMappedData;
}
}
bool Buffer::fill(size_t offset, size_t size, const void *data)
{
if (size == 0 || isImmutable() || dataUsage == BUFFERDATAUSAGE_READBACK)
return false;
if (!Range(0, getSize()).contains(Range(offset, size)))
return false;
VkBufferCreateInfo bufferInfo{};
bufferInfo.sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO;
bufferInfo.size = size;
bufferInfo.usage = VK_BUFFER_USAGE_TRANSFER_SRC_BIT;
VmaAllocationCreateInfo allocInfo{};
allocInfo.usage = VMA_MEMORY_USAGE_AUTO;
allocInfo.flags = VMA_ALLOCATION_CREATE_HOST_ACCESS_SEQUENTIAL_WRITE_BIT | VMA_ALLOCATION_CREATE_MAPPED_BIT;
VkBuffer fillBuffer;
VmaAllocation fillAllocation;
VmaAllocationInfo fillAllocInfo;
VkResult result = vmaCreateBuffer(allocator, &bufferInfo, &allocInfo, &fillBuffer, &fillAllocation, &fillAllocInfo);
if (result != VK_SUCCESS)
throw love::Exception("Failed to create Vulkan fill buffer: %s", Vulkan::getErrorString(result));
memcpy(fillAllocInfo.pMappedData, data, size);
VkMemoryPropertyFlags memoryProperties;
vmaGetAllocationMemoryProperties(allocator, fillAllocation, &memoryProperties);
if (~memoryProperties & VK_MEMORY_PROPERTY_HOST_COHERENT_BIT)
vmaFlushAllocation(allocator, fillAllocation, 0, size);
VkBufferCopy bufferCopy{};
bufferCopy.srcOffset = 0;
bufferCopy.dstOffset = offset;
bufferCopy.size = size;
auto cmd = vgfx->getCommandBufferForDataTransfer();
vkCmdCopyBuffer(cmd, fillBuffer, buffer, 1, &bufferCopy);
postGPUWriteBarrier(cmd);
vgfx->queueCleanUp([allocator = allocator, fillBuffer = fillBuffer, fillAllocation = fillAllocation]() {
vmaDestroyBuffer(allocator, fillBuffer, fillAllocation);
});
return true;
}
void Buffer::unmap(size_t usedoffset, size_t usedsize)
{
if (dataUsage != BUFFERDATAUSAGE_READBACK)
{
VkBufferCopy bufferCopy{};
bufferCopy.srcOffset = usedoffset - mappedRange.getOffset();
bufferCopy.dstOffset = usedoffset;
bufferCopy.size = usedsize;
VkMemoryPropertyFlags memoryProperties;
vmaGetAllocationMemoryProperties(allocator, stagingAllocation, &memoryProperties);
if (~memoryProperties & VK_MEMORY_PROPERTY_HOST_COHERENT_BIT)
vmaFlushAllocation(allocator, stagingAllocation, bufferCopy.srcOffset, usedsize);
auto cmd = vgfx->getCommandBufferForDataTransfer();
vkCmdCopyBuffer(cmd, stagingBuffer, buffer, 1, &bufferCopy);
postGPUWriteBarrier(cmd);
vgfx->queueCleanUp([allocator = allocator, stagingBuffer = stagingBuffer, stagingAllocation = stagingAllocation]() {
vmaDestroyBuffer(allocator, stagingBuffer, stagingAllocation);
});
}
}
void Buffer::clearInternal(size_t offset, size_t size)
{
auto cmd = vgfx->getCommandBufferForDataTransfer();
vkCmdFillBuffer(cmd, buffer, offset, size, 0);
postGPUWriteBarrier(cmd);
}
void Buffer::copyTo(love::graphics::Buffer *dest, size_t sourceoffset, size_t destoffset, size_t size)
{
auto commandBuffer = vgfx->getCommandBufferForDataTransfer();
VkBufferCopy bufferCopy{};
bufferCopy.srcOffset = sourceoffset;
bufferCopy.dstOffset = destoffset;
bufferCopy.size = size;
vkCmdCopyBuffer(commandBuffer, buffer, (VkBuffer) dest->getHandle(), 1, &bufferCopy);
((Buffer *)dest)->postGPUWriteBarrier(commandBuffer);
}
void Buffer::postGPUWriteBarrier(VkCommandBuffer cmd)
{
VkMemoryBarrier barrier{};
barrier.sType = VK_STRUCTURE_TYPE_MEMORY_BARRIER;
barrier.srcAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT;
barrier.dstAccessMask = barrierDstAccessFlags;
vkCmdPipelineBarrier(cmd, VK_PIPELINE_STAGE_TRANSFER_BIT, barrierDstStageFlags, 0, 1, &barrier, 0, nullptr, 0, nullptr);
}
} // vulkan
} // graphics
} // love
|