Files
test2/source/blender/draw/intern/draw_cache_impl_curves.cc
Hans Goudey abc8796cab Curves: Optimize edit/sculpt mode index buffer creation
Avoid function call overhead and parallelize the creation of the edit
and sculpt mode lines index buffers. Also, remove an extra index that
was added for each curve when there are no cyclic curves.

Though ideally these index buffers would be generated on the GPU, this
simple change makes this part of the GPU data building almost 5x faster
(from 1.23 ms to 0.25 ms for curves with 80 thousand points).

Pull Request: https://projects.blender.org/blender/blender/pulls/133897
2025-01-31 21:47:16 +01:00

1147 lines
43 KiB
C++

/* SPDX-FileCopyrightText: 2017 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup draw
*
* \brief Curves API for render engines
*/
#include <cstring>
#include "MEM_guardedalloc.h"
#include "BLI_array_utils.hh"
#include "BLI_listbase.h"
#include "BLI_math_base.h"
#include "BLI_math_vector.hh"
#include "BLI_math_vector_types.hh"
#include "BLI_span.hh"
#include "BLI_string.h"
#include "BLI_task.hh"
#include "BLI_utildefines.h"
#include "DNA_curves_types.h"
#include "DNA_object_types.h"
#include "DEG_depsgraph_query.hh"
#include "BKE_crazyspace.hh"
#include "BKE_curves.hh"
#include "BKE_curves_utils.hh"
#include "BKE_customdata.hh"
#include "BKE_geometry_set.hh"
#include "GPU_batch.hh"
#include "GPU_context.hh"
#include "GPU_material.hh"
#include "GPU_texture.hh"
#include "DRW_render.hh"
#include "draw_attributes.hh"
#include "draw_cache_impl.hh" /* own include */
#include "draw_cache_inline.hh"
#include "draw_curves_private.hh" /* own include */
namespace blender::draw {
#define EDIT_CURVES_NURBS_CONTROL_POINT (1u)
#define EDIT_CURVES_BEZIER_HANDLE (1u << 1)
#define EDIT_CURVES_ACTIVE_HANDLE (1u << 2)
/* Bezier curve control point lying on the curve.
* The one between left and right handles. */
#define EDIT_CURVES_BEZIER_KNOT (1u << 3)
#define EDIT_CURVES_HANDLE_TYPES_SHIFT (4u)
/* ---------------------------------------------------------------------- */
struct CurvesBatchCache {
CurvesEvalCache eval_cache;
gpu::Batch *edit_points;
gpu::Batch *edit_handles;
gpu::Batch *sculpt_cage;
gpu::IndexBuf *sculpt_cage_ibo;
/* Crazy-space point positions for original points. */
gpu::VertBuf *edit_points_pos;
/* Additional data needed for shader to choose color for each point in edit_points_pos.
* If first bit is set, then point is NURBS control point. EDIT_CURVES_NURBS_CONTROL_POINT is
* used to set and test. If second, then point is Bezier handle point. Set and tested with
* EDIT_CURVES_BEZIER_HANDLE.
* In Bezier case two handle types of HandleType are also encoded.
* Byte structure for Bezier knot point (handle middle point):
* | left handle type | right handle type | | BEZIER| NURBS|
* | 7 6 | 5 4 | 3 2 | 1 | 0 |
*
* If it is left or right handle point, then same handle type is repeated in both slots.
*/
gpu::VertBuf *edit_points_data;
/* Selection of original points. */
gpu::VertBuf *edit_points_selection;
gpu::IndexBuf *edit_handles_ibo;
gpu::Batch *edit_curves_lines;
gpu::VertBuf *edit_curves_lines_pos;
gpu::IndexBuf *edit_curves_lines_ibo;
/* Whether the cache is invalid. */
bool is_dirty;
/**
* The draw cache extraction is currently not multi-threaded for multiple objects, but if it was,
* some locking would be necessary because multiple objects can use the same curves data with
* different materials, etc. This is a placeholder to make multi-threading easier in the future.
*/
std::mutex render_mutex;
};
static uint DUMMY_ID;
static GPUVertFormat single_attr_vbo_format(const char *name,
const GPUVertCompType comp_type,
const uint comp_len,
const GPUVertFetchMode fetch_mode,
uint &attr_id = DUMMY_ID)
{
GPUVertFormat format{};
attr_id = GPU_vertformat_attr_add(&format, name, comp_type, comp_len, fetch_mode);
return format;
}
static bool batch_cache_is_dirty(const Curves &curves)
{
const CurvesBatchCache *cache = static_cast<CurvesBatchCache *>(curves.batch_cache);
return (cache && cache->is_dirty == false);
}
static void init_batch_cache(Curves &curves)
{
CurvesBatchCache *cache = static_cast<CurvesBatchCache *>(curves.batch_cache);
if (!cache) {
cache = MEM_new<CurvesBatchCache>(__func__);
curves.batch_cache = cache;
}
else {
cache->eval_cache = {};
}
cache->is_dirty = false;
}
static void discard_attributes(CurvesEvalCache &eval_cache)
{
for (const int i : IndexRange(GPU_MAX_ATTR)) {
GPU_VERTBUF_DISCARD_SAFE(eval_cache.proc_attributes_buf[i]);
}
for (const int j : IndexRange(GPU_MAX_ATTR)) {
GPU_VERTBUF_DISCARD_SAFE(eval_cache.final.attributes_buf[j]);
}
drw_attributes_clear(&eval_cache.final.attr_used);
}
static void clear_edit_data(CurvesBatchCache *cache)
{
/* TODO: more granular update tagging. */
GPU_VERTBUF_DISCARD_SAFE(cache->edit_points_pos);
GPU_VERTBUF_DISCARD_SAFE(cache->edit_points_data);
GPU_VERTBUF_DISCARD_SAFE(cache->edit_points_selection);
GPU_INDEXBUF_DISCARD_SAFE(cache->edit_handles_ibo);
GPU_BATCH_DISCARD_SAFE(cache->edit_points);
GPU_BATCH_DISCARD_SAFE(cache->edit_handles);
GPU_INDEXBUF_DISCARD_SAFE(cache->sculpt_cage_ibo);
GPU_BATCH_DISCARD_SAFE(cache->sculpt_cage);
GPU_VERTBUF_DISCARD_SAFE(cache->edit_curves_lines_pos);
GPU_INDEXBUF_DISCARD_SAFE(cache->edit_curves_lines_ibo);
GPU_BATCH_DISCARD_SAFE(cache->edit_curves_lines);
}
static void clear_final_data(CurvesEvalFinalCache &final_cache)
{
GPU_VERTBUF_DISCARD_SAFE(final_cache.proc_buf);
GPU_BATCH_DISCARD_SAFE(final_cache.proc_hairs);
for (const int j : IndexRange(GPU_MAX_ATTR)) {
GPU_VERTBUF_DISCARD_SAFE(final_cache.attributes_buf[j]);
}
}
static void clear_eval_data(CurvesEvalCache &eval_cache)
{
/* TODO: more granular update tagging. */
GPU_VERTBUF_DISCARD_SAFE(eval_cache.proc_point_buf);
GPU_VERTBUF_DISCARD_SAFE(eval_cache.proc_length_buf);
GPU_VERTBUF_DISCARD_SAFE(eval_cache.proc_strand_buf);
GPU_VERTBUF_DISCARD_SAFE(eval_cache.proc_strand_seg_buf);
clear_final_data(eval_cache.final);
discard_attributes(eval_cache);
}
static void clear_batch_cache(Curves &curves)
{
CurvesBatchCache *cache = static_cast<CurvesBatchCache *>(curves.batch_cache);
if (!cache) {
return;
}
clear_eval_data(cache->eval_cache);
clear_edit_data(cache);
}
static CurvesBatchCache &get_batch_cache(Curves &curves)
{
DRW_curves_batch_cache_validate(&curves);
return *static_cast<CurvesBatchCache *>(curves.batch_cache);
}
struct PositionAndParameter {
float3 position;
float parameter;
};
static void fill_points_position_time_vbo(const OffsetIndices<int> points_by_curve,
const Span<float3> positions,
MutableSpan<PositionAndParameter> posTime_data,
MutableSpan<float> hairLength_data)
{
threading::parallel_for(points_by_curve.index_range(), 1024, [&](const IndexRange range) {
for (const int i_curve : range) {
const IndexRange points = points_by_curve[i_curve];
Span<float3> curve_positions = positions.slice(points);
MutableSpan<PositionAndParameter> curve_posTime_data = posTime_data.slice(points);
float total_len = 0.0f;
for (const int i_point : curve_positions.index_range()) {
if (i_point > 0) {
total_len += math::distance(curve_positions[i_point - 1], curve_positions[i_point]);
}
curve_posTime_data[i_point].position = curve_positions[i_point];
curve_posTime_data[i_point].parameter = total_len;
}
hairLength_data[i_curve] = total_len;
/* Assign length value. */
if (total_len > 0.0f) {
const float factor = 1.0f / total_len;
/* Divide by total length to have a [0-1] number. */
for (const int i_point : curve_positions.index_range()) {
curve_posTime_data[i_point].parameter *= factor;
}
}
}
});
}
static void create_points_position_time_vbo(const bke::CurvesGeometry &curves,
CurvesEvalCache &cache)
{
GPUVertFormat format = {0};
GPU_vertformat_attr_add(&format, "posTime", GPU_COMP_F32, 4, GPU_FETCH_FLOAT);
cache.proc_point_buf = GPU_vertbuf_create_with_format_ex(
format, GPU_USAGE_STATIC | GPU_USAGE_FLAG_BUFFER_TEXTURE_ONLY);
GPU_vertbuf_data_alloc(*cache.proc_point_buf, cache.points_num);
GPUVertFormat length_format = {0};
GPU_vertformat_attr_add(&length_format, "hairLength", GPU_COMP_F32, 1, GPU_FETCH_FLOAT);
cache.proc_length_buf = GPU_vertbuf_create_with_format_ex(
length_format, GPU_USAGE_STATIC | GPU_USAGE_FLAG_BUFFER_TEXTURE_ONLY);
GPU_vertbuf_data_alloc(*cache.proc_length_buf, cache.curves_num);
/* TODO: Only create hairLength VBO when necessary. */
fill_points_position_time_vbo(curves.points_by_curve(),
curves.positions(),
cache.proc_point_buf->data<PositionAndParameter>(),
cache.proc_length_buf->data<float>());
}
static uint32_t bezier_data_value(int8_t handle_type, bool is_active)
{
return (handle_type << EDIT_CURVES_HANDLE_TYPES_SHIFT) | EDIT_CURVES_BEZIER_HANDLE |
(is_active ? EDIT_CURVES_ACTIVE_HANDLE : 0);
}
static void create_edit_points_position_and_data(
const bke::CurvesGeometry &curves,
const IndexMask bezier_curves,
const OffsetIndices<int> bezier_dst_offsets,
const bke::crazyspace::GeometryDeformation deformation,
CurvesBatchCache &cache)
{
static GPUVertFormat format_pos = single_attr_vbo_format(
"pos", GPU_COMP_F32, 3, GPU_FETCH_FLOAT);
/* GPU_COMP_U32 is used instead of GPU_COMP_U8 because depending on running hardware stride might
* still be 4. Thus adding complexity to the code and still sparing no memory. */
static GPUVertFormat format_data = single_attr_vbo_format(
"data", GPU_COMP_U32, 1, GPU_FETCH_INT);
Span<float3> deformed_positions = deformation.positions;
const int bezier_point_count = bezier_dst_offsets.total_size();
const int size = deformed_positions.size() + bezier_point_count * 2;
GPU_vertbuf_init_with_format(*cache.edit_points_pos, format_pos);
GPU_vertbuf_data_alloc(*cache.edit_points_pos, size);
GPU_vertbuf_init_with_format(*cache.edit_points_data, format_data);
GPU_vertbuf_data_alloc(*cache.edit_points_data, size);
MutableSpan<float3> pos_dst = cache.edit_points_pos->data<float3>();
pos_dst.take_front(deformed_positions.size()).copy_from(deformed_positions);
MutableSpan<uint32_t> data_dst = cache.edit_points_data->data<uint32_t>();
MutableSpan<uint32_t> handle_data_left(data_dst.data() + deformed_positions.size(),
bezier_point_count);
MutableSpan<uint32_t> handle_data_right(
data_dst.data() + deformed_positions.size() + bezier_point_count, bezier_point_count);
const Span<float3> left_handle_positions = curves.handle_positions_left();
const Span<float3> right_handle_positions = curves.handle_positions_right();
const VArray<int8_t> left_handle_types = curves.handle_types_left();
const VArray<int8_t> right_handle_types = curves.handle_types_right();
const OffsetIndices<int> points_by_curve = curves.points_by_curve();
const VArray<bool> selection_attr = *curves.attributes().lookup_or_default<bool>(
".selection", bke::AttrDomain::Point, true);
auto handle_other_curves = [&](const uint32_t fill_value, const bool mark_active) {
return [&, fill_value, mark_active](const IndexMask &selection) {
selection.foreach_index(GrainSize(256), [&](const int curve_i) {
const IndexRange points = points_by_curve[curve_i];
bool is_active = false;
if (mark_active) {
is_active = array_utils::count_booleans(selection_attr, points) > 0;
}
uint32_t data_value = fill_value | (is_active ? EDIT_CURVES_ACTIVE_HANDLE : 0u);
data_dst.slice(points).fill(data_value);
});
};
};
bke::curves::foreach_curve_by_type(
curves.curve_types(),
curves.curve_type_counts(),
curves.curves_range(),
handle_other_curves(0, false),
handle_other_curves(0, false),
[&](const IndexMask &selection) {
const VArray<bool> selection_left = *curves.attributes().lookup_or_default<bool>(
".selection_handle_left", bke::AttrDomain::Point, true);
const VArray<bool> selection_right = *curves.attributes().lookup_or_default<bool>(
".selection_handle_right", bke::AttrDomain::Point, true);
selection.foreach_index(GrainSize(256), [&](const int src_i, const int64_t dst_i) {
for (const int point : points_by_curve[src_i]) {
const int point_in_curve = point - points_by_curve[src_i].start();
const int dst_index = bezier_dst_offsets[dst_i].start() + point_in_curve;
data_dst[point] = EDIT_CURVES_BEZIER_KNOT;
bool is_active = selection_attr[point] || selection_left[point] ||
selection_right[point];
handle_data_left[dst_index] = bezier_data_value(left_handle_types[point], is_active);
handle_data_right[dst_index] = bezier_data_value(right_handle_types[point], is_active);
}
});
},
handle_other_curves(EDIT_CURVES_NURBS_CONTROL_POINT, true));
if (!bezier_point_count) {
return;
}
MutableSpan<float3> left_handles(pos_dst.data() + deformed_positions.size(), bezier_point_count);
MutableSpan<float3> right_handles(
pos_dst.data() + deformed_positions.size() + bezier_point_count, bezier_point_count);
/* TODO: Use deformed left_handle_positions and left_handle_positions. */
array_utils::gather_group_to_group(
points_by_curve, bezier_dst_offsets, bezier_curves, left_handle_positions, left_handles);
array_utils::gather_group_to_group(
points_by_curve, bezier_dst_offsets, bezier_curves, right_handle_positions, right_handles);
}
static void create_edit_points_selection(const bke::CurvesGeometry &curves,
const IndexMask bezier_curves,
const OffsetIndices<int> bezier_dst_offsets,
CurvesBatchCache &cache)
{
static GPUVertFormat format_data = single_attr_vbo_format(
"selection", GPU_COMP_F32, 1, GPU_FETCH_FLOAT);
const int bezier_point_count = bezier_dst_offsets.total_size();
const int vert_count = curves.points_num() + bezier_point_count * 2;
GPU_vertbuf_init_with_format(*cache.edit_points_selection, format_data);
GPU_vertbuf_data_alloc(*cache.edit_points_selection, vert_count);
MutableSpan<float> data = cache.edit_points_selection->data<float>();
const VArray<float> attribute = *curves.attributes().lookup_or_default<float>(
".selection", bke::AttrDomain::Point, 1.0f);
attribute.materialize(data.slice(0, curves.points_num()));
if (!bezier_point_count) {
return;
}
const VArray<float> attribute_left = *curves.attributes().lookup_or_default<float>(
".selection_handle_left", bke::AttrDomain::Point, 1.0f);
const VArray<float> attribute_right = *curves.attributes().lookup_or_default<float>(
".selection_handle_right", bke::AttrDomain::Point, 1.0f);
const OffsetIndices<int> points_by_curve = curves.points_by_curve();
IndexRange dst_range = IndexRange::from_begin_size(curves.points_num(), bezier_point_count);
array_utils::gather_group_to_group(
points_by_curve, bezier_dst_offsets, bezier_curves, attribute_left, data.slice(dst_range));
dst_range = dst_range.shift(bezier_point_count);
array_utils::gather_group_to_group(
points_by_curve, bezier_dst_offsets, bezier_curves, attribute_right, data.slice(dst_range));
}
static void create_lines_ibo_no_cyclic(const OffsetIndices<int> points_by_curve,
gpu::IndexBuf &ibo)
{
const int points_num = points_by_curve.total_size();
const int curves_num = points_by_curve.size();
const int indices_num = points_num + curves_num;
GPUIndexBufBuilder builder;
GPU_indexbuf_init(&builder, GPU_PRIM_LINE_STRIP, indices_num, points_num);
MutableSpan<uint> ibo_data = GPU_indexbuf_get_data(&builder);
threading::parallel_for(IndexRange(curves_num), 1024, [&](const IndexRange range) {
for (const int curve : range) {
const IndexRange points = points_by_curve[curve];
const IndexRange ibo_range = IndexRange(points.start() + curve, points.size() + 1);
for (const int i : points.index_range()) {
ibo_data[ibo_range[i]] = points[i];
}
ibo_data[ibo_range.last()] = gpu::RESTART_INDEX;
}
});
GPU_indexbuf_build_in_place_ex(&builder, 0, points_num, true, &ibo);
}
static void create_lines_ibo_with_cyclic(const OffsetIndices<int> points_by_curve,
const Span<bool> cyclic,
gpu::IndexBuf &ibo)
{
const int points_num = points_by_curve.total_size();
const int curves_num = points_by_curve.size();
const int indices_num = points_num + curves_num * 2;
GPUIndexBufBuilder builder;
GPU_indexbuf_init(&builder, GPU_PRIM_LINE_STRIP, indices_num, points_num);
MutableSpan<uint> ibo_data = GPU_indexbuf_get_data(&builder);
threading::parallel_for(IndexRange(curves_num), 1024, [&](const IndexRange range) {
for (const int curve : range) {
const IndexRange points = points_by_curve[curve];
const IndexRange ibo_range = IndexRange(points.start() + curve * 2, points.size() + 2);
for (const int i : points.index_range()) {
ibo_data[ibo_range[i]] = points[i];
}
ibo_data[ibo_range.last(1)] = cyclic[curve] ? points.first() : gpu::RESTART_INDEX;
ibo_data[ibo_range.last()] = gpu::RESTART_INDEX;
}
});
GPU_indexbuf_build_in_place_ex(&builder, 0, points_num, true, &ibo);
}
static void create_lines_ibo_with_cyclic(const OffsetIndices<int> points_by_curve,
const VArray<bool> &cyclic,
gpu::IndexBuf &ibo)
{
const array_utils::BooleanMix cyclic_mix = array_utils::booleans_mix_calc(cyclic);
if (cyclic_mix == array_utils::BooleanMix::AllFalse) {
create_lines_ibo_no_cyclic(points_by_curve, ibo);
}
else {
const VArraySpan<bool> cyclic_span(cyclic);
create_lines_ibo_with_cyclic(points_by_curve, cyclic_span, ibo);
}
}
static void calc_edit_handles_ibo(const bke::CurvesGeometry &curves,
const IndexMask bezier_curves,
const OffsetIndices<int> bezier_offsets,
const IndexMask other_curves,
CurvesBatchCache &cache)
{
const int bezier_point_count = bezier_offsets.total_size();
/* Left and right handle will be appended for each Bezier point. */
const int vert_len = curves.points_num() + 2 * bezier_point_count;
/* For each point has 2 lines from 2 points. */
const int index_len_for_bezier_handles = 4 * bezier_point_count;
const VArray<bool> cyclic = curves.cyclic();
/* For curves like NURBS each control point except last generates two point line.
* If one point curves or two point cyclic curves are present, not all builder's buffer space
* will be used. */
const int index_len_for_other_handles = (curves.points_num() - bezier_point_count -
other_curves.size()) *
2 +
array_utils::count_booleans(cyclic, other_curves) * 2;
const int index_len = index_len_for_other_handles + index_len_for_bezier_handles;
/* Use two index buffer builders for the same underlying memory. */
GPUIndexBufBuilder elb, right_elb;
GPU_indexbuf_init_ex(&elb, GPU_PRIM_LINES, index_len, vert_len);
memcpy(&right_elb, &elb, sizeof(elb));
right_elb.index_len = 2 * bezier_point_count;
const OffsetIndices points_by_curve = curves.points_by_curve();
bezier_curves.foreach_index([&](const int64_t src_i, const int64_t dst_i) {
IndexRange bezier_points = points_by_curve[src_i];
const int index_shift = curves.points_num() - bezier_points.first() +
bezier_offsets[dst_i].first();
for (const int point : bezier_points) {
const int point_left_i = index_shift + point;
GPU_indexbuf_add_line_verts(&elb, point_left_i, point);
GPU_indexbuf_add_line_verts(&right_elb, point_left_i + bezier_point_count, point);
}
});
other_curves.foreach_index([&](const int64_t src_i) {
IndexRange curve_points = points_by_curve[src_i];
if (curve_points.size() <= 1) {
return;
}
for (const int point : curve_points.drop_back(1)) {
GPU_indexbuf_add_line_verts(&right_elb, point, point + 1);
}
if (cyclic[src_i] && curve_points.size() > 2) {
GPU_indexbuf_add_line_verts(&right_elb, curve_points.first(), curve_points.last());
}
});
GPU_indexbuf_join(&elb, &right_elb);
GPU_indexbuf_build_in_place(&elb, cache.edit_handles_ibo);
}
static void alloc_final_attribute_vbo(CurvesEvalCache &cache,
const GPUVertFormat &format,
const int index,
const char * /*name*/)
{
cache.final.attributes_buf[index] = GPU_vertbuf_create_with_format_ex(
format, GPU_USAGE_DEVICE_ONLY | GPU_USAGE_FLAG_BUFFER_TEXTURE_ONLY);
/* Create a destination buffer for the transform feedback. Sized appropriately */
/* Those are points! not line segments. */
GPU_vertbuf_data_alloc(*cache.final.attributes_buf[index],
cache.final.resolution * cache.curves_num);
}
static void ensure_control_point_attribute(const Curves &curves,
CurvesEvalCache &cache,
const DRW_AttributeRequest &request,
const int index,
const GPUVertFormat &format)
{
if (cache.proc_attributes_buf[index] != nullptr) {
return;
}
GPU_VERTBUF_DISCARD_SAFE(cache.proc_attributes_buf[index]);
cache.proc_attributes_buf[index] = GPU_vertbuf_create_with_format_ex(
format, GPU_USAGE_STATIC | GPU_USAGE_FLAG_BUFFER_TEXTURE_ONLY);
gpu::VertBuf &attr_vbo = *cache.proc_attributes_buf[index];
GPU_vertbuf_data_alloc(attr_vbo,
request.domain == bke::AttrDomain::Point ? curves.geometry.point_num :
curves.geometry.curve_num);
const bke::AttributeAccessor attributes = curves.geometry.wrap().attributes();
/* TODO(@kevindietrich): float4 is used for scalar attributes as the implicit conversion done
* by OpenGL to vec4 for a scalar `s` will produce a `vec4(s, 0, 0, 1)`. However, following
* the Blender convention, it should be `vec4(s, s, s, 1)`. This could be resolved using a
* similar texture state swizzle to map the attribute correctly as for volume attributes, so we
* can control the conversion ourselves. */
bke::AttributeReader<ColorGeometry4f> attribute = attributes.lookup_or_default<ColorGeometry4f>(
request.attribute_name, request.domain, {0.0f, 0.0f, 0.0f, 1.0f});
MutableSpan<ColorGeometry4f> vbo_span = attr_vbo.data<ColorGeometry4f>();
attribute.varray.materialize(vbo_span);
}
static void ensure_final_attribute(const Curves &curves,
CurvesEvalCache &cache,
const DRW_AttributeRequest &request,
const int index)
{
char sampler_name[32];
drw_curves_get_attribute_sampler_name(request.attribute_name, sampler_name);
GPUVertFormat format = {0};
/* All attributes use vec4, see comment below. */
GPU_vertformat_attr_add(&format, sampler_name, GPU_COMP_F32, 4, GPU_FETCH_FLOAT);
ensure_control_point_attribute(curves, cache, request, index, format);
/* Existing final data may have been for a different attribute (with a different name or domain),
* free the data. */
GPU_VERTBUF_DISCARD_SAFE(cache.final.attributes_buf[index]);
/* Ensure final data for points. */
if (request.domain == bke::AttrDomain::Point) {
alloc_final_attribute_vbo(cache, format, index, sampler_name);
}
}
static void fill_curve_offsets_vbos(const OffsetIndices<int> points_by_curve,
GPUVertBufRaw &data_step,
GPUVertBufRaw &seg_step)
{
for (const int i : points_by_curve.index_range()) {
const IndexRange points = points_by_curve[i];
*(uint *)GPU_vertbuf_raw_step(&data_step) = points.start();
*(ushort *)GPU_vertbuf_raw_step(&seg_step) = points.size() - 1;
}
}
static void create_curve_offsets_vbos(const OffsetIndices<int> points_by_curve,
CurvesEvalCache &cache)
{
GPUVertBufRaw data_step, seg_step;
GPUVertFormat format_data = {0};
uint data_id = GPU_vertformat_attr_add(&format_data, "data", GPU_COMP_U32, 1, GPU_FETCH_INT);
GPUVertFormat format_seg = {0};
uint seg_id = GPU_vertformat_attr_add(&format_seg, "data", GPU_COMP_U16, 1, GPU_FETCH_INT);
/* Curve Data. */
cache.proc_strand_buf = GPU_vertbuf_create_with_format_ex(
format_data, GPU_USAGE_STATIC | GPU_USAGE_FLAG_BUFFER_TEXTURE_ONLY);
GPU_vertbuf_data_alloc(*cache.proc_strand_buf, cache.curves_num);
GPU_vertbuf_attr_get_raw_data(cache.proc_strand_buf, data_id, &data_step);
cache.proc_strand_seg_buf = GPU_vertbuf_create_with_format_ex(
format_seg, GPU_USAGE_STATIC | GPU_USAGE_FLAG_BUFFER_TEXTURE_ONLY);
GPU_vertbuf_data_alloc(*cache.proc_strand_seg_buf, cache.curves_num);
GPU_vertbuf_attr_get_raw_data(cache.proc_strand_seg_buf, seg_id, &seg_step);
fill_curve_offsets_vbos(points_by_curve, data_step, seg_step);
}
static void alloc_final_points_vbo(CurvesEvalCache &cache)
{
/* Same format as proc_point_buf. */
GPUVertFormat format = {0};
GPU_vertformat_attr_add(&format, "pos", GPU_COMP_F32, 4, GPU_FETCH_FLOAT);
cache.final.proc_buf = GPU_vertbuf_create_with_format_ex(
format, GPU_USAGE_DEVICE_ONLY | GPU_USAGE_FLAG_BUFFER_TEXTURE_ONLY);
/* Create a destination buffer for the transform feedback. Sized appropriately */
/* Those are points! not line segments. */
uint point_len = cache.final.resolution * cache.curves_num;
/* Avoid creating null sized VBO which can lead to crashes on certain platforms. */
point_len = max_ii(1, point_len);
GPU_vertbuf_data_alloc(*cache.final.proc_buf, point_len);
}
static void calc_final_indices(const bke::CurvesGeometry &curves,
CurvesEvalCache &cache,
const int thickness_res)
{
BLI_assert(thickness_res <= MAX_THICKRES); /* Cylinder strip not currently supported. */
/* Determine prim type and element count.
* NOTE: Metal backend uses non-restart prim types for optimal HW performance. */
bool use_strip_prims = (GPU_backend_get_type() != GPU_BACKEND_METAL);
int verts_per_curve;
GPUPrimType prim_type;
if (use_strip_prims) {
/* +1 for primitive restart */
verts_per_curve = cache.final.resolution * thickness_res;
prim_type = (thickness_res == 1) ? GPU_PRIM_LINE_STRIP : GPU_PRIM_TRI_STRIP;
}
else {
/* Use full primitive type. */
prim_type = (thickness_res == 1) ? GPU_PRIM_LINES : GPU_PRIM_TRIS;
int verts_per_segment = ((prim_type == GPU_PRIM_LINES) ? 2 : 6);
verts_per_curve = (cache.final.resolution - 1) * verts_per_segment;
}
static GPUVertFormat format = {0};
GPU_vertformat_clear(&format);
/* initialize vertex format */
GPU_vertformat_attr_add(&format, "dummy", GPU_COMP_U8, 1, GPU_FETCH_INT_TO_FLOAT_UNIT);
gpu::VertBuf *vbo = GPU_vertbuf_create_with_format(format);
GPU_vertbuf_data_alloc(*vbo, 1);
gpu::IndexBuf *ibo = nullptr;
eGPUBatchFlag owns_flag = GPU_BATCH_OWNS_VBO;
if (curves.curves_num()) {
ibo = GPU_indexbuf_build_curves_on_device(prim_type, curves.curves_num(), verts_per_curve);
owns_flag |= GPU_BATCH_OWNS_INDEX;
}
cache.final.proc_hairs = GPU_batch_create_ex(prim_type, vbo, ibo, owns_flag);
}
static bool ensure_attributes(const Curves &curves,
CurvesBatchCache &cache,
const GPUMaterial *gpu_material)
{
const CustomData &cd_curve = curves.geometry.curve_data;
const CustomData &cd_point = curves.geometry.point_data;
CurvesEvalFinalCache &final_cache = cache.eval_cache.final;
if (gpu_material) {
/* The following code should be kept in sync with `mesh_cd_calc_used_gpu_layers`. */
DRW_Attributes attrs_needed;
drw_attributes_clear(&attrs_needed);
ListBase gpu_attrs = GPU_material_attributes(gpu_material);
LISTBASE_FOREACH (const GPUMaterialAttribute *, gpu_attr, &gpu_attrs) {
const char *name = gpu_attr->name;
eCustomDataType type = static_cast<eCustomDataType>(gpu_attr->type);
int layer = -1;
std::optional<bke::AttrDomain> domain;
if (gpu_attr->type == CD_AUTO_FROM_NAME) {
/* We need to deduce what exact layer is used.
*
* We do it based on the specified name.
*/
if (name[0] != '\0') {
layer = CustomData_get_named_layer(&cd_curve, CD_PROP_FLOAT2, name);
type = CD_MTFACE;
domain = bke::AttrDomain::Curve;
if (layer == -1) {
/* Try to match a generic attribute, we use the first attribute domain with a
* matching name. */
if (drw_custom_data_match_attribute(cd_point, name, &layer, &type)) {
domain = bke::AttrDomain::Point;
}
else if (drw_custom_data_match_attribute(cd_curve, name, &layer, &type)) {
domain = bke::AttrDomain::Curve;
}
else {
domain.reset();
layer = -1;
}
}
if (layer == -1) {
continue;
}
}
else {
/* Fall back to the UV layer, which matches old behavior. */
type = CD_MTFACE;
}
}
else {
if (drw_custom_data_match_attribute(cd_curve, name, &layer, &type)) {
domain = bke::AttrDomain::Curve;
}
else if (drw_custom_data_match_attribute(cd_point, name, &layer, &type)) {
domain = bke::AttrDomain::Point;
}
}
switch (type) {
case CD_MTFACE: {
if (layer == -1) {
layer = (name[0] != '\0') ?
CustomData_get_named_layer(&cd_curve, CD_PROP_FLOAT2, name) :
CustomData_get_render_layer(&cd_curve, CD_PROP_FLOAT2);
if (layer != -1) {
domain = bke::AttrDomain::Curve;
}
}
if (layer == -1) {
layer = (name[0] != '\0') ?
CustomData_get_named_layer(&cd_point, CD_PROP_FLOAT2, name) :
CustomData_get_render_layer(&cd_point, CD_PROP_FLOAT2);
if (layer != -1) {
domain = bke::AttrDomain::Point;
}
}
if (layer != -1 && name[0] == '\0' && domain.has_value()) {
name = CustomData_get_layer_name(
domain == bke::AttrDomain::Curve ? &cd_curve : &cd_point, CD_PROP_FLOAT2, layer);
}
if (layer != -1 && domain.has_value()) {
drw_attributes_add_request(&attrs_needed, name, CD_PROP_FLOAT2, layer, *domain);
}
break;
}
case CD_TANGENT:
case CD_ORCO:
break;
case CD_PROP_BYTE_COLOR:
case CD_PROP_COLOR:
case CD_PROP_QUATERNION:
case CD_PROP_FLOAT3:
case CD_PROP_BOOL:
case CD_PROP_INT8:
case CD_PROP_INT32:
case CD_PROP_INT16_2D:
case CD_PROP_INT32_2D:
case CD_PROP_FLOAT:
case CD_PROP_FLOAT2: {
if (layer != -1 && domain.has_value()) {
drw_attributes_add_request(&attrs_needed, name, type, layer, *domain);
}
break;
}
default:
break;
}
}
if (!drw_attributes_overlap(&final_cache.attr_used, &attrs_needed)) {
/* Some new attributes have been added, free all and start over. */
for (const int i : IndexRange(GPU_MAX_ATTR)) {
GPU_VERTBUF_DISCARD_SAFE(final_cache.attributes_buf[i]);
GPU_VERTBUF_DISCARD_SAFE(cache.eval_cache.proc_attributes_buf[i]);
}
drw_attributes_merge(&final_cache.attr_used, &attrs_needed, cache.render_mutex);
}
drw_attributes_merge(&final_cache.attr_used_over_time, &attrs_needed, cache.render_mutex);
}
bool need_tf_update = false;
for (const int i : IndexRange(final_cache.attr_used.num_requests)) {
const DRW_AttributeRequest &request = final_cache.attr_used.requests[i];
if (cache.eval_cache.final.attributes_buf[i] != nullptr) {
continue;
}
if (request.domain == bke::AttrDomain::Point) {
need_tf_update = true;
}
ensure_final_attribute(curves, cache.eval_cache, request, i);
}
return need_tf_update;
}
static void request_attribute(Curves &curves, const char *name)
{
CurvesBatchCache &cache = get_batch_cache(curves);
CurvesEvalFinalCache &final_cache = cache.eval_cache.final;
DRW_Attributes attributes{};
bke::CurvesGeometry &curves_geometry = curves.geometry.wrap();
std::optional<bke::AttributeMetaData> meta_data = curves_geometry.attributes().lookup_meta_data(
name);
if (!meta_data) {
return;
}
const bke::AttrDomain domain = meta_data->domain;
const eCustomDataType type = meta_data->data_type;
const CustomData &custom_data = domain == bke::AttrDomain::Point ? curves.geometry.point_data :
curves.geometry.curve_data;
drw_attributes_add_request(
&attributes, name, type, CustomData_get_named_layer(&custom_data, type, name), domain);
drw_attributes_merge(&final_cache.attr_used, &attributes, cache.render_mutex);
}
void drw_curves_get_attribute_sampler_name(const char *layer_name, char r_sampler_name[32])
{
char attr_safe_name[GPU_MAX_SAFE_ATTR_NAME];
GPU_vertformat_safe_attr_name(layer_name, attr_safe_name, GPU_MAX_SAFE_ATTR_NAME);
/* Attributes use auto-name. */
BLI_snprintf(r_sampler_name, 32, "a%s", attr_safe_name);
}
bool curves_ensure_procedural_data(Curves *curves_id,
CurvesEvalCache **r_cache,
const GPUMaterial *gpu_material,
const int subdiv,
const int thickness_res)
{
const bke::CurvesGeometry &curves = curves_id->geometry.wrap();
bool need_ft_update = false;
CurvesBatchCache &cache = get_batch_cache(*curves_id);
CurvesEvalCache &eval_cache = cache.eval_cache;
if (eval_cache.final.hair_subdiv != subdiv || eval_cache.final.thickres != thickness_res) {
/* If the subdivision or indexing settings have changed, the evaluation cache is cleared. */
clear_final_data(eval_cache.final);
eval_cache.final.hair_subdiv = subdiv;
eval_cache.final.thickres = thickness_res;
}
eval_cache.curves_num = curves.curves_num();
eval_cache.points_num = curves.points_num();
const int steps = 3; /* TODO: don't hard-code? */
eval_cache.final.resolution = 1 << (steps + subdiv);
/* Refreshed on combing and simulation. */
if (eval_cache.proc_point_buf == nullptr || DRW_vbo_requested(eval_cache.proc_point_buf)) {
create_points_position_time_vbo(curves, eval_cache);
need_ft_update = true;
}
/* Refreshed if active layer or custom data changes. */
if (eval_cache.proc_strand_buf == nullptr) {
create_curve_offsets_vbos(curves.points_by_curve(), eval_cache);
}
/* Refreshed only on subdiv count change. */
if (eval_cache.final.proc_buf == nullptr) {
alloc_final_points_vbo(eval_cache);
need_ft_update = true;
}
if (eval_cache.final.proc_hairs == nullptr) {
calc_final_indices(curves, eval_cache, thickness_res);
}
eval_cache.final.thickres = thickness_res;
need_ft_update |= ensure_attributes(*curves_id, cache, gpu_material);
*r_cache = &eval_cache;
return need_ft_update;
}
void DRW_curves_batch_cache_dirty_tag(Curves *curves, int mode)
{
CurvesBatchCache *cache = static_cast<CurvesBatchCache *>(curves->batch_cache);
if (cache == nullptr) {
return;
}
switch (mode) {
case BKE_CURVES_BATCH_DIRTY_ALL:
cache->is_dirty = true;
break;
default:
BLI_assert_unreachable();
}
}
void DRW_curves_batch_cache_validate(Curves *curves)
{
if (!batch_cache_is_dirty(*curves)) {
clear_batch_cache(*curves);
init_batch_cache(*curves);
}
}
void DRW_curves_batch_cache_free(Curves *curves)
{
clear_batch_cache(*curves);
CurvesBatchCache *batch_cache = static_cast<CurvesBatchCache *>(curves->batch_cache);
MEM_delete(batch_cache);
curves->batch_cache = nullptr;
}
void DRW_curves_batch_cache_free_old(Curves *curves, int ctime)
{
CurvesBatchCache *cache = static_cast<CurvesBatchCache *>(curves->batch_cache);
if (cache == nullptr) {
return;
}
bool do_discard = false;
CurvesEvalFinalCache &final_cache = cache->eval_cache.final;
if (drw_attributes_overlap(&final_cache.attr_used_over_time, &final_cache.attr_used)) {
final_cache.last_attr_matching_time = ctime;
}
if (ctime - final_cache.last_attr_matching_time > U.vbotimeout) {
do_discard = true;
}
drw_attributes_clear(&final_cache.attr_used_over_time);
if (do_discard) {
discard_attributes(cache->eval_cache);
}
}
gpu::Batch *DRW_curves_batch_cache_get_edit_points(Curves *curves)
{
CurvesBatchCache &cache = get_batch_cache(*curves);
return DRW_batch_request(&cache.edit_points);
}
gpu::Batch *DRW_curves_batch_cache_get_sculpt_curves_cage(Curves *curves)
{
CurvesBatchCache &cache = get_batch_cache(*curves);
return DRW_batch_request(&cache.sculpt_cage);
}
gpu::Batch *DRW_curves_batch_cache_get_edit_curves_handles(Curves *curves)
{
CurvesBatchCache &cache = get_batch_cache(*curves);
return DRW_batch_request(&cache.edit_handles);
}
gpu::Batch *DRW_curves_batch_cache_get_edit_curves_lines(Curves *curves)
{
CurvesBatchCache &cache = get_batch_cache(*curves);
return DRW_batch_request(&cache.edit_curves_lines);
}
gpu::VertBuf **DRW_curves_texture_for_evaluated_attribute(Curves *curves,
const char *name,
bool *r_is_point_domain)
{
CurvesBatchCache &cache = get_batch_cache(*curves);
CurvesEvalFinalCache &final_cache = cache.eval_cache.final;
request_attribute(*curves, name);
int request_i = -1;
for (const int i : IndexRange(final_cache.attr_used.num_requests)) {
if (STREQ(final_cache.attr_used.requests[i].attribute_name, name)) {
request_i = i;
break;
}
}
if (request_i == -1) {
*r_is_point_domain = false;
return nullptr;
}
switch (final_cache.attr_used.requests[request_i].domain) {
case bke::AttrDomain::Point:
*r_is_point_domain = true;
return &final_cache.attributes_buf[request_i];
case bke::AttrDomain::Curve:
*r_is_point_domain = false;
return &cache.eval_cache.proc_attributes_buf[request_i];
default:
BLI_assert_unreachable();
return nullptr;
}
}
static void create_edit_points_position_vbo(
const bke::CurvesGeometry &curves,
const bke::crazyspace::GeometryDeformation & /*deformation*/,
CurvesBatchCache &cache)
{
static uint attr_id;
static GPUVertFormat format = single_attr_vbo_format(
"pos", GPU_COMP_F32, 3, GPU_FETCH_FLOAT, attr_id);
/* TODO: Deform curves using deformations. */
const Span<float3> positions = curves.evaluated_positions();
GPU_vertbuf_init_with_format(*cache.edit_curves_lines_pos, format);
GPU_vertbuf_data_alloc(*cache.edit_curves_lines_pos, positions.size());
cache.edit_curves_lines_pos->data<float3>().copy_from(positions);
}
void DRW_curves_batch_cache_create_requested(Object *ob)
{
Curves *curves_id = static_cast<Curves *>(ob->data);
Object *ob_orig = DEG_get_original_object(ob);
if (ob_orig == nullptr) {
return;
}
const Curves *curves_orig_id = static_cast<Curves *>(ob_orig->data);
draw::CurvesBatchCache &cache = draw::get_batch_cache(*curves_id);
const bke::CurvesGeometry &curves_orig = curves_orig_id->geometry.wrap();
bool is_edit_data_needed = false;
if (DRW_batch_requested(cache.edit_points, GPU_PRIM_POINTS)) {
DRW_vbo_request(cache.edit_points, &cache.edit_points_pos);
DRW_vbo_request(cache.edit_points, &cache.edit_points_data);
DRW_vbo_request(cache.edit_points, &cache.edit_points_selection);
is_edit_data_needed = true;
}
if (DRW_batch_requested(cache.sculpt_cage, GPU_PRIM_LINE_STRIP)) {
DRW_ibo_request(cache.sculpt_cage, &cache.sculpt_cage_ibo);
DRW_vbo_request(cache.sculpt_cage, &cache.edit_points_pos);
DRW_vbo_request(cache.sculpt_cage, &cache.edit_points_data);
DRW_vbo_request(cache.sculpt_cage, &cache.edit_points_selection);
is_edit_data_needed = true;
}
if (DRW_batch_requested(cache.edit_handles, GPU_PRIM_LINES)) {
DRW_ibo_request(cache.edit_handles, &cache.edit_handles_ibo);
DRW_vbo_request(cache.edit_handles, &cache.edit_points_pos);
DRW_vbo_request(cache.edit_handles, &cache.edit_points_data);
DRW_vbo_request(cache.edit_handles, &cache.edit_points_selection);
is_edit_data_needed = true;
}
if (DRW_batch_requested(cache.edit_curves_lines, GPU_PRIM_LINE_STRIP)) {
DRW_vbo_request(cache.edit_curves_lines, &cache.edit_curves_lines_pos);
DRW_ibo_request(cache.edit_curves_lines, &cache.edit_curves_lines_ibo);
}
const bke::crazyspace::GeometryDeformation deformation =
is_edit_data_needed || DRW_vbo_requested(cache.edit_curves_lines_pos) ?
bke::crazyspace::get_evaluated_curves_deformation(ob, *ob_orig) :
bke::crazyspace::GeometryDeformation();
if (DRW_ibo_requested(cache.sculpt_cage_ibo)) {
create_lines_ibo_no_cyclic(curves_orig.points_by_curve(), *cache.sculpt_cage_ibo);
}
if (DRW_vbo_requested(cache.edit_curves_lines_pos)) {
create_edit_points_position_vbo(curves_orig, deformation, cache);
}
if (DRW_ibo_requested(cache.edit_curves_lines_ibo)) {
create_lines_ibo_with_cyclic(curves_orig.evaluated_points_by_curve(),
curves_orig.cyclic(),
*cache.edit_curves_lines_ibo);
}
if (!is_edit_data_needed) {
return;
}
IndexMaskMemory memory;
const IndexMask bezier_curves = bke::curves::indices_for_type(curves_orig.curve_types(),
curves_orig.curve_type_counts(),
CURVE_TYPE_BEZIER,
curves_orig.curves_range(),
memory);
Array<int> bezier_point_offset_data(bezier_curves.size() + 1);
const OffsetIndices<int> bezier_offsets = offset_indices::gather_selected_offsets(
curves_orig.points_by_curve(), bezier_curves, bezier_point_offset_data);
if (DRW_vbo_requested(cache.edit_points_pos)) {
create_edit_points_position_and_data(
curves_orig, bezier_curves, bezier_offsets, deformation, cache);
}
if (DRW_vbo_requested(cache.edit_points_selection)) {
create_edit_points_selection(curves_orig, bezier_curves, bezier_offsets, cache);
}
if (DRW_ibo_requested(cache.edit_handles_ibo)) {
const IndexMask other_curves = bezier_curves.complement(curves_orig.curves_range(), memory);
calc_edit_handles_ibo(curves_orig, bezier_curves, bezier_offsets, other_curves, cache);
}
}
} // namespace blender::draw