Fix in-flight frame update Vulkan (#3122)

This commit is contained in:
Adrian Cojocaru
2025-01-09 23:04:41 +02:00
committed by GitHub
parent f700a412fe
commit c1f76cff98
11 changed files with 209 additions and 101 deletions

View File

@@ -51,14 +51,18 @@ public:
void update(const void* data, std::size_t size, std::size_t offset) noexcept;
std::size_t getSizeInBytes() const noexcept { return size; }
const void* contents() const noexcept { return (raw.empty() ? nullptr : raw.data()); }
const void* contents() const noexcept;
const void* contents(uint8_t resourceIndex) const noexcept;
Context& getContext() const noexcept { return context; }
const vk::Buffer& getVulkanBuffer() const noexcept { return bufferAllocation->buffer; }
std::size_t getVulkanBufferOffset() const noexcept;
std::size_t getVulkanBufferSize() const noexcept;
std::size_t getVulkanBufferOffset(uint8_t resourceIndex) const noexcept;
// update the current sub-buffer with the latest data
void updateVulkanBuffer();
void updateVulkanBuffer(const int8_t destination, const uint8_t source);
bool isValid() const noexcept { return !raw.empty(); }
bool isValid() const noexcept { return !!bufferAllocation; }
operator bool() const noexcept { return isValid(); }
bool operator!() const noexcept { return !isValid(); }
@@ -69,14 +73,14 @@ public:
protected:
Context& context;
std::vector<std::uint8_t> raw;
std::size_t size;
std::uint32_t usage;
std::uint16_t version = 0;
VersionType version = 0;
bool persistent;
SharedBufferAllocation bufferAllocation;
size_t bufferWindowSize = 0;
std::vector<VersionType> bufferWindowVersions;
};
} // namespace vulkan

View File

@@ -136,9 +136,7 @@ public:
RenderStaticData& staticData,
const std::vector<shaders::ClipUBO>& tileUBOs);
const std::unique_ptr<BufferResource>& getDummyVertexBuffer();
const std::unique_ptr<BufferResource>& getDummyUniformBuffer();
const std::unique_ptr<BufferResource>& getDummyStorageBuffer();
const std::unique_ptr<BufferResource>& getDummyBuffer();
const std::unique_ptr<Texture2D>& getDummyTexture();
const vk::DescriptorSetLayout& getDescriptorSetLayout(DescriptorSetType type);
@@ -190,9 +188,7 @@ private:
vulkan::UniformBufferArray globalUniformBuffers;
std::unordered_map<DescriptorSetType, DescriptorPoolGrowable> descriptorPoolMap;
std::unique_ptr<BufferResource> dummyVertexBuffer;
std::unique_ptr<BufferResource> dummyUniformBuffer;
std::unique_ptr<BufferResource> dummyStorageBuffer;
std::unique_ptr<BufferResource> dummyBuffer;
std::unique_ptr<Texture2D> dummyTexture2D;
vk::UniqueDescriptorSetLayout globalUniformDescriptorSetLayout;
vk::UniqueDescriptorSetLayout layerUniformDescriptorSetLayout;

View File

@@ -70,6 +70,8 @@ public:
void startFrameCapture();
void endFrameCapture();
void setFrameCaptureLoop(bool value);
void triggerFrameCapture(uint32_t frameCount = 1, uint32_t frameDelay = 0);
protected:
std::unique_ptr<gfx::Context> createContext() override;

View File

@@ -15,6 +15,7 @@ public:
~UniformBuffer() override;
const BufferResource& getBufferResource() const { return buffer; }
BufferResource& mutableBufferResource() { return buffer; }
UniformBuffer clone() const { return {buffer.clone()}; }
@@ -37,14 +38,21 @@ public:
descriptorStorageCount(descriptorStorageCount_),
descriptorUniformCount(descriptorUniformCount_) {}
UniformBufferArray(UniformBufferArray&& other)
: gfx::UniformBufferArray(std::move(other)) {}
UniformBufferArray(UniformBufferArray&& other) noexcept
: gfx::UniformBufferArray(std::move(other)),
descriptorSetType(other.descriptorSetType),
descriptorStartIndex(other.descriptorStartIndex),
descriptorStorageCount(other.descriptorStorageCount),
descriptorUniformCount(other.descriptorUniformCount),
descriptorSet(std::move(other.descriptorSet)) {}
UniformBufferArray(const UniformBufferArray&) = delete;
UniformBufferArray& operator=(UniformBufferArray&& other) {
UniformBufferArray& operator=(UniformBufferArray&& other) noexcept {
gfx::UniformBufferArray::operator=(std::move(other));
return *this;
}
UniformBufferArray& operator=(const UniformBufferArray& other) {
gfx::UniformBufferArray::operator=(other);
return *this;

View File

@@ -6,6 +6,7 @@
#include <mbgl/util/instrumentation.hpp>
#include <algorithm>
#include <numeric>
namespace mbgl {
namespace vulkan {
@@ -59,12 +60,25 @@ BufferResource::BufferResource(
if (usage & VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT || usage & VK_BUFFER_USAGE_STORAGE_BUFFER_BIT) {
const auto& backend = context.getBackend();
const auto& deviceProps = backend.getDeviceProperties();
const auto& align = deviceProps.limits.minUniformBufferOffsetAlignment;
vk::DeviceSize align = 0;
if (usage & VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT) {
align = deviceProps.limits.minUniformBufferOffsetAlignment;
}
if (usage & VK_BUFFER_USAGE_STORAGE_BUFFER_BIT) {
align = align ? std::lcm(align, deviceProps.limits.minStorageBufferOffsetAlignment)
: deviceProps.limits.minStorageBufferOffsetAlignment;
}
bufferWindowSize = (size + align - 1) & ~(align - 1);
assert(bufferWindowSize != 0);
totalSize = bufferWindowSize * backend.getMaxFrames();
const auto frameCount = backend.getMaxFrames();
totalSize = bufferWindowSize * frameCount;
bufferWindowVersions = std::vector<std::uint16_t>(frameCount, 0);
}
const auto bufferInfo = vk::BufferCreateInfo()
@@ -87,9 +101,7 @@ BufferResource::BufferResource(
vmaMapMemory(allocator, bufferAllocation->allocation, &bufferAllocation->mappedBuffer);
if (data) {
raw.resize(size);
std::memcpy(raw.data(), data, size);
std::memcpy(static_cast<uint8_t*>(bufferAllocation->mappedBuffer) + getVulkanBufferOffset(), data, size);
update(data, size, 0);
}
if (isValid()) {
@@ -104,12 +116,13 @@ BufferResource::BufferResource(
BufferResource::BufferResource(BufferResource&& other) noexcept
: context(other.context),
raw(std::move(other.raw)),
size(other.size),
usage(other.usage),
version(other.version),
persistent(other.persistent),
bufferAllocation(std::move(other.bufferAllocation)),
bufferWindowSize(other.bufferWindowSize) {
bufferWindowSize(other.bufferWindowSize),
bufferWindowVersions(std::move(other.bufferWindowVersions)) {
other.bufferAllocation = nullptr;
}
@@ -134,7 +147,7 @@ BufferResource& BufferResource::operator=(BufferResource&& other) noexcept {
context.renderingStats().numBuffers--;
context.renderingStats().memBuffers -= size;
};
raw = std::move(other.raw);
size = other.size;
usage = other.usage;
persistent = other.persistent;
@@ -152,21 +165,63 @@ void BufferResource::update(const void* newData, std::size_t updateSize, std::si
return;
}
uint8_t* data = static_cast<uint8_t*>(bufferAllocation->mappedBuffer) + getVulkanBufferOffset() + offset;
std::memcpy(data, newData, updateSize);
auto& stats = context.renderingStats();
std::memcpy(raw.data() + offset, newData, updateSize);
std::memcpy(
static_cast<uint8_t*>(bufferAllocation->mappedBuffer) + getVulkanBufferOffset() + offset, newData, updateSize);
stats.bufferUpdateBytes += updateSize;
stats.bufferUpdates++;
version++;
if (bufferWindowSize) {
const auto frameIndex = context.getCurrentFrameResourceIndex();
bufferWindowVersions[frameIndex] = version;
}
}
const void* BufferResource::contents() const noexcept {
return contents(context.getCurrentFrameResourceIndex());
}
const void* BufferResource::contents(uint8_t resourceIndex) const noexcept {
if (!isValid()) {
return nullptr;
}
return static_cast<uint8_t*>(bufferAllocation->mappedBuffer) + getVulkanBufferOffset(resourceIndex);
}
std::size_t BufferResource::getVulkanBufferOffset() const noexcept {
if (bufferWindowSize > 0) return 0;
return getVulkanBufferOffset(context.getCurrentFrameResourceIndex());
}
return context.getCurrentFrameResourceIndex() * bufferWindowSize;
std::size_t BufferResource::getVulkanBufferOffset(std::uint8_t resourceIndex) const noexcept {
assert(context.getBackend().getMaxFrames() >= resourceIndex);
return bufferWindowSize ? resourceIndex * bufferWindowSize : 0;
}
void BufferResource::updateVulkanBuffer() {
const auto frameCount = context.getBackend().getMaxFrames();
const int8_t currentIndex = context.getCurrentFrameResourceIndex();
const int8_t prevIndex = currentIndex == 0 ? frameCount - 1 : currentIndex - 1;
updateVulkanBuffer(currentIndex, prevIndex);
}
void BufferResource::updateVulkanBuffer(const int8_t destination, const uint8_t source) {
if (!bufferWindowSize) {
return;
}
if (bufferWindowVersions[destination] < bufferWindowVersions[source]) {
uint8_t* dstData = static_cast<uint8_t*>(bufferAllocation->mappedBuffer) + bufferWindowSize * destination;
uint8_t* srcData = static_cast<uint8_t*>(bufferAllocation->mappedBuffer) + bufferWindowSize * source;
std::memcpy(dstData, srcData, size);
bufferWindowVersions[destination] = bufferWindowVersions[source];
}
}
} // namespace vulkan

View File

@@ -563,25 +563,14 @@ bool Context::renderTileClippingMasks(gfx::RenderPass& renderPass,
return true;
}
const std::unique_ptr<BufferResource>& Context::getDummyVertexBuffer() {
if (!dummyVertexBuffer)
dummyVertexBuffer = std::make_unique<BufferResource>(
*this, nullptr, 16, VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT, false);
return dummyVertexBuffer;
}
const std::unique_ptr<BufferResource>& Context::getDummyBuffer() {
if (!dummyBuffer) {
const uint32_t usage = VK_BUFFER_USAGE_VERTEX_BUFFER_BIT | VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT |
VK_BUFFER_USAGE_STORAGE_BUFFER_BIT;
dummyBuffer = std::make_unique<BufferResource>(*this, nullptr, 16, usage, false);
}
const std::unique_ptr<BufferResource>& Context::getDummyUniformBuffer() {
if (!dummyUniformBuffer)
dummyUniformBuffer = std::make_unique<BufferResource>(
*this, nullptr, 16, VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT, false);
return dummyUniformBuffer;
}
const std::unique_ptr<BufferResource>& Context::getDummyStorageBuffer() {
if (!dummyStorageBuffer)
dummyStorageBuffer = std::make_unique<BufferResource>(
*this, nullptr, 16, VK_BUFFER_USAGE_STORAGE_BUFFER_BIT, false);
return dummyStorageBuffer;
return dummyBuffer;
}
const std::unique_ptr<Texture2D>& Context::getDummyTexture() {

View File

@@ -165,8 +165,7 @@ void UniformDescriptorSet::update(const gfx::UniformBufferArray& uniforms,
.setOffset(bufferResource.getVulkanBufferOffset())
.setRange(bufferResource.getSizeInBytes());
} else {
const auto& dummyBuffer = index < descriptorStorageCount ? context.getDummyStorageBuffer()
: context.getDummyUniformBuffer();
const auto& dummyBuffer = context.getDummyBuffer();
descriptorBufferInfo.setBuffer(dummyBuffer->getVulkanBuffer()).setOffset(0).setRange(VK_WHOLE_SIZE);
}

View File

@@ -219,7 +219,8 @@ void SurfaceRenderableResource::initDepthStencil() {
.setViewType(vk::ImageViewType::e2D)
.setFormat(depthFormat)
.setComponents(vk::ComponentMapping()) // defaults to vk::ComponentSwizzle::eIdentity
.setSubresourceRange(vk::ImageSubresourceRange(vk::ImageAspectFlagBits::eDepth, 0, 1, 0, 1));
.setSubresourceRange(vk::ImageSubresourceRange(
vk::ImageAspectFlagBits::eDepth | vk::ImageAspectFlagBits::eStencil, 0, 1, 0, 1));
depthAllocation->imageView = device->createImageViewUnique(imageViewCreateInfo);
@@ -307,28 +308,29 @@ void SurfaceRenderableResource::init(uint32_t w, uint32_t h) {
// create render pass
const auto colorLayout = surface ? vk::ImageLayout::ePresentSrcKHR : vk::ImageLayout::eTransferSrcOptimal;
const auto colorAttachment = vk::AttachmentDescription(vk::AttachmentDescriptionFlags())
.setFormat(colorFormat)
.setSamples(vk::SampleCountFlagBits::e1)
.setLoadOp(vk::AttachmentLoadOp::eClear)
.setStoreOp(vk::AttachmentStoreOp::eStore)
.setStencilLoadOp(vk::AttachmentLoadOp::eDontCare)
.setStencilStoreOp(vk::AttachmentStoreOp::eDontCare)
.setInitialLayout(vk::ImageLayout::eUndefined)
.setFinalLayout(colorLayout);
const std::array<vk::AttachmentDescription, 2> attachments = {
vk::AttachmentDescription()
.setFormat(colorFormat)
.setSamples(vk::SampleCountFlagBits::e1)
.setLoadOp(vk::AttachmentLoadOp::eClear)
.setStoreOp(vk::AttachmentStoreOp::eStore)
.setStencilLoadOp(vk::AttachmentLoadOp::eDontCare)
.setStencilStoreOp(vk::AttachmentStoreOp::eDontCare)
.setInitialLayout(vk::ImageLayout::eUndefined)
.setFinalLayout(colorLayout),
vk::AttachmentDescription()
.setFormat(depthFormat)
.setSamples(vk::SampleCountFlagBits::e1)
.setLoadOp(vk::AttachmentLoadOp::eClear)
.setStoreOp(vk::AttachmentStoreOp::eDontCare)
.setStencilLoadOp(vk::AttachmentLoadOp::eClear)
.setStencilStoreOp(vk::AttachmentStoreOp::eDontCare)
.setInitialLayout(vk::ImageLayout::eUndefined)
.setFinalLayout(vk::ImageLayout::eDepthStencilAttachmentOptimal)};
const vk::AttachmentReference colorAttachmentRef(0, vk::ImageLayout::eColorAttachmentOptimal);
const auto depthAttachment = vk::AttachmentDescription()
.setFormat(depthFormat)
.setSamples(vk::SampleCountFlagBits::e1)
.setLoadOp(vk::AttachmentLoadOp::eClear)
.setStoreOp(vk::AttachmentStoreOp::eDontCare)
.setStencilLoadOp(vk::AttachmentLoadOp::eClear)
.setStencilStoreOp(vk::AttachmentStoreOp::eDontCare)
.setInitialLayout(vk::ImageLayout::eUndefined)
.setFinalLayout(vk::ImageLayout::eDepthStencilAttachmentOptimal);
const vk::AttachmentReference depthAttachmentRef(1, vk::ImageLayout::eDepthStencilAttachmentOptimal);
const auto subpass = vk::SubpassDescription()
@@ -337,28 +339,28 @@ void SurfaceRenderableResource::init(uint32_t w, uint32_t h) {
.setColorAttachments(colorAttachmentRef)
.setPDepthStencilAttachment(&depthAttachmentRef);
const auto subpassSrcStageMask = vk::PipelineStageFlags() | vk::PipelineStageFlagBits::eColorAttachmentOutput |
vk::PipelineStageFlagBits::eLateFragmentTests;
const std::array<vk::SubpassDependency, 2> dependencies = {
vk::SubpassDependency()
.setSrcSubpass(VK_SUBPASS_EXTERNAL)
.setDstSubpass(0)
.setSrcStageMask(vk::PipelineStageFlagBits::eColorAttachmentOutput)
.setDstStageMask(vk::PipelineStageFlagBits::eColorAttachmentOutput)
.setSrcAccessMask({})
.setDstAccessMask(vk::AccessFlagBits::eColorAttachmentWrite),
const auto subpassDstStageMask = vk::PipelineStageFlags() | vk::PipelineStageFlagBits::eColorAttachmentOutput |
vk::PipelineStageFlagBits::eEarlyFragmentTests;
vk::SubpassDependency()
.setSrcSubpass(VK_SUBPASS_EXTERNAL)
.setDstSubpass(0)
.setSrcStageMask(vk::PipelineStageFlagBits::eEarlyFragmentTests |
vk::PipelineStageFlagBits::eLateFragmentTests)
.setDstStageMask(vk::PipelineStageFlagBits::eEarlyFragmentTests |
vk::PipelineStageFlagBits::eLateFragmentTests)
.setSrcAccessMask({})
.setDstAccessMask(vk::AccessFlagBits::eDepthStencilAttachmentWrite),
};
const auto subpassSrcAccessMask = vk::AccessFlags() | vk::AccessFlagBits::eDepthStencilAttachmentWrite;
const auto subpassDstAccessMask = vk::AccessFlags() | vk::AccessFlagBits::eColorAttachmentWrite |
vk::AccessFlagBits::eDepthStencilAttachmentWrite;
const auto subpassDependency = vk::SubpassDependency()
.setSrcSubpass(VK_SUBPASS_EXTERNAL)
.setDstSubpass(0)
.setSrcStageMask(subpassSrcStageMask)
.setDstStageMask(subpassDstStageMask)
.setSrcAccessMask(subpassSrcAccessMask)
.setDstAccessMask(subpassDstAccessMask);
const std::array<vk::AttachmentDescription, 2> attachments = {colorAttachment, depthAttachment};
const auto renderPassCreateInfo =
vk::RenderPassCreateInfo().setAttachments(attachments).setSubpasses(subpass).setDependencies(subpassDependency);
vk::RenderPassCreateInfo().setAttachments(attachments).setSubpasses(subpass).setDependencies(dependencies);
renderPass = device->createRenderPassUnique(renderPassCreateInfo);

View File

@@ -60,7 +60,13 @@ VULKAN_HPP_DEFAULT_DISPATCH_LOADER_DYNAMIC_STORAGE
#endif
#include "renderdoc_app.h"
static RENDERDOC_API_1_1_2* g_rdoc_api = nullptr;
static struct {
RENDERDOC_API_1_1_2* api = nullptr;
bool loop = false;
int32_t frameDelay = 0;
uint32_t frameCaptureCount = 0;
} g_rdoc;
#endif
@@ -187,13 +193,13 @@ void RendererBackend::initFrameCapture() {
#ifdef _WIN32
if (HMODULE mod = GetModuleHandleA("renderdoc.dll")) {
pRENDERDOC_GetAPI RENDERDOC_GetAPI = (pRENDERDOC_GetAPI)GetProcAddress(mod, "RENDERDOC_GetAPI");
int ret = RENDERDOC_GetAPI(eRENDERDOC_API_Version_1_1_2, (void**)&g_rdoc_api);
int ret = RENDERDOC_GetAPI(eRENDERDOC_API_Version_1_1_2, (void**)&g_rdoc.api);
assert(ret == 1);
}
#elif __unix__
if (void* mod = dlopen("librenderdoc.so", RTLD_NOW | RTLD_NOLOAD)) {
pRENDERDOC_GetAPI RENDERDOC_GetAPI = (pRENDERDOC_GetAPI)dlsym(mod, "RENDERDOC_GetAPI");
int ret = RENDERDOC_GetAPI(eRENDERDOC_API_Version_1_1_2, (void**)&g_rdoc_api);
int ret = RENDERDOC_GetAPI(eRENDERDOC_API_Version_1_1_2, (void**)&g_rdoc.api);
assert(ret == 1);
}
#endif
@@ -203,22 +209,49 @@ void RendererBackend::initFrameCapture() {
void RendererBackend::startFrameCapture() {
#ifdef ENABLE_RENDERDOC_FRAME_CAPTURE
if (g_rdoc_api) {
if (!g_rdoc.api) {
return;
}
if (g_rdoc.loop) {
RENDERDOC_DevicePointer devicePtr = RENDERDOC_DEVICEPOINTER_FROM_VKINSTANCE(instance->operator VkInstance_T*());
g_rdoc_api->StartFrameCapture(devicePtr, nullptr);
g_rdoc.api->StartFrameCapture(devicePtr, nullptr);
} else {
if (g_rdoc.frameCaptureCount > 0 && g_rdoc.frameDelay == 0) {
g_rdoc.api->TriggerMultiFrameCapture(g_rdoc.frameCaptureCount);
}
--g_rdoc.frameDelay;
}
#endif
}
void RendererBackend::endFrameCapture() {
#ifdef ENABLE_RENDERDOC_FRAME_CAPTURE
if (g_rdoc_api) {
if (g_rdoc.api && g_rdoc.loop) {
RENDERDOC_DevicePointer devicePtr = RENDERDOC_DEVICEPOINTER_FROM_VKINSTANCE(instance->operator VkInstance_T*());
g_rdoc_api->EndFrameCapture(devicePtr, nullptr);
g_rdoc.api->EndFrameCapture(devicePtr, nullptr);
}
#endif
}
void RendererBackend::setFrameCaptureLoop([[maybe_unused]] bool value) {
#ifdef ENABLE_RENDERDOC_FRAME_CAPTURE
g_rdoc.loop = value;
#endif
}
void RendererBackend::triggerFrameCapture([[maybe_unused]] uint32_t frameCount, [[maybe_unused]] uint32_t frameDelay) {
#ifdef ENABLE_RENDERDOC_FRAME_CAPTURE
if (!g_rdoc.api) {
return;
}
g_rdoc.frameCaptureCount = frameCount;
g_rdoc.frameDelay = frameDelay;
#endif
}
#ifdef ENABLE_VULKAN_VALIDATION
static VKAPI_ATTR VkBool32 VKAPI_CALL vkDebugUtilsCallback(VkDebugUtilsMessageSeverityFlagBitsEXT messageSeverity,

View File

@@ -74,6 +74,22 @@ void UniformBufferArray::bindDescriptorSets(CommandEncoder& encoder) {
}
descriptorSet->update(*this, descriptorStartIndex, descriptorStorageCount, descriptorUniformCount);
const auto frameCount = encoder.getContext().getBackend().getMaxFrames();
const int32_t currentIndex = encoder.getContext().getCurrentFrameResourceIndex();
const int32_t prevIndex = currentIndex == 0 ? frameCount - 1 : currentIndex - 1;
for (uint32_t i = 0; i < descriptorStorageCount + descriptorUniformCount; ++i) {
const uint32_t index = descriptorStartIndex + i;
if (!uniformBufferVector[index]) {
continue;
}
auto& buff = static_cast<UniformBuffer*>(uniformBufferVector[index].get())->mutableBufferResource();
buff.updateVulkanBuffer(currentIndex, prevIndex);
}
descriptorSet->bind(encoder);
}

View File

@@ -85,7 +85,7 @@ static const std::unique_ptr<gfx::VertexBufferResource> noBuffer;
const gfx::UniqueVertexBufferResource& UploadPass::getBuffer(const gfx::VertexVectorBasePtr& vec,
const gfx::BufferUsageType usage,
bool forceUpdate) {
[[maybe_unused]] bool forceUpdate) {
if (vec) {
const auto* rawBufPtr = vec->getRawData();
const auto rawBufSize = vec->getRawCount() * vec->getRawSize();
@@ -96,11 +96,11 @@ const gfx::UniqueVertexBufferResource& UploadPass::getBuffer(const gfx::VertexVe
// If it's changed, update it
if (rawBufSize <= resource.getSizeInBytes()) {
if (forceUpdate || vec->isModifiedAfter(resource.getLastUpdated())) {
updateVertexBufferResource(resource, rawBufPtr, rawBufSize);
resource.setLastUpdated(vec->getLastModified());
if (vec->isModifiedAfter(resource.getLastUpdated())) {
// updateVertexBufferResource(resource, rawBufPtr, rawBufSize);
} else {
return rawData->resource;
}
return rawData->resource;
}
}
// Otherwise, create a new one
@@ -108,6 +108,10 @@ const gfx::UniqueVertexBufferResource& UploadPass::getBuffer(const gfx::VertexVe
auto buffer = std::make_unique<VertexBuffer>();
buffer->resource = createVertexBufferResource(rawBufPtr, rawBufSize, usage, /*persistent=*/false);
vec->setBuffer(std::move(buffer));
auto* rawData = static_cast<VertexBuffer*>(vec->getBuffer());
auto& resource = static_cast<VertexBufferResource&>(*rawData->resource);
resource.setLastUpdated(vec->getLastModified());
return static_cast<VertexBuffer*>(vec->getBuffer())->resource;
}
}