// // Created by Bradley Austin Davis on 2016/08/07 // Adapted for Vulkan in 2022-2025 by dr Karol Suprynowicz. // Copyright 2013-2018 High Fidelity, Inc. // Copyright 2023-2025 Overte e.V. // // Distributed under the Apache License, Version 2.0. // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // SPDX-License-Identifier: Apache-2.0 // #include "VKBuffer.h" #include "VKBackend.h" using namespace gpu::vk; VKBuffer* VKBuffer::sync(VKBackend& backend, const gpu::Buffer& buffer, bool transfer) { if (buffer.getSysmem().getSize() != 0) { if (buffer._getUpdateCount == 0) { qWarning() << "Unsynced buffer"; } if (buffer._getUpdateCount < buffer._applyUpdateCount) { qWarning() << "Unsynced buffer " << buffer._getUpdateCount << " " << buffer._applyUpdateCount; } } VKBuffer* object = gpu::Backend::getGPUObject(buffer); // Has the storage size changed? VKTODO: is something missing here? bool newBuffer = false; if (!object || object->stagingAllocation.size < buffer._renderSysmem.getSize() /*object->_stamp != buffer._renderSysmem.getStamp()*/) { if (object) { backend._currentFrame->_bufferResizeCounter++; } else { backend._currentFrame->_bufferCreationCounter++; } // VKTODO: Should previous gpuobject be replaced? object = new VKBuffer(backend, buffer); newBuffer = true; backend._buffers.insert(object); } // VKTODO: delete the old buffer after rendering the frame if (0 != (buffer._renderPages._flags & PageManager::DIRTY) || newBuffer) { object->transferToStaging(backend); if (transfer) { if (backend._inRenderTransferPass) { // All barriers will be added in one command at the end of transfer pass. object->transferWithDelayedBarrier(backend, backend._currentCommandBuffer); } else { object->transferWithBarrier(backend, backend._currentCommandBuffer); } } } Q_ASSERT(object->allocation.size == buffer._renderSysmem.getSize()); return object; } VkBuffer VKBuffer::getBuffer(VKBackend& backend, const gpu::Buffer& buffer) { VKBuffer* bo = sync(backend, buffer); if (bo) { return bo->buffer; } else { return nullptr; } } void VKBuffer::transferToStaging(VKBackend &backend) { Size offset; Size blockSize; Size currentPage { 0 }; auto data = _gpuObject._renderSysmem.readData(); //auto dataSize = _gpuObject._renderSysmem.getSize(); while (_gpuObject._renderPages.getNextTransferBlock(offset, blockSize, currentPage)) { if (offset + blockSize > _localData.size()) { Q_ASSERT(false); } memcpy(_localData.data()+offset, data+offset, blockSize); } stagingAllocation.map(); stagingAllocation.copy(stagingAllocation.size, _localData.data()); stagingAllocation.flush(VK_WHOLE_SIZE); stagingAllocation.unmap(); _gpuObject._renderPages._flags &= ~PageManager::DIRTY; } void VKBuffer::transferOnly(VKBackend &backend) { // Only for development and testing, should never be called otherwise. Q_ASSERT(false); auto device = backend.getContext().device; VkCommandBuffer copyCmd = device->createCommandBuffer(device->graphicsCommandPool, VK_COMMAND_BUFFER_LEVEL_PRIMARY, true); VkBufferCopy copyRegion = {}; copyRegion.size = _localData.size(); vkCmdCopyBuffer( copyCmd, stagingBuffer, buffer, 1, ©Region); // VKTODO: is a memory barrier needed here? // VKTODO: all transfers should be done as a part of the transfer pass. device->flushCommandBuffer(copyCmd, backend.getContext().graphicsQueue, device->graphicsCommandPool, true); } void VKBuffer::transferWithBarrier(VKBackend &backend, VkCommandBuffer commandBuffer) { VkBufferCopy copyRegion = {}; copyRegion.size = _localData.size(); vkCmdCopyBuffer( commandBuffer, stagingBuffer, buffer, 1, ©Region); VkBufferMemoryBarrier bufferMemoryBarrier{ .sType = VK_STRUCTURE_TYPE_BUFFER_MEMORY_BARRIER, .pNext = nullptr, .srcAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT, .dstAccessMask = VK_ACCESS_SHADER_READ_BIT, .srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, .dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, .buffer = buffer, .offset = 0, .size = VK_WHOLE_SIZE }; vkCmdPipelineBarrier( commandBuffer, VK_PIPELINE_STAGE_TRANSFER_BIT, VK_PIPELINE_STAGE_VERTEX_SHADER_BIT, VK_FLAGS_NONE, 0, nullptr, 1, &bufferMemoryBarrier, 0, nullptr); backend._currentFrame->_bufferTransferCounterRenderPass++; incrementTransferCount(backend, copyRegion.size); // VKTODO: is a memory barrier needed here or just buffer barrier is ok? // VKTODO: all transfers should be done as a part of the transfer pass. } void VKBuffer::transferWithDelayedBarrier(VKBackend &backend, VkCommandBuffer commandBuffer) { VkBufferCopy copyRegion = {}; copyRegion.size = _localData.size() == 0 ? 256 : _localData.size(); vkCmdCopyBuffer( commandBuffer, stagingBuffer, buffer, 1, ©Region); VkBufferMemoryBarrier bufferMemoryBarrier{ .sType = VK_STRUCTURE_TYPE_BUFFER_MEMORY_BARRIER, .pNext = nullptr, .srcAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT, .dstAccessMask = VK_ACCESS_SHADER_READ_BIT, .srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, .dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, .buffer = buffer, .offset = 0, .size = VK_WHOLE_SIZE }; backend._currentFrame->addBufferBarrier(bufferMemoryBarrier); backend._currentFrame->_bufferTransferCounterTransferPass++; incrementTransferCount(backend, copyRegion.size); // VKTODO: is a memory barrier needed here or just buffer barrier is ok? } void VKBuffer::incrementTransferCount(VKBackend& backend, size_t transferSize) { backend._currentFrame->_bufferTransferredBytes += transferSize; if(_gpuObject.getUsage() & gpu::Buffer::UniformBuffer) { backend._currentFrame->_uniformBufferTransferCounter++; } if(_gpuObject.getUsage() & gpu::Buffer::VertexBuffer) { backend._currentFrame->_vertexBufferTransferCounter++; } if(_gpuObject.getUsage() & gpu::Buffer::IndexBuffer) { backend._currentFrame->_indexBufferTransferCounter++; } if(_gpuObject.getUsage() & gpu::Buffer::ResourceBuffer) { backend._currentFrame->_resourceBufferTransferCounter++; } } void VKBuffer::incrementCount(VKBackend& backend) { static int uniformCountTransfer = 0; static int uniformCountRender = 0; static int vertexCountTransfer = 0; static int vertexCountRender = 0; static int indexCountTransfer = 0; static int indexCountRender = 0; static int resourceCountTransfer = 0; static int resourceCountRender = 0; if(_gpuObject.getUsage() & gpu::Buffer::UniformBuffer) { if (backend._inRenderTransferPass) { uniformCountTransfer++; } else { uniformCountRender++; } } if(_gpuObject.getUsage() & gpu::Buffer::VertexBuffer) { if (backend._inRenderTransferPass) { vertexCountTransfer++; } else { vertexCountRender++; } } if(_gpuObject.getUsage() & gpu::Buffer::IndexBuffer) { if (backend._inRenderTransferPass) { indexCountTransfer++; } else { indexCountRender++; } } if(_gpuObject.getUsage() & gpu::Buffer::ResourceBuffer) { if (backend._inRenderTransferPass) { resourceCountTransfer++; } else { resourceCountRender++; } } } VKBuffer::VKBuffer(VKBackend& backend, const gpu::Buffer& gpuBuffer) : VKObject(backend, gpuBuffer), _stamp(gpuBuffer._renderSysmem.getStamp()) { Backend::setGPUObject(gpuBuffer, this); // Flags match VkBufferUsageFlagBits - this was in original Vulkan branch VkBufferUsageFlags usageFlags{ (VkBufferUsageFlags)gpuBuffer.getUsage() }; auto data = _gpuObject._renderSysmem.readData(); auto dataSize = _gpuObject._renderSysmem.getSize(); _localData.resize(dataSize); memcpy(_localData.data(), data, dataSize); this->stagingAllocation.size = gpuBuffer._renderSysmem.getSize(); this->allocation.size = this->stagingAllocation.size; VkBufferCreateInfo transferVkBufferCI{}; transferVkBufferCI.sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO; transferVkBufferCI.pNext = nullptr; transferVkBufferCI.flags = 0; transferVkBufferCI.size = stagingAllocation.size == 0 ? 256 : stagingAllocation.size; transferVkBufferCI.usage = VK_BUFFER_USAGE_2_TRANSFER_SRC_BIT; transferVkBufferCI.sharingMode = VK_SHARING_MODE_EXCLUSIVE; transferVkBufferCI.queueFamilyIndexCount = 0; transferVkBufferCI.pQueueFamilyIndices = nullptr; VmaAllocationCreateInfo transferAllocationCI = {}; transferAllocationCI.usage = VMA_MEMORY_USAGE_CPU_TO_GPU; // VKTODO: sometime in the future we should free staging buffers for large buffers that are not updated. transferAllocationCI.flags = VMA_ALLOCATION_CREATE_HOST_ACCESS_SEQUENTIAL_WRITE_BIT; VK_CHECK_RESULT(vmaCreateBuffer(stagingAllocation.getAllocator(), &transferVkBufferCI, &transferAllocationCI, &stagingBuffer, &stagingAllocation.allocation, nullptr)); VkBufferCreateInfo vkBufferCI{}; vkBufferCI.sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO; vkBufferCI.pNext = nullptr; vkBufferCI.flags = 0; vkBufferCI.size = allocation.size == 0 ? 256 : allocation.size; // TODO: why does renderer create zero-size buffers sometimes? vkBufferCI.usage = usageFlags | VK_BUFFER_USAGE_2_TRANSFER_DST_BIT; vkBufferCI.sharingMode = VK_SHARING_MODE_EXCLUSIVE; vkBufferCI.queueFamilyIndexCount = 0; vkBufferCI.pQueueFamilyIndices = nullptr; VmaAllocationCreateInfo allocationCI = {}; allocationCI.usage = VMA_MEMORY_USAGE_GPU_ONLY; allocationCI.flags = 0; VK_CHECK_RESULT(vmaCreateBuffer(allocation.getAllocator(), &vkBufferCI, &allocationCI, &buffer, &allocation.allocation, nullptr)); incrementCount(backend); Backend::bufferCount.increment(); Backend::bufferGPUMemSize.update(0, stagingAllocation.size); } VKBuffer::~VKBuffer() { Backend::bufferGPUMemSize.update(allocation.size, 0); Backend::bufferCount.decrement(); auto backend = _backend.lock(); if (backend) { auto &recycler = backend->getContext().recycler; recycler.trashVkBuffer(stagingBuffer); recycler.trashVmaAllocation(stagingAllocation.allocation); recycler.trashVkBuffer(buffer); recycler.trashVmaAllocation(allocation.allocation); recycler.bufferDeleted(this); } }