Files
test/source/blender/draw/intern/draw_curves.cc
Miguel Pozo a8975bf651 Fix #142097: Vulkan: Crash with unbound textures
On the Vulkan side, ensure that unbound textures don't result in
accessing uninitialized or out of bounds memory.
On the Draw side, ensure all Hair and Curves attributes have, at least,
a dummy attribute bound.

Pull Request: https://projects.blender.org/blender/blender/pulls/142265
2025-07-21 15:14:10 +02:00

418 lines
14 KiB
C++

/* SPDX-FileCopyrightText: 2017 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup draw
*
* \brief Contains procedural GPU hair drawing methods.
*/
#include "BLI_utildefines.h"
#include "DNA_curves_types.h"
#include "BKE_attribute.hh"
#include "BKE_curves.hh"
#include "BKE_customdata.hh"
#include "GPU_batch.hh"
#include "GPU_capabilities.hh"
#include "GPU_material.hh"
#include "GPU_shader.hh"
#include "GPU_texture.hh"
#include "GPU_vertex_buffer.hh"
#include "DRW_gpu_wrapper.hh"
#include "DRW_render.hh"
#include "draw_cache_impl.hh"
#include "draw_common.hh"
#include "draw_context_private.hh"
#include "draw_curves_private.hh"
#include "draw_hair_private.hh"
#include "draw_shader.hh"
namespace blender::draw {
using CurvesInfosBuf = UniformBuffer<CurvesInfos>;
CurvesInfosBuf &CurvesUniformBufPool::alloc()
{
CurvesInfosBuf *ptr;
if (used >= ubos.size()) {
ubos.append(std::make_unique<CurvesInfosBuf>());
ptr = ubos.last().get();
}
else {
ptr = ubos[used++].get();
}
memset(ptr->data(), 0, sizeof(CurvesInfos));
return *ptr;
}
gpu::VertBuf *CurvesModule::drw_curves_ensure_dummy_vbo()
{
GPUVertFormat format = {0};
uint dummy_id = GPU_vertformat_attr_add(&format, "dummy", gpu::VertAttrType::SFLOAT_32_32_32_32);
gpu::VertBuf *vbo = GPU_vertbuf_create_with_format_ex(
format, GPU_USAGE_STATIC | GPU_USAGE_FLAG_BUFFER_TEXTURE_ONLY);
const float vert[4] = {0.0f, 0.0f, 0.0f, 0.0f};
GPU_vertbuf_data_alloc(*vbo, 1);
GPU_vertbuf_attr_fill(vbo, dummy_id, vert);
/* Create vbo immediately to bind to texture buffer. */
GPU_vertbuf_use(vbo);
return vbo;
}
void DRW_curves_init(DRWData *drw_data)
{
if (drw_data == nullptr) {
drw_data = drw_get().data;
}
if (drw_data->curves_module == nullptr) {
drw_data->curves_module = MEM_new<CurvesModule>("CurvesModule");
}
}
void DRW_curves_begin_sync(DRWData *drw_data)
{
drw_data->curves_module->init();
}
void DRW_curves_module_free(CurvesModule *curves_module)
{
MEM_delete(curves_module);
}
static void drw_curves_cache_update_compute(CurvesEvalCache *cache,
const int curves_num,
gpu::VertBuf *output_buf,
gpu::VertBuf *input_buf)
{
BLI_assert(input_buf != nullptr);
BLI_assert(output_buf != nullptr);
GPUShader *shader = DRW_shader_curves_refine_get(CURVES_EVAL_CATMULL_ROM);
/* TODO(fclem): Remove Global access. */
PassSimple &pass = drw_get().data->curves_module->refine;
pass.shader_set(shader);
pass.bind_texture("hairPointBuffer", input_buf);
pass.bind_texture("hairStrandBuffer", cache->proc_strand_buf);
pass.bind_texture("hairStrandSegBuffer", cache->proc_strand_seg_buf);
pass.push_constant("hairStrandsRes", &cache->final.resolution);
pass.bind_ssbo("posTime", output_buf);
const int max_strands_per_call = GPU_max_work_group_count(0);
int strands_start = 0;
while (strands_start < curves_num) {
int batch_strands_len = std::min(curves_num - strands_start, max_strands_per_call);
pass.push_constant("hairStrandOffset", strands_start);
pass.dispatch(int3(batch_strands_len, cache->final.resolution, 1));
strands_start += batch_strands_len;
}
}
static void drw_curves_cache_update_compute(CurvesEvalCache *cache)
{
const int curves_num = cache->curves_num;
const int final_points_len = cache->final.resolution * curves_num;
if (final_points_len == 0) {
return;
}
drw_curves_cache_update_compute(cache, curves_num, cache->final.proc_buf, cache->proc_point_buf);
const VectorSet<std::string> &attrs = cache->final.attr_used;
for (const int i : attrs.index_range()) {
if (!cache->proc_attributes_point_domain[i]) {
continue;
}
drw_curves_cache_update_compute(
cache, curves_num, cache->final.attributes_buf[i], cache->proc_attributes_buf[i]);
}
}
static CurvesEvalCache *drw_curves_cache_get(Curves &curves,
GPUMaterial *gpu_material,
int subdiv,
int thickness_res)
{
CurvesEvalCache *cache;
const bool update = curves_ensure_procedural_data(
&curves, &cache, gpu_material, subdiv, thickness_res);
if (update) {
drw_curves_cache_update_compute(cache);
}
return cache;
}
gpu::VertBuf *DRW_curves_pos_buffer_get(Object *object)
{
const DRWContext *draw_ctx = DRW_context_get();
const Scene *scene = draw_ctx->scene;
const int subdiv = scene->r.hair_subdiv;
const int thickness_res = (scene->r.hair_type == SCE_HAIR_SHAPE_STRAND) ? 1 : 2;
Curves &curves = DRW_object_get_data_for_drawing<Curves>(*object);
CurvesEvalCache *cache = drw_curves_cache_get(curves, nullptr, subdiv, thickness_res);
return cache->final.proc_buf;
}
static int attribute_index_in_material(GPUMaterial *gpu_material, const StringRef name)
{
if (!gpu_material) {
return -1;
}
int index = 0;
ListBase gpu_attrs = GPU_material_attributes(gpu_material);
LISTBASE_FOREACH (GPUMaterialAttribute *, gpu_attr, &gpu_attrs) {
if (gpu_attr->name == name) {
return index;
}
index++;
}
return -1;
}
void DRW_curves_update(draw::Manager &manager)
{
DRW_submission_start();
/* TODO(fclem): Remove Global access. */
PassSimple &pass = drw_get().data->curves_module->refine;
/* NOTE: This also update legacy hairs too as they populate the same pass. */
manager.submit(pass);
GPU_memory_barrier(GPU_BARRIER_SHADER_STORAGE);
/* Make sure calling this function again will not subdivide the same data. */
pass.init();
DRW_submission_end();
}
/* New Draw Manager. */
static CurvesEvalCache *curves_cache_get(Curves &curves,
GPUMaterial *gpu_material,
int subdiv,
int thickness_res)
{
CurvesEvalCache *cache;
const bool update = curves_ensure_procedural_data(
&curves, &cache, gpu_material, subdiv, thickness_res);
if (!update) {
return cache;
}
const int curves_num = cache->curves_num;
const int final_points_len = cache->final.resolution * curves_num;
CurvesModule &module = *drw_get().data->curves_module;
auto cache_update = [&](gpu::VertBuf *output_buf, gpu::VertBuf *input_buf) {
PassSimple::Sub &ob_ps = module.refine.sub("Object Pass");
ob_ps.shader_set(DRW_shader_curves_refine_get(CURVES_EVAL_CATMULL_ROM));
ob_ps.bind_texture("hairPointBuffer", input_buf);
ob_ps.bind_texture("hairStrandBuffer", cache->proc_strand_buf);
ob_ps.bind_texture("hairStrandSegBuffer", cache->proc_strand_seg_buf);
ob_ps.push_constant("hairStrandsRes", &cache->final.resolution);
ob_ps.bind_ssbo("posTime", output_buf);
const int max_strands_per_call = GPU_max_work_group_count(0);
int strands_start = 0;
while (strands_start < curves_num) {
int batch_strands_len = std::min(curves_num - strands_start, max_strands_per_call);
PassSimple::Sub &sub_ps = ob_ps.sub("Sub Pass");
sub_ps.push_constant("hairStrandOffset", strands_start);
sub_ps.dispatch(int3(batch_strands_len, cache->final.resolution, 1));
strands_start += batch_strands_len;
}
};
if (final_points_len > 0) {
cache_update(cache->final.proc_buf, cache->proc_point_buf);
const VectorSet<std::string> &attrs = cache->final.attr_used;
for (const int i : attrs.index_range()) {
/* Only refine point attributes. */
if (cache->proc_attributes_point_domain[i]) {
cache_update(cache->final.attributes_buf[i], cache->proc_attributes_buf[i]);
}
}
}
return cache;
}
gpu::VertBuf *curves_pos_buffer_get(Scene *scene, Object *object)
{
const int subdiv = scene->r.hair_subdiv;
const int thickness_res = (scene->r.hair_type == SCE_HAIR_SHAPE_STRAND) ? 1 : 2;
Curves &curves = DRW_object_get_data_for_drawing<Curves>(*object);
CurvesEvalCache *cache = curves_cache_get(curves, nullptr, subdiv, thickness_res);
return cache->final.proc_buf;
}
static std::optional<StringRef> get_first_uv_name(const bke::AttributeAccessor &attributes)
{
std::optional<StringRef> name;
attributes.foreach_attribute([&](const bke::AttributeIter &iter) {
if (iter.data_type == bke::AttrType::Float2) {
name = iter.name;
iter.stop();
}
});
return name;
}
template<typename PassT>
gpu::Batch *curves_sub_pass_setup_implementation(PassT &sub_ps,
const Scene *scene,
Object *ob,
GPUMaterial *gpu_material)
{
/** NOTE: This still relies on the old DRW_curves implementation. */
CurvesModule &module = *drw_get().data->curves_module;
CurvesInfosBuf &curves_infos = module.ubo_pool.alloc();
BLI_assert(ob->type == OB_CURVES);
Curves &curves_id = DRW_object_get_data_for_drawing<Curves>(*ob);
const int subdiv = scene->r.hair_subdiv;
const int thickness_res = (scene->r.hair_type == SCE_HAIR_SHAPE_STRAND) ? 1 : 2;
CurvesEvalCache *curves_cache = drw_curves_cache_get(
curves_id, gpu_material, subdiv, thickness_res);
/* Ensure we have no unbound resources.
* Required for Vulkan.
* Fixes issues with certain GL drivers not drawing anything. */
sub_ps.bind_texture("u", module.dummy_vbo);
sub_ps.bind_texture("au", module.dummy_vbo);
sub_ps.bind_texture("a", module.dummy_vbo);
sub_ps.bind_texture("c", module.dummy_vbo);
sub_ps.bind_texture("ac", module.dummy_vbo);
if (gpu_material) {
ListBase attr_list = GPU_material_attributes(gpu_material);
ListBaseWrapper<GPUMaterialAttribute> attrs(attr_list);
for (const GPUMaterialAttribute *attr : attrs) {
sub_ps.bind_texture(attr->input_name, module.dummy_vbo);
}
}
/* TODO: Generalize radius implementation for curves data type. */
float hair_rad_shape = 0.0f;
float hair_rad_root = 0.005f;
float hair_rad_tip = 0.0f;
bool hair_close_tip = true;
/* Use the radius of the root and tip of the first curve for now. This is a workaround that we
* use for now because we can't use a per-point radius yet. */
const bke::CurvesGeometry &curves = curves_id.geometry.wrap();
if (curves.curves_num() >= 1) {
VArray<float> radii = *curves.attributes().lookup_or_default(
"radius", bke::AttrDomain::Point, 0.005f);
const IndexRange first_curve_points = curves.points_by_curve()[0];
const float first_radius = radii[first_curve_points.first()];
const float last_radius = radii[first_curve_points.last()];
const float middle_radius = radii[first_curve_points.size() / 2];
hair_rad_root = radii[first_curve_points.first()];
hair_rad_tip = radii[first_curve_points.last()];
hair_rad_shape = std::clamp(
math::safe_divide(middle_radius - first_radius, last_radius - first_radius) * 2.0f - 1.0f,
-1.0f,
1.0f);
}
sub_ps.bind_texture("hairPointBuffer", curves_cache->final.proc_buf);
if (curves_cache->proc_length_buf) {
sub_ps.bind_texture("l", curves_cache->proc_length_buf);
}
const std::optional<StringRef> uv_name = get_first_uv_name(
curves_id.geometry.wrap().attributes());
const VectorSet<std::string> &attrs = curves_cache->final.attr_used;
for (const int i : attrs.index_range()) {
const StringRef name = attrs[i];
char sampler_name[32];
drw_curves_get_attribute_sampler_name(name, sampler_name);
if (!curves_cache->proc_attributes_point_domain[i]) {
if (!curves_cache->proc_attributes_buf[i]) {
continue;
}
sub_ps.bind_texture(sampler_name, curves_cache->proc_attributes_buf[i]);
if (name == uv_name) {
sub_ps.bind_texture("a", curves_cache->proc_attributes_buf[i]);
}
}
else {
if (!curves_cache->final.attributes_buf[i]) {
continue;
}
sub_ps.bind_texture(sampler_name, curves_cache->final.attributes_buf[i]);
if (name == uv_name) {
sub_ps.bind_texture("a", curves_cache->final.attributes_buf[i]);
}
}
/* Some attributes may not be used in the shader anymore and were not garbage collected yet, so
* we need to find the right index for this attribute as uniforms defining the scope of the
* attributes are based on attribute loading order, which is itself based on the material's
* attributes. */
const int index = attribute_index_in_material(gpu_material, name);
if (index != -1) {
curves_infos.is_point_attribute[index][0] = curves_cache->proc_attributes_point_domain[i];
}
}
curves_infos.push_update();
sub_ps.bind_ubo("drw_curves", curves_infos);
sub_ps.push_constant("hairStrandsRes", &curves_cache->final.resolution, 1);
sub_ps.push_constant("hairThicknessRes", thickness_res);
sub_ps.push_constant("hairRadShape", hair_rad_shape);
sub_ps.push_constant("hairDupliMatrix", ob->object_to_world());
sub_ps.push_constant("hairRadRoot", hair_rad_root);
sub_ps.push_constant("hairRadTip", hair_rad_tip);
sub_ps.push_constant("hairCloseTip", hair_close_tip);
return curves_cache->final.proc_hairs;
}
gpu::Batch *curves_sub_pass_setup(PassMain::Sub &ps,
const Scene *scene,
Object *ob,
GPUMaterial *gpu_material)
{
return curves_sub_pass_setup_implementation(ps, scene, ob, gpu_material);
}
gpu::Batch *curves_sub_pass_setup(PassSimple::Sub &ps,
const Scene *scene,
Object *ob,
GPUMaterial *gpu_material)
{
return curves_sub_pass_setup_implementation(ps, scene, ob, gpu_material);
}
} // namespace blender::draw