Listing the "Blender Foundation" as copyright holder implied the Blender Foundation holds copyright to files which may include work from many developers. While keeping copyright on headers makes sense for isolated libraries, Blender's own code may be refactored or moved between files in a way that makes the per file copyright holders less meaningful. Copyright references to the "Blender Foundation" have been replaced with "Blender Authors", with the exception of `./extern/` since these this contains libraries which are more isolated, any changed to license headers there can be handled on a case-by-case basis. Some directories in `./intern/` have also been excluded: - `./intern/cycles/` it's own `AUTHORS` file is planned. - `./intern/opensubdiv/`. An "AUTHORS" file has been added, using the chromium projects authors file as a template. Design task: #110784 Ref !110783.
266 lines
8.1 KiB
C++
266 lines
8.1 KiB
C++
/* SPDX-FileCopyrightText: 2023 Blender Authors
|
|
*
|
|
* SPDX-License-Identifier: GPL-2.0-or-later */
|
|
|
|
/** \file
|
|
* \ingroup gpu
|
|
*
|
|
* Push constants is a way to quickly provide a small amount of uniform data to shaders. It should
|
|
* be much quicker than UBOs but a huge limitation is the size of data - spec requires 128 bytes to
|
|
* be available for a push constant range. Hardware vendors may support more, but compared to other
|
|
* means it is still very little (for example 256 bytes).
|
|
*
|
|
* Due to this size requirements we try to use push constants when it fits on the device. If it
|
|
* doesn't fit we fallback to use an uniform buffer.
|
|
*
|
|
* Shader developers are responsible to fine-tune the performance of the shader. One way to do this
|
|
* is to tailor what will be sent as a push constant to keep the push constants within the limits.
|
|
*/
|
|
|
|
#pragma once
|
|
|
|
#include "BLI_utility_mixins.hh"
|
|
#include "BLI_vector.hh"
|
|
|
|
#include "gpu_shader_create_info.hh"
|
|
|
|
#include "vk_common.hh"
|
|
#include "vk_descriptor_set.hh"
|
|
|
|
namespace blender::gpu {
|
|
class VKShaderInterface;
|
|
class VKUniformBuffer;
|
|
class VKContext;
|
|
class VKDevice;
|
|
|
|
/**
|
|
* Container to store push constants in a buffer.
|
|
*
|
|
* Can handle buffers with different memory layouts (std140/std430)
|
|
* Which memory layout is used is based on the storage type.
|
|
*
|
|
* VKPushConstantsLayout only describes the buffer, an instance of this
|
|
* class can handle setting/modifying/duplicating push constants.
|
|
*
|
|
* It should also keep track of the submissions in order to reuse the allocated
|
|
* data.
|
|
*/
|
|
class VKPushConstants : VKResourceTracker<VKUniformBuffer> {
|
|
public:
|
|
/** Different methods to store push constants. */
|
|
enum class StorageType {
|
|
/** Push constants aren't in use. */
|
|
NONE,
|
|
|
|
/** Store push constants as regular vulkan push constants. */
|
|
PUSH_CONSTANTS,
|
|
|
|
/**
|
|
* Fallback when push constants doesn't meet the device requirements.
|
|
*/
|
|
UNIFORM_BUFFER,
|
|
};
|
|
|
|
/**
|
|
* Describe the layout of the push constants and the storage type that should be used.
|
|
*/
|
|
struct Layout {
|
|
static constexpr StorageType STORAGE_TYPE_DEFAULT = StorageType::PUSH_CONSTANTS;
|
|
static constexpr StorageType STORAGE_TYPE_FALLBACK = StorageType::UNIFORM_BUFFER;
|
|
|
|
struct PushConstant {
|
|
/* Used as lookup based on ShaderInput. */
|
|
int32_t location;
|
|
|
|
/** Offset in the push constant data (in bytes). */
|
|
uint32_t offset;
|
|
shader::Type type;
|
|
int array_size;
|
|
};
|
|
|
|
private:
|
|
Vector<PushConstant> push_constants;
|
|
uint32_t size_in_bytes_ = 0;
|
|
StorageType storage_type_ = StorageType::NONE;
|
|
/**
|
|
* Binding index in the descriptor set when the push constants use an uniform buffer.
|
|
*/
|
|
VKDescriptorSet::Location descriptor_set_location_;
|
|
|
|
public:
|
|
/**
|
|
* Return the desired storage type that can fit the push constants of the given shader create
|
|
* info, matching the limits of the given device.
|
|
*
|
|
* Returns:
|
|
* - StorageType::NONE: No push constants are needed.
|
|
* - StorageType::PUSH_CONSTANTS: Regular vulkan push constants can be used.
|
|
* - StorageType::UNIFORM_BUFFER: The push constants don't fit in the limits of the given
|
|
* device. A uniform buffer should be used as a fallback method.
|
|
*/
|
|
static StorageType determine_storage_type(const shader::ShaderCreateInfo &info,
|
|
const VKDevice &device);
|
|
|
|
/**
|
|
* Initialize the push constants of the given shader create info with the
|
|
* binding location.
|
|
*
|
|
* interface: Uniform locations of the interface are used as lookup key.
|
|
* storage_type: The type of storage for push constants to use.
|
|
* location: When storage_type=StorageType::UNIFORM_BUFFER this contains
|
|
* the location in the descriptor set where the uniform buffer can be
|
|
* bound.
|
|
*/
|
|
void init(const shader::ShaderCreateInfo &info,
|
|
const VKShaderInterface &interface,
|
|
StorageType storage_type,
|
|
VKDescriptorSet::Location location);
|
|
|
|
/**
|
|
* Return the storage type that is used.
|
|
*/
|
|
StorageType storage_type_get() const
|
|
{
|
|
return storage_type_;
|
|
}
|
|
|
|
/**
|
|
* Get the binding location for the uniform buffer.
|
|
*
|
|
* Only valid when storage_type=StorageType::UNIFORM_BUFFER.
|
|
*/
|
|
VKDescriptorSet::Location descriptor_set_location_get() const
|
|
{
|
|
return descriptor_set_location_;
|
|
}
|
|
|
|
/**
|
|
* Get the size needed to store the push constants.
|
|
*/
|
|
uint32_t size_in_bytes() const
|
|
{
|
|
return size_in_bytes_;
|
|
}
|
|
|
|
/**
|
|
* Find the push constant layout for the given location.
|
|
* Location = ShaderInput.location.
|
|
*/
|
|
const PushConstant *find(int32_t location) const;
|
|
|
|
void debug_print() const;
|
|
};
|
|
|
|
private:
|
|
const Layout *layout_ = nullptr;
|
|
void *data_ = nullptr;
|
|
bool is_dirty_ = false;
|
|
|
|
public:
|
|
VKPushConstants();
|
|
VKPushConstants(const Layout *layout);
|
|
VKPushConstants(VKPushConstants &&other);
|
|
virtual ~VKPushConstants();
|
|
|
|
VKPushConstants &operator=(VKPushConstants &&other);
|
|
|
|
size_t offset() const
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
const Layout &layout_get() const
|
|
{
|
|
return *layout_;
|
|
}
|
|
|
|
/**
|
|
* Part of Resource Tracking API is called when new resource is needed.
|
|
*/
|
|
std::unique_ptr<VKUniformBuffer> create_resource(VKContext &context) override;
|
|
|
|
/**
|
|
* Get the reference to the active data.
|
|
*
|
|
* Data can get inactive when push constants are modified, after being added to the command
|
|
* queue. We still keep track of the old data for reuse and make sure we don't overwrite data
|
|
* that is still not on the GPU.
|
|
*/
|
|
const void *data() const
|
|
{
|
|
return data_;
|
|
}
|
|
|
|
/**
|
|
* Modify a push constant.
|
|
*
|
|
* location: ShaderInput.location of the push constant to update.
|
|
* comp_len: number of components has the data type that is being updated.
|
|
* array_size: number of elements when an array to update. (0=no array)
|
|
* input_data: packed source data to use.
|
|
*/
|
|
template<typename T>
|
|
void push_constant_set(int32_t location,
|
|
int32_t comp_len,
|
|
int32_t array_size,
|
|
const T *input_data)
|
|
{
|
|
const Layout::PushConstant *push_constant_layout = layout_->find(location);
|
|
if (push_constant_layout == nullptr) {
|
|
/* Legacy code can still try to update push constants when they don't exist. For example
|
|
* `immDrawPixelsTexSetup` will bind an image slot manually. This works in OpenGL, but in
|
|
* vulkan images aren't stored as push constants. */
|
|
return;
|
|
}
|
|
|
|
uint8_t *bytes = static_cast<uint8_t *>(data_);
|
|
T *dst = static_cast<T *>(static_cast<void *>(&bytes[push_constant_layout->offset]));
|
|
const bool is_tightly_std140_packed = (comp_len % 4) == 0;
|
|
if (layout_->storage_type_get() == StorageType::PUSH_CONSTANTS || array_size == 0 ||
|
|
push_constant_layout->array_size == 0 || is_tightly_std140_packed)
|
|
{
|
|
const size_t copy_size_in_bytes = comp_len * max_ii(array_size, 1) * sizeof(T);
|
|
BLI_assert_msg(push_constant_layout->offset + copy_size_in_bytes <= layout_->size_in_bytes(),
|
|
"Tried to write outside the push constant allocated memory.");
|
|
memcpy(dst, input_data, copy_size_in_bytes);
|
|
is_dirty_ = true;
|
|
return;
|
|
}
|
|
|
|
/* Store elements in uniform buffer as array. In Std140 arrays have an element stride of 16
|
|
* bytes. */
|
|
BLI_assert(sizeof(T) == 4);
|
|
const T *src = input_data;
|
|
for (const int i : IndexRange(array_size)) {
|
|
UNUSED_VARS(i);
|
|
memcpy(dst, src, comp_len * sizeof(T));
|
|
src += comp_len;
|
|
dst += 4;
|
|
}
|
|
is_dirty_ = true;
|
|
}
|
|
|
|
/**
|
|
* Update the GPU resources with the latest push constants.
|
|
*/
|
|
void update(VKContext &context);
|
|
|
|
private:
|
|
/**
|
|
* When storage type = StorageType::UNIFORM_BUFFER use this method to update the uniform
|
|
* buffer.
|
|
*
|
|
* It must be called just before adding a draw/compute command to the command queue.
|
|
*/
|
|
void update_uniform_buffer();
|
|
|
|
/**
|
|
* Get a reference to the uniform buffer.
|
|
*
|
|
* Only valid when storage type = StorageType::UNIFORM_BUFFER.
|
|
*/
|
|
std::unique_ptr<VKUniformBuffer> &uniform_buffer_get();
|
|
};
|
|
|
|
} // namespace blender::gpu
|