This PR hooks up the vulkan backend with the render graph for drawing. It can run Blender better than the previous implementation so we also flipped it to be the default implementation. **Some highlights** - Adds support for framebuffer load/store operations - Adds support for framebuffer subpass transitions - Fixes workbench shadows - Performance is just below OpenGL performance when comparing fps. But the screen feels more fluent when using complex scenes. - Current performance is without doing any optimizations so will improve in the future. - EEVEE will not crash but has artifacts and many parts that require more work. **Related to** - #121648 - #118330 **Known Limitation** - Similar to previous implementation resources can be freed when still in use crashing Blender. This is typically the case when playing back an animation or updating a material icon. **Next steps** - Remove old implementation - Get EEVEE to work - Fix double resource freeing - Improve performance by identifying hotspots and change them Pull Request: https://projects.blender.org/blender/blender/pulls/121787
428 lines
13 KiB
C++
428 lines
13 KiB
C++
/* SPDX-FileCopyrightText: 2023 Blender Authors All rights reserved.
|
|
*
|
|
* SPDX-License-Identifier: GPL-2.0-or-later */
|
|
|
|
#include "vk_vertex_attribute_object.hh"
|
|
|
|
#include "vk_batch.hh"
|
|
#include "vk_context.hh"
|
|
#include "vk_immediate.hh"
|
|
#include "vk_shader.hh"
|
|
#include "vk_shader_interface.hh"
|
|
#include "vk_vertex_buffer.hh"
|
|
|
|
#include "BLI_array.hh"
|
|
#include "BLI_math_vector_types.hh"
|
|
|
|
namespace blender::gpu {
|
|
VKVertexAttributeObject::VKVertexAttributeObject()
|
|
{
|
|
clear();
|
|
}
|
|
|
|
void VKVertexAttributeObject::clear()
|
|
{
|
|
is_valid = false;
|
|
info.pNext = nullptr;
|
|
bindings.clear();
|
|
attributes.clear();
|
|
vbos.clear();
|
|
buffers.clear();
|
|
}
|
|
|
|
VKVertexAttributeObject &VKVertexAttributeObject::operator=(const VKVertexAttributeObject &other)
|
|
{
|
|
if (this == &other) {
|
|
return *this;
|
|
}
|
|
|
|
is_valid = other.is_valid;
|
|
info = other.info;
|
|
bindings.clear();
|
|
bindings.extend(other.bindings);
|
|
attributes.clear();
|
|
attributes.extend(other.attributes);
|
|
vbos.clear();
|
|
vbos.extend(other.vbos);
|
|
buffers.clear();
|
|
buffers.extend(other.buffers);
|
|
return *this;
|
|
}
|
|
|
|
/* -------------------------------------------------------------------- */
|
|
/** \name Bind resources
|
|
* \{ */
|
|
|
|
void VKVertexAttributeObject::bind(
|
|
render_graph::VKVertexBufferBindings &r_vertex_buffer_bindings) const
|
|
{
|
|
/* TODO: VBOs and Buffers can share the same setup and can also split the buffers from the
|
|
* offsets so we don't need to loop. */
|
|
const bool use_vbos = !vbos.is_empty();
|
|
Array<bool> visited_bindings(bindings.size());
|
|
visited_bindings.fill(false);
|
|
|
|
const VKBuffer &dummy = VKBackend::get().device_get().dummy_buffer_get();
|
|
for (VkVertexInputAttributeDescription attribute : attributes) {
|
|
if (visited_bindings[attribute.binding]) {
|
|
continue;
|
|
}
|
|
visited_bindings[attribute.binding] = true;
|
|
|
|
VkBuffer buffer = dummy.vk_handle();
|
|
VkDeviceSize offset = 0;
|
|
|
|
if (use_vbos && attribute.binding < vbos.size()) {
|
|
BLI_assert(vbos[attribute.binding]);
|
|
buffer = vbos[attribute.binding]->vk_handle();
|
|
}
|
|
else if (!use_vbos && attribute.binding < buffers.size()) {
|
|
buffer = buffers[attribute.binding].buffer.vk_handle();
|
|
offset = buffers[attribute.binding].offset;
|
|
}
|
|
|
|
r_vertex_buffer_bindings.buffer[attribute.binding] = buffer;
|
|
r_vertex_buffer_bindings.offset[attribute.binding] = offset;
|
|
r_vertex_buffer_bindings.buffer_count = max_ii(r_vertex_buffer_bindings.buffer_count,
|
|
attribute.binding + 1);
|
|
}
|
|
}
|
|
|
|
void VKVertexAttributeObject::bind(VKContext &context)
|
|
{
|
|
const bool use_vbos = !vbos.is_empty();
|
|
if (use_vbos) {
|
|
bind_vbos(context);
|
|
}
|
|
else {
|
|
bind_buffers(context);
|
|
}
|
|
}
|
|
|
|
void VKVertexAttributeObject::bind_vbos(VKContext &context)
|
|
{
|
|
/* Bind VBOS from batches. */
|
|
Array<bool> visited_bindings(bindings.size());
|
|
visited_bindings.fill(false);
|
|
|
|
for (VkVertexInputAttributeDescription attribute : attributes) {
|
|
if (visited_bindings[attribute.binding]) {
|
|
continue;
|
|
}
|
|
visited_bindings[attribute.binding] = true;
|
|
|
|
if (attribute.binding < vbos.size()) {
|
|
BLI_assert(vbos[attribute.binding]);
|
|
VKVertexBuffer &vbo = *vbos[attribute.binding];
|
|
vbo.upload();
|
|
context.command_buffers_get().bind(attribute.binding, vbo, 0);
|
|
}
|
|
else {
|
|
const VKBuffer &buffer = VKBackend::get().device_get().dummy_buffer_get();
|
|
const VKBufferWithOffset buffer_with_offset = {buffer, 0};
|
|
context.command_buffers_get().bind(attribute.binding, buffer_with_offset);
|
|
}
|
|
}
|
|
}
|
|
|
|
void VKVertexAttributeObject::bind_buffers(VKContext &context)
|
|
{
|
|
/* Bind dynamic buffers from immediate mode. */
|
|
Array<bool> visited_bindings(bindings.size());
|
|
visited_bindings.fill(false);
|
|
|
|
for (VkVertexInputAttributeDescription attribute : attributes) {
|
|
if (visited_bindings[attribute.binding]) {
|
|
continue;
|
|
}
|
|
visited_bindings[attribute.binding] = true;
|
|
|
|
if (attribute.binding < buffers.size()) {
|
|
VKBufferWithOffset &buffer = buffers[attribute.binding];
|
|
context.command_buffers_get().bind(attribute.binding, buffer);
|
|
}
|
|
else {
|
|
const VKBuffer &buffer = VKBackend::get().device_get().dummy_buffer_get();
|
|
const VKBufferWithOffset buffer_with_offset = {buffer, 0};
|
|
context.command_buffers_get().bind(attribute.binding, buffer_with_offset);
|
|
}
|
|
}
|
|
}
|
|
|
|
void VKVertexAttributeObject::ensure_vbos_uploaded() const
|
|
{
|
|
for (VKVertexBuffer *vbo : vbos) {
|
|
if (vbo) {
|
|
vbo->upload();
|
|
}
|
|
}
|
|
}
|
|
|
|
/** \} */
|
|
|
|
/* -------------------------------------------------------------------- */
|
|
/** \name Update bindings
|
|
* \{ */
|
|
|
|
void VKVertexAttributeObject::update_bindings(const VKContext &context, VKBatch &batch)
|
|
{
|
|
clear();
|
|
const VKShaderInterface &interface = unwrap(context.shader)->interface_get();
|
|
AttributeMask occupied_attributes = 0;
|
|
|
|
for (int v = 0; v < GPU_BATCH_INST_VBO_MAX_LEN; v++) {
|
|
VKVertexBuffer *vbo = batch.instance_buffer_get(v);
|
|
if (vbo) {
|
|
vbo->device_format_ensure();
|
|
update_bindings(vbo->device_format_get(),
|
|
vbo,
|
|
nullptr,
|
|
vbo->vertex_len,
|
|
interface,
|
|
occupied_attributes,
|
|
true);
|
|
}
|
|
}
|
|
for (int v = 0; v < GPU_BATCH_VBO_MAX_LEN; v++) {
|
|
VKVertexBuffer *vbo = batch.vertex_buffer_get(v);
|
|
if (vbo) {
|
|
vbo->device_format_ensure();
|
|
update_bindings(vbo->device_format_get(),
|
|
vbo,
|
|
nullptr,
|
|
vbo->vertex_len,
|
|
interface,
|
|
occupied_attributes,
|
|
false);
|
|
}
|
|
}
|
|
|
|
if (occupied_attributes != interface.enabled_attr_mask_) {
|
|
fill_unused_bindings(interface, occupied_attributes);
|
|
}
|
|
is_valid = true;
|
|
}
|
|
|
|
/* Determine the number of binding location the given attribute uses. */
|
|
static uint32_t to_binding_location_len(const GPUVertAttr &attribute)
|
|
{
|
|
return ceil_division(attribute.comp_len, 4u);
|
|
}
|
|
|
|
/* Determine the number of binding location the given type uses. */
|
|
static uint32_t to_binding_location_len(const shader::Type type)
|
|
{
|
|
switch (type) {
|
|
case shader::Type::FLOAT:
|
|
case shader::Type::VEC2:
|
|
case shader::Type::VEC3:
|
|
case shader::Type::VEC4:
|
|
case shader::Type::UINT:
|
|
case shader::Type::UVEC2:
|
|
case shader::Type::UVEC3:
|
|
case shader::Type::UVEC4:
|
|
case shader::Type::INT:
|
|
case shader::Type::IVEC2:
|
|
case shader::Type::IVEC3:
|
|
case shader::Type::IVEC4:
|
|
case shader::Type::BOOL:
|
|
case shader::Type::VEC3_101010I2:
|
|
case shader::Type::UCHAR:
|
|
case shader::Type::UCHAR2:
|
|
case shader::Type::UCHAR3:
|
|
case shader::Type::UCHAR4:
|
|
case shader::Type::CHAR:
|
|
case shader::Type::CHAR2:
|
|
case shader::Type::CHAR3:
|
|
case shader::Type::CHAR4:
|
|
case shader::Type::SHORT:
|
|
case shader::Type::SHORT2:
|
|
case shader::Type::SHORT3:
|
|
case shader::Type::SHORT4:
|
|
case shader::Type::USHORT:
|
|
case shader::Type::USHORT2:
|
|
case shader::Type::USHORT3:
|
|
case shader::Type::USHORT4:
|
|
return 1;
|
|
case shader::Type::MAT3:
|
|
return 3;
|
|
case shader::Type::MAT4:
|
|
return 4;
|
|
}
|
|
|
|
return 1;
|
|
}
|
|
|
|
void VKVertexAttributeObject::fill_unused_bindings(const VKShaderInterface &interface,
|
|
const AttributeMask occupied_attributes)
|
|
{
|
|
for (int location : IndexRange(16)) {
|
|
AttributeMask location_mask = 1 << location;
|
|
/* Skip occupied slots */
|
|
if (occupied_attributes & location_mask) {
|
|
continue;
|
|
}
|
|
/* Skip slots that are not used by the vertex shader. */
|
|
if ((interface.enabled_attr_mask_ & location_mask) == 0) {
|
|
continue;
|
|
}
|
|
|
|
/* Use dummy binding. */
|
|
shader::Type attribute_type = interface.get_attribute_type(location);
|
|
const uint32_t num_locations = to_binding_location_len(attribute_type);
|
|
for (const uint32_t location_offset : IndexRange(num_locations)) {
|
|
const uint32_t binding = bindings.size();
|
|
VkVertexInputAttributeDescription attribute_description = {};
|
|
attribute_description.binding = binding;
|
|
attribute_description.location = location + location_offset;
|
|
attribute_description.offset = 0;
|
|
attribute_description.format = to_vk_format(attribute_type);
|
|
attributes.append(attribute_description);
|
|
|
|
VkVertexInputBindingDescription vk_binding_descriptor = {};
|
|
vk_binding_descriptor.binding = binding;
|
|
vk_binding_descriptor.stride = 0;
|
|
vk_binding_descriptor.inputRate = VK_VERTEX_INPUT_RATE_INSTANCE;
|
|
bindings.append(vk_binding_descriptor);
|
|
}
|
|
}
|
|
}
|
|
|
|
void VKVertexAttributeObject::update_bindings(VKImmediate &immediate)
|
|
{
|
|
clear();
|
|
const VKShaderInterface &interface = unwrap(unwrap(immediate.shader))->interface_get();
|
|
AttributeMask occupied_attributes = 0;
|
|
|
|
VKBufferWithOffset immediate_buffer = {*immediate.active_resource(),
|
|
immediate.subbuffer_offset_get()};
|
|
|
|
update_bindings(immediate.vertex_format_converter.device_format_get(),
|
|
nullptr,
|
|
&immediate_buffer,
|
|
immediate.vertex_len,
|
|
interface,
|
|
occupied_attributes,
|
|
false);
|
|
is_valid = true;
|
|
BLI_assert(interface.enabled_attr_mask_ == occupied_attributes);
|
|
}
|
|
|
|
void VKVertexAttributeObject::update_bindings(const GPUVertFormat &vertex_format,
|
|
VKVertexBuffer *vertex_buffer,
|
|
VKBufferWithOffset *immediate_vertex_buffer,
|
|
const int64_t vertex_len,
|
|
const VKShaderInterface &interface,
|
|
AttributeMask &r_occupied_attributes,
|
|
const bool use_instancing)
|
|
{
|
|
BLI_assert(vertex_buffer || immediate_vertex_buffer);
|
|
BLI_assert(!(vertex_buffer && immediate_vertex_buffer));
|
|
|
|
if (vertex_format.attr_len <= 0) {
|
|
return;
|
|
}
|
|
|
|
uint32_t offset = 0;
|
|
uint32_t stride = vertex_format.stride;
|
|
|
|
for (uint32_t attribute_index = 0; attribute_index < vertex_format.attr_len; attribute_index++) {
|
|
const GPUVertAttr &attribute = vertex_format.attrs[attribute_index];
|
|
if (vertex_format.deinterleaved) {
|
|
offset += ((attribute_index == 0) ? 0 : vertex_format.attrs[attribute_index - 1].size) *
|
|
vertex_len;
|
|
stride = attribute.size;
|
|
}
|
|
else {
|
|
offset = attribute.offset;
|
|
}
|
|
|
|
for (uint32_t name_index = 0; name_index < attribute.name_len; name_index++) {
|
|
const char *name = GPU_vertformat_attr_name_get(&vertex_format, &attribute, name_index);
|
|
const ShaderInput *shader_input = interface.attr_get(name);
|
|
if (shader_input == nullptr || shader_input->location == -1) {
|
|
continue;
|
|
}
|
|
|
|
/* Don't overwrite attributes that are already occupied. */
|
|
AttributeMask attribute_mask = 1 << shader_input->location;
|
|
if (r_occupied_attributes & attribute_mask) {
|
|
continue;
|
|
}
|
|
r_occupied_attributes |= attribute_mask;
|
|
const uint32_t num_locations = to_binding_location_len(attribute);
|
|
for (const uint32_t location_offset : IndexRange(num_locations)) {
|
|
const uint32_t binding = bindings.size();
|
|
VkVertexInputAttributeDescription attribute_description = {};
|
|
attribute_description.binding = binding;
|
|
attribute_description.location = shader_input->location + location_offset;
|
|
attribute_description.offset = offset + location_offset * sizeof(float4);
|
|
attribute_description.format = to_vk_format(
|
|
static_cast<GPUVertCompType>(attribute.comp_type),
|
|
attribute.size,
|
|
static_cast<GPUVertFetchMode>(attribute.fetch_mode));
|
|
attributes.append(attribute_description);
|
|
|
|
VkVertexInputBindingDescription vk_binding_descriptor = {};
|
|
vk_binding_descriptor.binding = binding;
|
|
vk_binding_descriptor.stride = stride;
|
|
vk_binding_descriptor.inputRate = use_instancing ? VK_VERTEX_INPUT_RATE_INSTANCE :
|
|
VK_VERTEX_INPUT_RATE_VERTEX;
|
|
bindings.append(vk_binding_descriptor);
|
|
if (vertex_buffer) {
|
|
vbos.append(vertex_buffer);
|
|
}
|
|
if (immediate_vertex_buffer) {
|
|
buffers.append(*immediate_vertex_buffer);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/** \} */
|
|
|
|
/* -------------------------------------------------------------------- */
|
|
/** \name Debugging
|
|
* \{ */
|
|
|
|
void VKVertexAttributeObject::debug_print() const
|
|
{
|
|
std::cout << __FILE__ << "::" << __func__ << "\n";
|
|
Array<bool> visited_bindings(bindings.size());
|
|
visited_bindings.fill(false);
|
|
|
|
for (VkVertexInputAttributeDescription attribute : attributes) {
|
|
std::cout << " - attribute(binding=" << attribute.binding
|
|
<< ", location=" << attribute.location << ")";
|
|
|
|
if (visited_bindings[attribute.binding]) {
|
|
std::cout << " WARNING: Already bound\n";
|
|
continue;
|
|
}
|
|
visited_bindings[attribute.binding] = true;
|
|
|
|
/* Bind VBOS from batches. */
|
|
if (!vbos.is_empty()) {
|
|
if (attribute.binding < vbos.size()) {
|
|
std::cout << " Attach to VBO [" << vbos[attribute.binding] << "]\n";
|
|
}
|
|
else {
|
|
std::cout << " WARNING: Attach to dummy\n";
|
|
}
|
|
}
|
|
else if (!buffers.is_empty()) {
|
|
if (attribute.binding < vbos.size()) {
|
|
std::cout << " Attach to ImmediateModeVBO\n";
|
|
}
|
|
else {
|
|
std::cout << " WARNING: Attach to dummy\n";
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/** \} */
|
|
|
|
} // namespace blender::gpu
|