Files
test2/source/blender/gpu/vulkan/vk_state_manager.hh
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

253 lines
5.8 KiB
C++

/* SPDX-FileCopyrightText: 2023 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup gpu
*/
#pragma once
#include "gpu_state_private.hh"
#include "BLI_array.hh"
#include "render_graph/vk_resource_access_info.hh"
namespace blender::gpu {
class VKTexture;
class VKUniformBuffer;
class VKVertexBuffer;
class VKStorageBuffer;
class VKIndexBuffer;
class VKContext;
class VKDescriptorSetTracker;
/**
* Offset when searching for bindings.
*
* When shaders combine images and samplers, the images have to be offset to find the correct
* shader input. Both textures and images are stored in the uniform list and their ID can be
* overlapping.
*/
static constexpr int BIND_SPACE_IMAGE_OFFSET = 512;
/** Bind space for a uniform buffers. */
class BindSpaceUniformBuffers {
public:
Vector<VKUniformBuffer *> bound_resources;
void bind(VKUniformBuffer *resource, int binding)
{
if (bound_resources.size() <= binding) {
bound_resources.resize(binding + 1);
}
bound_resources[binding] = resource;
}
VKUniformBuffer *get(int binding) const
{
return bound_resources[binding];
}
void unbind(void *resource)
{
for (int index : IndexRange(bound_resources.size())) {
if (bound_resources[index] == resource) {
bound_resources[index] = nullptr;
}
}
}
void unbind_all()
{
bound_resources.clear();
}
};
/**
* Bind space for image resources.
*/
template<int Offset> class BindSpaceImages {
public:
Vector<VKTexture *> bound_resources;
void bind(VKTexture *resource, int binding)
{
if (binding >= Offset) {
binding -= Offset;
}
if (bound_resources.size() <= binding) {
bound_resources.resize(binding + 1);
}
bound_resources[binding] = resource;
}
VKTexture *get(int binding) const
{
if (binding >= Offset) {
binding -= Offset;
}
return bound_resources[binding];
}
void unbind(void *resource)
{
for (int index : IndexRange(bound_resources.size())) {
if (bound_resources[index] == resource) {
bound_resources[index] = nullptr;
}
}
}
void unbind_all()
{
bound_resources.clear();
}
};
/** Bind space for storage buffers. */
class BindSpaceStorageBuffers {
public:
enum class Type {
Unused,
UniformBuffer,
VertexBuffer,
IndexBuffer,
StorageBuffer,
};
struct Elem {
Type resource_type;
void *resource;
};
Vector<Elem> bound_resources;
void bind(Type resource_type, void *resource, int binding)
{
if (bound_resources.size() <= binding) {
bound_resources.resize(binding + 1);
}
bound_resources[binding].resource_type = resource_type;
bound_resources[binding].resource = resource;
}
const Elem &get(int binding) const
{
return bound_resources[binding];
}
void unbind(void *resource)
{
for (int index : IndexRange(bound_resources.size())) {
if (bound_resources[index].resource == resource) {
bound_resources[index].resource = nullptr;
bound_resources[index].resource_type = Type::Unused;
}
}
}
void unbind_all()
{
bound_resources.clear();
}
};
/** Bind space for textures. */
class BindSpaceTextures {
public:
enum class Type {
Unused,
Texture,
VertexBuffer,
};
struct Elem {
Type resource_type;
void *resource;
GPUSamplerState sampler;
};
Vector<Elem> bound_resources;
void bind(Type resource_type, void *resource, GPUSamplerState sampler, int binding)
{
if (bound_resources.size() <= binding) {
bound_resources.resize(binding + 1);
}
bound_resources[binding].resource_type = resource_type;
bound_resources[binding].resource = resource;
bound_resources[binding].sampler = sampler;
}
const Elem &get(int binding) const
{
return bound_resources[binding];
}
void unbind(void *resource)
{
for (int index : IndexRange(bound_resources.size())) {
if (bound_resources[index].resource == resource) {
bound_resources[index].resource = nullptr;
bound_resources[index].resource_type = Type::Unused;
bound_resources[index].sampler = GPUSamplerState::default_sampler();
}
}
}
void unbind_all()
{
bound_resources.clear();
}
};
class VKStateManager : public StateManager {
friend class VKDescriptorSetTracker;
uint texture_unpack_row_length_ = 0;
BindSpaceTextures textures_;
BindSpaceImages<BIND_SPACE_IMAGE_OFFSET> images_;
BindSpaceUniformBuffers uniform_buffers_;
BindSpaceStorageBuffers storage_buffers_;
public:
bool is_dirty = false;
void apply_state() override;
void force_state() override;
void issue_barrier(eGPUBarrier barrier_bits) override;
void texture_bind(Texture *tex, GPUSamplerState sampler, int unit) override;
void texture_unbind(Texture *tex) override;
void texture_unbind_all() override;
void image_bind(Texture *tex, int unit) override;
void image_unbind(Texture *tex) override;
void image_unbind_all() override;
void uniform_buffer_bind(VKUniformBuffer *uniform_buffer, int slot);
void uniform_buffer_unbind(VKUniformBuffer *uniform_buffer);
void uniform_buffer_unbind_all();
void texel_buffer_bind(VKVertexBuffer &vertex_buffer, int slot);
void texel_buffer_unbind(VKVertexBuffer &vertex_buffer);
void storage_buffer_bind(BindSpaceStorageBuffers::Type resource_type,
void *resource,
int binding);
void storage_buffer_unbind(void *resource);
void storage_buffer_unbind_all();
void unbind_from_all_namespaces(void *resource);
void texture_unpack_row_length_set(uint len) override;
/**
* Row length for unpacking host data when uploading texture data.
*
* When set to zero (0) host data can be assumed to be stored sequential.
*/
uint texture_unpack_row_length_get() const;
};
} // namespace blender::gpu