Files
test2/source/blender/gpu/vulkan/vk_storage_buffer.cc
Jeroen Bakker 93cdea62f6 Fix #146560: Vulkan: Improve multires out of memory issue
This seems like an out of memory issue where the storage
buffer could not be allocated but still used as a destination
for a copy. After an out of memory issue memory can be fragmented
and any allocation can still fail.

On `AMD Radeon(TM) 890M Graphics Advanced Micro Devices AMD`
`24.30.58` I got to multires subdivision level 2+6 using the steps
described in the report. 2+7 is failing for me. 2+6 already requires 16GB
of memory using large chunks.

OpenGL and Vulkan work with other limits and memory models and can
have different behavior. This PR only improves the mentioned issue, but
can still fail in other areas.

Pull Request: https://projects.blender.org/blender/blender/pulls/147713
2025-10-10 15:19:43 +02:00

162 lines
4.5 KiB
C++

/* SPDX-FileCopyrightText: 2022 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup gpu
*/
#include "vk_shader.hh"
#include "vk_shader_interface.hh"
#include "vk_staging_buffer.hh"
#include "vk_state_manager.hh"
#include "vk_vertex_buffer.hh"
#include "vk_storage_buffer.hh"
#include "CLG_log.h"
static CLG_LogRef LOG = {"gpu.vulkan"};
namespace blender::gpu {
VKStorageBuffer::VKStorageBuffer(size_t size, GPUUsageType usage, const char *name)
: StorageBuf(size, name), usage_(usage)
{
UNUSED_VARS(usage_);
}
VKStorageBuffer::~VKStorageBuffer()
{
if (async_read_buffer_) {
MEM_delete(async_read_buffer_);
async_read_buffer_ = nullptr;
}
}
void VKStorageBuffer::update(const void *data)
{
VKContext &context = *VKContext::get();
ensure_allocated();
if (!buffer_.is_allocated()) {
CLOG_WARN(&LOG,
"Unable to upload data to storage buffer as the storage buffer could not be "
"allocated on GPU.");
return;
}
if (usage_ == GPU_USAGE_STREAM) {
const VKDevice &device = VKBackend::get().device;
VKStreamingBuffer &streaming_buffer = *context.get_or_create_streaming_buffer(
buffer_, device.physical_device_properties_get().limits.minStorageBufferOffsetAlignment);
offset_ = streaming_buffer.update(context, data, usage_size_in_bytes_);
return;
}
VKStagingBuffer staging_buffer(
buffer_, VKStagingBuffer::Direction::HostToDevice, 0, usage_size_in_bytes_);
VKBuffer &buffer = staging_buffer.host_buffer_get();
if (buffer.is_allocated()) {
buffer.update_immediately(data);
staging_buffer.copy_to_device(context);
}
else {
CLOG_ERROR(
&LOG,
"Unable to upload data to storage buffer via a staging buffer as the staging buffer "
"could not be allocated. Storage buffer will be filled with on zeros to reduce "
"drawing artifacts due to read from uninitialized memory.");
buffer_.clear(context, 0u);
}
}
void VKStorageBuffer::ensure_allocated()
{
if (!buffer_.is_allocated()) {
allocate();
}
}
void VKStorageBuffer::allocate()
{
const VkBufferUsageFlags buffer_usage_flags = VK_BUFFER_USAGE_INDIRECT_BUFFER_BIT |
VK_BUFFER_USAGE_STORAGE_BUFFER_BIT |
VK_BUFFER_USAGE_TRANSFER_SRC_BIT |
VK_BUFFER_USAGE_TRANSFER_DST_BIT;
buffer_.create(size_in_bytes_,
buffer_usage_flags,
VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT,
VkMemoryPropertyFlags(0),
VmaAllocationCreateFlags(0),
0.8f);
if (buffer_.is_allocated()) {
debug::object_label(buffer_.vk_handle(), name_);
}
}
void VKStorageBuffer::bind(int slot)
{
VKContext &context = *VKContext::get();
context.state_manager_get().storage_buffer_bind(
BindSpaceStorageBuffers::Type::StorageBuffer, this, slot, offset_);
}
void VKStorageBuffer::unbind()
{
VKContext *context = VKContext::get();
if (context) {
context->state_manager_get().storage_buffer_unbind(this);
}
}
void VKStorageBuffer::clear(uint32_t clear_value)
{
ensure_allocated();
VKContext &context = *VKContext::get();
buffer_.clear(context, clear_value);
}
void VKStorageBuffer::copy_sub(VertBuf *src, uint dst_offset, uint src_offset, uint copy_size)
{
ensure_allocated();
VKVertexBuffer &src_vertex_buffer = *unwrap(src);
src_vertex_buffer.upload();
render_graph::VKCopyBufferNode::CreateInfo copy_buffer = {};
copy_buffer.src_buffer = src_vertex_buffer.vk_handle();
copy_buffer.dst_buffer = vk_handle();
copy_buffer.region.srcOffset = src_offset;
copy_buffer.region.dstOffset = dst_offset;
copy_buffer.region.size = copy_size;
VKContext &context = *VKContext::get();
context.render_graph().add_node(copy_buffer);
}
void VKStorageBuffer::async_flush_to_host()
{
if (async_read_buffer_ != nullptr) {
return;
}
ensure_allocated();
VKContext &context = *VKContext::get();
async_read_buffer_ = MEM_new<VKStagingBuffer>(
__func__, buffer_, VKStagingBuffer::Direction::DeviceToHost);
async_read_buffer_->copy_from_device(context);
async_read_buffer_->host_buffer_get().async_flush_to_host(context);
}
void VKStorageBuffer::read(void *data)
{
if (async_read_buffer_ == nullptr) {
async_flush_to_host();
}
VKContext &context = *VKContext::get();
async_read_buffer_->host_buffer_get().read_async(context, data);
MEM_delete(async_read_buffer_);
async_read_buffer_ = nullptr;
}
} // namespace blender::gpu