Files
test2/source/blender/gpu/vulkan/vk_state_manager.cc
Jeroen Bakker d75cf2efd4 Vulkan: Refactor resource binding
Resource binding was over-complicated as I didn't understood the state
manager and vulkan to make the correct decisions at that time. This
refactor will remove a lot of the complexity and improves the performance.

**Performance**

The performance improvement is noticeable in complex grease pencil
scenes.

Grease pencil benchmark file picknick:
- `NVIDIA Quadro RTX 6000` 17 fps -> 24 fps
- `Intel(R) Arc(tm) A750 Graphics (DG2)` 6 -> 21 fps

**Bottle-neck**

The performance improvements originates from moving the update entry
point from state manager to shader interface. The previous implementation
(state manager) had to loop over all the bound resources and find in the
shader interface where it was located in the descriptor set. Ignoring
resources that were not used by the shader. But also making it hard
to determine if descriptor sets actually changed. Previous implementation
assumed descriptor sets always changed.

When descriptor set changed a new descriptor set needed to be allocated.
Most drivers this is a fast operation, but on Intel/Mesa this was measurable
slow. Using an allocation pool doesn't fit the Vulkan API as you are only
able to reuse when the layout matches exactly. Of course doable, but requires
another structure to keep track of the actual layouts.

**Solution**

By using the shader interface as entry point we can:
1. Keep track if there are any changes in the state manager. If not and the
   layout is the same, the previous shader can be reused.
2. In stead of looping over each bound resource, we loop over bind points.

**Future extensions**

Bundle all descriptor set uploads just before use. This would be more
in line with how 'modern' Vulkan should be implemented. This PR already
separates the uploading from the updating and technically allows to upload
more than one descriptor set.

Instead of looking 1 set back we should measure if we can handle multiple
or keep track of the different layouts resources to improve the performance
even further.

Optional use `VK_KHR_descriptor_buffer` when available.

Pull Request: https://projects.blender.org/blender/blender/pulls/128068
2024-09-26 10:59:45 +02:00

167 lines
4.3 KiB
C++

/* SPDX-FileCopyrightText: 2023 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup gpu
*/
#include "vk_state_manager.hh"
#include "vk_context.hh"
#include "vk_index_buffer.hh"
#include "vk_shader.hh"
#include "vk_storage_buffer.hh"
#include "vk_texture.hh"
#include "vk_vertex_buffer.hh"
#include "GPU_capabilities.hh"
namespace blender::gpu {
void VKStateManager::apply_state()
{
/* Intentionally empty. State is polled during pipeline creation and doesn't need to be applied.
* If this leads to issues we should have an active state. */
}
void VKStateManager::force_state()
{
/* Intentionally empty. State is polled during pipeline creation and is always forced. */
}
void VKStateManager::issue_barrier(eGPUBarrier barrier_bits)
{
/**
* Workaround for EEVEE ThicknessFromShadow shader.
*
* EEVEE light evaluation uses layered sub-pass tracking. Currently, the tracking supports
* transitioning a layer to a different layout once per rendering scope. When using the thickness
* from shadow, the layers need to be transitioned twice: once to image load/store for the
* thickness from shadow shader and then to a sampler for the light evaluation shader. We work
* around this limitation by suspending the rendering.
*
* The reason we need to suspend the rendering is that Vulkan, by default, doesn't support layout
* transitions between the begin and end of rendering. By suspending the render, the graph will
* create a new node group that allows the necessary image layout transition.
*
* This limitation could also be addressed in the render graph scheduler, but that would be quite
* a hassle to track and might not be worth the effort.
*/
if (bool(barrier_bits & GPU_BARRIER_SHADER_IMAGE_ACCESS)) {
VKContext &context = *VKContext::get();
context.rendering_end();
}
}
void VKStateManager::texture_bind(Texture *texture, GPUSamplerState sampler, int binding)
{
textures_.bind(BindSpaceTextures::Type::Texture, texture, sampler, binding);
is_dirty = true;
}
void VKStateManager::texture_unbind(Texture *texture)
{
textures_.unbind(texture);
is_dirty = true;
}
void VKStateManager::texture_unbind_all()
{
textures_.unbind_all();
is_dirty = true;
}
void VKStateManager::image_bind(Texture *tex, int binding)
{
VKTexture *texture = unwrap(tex);
images_.bind(texture, binding);
is_dirty = true;
}
void VKStateManager::image_unbind(Texture *tex)
{
VKTexture *texture = unwrap(tex);
images_.unbind(texture);
is_dirty = true;
}
void VKStateManager::image_unbind_all()
{
images_.unbind_all();
is_dirty = true;
}
void VKStateManager::uniform_buffer_bind(VKUniformBuffer *uniform_buffer, int binding)
{
uniform_buffers_.bind(uniform_buffer, binding);
is_dirty = true;
}
void VKStateManager::uniform_buffer_unbind(VKUniformBuffer *uniform_buffer)
{
uniform_buffers_.unbind(uniform_buffer);
is_dirty = true;
}
void VKStateManager::uniform_buffer_unbind_all()
{
uniform_buffers_.unbind_all();
is_dirty = true;
}
void VKStateManager::unbind_from_all_namespaces(void *resource)
{
uniform_buffers_.unbind(resource);
storage_buffers_.unbind(resource);
images_.unbind(resource);
textures_.unbind(resource);
is_dirty = true;
}
void VKStateManager::texel_buffer_bind(VKVertexBuffer &vertex_buffer, int binding)
{
textures_.bind(BindSpaceTextures::Type::VertexBuffer,
&vertex_buffer,
GPUSamplerState::default_sampler(),
binding);
is_dirty = true;
}
void VKStateManager::texel_buffer_unbind(VKVertexBuffer &vertex_buffer)
{
textures_.unbind(&vertex_buffer);
is_dirty = true;
}
void VKStateManager::storage_buffer_bind(BindSpaceStorageBuffers::Type resource_type,
void *resource,
int binding)
{
storage_buffers_.bind(resource_type, resource, binding);
is_dirty = true;
}
void VKStateManager::storage_buffer_unbind(void *resource)
{
storage_buffers_.unbind(resource);
is_dirty = true;
}
void VKStateManager::storage_buffer_unbind_all()
{
storage_buffers_.unbind_all();
is_dirty = true;
}
void VKStateManager::texture_unpack_row_length_set(uint len)
{
texture_unpack_row_length_ = len;
}
uint VKStateManager::texture_unpack_row_length_get() const
{
return texture_unpack_row_length_;
}
} // namespace blender::gpu