DRW: New Curve Drawing

Implementation of the design task #142969.

This adds the following:
- Exact GPU interpolation of curves of all types.
- Radius attribute support.
- Cyclic curve support.
- Resolution attribute support.
- New Cylinder hair shape type.
![image.png](/attachments/a8e7aea0-b0e5-4694-b660-89fb3df1ddcd)

What changed:
- EEVEE doesn't compute random normals for strand hairs anymore. These are considered legacy now.
- EEVEE now have an internal shadow bias to avoid self shadowing on hair.
- Workbench Curves Strip display option is no longer flat and has better shading.
- Legacy Hair particle system evaluates radius at control points before applying additional subdivision. This now matches Cycles.
- Color Attribute Node without a name do not fetch the active color attribute anymore. This now matches Cycles.

Notes:
- This is not 100% matching the CPU implementation for interpolation (see the epsilons in the tests).
- Legacy Hair Particle points is now stored in local space after interpolation.

The new cylinder shape allows for more correct hair shading in workbench and better intersection in EEVEE.

|      | Strand | Strip | Cylinder |
| ---- | --- | --- | --- |
| Main | ![main_strand.png](/attachments/67d3b792-962c-4272-a92c-1c0c7c6cf8de) | ![main_strip.png](/attachments/f2aa3575-368e-4fbb-b888-74df845918f1) | N/A |
| PR   | ![pr_strand.png](/attachments/cc012483-25f0-491f-a06e-ad3029981d47) | ![pr_strip.png](/attachments/73fa2f5c-5252-4b30-a334-e935ed0fb938) | ![pr_cylinder.png](/attachments/3133b2d4-a6f2-41ee-8e2d-f6fd00db0c8d) |

|      | Strand | Strip | Cylinder |
| ---- | --- | --- | --- |
| Main | ![main_strand_closeup.png](/attachments/730bd79c-6762-446d-819b-3ea47961ff9f) |![main_strip_closeup.png](/attachments/d9ace578-cfeb-4895-9896-3625b6ad7a02) | N/A |
| PR   | ![pr_strand_closeup.png](/attachments/ac8f3b0c-6ef6-4d54-b714-6322f9865036)|![pr_strip_closeup.png](/attachments/8504711a-955b-4ab2-aa3d-c2d114baf9d4)| ![pr_cylinder_closeup.png](/attachments/1e2899a8-0a5c-431f-ac6c-5184d87e9598) |

Cyclic Curve, Mixed curve type, and proper radius support:
![image.png](/attachments/7f0bf05e-62ee-4ae9-aef9-a5599249b8d7)

Test file for attribute lookup: [test_attribute_lookup.blend](/attachments/1d54dd06-379b-4480-a1c5-96adc1953f77)

Follow Up Tasks:
- Correct full tube segments orientation based on tangent and normal attributes
- Correct V resolution property per object
- More attribute type support (currently only color)

TODO:
- [x] Attribute Loading Changes
  - [x] Generic Attributes
  - [x] Length Attribute
  - [x] Intercept Attribute
  - [x] Original Coordinate Attribute
- [x] Cyclic Curves
- [x] Legacy Hair Particle conversion
  - [x] Attribute Loading
  - [x] Additional Subdivision
- [x] Move some function to generic headers (VertBuf, OffsetIndices)
- [x] Fix default UV/Color attribute assignment

Pull Request: https://projects.blender.org/blender/blender/pulls/143180
This commit is contained in:
Clément Foucault
2025-08-27 09:49:43 +02:00
committed by Clément Foucault
parent 15da5dbaf8
commit ba4589e894
122 changed files with 4890 additions and 2315 deletions

View File

@@ -18,7 +18,7 @@ using OpenSubdiv::Osd::PatchArrayVector;
namespace blender::opensubdiv {
// TODO: use Blenlib NonCopyable.
class GPUPatchTable : private NonCopyable<GPUPatchTable> {
class GPUPatchTable : private OpenSubdiv::Osd::NonCopyable<GPUPatchTable> {
public:
~GPUPatchTable();

View File

@@ -690,7 +690,7 @@ def draw_curves_settings(self, context):
class RENDER_PT_eevee_hair(RenderButtonsPanel, Panel):
bl_label = "Curves"
bl_options = {'DEFAULT_CLOSED'}
COMPAT_ENGINES = {'BLENDER_EEVEE'}
COMPAT_ENGINES = {'BLENDER_EEVEE', 'BLENDER_WORKBENCH'}
@classmethod
def poll(cls, context):

View File

@@ -316,7 +316,6 @@ set(GLSL_SRC
engines/eevee/shaders/eevee_ambient_occlusion_lib.glsl
engines/eevee/shaders/eevee_ambient_occlusion_pass_comp.glsl
engines/eevee/shaders/eevee_attributes_curves_lib.glsl
engines/eevee/shaders/eevee_attributes_gpencil_lib.glsl
engines/eevee/shaders/eevee_attributes_mesh_lib.glsl
engines/eevee/shaders/eevee_attributes_pointcloud_lib.glsl
engines/eevee/shaders/eevee_attributes_volume_lib.glsl
@@ -554,8 +553,11 @@ set(GLSL_SRC
intern/shaders/draw_debug_draw_lib.glsl
intern/shaders/draw_debug_shape_lib.glsl
intern/shaders/draw_fxaa_lib.glsl
intern/shaders/draw_curves_interpolation_comp.glsl
intern/shaders/draw_curves_length_intercept_comp.glsl
intern/shaders/draw_curves_lib.glsl
intern/shaders/draw_hair_refine_comp.glsl
intern/shaders/draw_curves_test.glsl
intern/shaders/draw_curves_topology_comp.glsl
intern/shaders/draw_shape_lib.glsl
intern/shaders/draw_command_generate_comp.glsl
intern/shaders/draw_debug_draw_display_frag.glsl
@@ -573,8 +575,10 @@ set(GLSL_SRC
intern/shaders/draw_view_reconstruction_lib.glsl
intern/shaders/draw_visibility_comp.glsl
intern/draw_attribute_shader_shared.hh
intern/draw_command_shared.hh
intern/draw_defines.hh
intern/draw_curves_defines.hh
intern/draw_pointcloud_private.hh
intern/draw_shader_shared.hh
intern/draw_subdiv_defines.hh
@@ -828,6 +832,7 @@ if(WITH_GTESTS)
if(WITH_GPU_DRAW_TESTS)
set(TEST_SRC
tests/draw_pass_test.cc
tests/draw_curves_test.cc
tests/draw_testing.cc
tests/eevee_test.cc

View File

@@ -576,7 +576,7 @@ class SamplerSlots {
{
index_ = 0;
if (ELEM(geometry_type, MAT_GEOM_POINTCLOUD, MAT_GEOM_CURVES)) {
index_ = 1;
index_ = 2;
}
first_reserved_ = MATERIAL_TEXTURE_RESERVED_SLOT_FIRST;
@@ -863,8 +863,31 @@ void ShaderModule::material_create_info_amend(GPUMaterial *gpumat, GPUCodegenOut
info.vertex_out_interfaces_.clear();
}
const char *domain_type_frag = "";
const char *domain_type_vert = "";
switch (geometry_type) {
case MAT_GEOM_MESH:
domain_type_frag = (pipeline_type == MAT_PIPE_VOLUME_MATERIAL) ? "VolumePoint" :
"MeshVertex";
domain_type_vert = "MeshVertex";
break;
case MAT_GEOM_POINTCLOUD:
domain_type_frag = domain_type_vert = "PointCloudPoint";
break;
case MAT_GEOM_CURVES:
domain_type_frag = domain_type_vert = "CurvesPoint";
break;
case MAT_GEOM_WORLD:
domain_type_frag = (pipeline_type == MAT_PIPE_VOLUME_MATERIAL) ? "VolumePoint" :
"WorldPoint";
domain_type_vert = "WorldPoint";
break;
case MAT_GEOM_VOLUME:
domain_type_frag = domain_type_vert = "VolumePoint";
break;
}
std::stringstream attr_load;
attr_load << "void attrib_load()\n";
attr_load << "{\n";
attr_load << (!codegen.attr_load.empty() ? codegen.attr_load : "");
attr_load << "}\n\n";
@@ -872,12 +895,14 @@ void ShaderModule::material_create_info_amend(GPUMaterial *gpumat, GPUCodegenOut
std::stringstream vert_gen, frag_gen;
if (do_vertex_attrib_load) {
vert_gen << global_vars.str() << attr_load.str();
frag_gen << "void attrib_load() {}\n"; /* Placeholder. */
vert_gen << global_vars.str() << "void attrib_load(" << domain_type_vert << " domain)"
<< attr_load.str();
frag_gen << "void attrib_load(" << domain_type_frag << " domain) {}\n"; /* Placeholder. */
}
else {
vert_gen << "void attrib_load() {}\n"; /* Placeholder. */
frag_gen << global_vars.str() << attr_load.str();
vert_gen << "void attrib_load(" << domain_type_vert << " domain) {}\n"; /* Placeholder. */
frag_gen << global_vars.str() << "void attrib_load(" << domain_type_frag << " domain)"
<< attr_load.str();
}
/* TODO(fclem): This should become part of the dependency system. */

View File

@@ -178,22 +178,12 @@ bool VelocityModule::step_object_sync(ObjectKey &object_key,
auto add_cb = [&]() {
VelocityGeometryData data;
if (particle_sys) {
if (inst_.is_viewport()) {
data.pos_buf = DRW_hair_pos_buffer_get(ob, particle_sys, modifier_data);
}
else {
data.pos_buf = draw::hair_pos_buffer_get(inst_.scene, ob, particle_sys, modifier_data);
}
data.pos_buf = draw::hair_pos_buffer_get(inst_.scene, ob, particle_sys, modifier_data);
return data;
}
switch (ob->type) {
case OB_CURVES:
if (inst_.is_viewport()) {
data.pos_buf = DRW_curves_pos_buffer_get(ob);
}
else {
data.pos_buf = draw::curves_pos_buffer_get(inst_.scene, ob);
}
data.pos_buf = draw::curves_pos_buffer_get(ob);
break;
case OB_POINTCLOUD:
data.pos_buf = DRW_pointcloud_position_and_radius_buffer_get(ob);

View File

@@ -155,7 +155,6 @@ set(SRC_GLSL_COMP
set(SRC_GLSL_LIB
eevee_ambient_occlusion_lib.glsl
eevee_attributes_curves_lib.glsl
eevee_attributes_gpencil_lib.glsl
eevee_attributes_mesh_lib.glsl
eevee_attributes_pointcloud_lib.glsl
eevee_attributes_volume_lib.glsl

View File

@@ -7,22 +7,21 @@
#include "draw_object_infos_info.hh"
#ifdef GPU_LIBRARY_SHADER
# define HAIR_SHADER
# define CURVES_SHADER
# define DRW_HAIR_INFO
#endif
SHADER_LIBRARY_CREATE_INFO(draw_modelmat)
SHADER_LIBRARY_CREATE_INFO(draw_hair)
SHADER_LIBRARY_CREATE_INFO(draw_curves)
#include "draw_curves_lib.glsl" /* TODO rename to curve. */
#include "draw_curves_lib.glsl"
#include "draw_model_lib.glsl"
#include "draw_object_infos_lib.glsl"
#include "eevee_nodetree_lib.glsl"
#include "gpu_shader_codegen_lib.glsl"
#include "gpu_shader_math_matrix_lib.glsl"
#include "gpu_shader_math_vector_lib.glsl"
#ifdef GPU_VERTEX_SHADER
/* -------------------------------------------------------------------- */
/** \name Curve
*
@@ -30,62 +29,52 @@ SHADER_LIBRARY_CREATE_INFO(draw_hair)
* Per attribute scope follows loading order.
* \{ */
# ifdef OBINFO_LIB
float3 attr_load_orco(float4 orco)
#ifdef OBINFO_LIB
float3 attr_load_orco(CurvesPoint point, float4 orco, int index)
{
float3 P = hair_get_strand_pos();
float3 lP = transform_point(drw_modelinv(), P);
float3 lP = curves::get_curve_root_pos(point.point_id, point.curve_segment);
return drw_object_orco(lP);
}
# endif
int g_curves_attr_id = 0;
#endif
/* Return the index to use for looking up the attribute value in the sampler
* based on the attribute scope (point or spline). */
int curves_attribute_element_id()
int curves_attribute_element_id(CurvesPoint point, int index)
{
int id = curve_interp_flat.strand_id;
if (drw_curves.is_point_attribute[g_curves_attr_id][0] != 0u) {
# ifdef COMMON_HAIR_LIB
id = hair_get_base_id();
# endif
if (drw_curves.is_point_attribute[index][0] != 0u) {
return int(point.point_id);
}
g_curves_attr_id += 1;
return id;
return point.curve_id;
}
float4 attr_load_tangent(samplerBuffer cd_buf)
float4 attr_load_tangent(CurvesPoint point, samplerBuffer cd_buf, int index)
{
/* Not supported for the moment. */
return float4(0.0f, 0.0f, 0.0f, 1.0f);
}
float3 attr_load_uv(samplerBuffer cd_buf)
float3 attr_load_uv(CurvesPoint point, samplerBuffer cd_buf, int index)
{
return texelFetch(cd_buf, curve_interp_flat.strand_id).rgb;
return texelFetch(cd_buf, point.curve_id).rgb;
}
float4 attr_load_color(samplerBuffer cd_buf)
float4 attr_load_color(CurvesPoint point, samplerBuffer cd_buf, int index)
{
return texelFetch(cd_buf, curve_interp_flat.strand_id).rgba;
return texelFetch(cd_buf, point.curve_id).rgba;
}
float4 attr_load_vec4(samplerBuffer cd_buf)
float4 attr_load_vec4(CurvesPoint point, samplerBuffer cd_buf, int index)
{
return texelFetch(cd_buf, curves_attribute_element_id()).rgba;
return texelFetch(cd_buf, curves_attribute_element_id(point, index)).rgba;
}
float3 attr_load_vec3(samplerBuffer cd_buf)
float3 attr_load_vec3(CurvesPoint point, samplerBuffer cd_buf, int index)
{
return texelFetch(cd_buf, curves_attribute_element_id()).rgb;
return texelFetch(cd_buf, curves_attribute_element_id(point, index)).rgb;
}
float2 attr_load_vec2(samplerBuffer cd_buf)
float2 attr_load_vec2(CurvesPoint point, samplerBuffer cd_buf, int index)
{
return texelFetch(cd_buf, curves_attribute_element_id()).rg;
return texelFetch(cd_buf, curves_attribute_element_id(point, index)).rg;
}
float attr_load_float(samplerBuffer cd_buf)
float attr_load_float(CurvesPoint point, samplerBuffer cd_buf, int index)
{
return texelFetch(cd_buf, curves_attribute_element_id()).r;
return texelFetch(cd_buf, curves_attribute_element_id(point, index)).r;
}
/** \} */
#endif

View File

@@ -1,65 +0,0 @@
/* SPDX-FileCopyrightText: 2022-2023 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
#pragma once
#include "draw_object_infos_info.hh"
#ifdef GPU_LIBRARY_SHADER
SHADER_LIBRARY_CREATE_INFO(draw_modelmat)
#endif
#include "draw_model_lib.glsl"
#include "draw_object_infos_lib.glsl"
#include "gpu_shader_codegen_lib.glsl"
#include "gpu_shader_math_matrix_lib.glsl"
#include "gpu_shader_math_vector_lib.glsl"
/* -------------------------------------------------------------------- */
/** \name Grease Pencil
*
* Grease Pencil objects have one uv and one color attribute layer.
* \{ */
/* Globals to feed the load functions. */
packed_float2 g_uvs;
packed_float4 g_color;
#ifdef OBINFO_LIB
float3 attr_load_orco(float4 orco)
{
float3 lP = drw_point_world_to_object(interp.P);
return drw_object_orco(lP);
}
#endif
float4 attr_load_tangent(float4 tangent)
{
return float4(0.0f, 0.0f, 0.0f, 1.0f);
}
float3 attr_load_uv(float3 dummy)
{
return float3(g_uvs, 0.0f);
}
float4 attr_load_color(float4 dummy)
{
return g_color;
}
float4 attr_load_vec4(float4 attr)
{
return float4(0.0f);
}
float3 attr_load_vec3(float3 attr)
{
return float3(0.0f);
}
float2 attr_load_vec2(float2 attr)
{
return float2(0.0f);
}
float attr_load_float(float attr)
{
return 0.0f;
}
/** \} */

View File

@@ -12,6 +12,7 @@ SHADER_LIBRARY_CREATE_INFO(draw_modelmat)
#include "draw_model_lib.glsl"
#include "draw_object_infos_lib.glsl"
#include "eevee_nodetree_lib.glsl"
#include "gpu_shader_codegen_lib.glsl"
#include "gpu_shader_math_matrix_lib.glsl"
#include "gpu_shader_math_vector_lib.glsl"
@@ -23,7 +24,7 @@ SHADER_LIBRARY_CREATE_INFO(draw_modelmat)
* \{ */
#ifdef OBINFO_LIB
float3 attr_load_orco(float4 orco)
float3 attr_load_orco(MeshVertex vert, float4 orco, int index)
{
# ifdef GPU_VERTEX_SHADER
/* We know when there is no orco layer when orco.w is 1.0 because it uses the generic vertex
@@ -37,32 +38,32 @@ float3 attr_load_orco(float4 orco)
return orco.xyz * 0.5f + 0.5f;
}
#endif
float4 attr_load_tangent(float4 tangent)
float4 attr_load_tangent(MeshVertex vert, float4 tangent, int index)
{
tangent.xyz = safe_normalize(drw_normal_object_to_world(tangent.xyz));
return tangent;
}
float4 attr_load_vec4(float4 attr)
float4 attr_load_vec4(MeshVertex vert, float4 attr, int index)
{
return attr;
}
float3 attr_load_vec3(float3 attr)
float3 attr_load_vec3(MeshVertex vert, float3 attr, int index)
{
return attr;
}
float2 attr_load_vec2(float2 attr)
float2 attr_load_vec2(MeshVertex vert, float2 attr, int index)
{
return attr;
}
float attr_load_float(float attr)
float attr_load_float(MeshVertex vert, float attr, int index)
{
return attr;
}
float4 attr_load_color(float4 attr)
float4 attr_load_color(MeshVertex vert, float4 attr, int index)
{
return attr;
}
float3 attr_load_uv(float3 attr)
float3 attr_load_uv(MeshVertex vert, float3 attr, int index)
{
return attr;
}

View File

@@ -17,6 +17,7 @@ SHADER_LIBRARY_CREATE_INFO(draw_pointcloud)
#include "draw_model_lib.glsl"
#include "draw_object_infos_lib.glsl"
#include "draw_pointcloud_lib.glsl"
#include "eevee_nodetree_lib.glsl"
#include "gpu_shader_codegen_lib.glsl"
#include "gpu_shader_math_matrix_lib.glsl"
#include "gpu_shader_math_vector_lib.glsl"
@@ -28,7 +29,7 @@ SHADER_LIBRARY_CREATE_INFO(draw_pointcloud)
* \{ */
#ifdef OBINFO_LIB
float3 attr_load_orco(float4 orco)
float3 attr_load_orco(PointCloudPoint point, float4 orco, int index)
{
float3 P = pointcloud_get_pos();
float3 lP = transform_point(drw_modelinv(), P);
@@ -36,31 +37,31 @@ float3 attr_load_orco(float4 orco)
}
#endif
float4 attr_load_tangent(samplerBuffer cd_buf)
float4 attr_load_tangent(PointCloudPoint point, samplerBuffer cd_buf, int index)
{
return pointcloud_get_customdata_vec4(cd_buf);
}
float3 attr_load_uv(samplerBuffer cd_buf)
float3 attr_load_uv(PointCloudPoint point, samplerBuffer cd_buf, int index)
{
return pointcloud_get_customdata_vec3(cd_buf);
}
float4 attr_load_color(samplerBuffer cd_buf)
float4 attr_load_color(PointCloudPoint point, samplerBuffer cd_buf, int index)
{
return pointcloud_get_customdata_vec4(cd_buf);
}
float4 attr_load_vec4(samplerBuffer cd_buf)
float4 attr_load_vec4(PointCloudPoint point, samplerBuffer cd_buf, int index)
{
return pointcloud_get_customdata_vec4(cd_buf);
}
float3 attr_load_vec3(samplerBuffer cd_buf)
float3 attr_load_vec3(PointCloudPoint point, samplerBuffer cd_buf, int index)
{
return pointcloud_get_customdata_vec3(cd_buf);
}
float2 attr_load_vec2(samplerBuffer cd_buf)
float2 attr_load_vec2(PointCloudPoint point, samplerBuffer cd_buf, int index)
{
return pointcloud_get_customdata_vec2(cd_buf);
}
float attr_load_float(samplerBuffer cd_buf)
float attr_load_float(PointCloudPoint point, samplerBuffer cd_buf, int index)
{
return pointcloud_get_customdata_float(cd_buf);
}

View File

@@ -12,6 +12,7 @@ SHADER_LIBRARY_CREATE_INFO(draw_modelmat)
#include "draw_model_lib.glsl"
#include "draw_object_infos_lib.glsl"
#include "eevee_nodetree_lib.glsl"
#include "gpu_shader_codegen_lib.glsl"
#include "gpu_shader_math_matrix_lib.glsl"
#include "gpu_shader_math_vector_lib.glsl"
@@ -42,56 +43,51 @@ float3 g_lP = float3(0.0f);
float3 g_wP = float3(0.0f);
# endif
float3 grid_coordinates()
float3 grid_coordinates(int index)
{
# ifdef GRID_ATTRIBUTES
float3 co = (drw_volume.grids_xform[g_attr_id] * float4(g_lP, 1.0f)).xyz;
return (drw_volume.grids_xform[index] * float4(g_lP, 1.0f)).xyz;
# else
/* Only for test shaders. All the runtime shaders require `draw_object_infos` and
* `draw_volume_infos`. */
float3 co = float3(0.0f);
return float3(0.0f);
# endif
g_attr_id += 1;
return co;
}
float3 attr_load_orco(sampler3D tex)
float3 attr_load_orco(VolumePoint point, sampler3D tex, int index)
{
g_attr_id += 1;
# ifdef GRID_ATTRIBUTES
return drw_object_orco(g_lP);
# else
return g_wP;
# endif
}
float4 attr_load_tangent(sampler3D tex)
float4 attr_load_tangent(VolumePoint point, sampler3D tex, int index)
{
g_attr_id += 1;
return float4(0);
}
float4 attr_load_vec4(sampler3D tex)
float4 attr_load_vec4(VolumePoint point, sampler3D tex, int index)
{
return texture(tex, grid_coordinates());
return texture(tex, grid_coordinates(index));
}
float3 attr_load_vec3(sampler3D tex)
float3 attr_load_vec3(VolumePoint point, sampler3D tex, int index)
{
return texture(tex, grid_coordinates()).rgb;
return texture(tex, grid_coordinates(index)).rgb;
}
float2 attr_load_vec2(sampler3D tex)
float2 attr_load_vec2(VolumePoint point, sampler3D tex, int index)
{
return texture(tex, grid_coordinates()).rg;
return texture(tex, grid_coordinates(index)).rg;
}
float attr_load_float(sampler3D tex)
float attr_load_float(VolumePoint point, sampler3D tex, int index)
{
return texture(tex, grid_coordinates()).r;
return texture(tex, grid_coordinates(index)).r;
}
float4 attr_load_color(sampler3D tex)
float4 attr_load_color(VolumePoint point, sampler3D tex, int index)
{
return texture(tex, grid_coordinates());
return texture(tex, grid_coordinates(index));
}
float3 attr_load_uv(sampler3D attr)
float3 attr_load_uv(VolumePoint point, sampler3D attr, int index)
{
g_attr_id += 1;
return float3(0);
}

View File

@@ -4,6 +4,7 @@
#pragma once
#include "eevee_nodetree_lib.glsl"
#include "gpu_shader_codegen_lib.glsl"
#include "gpu_shader_math_matrix_lib.glsl"
#include "gpu_shader_math_vector_lib.glsl"
@@ -14,35 +15,35 @@
* World has no attributes other than orco.
* \{ */
float3 attr_load_orco(float4 orco)
float3 attr_load_orco(WorldPoint point, float4 orco, int index)
{
return -g_data.N;
}
float4 attr_load_tangent(float4 tangent)
float4 attr_load_tangent(WorldPoint point, float4 tangent, int index)
{
return float4(0);
}
float4 attr_load_vec4(float4 attr)
float4 attr_load_vec4(WorldPoint point, float4 attr, int index)
{
return float4(0);
}
float3 attr_load_vec3(float3 attr)
float3 attr_load_vec3(WorldPoint point, float3 attr, int index)
{
return float3(0);
}
float2 attr_load_vec2(float2 attr)
float2 attr_load_vec2(WorldPoint point, float2 attr, int index)
{
return float2(0);
}
float attr_load_float(float attr)
float attr_load_float(WorldPoint point, float attr, int index)
{
return 0.0f;
}
float4 attr_load_color(float4 attr)
float4 attr_load_color(WorldPoint point, float4 attr, int index)
{
return float4(0);
}
float3 attr_load_uv(float3 attr)
float3 attr_load_uv(WorldPoint point, float3 attr, int index)
{
return float3(0);
}

View File

@@ -7,7 +7,7 @@
VERTEX_SHADER_CREATE_INFO(eevee_clip_plane)
VERTEX_SHADER_CREATE_INFO(eevee_geom_curves)
#include "draw_curves_lib.glsl" /* TODO rename to curve. */
#include "draw_curves_lib.glsl"
#include "draw_model_lib.glsl"
#include "eevee_attributes_curves_lib.glsl"
#include "eevee_nodetree_vert_lib.glsl"
@@ -24,27 +24,31 @@ void main()
init_interface();
bool is_persp = (drw_view().winmat[3][3] == 0.0f);
hair_get_pos_tan_binor_time(is_persp,
drw_modelinv(),
drw_view().viewinv[3].xyz,
drw_view().viewinv[2].xyz,
interp.P,
curve_interp.tangent,
curve_interp.binormal,
curve_interp.time,
curve_interp.thickness,
curve_interp.time_width);
const curves::Point ls_pt = curves::point_get(uint(gl_VertexID));
const curves::Point ws_pt = curves::object_to_world(ls_pt, drw_modelmat());
const float3 V = drw_world_incident_vector(ws_pt.P);
const curves::ShapePoint pt = curves::shape_point_get(ws_pt, V);
interp.P = pt.P;
/* Correct normal is derived in fragment shader. */
interp.N = pt.curve_N;
curve_interp.binormal = pt.curve_B;
curve_interp.tangent = pt.curve_T;
/* Final radius is used for correct normal interpolation. */
curve_interp.radius = ws_pt.radius;
/* Scaled by radius for correct interpolation. */
curve_interp.time_width = ws_pt.azimuthal_offset * ws_pt.radius;
/* Note: Used for attribute loading. */
curve_interp.point_id = float(ws_pt.point_id);
curve_interp_flat.strand_id = ws_pt.curve_id;
interp.N = cross(curve_interp.tangent, curve_interp.binormal);
curve_interp_flat.strand_id = hair_get_strand_id();
curve_interp.barycentric_coords = hair_get_barycentric();
#ifdef MAT_VELOCITY
/* Due to the screen space nature of the vertex positioning, we compute only the motion of curve
* strand, not its cylinder. Otherwise we would add the rotation velocity. */
int vert_idx = hair_get_base_id();
int vert_idx = ws_pt.point_id;
float3 prv, nxt;
float3 pos = hair_get_point(vert_idx).position;
float3 pos = ls_pt.P;
velocity_local_pos_get(pos, vert_idx, prv, nxt);
/* FIXME(fclem): Evaluating before displacement avoid displacement being treated as motion but
* ignores motion from animated displacement. Supporting animated displacement motion vectors
@@ -55,10 +59,16 @@ void main()
#endif
init_globals();
attrib_load();
attrib_load(CurvesPoint(ws_pt.curve_id, ws_pt.point_id, ws_pt.curve_segment));
interp.P += nodetree_displacement();
#ifdef MAT_SHADOW
/* Since curves always face the view, camera and shadow orientation don't match.
* Apply a bias to avoid self-shadow issues. */
interp.P -= V * ws_pt.radius;
#endif
#ifdef MAT_CLIP_PLANE
clip_interp.clip_distance = dot(clip_plane.plane, float4(interp.P, 1.0f));
#endif

View File

@@ -37,7 +37,7 @@ void main()
#endif
init_globals();
attrib_load();
attrib_load(MeshVertex(0));
interp.P += nodetree_displacement();

View File

@@ -47,7 +47,7 @@ void main()
#endif
init_globals();
attrib_load();
attrib_load(PointCloudPoint(0));
interp.P += nodetree_displacement();

View File

@@ -9,7 +9,8 @@
#include "eevee_nodetree_lib.glsl"
/* Loading of the attributes into GlobalData. */
void attrib_load() {}
void attrib_load(WorldPoint domain) {}
void attrib_load(VolumePoint domain) {}
/* Material graph connected to the displacement output. */
float3 nodetree_displacement()

View File

@@ -18,6 +18,39 @@ SHADER_LIBRARY_CREATE_INFO(eevee_utility_texture)
#include "gpu_shader_math_vector_lib.glsl"
#include "gpu_shader_utildefines_lib.glsl"
struct MeshVertex {
int _pad; /* TODO(fclem): Add explicit attribute loading for mesh. */
METAL_CONSTRUCTOR_1(MeshVertex, int, _pad)
};
struct PointCloudPoint {
int _pad; /* TODO(fclem): Add explicit attribute loading for mesh. */
METAL_CONSTRUCTOR_1(PointCloudPoint, int, _pad)
};
struct CurvesPoint {
int curve_id;
int point_id;
int curve_segment;
METAL_CONSTRUCTOR_3(CurvesPoint, int, curve_id, int, point_id, int, curve_segment)
};
struct WorldPoint {
int _pad;
METAL_CONSTRUCTOR_1(WorldPoint, int, _pad)
};
struct VolumePoint {
int _pad; /* TODO(fclem): Add explicit attribute loading for volumes. */
METAL_CONSTRUCTOR_1(VolumePoint, int, _pad)
};
struct GPencilPoint {
int _pad;
METAL_CONSTRUCTOR_1(GPencilPoint, int, _pad)
};
packed_float3 g_emission;
packed_float3 g_transmittance;
float g_holdout;
@@ -412,7 +445,6 @@ float ambient_occlusion_eval(float3 normal,
}
#ifndef GPU_METAL
void attrib_load();
Closure nodetree_surface(float closure_rand);
Closure nodetree_volume();
float3 nodetree_displacement();

View File

@@ -9,7 +9,10 @@
#include "eevee_nodetree_lib.glsl"
/* Loading of the attributes into GlobalData. */
void attrib_load() {}
void attrib_load(MeshVertex domain) {}
void attrib_load(PointCloudPoint domain) {}
void attrib_load(CurvesPoint domain) {}
void attrib_load(GPencilPoint domain) {}
/* Material graph connected to the displacement output. */
float3 nodetree_displacement()

View File

@@ -37,21 +37,8 @@ void init_globals_curves()
{
#if defined(MAT_GEOM_CURVES)
/* Shade as a cylinder. */
float cos_theta = curve_interp.time_width / curve_interp.thickness;
# if defined(GPU_FRAGMENT_SHADER)
if (hairThicknessRes == 1) {
# ifdef EEVEE_UTILITY_TX
/* Random cosine normal distribution on the hair surface. */
float noise = utility_tx_fetch(utility_tx, gl_FragCoord.xy, UTIL_BLUE_NOISE_LAYER).x;
# ifdef EEVEE_SAMPLING_DATA
/* Needs to check for SAMPLING_DATA, otherwise surfel shader validation fails. */
noise = fract(noise + sampling_rng_1D_get(SAMPLING_CURVES_U));
# endif
cos_theta = noise * 2.0f - 1.0f;
# endif
}
# endif
float sin_theta = sqrt(max(0.0f, 1.0f - cos_theta * cos_theta));
float cos_theta = curve_interp.time_width / curve_interp.radius;
float sin_theta = sin_from_cos(cos_theta);
g_data.N = g_data.Ni = normalize(interp.N * sin_theta + curve_interp.binormal * cos_theta);
/* Costly, but follows cycles per pixel tangent space (not following curve shape). */
@@ -61,11 +48,11 @@ void init_globals_curves()
g_data.curve_N = safe_normalize(cross(g_data.curve_T, g_data.curve_B));
g_data.is_strand = true;
g_data.hair_time = curve_interp.time;
g_data.hair_thickness = curve_interp.thickness;
g_data.hair_diameter = curve_interp.radius * 2.0;
g_data.hair_strand_id = curve_interp_flat.strand_id;
# if defined(USE_BARYCENTRICS) && defined(GPU_FRAGMENT_SHADER)
g_data.barycentric_coords = hair_resolve_barycentric(curve_interp.barycentric_coords);
g_data.barycentric_coords.y = fract(curve_interp.point_id);
g_data.barycentric_coords.x = 1.0 - g_data.barycentric_coords.y;
# endif
#endif
}
@@ -78,8 +65,7 @@ void init_globals()
g_data.N = safe_normalize(interp.N);
g_data.Ng = g_data.N;
g_data.is_strand = false;
g_data.hair_time = 0.0f;
g_data.hair_thickness = 0.0f;
g_data.hair_diameter = 0.0f;
g_data.hair_strand_id = 0;
#if defined(MAT_SHADOW)
g_data.ray_type = RAY_TYPE_SHADOW;

View File

@@ -31,8 +31,7 @@ GlobalData init_globals(float3 wP)
surf.N = float3(0.0f);
surf.Ng = float3(0.0f);
surf.is_strand = false;
surf.hair_time = 0.0f;
surf.hair_thickness = 0.0f;
surf.hair_diameter = 0.0f;
surf.hair_strand_id = 0;
surf.barycentric_coords = float2(0.0f);
surf.barycentric_dists = float3(0.0f);
@@ -68,7 +67,7 @@ VolumeProperties eval_froxel(int3 froxel, float jitter)
#endif
g_data = init_globals(wP);
attrib_load();
attrib_load(VolumePoint(0));
nodetree_volume();
#if defined(MAT_GEOM_VOLUME)

View File

@@ -37,7 +37,7 @@ void main()
g_data.N = drw_normal_view_to_world(drw_view_incident_vector(interp.P));
g_data.Ng = g_data.N;
g_data.P = -g_data.N;
attrib_load();
attrib_load(WorldPoint(0));
nodetree_surface(0.0f);

View File

@@ -112,7 +112,7 @@ float4 velocity_resolve(sampler2D vector_tx, int2 texel, float depth)
/**
* Given a triple of position, compute the previous and next motion vectors.
* Returns a tuple of world space motion deltas.
* Returns a tuple of local space motion deltas.
*/
void velocity_local_pos_get(float3 lP, int vert_id, out float3 lP_prev, out float3 lP_next)
{

View File

@@ -15,7 +15,7 @@
# include "eevee_common_info.hh"
# include "eevee_volume_info.hh"
# define HAIR_SHADER
# define CURVES_SHADER
# define DRW_HAIR_INFO
# define POINTCLOUD_SHADER
@@ -91,12 +91,12 @@ ADDITIONAL_INFO(draw_view)
GPU_SHADER_CREATE_END()
GPU_SHADER_NAMED_INTERFACE_INFO(eevee_surf_curve_iface, curve_interp)
SMOOTH(float2, barycentric_coords)
SMOOTH(float3, tangent)
SMOOTH(float3, binormal)
SMOOTH(float, time)
SMOOTH(float, time_width)
SMOOTH(float, thickness)
SMOOTH(float, radius)
SMOOTH(float, point_id) /* Smooth to be used for barycentric. */
GPU_SHADER_NAMED_INTERFACE_END(curve_interp)
GPU_SHADER_NAMED_INTERFACE_INFO(eevee_surf_curve_flat_iface, curve_interp_flat)
FLAT(int, strand_id)
@@ -113,7 +113,7 @@ ADDITIONAL_INFO(draw_modelmat)
ADDITIONAL_INFO(draw_object_infos)
ADDITIONAL_INFO(draw_resource_id_varying)
ADDITIONAL_INFO(draw_view)
ADDITIONAL_INFO(draw_hair)
ADDITIONAL_INFO(draw_curves)
ADDITIONAL_INFO(draw_curves_infos)
GPU_SHADER_CREATE_END()

View File

@@ -227,14 +227,17 @@ class AttributeViewer : Overlay {
{
if (attribute_type_supports_viewer_overlay(meta_data->data_type)) {
bool is_point_domain;
gpu::VertBuf **texture = DRW_curves_texture_for_evaluated_attribute(
&curves_id, ".viewer", &is_point_domain);
auto &sub = *curves_sub_;
gpu::Batch *batch = curves_sub_pass_setup(sub, state.scene, ob_ref.object);
sub.push_constant("opacity", opacity);
sub.push_constant("is_point_domain", is_point_domain);
sub.bind_texture("color_tx", *texture);
sub.draw(batch, manager.unique_handle(ob_ref));
bool is_valid;
gpu::VertBufPtr &texture = DRW_curves_texture_for_evaluated_attribute(
&curves_id, ".viewer", is_point_domain, is_valid);
if (is_valid) {
auto &sub = *curves_sub_;
gpu::Batch *batch = curves_sub_pass_setup(sub, state.scene, ob_ref.object);
sub.push_constant("opacity", opacity);
sub.push_constant("is_point_domain", is_point_domain);
sub.bind_texture("color_tx", texture);
sub.draw(batch, manager.unique_handle(ob_ref));
}
}
}
break;

View File

@@ -128,16 +128,17 @@ class Sculpts : Overlay {
if (show_mask_ && !everything_selected(curves)) {
/* Retrieve the location of the texture. */
bool is_point_domain;
gpu::VertBuf **select_attr_buf = DRW_curves_texture_for_evaluated_attribute(
&curves, ".selection", &is_point_domain);
if (select_attr_buf) {
bool is_valid;
gpu::VertBufPtr &select_attr_buf = DRW_curves_texture_for_evaluated_attribute(
&curves, ".selection", is_point_domain, is_valid);
if (is_valid) {
/* Evaluate curves and their attributes if necessary. */
gpu::Batch *geometry = curves_sub_pass_setup(*curves_ps_, state.scene, ob_ref.object);
if (*select_attr_buf) {
if (select_attr_buf.get()) {
ResourceHandleRange handle = manager.unique_handle(ob_ref);
curves_ps_->push_constant("is_point_domain", is_point_domain);
curves_ps_->bind_texture("selection_tx", *select_attr_buf);
curves_ps_->bind_texture("selection_tx", select_attr_buf);
curves_ps_->draw(geometry, handle);
}
}

View File

@@ -13,7 +13,7 @@
# include "overlay_shader_shared.hh"
# define HAIR_SHADER
# define CURVES_SHADER
# define DRW_HAIR_INFO
# define POINTCLOUD_SHADER
@@ -810,7 +810,8 @@ OVERLAY_INFO_VARIATIONS_MODELMAT(overlay_depth_pointcloud, overlay_depth_pointcl
GPU_SHADER_CREATE_INFO(overlay_depth_curves_base)
VERTEX_SOURCE("overlay_depth_only_curves_vert.glsl")
FRAGMENT_SOURCE("overlay_depth_only_frag.glsl")
ADDITIONAL_INFO(draw_hair)
ADDITIONAL_INFO(draw_curves)
ADDITIONAL_INFO(draw_curves_infos)
ADDITIONAL_INFO(draw_globals)
ADDITIONAL_INFO(draw_view)
GPU_SHADER_CREATE_END()

View File

@@ -15,7 +15,7 @@
# include "overlay_shader_shared.hh"
# define HAIR_SHADER
# define CURVES_SHADER
# define DRW_HAIR_INFO
# define POINTCLOUD_SHADER
@@ -65,7 +65,8 @@ VERTEX_SOURCE("overlay_outline_prepass_curves_vert.glsl")
ADDITIONAL_INFO(draw_view)
ADDITIONAL_INFO(draw_modelmat)
ADDITIONAL_INFO(draw_globals)
ADDITIONAL_INFO(draw_hair)
ADDITIONAL_INFO(draw_curves)
ADDITIONAL_INFO(draw_curves_infos)
ADDITIONAL_INFO(draw_object_infos)
ADDITIONAL_INFO(overlay_outline_prepass)
GPU_SHADER_CREATE_END()

View File

@@ -9,7 +9,7 @@
# include "draw_object_infos_info.hh"
# include "draw_view_info.hh"
# define HAIR_SHADER
# define CURVES_SHADER
# define DRW_HAIR_INFO
#endif
@@ -23,7 +23,7 @@ GPU_SHADER_CREATE_INFO(overlay_sculpt_curves_selection)
DO_STATIC_COMPILATION()
PUSH_CONSTANT(bool, is_point_domain)
PUSH_CONSTANT(float, selection_opacity)
SAMPLER(1, samplerBuffer, selection_tx)
SAMPLER(2, samplerBuffer, selection_tx)
VERTEX_OUT(overlay_sculpt_curves_selection_iface)
VERTEX_SOURCE("overlay_sculpt_curves_selection_vert.glsl")
FRAGMENT_SOURCE("overlay_sculpt_curves_selection_frag.glsl")
@@ -31,7 +31,8 @@ FRAGMENT_OUT(0, float4, out_color)
ADDITIONAL_INFO(draw_view)
ADDITIONAL_INFO(draw_modelmat)
ADDITIONAL_INFO(draw_globals)
ADDITIONAL_INFO(draw_hair)
ADDITIONAL_INFO(draw_curves)
ADDITIONAL_INFO(draw_curves_infos)
GPU_SHADER_CREATE_END()
OVERLAY_INFO_CLIP_VARIATION(overlay_sculpt_curves_selection)

View File

@@ -10,7 +10,7 @@
# include "draw_view_info.hh"
# include "overlay_common_info.hh"
# define HAIR_SHADER
# define CURVES_SHADER
# define DRW_HAIR_INFO
# define POINTCLOUD_SHADER
@@ -84,11 +84,12 @@ VERTEX_SOURCE("overlay_viewer_attribute_curves_vert.glsl")
FRAGMENT_SOURCE("overlay_viewer_attribute_frag.glsl")
FRAGMENT_OUT(0, float4, out_color)
FRAGMENT_OUT(1, float4, line_output)
SAMPLER(1, samplerBuffer, color_tx)
SAMPLER(2, samplerBuffer, color_tx)
PUSH_CONSTANT(bool, is_point_domain)
VERTEX_OUT(overlay_viewer_attribute_iface)
ADDITIONAL_INFO(overlay_viewer_attribute_common)
ADDITIONAL_INFO(draw_hair)
ADDITIONAL_INFO(draw_curves)
ADDITIONAL_INFO(draw_curves_infos)
ADDITIONAL_INFO(draw_view)
ADDITIONAL_INFO(draw_globals)
ADDITIONAL_INFO(draw_modelmat)

View File

@@ -16,19 +16,9 @@ void main()
{
select_id_set(drw_custom_id());
bool is_persp = (drw_view().winmat[3][3] == 0.0f);
float time, thick_time, thickness;
float3 world_pos, tangent, binor;
hair_get_pos_tan_binor_time(is_persp,
drw_modelinv(),
drw_view().viewinv[3].xyz,
drw_view().viewinv[2].xyz,
world_pos,
tangent,
binor,
time,
thickness,
thick_time);
const curves::Point ls_pt = curves::point_get(uint(gl_VertexID));
const curves::Point ws_pt = curves::object_to_world(ls_pt, drw_modelmat());
float3 world_pos = curves::shape_point_get(ws_pt, drw_world_incident_vector(ws_pt.P)).P;
gl_Position = drw_point_world_to_homogenous(world_pos);

View File

@@ -33,41 +33,17 @@ uint outline_colorid_get()
void main()
{
bool is_persp = (drw_view().winmat[3][3] == 0.0f);
float time, thickness;
float3 center_wpos, tangent, binor;
hair_get_center_pos_tan_binor_time(is_persp,
drw_view().viewinv[3].xyz,
drw_view().viewinv[2].xyz,
center_wpos,
tangent,
binor,
time,
thickness);
float3 world_pos;
if (hairThicknessRes > 1) {
/* Calculate the thickness, thick-time, world-position taken into account the outline. */
float outline_width = drw_point_world_to_homogenous(center_wpos).w * 1.25f *
uniform_buf.size_viewport_inv.y * drw_view().wininv[1][1];
thickness += outline_width;
float thick_time = float(gl_VertexID % hairThicknessRes) / float(hairThicknessRes - 1);
thick_time = thickness * (thick_time * 2.0f - 1.0f);
/* Take object scale into account.
* NOTE: This only works fine with uniform scaling. */
float scale = 1.0f / length(to_float3x3(drw_modelinv()) * binor);
world_pos = center_wpos + binor * thick_time * scale;
}
else {
world_pos = center_wpos;
}
const curves::Point ls_pt = curves::point_get(uint(gl_VertexID));
const curves::Point ws_pt = curves::object_to_world(ls_pt, drw_modelmat());
#if 0 /* TODO(fclem) Unsure what it does. Verify if we can get rid of it. */
/* Calculate the thickness, thick-time, world-position taken into account the outline. */
float outline_width = drw_point_world_to_homogenous(ws_pt.P).w * 1.25f *
uniform_buf.size_viewport_inv.y * drw_view().wininv[1][1];
#endif
float3 world_pos = curves::shape_point_get(ws_pt, drw_world_incident_vector(ws_pt.P)).P;
gl_Position = drw_point_world_to_homogenous(world_pos);
#ifdef USE_GEOM
vert.pos = drw_point_world_to_view(world_pos);
#endif
/* Small bias to always be on top of the geom. */
gl_Position.z -= 1e-3f;

View File

@@ -35,9 +35,6 @@ void main()
float3 world_pos = drw_point_object_to_world(pos);
gl_Position = drw_point_world_to_homogenous(world_pos);
#ifdef USE_GEOM
vert.pos = drw_point_world_to_view(world_pos);
#endif
/* Small bias to always be on top of the geom. */
gl_Position.z -= 1e-3f;

View File

@@ -11,33 +11,23 @@ VERTEX_SHADER_CREATE_INFO(overlay_sculpt_curves_selection)
#include "draw_view_clipping_lib.glsl"
#include "draw_view_lib.glsl"
float retrieve_selection()
float retrieve_selection(const curves::Point pt)
{
if (is_point_domain) {
return texelFetch(selection_tx, hair_get_base_id()).r;
return texelFetch(selection_tx, pt.point_id).r;
}
return texelFetch(selection_tx, hair_get_strand_id()).r;
return texelFetch(selection_tx, pt.curve_id).r;
}
void main()
{
bool is_persp = (drw_view().winmat[3][3] == 0.0f);
float time, thick_time, thickness;
float3 world_pos, tangent, binor;
hair_get_pos_tan_binor_time(is_persp,
drw_modelinv(),
drw_view().viewinv[3].xyz,
drw_view().viewinv[2].xyz,
world_pos,
tangent,
binor,
time,
thickness,
thick_time);
const curves::Point ls_pt = curves::point_get(uint(gl_VertexID));
const curves::Point ws_pt = curves::object_to_world(ls_pt, drw_modelmat());
float3 world_pos = curves::shape_point_get(ws_pt, drw_world_incident_vector(ws_pt.P)).P;
gl_Position = drw_point_world_to_homogenous(world_pos);
mask_weight = 1.0f - (selection_opacity - retrieve_selection() * selection_opacity);
mask_weight = 1.0f - (selection_opacity - retrieve_selection(ws_pt) * selection_opacity);
view_clipping_distances(world_pos);
}

View File

@@ -13,25 +13,17 @@ VERTEX_SHADER_CREATE_INFO(overlay_viewer_attribute_curves)
void main()
{
bool is_persp = (drw_view().winmat[3][3] == 0.0f);
float time, thick_time, thickness;
float3 world_pos, tangent, binor;
hair_get_pos_tan_binor_time(is_persp,
drw_modelinv(),
drw_view().viewinv[3].xyz,
drw_view().viewinv[2].xyz,
world_pos,
tangent,
binor,
time,
thickness,
thick_time);
const curves::Point ls_pt = curves::point_get(uint(gl_VertexID));
const curves::Point ws_pt = curves::object_to_world(ls_pt, drw_modelmat());
float3 world_pos = curves::shape_point_get(ws_pt, drw_world_incident_vector(ws_pt.P)).P;
gl_Position = drw_point_world_to_homogenous(world_pos);
if (is_point_domain) {
final_color = texelFetch(color_tx, hair_get_base_id());
final_color = texelFetch(color_tx, int(ws_pt.point_id));
}
else {
final_color = texelFetch(color_tx, hair_get_strand_id());
final_color = texelFetch(color_tx, int(ws_pt.curve_id));
}
}

View File

@@ -20,7 +20,7 @@
# define WORKBENCH_COLOR_VERTEX
# define WORKBENCH_LIGHTING_MATCAP
# define HAIR_SHADER
# define CURVES_SHADER
# define DRW_HAIR_INFO
# define POINTCLOUD_SHADER
@@ -49,7 +49,8 @@ SAMPLER_FREQ(WB_CURVES_UV_SLOT, samplerBuffer, au, BATCH)
PUSH_CONSTANT(int, emitter_object_id)
VERTEX_SOURCE("workbench_prepass_hair_vert.glsl")
ADDITIONAL_INFO(draw_modelmat_with_custom_id)
ADDITIONAL_INFO(draw_hair)
ADDITIONAL_INFO(draw_curves)
ADDITIONAL_INFO(draw_curves_infos)
GPU_SHADER_CREATE_END()
GPU_SHADER_CREATE_INFO(workbench_pointcloud)

View File

@@ -27,13 +27,12 @@ float integer_noise(int n)
return (float(nn) / 1073741824.0f);
}
float3 workbench_hair_random_normal(float3 tangent, float3 binor, float rand)
float3 workbench_hair_random_normal(float3 tangent, float3 binor, float3 nor, float rand)
{
/* To "simulate" anisotropic shading, randomize hair normal per strand. */
float3 nor = cross(tangent, binor);
nor = normalize(mix(nor, -tangent, rand * 0.1f));
float cos_theta = (rand * 2.0f - 1.0f) * 0.2f;
float sin_theta = sqrt(max(0.0f, 1.0f - cos_theta * cos_theta));
float sin_theta = sin_from_cos(cos_theta);
nor = nor * sin_theta + binor * cos_theta;
return nor;
}
@@ -47,41 +46,43 @@ void workbench_hair_random_material(float rand,
rand -= 0.5f;
rand *= 0.1f;
/* Add some variation to the hairs to avoid uniform look. */
metallic = clamp(metallic + rand, 0.0f, 1.0f);
roughness = clamp(roughness + rand, 0.0f, 1.0f);
metallic = saturate(metallic + rand);
roughness = saturate(roughness + rand);
/* Modulate by color intensity to reduce very high contrast when color is dark. */
color = clamp(color + rand * (color + 0.05f), 0.0f, 1.0f);
color = saturate(color + rand * (color + 0.05f));
}
void main()
{
bool is_persp = (drw_view().winmat[3][3] == 0.0f);
float time = 0.0f, thick_time = 0.0f, thickness = 0.0f;
float3 world_pos, tangent, binor;
hair_get_pos_tan_binor_time(is_persp,
drw_modelinv(),
drw_view().viewinv[3].xyz,
drw_view().viewinv[2].xyz,
world_pos,
tangent,
binor,
time,
thickness,
thick_time);
const curves::Point ls_pt = curves::point_get(uint(gl_VertexID));
const curves::Point ws_pt = curves::object_to_world(ls_pt, drw_modelmat());
const curves::ShapePoint pt = curves::shape_point_get(ws_pt, drw_world_incident_vector(ws_pt.P));
float3 world_pos = pt.P;
gl_Position = drw_point_world_to_homogenous(world_pos);
float hair_rand = integer_noise(hair_get_strand_id());
float3 nor = workbench_hair_random_normal(tangent, binor, hair_rand);
float hair_rand = integer_noise(ws_pt.curve_id);
float3 nor = pt.N;
if (drw_curves.half_cylinder_face_count == 1) {
/* Very cheap smooth normal using attribute interpolator.
* Using the correct normals over the cylinder (-1..1) leads to unwanted result as the
* interpolation is not spherical but linear. So we use a smaller range (-SQRT2..SQRT2) in
* which the linear interpolation is close enough to the desired result. */
nor = pt.N + pt.curve_N;
}
else if (drw_curves.half_cylinder_face_count == 0) {
nor = workbench_hair_random_normal(pt.curve_T, pt.curve_B, pt.curve_N, hair_rand);
}
view_clipping_distances(world_pos);
uv_interp = hair_get_customdata_vec2(au);
uv_interp = curves::get_customdata_vec2(ws_pt.curve_id, au);
normal_interp = normalize(drw_normal_world_to_view(nor));
workbench_material_data_get(int(drw_custom_id()),
hair_get_customdata_vec3(ac),
curves::get_customdata_vec3(ws_pt.curve_id, ac),
color_interp,
alpha_interp,
_roughness,

View File

@@ -7,13 +7,13 @@
/* Resources bind slots. */
/* Textures. */
/* Slot 0 is reserved by draw_hair. */
#define WB_MATCAP_SLOT 1
#define WB_TEXTURE_SLOT 2
#define WB_TILE_ARRAY_SLOT 3
#define WB_TILE_DATA_SLOT 4
#define WB_CURVES_UV_SLOT 5
#define WB_CURVES_COLOR_SLOT 6
/* Slot 0-1 are reserved by curves and pointcloud attributes. */
#define WB_MATCAP_SLOT 2
#define WB_TEXTURE_SLOT 3
#define WB_TILE_ARRAY_SLOT 4
#define WB_TILE_DATA_SLOT 5
#define WB_CURVES_UV_SLOT 6
#define WB_CURVES_COLOR_SLOT 7
/* UBOs (Storage buffers in Workbench Next). */
#define WB_MATERIAL_SLOT 0

View File

@@ -0,0 +1,103 @@
/* SPDX-FileCopyrightText: 2025 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
#pragma once
#ifndef GPU_SHADER
# include "GPU_shader_shared_utils.hh"
#endif
/* Copy of DNA enum in `DNA_curves_types.h`. */
enum CurveType : uint32_t {
CURVE_TYPE_CATMULL_ROM = 0u,
CURVE_TYPE_POLY = 1u,
CURVE_TYPE_BEZIER = 2u,
CURVE_TYPE_NURBS = 3u,
};
/* -------------------------------------------------------------------- */
/** \name Generic Attribute
*
* These types are necessary to overcome the issue with float3 alignment on GPU.
* Having all types using the same interface allows the usage of templates to load and manipulate
* them inside the shaders.
* \{ */
struct StoredFloat4 {
float x;
float y;
float z;
float w;
};
struct StoredFloat3 {
float x;
float y;
float z;
};
struct StoredFloat2 {
float x;
float y;
};
struct StoredFloat {
float x;
};
float4 load_data(StoredFloat4 data)
{
return float4(data.x, data.y, data.z, data.w);
}
float3 load_data(StoredFloat3 data)
{
return float3(data.x, data.y, data.z);
}
float2 load_data(StoredFloat2 data)
{
return float2(data.x, data.y);
}
float load_data(StoredFloat data)
{
return float(data.x);
}
StoredFloat4 as_data(float4 interp)
{
StoredFloat4 data;
data.x = interp.x;
data.y = interp.y;
data.z = interp.z;
data.w = interp.w;
return data;
}
StoredFloat3 as_data(float3 interp)
{
StoredFloat3 data;
data.x = interp.x;
data.y = interp.y;
data.z = interp.z;
return data;
}
StoredFloat2 as_data(float2 interp)
{
StoredFloat2 data;
data.x = interp.x;
data.y = interp.y;
return data;
}
StoredFloat as_data(float interp)
{
StoredFloat data;
data.x = interp;
return data;
}
/** \} */

View File

@@ -14,6 +14,8 @@
#include "BLI_span.hh"
#include "BLI_string_ref.hh"
#include "GPU_vertex_buffer.hh"
struct GPUMaterial;
namespace blender::gpu {
class Batch;
@@ -138,9 +140,10 @@ blender::gpu::Batch *DRW_lattice_batch_cache_get_edit_verts(Lattice *lt);
* \return A pointer to location where the texture will be
* stored, which will be filled by #DRW_shgroup_curves_create_sub.
*/
gpu::VertBuf **DRW_curves_texture_for_evaluated_attribute(Curves *curves,
StringRef name,
bool *r_is_point_domain);
blender::gpu::VertBufPtr &DRW_curves_texture_for_evaluated_attribute(Curves *curves,
StringRef name,
bool &r_is_point_domain,
bool &r_valid_attribute);
blender::gpu::Batch *DRW_curves_batch_cache_get_edit_points(Curves *curves);
blender::gpu::Batch *DRW_curves_batch_cache_get_sculpt_curves_cage(Curves *curves);

View File

@@ -20,7 +20,6 @@
#include "BLI_span.hh"
#include "BLI_string_utf8.h"
#include "BLI_task.hh"
#include "BLI_utildefines.h"
#include "DNA_curves_types.h"
#include "DNA_object_types.h"
@@ -44,7 +43,9 @@
#include "draw_attributes.hh"
#include "draw_cache_impl.hh" /* own include */
#include "draw_cache_inline.hh"
#include "draw_context_private.hh"
#include "draw_curves_private.hh" /* own include */
#include "draw_hair_private.hh"
namespace blender::draw {
@@ -117,19 +118,6 @@ static void init_batch_cache(Curves &curves)
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]);
}
eval_cache.final.attr_used.clear();
}
static void clear_edit_data(CurvesBatchCache *cache)
{
/* TODO: more granular update tagging. */
@@ -149,26 +137,47 @@ static void clear_edit_data(CurvesBatchCache *cache)
GPU_BATCH_DISCARD_SAFE(cache->edit_curves_lines);
}
static void clear_final_data(CurvesEvalFinalCache &final_cache)
void CurvesEvalCache::discard_attributes()
{
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]);
for (const int i : IndexRange(GPU_MAX_ATTR)) {
this->evaluated_attributes_buf[i].reset();
}
for (const int i : IndexRange(GPU_MAX_ATTR)) {
this->curve_attributes_buf[i].reset();
}
this->attr_used.clear();
}
static void clear_eval_data(CurvesEvalCache &eval_cache)
void CurvesEvalCache::clear()
{
/* 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);
this->evaluated_pos_rad_buf.reset();
this->evaluated_time_buf.reset();
this->curves_length_buf.reset();
clear_final_data(eval_cache.final);
this->points_by_curve_buf.reset();
this->evaluated_points_by_curve_buf.reset();
this->curves_type_buf.reset();
this->curves_resolution_buf.reset();
this->curves_cyclic_buf.reset();
discard_attributes(eval_cache);
this->handles_positions_left_buf.reset();
this->handles_positions_right_buf.reset();
this->bezier_offsets_buf.reset();
this->curves_order_buf.reset();
this->control_weights_buf.reset();
this->basis_cache_buf.reset();
this->basis_cache_offset_buf.reset();
this->indirection_cylinder_buf.reset();
this->indirection_ribbon_buf.reset();
for (gpu::Batch *&batch : this->batch) {
GPU_BATCH_DISCARD_SAFE(batch);
}
this->discard_attributes();
}
static void clear_batch_cache(Curves &curves)
@@ -178,7 +187,7 @@ static void clear_batch_cache(Curves &curves)
return;
}
clear_eval_data(cache->eval_cache);
cache->eval_cache.clear();
clear_edit_data(cache);
}
@@ -188,69 +197,6 @@ static CurvesBatchCache &get_batch_cache(Curves &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::VertAttrType::SFLOAT_32_32_32_32);
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", blender::gpu::VertAttrType::SFLOAT_32);
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 |
@@ -566,21 +512,18 @@ static void calc_edit_handles_ibo(const OffsetIndices<int> points_by_curve,
&builder, 0, handles_and_points_num(points_num, bezier_offsets), false, &ibo);
}
static void alloc_final_attribute_vbo(CurvesEvalCache &cache,
const GPUVertFormat &format,
const int index,
const char * /*name*/)
static gpu::VertBufPtr alloc_evaluated_point_attribute_vbo(const GPUVertFormat &format,
const StringRef /*name*/,
int64_t size)
{
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);
gpu::VertBufPtr buf = gpu::VertBufPtr(GPU_vertbuf_create_with_format_ex(
format, GPU_USAGE_DEVICE_ONLY | GPU_USAGE_FLAG_BUFFER_TEXTURE_ONLY));
/* Create a destination buffer for the evaluation. Sized appropriately */
GPU_vertbuf_data_alloc(*buf, size);
return buf;
}
static gpu::VertBufPtr ensure_control_point_attribute(const Curves &curves_id,
static gpu::VertBufPtr ensure_control_point_attribute(const bke::CurvesGeometry &curves,
const StringRef name,
const GPUVertFormat &format,
bool &r_is_point_domain)
@@ -588,7 +531,6 @@ static gpu::VertBufPtr ensure_control_point_attribute(const Curves &curves_id,
gpu::VertBufPtr vbo = gpu::VertBufPtr(GPU_vertbuf_create_with_format_ex(
format, GPU_USAGE_STATIC | GPU_USAGE_FLAG_BUFFER_TEXTURE_ONLY));
const bke::CurvesGeometry &curves = curves_id.geometry.wrap();
const bke::AttributeAccessor attributes = curves.wrap().attributes();
/* TODO(@kevindietrich): float4 is used for scalar attributes as the implicit conversion done
@@ -597,7 +539,12 @@ static gpu::VertBufPtr ensure_control_point_attribute(const Curves &curves_id,
* similar texture state swizzle to map the attribute correctly as for volume attributes, so we
* can control the conversion ourselves. */
const bke::AttributeReader<ColorGeometry4f> attribute = attributes.lookup<ColorGeometry4f>(name);
if (!attribute) {
/* Attribute doesn't exist or is of an incompatible type.
* Replace it with a black curve domain attribute. */
/* TODO(fclem): Eventually, this should become unecessary if merge all attributes in one buffer
* and use an indirection table. */
GPU_vertbuf_data_alloc(*vbo, curves.curves_num());
vbo->data<ColorGeometry4f>().fill({0.0f, 0.0f, 0.0f, 1.0f});
r_is_point_domain = false;
@@ -610,129 +557,6 @@ static gpu::VertBufPtr ensure_control_point_attribute(const Curves &curves_id,
return vbo;
}
static void ensure_final_attribute(const Curves &curves,
const StringRef name,
const int index,
CurvesEvalCache &cache)
{
char sampler_name[32];
drw_curves_get_attribute_sampler_name(name, sampler_name);
GPUVertFormat format = {0};
/* All attributes use float4, see comment below. */
GPU_vertformat_attr_add(&format, sampler_name, blender::gpu::VertAttrType::SFLOAT_32_32_32_32);
if (!cache.proc_attributes_buf[index]) {
gpu::VertBufPtr vbo = ensure_control_point_attribute(
curves, name, format, cache.proc_attributes_point_domain[index]);
cache.proc_attributes_buf[index] = vbo.release();
}
/* 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 (cache.proc_attributes_point_domain[index]) {
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();
*(uint *)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", blender::gpu::VertAttrType::UINT_32);
GPUVertFormat format_seg = {0};
uint seg_id = GPU_vertformat_attr_add(&format_seg, "data", blender::gpu::VertAttrType::UINT_32);
/* 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::VertAttrType::SFLOAT_32_32_32_32);
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 const GPUVertFormat format = GPU_vertformat_from_attribute("dummy",
gpu::VertAttrType::UINT_32);
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 std::optional<StringRef> get_first_uv_name(const bke::AttributeAccessor &attributes)
{
std::optional<StringRef> name;
@@ -745,12 +569,83 @@ static std::optional<StringRef> get_first_uv_name(const bke::AttributeAccessor &
return name;
}
static bool ensure_attributes(const Curves &curves,
CurvesBatchCache &cache,
const GPUMaterial *gpu_material)
static void request_attribute(Curves &curves, const StringRef name)
{
const bke::AttributeAccessor attributes = curves.geometry.wrap().attributes();
CurvesEvalFinalCache &final_cache = cache.eval_cache.final;
CurvesEvalCache &cache = get_batch_cache(curves).eval_cache;
VectorSet<std::string> attributes{};
bke::CurvesGeometry &curves_geometry = curves.geometry.wrap();
if (!curves_geometry.attributes().contains(name)) {
return;
}
drw_attributes_add_request(&attributes, name);
drw_attributes_merge(&cache.attr_used, &attributes);
}
void drw_curves_get_attribute_sampler_name(const StringRef 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_utf8(r_sampler_name, 32, "a%s", attr_safe_name);
}
void CurvesEvalCache::ensure_attribute(CurvesModule &module,
const bke::CurvesGeometry &curves,
const StringRef name,
const int index)
{
char sampler_name[32];
drw_curves_get_attribute_sampler_name(name, sampler_name);
GPUVertFormat format = {0};
/* All attributes use float4, see comment below. */
/* TODO(fclem): Other types. */
GPU_vertformat_attr_add(&format, sampler_name, blender::gpu::VertAttrType::SFLOAT_32_32_32_32);
gpu::VertBufPtr attr_buf = ensure_control_point_attribute(
curves, name, format, attributes_point_domain[index]);
/* Existing final data may have been for a different attribute (with a different name or domain),
* free the data. */
this->evaluated_attributes_buf[index].reset();
/* Ensure final data for points. */
if (attributes_point_domain[index]) {
this->ensure_common(curves);
if (curves.has_curve_with_type(CURVE_TYPE_BEZIER)) {
this->ensure_bezier(curves);
}
if (curves.has_curve_with_type(CURVE_TYPE_NURBS)) {
this->ensure_nurbs(curves);
}
this->evaluated_attributes_buf[index] = alloc_evaluated_point_attribute_vbo(
format, name, evaluated_point_count_with_cyclic(curves));
module.evaluate_curve_attribute(curves.has_curve_with_type(CURVE_TYPE_CATMULL_ROM),
curves.has_curve_with_type(CURVE_TYPE_BEZIER),
curves.has_curve_with_type(CURVE_TYPE_POLY),
curves.has_curve_with_type(CURVE_TYPE_NURBS),
curves.has_cyclic_curve(),
curves.curves_num(),
*this,
CURVES_EVAL_FLOAT4,
std::move(attr_buf),
this->evaluated_attributes_buf[index]);
}
else {
this->curve_attributes_buf[index] = std::move(attr_buf);
}
}
void CurvesEvalCache::ensure_attributes(CurvesModule &module,
const bke::CurvesGeometry &curves,
const GPUMaterial *gpu_material)
{
const bke::AttributeAccessor attributes = curves.attributes();
if (gpu_material) {
VectorSet<std::string> attrs_needed;
@@ -768,108 +663,210 @@ static bool ensure_attributes(const Curves &curves,
drw_attributes_add_request(&attrs_needed, name);
}
if (!drw_attributes_overlap(&final_cache.attr_used, &attrs_needed)) {
if (!drw_attributes_overlap(&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]);
this->curve_attributes_buf[i].reset();
this->evaluated_attributes_buf[i].reset();
}
drw_attributes_merge(&final_cache.attr_used, &attrs_needed);
drw_attributes_merge(&attr_used, &attrs_needed);
}
drw_attributes_merge(&final_cache.attr_used_over_time, &attrs_needed);
drw_attributes_merge(&attr_used_over_time, &attrs_needed);
}
bool need_tf_update = false;
for (const int i : final_cache.attr_used.index_range()) {
if (cache.eval_cache.final.attributes_buf[i] != nullptr) {
for (const int i : attr_used.index_range()) {
if (this->curve_attributes_buf[i] || this->evaluated_attributes_buf[i]) {
continue;
}
ensure_final_attribute(curves, final_cache.attr_used[i], i, cache.eval_cache);
if (cache.eval_cache.proc_attributes_point_domain[i]) {
need_tf_update = true;
}
ensure_attribute(module, curves, attr_used[i], i);
}
return need_tf_update;
}
static void request_attribute(Curves &curves, const StringRef name)
void CurvesEvalCache::ensure_common(const bke::CurvesGeometry &curves)
{
CurvesBatchCache &cache = get_batch_cache(curves);
CurvesEvalFinalCache &final_cache = cache.eval_cache.final;
VectorSet<std::string> attributes{};
bke::CurvesGeometry &curves_geometry = curves.geometry.wrap();
if (!curves_geometry.attributes().contains(name)) {
if (this->points_by_curve_buf) {
return;
}
drw_attributes_add_request(&attributes, name);
this->points_by_curve_buf = gpu::VertBuf::from_span(curves.points_by_curve().data());
this->evaluated_points_by_curve_buf = gpu::VertBuf::from_span(
curves.evaluated_points_by_curve().data());
drw_attributes_merge(&final_cache.attr_used, &attributes);
/* TODO(fclem): Optimize shaders to avoid needing to upload this data if data is uniform.
* This concerns all varray. */
this->curves_type_buf = gpu::VertBuf::from_varray(curves.curve_types());
this->curves_resolution_buf = gpu::VertBuf::from_varray(curves.resolution());
this->curves_cyclic_buf = gpu::VertBuf::from_varray(curves.cyclic());
}
void drw_curves_get_attribute_sampler_name(const StringRef layer_name, char r_sampler_name[32])
void CurvesEvalCache::ensure_bezier(const bke::CurvesGeometry &curves)
{
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_utf8(r_sampler_name, 32, "a%s", attr_safe_name);
if (this->handles_positions_left_buf) {
return;
}
const Span<float3> left = curves.handle_positions_left().value_or(curves.positions());
const Span<float3> right = curves.handle_positions_right().value_or(curves.positions());
this->handles_positions_left_buf = gpu::VertBuf::from_span(left);
this->handles_positions_right_buf = gpu::VertBuf::from_span(right);
this->bezier_offsets_buf = gpu::VertBuf::from_span(
curves.runtime->evaluated_offsets_cache.data().all_bezier_offsets.as_span());
}
bool curves_ensure_procedural_data(Curves *curves_id,
CurvesEvalCache **r_cache,
const GPUMaterial *gpu_material,
const int subdiv,
const int thickness_res)
void CurvesEvalCache::ensure_nurbs(const bke::CurvesGeometry &curves)
{
const bke::CurvesGeometry &curves = curves_id->geometry.wrap();
bool need_ft_update = false;
if (curves_order_buf) {
return;
}
using BasisCache = bke::curves::nurbs::BasisCache;
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;
/* TODO(fclem): Optimize shaders to avoid needing to upload this data if data is uniform.
* This concerns all varray. */
this->curves_order_buf = gpu::VertBuf::from_varray(curves.nurbs_orders());
if (curves.nurbs_weights().has_value()) {
this->control_weights_buf = gpu::VertBuf::from_span(curves.nurbs_weights().value());
}
eval_cache.curves_num = curves.curves_num();
eval_cache.points_num = curves.points_num();
curves.ensure_can_interpolate_to_evaluated();
const int steps = 3; /* TODO: don't hard-code? */
eval_cache.final.resolution = 1 << (steps + subdiv);
const Span<BasisCache> nurbs_basis_cache = curves.runtime->nurbs_basis_cache.data();
/* 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;
Vector<int> basis_cache_offset;
Vector<uint32_t> basis_cache_packed;
for (const BasisCache &cache : nurbs_basis_cache) {
basis_cache_offset.append(cache.invalid ? -1 : basis_cache_packed.size());
if (!cache.invalid) {
basis_cache_packed.extend(cache.start_indices.as_span().cast<uint32_t>());
basis_cache_packed.extend(cache.weights.as_span().cast<uint32_t>());
}
}
/* Ensure buffer is not empty. */
if (basis_cache_packed.is_empty()) {
basis_cache_packed.append(0);
}
/* 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);
this->basis_cache_offset_buf = gpu::VertBuf::from_span(basis_cache_offset.as_span());
this->basis_cache_buf = gpu::VertBuf::from_span(basis_cache_packed.as_span());
}
int CurvesEvalCache::evaluated_point_count_with_cyclic(const bke::CurvesGeometry &curves)
{
if (curves.has_cyclic_curve()) {
return curves.evaluated_points_num() + curves.curves_num();
}
return curves.evaluated_points_num();
}
void CurvesEvalCache::ensure_positions(CurvesModule &module, const bke::CurvesGeometry &curves)
{
if (evaluated_pos_rad_buf) {
return;
}
/* Refreshed only on subdiv count change. */
if (eval_cache.final.proc_buf == nullptr) {
alloc_final_points_vbo(eval_cache);
need_ft_update = true;
if (curves.is_empty()) {
/* Can happen when called from `curves_pos_buffer_get()`. Caller has to deal with nullptr. */
return;
}
if (eval_cache.final.proc_hairs == nullptr) {
calc_final_indices(curves, eval_cache, thickness_res);
this->ensure_common(curves);
if (curves.has_curve_with_type(CURVE_TYPE_BEZIER)) {
this->ensure_bezier(curves);
}
if (curves.has_curve_with_type(CURVE_TYPE_NURBS)) {
this->ensure_nurbs(curves);
}
eval_cache.final.thickres = thickness_res;
need_ft_update |= ensure_attributes(*curves_id, cache, gpu_material);
/* TODO(fclem): Optimize shaders to avoid needing to upload this data if data is uniform.
* This concerns all varray. */
gpu::VertBufPtr points_pos_buf = gpu::VertBuf::from_span(curves.positions());
gpu::VertBufPtr points_rad_buf = gpu::VertBuf::from_varray(curves.radius());
*r_cache = &eval_cache;
return need_ft_update;
this->evaluated_pos_rad_buf = gpu::VertBuf::device_only<float4>(
evaluated_point_count_with_cyclic(curves));
module.evaluate_positions(curves.has_curve_with_type(CURVE_TYPE_CATMULL_ROM),
curves.has_curve_with_type(CURVE_TYPE_BEZIER),
curves.has_curve_with_type(CURVE_TYPE_POLY),
curves.has_curve_with_type(CURVE_TYPE_NURBS),
curves.has_cyclic_curve(),
curves.curves_num(),
*this,
std::move(points_pos_buf),
std::move(points_rad_buf),
evaluated_pos_rad_buf);
/* TODO(fclem): Make time and length optional. */
this->evaluated_time_buf = gpu::VertBuf::device_only<float>(
evaluated_point_count_with_cyclic(curves));
this->curves_length_buf = gpu::VertBuf::device_only<float>(curves.curves_num());
module.evaluate_curve_length_intercept(curves.has_cyclic_curve(), curves.curves_num(), *this);
}
gpu::VertBufPtr &CurvesEvalCache::indirection_buf_get(CurvesModule &module,
const bke::CurvesGeometry &curves,
const int face_per_segment)
{
const bool is_ribbon = face_per_segment < 2;
gpu::VertBufPtr &indirection_buf = is_ribbon ? this->indirection_ribbon_buf :
this->indirection_cylinder_buf;
if (indirection_buf) {
return indirection_buf;
}
this->ensure_common(curves);
indirection_buf = module.evaluate_topology_indirection(curves.curves_num(),
curves.evaluated_points_num(),
*this,
is_ribbon,
curves.has_cyclic_curve());
return indirection_buf;
}
gpu::Batch *CurvesEvalCache::batch_get(const int evaluated_point_count,
const int curve_count,
const int face_per_segment,
const bool use_cyclic)
{
gpu::Batch *&batch = this->batch[face_per_segment];
if (batch) {
return batch;
}
if (face_per_segment == 0) {
/* Add one point per curve to restart the primitive. */
int segment_count = evaluated_point_count + curve_count;
if (use_cyclic) {
segment_count += curve_count;
}
batch = GPU_batch_create_procedural(GPU_PRIM_LINE_STRIP, segment_count);
}
else if (face_per_segment == 1) {
/* Add one point per curve to restart the primitive. */
int segment_count = evaluated_point_count + curve_count;
if (use_cyclic) {
segment_count += curve_count;
}
/* Add one point per curve to restart the primitive. */
batch = GPU_batch_create_procedural(GPU_PRIM_TRI_STRIP, segment_count * 2);
}
else if (face_per_segment >= 2) {
int segment_count = evaluated_point_count - curve_count;
if (use_cyclic) {
segment_count += curve_count;
}
/* Add one vertex per segment to restart the primitive. */
int vert_per_segment = (face_per_segment + 1) * 2 + 1;
batch = GPU_batch_create_procedural(GPU_PRIM_TRI_STRIP, segment_count * vert_per_segment);
}
return batch;
}
CurvesEvalCache &curves_get_eval_cache(Curves &curves_id)
{
return get_batch_cache(curves_id).eval_cache;
}
void DRW_curves_batch_cache_dirty_tag(Curves *curves, int mode)
@@ -912,20 +909,20 @@ void DRW_curves_batch_cache_free_old(Curves *curves, int ctime)
bool do_discard = false;
CurvesEvalFinalCache &final_cache = cache->eval_cache.final;
CurvesEvalCache &eval_cache = cache->eval_cache;
if (drw_attributes_overlap(&final_cache.attr_used_over_time, &final_cache.attr_used)) {
final_cache.last_attr_matching_time = ctime;
if (drw_attributes_overlap(&eval_cache.attr_used_over_time, &eval_cache.attr_used)) {
eval_cache.last_attr_matching_time = ctime;
}
if (ctime - final_cache.last_attr_matching_time > U.vbotimeout) {
if (ctime - eval_cache.last_attr_matching_time > U.vbotimeout) {
do_discard = true;
}
final_cache.attr_used_over_time.clear();
eval_cache.attr_used_over_time.clear();
if (do_discard) {
discard_attributes(cache->eval_cache);
cache->eval_cache.discard_attributes();
}
}
@@ -953,32 +950,33 @@ gpu::Batch *DRW_curves_batch_cache_get_edit_curves_lines(Curves *curves)
return DRW_batch_request(&cache.edit_curves_lines);
}
gpu::VertBuf **DRW_curves_texture_for_evaluated_attribute(Curves *curves,
const StringRef name,
bool *r_is_point_domain)
gpu::VertBufPtr &DRW_curves_texture_for_evaluated_attribute(Curves *curves,
const StringRef name,
bool &r_is_point_domain,
bool &r_valid_attribute)
{
CurvesBatchCache &cache = get_batch_cache(*curves);
CurvesEvalFinalCache &final_cache = cache.eval_cache.final;
CurvesEvalCache &cache = get_batch_cache(*curves).eval_cache;
request_attribute(*curves, name);
int request_i = -1;
for (const int i : final_cache.attr_used.index_range()) {
if (final_cache.attr_used[i] == name) {
request_i = i;
break;
/* TODO(fclem): Remove Global access. */
CurvesModule &module = *drw_get().data->curves_module;
cache.ensure_attributes(module, curves->geometry.wrap(), nullptr);
for (const int i : cache.attr_used.index_range()) {
if (cache.attr_used[i] == name) {
r_valid_attribute = true;
if (cache.attributes_point_domain[i]) {
r_is_point_domain = true;
return cache.evaluated_attributes_buf[i];
}
r_is_point_domain = false;
return cache.curve_attributes_buf[i];
}
}
if (request_i == -1) {
*r_is_point_domain = false;
return nullptr;
}
if (cache.eval_cache.proc_attributes_point_domain[request_i]) {
*r_is_point_domain = true;
return &final_cache.attributes_buf[request_i];
}
*r_is_point_domain = false;
return &cache.eval_cache.proc_attributes_buf[request_i];
r_valid_attribute = false;
r_is_point_domain = false;
return cache.evaluated_attributes_buf[0];
}
static void create_edit_points_position_vbo(

View File

@@ -8,7 +8,9 @@
* \brief Particle API for render engines
*/
#include "BLI_color.hh"
#include "DNA_collection_types.h"
#include "DNA_curves_types.h"
#include "DNA_scene_types.h"
#include "DRW_render.hh"
@@ -17,6 +19,7 @@
#include "BLI_alloca.h"
#include "BLI_math_color.h"
#include "BLI_math_vector.h"
#include "BLI_offset_indices.hh"
#include "BLI_string_utf8.h"
#include "BLI_utildefines.h"
@@ -40,6 +43,7 @@
#include "DEG_depsgraph_query.hh"
#include "draw_attributes.hh"
#include "draw_cache_impl.hh" /* own include */
#include "draw_hair_private.hh"
@@ -50,6 +54,32 @@ static void particle_batch_cache_clear(ParticleSystem *psys);
/* ---------------------------------------------------------------------- */
/* Particle gpu::Batch Cache */
struct ParticleHairFinalCache {
/* Output of the subdivision stage: vertex buff sized to subdiv level. */
blender::gpu::VertBuf *proc_buf;
/* Just contains a huge index buffer used to draw the final hair. */
blender::gpu::Batch *proc_hairs[MAX_THICKRES];
int strands_res; /* points per hair, at least 2 */
};
struct ParticleHairCache {
blender::gpu::VertBuf *pos;
blender::gpu::IndexBuf *indices;
blender::gpu::Batch *hairs;
int strands_len;
int elems_len;
int point_len;
/* Equivalent to the new Curves data structure.
* Allows to create the eval cache. */
Vector<int> points_by_curve_storage;
Vector<int> evaluated_points_by_curve_storage;
CurvesEvalCache eval_cache;
};
struct ParticlePointCache {
gpu::VertBuf *pos;
gpu::Batch *points;
@@ -132,11 +162,12 @@ static void particle_batch_cache_init(ParticleSystem *psys)
ParticleBatchCache *cache = static_cast<ParticleBatchCache *>(psys->batch_cache);
if (!cache) {
cache = static_cast<ParticleBatchCache *>(
psys->batch_cache = MEM_callocN(sizeof(*cache), __func__));
cache = MEM_new<ParticleBatchCache>(__func__);
psys->batch_cache = cache;
}
else {
memset(cache, 0, sizeof(*cache));
cache->edit_hair.eval_cache = {};
cache->hair.eval_cache = {};
}
cache->is_dirty = false;
@@ -175,36 +206,16 @@ static void particle_batch_cache_clear_point(ParticlePointCache *point_cache)
static void particle_batch_cache_clear_hair(ParticleHairCache *hair_cache)
{
/* TODO: more granular update tagging. */
GPU_VERTBUF_DISCARD_SAFE(hair_cache->proc_point_buf);
GPU_VERTBUF_DISCARD_SAFE(hair_cache->proc_length_buf);
GPU_VERTBUF_DISCARD_SAFE(hair_cache->proc_strand_buf);
GPU_VERTBUF_DISCARD_SAFE(hair_cache->proc_strand_seg_buf);
for (int i = 0; i < MAX_MTFACE; i++) {
GPU_VERTBUF_DISCARD_SAFE(hair_cache->proc_uv_buf[i]);
GPU_TEXTURE_FREE_SAFE(hair_cache->uv_tex[i]);
}
for (int i = 0; i < hair_cache->num_col_layers; i++) {
GPU_VERTBUF_DISCARD_SAFE(hair_cache->proc_col_buf[i]);
GPU_TEXTURE_FREE_SAFE(hair_cache->col_tex[i]);
}
for (int i = 0; i < MAX_HAIR_SUBDIV; i++) {
GPU_VERTBUF_DISCARD_SAFE(hair_cache->final[i].proc_buf);
for (int j = 0; j < MAX_THICKRES; j++) {
GPU_BATCH_DISCARD_SAFE(hair_cache->final[i].proc_hairs[j]);
}
}
/* "Normal" legacy hairs */
GPU_BATCH_DISCARD_SAFE(hair_cache->hairs);
GPU_VERTBUF_DISCARD_SAFE(hair_cache->pos);
GPU_INDEXBUF_DISCARD_SAFE(hair_cache->indices);
MEM_SAFE_FREE(hair_cache->proc_col_buf);
MEM_SAFE_FREE(hair_cache->col_tex);
MEM_SAFE_FREE(hair_cache->col_layer_names);
hair_cache->evaluated_points_by_curve_storage.clear();
hair_cache->points_by_curve_storage.clear();
hair_cache->eval_cache.clear();
}
static void particle_batch_cache_clear(ParticleSystem *psys)
@@ -230,7 +241,74 @@ static void particle_batch_cache_clear(ParticleSystem *psys)
void DRW_particle_batch_cache_free(ParticleSystem *psys)
{
particle_batch_cache_clear(psys);
MEM_SAFE_FREE(psys->batch_cache);
ParticleBatchCache *batch_cache = static_cast<ParticleBatchCache *>(psys->batch_cache);
MEM_delete(batch_cache);
psys->batch_cache = nullptr;
}
void ParticleSpans::foreach_strand(FunctionRef<void(Span<ParticleCacheKey>)> callback)
{
for (const auto &particle : parent) {
callback(Span<ParticleCacheKey>(particle, particle->segments + 1));
}
for (const auto &particle : children) {
callback(Span<ParticleCacheKey>(particle, particle->segments + 1));
}
}
ParticleSpans ParticleDrawSource::particles_get()
{
if (edit && edit->pathcache) {
/* Edit particles only display their parent. */
return {{edit->pathcache, edit->totcached}, {}};
}
ParticleSpans spans;
const bool display_parent = !psys->childcache || (psys->part->draw & PART_DRAW_PARENT);
if (psys->pathcache && display_parent) {
spans.parent = {psys->pathcache, psys->totpart};
}
if (psys->childcache) {
spans.children = {psys->childcache, psys->totchild * psys->part->disp / 100};
}
return spans;
}
OffsetIndices<int> ParticleDrawSource::points_by_curve()
{
if (!points_by_curve_storage_.is_empty()) {
return points_by_curve_storage_.as_span();
}
int total = 0;
points_by_curve_storage_.append(total);
particles_get().foreach_strand([&](Span<ParticleCacheKey> strand) {
total += strand.size();
points_by_curve_storage_.append(total);
});
return points_by_curve_storage_.as_span();
}
OffsetIndices<int> ParticleDrawSource::evaluated_points_by_curve()
{
if (additional_subdivision_ == 0) {
return points_by_curve();
}
if (!evaluated_points_by_curve_storage_.is_empty()) {
return evaluated_points_by_curve_storage_.as_span();
}
int segment_multiplier = this->resolution();
int total = 0;
evaluated_points_by_curve_storage_.append(total);
particles_get().foreach_strand([&](Span<ParticleCacheKey> strand) {
int size = strand.size();
total += (size > 1) ? size * segment_multiplier : 1;
evaluated_points_by_curve_storage_.append(total);
});
return evaluated_points_by_curve_storage_.as_span();
}
static void count_cache_segment_keys(ParticleCacheKey **pathcache,
@@ -251,9 +329,7 @@ static void ensure_seg_pt_count(PTCacheEdit *edit,
ParticleSystem *psys,
ParticleHairCache *hair_cache)
{
if ((hair_cache->pos != nullptr && hair_cache->indices != nullptr) ||
(hair_cache->proc_point_buf != nullptr))
{
if (hair_cache->pos != nullptr && hair_cache->indices != nullptr) {
return;
}
@@ -625,41 +701,6 @@ static int particle_batch_cache_fill_segments(ParticleSystem *psys,
return curr_point;
}
static void particle_batch_cache_fill_segments_proc_pos(ParticleCacheKey **path_cache,
const int num_path_keys,
GPUVertBufRaw *attr_step,
GPUVertBufRaw *length_step)
{
for (int i = 0; i < num_path_keys; i++) {
ParticleCacheKey *path = path_cache[i];
if (path->segments <= 0) {
continue;
}
float total_len = 0.0f;
float *co_prev = nullptr, *seg_data_first;
for (int j = 0; j <= path->segments; j++) {
float *seg_data = (float *)GPU_vertbuf_raw_step(attr_step);
copy_v3_v3(seg_data, path[j].co);
if (co_prev) {
total_len += len_v3v3(co_prev, path[j].co);
}
else {
seg_data_first = seg_data;
}
seg_data[3] = total_len;
co_prev = path[j].co;
}
/* Assign length value. */
*(float *)GPU_vertbuf_raw_step(length_step) = total_len;
if (total_len > 0.0f) {
/* Divide by total length to have a [0-1] number. */
for (int j = 0; j <= path->segments; j++, seg_data_first += 4) {
seg_data_first[3] /= total_len;
}
}
}
}
static float particle_key_weight(const ParticleData *particle, int strand, float t)
{
const ParticleData *part = particle + strand;
@@ -714,459 +755,6 @@ static int particle_batch_cache_fill_segments_edit(
return curr_point;
}
static int particle_batch_cache_fill_segments_indices(ParticleCacheKey **path_cache,
const int start_index,
const int num_path_keys,
const int res,
GPUIndexBufBuilder *elb)
{
int curr_point = start_index;
for (int i = 0; i < num_path_keys; i++) {
ParticleCacheKey *path = path_cache[i];
if (path->segments <= 0) {
continue;
}
for (int k = 0; k < res; k++) {
GPU_indexbuf_add_generic_vert(elb, curr_point++);
}
GPU_indexbuf_add_primitive_restart(elb);
}
return curr_point;
}
static int particle_batch_cache_fill_strands_data(ParticleSystem *psys,
ParticleSystemModifierData *psmd,
ParticleCacheKey **path_cache,
const ParticleSource particle_source,
const int start_index,
const int num_path_keys,
GPUVertBufRaw *data_step,
GPUVertBufRaw *seg_step,
float (***r_parent_uvs)[2],
GPUVertBufRaw *uv_step,
const MTFace **mtfaces,
int num_uv_layers,
MCol ***r_parent_mcol,
GPUVertBufRaw *col_step,
const MCol **mcols,
int num_col_layers)
{
const bool is_simple = (psys->part->childtype == PART_CHILD_PARTICLES);
const bool is_child = (particle_source == PARTICLE_SOURCE_CHILDREN);
if (is_simple && *r_parent_uvs == nullptr) {
/* TODO(sergey): For edit mode it should be edit->totcached. */
*r_parent_uvs = static_cast<float(**)[2]>(
MEM_callocN(sizeof(*r_parent_uvs) * psys->totpart, "Parent particle UVs"));
}
if (is_simple && *r_parent_mcol == nullptr) {
*r_parent_mcol = static_cast<MCol **>(
MEM_callocN(sizeof(*r_parent_mcol) * psys->totpart, "Parent particle MCol"));
}
int curr_point = start_index;
for (int i = 0; i < num_path_keys; i++) {
ParticleCacheKey *path = path_cache[i];
if (path->segments <= 0) {
continue;
}
*(uint *)GPU_vertbuf_raw_step(data_step) = curr_point;
*(uint *)GPU_vertbuf_raw_step(seg_step) = path->segments;
curr_point += path->segments + 1;
if (psmd != nullptr) {
float(*uv)[2] = nullptr;
MCol *mcol = nullptr;
particle_calculate_uvs(psys,
psmd,
is_simple,
num_uv_layers,
is_child ? psys->child[i].parent : i,
is_child ? i : -1,
mtfaces,
*r_parent_uvs,
&uv);
particle_calculate_mcol(psys,
psmd,
is_simple,
num_col_layers,
is_child ? psys->child[i].parent : i,
is_child ? i : -1,
mcols,
*r_parent_mcol,
&mcol);
for (int k = 0; k < num_uv_layers; k++) {
float *t_uv = (float *)GPU_vertbuf_raw_step(uv_step + k);
copy_v2_v2(t_uv, uv[k]);
}
for (int k = 0; k < num_col_layers; k++) {
ushort *scol = (ushort *)GPU_vertbuf_raw_step(col_step + k);
particle_pack_mcol((is_simple && is_child) ? &(*r_parent_mcol)[psys->child[i].parent][k] :
&mcol[k],
scol);
}
if (!is_simple) {
MEM_freeN(uv);
MEM_freeN(mcol);
}
}
}
return curr_point;
}
static void particle_batch_cache_ensure_procedural_final_points(ParticleHairCache *cache,
int subdiv)
{
/* Same format as proc_point_buf. */
static const GPUVertFormat format = GPU_vertformat_from_attribute(
"pos", gpu::VertAttrType::SFLOAT_32_32_32_32);
/* Procedural Subdiv buffer only needs to be resident in device memory. */
cache->final[subdiv].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 procedural Subdiv. Sized appropriately */
/* Those are points! not line segments. */
uint point_len = cache->final[subdiv].strands_res * cache->strands_len;
/* 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[subdiv].proc_buf, point_len);
}
static void particle_batch_cache_ensure_procedural_strand_data(PTCacheEdit *edit,
ParticleSystem *psys,
ModifierData *md,
ParticleHairCache *cache)
{
int active_uv = 0;
int render_uv = 0;
int active_col = 0;
int render_col = 0;
ParticleSystemModifierData *psmd = (ParticleSystemModifierData *)md;
if (psmd != nullptr && psmd->mesh_final != nullptr) {
if (CustomData_has_layer(&psmd->mesh_final->corner_data, CD_PROP_FLOAT2)) {
cache->num_uv_layers = CustomData_number_of_layers(&psmd->mesh_final->corner_data,
CD_PROP_FLOAT2);
active_uv = CustomData_get_active_layer(&psmd->mesh_final->corner_data, CD_PROP_FLOAT2);
render_uv = CustomData_get_render_layer(&psmd->mesh_final->corner_data, CD_PROP_FLOAT2);
}
if (CustomData_has_layer(&psmd->mesh_final->corner_data, CD_PROP_BYTE_COLOR)) {
cache->num_col_layers = CustomData_number_of_layers(&psmd->mesh_final->corner_data,
CD_PROP_BYTE_COLOR);
if (psmd->mesh_final->active_color_attribute != nullptr) {
active_col = CustomData_get_named_layer(&psmd->mesh_final->corner_data,
CD_PROP_BYTE_COLOR,
psmd->mesh_final->active_color_attribute);
}
if (psmd->mesh_final->default_color_attribute != nullptr) {
render_col = CustomData_get_named_layer(&psmd->mesh_final->corner_data,
CD_PROP_BYTE_COLOR,
psmd->mesh_final->default_color_attribute);
}
}
}
GPUVertBufRaw data_step, seg_step;
GPUVertBufRaw uv_step[MAX_MTFACE];
GPUVertBufRaw *col_step = BLI_array_alloca(col_step, cache->num_col_layers);
const MTFace *mtfaces[MAX_MTFACE] = {nullptr};
const MCol **mcols = BLI_array_alloca(mcols, cache->num_col_layers);
float(**parent_uvs)[2] = nullptr;
MCol **parent_mcol = nullptr;
GPUVertFormat format_data = {0};
uint data_id = GPU_vertformat_attr_add(
&format_data, "data", blender::gpu::VertAttrType::UINT_32);
GPUVertFormat format_seg = {0};
uint seg_id = GPU_vertformat_attr_add(&format_seg, "data", blender::gpu::VertAttrType::UINT_32);
GPUVertFormat format_uv = {0};
uint uv_id = GPU_vertformat_attr_add(&format_uv, "uv", blender::gpu::VertAttrType::SFLOAT_32_32);
GPUVertFormat format_col = {0};
uint col_id = GPU_vertformat_attr_add(
&format_col, "col", blender::gpu::VertAttrType::UNORM_16_16_16_16);
memset(cache->uv_layer_names, 0, sizeof(cache->uv_layer_names));
/* Strand 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, max_ii(1, cache->strands_len));
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, max_ii(1, cache->strands_len));
GPU_vertbuf_attr_get_raw_data(cache->proc_strand_seg_buf, seg_id, &seg_step);
/* UV layers */
for (int i = 0; i < cache->num_uv_layers; i++) {
cache->proc_uv_buf[i] = GPU_vertbuf_create_with_format_ex(
format_uv, GPU_USAGE_STATIC | GPU_USAGE_FLAG_BUFFER_TEXTURE_ONLY);
GPU_vertbuf_data_alloc(*cache->proc_uv_buf[i], max_ii(1, cache->strands_len));
GPU_vertbuf_attr_get_raw_data(cache->proc_uv_buf[i], uv_id, &uv_step[i]);
char attr_safe_name[GPU_MAX_SAFE_ATTR_NAME];
const char *name = CustomData_get_layer_name(
&psmd->mesh_final->corner_data, CD_PROP_FLOAT2, i);
GPU_vertformat_safe_attr_name(name, attr_safe_name, GPU_MAX_SAFE_ATTR_NAME);
int n = 0;
SNPRINTF_UTF8(cache->uv_layer_names[i][n], "a%s", attr_safe_name);
n++;
if (i == active_uv) {
STRNCPY_UTF8(cache->uv_layer_names[i][n], "au");
n++;
}
if (i == render_uv) {
STRNCPY_UTF8(cache->uv_layer_names[i][n], "a");
n++;
}
}
MEM_SAFE_FREE(cache->proc_col_buf);
MEM_SAFE_FREE(cache->col_tex);
MEM_SAFE_FREE(cache->col_layer_names);
cache->proc_col_buf = MEM_calloc_arrayN<gpu::VertBuf *>(cache->num_col_layers, "proc_col_buf");
cache->col_tex = MEM_calloc_arrayN<gpu::Texture *>(cache->num_col_layers, "col_tex");
cache->col_layer_names = MEM_calloc_arrayN<char[4][14]>(cache->num_col_layers,
"col_layer_names");
/* Vertex colors */
for (int i = 0; i < cache->num_col_layers; i++) {
cache->proc_col_buf[i] = GPU_vertbuf_create_with_format_ex(
format_col, GPU_USAGE_STATIC | GPU_USAGE_FLAG_BUFFER_TEXTURE_ONLY);
GPU_vertbuf_data_alloc(*cache->proc_col_buf[i], max_ii(1, cache->strands_len));
GPU_vertbuf_attr_get_raw_data(cache->proc_col_buf[i], col_id, &col_step[i]);
char attr_safe_name[GPU_MAX_SAFE_ATTR_NAME];
const char *name = CustomData_get_layer_name(
&psmd->mesh_final->corner_data, CD_PROP_BYTE_COLOR, i);
GPU_vertformat_safe_attr_name(name, attr_safe_name, GPU_MAX_SAFE_ATTR_NAME);
int n = 0;
SNPRINTF_UTF8(cache->col_layer_names[i][n], "a%s", attr_safe_name);
n++;
if (i == active_col) {
STRNCPY_UTF8(cache->col_layer_names[i][n], "ac");
n++;
}
if (i == render_col) {
STRNCPY_UTF8(cache->col_layer_names[i][n], "c");
n++;
}
}
if (cache->num_uv_layers || cache->num_col_layers) {
BKE_mesh_tessface_ensure(psmd->mesh_final);
if (cache->num_uv_layers) {
for (int j = 0; j < cache->num_uv_layers; j++) {
mtfaces[j] = (const MTFace *)CustomData_get_layer_n(
&psmd->mesh_final->fdata_legacy, CD_MTFACE, j);
}
}
if (cache->num_col_layers) {
for (int j = 0; j < cache->num_col_layers; j++) {
mcols[j] = (const MCol *)CustomData_get_layer_n(
&psmd->mesh_final->fdata_legacy, CD_MCOL, j);
}
}
}
if (edit != nullptr && edit->pathcache != nullptr) {
particle_batch_cache_fill_strands_data(psys,
psmd,
edit->pathcache,
PARTICLE_SOURCE_PARENT,
0,
edit->totcached,
&data_step,
&seg_step,
&parent_uvs,
uv_step,
mtfaces,
cache->num_uv_layers,
&parent_mcol,
col_step,
mcols,
cache->num_col_layers);
}
else {
int curr_point = 0;
if ((psys->pathcache != nullptr) &&
(!psys->childcache || (psys->part->draw & PART_DRAW_PARENT)))
{
curr_point = particle_batch_cache_fill_strands_data(psys,
psmd,
psys->pathcache,
PARTICLE_SOURCE_PARENT,
0,
psys->totpart,
&data_step,
&seg_step,
&parent_uvs,
uv_step,
mtfaces,
cache->num_uv_layers,
&parent_mcol,
col_step,
mcols,
cache->num_col_layers);
}
if (psys->childcache) {
const int child_count = psys->totchild * psys->part->disp / 100;
curr_point = particle_batch_cache_fill_strands_data(psys,
psmd,
psys->childcache,
PARTICLE_SOURCE_CHILDREN,
curr_point,
child_count,
&data_step,
&seg_step,
&parent_uvs,
uv_step,
mtfaces,
cache->num_uv_layers,
&parent_mcol,
col_step,
mcols,
cache->num_col_layers);
}
}
/* Cleanup. */
if (parent_uvs != nullptr) {
/* TODO(sergey): For edit mode it should be edit->totcached. */
for (int i = 0; i < psys->totpart; i++) {
MEM_SAFE_FREE(parent_uvs[i]);
}
MEM_freeN(parent_uvs);
}
if (parent_mcol != nullptr) {
for (int i = 0; i < psys->totpart; i++) {
MEM_SAFE_FREE(parent_mcol[i]);
}
MEM_freeN(parent_mcol);
}
for (int i = 0; i < cache->num_uv_layers; i++) {
GPU_vertbuf_use(cache->proc_uv_buf[i]);
cache->uv_tex[i] = GPU_texture_create_from_vertbuf("part_uv", cache->proc_uv_buf[i]);
}
for (int i = 0; i < cache->num_col_layers; i++) {
GPU_vertbuf_use(cache->proc_col_buf[i]);
cache->col_tex[i] = GPU_texture_create_from_vertbuf("part_col", cache->proc_col_buf[i]);
}
}
static void particle_batch_cache_ensure_procedural_indices(PTCacheEdit *edit,
ParticleSystem *psys,
ParticleHairCache *cache,
int thickness_res,
int subdiv)
{
BLI_assert(thickness_res <= MAX_THICKRES); /* Cylinder strip not currently supported. */
if (cache->final[subdiv].proc_hairs[thickness_res - 1] != nullptr) {
return;
}
int verts_per_hair = cache->final[subdiv].strands_res * thickness_res;
/* +1 for primitive restart */
int element_count = (verts_per_hair + 1) * cache->strands_len;
GPUPrimType prim_type = (thickness_res == 1) ? GPU_PRIM_LINE_STRIP : GPU_PRIM_TRI_STRIP;
static const GPUVertFormat format = GPU_vertformat_from_attribute("dummy",
gpu::VertAttrType::UINT_32);
gpu::VertBuf *vbo = GPU_vertbuf_create_with_format(format);
GPU_vertbuf_data_alloc(*vbo, 1);
GPUIndexBufBuilder elb;
GPU_indexbuf_init_ex(&elb, prim_type, element_count, element_count);
if (edit != nullptr && edit->pathcache != nullptr) {
particle_batch_cache_fill_segments_indices(
edit->pathcache, 0, edit->totcached, verts_per_hair, &elb);
}
else {
int curr_point = 0;
if ((psys->pathcache != nullptr) &&
(!psys->childcache || (psys->part->draw & PART_DRAW_PARENT)))
{
curr_point = particle_batch_cache_fill_segments_indices(
psys->pathcache, 0, psys->totpart, verts_per_hair, &elb);
}
if (psys->childcache) {
const int child_count = psys->totchild * psys->part->disp / 100;
curr_point = particle_batch_cache_fill_segments_indices(
psys->childcache, curr_point, child_count, verts_per_hair, &elb);
}
}
cache->final[subdiv].proc_hairs[thickness_res - 1] = GPU_batch_create_ex(
prim_type, vbo, GPU_indexbuf_build(&elb), GPU_BATCH_OWNS_VBO | GPU_BATCH_OWNS_INDEX);
}
static void particle_batch_cache_ensure_procedural_pos(PTCacheEdit *edit,
ParticleSystem *psys,
ParticleHairCache *cache,
GPUMaterial * /*gpu_material*/)
{
if (cache->proc_point_buf == nullptr) {
/* initialize vertex format */
GPUVertFormat pos_format = {0};
uint pos_id = GPU_vertformat_attr_add(
&pos_format, "posTime", blender::gpu::VertAttrType::SFLOAT_32_32_32_32);
cache->proc_point_buf = GPU_vertbuf_create_with_format_ex(
pos_format, GPU_USAGE_STATIC | GPU_USAGE_FLAG_BUFFER_TEXTURE_ONLY);
GPU_vertbuf_data_alloc(*cache->proc_point_buf, cache->point_len);
GPUVertBufRaw pos_step;
GPU_vertbuf_attr_get_raw_data(cache->proc_point_buf, pos_id, &pos_step);
GPUVertFormat length_format = {0};
uint length_id = GPU_vertformat_attr_add(
&length_format, "hairLength", blender::gpu::VertAttrType::SFLOAT_32);
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->strands_len);
GPUVertBufRaw length_step;
GPU_vertbuf_attr_get_raw_data(cache->proc_length_buf, length_id, &length_step);
if (edit != nullptr && edit->pathcache != nullptr) {
particle_batch_cache_fill_segments_proc_pos(
edit->pathcache, edit->totcached, &pos_step, &length_step);
}
else {
if ((psys->pathcache != nullptr) &&
(!psys->childcache || (psys->part->draw & PART_DRAW_PARENT)))
{
particle_batch_cache_fill_segments_proc_pos(
psys->pathcache, psys->totpart, &pos_step, &length_step);
}
if (psys->childcache) {
const int child_count = psys->totchild * psys->part->disp / 100;
particle_batch_cache_fill_segments_proc_pos(
psys->childcache, child_count, &pos_step, &length_step);
}
}
}
}
static void particle_batch_cache_ensure_pos_and_seg(PTCacheEdit *edit,
ParticleSystem *psys,
ModifierData *md,
@@ -1466,7 +1054,7 @@ static void drw_particle_update_ptcache_edit(Object *object_eval,
}
}
static void drw_particle_update_ptcache(Object *object_eval, ParticleSystem *psys)
void drw_particle_update_ptcache(Object *object_eval, ParticleSystem *psys)
{
if ((object_eval->mode & OB_MODE_PARTICLE_EDIT) == 0) {
return;
@@ -1480,28 +1068,27 @@ static void drw_particle_update_ptcache(Object *object_eval, ParticleSystem *psy
}
}
struct ParticleDrawSource {
Object *object;
ParticleSystem *psys;
ModifierData *md;
PTCacheEdit *edit;
};
static void drw_particle_get_hair_source(Object *object,
ParticleSystem *psys,
ModifierData *md,
PTCacheEdit *edit,
ParticleDrawSource *r_draw_source)
ParticleDrawSource drw_particle_get_hair_source(Object *object,
ParticleSystem *psys,
ModifierData *md,
PTCacheEdit *edit,
const int additional_subdivision)
{
const DRWContext *draw_ctx = DRW_context_get();
r_draw_source->object = object;
r_draw_source->psys = psys;
r_draw_source->md = md;
r_draw_source->edit = edit;
if (psys_in_edit_mode(draw_ctx->depsgraph, psys)) {
r_draw_source->object = DEG_get_original(object);
r_draw_source->psys = psys_orig_get(psys);
object = DEG_get_original(object);
psys = psys_orig_get(psys);
}
ParticleBatchCache *cache = particle_batch_cache_get(psys);
ParticleDrawSource src = ParticleDrawSource(cache->hair.points_by_curve_storage,
cache->hair.evaluated_points_by_curve_storage,
math::clamp(additional_subdivision, 0, 3));
src.object = object;
src.psys = psys;
src.md = md;
src.edit = edit;
return src;
}
gpu::Batch *DRW_particles_batch_cache_get_hair(Object *object,
@@ -1511,8 +1098,7 @@ gpu::Batch *DRW_particles_batch_cache_get_hair(Object *object,
ParticleBatchCache *cache = particle_batch_cache_get(psys);
if (cache->hair.hairs == nullptr) {
drw_particle_update_ptcache(object, psys);
ParticleDrawSource source;
drw_particle_get_hair_source(object, psys, md, nullptr, &source);
ParticleDrawSource source = drw_particle_get_hair_source(object, psys, md, nullptr, 0);
ensure_seg_pt_count(source.edit, source.psys, &cache->hair);
particle_batch_cache_ensure_pos_and_seg(source.edit, source.psys, source.md, &cache->hair);
cache->hair.hairs = GPU_batch_create(
@@ -1705,54 +1291,377 @@ gpu::Batch *DRW_particles_batch_cache_get_edit_tip_points(Object *object,
return cache->edit_tip_points;
}
bool particles_ensure_procedural_data(Object *object,
ParticleSystem *psys,
ModifierData *md,
ParticleHairCache **r_hair_cache,
GPUMaterial *gpu_material,
int subdiv,
int thickness_res)
/* Can return DMCACHE_NOTFOUND in case of invalid mapping. */
static int particle_mface_index(const ParticleData &particle, int face_count_legacy)
{
bool need_ft_update = false;
if (!ELEM(particle.num_dmcache, DMCACHE_NOTFOUND, DMCACHE_ISCHILD)) {
return particle.num_dmcache;
}
if (particle.num < face_count_legacy) {
return (particle.num == DMCACHE_ISCHILD) ? DMCACHE_NOTFOUND : particle.num;
}
return DMCACHE_NOTFOUND;
}
static int particle_mface_index(const ChildParticle &particle, int /*face_count_legacy*/)
{
return particle.num;
}
drw_particle_update_ptcache(object, psys);
static float4 particle_mcol_convert(const MCol &mcol)
{
float4 col;
/* Convert to linear ushort and swizzle */
col[0] = BLI_color_from_srgb_table[mcol.b];
col[1] = BLI_color_from_srgb_table[mcol.g];
col[2] = BLI_color_from_srgb_table[mcol.r];
col[3] = mcol.a / 255.0f;
return col;
}
ParticleDrawSource source;
drw_particle_get_hair_source(object, psys, md, nullptr, &source);
template<typename ParticleDataT>
static float4 interpolate(const ParticleDataT &particle, Span<MFace> mfaces, Span<MCol> mcols)
{
int num = particle_mface_index(particle, mfaces.size());
if (num == DMCACHE_NOTFOUND) {
return float4(0, 0, 0, 1);
}
/* CustomDataLayer CD_MCOL has 4 structs per face. */
MCol mcol;
psys_interpolate_mcol(mcols.slice(num * 4, 4).data(), mfaces[num].v4, particle.fuv, &mcol);
return particle_mcol_convert(mcol);
}
ParticleSettings *part = source.psys->part;
ParticleBatchCache *cache = particle_batch_cache_get(source.psys);
*r_hair_cache = &cache->hair;
template<typename ParticleDataT>
static float2 interpolate(const ParticleDataT &particle, Span<MFace> mfaces, Span<MTFace> mtfaces)
{
int num = particle_mface_index(particle, mfaces.size());
if (num == DMCACHE_NOTFOUND) {
return float2(0);
}
float2 uv;
psys_interpolate_uvs(&mtfaces[num], mfaces[num].v4, particle.fuv, uv);
return uv;
}
(*r_hair_cache)->final[subdiv].strands_res = 1 << (part->draw_step + subdiv);
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;
}
/* Refreshed on combing and simulation. */
if ((*r_hair_cache)->proc_point_buf == nullptr ||
(gpu_material && (*r_hair_cache)->proc_length_buf == nullptr))
{
ensure_seg_pt_count(source.edit, source.psys, &cache->hair);
particle_batch_cache_ensure_procedural_pos(
source.edit, source.psys, &cache->hair, gpu_material);
need_ft_update = true;
template<typename T>
Span<T> span_from_custom_data_layer(const Mesh &mesh,
const eCustomDataType type,
const StringRef name)
{
int layer_id = CustomData_get_named_layer(&mesh.fdata_legacy, type, name);
return {static_cast<const T *>(CustomData_get_layer_n(&mesh.fdata_legacy, type, layer_id)),
/* There is 4 MCol per face. */
mesh.totface_legacy * (std::is_same_v<T, MCol> ? 4 : 1)};
}
template<typename T>
Span<T> span_from_custom_data_layer(const Mesh &mesh, const eCustomDataType type)
{
return {static_cast<const T *>(CustomData_get_layer(&mesh.fdata_legacy, type)),
mesh.totface_legacy};
}
template<typename InputT, typename OutputT, eCustomDataType data_type>
static gpu::VertBufPtr interpolate_face_corner_attribute_to_curve(ParticleDrawSource &src,
const StringRef name)
{
ParticleSystemModifierData *psmd = (ParticleSystemModifierData *)src.md;
Mesh &mesh = *psmd->mesh_final;
/* TODO(fclem): Use normalized integer format. */
gpu::VertBufPtr vbo = gpu::VertBufPtr(
GPU_vertbuf_create_with_format_ex(gpu::GenericVertexFormat<OutputT>::format(),
GPU_USAGE_STATIC | GPU_USAGE_FLAG_BUFFER_TEXTURE_ONLY));
vbo->allocate(src.curves_num());
MutableSpan<OutputT> data = vbo->data<OutputT>();
const int emit_from = psmd->psys->part->from;
/* True if no interpolation for child particle. */
const bool is_simple = (src.psys->part->childtype == PART_CHILD_PARTICLES) ||
!ELEM(emit_from, PART_FROM_FACE, PART_FROM_VOLUME);
BKE_mesh_tessface_ensure(&mesh);
Span<InputT> attr = span_from_custom_data_layer<InputT>(mesh, data_type, name);
Span<MFace> mfaces = span_from_custom_data_layer<MFace>(mesh, CD_MFACE);
Span<ChildParticle> children(src.psys->child, src.psys->totchild);
Span<ParticleData> particles(src.psys->particles, src.psys->totpart);
/* Index of the particle/hair curve. Note that the order of the loops matter. */
int curve_index = 0;
ParticleSpans part_spans = src.particles_get();
for (const int particle_index : part_spans.parent.index_range()) {
data[curve_index++] = interpolate(particles[particle_index], mfaces, attr);
}
/* Refreshed if active layer or custom data changes. */
if ((*r_hair_cache)->proc_strand_buf == nullptr) {
particle_batch_cache_ensure_procedural_strand_data(
source.edit, source.psys, source.md, &cache->hair);
if (is_simple) {
/* Fallback array if parent particles are not displayed. */
Vector<OutputT> parent_data;
if (part_spans.parent.is_empty()) {
parent_data.reserve(src.psys->totpart);
for (int particle_index : IndexRange(src.psys->totpart)) {
parent_data.append(interpolate(particles[particle_index], mfaces, attr));
}
}
Span<OutputT> data_parent(part_spans.parent.is_empty() ? parent_data.data() : data.data(),
src.psys->totpart);
for (const int particle_index : part_spans.children.index_range()) {
/* Simple copy of the parent data. */
data[curve_index++] = data_parent[children[particle_index].parent];
}
}
else {
for (const int particle_index : part_spans.children.index_range()) {
data[curve_index++] = interpolate(children[particle_index], mfaces, attr);
}
}
return vbo;
}
static gpu::VertBufPtr ensure_curve_attribute(ParticleDrawSource &src,
const Mesh &mesh,
const StringRef name,
bool &r_is_point_domain)
{
using namespace bke;
/* Note: All legacy hair attributes come from the emitter mesh and are on per curve domain. */
r_is_point_domain = false;
const AttributeAccessor attributes = mesh.attributes();
auto meta_data = attributes.lookup_meta_data(name);
if (meta_data && meta_data->domain == bke::AttrDomain::Corner) {
if (meta_data->data_type == AttrType::ColorByte) {
return interpolate_face_corner_attribute_to_curve<MCol, float4, CD_MCOL>(src, name);
}
if (meta_data->data_type == AttrType::Float2) {
return interpolate_face_corner_attribute_to_curve<MTFace, float2, CD_MTFACE>(src, name);
}
}
/* Attribute doesn't exist or is of an incompatible type.
* Replace it with a black curve domain attribute. */
return gpu::VertBuf::from_varray(VArray<float>::from_single(1, src.curves_num()));
}
void CurvesEvalCache::ensure_attribute(CurvesModule & /*module*/,
ParticleDrawSource &src,
const Mesh &mesh,
const StringRef name,
const int index)
{
char sampler_name[32];
drw_curves_get_attribute_sampler_name(name, sampler_name);
gpu::VertBufPtr attr_buf = ensure_curve_attribute(
src, mesh, name, attributes_point_domain[index]);
/* Existing final data may have been for a different attribute (with a different name or domain),
* free the data. */
this->curve_attributes_buf[index].reset();
/* Ensure final data for points. */
if (attributes_point_domain[index]) {
BLI_assert_unreachable();
}
else {
this->curve_attributes_buf[index] = std::move(attr_buf);
}
}
void CurvesEvalCache::ensure_attributes(CurvesModule &module,
ParticleDrawSource &src,
const GPUMaterial *gpu_material)
{
ParticleSystemModifierData *psmd = (ParticleSystemModifierData *)src.md;
if (psmd == nullptr || psmd->mesh_final == nullptr || src.curves_num() == 0) {
return;
}
const Mesh &mesh = *psmd->mesh_final;
const bke::AttributeAccessor attributes = mesh.attributes();
if (gpu_material) {
VectorSet<std::string> attrs_needed;
ListBase gpu_attrs = GPU_material_attributes(gpu_material);
LISTBASE_FOREACH (GPUMaterialAttribute *, gpu_attr, &gpu_attrs) {
StringRef name = gpu_attr->name;
if (name.is_empty()) {
if (std::optional<StringRef> uv_name = get_first_uv_name(attributes)) {
drw_attributes_add_request(&attrs_needed, *uv_name);
}
}
if (!attributes.contains(name)) {
continue;
}
drw_attributes_add_request(&attrs_needed, name);
}
if (!drw_attributes_overlap(&attr_used, &attrs_needed)) {
/* Some new attributes have been added, free all and start over. */
for (const int i : IndexRange(GPU_MAX_ATTR)) {
this->curve_attributes_buf[i].reset();
}
drw_attributes_merge(&attr_used, &attrs_needed);
}
drw_attributes_merge(&attr_used_over_time, &attrs_needed);
}
/* Refreshed only on subdiv count change. */
if ((*r_hair_cache)->final[subdiv].proc_buf == nullptr) {
particle_batch_cache_ensure_procedural_final_points(&cache->hair, subdiv);
need_ft_update = true;
for (const int i : attr_used.index_range()) {
if (this->curve_attributes_buf[i]) {
continue;
}
ensure_attribute(module, src, mesh, attr_used[i], i);
}
if ((*r_hair_cache)->final[subdiv].proc_hairs[thickness_res - 1] == nullptr) {
particle_batch_cache_ensure_procedural_indices(
source.edit, source.psys, &cache->hair, thickness_res, subdiv);
}
void CurvesEvalCache::ensure_common(ParticleDrawSource &src)
{
if (points_by_curve_buf) {
return;
}
return need_ft_update;
this->points_by_curve_buf = gpu::VertBuf::from_span(src.points_by_curve().data());
this->evaluated_points_by_curve_buf = gpu::VertBuf::from_span(
src.evaluated_points_by_curve().data());
/* Use the same type for all curves. */
auto type_varray = VArray<int8_t>::from_single(CURVE_TYPE_CATMULL_ROM, src.curves_num());
auto resolution_varray = VArray<int32_t>::from_single(src.resolution(), src.curves_num());
/* Not used. */
auto cyclic_offsets_varray = VArray<int32_t>::from_single(0, 2);
/* TODO(fclem): Optimize shaders to avoid needing to upload this data if data is uniform.
* This concerns all varray. */
this->curves_type_buf = gpu::VertBuf::from_varray(type_varray);
this->curves_resolution_buf = gpu::VertBuf::from_varray(resolution_varray);
this->curves_cyclic_buf = gpu::VertBuf::from_varray(cyclic_offsets_varray);
}
/* Copied from cycles. */
static float hair_shape_radius(float shape, float root, float tip, float time)
{
BLI_assert(time >= 0.0f);
BLI_assert(time <= 1.0f);
float radius = 1.0f - time;
if (shape < 0.0f) {
radius = powf(radius, 1.0f + shape);
}
else {
radius = powf(radius, 1.0f / (1.0f - shape));
}
return (radius * (root - tip)) + tip;
}
void CurvesEvalCache::ensure_positions(CurvesModule &module, ParticleDrawSource &src)
{
if (evaluated_pos_rad_buf) {
return;
}
if (src.curves_num() == 0) {
/* Garbage data. */
this->evaluated_pos_rad_buf = gpu::VertBuf::device_only<float4>(1);
this->evaluated_time_buf = gpu::VertBuf::device_only<float>(4);
this->curves_length_buf = gpu::VertBuf::device_only<float>(4);
return;
}
ensure_common(src);
gpu::VertBufPtr points_pos_buf = gpu::VertBuf::from_size<float3>(src.points_num());
gpu::VertBufPtr points_rad_buf = gpu::VertBuf::from_size<float>(src.points_num());
MutableSpan<float3> points_pos = points_pos_buf->data<float3>();
MutableSpan<float> points_rad = points_rad_buf->data<float>();
const ParticleSettings &part = *src.psys->part;
const float hair_rad_shape = part.shape;
const float hair_rad_root = part.rad_root * part.rad_scale * 0.5f;
const float hair_rad_tip = part.rad_tip * part.rad_scale * 0.5f;
const bool hair_close_tip = (part.shape_flag & PART_SHAPE_CLOSE_TIP) != 0;
int i = 0;
src.particles_get().foreach_strand([&](Span<ParticleCacheKey> strand) {
int j = 0;
for (const ParticleCacheKey &point : strand) {
points_pos[i] = point.co;
points_rad[i] = (hair_close_tip && (j == strand.index_range().last())) ?
0.0f :
hair_shape_radius(
hair_rad_shape, hair_rad_root, hair_rad_tip, point.time);
i++, j++;
}
});
this->evaluated_pos_rad_buf = gpu::VertBuf::device_only<float4>(src.evaluated_points_num());
float4x4 transform = src.object->world_to_object();
module.evaluate_positions(true,
false,
false,
false,
false,
src.curves_num(),
*this,
std::move(points_pos_buf),
std::move(points_rad_buf),
evaluated_pos_rad_buf,
transform);
/* TODO(fclem): Make time and length optional. */
this->evaluated_time_buf = gpu::VertBuf::device_only<float>(src.evaluated_points_num());
this->curves_length_buf = gpu::VertBuf::device_only<float>(src.curves_num());
module.evaluate_curve_length_intercept(false, src.curves_num(), *this);
}
gpu::VertBufPtr &CurvesEvalCache::indirection_buf_get(CurvesModule &module,
ParticleDrawSource &src,
int face_per_segment)
{
const bool is_ribbon = face_per_segment < 2;
gpu::VertBufPtr &indirection_buf = is_ribbon ? this->indirection_ribbon_buf :
this->indirection_cylinder_buf;
if (indirection_buf) {
return indirection_buf;
}
if (src.curves_num() == 0) {
/* Garbage data. */
indirection_buf = gpu::VertBuf::device_only<int>(4);
return indirection_buf;
}
ensure_common(src);
indirection_buf = module.evaluate_topology_indirection(
src.curves_num(), src.evaluated_points_num(), *this, is_ribbon, false);
return indirection_buf;
}
CurvesEvalCache &hair_particle_get_eval_cache(ParticleDrawSource &src)
{
ParticleBatchCache *cache = particle_batch_cache_get(src.psys);
CurvesEvalCache &eval_cache = cache->hair.eval_cache;
if (assign_if_different(eval_cache.resolution, src.resolution())) {
particle_batch_cache_clear_hair(&cache->hair);
}
return eval_cache;
}
} // namespace blender::draw

View File

@@ -18,6 +18,9 @@ namespace blender::draw {
void hair_init();
/**
* \note Only valid after #DRW_curves_update().
*/
gpu::VertBuf *hair_pos_buffer_get(Scene *scene,
Object *object,
ParticleSystem *psys,
@@ -39,7 +42,10 @@ gpu::Batch *hair_sub_pass_setup(PassSimple::Sub &sub_ps,
/** Curves. */
gpu::VertBuf *curves_pos_buffer_get(Scene *scene, Object *object);
/**
* \note Content of the vertex buf is only valid after #DRW_curves_update().
*/
gpu::VertBuf *curves_pos_buffer_get(Object *object);
gpu::Batch *curves_sub_pass_setup(PassMain::Sub &ps,
const Scene *scene,

View File

@@ -31,24 +31,10 @@ struct VolumeModule;
class ObjectRef;
} // namespace blender::draw
/* draw_hair.cc */
/**
* \note Only valid after #DRW_curves_update().
*/
blender::gpu::VertBuf *DRW_hair_pos_buffer_get(Object *object,
ParticleSystem *psys,
ModifierData *md);
/* draw_curves.cc */
namespace blender::draw {
/**
* \note Only valid after #DRW_curves_update().
*/
gpu::VertBuf *DRW_curves_pos_buffer_get(Object *object);
/* If drw_data is nullptr, DST global is accessed to get it. */
void DRW_curves_init(DRWData *drw_data = nullptr);
void DRW_curves_begin_sync(DRWData *drw_data);

View File

@@ -8,13 +8,12 @@
* \brief Contains procedural GPU hair drawing methods.
*/
#include "BLI_utildefines.h"
#include "DNA_curves_types.h"
#include "BLI_math_base.h"
#include "BKE_attribute.hh"
#include "BKE_curves.hh"
#include "BKE_customdata.hh"
#include "GPU_batch.hh"
#include "GPU_capabilities.hh"
@@ -29,6 +28,7 @@
#include "draw_cache_impl.hh"
#include "draw_common.hh"
#include "draw_context_private.hh"
#include "draw_curves_defines.hh"
#include "draw_curves_private.hh"
#include "draw_hair_private.hh"
#include "draw_shader.hh"
@@ -88,84 +88,208 @@ 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)
void CurvesModule::dispatch(const int curve_count, PassSimple::Sub &pass)
{
BLI_assert(input_buf != nullptr);
BLI_assert(output_buf != nullptr);
gpu::Shader *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);
/* Note that the GPU_max_work_group_count can be INT_MAX.
* Promote to 64bit int to avoid overflow. */
const int64_t max_strands_per_call = int64_t(GPU_max_work_group_count(0)) *
CURVES_PER_THREADGROUP;
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));
while (strands_start < curve_count) {
int batch_strands_len = std::min(int64_t(curve_count - strands_start), max_strands_per_call);
pass.push_constant("curves_start", strands_start);
pass.push_constant("curves_count", batch_strands_len);
pass.dispatch(divide_ceil_u(batch_strands_len, CURVES_PER_THREADGROUP));
strands_start += batch_strands_len;
}
}
static void drw_curves_cache_update_compute(CurvesEvalCache *cache)
gpu::VertBufPtr CurvesModule::evaluate_topology_indirection(const int curve_count,
const int point_count,
CurvesEvalCache &cache,
bool is_ribbon,
bool has_cyclic)
{
const int curves_num = cache->curves_num;
const int final_points_len = cache->final.resolution * curves_num;
if (final_points_len == 0) {
return;
int element_count = is_ribbon ? (point_count + curve_count) : (point_count - curve_count);
if (has_cyclic) {
element_count += curve_count;
}
gpu::VertBufPtr indirection_buf = gpu::VertBuf::device_only<int>(element_count);
drw_curves_cache_update_compute(cache, curves_num, cache->final.proc_buf, cache->proc_point_buf);
PassSimple::Sub &pass = refine.sub("Topology");
pass.shader_set(DRW_shader_curves_topology_get());
pass.bind_ssbo("evaluated_offsets_buf", cache.evaluated_points_by_curve_buf);
pass.bind_ssbo("curves_cyclic_buf", cache.curves_cyclic_buf);
pass.bind_ssbo("indirection_buf", indirection_buf);
pass.push_constant("is_ribbon_topology", is_ribbon);
pass.push_constant("use_cyclic", has_cyclic);
dispatch(curve_count, pass);
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]);
}
return indirection_buf;
}
static CurvesEvalCache *drw_curves_cache_get(Curves &curves,
GPUMaterial *gpu_material,
int subdiv,
int thickness_res)
void CurvesModule::evaluate_curve_attribute(const bool has_catmull,
const bool has_bezier,
const bool has_poly,
const bool has_nurbs,
const bool has_cyclic,
const int curve_count,
CurvesEvalCache &cache,
CurvesEvalShader shader_type,
gpu::VertBufPtr input_buf,
gpu::VertBufPtr &output_buf,
gpu::VertBuf *input2_buf /* = nullptr */,
float4x4 transform /* = float4x4::identity() */)
{
CurvesEvalCache *cache;
const bool update = curves_ensure_procedural_data(
&curves, &cache, gpu_material, subdiv, thickness_res);
BLI_assert(input_buf != nullptr);
BLI_assert(output_buf != nullptr);
if (update) {
drw_curves_cache_update_compute(cache);
gpu::Shader *shader = DRW_shader_curves_refine_get(shader_type);
const char *pass_name = nullptr;
switch (shader_type) {
case CURVES_EVAL_POSITION:
pass_name = "Position";
break;
case CURVES_EVAL_FLOAT:
pass_name = "Float Attribute";
break;
case CURVES_EVAL_FLOAT2:
pass_name = "Float2 Attribute";
break;
case CURVES_EVAL_FLOAT3:
pass_name = "Float3 Attribute";
break;
case CURVES_EVAL_FLOAT4:
pass_name = "Float4 Attribute";
break;
case CURVES_EVAL_LENGTH_INTERCEPT:
pass_name = "Length-Intercept Attributes";
break;
}
return cache;
PassSimple::Sub &pass = refine.sub(pass_name);
pass.bind_ssbo(POINTS_BY_CURVES_SLOT, cache.points_by_curve_buf);
pass.bind_ssbo(CURVE_TYPE_SLOT, cache.curves_type_buf);
pass.bind_ssbo(CURVE_CYCLIC_SLOT, cache.curves_cyclic_buf);
pass.bind_ssbo(CURVE_RESOLUTION_SLOT, cache.curves_resolution_buf);
pass.bind_ssbo(EVALUATED_POINT_SLOT, cache.evaluated_points_by_curve_buf);
switch (shader_type) {
case CURVES_EVAL_POSITION:
pass.bind_ssbo(POINT_POSITIONS_SLOT, input_buf);
pass.bind_ssbo(POINT_RADII_SLOT, input2_buf);
pass.bind_ssbo(EVALUATED_POS_RAD_SLOT, cache.evaluated_pos_rad_buf);
/* Move ownership of the radius input vbo to the module. */
this->transient_buffers.append(gpu::VertBufPtr(input2_buf));
break;
case CURVES_EVAL_FLOAT:
case CURVES_EVAL_FLOAT2:
case CURVES_EVAL_FLOAT3:
case CURVES_EVAL_FLOAT4:
pass.bind_ssbo(POINT_ATTR_SLOT, input_buf);
pass.bind_ssbo(EVALUATED_ATTR_SLOT, output_buf);
break;
case CURVES_EVAL_LENGTH_INTERCEPT:
pass.bind_ssbo(EVALUATED_POS_RAD_SLOT, cache.evaluated_pos_rad_buf);
pass.bind_ssbo(EVALUATED_TIME_SLOT, cache.evaluated_time_buf);
pass.bind_ssbo(CURVES_LENGTH_SLOT, cache.curves_length_buf);
/* Synchronize positions reads. */
pass.barrier(GPU_BARRIER_SHADER_STORAGE);
break;
}
if (has_catmull) {
PassSimple::Sub &sub = pass.sub("Catmull-Rom");
sub.specialize_constant(shader, "evaluated_type", int(CURVE_TYPE_CATMULL_ROM));
sub.shader_set(shader);
/* Dummy, not used for Catmull-Rom. */
sub.bind_ssbo("handles_positions_left_buf", this->dummy_vbo);
sub.bind_ssbo("handles_positions_right_buf", this->dummy_vbo);
sub.bind_ssbo("bezier_offsets_buf", this->dummy_vbo);
/* Bake object transform for legacy hair particle. */
sub.push_constant("transform", transform);
sub.push_constant("use_cyclic", has_cyclic);
dispatch(curve_count, sub);
}
if (has_bezier) {
PassSimple::Sub &sub = pass.sub("Bezier");
sub.specialize_constant(shader, "evaluated_type", int(CURVE_TYPE_BEZIER));
sub.shader_set(shader);
sub.bind_ssbo("handles_positions_left_buf", cache.handles_positions_left_buf);
sub.bind_ssbo("handles_positions_right_buf", cache.handles_positions_right_buf);
sub.bind_ssbo("bezier_offsets_buf", cache.bezier_offsets_buf);
/* Bake object transform for legacy hair particle. */
sub.push_constant("transform", transform);
sub.push_constant("use_cyclic", has_cyclic);
dispatch(curve_count, sub);
}
if (has_nurbs) {
PassSimple::Sub &sub = pass.sub("Nurbs");
sub.specialize_constant(shader, "evaluated_type", int(CURVE_TYPE_NURBS));
sub.shader_set(shader);
sub.bind_ssbo("curves_resolution_buf", cache.curves_order_buf);
sub.bind_ssbo("handles_positions_left_buf", cache.basis_cache_buf);
sub.bind_ssbo("handles_positions_right_buf",
cache.control_weights_buf.get() ? cache.control_weights_buf :
cache.basis_cache_buf);
sub.bind_ssbo("bezier_offsets_buf", cache.basis_cache_offset_buf);
sub.push_constant("use_point_weight", cache.control_weights_buf.get() != nullptr);
/* Bake object transform for legacy hair particle. */
sub.push_constant("transform", transform);
sub.push_constant("use_cyclic", has_cyclic);
dispatch(curve_count, sub);
}
if (has_poly) {
PassSimple::Sub &sub = pass.sub("Poly");
sub.specialize_constant(shader, "evaluated_type", int(CURVE_TYPE_POLY));
sub.shader_set(shader);
/* Dummy, not used for Poly. */
sub.bind_ssbo("curves_resolution_buf", this->dummy_vbo);
sub.bind_ssbo("handles_positions_left_buf", this->dummy_vbo);
sub.bind_ssbo("handles_positions_right_buf", this->dummy_vbo);
sub.bind_ssbo("bezier_offsets_buf", this->dummy_vbo);
/* Bake object transform for legacy hair particle. */
sub.push_constant("transform", transform);
sub.push_constant("use_cyclic", has_cyclic);
dispatch(curve_count, sub);
}
/* Move ownership of the input vbo to the module. */
this->transient_buffers.append(std::move(input_buf));
}
gpu::VertBuf *DRW_curves_pos_buffer_get(Object *object)
void CurvesModule::evaluate_curve_length_intercept(const bool has_cyclic,
const int curve_count,
CurvesEvalCache &cache)
{
const DRWContext *draw_ctx = DRW_context_get();
const Scene *scene = draw_ctx->scene;
gpu::Shader *shader = DRW_shader_curves_refine_get(CURVES_EVAL_LENGTH_INTERCEPT);
const int subdiv = scene->r.hair_subdiv;
const int thickness_res = (scene->r.hair_type == SCE_HAIR_SHAPE_STRAND) ? 1 : 2;
PassSimple::Sub &pass = refine.sub("Length-Intercept Attributes");
pass.shader_set(shader);
pass.bind_ssbo(POINTS_BY_CURVES_SLOT, cache.points_by_curve_buf);
pass.bind_ssbo(CURVE_TYPE_SLOT, cache.curves_type_buf);
pass.bind_ssbo(CURVE_CYCLIC_SLOT, cache.curves_cyclic_buf);
pass.bind_ssbo(CURVE_RESOLUTION_SLOT, cache.curves_resolution_buf);
pass.bind_ssbo(EVALUATED_POINT_SLOT, cache.evaluated_points_by_curve_buf);
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;
pass.bind_ssbo(EVALUATED_POS_RAD_SLOT, cache.evaluated_pos_rad_buf);
pass.bind_ssbo(EVALUATED_TIME_SLOT, cache.evaluated_time_buf);
pass.bind_ssbo(CURVES_LENGTH_SLOT, cache.curves_length_buf);
pass.barrier(GPU_BARRIER_SHADER_STORAGE);
/* Bake object transform for legacy hair particle. */
pass.push_constant("use_cyclic", has_cyclic);
dispatch(curve_count, pass);
}
static int attribute_index_in_material(GPUMaterial *gpu_material, const StringRef name)
static int attribute_index_in_material(const GPUMaterial *gpu_material,
const StringRef name,
bool is_curve_length = false,
bool is_curve_intercept = false)
{
if (!gpu_material) {
return -1;
@@ -175,10 +299,19 @@ static int attribute_index_in_material(GPUMaterial *gpu_material, const StringRe
ListBase gpu_attrs = GPU_material_attributes(gpu_material);
LISTBASE_FOREACH (GPUMaterialAttribute *, gpu_attr, &gpu_attrs) {
if (gpu_attr->name == name) {
if (gpu_attr->is_hair_length == true) {
if (gpu_attr->is_hair_length == is_curve_length) {
return index;
}
}
else if (gpu_attr->is_hair_intercept == true) {
if (gpu_attr->is_hair_intercept == is_curve_intercept) {
return index;
}
}
else if (gpu_attr->name == name) {
return index;
}
index++;
}
@@ -190,84 +323,31 @@ void DRW_curves_update(draw::Manager &manager)
DRW_submission_start();
/* TODO(fclem): Remove Global access. */
PassSimple &pass = drw_get().data->curves_module->refine;
CurvesModule &module = *drw_get().data->curves_module;
/* NOTE: This also update legacy hairs too as they populate the same pass. */
manager.submit(pass);
manager.submit(module.refine);
GPU_memory_barrier(GPU_BARRIER_SHADER_STORAGE);
module.transient_buffers.clear();
/* Make sure calling this function again will not subdivide the same data. */
pass.init();
module.refine.init();
DRW_submission_end();
}
/* New Draw Manager. */
static CurvesEvalCache *curves_cache_get(Curves &curves,
GPUMaterial *gpu_material,
int subdiv,
int thickness_res)
gpu::VertBuf *curves_pos_buffer_get(Object *object)
{
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;
CurvesEvalCache &cache = curves_get_eval_cache(curves);
cache.ensure_positions(module, curves.geometry.wrap());
return cache.evaluated_pos_rad_buf.get();
}
static std::optional<StringRef> get_first_uv_name(const bke::AttributeAccessor &attributes)
@@ -282,25 +362,33 @@ static std::optional<StringRef> get_first_uv_name(const bke::AttributeAccessor &
return name;
}
template<typename PassT>
gpu::Batch *curves_sub_pass_setup_implementation(PassT &sub_ps,
const Scene *scene,
Object *ob,
GPUMaterial *gpu_material)
/* Return true if attribute exists in shader. */
static bool set_attribute_type(const GPUMaterial *gpu_material,
const StringRef name,
CurvesInfosBuf &curves_infos,
const bool is_point_domain)
{
/** 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);
/* 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) {
return false;
}
curves_infos.is_point_attribute[index][0] = is_point_domain;
return true;
}
template<typename PassT>
void curves_bind_resources_implementation(PassT &sub_ps,
CurvesModule &module,
CurvesEvalCache &cache,
const int face_per_segment,
GPUMaterial *gpu_material,
gpu::VertBufPtr &indirection_buf,
const std::optional<StringRef> uv_name)
{
/* Ensure we have no unbound resources.
* Required for Vulkan.
* Fixes issues with certain GL drivers not drawing anything. */
@@ -309,6 +397,8 @@ gpu::Batch *curves_sub_pass_setup_implementation(PassT &sub_ps,
sub_ps.bind_texture("a", module.dummy_vbo);
sub_ps.bind_texture("c", module.dummy_vbo);
sub_ps.bind_texture("ac", module.dummy_vbo);
sub_ps.bind_texture("l", module.dummy_vbo);
sub_ps.bind_texture("i", module.dummy_vbo);
if (gpu_material) {
ListBase attr_list = GPU_material_attributes(gpu_material);
ListBaseWrapper<GPUMaterialAttribute> attrs(attr_list);
@@ -317,85 +407,133 @@ gpu::Batch *curves_sub_pass_setup_implementation(PassT &sub_ps,
}
}
/* 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;
CurvesInfosBuf &curves_infos = module.ubo_pool.alloc();
/* 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);
{
/* TODO(fclem): Compute only if needed. */
const int index = attribute_index_in_material(gpu_material, "", true, false);
if (index != -1) {
sub_ps.bind_texture("l", cache.curves_length_buf);
curves_infos.is_point_attribute[index][0] = false;
}
}
{
/* TODO(fclem): Compute only if needed. */
const int index = attribute_index_in_material(gpu_material, "", false, true);
if (index != -1) {
sub_ps.bind_texture("i", cache.evaluated_time_buf);
curves_infos.is_point_attribute[index][0] = true;
}
}
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;
const VectorSet<std::string> &attrs = cache.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]) {
if (cache.attributes_point_domain[i]) {
if (!cache.evaluated_attributes_buf[i]) {
continue;
}
sub_ps.bind_texture(sampler_name, curves_cache->proc_attributes_buf[i]);
if (set_attribute_type(gpu_material, name, curves_infos, true)) {
sub_ps.bind_texture(sampler_name, cache.evaluated_attributes_buf[i]);
}
if (name == uv_name) {
sub_ps.bind_texture("a", curves_cache->proc_attributes_buf[i]);
if (set_attribute_type(gpu_material, "", curves_infos, true)) {
sub_ps.bind_texture("a", cache.evaluated_attributes_buf[i]);
}
}
}
else {
if (!curves_cache->final.attributes_buf[i]) {
if (!cache.curve_attributes_buf[i]) {
continue;
}
sub_ps.bind_texture(sampler_name, curves_cache->final.attributes_buf[i]);
if (set_attribute_type(gpu_material, name, curves_infos, false)) {
sub_ps.bind_texture(sampler_name, cache.curve_attributes_buf[i]);
}
if (name == uv_name) {
sub_ps.bind_texture("a", curves_cache->final.attributes_buf[i]);
if (set_attribute_type(gpu_material, "", curves_infos, false)) {
sub_ps.bind_texture("a", cache.curve_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.half_cylinder_face_count = face_per_segment;
curves_infos.vertex_per_segment = face_per_segment < 2 ? (face_per_segment + 1) :
((face_per_segment + 1) * 2 + 1);
curves_infos.push_update();
sub_ps.bind_ubo("drw_curves", curves_infos);
sub_ps.bind_texture("curves_pos_rad_buf", cache.evaluated_pos_rad_buf);
sub_ps.bind_texture("curves_indirection_buf", indirection_buf);
}
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);
void curves_bind_resources(PassMain::Sub &sub_ps,
CurvesModule &module,
CurvesEvalCache &cache,
const int face_per_segment,
GPUMaterial *gpu_material,
gpu::VertBufPtr &indirection_buf,
const std::optional<StringRef> active_uv_name)
{
curves_bind_resources_implementation(
sub_ps, module, cache, face_per_segment, gpu_material, indirection_buf, active_uv_name);
}
return curves_cache->final.proc_hairs;
void curves_bind_resources(PassSimple::Sub &sub_ps,
CurvesModule &module,
CurvesEvalCache &cache,
const int face_per_segment,
GPUMaterial *gpu_material,
gpu::VertBufPtr &indirection_buf,
const std::optional<StringRef> active_uv_name)
{
curves_bind_resources_implementation(
sub_ps, module, cache, face_per_segment, gpu_material, indirection_buf, active_uv_name);
}
template<typename PassT>
gpu::Batch *curves_sub_pass_setup_implementation(PassT &sub_ps,
const Scene *scene,
Object *ob,
GPUMaterial *gpu_material)
{
BLI_assert(ob->type == OB_CURVES);
Curves &curves_id = DRW_object_get_data_for_drawing<Curves>(*ob);
const bke::CurvesGeometry &curves = curves_id.geometry.wrap();
const int face_per_segment = (scene->r.hair_type == SCE_HAIR_SHAPE_STRAND) ? 0 :
(scene->r.hair_type == SCE_HAIR_SHAPE_CYLINDER) ? 3 :
1;
CurvesEvalCache &curves_cache = curves_get_eval_cache(curves_id);
if (curves.curves_num() == 0) {
/* Nothing to draw. Just return an empty drawcall that will be skipped. */
return curves_cache.batch_get(0, 0, face_per_segment, false);
}
CurvesModule &module = *drw_get().data->curves_module;
curves_cache.ensure_positions(module, curves);
curves_cache.ensure_attributes(module, curves, gpu_material);
gpu::VertBufPtr &indirection_buf = curves_cache.indirection_buf_get(
module, curves, face_per_segment);
const std::optional<StringRef> uv_name = get_first_uv_name(
curves_id.geometry.wrap().attributes());
curves_bind_resources(
sub_ps, module, curves_cache, face_per_segment, gpu_material, indirection_buf, uv_name);
return curves_cache.batch_get(curves.evaluated_points_num(),
curves.curves_num(),
face_per_segment,
curves.has_cyclic_curve());
}
gpu::Batch *curves_sub_pass_setup(PassMain::Sub &ps,

View File

@@ -0,0 +1,44 @@
/* SPDX-FileCopyrightText: 2025 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup draw
*
* List of defines that are shared with the GPUShaderCreateInfos. We do this to avoid
* dragging larger headers into the createInfo pipeline which would cause problems.
*/
#pragma once
/** Curves evaluation. */
#define CURVES_PER_THREADGROUP 64
#define POINTS_BY_CURVES_SLOT 0
#define CURVE_TYPE_SLOT 1
#define CURVE_RESOLUTION_SLOT 2
#define EVALUATED_POINT_SLOT 3
#define CURVE_CYCLIC_SLOT 4
#define HANDLES_POS_LEFT_SLOT 5
#define HANDLES_POS_RIGHT_SLOT 6
#define BEZIER_OFFSETS_SLOT 7
/* Nurbs (alias of other buffers). */
#define CURVES_ORDER_SLOT CURVE_RESOLUTION_SLOT
#define BASIS_CACHE_SLOT HANDLES_POS_LEFT_SLOT
#define CONTROL_WEIGHTS_SLOT HANDLES_POS_RIGHT_SLOT
#define BASIS_CACHE_OFFSET_SLOT BEZIER_OFFSETS_SLOT
/* Position evaluation. */
#define POINT_POSITIONS_SLOT 8
#define POINT_RADII_SLOT 9
#define EVALUATED_POS_RAD_SLOT 10
/* Attribute evaluation. */
#define POINT_ATTR_SLOT 8
#define EVALUATED_ATTR_SLOT 9
/* Intercept evaluation. */
#define EVALUATED_TIME_SLOT 8
#define CURVES_LENGTH_SLOT 9

View File

@@ -12,10 +12,22 @@
#include <string>
#include "GPU_shader.hh"
#include "GPU_vertex_buffer.hh"
#include "BLI_vector_set.hh"
#include "draw_pass.hh"
struct Curves;
struct Object;
struct ParticleSystem;
struct PTCacheEdit;
struct ModifierData;
struct ParticleCacheKey;
namespace blender::bke {
class CurvesGeometry;
} // namespace blender::bke
namespace blender::gpu {
class Batch;
class VertBuf;
@@ -24,84 +36,239 @@ struct GPUMaterial;
namespace blender::draw {
#define MAX_THICKRES 2 /* see eHairType */
struct CurvesModule;
#define MAX_FACE_PER_SEGMENT 5
#define MAX_HAIR_SUBDIV 4 /* see hair_subdiv rna */
enum CurvesEvalShader {
CURVES_EVAL_CATMULL_ROM = 0,
CURVES_EVAL_BEZIER = 1,
CURVES_EVAL_POSITION = 0,
CURVES_EVAL_FLOAT = 1,
CURVES_EVAL_FLOAT2 = 2,
CURVES_EVAL_FLOAT3 = 3,
CURVES_EVAL_FLOAT4 = 4,
CURVES_EVAL_LENGTH_INTERCEPT = 5,
};
#define CURVES_EVAL_SHADER_NUM 3
struct CurvesEvalFinalCache {
/** The "additional subdivision" setting from the scene. See #MAX_HAIR_SUBDIV. */
int hair_subdiv;
/* The "strand or strip" setting from the scene. See #MAX_THICKRES. */
int thickres;
/* Legacy Hair Particle. */
/* Output of the subdivision stage: vertex buffer sized to subdiv level. */
gpu::VertBuf *proc_buf;
struct ParticleSpans {
Span<ParticleCacheKey *> parent;
Span<ParticleCacheKey *> children;
/** Just contains a huge index buffer used to draw the final curves. */
gpu::Batch *proc_hairs;
void foreach_strand(FunctionRef<void(Span<ParticleCacheKey>)> callback);
};
/** Points per curve, at least 2. */
int resolution;
struct ParticleDrawSource {
public:
Object *object = nullptr;
ParticleSystem *psys = nullptr;
ModifierData *md = nullptr;
PTCacheEdit *edit = nullptr;
private:
Vector<int> &points_by_curve_storage_;
Vector<int> &evaluated_points_by_curve_storage_;
int additional_subdivision_;
public:
ParticleDrawSource(Vector<int> &points_by_curve_storage,
Vector<int> &evaluated_points_by_curve_storage,
int additional_subdivision)
: points_by_curve_storage_(points_by_curve_storage),
evaluated_points_by_curve_storage_(evaluated_points_by_curve_storage),
additional_subdivision_(additional_subdivision)
{
}
int curves_num()
{
if (points_by_curve_storage_.is_empty()) {
points_by_curve();
}
return points_by_curve_storage_.size() - 1;
}
int points_num()
{
if (points_by_curve_storage_.is_empty()) {
points_by_curve();
}
return points_by_curve_storage_.last();
}
int evaluated_points_num()
{
if (additional_subdivision_ == 0) {
return points_num();
}
evaluated_points_by_curve();
return evaluated_points_by_curve_storage_.last();
}
int resolution()
{
return 1 << additional_subdivision_;
}
OffsetIndices<int> points_by_curve();
OffsetIndices<int> evaluated_points_by_curve();
ParticleSpans particles_get();
};
#define CURVES_EVAL_SHADER_NUM 5
/* Curves procedural display: Evaluation is done on the GPU. */
struct CurvesEvalCache {
/* --- Required attributes. --- */
/** Position and radius per evaluated point. Always evaluated. */
gpu::VertBufPtr evaluated_pos_rad_buf;
/** Intercept time per evaluated point. */
/* TODO(fclem): Move it to generic point domain attributes. */
gpu::VertBufPtr evaluated_time_buf;
/** Intercept time per curve. */
/* TODO(fclem): Move it to generic curve domain attributes. */
gpu::VertBufPtr curves_length_buf;
/* --- Indirection buffers. --- */
/* Map primitive to point ID and curve ID. Contains restart indices for line and triangle strip
* primitive. */
gpu::VertBufPtr indirection_ribbon_buf;
/* Map primitive to point ID and curve ID. Compacted for cylinder primitive. */
gpu::VertBufPtr indirection_cylinder_buf;
/* --- Buffers common to all curve types. --- */
/** Buffer containing `CurveGeometry::points_by_curve()`. */
gpu::VertBufPtr points_by_curve_buf;
/** Buffer containing `CurveGeometry::evaluated_points_by_curve()`. */
gpu::VertBufPtr evaluated_points_by_curve_buf;
/** Buffer containing `CurveGeometry::curve_types()`. */
gpu::VertBufPtr curves_type_buf;
/** Buffer containing `CurveGeometry::resolution()`. */
gpu::VertBufPtr curves_resolution_buf;
/** Buffer containing `CurveGeometry::cyclic_offsets()` or dummy data if not needed. */
gpu::VertBufPtr curves_cyclic_buf;
/* --- Buffers only needed if geometry has Bezier curves. Dummy sized otherwise. --- */
/** Buffer containing `CurveGeometry::handle_positions_left()`. */
gpu::VertBufPtr handles_positions_left_buf;
/** Buffer containing `CurveGeometry::handle_positions_right()`. */
gpu::VertBufPtr handles_positions_right_buf;
/** Buffer containing `EvaluatedOffsets::all_bezier_offsets`. */
gpu::VertBufPtr bezier_offsets_buf;
/* --- Buffers only needed if geometry has Nurbs curves. Dummy sized otherwise. --- */
/** Buffer containing `CurveGeometry::nurbs_orders()`. */
gpu::VertBufPtr curves_order_buf;
/** Buffer containing `CurveGeometry::nurbs_weights()`. */
gpu::VertBufPtr control_weights_buf;
/** Buffer containing all `nurbs::BasisCache` concatenated. */
gpu::VertBufPtr basis_cache_buf;
/** Buffer containing offsets to the start of each `nurbs::BasisCache` for each curve. */
gpu::VertBufPtr basis_cache_offset_buf;
/* --- Generic Attributes. --- */
/** Attributes currently being drawn or about to be drawn. */
VectorSet<std::string> attr_used;
/**
* Attributes that were used at some point. This is used for garbage collection, to remove
* attributes that are not used in shaders anymore due to user edits.
*/
VectorSet<std::string> attr_used_over_time;
/**
* The last time in seconds that the `attr_used` and `attr_used_over_time` were exactly the same.
* If the delta between this time and the current scene time is greater than the timeout set in
* user preferences (`U.vbotimeout`) then garbage collection is performed.
*/
int last_attr_matching_time;
/* Attributes stored per curve. Nullptr if attribute is not from this domain. */
gpu::VertBufPtr curve_attributes_buf[GPU_MAX_ATTR];
/* Output of the evaluation stage. This is only used by attributes on point domain. */
gpu::VertBufPtr evaluated_attributes_buf[GPU_MAX_ATTR];
/* If attribute is point domain, use evaluated_attributes_buf. Otherwise curve_attributes_buf. */
std::array<bool, GPU_MAX_ATTR> attributes_point_domain;
/* Output of the subdivision stage: vertex buffers sized to subdiv level. This is only attributes
* on point domain. */
gpu::VertBuf *attributes_buf[GPU_MAX_ATTR];
/* --- Procedural Drawcalls. --- */
std::array<gpu::Batch *, MAX_FACE_PER_SEGMENT> batch;
void ensure_attribute(struct CurvesModule &module,
const bke::CurvesGeometry &curves,
StringRef name,
int index);
void ensure_attributes(struct CurvesModule &module,
const bke::CurvesGeometry &curves,
const GPUMaterial *gpu_material);
void ensure_common(const bke::CurvesGeometry &curves);
void ensure_bezier(const bke::CurvesGeometry &curves);
void ensure_nurbs(const bke::CurvesGeometry &curves);
void ensure_positions(CurvesModule &module, const bke::CurvesGeometry &curves);
gpu::VertBufPtr &indirection_buf_get(CurvesModule &module,
const bke::CurvesGeometry &curves,
int face_per_segment);
gpu::Batch *batch_get(int evaluated_point_count,
int curve_count,
int face_per_segment,
bool use_cyclic);
void discard_attributes();
void clear();
/* --- Legacy Hair Particle system. --- */
int resolution = 0;
void ensure_attribute(CurvesModule &module,
ParticleDrawSource &src,
const Mesh &mesh,
const StringRef name,
const int index);
void ensure_attributes(CurvesModule &module,
ParticleDrawSource &src,
const GPUMaterial *gpu_material);
void ensure_common(ParticleDrawSource &src);
void ensure_positions(CurvesModule &module, ParticleDrawSource &src);
gpu::VertBufPtr &indirection_buf_get(CurvesModule &module,
ParticleDrawSource &src,
int face_per_segment);
private:
/* In the case where there is cyclic curves, add one padding point per curve to ensure easy
* indexing in the drawing shader. */
int evaluated_point_count_with_cyclic(const bke::CurvesGeometry &curves);
};
/* Curves procedural display: Evaluation is done on the GPU. */
struct CurvesEvalCache {
/* Control point positions on evaluated data-block combined with parameter data. */
gpu::VertBuf *proc_point_buf;
/** Info of control points strands (segment count and base index) */
gpu::VertBuf *proc_strand_buf;
/* Curve length data. */
gpu::VertBuf *proc_length_buf;
gpu::VertBuf *proc_strand_seg_buf;
CurvesEvalFinalCache final;
/* For point attributes, which need subdivision, these buffers contain the input data.
* For curve domain attributes, which do not need subdivision, these are the final data. */
gpu::VertBuf *proc_attributes_buf[GPU_MAX_ATTR];
std::array<bool, GPU_MAX_ATTR> proc_attributes_point_domain;
int curves_num;
int points_num;
};
/**
* Ensure all necessary textures and buffers exist for GPU accelerated drawing.
*/
bool curves_ensure_procedural_data(Curves *curves_id,
CurvesEvalCache **r_cache,
const GPUMaterial *gpu_material,
int subdiv,
int thickness_res);
CurvesEvalCache &curves_get_eval_cache(Curves &curves_id);
void drw_curves_get_attribute_sampler_name(StringRef layer_name, char r_sampler_name[32]);
void curves_bind_resources(draw::PassMain::Sub &sub_ps,
CurvesModule &module,
CurvesEvalCache &cache,
const int face_per_segment,
GPUMaterial *gpu_material,
gpu::VertBufPtr &indirection_buf,
std::optional<StringRef> active_uv_name);
void curves_bind_resources(draw::PassSimple::Sub &sub_ps,
CurvesModule &module,
CurvesEvalCache &cache,
const int face_per_segment,
GPUMaterial *gpu_material,
gpu::VertBufPtr &indirection_buf,
std::optional<StringRef> active_uv_name);
} // namespace blender::draw

View File

@@ -8,158 +8,47 @@
* \brief Contains procedural GPU hair drawing methods.
*/
#include "DNA_scene_types.h"
#include "DRW_render.hh"
#include "BKE_customdata.hh"
#include "BLI_math_matrix.h"
#include "BLI_math_vector.h"
#include "DNA_collection_types.h"
#include "DNA_modifier_types.h"
#include "DNA_particle_types.h"
#include "DNA_scene_types.h"
#include "BKE_duplilist.hh"
#include "DRW_render.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 "draw_common_c.hh"
#include "draw_context_private.hh"
#include "draw_hair_private.hh"
#include "draw_manager.hh"
#include "draw_shader.hh"
#include "draw_shader_shared.hh"
static void drw_hair_particle_cache_update_compute(ParticleHairCache *cache, const int subdiv)
{
const int strands_len = cache->strands_len;
const int final_points_len = cache->final[subdiv].strands_res * strands_len;
if (final_points_len > 0) {
using namespace blender::draw;
blender::gpu::Shader *shader = DRW_shader_hair_refine_get(PART_REFINE_CATMULL_ROM);
/* TODO(fclem): Remove Global access. */
PassSimple &pass = drw_get().data->curves_module->refine;
pass.shader_set(shader);
pass.bind_texture("hairPointBuffer", cache->proc_point_buf);
pass.bind_texture("hairStrandBuffer", cache->proc_strand_buf);
pass.bind_texture("hairStrandSegBuffer", cache->proc_strand_seg_buf);
pass.push_constant("hairStrandsRes", &cache->final[subdiv].strands_res);
pass.bind_ssbo("posTime", cache->final[subdiv].proc_buf);
const int max_strands_per_call = GPU_max_work_group_count(0);
int strands_start = 0;
while (strands_start < strands_len) {
int batch_strands_len = std::min(strands_len - strands_start, max_strands_per_call);
pass.push_constant("hairStrandOffset", strands_start);
pass.dispatch(int3(batch_strands_len, cache->final[subdiv].strands_res, 1));
strands_start += batch_strands_len;
}
}
}
static ParticleHairCache *drw_hair_particle_cache_get(Object *object,
ParticleSystem *psys,
ModifierData *md,
GPUMaterial *gpu_material,
int subdiv,
int thickness_res)
{
using namespace blender::draw;
ParticleHairCache *cache;
bool update = particles_ensure_procedural_data(
object, psys, md, &cache, gpu_material, subdiv, thickness_res);
if (update) {
drw_hair_particle_cache_update_compute(cache, subdiv);
}
return cache;
}
blender::gpu::VertBuf *DRW_hair_pos_buffer_get(Object *object,
ParticleSystem *psys,
ModifierData *md)
{
const DRWContext *draw_ctx = DRW_context_get();
Scene *scene = draw_ctx->scene;
int subdiv = scene->r.hair_subdiv;
int thickness_res = (scene->r.hair_type == SCE_HAIR_SHAPE_STRAND) ? 1 : 2;
ParticleHairCache *cache = drw_hair_particle_cache_get(
object, psys, md, nullptr, subdiv, thickness_res);
return cache->final[subdiv].proc_buf;
}
/* New Draw Manager. */
#include "draw_common.hh"
namespace blender::draw {
static ParticleHairCache *hair_particle_cache_get(Object *object,
ParticleSystem *psys,
ModifierData *md,
GPUMaterial *gpu_material,
int subdiv,
int thickness_res)
{
using namespace blender::draw;
ParticleHairCache *cache;
bool update = particles_ensure_procedural_data(
object, psys, md, &cache, gpu_material, subdiv, thickness_res);
if (!update) {
return cache;
}
CurvesModule &module = *drw_get().data->curves_module;
const int strands_len = cache->strands_len;
const int final_points_len = cache->final[subdiv].strands_res * strands_len;
if (final_points_len > 0) {
PassSimple::Sub &ob_ps = module.refine.sub("Object Pass");
ob_ps.shader_set(DRW_shader_hair_refine_get(PART_REFINE_CATMULL_ROM));
ob_ps.bind_texture("hairPointBuffer", cache->proc_point_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[subdiv].strands_res);
ob_ps.bind_ssbo("posTime", cache->final[subdiv].proc_buf);
const int max_strands_per_call = GPU_max_work_group_count(0);
int strands_start = 0;
while (strands_start < strands_len) {
int batch_strands_len = std::min(strands_len - 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[subdiv].strands_res, 1));
strands_start += batch_strands_len;
}
}
return cache;
}
blender::gpu::VertBuf *hair_pos_buffer_get(Scene *scene,
Object *object,
ParticleSystem *psys,
ModifierData *md)
{
int subdiv = scene->r.hair_subdiv;
int thickness_res = (scene->r.hair_type == SCE_HAIR_SHAPE_STRAND) ? 1 : 2;
/* TODO(fclem): Remove Global access. */
CurvesModule &module = *drw_get().data->curves_module;
ParticleHairCache *cache = hair_particle_cache_get(
object, psys, md, nullptr, subdiv, thickness_res);
drw_particle_update_ptcache(object, psys);
ParticleDrawSource source = drw_particle_get_hair_source(
object, psys, md, nullptr, scene->r.hair_subdiv);
return cache->final[subdiv].proc_buf;
CurvesEvalCache &cache = hair_particle_get_eval_cache(source);
cache.ensure_positions(module, source);
return cache.evaluated_pos_rad_buf.get();
}
template<typename PassT>
@@ -173,66 +62,41 @@ blender::gpu::Batch *hair_sub_pass_setup_implementation(PassT &sub_ps,
/** NOTE: This still relies on the old DRW_hair implementation. */
Object *object = ob_ref.object;
int subdiv = scene->r.hair_subdiv;
int thickness_res = (scene->r.hair_type == SCE_HAIR_SHAPE_STRAND) ? 1 : 2;
ParticleHairCache *hair_cache = drw_hair_particle_cache_get(
object, psys, md, gpu_material, subdiv, thickness_res);
drw_particle_update_ptcache(object, psys);
ParticleDrawSource source = drw_particle_get_hair_source(
object, psys, md, nullptr, scene->r.hair_subdiv);
CurvesEvalCache &cache = hair_particle_get_eval_cache(source);
const int face_per_segment = (scene->r.hair_type == SCE_HAIR_SHAPE_STRAND) ? 0 :
(scene->r.hair_type == SCE_HAIR_SHAPE_CYLINDER) ? 3 :
1;
if (source.evaluated_points_num() == 0) {
/* Nothing to draw. Just return an empty drawcall that will be skipped. */
return cache.batch_get(0, 0, face_per_segment, false);
}
/* TODO(fclem): Remove Global access. */
CurvesModule &module = *drw_get().data->curves_module;
/* 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);
}
cache.ensure_positions(module, source);
cache.ensure_attributes(module, source, gpu_material);
gpu::VertBufPtr &indirection_buf = cache.indirection_buf_get(module, source, face_per_segment);
{
ParticleSystemModifierData *psmd = (ParticleSystemModifierData *)source.md;
Mesh &mesh = *psmd->mesh_final;
const StringRef active_uv = CustomData_get_active_layer_name(&mesh.corner_data,
CD_PROP_FLOAT2);
curves_bind_resources(
sub_ps, module, cache, face_per_segment, gpu_material, indirection_buf, active_uv);
}
/* TODO: optimize this. Only bind the ones #GPUMaterial needs. */
for (int i : IndexRange(hair_cache->num_uv_layers)) {
for (int n = 0; n < MAX_LAYER_NAME_CT && hair_cache->uv_layer_names[i][n][0] != '\0'; n++) {
sub_ps.bind_texture(hair_cache->uv_layer_names[i][n], hair_cache->uv_tex[i]);
}
}
for (int i : IndexRange(hair_cache->num_col_layers)) {
for (int n = 0; n < MAX_LAYER_NAME_CT && hair_cache->col_layer_names[i][n][0] != '\0'; n++) {
sub_ps.bind_texture(hair_cache->col_layer_names[i][n], hair_cache->col_tex[i]);
}
}
float4x4 dupli_mat = ob_ref.particles_matrix();
/* Get hair shape parameters. */
ParticleSettings *part = psys->part;
float hair_rad_shape = part->shape;
float hair_rad_root = part->rad_root * part->rad_scale * 0.5f;
float hair_rad_tip = part->rad_tip * part->rad_scale * 0.5f;
bool hair_close_tip = (part->shape_flag & PART_SHAPE_CLOSE_TIP) != 0;
sub_ps.bind_texture("hairPointBuffer", hair_cache->final[subdiv].proc_buf);
if (hair_cache->proc_length_buf) {
sub_ps.bind_texture("l", hair_cache->proc_length_buf);
}
sub_ps.bind_ubo("drw_curves", module.ubo_pool.dummy_get());
sub_ps.push_constant("hairStrandsRes", &hair_cache->final[subdiv].strands_res, 1);
sub_ps.push_constant("hairThicknessRes", thickness_res);
sub_ps.push_constant("hairRadShape", hair_rad_shape);
sub_ps.push_constant("hairDupliMatrix", dupli_mat);
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 hair_cache->final[subdiv].proc_hairs[thickness_res - 1];
return cache.batch_get(
source.evaluated_points_num(), source.curves_num(), face_per_segment, false);
}
blender::gpu::Batch *hair_sub_pass_setup(PassMain::Sub &sub_ps,

View File

@@ -8,10 +8,17 @@
#pragma once
#include "draw_curves_private.hh"
#include "draw_pass.hh"
namespace blender::bke {
class CurvesGeometry;
}
namespace blender::draw {
struct CurvesEvalCache;
class CurveRefinePass : public PassSimple {
public:
CurveRefinePass(const char *name) : PassSimple(name){};
@@ -42,6 +49,9 @@ struct CurvesUniformBufPool {
struct CurvesModule {
CurvesUniformBufPool ubo_pool;
CurveRefinePass refine = {"CurvesEvalPass"};
/* Contains all transient input buffers contained inside `refine`.
* Cleared after update. */
Vector<gpu::VertBufPtr> transient_buffers;
gpu::VertBuf *dummy_vbo = drw_curves_ensure_dummy_vbo();
@@ -58,8 +68,62 @@ struct CurvesModule {
refine.state_set(DRW_STATE_NO_DRAW);
}
/* Record evaluation inside `refine`.
* Output will be ready once `refine` pass has been submitted. */
void evaluate_curve_attribute(bool has_catmull,
bool has_bezier,
bool has_poly,
bool has_nurbs,
bool has_cyclic,
int curve_count,
CurvesEvalCache &cache,
CurvesEvalShader shader_type,
gpu::VertBufPtr input_buf,
gpu::VertBufPtr &output_buf,
/* For radius during position evaluation. */
gpu::VertBuf *input2_buf = nullptr,
/* For baking a transform during position evaluation. */
float4x4 transform = float4x4::identity());
void evaluate_positions(bool has_catmull,
bool has_bezier,
bool has_poly,
bool has_nurbs,
bool has_cyclic,
int curve_count,
CurvesEvalCache &cache,
gpu::VertBufPtr input_pos_buf,
gpu::VertBufPtr input_rad_buf,
gpu::VertBufPtr &output_pos_buf,
float4x4 transform = float4x4::identity())
{
evaluate_curve_attribute(has_catmull,
has_bezier,
has_poly,
has_nurbs,
has_cyclic,
curve_count,
cache,
CURVES_EVAL_POSITION,
std::move(input_pos_buf),
output_pos_buf,
/* Transfer ownership through optional argument. */
input_rad_buf.release(),
transform);
}
void evaluate_curve_length_intercept(bool has_cyclic, int curve_count, CurvesEvalCache &cache);
gpu::VertBufPtr evaluate_topology_indirection(const int curve_count,
const int point_count,
CurvesEvalCache &cache,
bool is_ribbon,
bool has_cyclic);
private:
gpu::VertBuf *drw_curves_ensure_dummy_vbo();
void dispatch(int curve_count, PassSimple::Sub &pass);
};
} // namespace blender::draw
@@ -79,61 +143,15 @@ struct Object;
struct ParticleHairCache;
struct ParticleSystem;
struct ParticleHairFinalCache {
/* Output of the subdivision stage: vertex buff sized to subdiv level. */
blender::gpu::VertBuf *proc_buf;
/* Just contains a huge index buffer used to draw the final hair. */
blender::gpu::Batch *proc_hairs[MAX_THICKRES];
int strands_res; /* points per hair, at least 2 */
};
struct ParticleHairCache {
blender::gpu::VertBuf *pos;
blender::gpu::IndexBuf *indices;
blender::gpu::Batch *hairs;
/* Hair Procedural display: Interpolation is done on the GPU. */
blender::gpu::VertBuf *proc_point_buf; /* Input control points */
/** Information of control points strands (segment count and base index) */
blender::gpu::VertBuf *proc_strand_buf;
/* Hair Length */
blender::gpu::VertBuf *proc_length_buf;
blender::gpu::VertBuf *proc_strand_seg_buf;
blender::gpu::VertBuf *proc_uv_buf[MAX_MTFACE];
blender::gpu::Texture *uv_tex[MAX_MTFACE];
char uv_layer_names[MAX_MTFACE][MAX_LAYER_NAME_CT][MAX_LAYER_NAME_LEN];
blender::gpu::VertBuf **proc_col_buf;
blender::gpu::Texture **col_tex;
char (*col_layer_names)[MAX_LAYER_NAME_CT][MAX_LAYER_NAME_LEN];
int num_uv_layers;
int num_col_layers;
ParticleHairFinalCache final[MAX_HAIR_SUBDIV];
int strands_len;
int elems_len;
int point_len;
};
namespace blender::draw {
/**
* Ensure all textures and buffers needed for GPU accelerated drawing.
*/
bool particles_ensure_procedural_data(Object *object,
ParticleSystem *psys,
ModifierData *md,
ParticleHairCache **r_hair_cache,
GPUMaterial *gpu_material,
int subdiv,
int thickness_res);
void drw_particle_update_ptcache(Object *object_eval, ParticleSystem *psys);
ParticleDrawSource drw_particle_get_hair_source(Object *object,
ParticleSystem *psys,
ModifierData *md,
PTCacheEdit *edit,
int additional_subdivision);
CurvesEvalCache &hair_particle_get_eval_cache(ParticleDrawSource &src);
} // namespace blender::draw

View File

@@ -356,10 +356,12 @@ class PassBase {
GPUSamplerState state = sampler_auto);
void bind_texture(const char *name, gpu::VertBuf *buffer);
void bind_texture(const char *name, gpu::VertBuf **buffer);
void bind_texture(const char *name, gpu::VertBufPtr &buffer);
void bind_texture(int slot, gpu::Texture *texture, GPUSamplerState state = sampler_auto);
void bind_texture(int slot, gpu::Texture **texture, GPUSamplerState state = sampler_auto);
void bind_texture(int slot, gpu::VertBuf *buffer);
void bind_texture(int slot, gpu::VertBuf **buffer);
void bind_texture(int slot, gpu::VertBufPtr &buffer);
void bind_ssbo(const char *name, gpu::StorageBuf *buffer);
void bind_ssbo(const char *name, gpu::StorageBuf **buffer);
void bind_ssbo(int slot, gpu::StorageBuf *buffer);
@@ -370,8 +372,10 @@ class PassBase {
void bind_ssbo(int slot, gpu::UniformBuf **buffer);
void bind_ssbo(const char *name, gpu::VertBuf *buffer);
void bind_ssbo(const char *name, gpu::VertBuf **buffer);
void bind_ssbo(const char *name, gpu::VertBufPtr &buffer);
void bind_ssbo(int slot, gpu::VertBuf *buffer);
void bind_ssbo(int slot, gpu::VertBuf **buffer);
void bind_ssbo(int slot, gpu::VertBufPtr &buffer);
void bind_ssbo(const char *name, gpu::IndexBuf *buffer);
void bind_ssbo(const char *name, gpu::IndexBuf **buffer);
void bind_ssbo(int slot, gpu::IndexBuf *buffer);
@@ -893,6 +897,7 @@ inline void PassBase<T>::draw(gpu::Batch *batch,
if (instance_len == 0 || vertex_len == 0) {
return;
}
BLI_assert(batch);
BLI_assert(shader_);
draw_commands_buf_.append_draw(headers_,
commands_,
@@ -1224,6 +1229,12 @@ template<class T> inline void PassBase<T>::bind_ssbo(const char *name, gpu::Vert
this->bind_ssbo(GPU_shader_get_ssbo_binding(shader_, name), buffer);
}
template<class T> inline void PassBase<T>::bind_ssbo(const char *name, gpu::VertBufPtr &buffer)
{
BLI_assert(buffer.get() != nullptr);
this->bind_ssbo(GPU_shader_get_ssbo_binding(shader_, name), buffer.get());
}
template<class T> inline void PassBase<T>::bind_ssbo(const char *name, gpu::IndexBuf *buffer)
{
BLI_assert(buffer != nullptr);
@@ -1263,6 +1274,12 @@ template<class T> inline void PassBase<T>::bind_texture(const char *name, gpu::V
this->bind_texture(GPU_shader_get_sampler_binding(shader_, name), buffer);
}
template<class T> inline void PassBase<T>::bind_texture(const char *name, gpu::VertBufPtr &buffer)
{
BLI_assert(buffer.get() != nullptr);
this->bind_texture(GPU_shader_get_sampler_binding(shader_, name), buffer.get());
}
template<class T> inline void PassBase<T>::bind_image(const char *name, gpu::Texture *image)
{
BLI_assert(image != nullptr);
@@ -1303,6 +1320,13 @@ template<class T> inline void PassBase<T>::bind_ssbo(int slot, gpu::VertBuf **bu
slot, buffer, ResourceBind::Type::VertexAsStorageBuf};
}
template<class T> inline void PassBase<T>::bind_ssbo(int slot, gpu::VertBufPtr &buffer)
{
BLI_assert(buffer.get() != nullptr);
create_command(Type::ResourceBind).resource_bind = {
slot, buffer.get(), ResourceBind::Type::VertexAsStorageBuf};
}
template<class T> inline void PassBase<T>::bind_ssbo(int slot, gpu::IndexBuf *buffer)
{
BLI_assert(buffer != nullptr);
@@ -1342,6 +1366,12 @@ template<class T> inline void PassBase<T>::bind_texture(int slot, gpu::VertBuf *
create_command(Type::ResourceBind).resource_bind = {slot, buffer};
}
template<class T> inline void PassBase<T>::bind_texture(int slot, gpu::VertBufPtr &buffer)
{
BLI_assert(buffer.get() != nullptr);
create_command(Type::ResourceBind).resource_bind = {slot, buffer.get()};
}
template<class T> inline void PassBase<T>::bind_image(int slot, gpu::Texture *image)
{
BLI_assert(image != nullptr);

View File

@@ -98,7 +98,13 @@ class ShaderCache {
get_static_cache().release();
}
gpu::StaticShader hair_refine = {"draw_hair_refine_compute"};
gpu::StaticShader curves_topology = {"draw_curves_topology"};
gpu::StaticShader curves_evaluate_position = {"draw_curves_interpolate_position"};
gpu::StaticShader curves_evaluate_float4 = {"draw_curves_interpolate_float4_attribute"};
gpu::StaticShader curves_evaluate_float3 = {"draw_curves_interpolate_float3_attribute"};
gpu::StaticShader curves_evaluate_float2 = {"draw_curves_interpolate_float2_attribute"};
gpu::StaticShader curves_evaluate_float = {"draw_curves_interpolate_float_attribute"};
gpu::StaticShader curves_evaluate_length_intercept = {"draw_curves_evaluate_length_intercept"};
gpu::StaticShader debug_draw_display = {"draw_debug_draw_display"};
gpu::StaticShader draw_visibility_compute = {"draw_visibility_compute"};
gpu::StaticShader draw_view_finalize = {"draw_view_finalize"};
@@ -149,15 +155,29 @@ class ShaderCache {
using namespace blender::draw::Shader;
blender::gpu::Shader *DRW_shader_hair_refine_get(ParticleRefineShader /*refinement*/)
blender::gpu::Shader *DRW_shader_curves_topology_get()
{
return ShaderCache::get().hair_refine.get();
return ShaderCache::get().curves_topology.get();
}
blender::gpu::Shader *DRW_shader_curves_refine_get(blender::draw::CurvesEvalShader /*type*/)
blender::gpu::Shader *DRW_shader_curves_refine_get(blender::draw::CurvesEvalShader type)
{
/* TODO: Implement curves evaluation types (Bezier and Catmull Rom). */
return ShaderCache::get().hair_refine.get();
switch (type) {
case blender::draw::CURVES_EVAL_POSITION:
return ShaderCache::get().curves_evaluate_position.get();
case blender::draw::CURVES_EVAL_FLOAT4:
return ShaderCache::get().curves_evaluate_float4.get();
case blender::draw::CURVES_EVAL_FLOAT3:
return ShaderCache::get().curves_evaluate_float3.get();
case blender::draw::CURVES_EVAL_FLOAT2:
return ShaderCache::get().curves_evaluate_float2.get();
case blender::draw::CURVES_EVAL_FLOAT:
return ShaderCache::get().curves_evaluate_float.get();
case blender::draw::CURVES_EVAL_LENGTH_INTERCEPT:
return ShaderCache::get().curves_evaluate_length_intercept.get();
}
BLI_assert_unreachable();
return nullptr;
}
blender::gpu::Shader *DRW_shader_debug_draw_display_get()

View File

@@ -17,8 +17,7 @@ class Shader;
/* draw_shader.cc */
blender::gpu::Shader *DRW_shader_hair_refine_get(ParticleRefineShader refinement);
blender::gpu::Shader *DRW_shader_curves_topology_get();
blender::gpu::Shader *DRW_shader_curves_refine_get(blender::draw::CurvesEvalShader type);
blender::gpu::Shader *DRW_shader_debug_draw_display_get();

View File

@@ -249,10 +249,18 @@ struct VolumeInfos {
BLI_STATIC_ASSERT_ALIGN(VolumeInfos, 16)
struct CurvesInfos {
/* TODO(fclem): Make it a single uint. */
/** Per attribute scope, follows loading order.
* \note uint as bool in GLSL is 4 bytes.
* \note GLSL pad arrays of scalar to 16 bytes (std140). */
uint4 is_point_attribute[DRW_ATTRIBUTE_PER_CURVES_MAX];
/* Number of vertex in a segment (including restart vertex for cylinder). */
uint vertex_per_segment;
/* Edge count for the visible half cylinder. Equal to face count + 1. */
uint half_cylinder_face_count;
uint _pad0;
uint _pad1;
};
BLI_STATIC_ASSERT_ALIGN(CurvesInfos, 16)

View File

@@ -17,6 +17,8 @@ set(INC_GLSL
set(SRC_GLSL_VERT
draw_debug_draw_display_vert.glsl
draw_curves_test.glsl
)
set(SRC_GLSL_FRAG
@@ -24,13 +26,16 @@ set(SRC_GLSL_FRAG
)
set(SRC_GLSL_COMP
draw_hair_refine_comp.glsl
draw_command_generate_comp.glsl
# Failed because of mixed definition of ObjectInfos
# draw_resource_finalize_comp.glsl
draw_view_finalize_comp.glsl
draw_visibility_comp.glsl
draw_curves_topology_comp.glsl
draw_curves_interpolation_comp.glsl
draw_curves_length_intercept_comp.glsl
subdiv_ibo_lines_comp.glsl
subdiv_ibo_tris_comp.glsl
subdiv_vbo_edge_fac_comp.glsl

View File

@@ -0,0 +1,133 @@
/* SPDX-FileCopyrightText: 2025 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup draw
*/
#ifdef GPU_SHADER
# pragma once
# include "gpu_glsl_cpp_stubs.hh"
# include "draw_attribute_shader_shared.hh"
# include "draw_object_infos_info.hh"
# define DRW_HAIR_INFO
#endif
#include "draw_curves_defines.hh"
#include "gpu_shader_create_info.hh"
GPU_SHADER_CREATE_INFO(draw_curves_topology)
LOCAL_GROUP_SIZE(CURVES_PER_THREADGROUP)
/* Offsets giving the start and end of the curve. */
STORAGE_BUF(0, read, int, evaluated_offsets_buf[])
STORAGE_BUF(1, read, uint, curves_cyclic_buf[]) /* Actually bool (1 byte). */
STORAGE_BUF(2, write, int, indirection_buf[])
PUSH_CONSTANT(int, curves_start)
PUSH_CONSTANT(int, curves_count)
PUSH_CONSTANT(bool, is_ribbon_topology)
PUSH_CONSTANT(bool, use_cyclic)
COMPUTE_SOURCE("draw_curves_topology_comp.glsl")
DO_STATIC_COMPILATION()
GPU_SHADER_CREATE_END()
GPU_SHADER_CREATE_INFO(draw_curves_data)
LOCAL_GROUP_SIZE(CURVES_PER_THREADGROUP)
/* Offsets giving the start and end of the curve. */
STORAGE_BUF(EVALUATED_POINT_SLOT, read, int, evaluated_points_by_curve_buf[])
STORAGE_BUF(POINTS_BY_CURVES_SLOT, read, int, points_by_curve_buf[])
STORAGE_BUF(CURVE_RESOLUTION_SLOT, read, uint, curves_resolution_buf[])
STORAGE_BUF(CURVE_TYPE_SLOT, read, uint, curves_type_buf[]) /* Actually int8_t. */
STORAGE_BUF(CURVE_CYCLIC_SLOT, read, uint, curves_cyclic_buf[]) /* Actually bool (1 byte). */
/* Bezier handles (if needed). */
STORAGE_BUF(HANDLES_POS_LEFT_SLOT, read, float, handles_positions_left_buf[])
STORAGE_BUF(HANDLES_POS_RIGHT_SLOT, read, float, handles_positions_right_buf[])
STORAGE_BUF(BEZIER_OFFSETS_SLOT, read, int, bezier_offsets_buf[])
/* Nurbs (alias of other buffers). */
// STORAGE_BUF(CURVES_ORDER_SLOT, read, uint, curves_order_buf[]) /* Actually int8_t. */
// STORAGE_BUF(BASIS_CACHE_SLOT, read, float, basis_cache_buf[])
// STORAGE_BUF(CONTROL_WEIGHTS_SLOT, read, float, control_weights_buf[])
// STORAGE_BUF(BASIS_CACHE_OFFSET_SLOT, read, int, basis_cache_offset_buf[])
PUSH_CONSTANT(int, curves_start)
PUSH_CONSTANT(int, curves_count)
PUSH_CONSTANT(bool, use_point_weight)
PUSH_CONSTANT(bool, use_cyclic)
/** IMPORTANT: For very dumb reasons, on GL the default specialization is compiled and used for
* creating the shader interface. If this happens to optimize out some push_constants that are
* valid in other specialization, we will never be able to set them. So choose the specialization
* that uses all push_constants. */
SPECIALIZATION_CONSTANT(int, evaluated_type, 3) /* CURVE_TYPE_NURBS */
TYPEDEF_SOURCE("draw_attribute_shader_shared.hh")
COMPUTE_SOURCE("draw_curves_interpolation_comp.glsl")
GPU_SHADER_CREATE_END()
GPU_SHADER_CREATE_INFO(draw_curves_interpolate_position)
ADDITIONAL_INFO(draw_curves_data)
/* Attributes. */
STORAGE_BUF(POINT_POSITIONS_SLOT, read, float, positions_buf[])
STORAGE_BUF(POINT_RADII_SLOT, read, float, radii_buf[])
/* Outputs. */
STORAGE_BUF(EVALUATED_POS_RAD_SLOT, read_write, float4, evaluated_positions_radii_buf[])
PUSH_CONSTANT(float4x4, transform)
COMPUTE_FUNCTION("evaluate_position_radius")
DO_STATIC_COMPILATION()
GPU_SHADER_CREATE_END()
GPU_SHADER_CREATE_INFO(draw_curves_interpolate_float4_attribute)
ADDITIONAL_INFO(draw_curves_data)
STORAGE_BUF(POINT_ATTR_SLOT, read, StoredFloat4, attribute_float4_buf[])
STORAGE_BUF(EVALUATED_ATTR_SLOT, read_write, StoredFloat4, evaluated_float4_buf[])
COMPUTE_FUNCTION("evaluate_attribute_float4")
DO_STATIC_COMPILATION()
GPU_SHADER_CREATE_END()
GPU_SHADER_CREATE_INFO(draw_curves_interpolate_float3_attribute)
ADDITIONAL_INFO(draw_curves_data)
STORAGE_BUF(POINT_ATTR_SLOT, read, StoredFloat3, attribute_float3_buf[])
STORAGE_BUF(EVALUATED_ATTR_SLOT, read_write, StoredFloat3, evaluated_float3_buf[])
COMPUTE_FUNCTION("evaluate_attribute_float3")
DO_STATIC_COMPILATION()
GPU_SHADER_CREATE_END()
GPU_SHADER_CREATE_INFO(draw_curves_interpolate_float2_attribute)
ADDITIONAL_INFO(draw_curves_data)
STORAGE_BUF(POINT_ATTR_SLOT, read, StoredFloat2, attribute_float2_buf[])
STORAGE_BUF(EVALUATED_ATTR_SLOT, read_write, StoredFloat2, evaluated_float2_buf[])
COMPUTE_FUNCTION("evaluate_attribute_float2")
DO_STATIC_COMPILATION()
GPU_SHADER_CREATE_END()
GPU_SHADER_CREATE_INFO(draw_curves_interpolate_float_attribute)
ADDITIONAL_INFO(draw_curves_data)
STORAGE_BUF(POINT_ATTR_SLOT, read, StoredFloat, attribute_float_buf[])
STORAGE_BUF(EVALUATED_ATTR_SLOT, read_write, StoredFloat, evaluated_float_buf[])
COMPUTE_FUNCTION("evaluate_attribute_float")
DO_STATIC_COMPILATION()
GPU_SHADER_CREATE_END()
GPU_SHADER_CREATE_INFO(draw_curves_evaluate_length_intercept)
LOCAL_GROUP_SIZE(CURVES_PER_THREADGROUP)
STORAGE_BUF(EVALUATED_POINT_SLOT, read, int, evaluated_points_by_curve_buf[])
STORAGE_BUF(EVALUATED_POS_RAD_SLOT, read, float4, evaluated_positions_radii_buf[])
STORAGE_BUF(EVALUATED_TIME_SLOT, read_write, float, evaluated_time_buf[])
STORAGE_BUF(CURVES_LENGTH_SLOT, write, float, curves_length_buf[])
PUSH_CONSTANT(int, curves_start)
PUSH_CONSTANT(int, curves_count)
PUSH_CONSTANT(bool, use_cyclic)
COMPUTE_FUNCTION("evaluate_length_intercept")
COMPUTE_SOURCE("draw_curves_length_intercept_comp.glsl")
DO_STATIC_COMPILATION()
GPU_SHADER_CREATE_END()
GPU_SHADER_CREATE_INFO(draw_curves_test)
STORAGE_BUF(0, write, float, result_pos_buf[])
STORAGE_BUF(1, write, int4, result_indices_buf[])
VERTEX_SOURCE("draw_curves_test.glsl")
FRAGMENT_SOURCE("draw_curves_test.glsl")
ADDITIONAL_INFO(draw_curves_infos)
ADDITIONAL_INFO(draw_curves)
DO_STATIC_COMPILATION()
GPU_SHADER_CREATE_END()

View File

@@ -0,0 +1,575 @@
/* SPDX-FileCopyrightText: 2025 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/**
* GPU generated interpolated position and radius. Updated on attribute change.
* One thread processes one curve.
*
* Equivalent of `CurvesGeometry::evaluated_positions()`.
*/
#include "draw_curves_info.hh"
#include "gpu_shader_attribute_load_lib.glsl"
#include "gpu_shader_math_base_lib.glsl"
#include "gpu_shader_math_matrix_lib.glsl"
#include "gpu_shader_offset_indices_lib.glsl"
/* We workaround the lack of function pointers by using different type to overload the attribute
* implementation. */
struct InterpPosition {
/* Position, Radius. */
float4 data;
METAL_CONSTRUCTOR_1(InterpPosition, float4, data)
static InterpPosition zero()
{
return InterpPosition(float4(0));
}
};
/** Input Load. */
/* Template this function to be able to call it with only no extra argument. */
template<typename T> T input_load(int point_index)
{
return T(0);
}
template<> InterpPosition input_load<InterpPosition>(int point_index)
{
const auto &transform = push_constant_get(draw_curves_interpolate_position, transform);
const auto &positions = buffer_get(draw_curves_interpolate_position, positions_buf);
InterpPosition interp;
interp.data.xyz = gpu_attr_load_float3(positions, int2(3, 0), point_index);
interp.data.w = buffer_get(draw_curves_interpolate_position, radii_buf)[point_index];
/* Bake object transform for legacy hair particle. */
interp.data.xyz = transform_point(transform, interp.data.xyz);
return interp;
}
template<> float4 input_load<float4>(int point_index)
{
StoredFloat4 data = buffer_get(draw_curves_interpolate_float4_attribute,
attribute_float4_buf)[point_index];
return load_data(data);
}
template<> float3 input_load<float3>(int point_index)
{
StoredFloat3 data = buffer_get(draw_curves_interpolate_float3_attribute,
attribute_float3_buf)[point_index];
return load_data(data);
}
template<> float2 input_load<float2>(int point_index)
{
StoredFloat2 data = buffer_get(draw_curves_interpolate_float2_attribute,
attribute_float2_buf)[point_index];
return load_data(data);
}
template<> float input_load<float>(int point_index)
{
StoredFloat data = buffer_get(draw_curves_interpolate_float_attribute,
attribute_float_buf)[point_index];
return load_data(data);
}
/** Output Load. */
/* Template this function to be able to call it with only no extra argument. */
template<typename T> T output_load(int evaluated_point_index)
{
return T(0);
}
template<> InterpPosition output_load<InterpPosition>(int evaluated_point_index)
{
InterpPosition data;
data.data = buffer_get(draw_curves_interpolate_position,
evaluated_positions_radii_buf)[evaluated_point_index];
return data;
}
template<> float4 output_load<float4>(int evaluated_point_index)
{
StoredFloat4 data = buffer_get(draw_curves_interpolate_float4_attribute,
evaluated_float4_buf)[evaluated_point_index];
return load_data(data);
}
template<> float3 output_load<float3>(int evaluated_point_index)
{
StoredFloat3 data = buffer_get(draw_curves_interpolate_float3_attribute,
evaluated_float3_buf)[evaluated_point_index];
return load_data(data);
}
template<> float2 output_load<float2>(int evaluated_point_index)
{
StoredFloat2 data = buffer_get(draw_curves_interpolate_float2_attribute,
evaluated_float2_buf)[evaluated_point_index];
return load_data(data);
}
template<> float output_load<float>(int evaluated_point_index)
{
StoredFloat data = buffer_get(draw_curves_interpolate_float_attribute,
evaluated_float_buf)[evaluated_point_index];
return load_data(data);
}
/** Output Write. */
void output_write(int evaluated_point_index, InterpPosition interp)
{
/* Clamp radius to 0 to avoid negative radius due to interpolation. */
interp.data.w = max(0.0, interp.data.w);
buffer_get(draw_curves_interpolate_position,
evaluated_positions_radii_buf)[evaluated_point_index] = interp.data;
}
void output_write(int evaluated_point_index, const float4 interp)
{
buffer_get(draw_curves_interpolate_float4_attribute,
evaluated_float4_buf)[evaluated_point_index] = as_data(interp);
}
void output_write(int evaluated_point_index, const float3 interp)
{
buffer_get(draw_curves_interpolate_float3_attribute,
evaluated_float3_buf)[evaluated_point_index] = as_data(interp);
}
void output_write(int evaluated_point_index, const float2 interp)
{
buffer_get(draw_curves_interpolate_float2_attribute,
evaluated_float2_buf)[evaluated_point_index] = as_data(interp);
}
void output_write(int evaluated_point_index, const float interp)
{
buffer_get(draw_curves_interpolate_float_attribute,
evaluated_float_buf)[evaluated_point_index] = as_data(interp);
}
/** Output Weighted Add. */
void output_weighted_add(int evaluated_point_index, float w, const InterpPosition src)
{
buffer_get(draw_curves_interpolate_position,
evaluated_positions_radii_buf)[evaluated_point_index] += src.data * w;
}
template<typename InterpType>
void output_weighted_add(int evaluated_point_index, float w, const InterpType src)
{
InterpType dst = output_load<InterpType>(evaluated_point_index);
dst += src * w;
output_write(evaluated_point_index, dst);
}
template void output_weighted_add<float4>(int, float, float4);
template void output_weighted_add<float3>(int, float, float3);
template void output_weighted_add<float2>(int, float, float2);
template void output_weighted_add<float>(int, float, float);
/** Output Mul. */
template<typename InterpType> void output_mul(int evaluated_point_index, float w)
{
InterpType dst = output_load<InterpType>(evaluated_point_index);
dst *= w;
output_write(evaluated_point_index, dst);
}
template void output_mul<float4>(int, float);
template void output_mul<float3>(int, float);
template void output_mul<float2>(int, float);
template void output_mul<float>(int, float);
template<> void output_mul<InterpPosition>(int evaluated_point_index, float w)
{
buffer_get(draw_curves_interpolate_position,
evaluated_positions_radii_buf)[evaluated_point_index] *= w;
}
/** Output Set To Zero. */
template<typename InterpType> void output_set_zero(int evaluated_point_index)
{
output_write(evaluated_point_index, InterpType(0.0f));
}
template void output_set_zero<float4>(int);
template void output_set_zero<float3>(int);
template void output_set_zero<float2>(int);
template void output_set_zero<float>(int);
template<> void output_set_zero<InterpPosition>(int evaluated_point_index)
{
buffer_get(draw_curves_interpolate_position,
evaluated_positions_radii_buf)[evaluated_point_index] = float4(0.0);
}
/** Mix 4. */
InterpPosition mix4(
InterpPosition v0, InterpPosition v1, InterpPosition v2, InterpPosition v3, float4 w)
{
v0.data = v0.data * w.x + v1.data * w.y + v2.data * w.z + v3.data * w.w;
return v0;
}
template<typename DataT> DataT mix4(DataT v0, DataT v1, DataT v2, DataT v3, float4 w)
{
v0 = v0 * w.x + v1 * w.y + v2 * w.z + v3 * w.w;
return v0;
}
template float4 mix4<float4>(float4, float4, float4, float4, float4);
template float3 mix4<float3>(float3, float3, float3, float3, float4);
template float2 mix4<float2>(float2, float2, float2, float2, float4);
template float mix4<float>(float, float, float, float, float4);
/** Utilities. */
bool curve_cyclic_get(int curve_index)
{
const auto &use_cyclic = push_constant_get(draw_curves_data, use_cyclic);
const auto &curves_cyclic_buf = buffer_get(draw_curves_data, curves_cyclic_buf);
if (use_cyclic) {
return gpu_attr_load_bool(curves_cyclic_buf, curve_index);
}
return false;
}
namespace catmull_rom {
float4 calculate_basis(const float parameter)
{
/* Adapted from Cycles #catmull_rom_basis_eval function. */
const float t = parameter;
const float s = 1.0f - parameter;
return 0.5f * float4(-t * s * s,
2.0f + t * t * (3.0f * t - 5.0f),
2.0f + s * s * (3.0f * s - 5.0f),
-s * t * t);
}
int4 get_points(uint point_id, IndexRange points, const bool cyclic)
{
int4 point_ids = int(point_id) + int4(-1, +0, +1, +2);
if (cyclic) {
/* Wrap around. Note the offset by size to avoid modulo with negative values. */
point_ids = ((point_ids + points.size()) % points.size());
}
else {
point_ids = clamp(point_ids, int4(0), int4(points.size() - 1));
}
return points.start() + point_ids;
}
template<typename InterpType>
void evaluate_curve(const IndexRange points,
const IndexRange evaluated_points,
const int curve_index)
{
const auto &curves_resolution_buf = buffer_get(draw_curves_data, curves_resolution_buf);
const uint curve_resolution = curves_resolution_buf[curve_index];
const bool is_curve_cyclic = curve_cyclic_get(curve_index);
for (uint i = 0; i < evaluated_points.size(); i++) {
const int evaluated_point_id = evaluated_points.start() + int(i);
const uint point_id = i / curve_resolution;
const float parameter = float(i % curve_resolution) / float(curve_resolution);
const float4 weights = calculate_basis(parameter);
const int4 point_ids = get_points(point_id, points, is_curve_cyclic);
InterpType p0 = input_load<InterpType>(point_ids.x);
InterpType p1 = input_load<InterpType>(point_ids.y);
InterpType p2 = input_load<InterpType>(point_ids.z);
InterpType p3 = input_load<InterpType>(point_ids.w);
InterpType result = mix4(p0, p1, p2, p3, weights);
output_write(evaluated_point_id, result);
}
}
template void evaluate_curve<InterpPosition>(IndexRange, IndexRange, int);
template void evaluate_curve<float>(IndexRange, IndexRange, int);
template void evaluate_curve<float2>(IndexRange, IndexRange, int);
template void evaluate_curve<float3>(IndexRange, IndexRange, int);
template void evaluate_curve<float4>(IndexRange, IndexRange, int);
} // namespace catmull_rom
namespace bezier {
template<typename InterpType> void evaluate_segment(const int2 points, const IndexRange result)
{
InterpType p0 = input_load<InterpType>(points.x);
InterpType p1 = input_load<InterpType>(points.y);
const float step = 1.0f / float(result.size());
for (int i = 0; i < result.size(); i++) {
output_write(result.start() + i, mix(p0, p1, float(i) * step));
}
}
template<> void evaluate_segment<InterpPosition>(const int2 points, const IndexRange result)
{
const auto &handles_right = buffer_get(draw_curves_interpolate_position,
handles_positions_right_buf);
const auto &handles_left = buffer_get(draw_curves_interpolate_position,
handles_positions_left_buf);
InterpPosition p0 = input_load<InterpPosition>(points.x);
InterpPosition p1 = input_load<InterpPosition>(points.y);
const float3 point_0 = p0.data.xyz;
const float3 point_1 = gpu_attr_load_float3(handles_right, int2(3, 0), points.x);
const float3 point_2 = gpu_attr_load_float3(handles_left, int2(3, 0), points.y);
const float3 point_3 = p1.data.xyz;
const float rad_0 = p0.data.w;
const float rad_1 = p1.data.w;
assert(result.size > 0);
const float inv_len = 1.0f / float(result.size());
const float inv_len_squared = inv_len * inv_len;
const float inv_len_cubed = inv_len_squared * inv_len;
const float3 rt1 = 3.0f * (point_1 - point_0) * inv_len;
const float3 rt2 = 3.0f * (point_0 - 2.0f * point_1 + point_2) * inv_len_squared;
const float3 rt3 = (point_3 - point_0 + 3.0f * (point_1 - point_2)) * inv_len_cubed;
float3 q0 = point_0;
float3 q1 = rt1 + rt2 + rt3;
float3 q2 = 2.0f * rt2 + 6.0f * rt3;
float3 q3 = 6.0f * rt3;
for (int i = 0; i < result.size(); i++) {
float rad = mix(rad_0, rad_1, float(i) * inv_len);
InterpPosition interp;
interp.data = float4(q0, rad);
output_write(result.start() + i, interp);
q0 += q1;
q1 += q2;
q2 += q3;
}
}
template void evaluate_segment<float>(int2, IndexRange);
template void evaluate_segment<float2>(int2, IndexRange);
template void evaluate_segment<float3>(int2, IndexRange);
template void evaluate_segment<float4>(int2, IndexRange);
IndexRange per_curve_point_offsets_range(const IndexRange points, const int curve_index)
{
return IndexRange(curve_index + points.start(), points.size() + 1);
}
int2 get_points(uint point_id, IndexRange points, const bool cyclic)
{
int2 point_ids = int(point_id) + int2(+0, +1);
if (cyclic) {
/* Wrap around. Note the offset by size to avoid modulo with negative values. */
point_ids = ((point_ids + points.size()) % points.size());
}
else {
point_ids = clamp(point_ids, int2(0), int2(points.size() - 1));
}
return points.start() + point_ids;
}
template<typename InterpType>
void evaluate_curve(const IndexRange points,
const IndexRange evaluated_points,
const int curve_index)
{
const auto &bezier_offsets_buf = buffer_get(draw_curves_data, bezier_offsets_buf);
/* Range used for indexing bezier offsets. */
const IndexRange offsets = per_curve_point_offsets_range(points, curve_index);
const bool is_curve_cyclic = curve_cyclic_get(curve_index);
for (int i = 0; i < points.size(); i++) {
/* Bezier curves can have different number of evaluated segment per curve segment. */
const IndexRange segment_range = offset_indices::load_range_from_buffer(bezier_offsets_buf,
offsets.start() + i);
const IndexRange evaluated_segment_range = evaluated_points.slice(segment_range);
const int2 point_ids = get_points(i, points, is_curve_cyclic);
evaluate_segment<InterpType>(point_ids, evaluated_segment_range);
}
if (is_curve_cyclic) {
/* The closing point is not contained inside `bezier_offsets_buf` so we do manual copy. */
output_write(evaluated_points.last(), output_load<InterpType>(evaluated_points.first()));
}
}
template void evaluate_curve<InterpPosition>(IndexRange, IndexRange, int);
template void evaluate_curve<float>(IndexRange, IndexRange, int);
template void evaluate_curve<float2>(IndexRange, IndexRange, int);
template void evaluate_curve<float3>(IndexRange, IndexRange, int);
template void evaluate_curve<float4>(IndexRange, IndexRange, int);
} // namespace bezier
template<typename InterpType>
void copy_curve_data(const IndexRange points,
const IndexRange evaluated_points,
const int curve_index)
{
assert(points.size == evaluated_points.size);
const bool is_curve_cyclic = curve_cyclic_get(curve_index);
for (int i = 0; i < points.size(); i++) {
output_write(evaluated_points.start() + i, input_load<InterpType>(points.start() + i));
}
if (is_curve_cyclic) {
/* The closing point is not contained inside `bezier_offsets_buf` so we do manual copy. */
output_write(evaluated_points.last(), output_load<InterpType>(evaluated_points.first()));
}
}
template void copy_curve_data<InterpPosition>(IndexRange, IndexRange, int);
template void copy_curve_data<float>(IndexRange, IndexRange, int);
template void copy_curve_data<float2>(IndexRange, IndexRange, int);
template void copy_curve_data<float3>(IndexRange, IndexRange, int);
template void copy_curve_data<float4>(IndexRange, IndexRange, int);
namespace nurbs {
template<typename InterpType>
void evaluate_curve(const IndexRange points,
const IndexRange evaluated_points_padded,
const int curve_index)
{
const auto &use_cyclic = push_constant_get(draw_curves_data, use_cyclic);
/* Buffer aliasing to same bind point. We cannot dispatch with different type of curve. */
const auto &curves_order_buf = buffer_get(draw_curves_data, curves_resolution_buf);
const auto &basis_cache_offset_buf = buffer_get(draw_curves_data, bezier_offsets_buf);
const int order = int(gpu_attr_load_uchar(curves_order_buf, curve_index));
const int basis_cache_start = basis_cache_offset_buf[curve_index];
const bool invalid = basis_cache_start < 0;
if (invalid) {
copy_curve_data<InterpType>(points, evaluated_points_padded, curve_index);
return;
}
const auto &use_point_weight = push_constant_get(draw_curves_data, use_point_weight);
const bool is_curve_cyclic = curve_cyclic_get(curve_index);
/* Recover original points range without closing cyclic point. */
const IndexRange evaluated_points = IndexRange(evaluated_points_padded.start(),
evaluated_points_padded.size() - int(use_cyclic));
const int start_indices_range_start = basis_cache_start;
const int weights_range_start = basis_cache_start + evaluated_points.size();
/* Buffer aliasing to same bind point. We cannot dispatch with different type of curve. */
const auto &basis_cache_buf = buffer_get(draw_curves_data, handles_positions_left_buf);
const auto &control_weights_buf = buffer_get(draw_curves_data, handles_positions_right_buf);
for (int i = 0; i < evaluated_points.size(); i++) {
int evaluated_point_index = evaluated_points.start() + i;
/* Equivalent to `attribute_math::DefaultMixer<T> mixer{dst}`. */
output_set_zero<InterpType>(evaluated_point_index);
float total_weight = 0.0f;
const IndexRange point_weights = IndexRange(weights_range_start + i * order, order);
const int start_index = floatBitsToInt(basis_cache_buf[start_indices_range_start + i]);
for (int j = 0; j < point_weights.size(); j++) {
const int point_index = points.start() + (start_index + j) % points.size();
const float point_weight = basis_cache_buf[point_weights.start() + j];
const float control_weight = use_point_weight ? control_weights_buf[point_index] : 1.0f;
const float weight = point_weight * control_weight;
/* Equivalent to `mixer.mix_in()`. */
output_weighted_add(evaluated_point_index, weight, input_load<InterpType>(point_index));
total_weight += weight;
}
/* Equivalent to `mixer.finalize()` */
output_mul<InterpType>(evaluated_point_index, safe_rcp(total_weight));
}
if (is_curve_cyclic) {
/* The closing point is not contained inside the NURBS data structure so we do manual copy. */
output_write(evaluated_points_padded.last(),
output_load<InterpType>(evaluated_points_padded.first()));
}
}
template void evaluate_curve<InterpPosition>(IndexRange, IndexRange, int);
template void evaluate_curve<float>(IndexRange, IndexRange, int);
template void evaluate_curve<float2>(IndexRange, IndexRange, int);
template void evaluate_curve<float3>(IndexRange, IndexRange, int);
template void evaluate_curve<float4>(IndexRange, IndexRange, int);
} // namespace nurbs
template<typename InterpType> void evaluate_curve()
{
const auto &use_cyclic = push_constant_get(draw_curves_data, use_cyclic);
const auto &curves_count = push_constant_get(draw_curves_data, curves_count);
const auto &curves_start = push_constant_get(draw_curves_data, curves_start);
const auto &evaluated_type = push_constant_get(draw_curves_data, evaluated_type);
const auto &curves_type_buf = buffer_get(draw_curves_data, curves_type_buf);
const auto &points_by_curve_buf = buffer_get(draw_curves_data, points_by_curve_buf);
const auto &evaluated_points_by_curve_buf = buffer_get(draw_curves_data,
evaluated_points_by_curve_buf);
/* Only for gl_GlobalInvocationID. To be removed. */
COMPUTE_SHADER_CREATE_INFO(draw_curves_interpolate_position)
if (gl_GlobalInvocationID.x >= uint(curves_count)) {
return;
}
int curve_index = int(gl_GlobalInvocationID.x) + curves_start;
const CurveType curve_type = CurveType(gpu_attr_load_uchar(curves_type_buf, curve_index));
if (curve_type != CurveType(evaluated_type)) {
return;
}
IndexRange points = offset_indices::load_range_from_buffer(points_by_curve_buf, curve_index);
IndexRange evaluated_points = offset_indices::load_range_from_buffer(
evaluated_points_by_curve_buf, curve_index);
if (use_cyclic) {
evaluated_points = IndexRange(evaluated_points.start() + curve_index,
evaluated_points.size() + 1);
}
if (CurveType(evaluated_type) == CURVE_TYPE_CATMULL_ROM) {
catmull_rom::evaluate_curve<InterpType>(points, evaluated_points, curve_index);
}
else if (CurveType(evaluated_type) == CURVE_TYPE_BEZIER) {
bezier::evaluate_curve<InterpType>(points, evaluated_points, curve_index);
}
else if (CurveType(evaluated_type) == CURVE_TYPE_NURBS) {
nurbs::evaluate_curve<InterpType>(points, evaluated_points, curve_index);
}
else if (CurveType(evaluated_type) == CURVE_TYPE_POLY) {
/* Simple copy. */
copy_curve_data<InterpType>(points, evaluated_points, curve_index);
}
}
template void evaluate_curve<InterpPosition>();
template void evaluate_curve<float>();
template void evaluate_curve<float2>();
template void evaluate_curve<float3>();
template void evaluate_curve<float4>();
void evaluate_position_radius()
{
evaluate_curve<InterpPosition>();
}
void evaluate_attribute_float()
{
evaluate_curve<float>();
}
void evaluate_attribute_float2()
{
evaluate_curve<float2>();
}
void evaluate_attribute_float3()
{
evaluate_curve<float3>();
}
void evaluate_attribute_float4()
{
evaluate_curve<float4>();
}

View File

@@ -0,0 +1,58 @@
/* SPDX-FileCopyrightText: 2025 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/**
* GPU computed length and intercept attribute.
* One thread processes one curve.
*/
#include "draw_curves_info.hh"
COMPUTE_SHADER_CREATE_INFO(draw_curves_evaluate_length_intercept)
#include "gpu_shader_attribute_load_lib.glsl"
#include "gpu_shader_math_base_lib.glsl"
#include "gpu_shader_offset_indices_lib.glsl"
/* Run on the evaluated position and compute the intercept time with the curve and the total curve
* length. */
void evaluate_length_and_time(const IndexRange evaluated_points, const int curve_index)
{
const auto &evaluated_positions_radii = buffer_get(draw_curves_evaluate_length_intercept,
evaluated_positions_radii_buf);
auto &evaluated_time = buffer_get(draw_curves_evaluate_length_intercept, evaluated_time_buf);
auto &curves_length = buffer_get(draw_curves_evaluate_length_intercept, curves_length_buf);
float distance_along_curve = 0.0f;
evaluated_time[evaluated_points.first()] = 0.0f;
for (int i = 1; i < evaluated_points.size(); i++) {
int p = evaluated_points.start() + i;
distance_along_curve += distance(evaluated_positions_radii[p].xyz,
evaluated_positions_radii[p - 1].xyz);
evaluated_time[p] = distance_along_curve;
}
for (int i = 1; i < evaluated_points.size(); i++) {
int p = evaluated_points.start() + i;
evaluated_time[p] /= distance_along_curve;
}
curves_length[curve_index] = distance_along_curve;
}
void evaluate_length_intercept()
{
if (gl_GlobalInvocationID.x >= uint(curves_count)) {
return;
}
int curve_index = int(gl_GlobalInvocationID.x) + curves_start;
IndexRange evaluated_points = offset_indices::load_range_from_buffer(
evaluated_points_by_curve_buf, curve_index);
if (use_cyclic) {
evaluated_points = IndexRange(evaluated_points.start() + curve_index,
evaluated_points.size() + 1);
}
evaluate_length_and_time(evaluated_points, curve_index);
}

View File

@@ -13,309 +13,272 @@
* of data the CPU has to precompute and transfer for each update.
*/
#include "gpu_shader_math_matrix_lib.glsl"
/* Avoid including hair functionality for shaders and materials which do not require hair.
* Required to prevent compilation failure for missing shader inputs and uniforms when hair library
* is included via other libraries. These are only specified in the ShaderCreateInfo when needed.
*/
#ifdef HAIR_SHADER
# define COMMON_HAIR_LIB
#ifdef CURVES_SHADER
# ifndef DRW_HAIR_INFO
# error Ensure createInfo includes draw_hair for general use or eevee_legacy_hair_lib for EEVEE.
# error Ensure createInfo includes draw_hair.
# endif
struct CurvePoint {
float3 position;
/* Position along the curve length. O at root, 1 at the tip. */
float time;
namespace curves {
struct Segment {
/* Index of this segment. Used to load indirection buffer. */
uint id;
/* Vertex index inside this segment. */
uint v_idx;
/* Restart triangle strip if true. Only for cylinder topology. */
bool is_end_of_segment;
};
CurvePoint hair_get_point(int point_id)
/* Indirection buffer indexing. */
Segment segment_get(uint vertex_id)
{
SHADER_LIBRARY_CREATE_INFO(draw_hair)
const auto &drw_curves = buffer_get(draw_curves_infos, drw_curves);
float4 data = texelFetch(hairPointBuffer, point_id);
CurvePoint pt;
pt.position = data.xyz;
pt.time = data.w;
const bool is_cylinder = drw_curves.half_cylinder_face_count > 1u;
Segment segment;
segment.id = vertex_id / drw_curves.vertex_per_segment;
segment.v_idx = vertex_id % drw_curves.vertex_per_segment;
segment.is_end_of_segment = is_cylinder && (segment.v_idx == drw_curves.vertex_per_segment - 1);
if (is_cylinder && !segment.is_end_of_segment && (segment.id & 1u) == 0u) {
/* The topology is not actually restarted and the winding order changes (because we skip an odd
* number of triangles). So we have to manually reverse the winding so that is stays
* consistent.
*/
segment.v_idx ^= 1u;
}
return segment;
}
/* Result of interpreting the indirection buffer.
* The indirection buffer maps drawn segments back to their curves and curve segments.
* This is needed for attribute loading. */
struct Indirection {
/* Can be equal to INT_MAX with ribbon draw type. */
int curve_id;
/* Segment ID starting at 0 at curve start. */
int curve_segment;
/* Restart triangle strip if true. Only for ribbon topology. */
bool is_end_of_curve;
/* Does these vertices correspond to the last point of a cyclic curve (duplicate of start). */
bool is_cyclic_point;
};
Indirection indirection_get(Segment segment)
{
const auto &curves_indirection_buf = sampler_get(draw_curves, curves_indirection_buf);
const auto &drw_curves = buffer_get(draw_curves_infos, drw_curves);
Indirection ind;
ind.is_cyclic_point = false;
ind.is_end_of_curve = false;
ind.curve_id = 0;
constexpr int cyclic_endpoint_pivot = INT_MAX / 2;
constexpr int end_of_curve = INT_MAX;
int indirection_value = texelFetch(curves_indirection_buf, int(segment.id)).r;
if (indirection_value == end_of_curve) {
ind.curve_segment = 0;
ind.is_end_of_curve = true;
}
else if (indirection_value >= 0) {
/* This is start of curve. The indirection value is the curve ID. */
ind.curve_segment = 0;
ind.curve_id = indirection_value;
}
else if (indirection_value <= -cyclic_endpoint_pivot) {
/* This is the last segment of a cyclic curve. The indirection value is the offset to the start
* of the curve offsetted by cyclic_endpoint_pivot. */
ind.curve_segment = -indirection_value - cyclic_endpoint_pivot;
ind.is_cyclic_point = true;
}
else {
/* This is a normal segment. The indirection value is the offset to the start of the curve. */
ind.curve_segment = -indirection_value;
}
if (ind.curve_segment != 0) {
ind.curve_id = texelFetch(curves_indirection_buf, int(segment.id) - ind.curve_segment).r;
}
const bool is_cylinder = drw_curves.half_cylinder_face_count > 1u;
if (is_cylinder) {
bool is_end_of_segment = (segment.v_idx & 1u) != 0u;
ind.curve_segment += int(is_end_of_segment);
/* Only the end of the segment is to be considered the cyclic point. */
if (!is_end_of_segment) {
ind.is_cyclic_point = false;
}
}
return ind;
}
int point_id_get(Segment segment, Indirection indirection)
{
const auto &drw_curves = buffer_get(draw_curves_infos, drw_curves);
const bool is_cylinder = drw_curves.half_cylinder_face_count > 1u;
if (is_cylinder) {
return int(segment.id) + indirection.curve_id + int(segment.v_idx & 1u);
}
return int(segment.id) - indirection.curve_id;
}
float azimuthal_offset_get(Segment segment)
{
const auto &drw_curves = buffer_get(draw_curves_infos, drw_curves);
float offset;
const bool is_strand = drw_curves.half_cylinder_face_count == 0u;
const bool is_cylinder = drw_curves.half_cylinder_face_count > 1u;
if (is_strand) {
offset = 0.5f;
}
else if (is_cylinder) {
offset = float(segment.v_idx >> 1) / float(drw_curves.half_cylinder_face_count);
}
else {
offset = float(segment.v_idx);
}
return offset * 2.0f - 1.0f;
}
float4 point_position_and_radius_get(uint point_id)
{
const auto &curves_pos_rad_buf = buffer_get(draw_curves, curves_pos_rad_buf);
return texelFetch(curves_pos_rad_buf, int(point_id));
}
float3 point_position_get(uint point_id)
{
const auto &curves_pos_rad_buf = buffer_get(draw_curves, curves_pos_rad_buf);
return texelFetch(curves_pos_rad_buf, int(point_id)).rgb;
}
struct Point {
/* Position of the evaluated curve point (not the shape / cylinder point). */
float3 P;
/* Tangent vector going from the root to the tip of the curve. */
float3 T;
float radius;
/* Lateral/Azimuthal offset from the center of the curve's width. Range [-1..1]. */
float azimuthal_offset;
int point_id;
int curve_id;
int curve_segment;
};
/* Return data about the curve point. */
Point point_get(uint vertex_id)
{
Segment segment = segment_get(vertex_id);
Indirection indirection = indirection_get(segment);
Point pt;
pt.point_id = point_id_get(segment, indirection);
pt.curve_id = indirection.curve_id;
pt.curve_segment = indirection.curve_segment;
float4 pos_rad = point_position_and_radius_get(pt.point_id);
bool restart_strip = indirection.is_end_of_curve || segment.is_end_of_segment;
pt.P = (restart_strip) ? float3(NAN_FLT) : pos_rad.xyz;
pt.radius = pos_rad.w;
pt.azimuthal_offset = azimuthal_offset_get(segment);
if (pt.curve_segment == 0) {
/* Hair root. */
pt.T = point_position_get(pt.point_id + 1) - pt.P;
}
else if (indirection.is_cyclic_point) {
/* Cyclic end point must match start point. */
pt.T = point_position_get(pt.point_id - pt.curve_segment + 1) - pt.P;
}
else {
pt.T = pt.P - point_position_get(pt.point_id - 1);
}
return pt;
}
/* -- Subdivision stage -- */
Point object_to_world(Point pt, float4x4 object_to_world)
{
pt.P = transform_point(object_to_world, pt.P);
pt.T = normalize(transform_direction(object_to_world, pt.T));
pt.radius *= length(to_scale(object_to_world)) * M_SQRT1_3;
return pt;
}
struct ShapePoint {
/* Curve tangent space. */
float3 curve_N;
float3 curve_T;
float3 curve_B;
/* Position on the curve shape. */
float3 P;
/* Shading normal at the position on the curve shape. */
float3 N;
};
/**
* We use a transform feedback or compute shader to preprocess the strands and add more subdivision
* to it. For the moment these are simple smooth interpolation but one could hope to see the full
* children particle modifiers being evaluated at this stage.
*
* If no more subdivision is needed, we can skip this step.
* Return the position of the expanded position in world-space.
* \arg pt : world space curve point.
* \arg V : world space view vector (toward viewer) at `pt.P`.
*/
float hair_get_local_time()
ShapePoint shape_point_get(const Point pt, const float3 V)
{
# ifdef GPU_VERTEX_SHADER
VERTEX_SHADER_CREATE_INFO(draw_hair)
return float(gl_VertexID % hairStrandsRes) / float(hairStrandsRes - 1);
# elif defined(GPU_COMPUTE_SHADER)
COMPUTE_SHADER_CREATE_INFO(draw_hair_refine_compute)
return float(gl_GlobalInvocationID.y) / float(hairStrandsRes - 1);
# else
return 0;
# endif
const bool is_strand = buffer_get(draw_curves_infos, drw_curves).half_cylinder_face_count == 0u;
ShapePoint shape;
/* Shading tangent is inverted because of legacy reason. */
/* TODO(fclem): Change user code. */
shape.curve_T = -pt.T;
shape.curve_B = normalize(cross(pt.T, V));
shape.curve_N = cross(shape.curve_B, pt.T);
/* Point in curve azimuthal space. */
const float2 lP = float2(pt.azimuthal_offset, sin_from_cos(abs(pt.azimuthal_offset)));
shape.N = shape.curve_B * lP.x + shape.curve_N * lP.y;
shape.P = pt.P + shape.N * (is_strand ? 0.0f : pt.radius);
return shape;
}
int hair_get_id()
float get_customdata_float(const int curve_id, const samplerBuffer cd_buf)
{
# ifdef GPU_VERTEX_SHADER
VERTEX_SHADER_CREATE_INFO(draw_hair)
return gl_VertexID / hairStrandsRes;
# elif defined(GPU_COMPUTE_SHADER)
COMPUTE_SHADER_CREATE_INFO(draw_hair_refine_compute)
return int(gl_GlobalInvocationID.x) + hairStrandOffset;
# else
return 0;
# endif
return texelFetch(cd_buf, curve_id).x;
}
# ifdef HAIR_PHASE_SUBDIV
COMPUTE_SHADER_CREATE_INFO(draw_hair_refine_compute)
int hair_get_base_id(float local_time, int strand_segments, out float interp_time)
float2 get_customdata_vec2(const int curve_id, const samplerBuffer cd_buf)
{
float time_per_strand_seg = 1.0f / float(strand_segments);
float ratio = local_time / time_per_strand_seg;
interp_time = fract(ratio);
return int(ratio);
return texelFetch(cd_buf, curve_id).xy;
}
void hair_get_interp_attrs(
out float4 data0, out float4 data1, out float4 data2, out float4 data3, out float interp_time)
float3 get_customdata_vec3(const int curve_id, const samplerBuffer cd_buf)
{
float local_time = hair_get_local_time();
int hair_id = hair_get_id();
int strand_offset = int(texelFetch(hairStrandBuffer, hair_id).x);
int strand_segments = int(texelFetch(hairStrandSegBuffer, hair_id).x);
int id = hair_get_base_id(local_time, strand_segments, interp_time);
int ofs_id = id + strand_offset;
data0 = texelFetch(hairPointBuffer, ofs_id - 1);
data1 = texelFetch(hairPointBuffer, ofs_id);
data2 = texelFetch(hairPointBuffer, ofs_id + 1);
data3 = texelFetch(hairPointBuffer, ofs_id + 2);
if (id <= 0) {
/* root points. Need to reconstruct previous data. */
data0 = data1 * 2.0f - data2;
}
if (id + 1 >= strand_segments) {
/* tip points. Need to reconstruct next data. */
data3 = data2 * 2.0f - data1;
}
}
# endif
/* -- Drawing stage -- */
/**
* For final drawing, the vertex index and the number of vertex per segment
*/
# if !defined(HAIR_PHASE_SUBDIV) && defined(GPU_VERTEX_SHADER)
VERTEX_SHADER_CREATE_INFO(draw_hair)
int hair_get_strand_id()
{
return gl_VertexID / (hairStrandsRes * hairThicknessRes);
return texelFetch(cd_buf, curve_id).xyz;
}
int hair_get_base_id()
float4 get_customdata_vec4(const int curve_id, const samplerBuffer cd_buf)
{
return gl_VertexID / hairThicknessRes;
return texelFetch(cd_buf, curve_id).xyzw;
}
/* Copied from cycles. */
float hair_shaperadius(float shape, float root, float tip, float time)
float3 get_curve_root_pos(const int point_id, const int curve_segment)
{
float radius = 1.0f - time;
const auto &curves_pos_rad_buf = buffer_get(draw_curves, curves_pos_rad_buf);
if (shape < 0.0f) {
radius = pow(radius, 1.0f + shape);
}
else {
radius = pow(radius, 1.0f / (1.0f - shape));
}
if (hairCloseTip && (time > 0.99f)) {
return 0.0f;
}
return (radius * (root - tip)) + tip;
int curve_start = point_id - curve_segment;
return texelFetch(curves_pos_rad_buf, curve_start).xyz;
}
# if defined(OS_MAC) && defined(GPU_OPENGL)
in float dummy;
# endif
} // namespace curves
void hair_get_center_pos_tan_binor_time(bool is_persp,
float3 camera_pos,
float3 camera_z,
out float3 wpos,
out float3 wtan,
out float3 wbinor,
out float time,
out float thickness)
{
int id = hair_get_base_id();
CurvePoint data = hair_get_point(id);
wpos = data.position;
time = data.time;
# if defined(OS_MAC) && defined(GPU_OPENGL)
/* Generate a dummy read to avoid the driver bug with shaders having no
* vertex reads on macOS (#60171) */
wpos.y += dummy * 0.0f;
# endif
if (time == 0.0f) {
/* Hair root */
wtan = hair_get_point(id + 1).position - wpos;
}
else {
wtan = wpos - hair_get_point(id - 1).position;
}
float4x4 obmat = hairDupliMatrix;
wpos = (obmat * float4(wpos, 1.0f)).xyz;
wtan = -normalize(to_float3x3(obmat) * wtan);
float3 camera_vec = (is_persp) ? camera_pos - wpos : camera_z;
wbinor = normalize(cross(camera_vec, wtan));
thickness = hair_shaperadius(hairRadShape, hairRadRoot, hairRadTip, time);
}
void hair_get_pos_tan_binor_time(bool is_persp,
float4x4 invmodel_mat,
float3 camera_pos,
float3 camera_z,
out float3 wpos,
out float3 wtan,
out float3 wbinor,
out float time,
out float thickness,
out float thick_time)
{
hair_get_center_pos_tan_binor_time(
is_persp, camera_pos, camera_z, wpos, wtan, wbinor, time, thickness);
if (hairThicknessRes > 1) {
thick_time = float(gl_VertexID % hairThicknessRes) / float(hairThicknessRes - 1);
thick_time = thickness * (thick_time * 2.0f - 1.0f);
/* Take object scale into account.
* NOTE: This only works fine with uniform scaling. */
float scale = 1.0f / length(to_float3x3(invmodel_mat) * wbinor);
wpos += wbinor * thick_time * scale;
}
else {
/* NOTE: Ensures 'hairThickTime' is initialized -
* avoids undefined behavior on certain macOS configurations. */
thick_time = 0.0f;
}
}
float hair_get_customdata_float(const samplerBuffer cd_buf)
{
int id = hair_get_strand_id();
return texelFetch(cd_buf, id).r;
}
float2 hair_get_customdata_vec2(const samplerBuffer cd_buf)
{
int id = hair_get_strand_id();
return texelFetch(cd_buf, id).rg;
}
float3 hair_get_customdata_vec3(const samplerBuffer cd_buf)
{
int id = hair_get_strand_id();
return texelFetch(cd_buf, id).rgb;
}
float4 hair_get_customdata_vec4(const samplerBuffer cd_buf)
{
int id = hair_get_strand_id();
return texelFetch(cd_buf, id).rgba;
}
float3 hair_get_strand_pos()
{
int id = hair_get_strand_id() * hairStrandsRes;
return hair_get_point(id).position;
}
float2 hair_get_barycentric()
{
/* To match cycles without breaking into individual segment we encode if we need to invert
* the first component into the second component. We invert if the barycentricTexCo.y
* is NOT 0.0 or 1.0. */
int id = hair_get_base_id();
return float2(float((id % 2) == 1), float(((id % 4) % 3) > 0));
}
# endif
/* To be fed the result of hair_get_barycentric from vertex shader. */
float2 hair_resolve_barycentric(float2 vert_barycentric)
{
if (fract(vert_barycentric.y) != 0.0f) {
return float2(vert_barycentric.x, 0.0f);
}
else {
return float2(1.0f - vert_barycentric.x, 0.0f);
}
}
/* Hair interpolation functions. */
float4 hair_get_weights_cardinal(float t)
{
float t2 = t * t;
float t3 = t2 * t;
# if defined(CARDINAL)
float fc = 0.71f;
# else /* defined(CATMULL_ROM) */
float fc = 0.5f;
# endif
float4 weights;
/* GLSL Optimized version of key_curve_position_weights() */
float fct = t * fc;
float fct2 = t2 * fc;
float fct3 = t3 * fc;
weights.x = (fct2 * 2.0f - fct3) - fct;
weights.y = (t3 * 2.0f - fct3) + (-t2 * 3.0f + fct2) + 1.0f;
weights.z = (-t3 * 2.0f + fct3) + (t2 * 3.0f - (2.0f * fct2)) + fct;
weights.w = fct3 - fct2;
return weights;
}
/* TODO(fclem): This one is buggy, find why. (it's not the optimization!!) */
float4 hair_get_weights_bspline(float t)
{
float t2 = t * t;
float t3 = t2 * t;
float4 weights;
/* GLSL Optimized version of key_curve_position_weights() */
weights.xz = float2(-0.16666666f, -0.5f) * t3 + (0.5f * t2 + 0.5f * float2(-t, t) + 0.16666666f);
weights.y = (0.5f * t3 - t2 + 0.66666666f);
weights.w = (0.16666666f * t3);
return weights;
}
float4 hair_interp_data(float4 v0, float4 v1, float4 v2, float4 v3, float4 w)
{
return v0 * w.x + v1 * w.y + v2 * w.z + v3 * w.w;
}
#endif

View File

@@ -0,0 +1,24 @@
/* SPDX-FileCopyrightText: 2025 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
#include "draw_curves_info.hh"
VERTEX_SHADER_CREATE_INFO(draw_curves_test)
#include "draw_curves_lib.glsl"
void main()
{
#ifdef GPU_VERTEX_SHADER
curves::Point pt = curves::point_get(uint(gl_VertexID));
result_pos_buf[gl_VertexID] = pt.P.x;
result_indices_buf[gl_VertexID].x = pt.point_id;
result_indices_buf[gl_VertexID].y = pt.curve_id;
result_indices_buf[gl_VertexID].z = pt.curve_segment;
result_indices_buf[gl_VertexID].w = int(pt.azimuthal_offset);
gl_Position = float4(0);
#endif
}

View File

@@ -0,0 +1,85 @@
/* SPDX-FileCopyrightText: 2025 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/**
* GPU generated indirection buffer. Updated on topology change.
* One thread processes one curve.
*/
#include "draw_curves_info.hh"
COMPUTE_SHADER_CREATE_INFO(draw_curves_topology)
#include "gpu_shader_attribute_load_lib.glsl"
#include "gpu_shader_offset_indices_lib.glsl"
#include "gpu_shader_utildefines_lib.glsl"
void main()
{
if (gl_GlobalInvocationID.x >= uint(curves_count)) {
return;
}
uint curve_id = gl_GlobalInvocationID.x + uint(curves_start);
bool is_curve_cyclic = false;
if (use_cyclic) {
is_curve_cyclic = gpu_attr_load_bool(curves_cyclic_buf, curve_id);
}
IndexRange points = offset_indices::load_range_from_buffer(evaluated_offsets_buf, curve_id);
int index_start = points.start();
int num_segment = points.size();
if (use_cyclic) {
index_start += int(curve_id);
num_segment += 1;
}
constexpr int cyclic_endpoint_pivot = INT_MAX / 2;
constexpr int end_of_curve = INT_MAX;
int indirection_index_count = num_segment + (is_ribbon_topology ? 1 : -1);
index_start += int(is_ribbon_topology ? curve_id : -curve_id);
for (int i = 0; i < indirection_index_count; i++) {
int value = int((i == 0) ? curve_id : -i);
bool is_restart = false;
bool is_cyclic_last_segment = false;
if (use_cyclic) {
if (is_ribbon_topology) {
is_cyclic_last_segment = i == indirection_index_count - 2;
if (is_curve_cyclic) {
is_restart = i == indirection_index_count - 1;
}
else {
is_restart = i >= indirection_index_count - 2;
}
}
else {
is_cyclic_last_segment = i == indirection_index_count - 1;
if (is_curve_cyclic) {
is_restart = false;
}
else {
is_restart = i == indirection_index_count - 1;
}
}
}
else {
if (is_ribbon_topology) {
is_restart = i == indirection_index_count - 1;
}
else {
is_restart = false;
}
}
indirection_buf[index_start + i] = is_restart ? end_of_curve :
(is_cyclic_last_segment ?
value - cyclic_endpoint_pivot :
value);
}
}

View File

@@ -1,22 +0,0 @@
/* SPDX-FileCopyrightText: 2021-2022 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
#include "draw_hair_refine_info.hh"
#include "draw_curves_lib.glsl"
COMPUTE_SHADER_CREATE_INFO(draw_hair_refine_compute)
void main()
{
float interp_time = 0.0f;
float4 data0, data1, data2, data3;
hair_get_interp_attrs(data0, data1, data2, data3, interp_time);
float4 weights = hair_get_weights_cardinal(interp_time);
float4 result = hair_interp_data(data0, data1, data2, data3, weights);
uint index = uint(hair_get_id() * hairStrandsRes) + gl_GlobalInvocationID.y;
posTime[index] = result;
}

View File

@@ -1,32 +0,0 @@
/* SPDX-FileCopyrightText: 2022 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup draw
*/
#ifdef GPU_SHADER
# pragma once
# include "gpu_glsl_cpp_stubs.hh"
# include "draw_object_infos_info.hh"
# define HAIR_PHASE_SUBDIV
# define HAIR_SHADER
# define DRW_HAIR_INFO
#endif
#include "gpu_shader_create_info.hh"
GPU_SHADER_CREATE_INFO(draw_hair_refine_compute)
LOCAL_GROUP_SIZE(1, 1)
STORAGE_BUF(0, write, float4, posTime[])
/* Per strands data. */
SAMPLER(1, usamplerBuffer, hairStrandBuffer)
SAMPLER(2, usamplerBuffer, hairStrandSegBuffer)
COMPUTE_SOURCE("draw_hair_refine_comp.glsl")
DEFINE("HAIR_PHASE_SUBDIV")
ADDITIONAL_INFO(draw_hair)
DO_STATIC_COMPILATION()
GPU_SHADER_CREATE_END()

View File

@@ -11,7 +11,7 @@
# include "draw_view_info.hh"
# define HAIR_SHADER
# define CURVES_SHADER
# define DRW_GPENCIL_INFO
#endif
@@ -59,33 +59,11 @@ GPU_SHADER_CREATE_INFO(draw_mesh)
ADDITIONAL_INFO(draw_modelmat)
GPU_SHADER_CREATE_END()
GPU_SHADER_CREATE_INFO(draw_hair)
DEFINE("HAIR_SHADER")
GPU_SHADER_CREATE_INFO(draw_curves)
DEFINE("CURVES_SHADER")
DEFINE("DRW_HAIR_INFO")
/* Per control points data inside subdivision shader
* or
* per tessellated point inside final shader. */
SAMPLER(0, samplerBuffer, hairPointBuffer)
/* TODO(@fclem): Pack these into one UBO. */
/* hairStrandsRes: Number of points per hair strand.
* 2 - no subdivision
* 3+ - 1 or more interpolated points per hair. */
PUSH_CONSTANT(int, hairStrandsRes)
/* hairThicknessRes : Subdivide around the hair.
* 1 - Wire Hair: Only one pixel thick, independent of view distance.
* 2 - Poly-strip Hair: Correct width, flat if camera is parallel.
* 3+ - Cylinder Hair: Massive calculation but potentially perfect. Still need proper support. */
PUSH_CONSTANT(int, hairThicknessRes)
/* Hair thickness shape. */
PUSH_CONSTANT(float, hairRadRoot)
PUSH_CONSTANT(float, hairRadTip)
PUSH_CONSTANT(float, hairRadShape)
PUSH_CONSTANT(bool, hairCloseTip)
/* Strand batch offset when used in compute shaders. */
PUSH_CONSTANT(int, hairStrandOffset)
/* Hair particles are stored in world space coordinate.
* This matrix convert to the instance "world space". */
PUSH_CONSTANT(float4x4, hairDupliMatrix)
SAMPLER_FREQ(0, samplerBuffer, curves_pos_rad_buf, BATCH)
SAMPLER_FREQ(1, isamplerBuffer, curves_indirection_buf, BATCH)
GPU_SHADER_CREATE_END()
GPU_SHADER_CREATE_INFO(draw_pointcloud)

View File

@@ -0,0 +1,1343 @@
/* SPDX-FileCopyrightText: 2025 Blender Authors
*
* SPDX-License-Identifier: Apache-2.0 */
#include "DNA_curves_types.h"
#include "BKE_curves.hh"
#include "GPU_batch.hh"
#include "GPU_shader.hh"
#include "draw_curves_defines.hh"
#include "draw_manager.hh"
#include "draw_pass.hh"
#include "draw_testing.hh"
#include "draw_shader_shared.hh"
namespace blender::draw {
static void test_draw_curves_lib()
{
Manager manager;
gpu::Shader *sh = GPU_shader_create_from_info_name("draw_curves_test");
struct Indirection {
int index;
GPU_VERTEX_FORMAT_FUNC(Indirection, index);
};
gpu::VertBuf *indirection_ribbon_buf = GPU_vertbuf_create_with_format_ex(
Indirection::format(), GPU_USAGE_FLAG_BUFFER_TEXTURE_ONLY);
indirection_ribbon_buf->allocate(9);
indirection_ribbon_buf->data<int>().copy_from({0, -1, -2, -3, -4, 0x7FFFFFFF, 1, -1, -2});
gpu::Batch *batch_ribbon = GPU_batch_create_procedural(GPU_PRIM_TRI_STRIP, 2 * 9);
gpu::VertBuf *indirection_cylinder_buf = GPU_vertbuf_create_with_format_ex(
Indirection::format(), GPU_USAGE_FLAG_BUFFER_TEXTURE_ONLY);
indirection_cylinder_buf->allocate(6);
indirection_cylinder_buf->data<int>().copy_from({0, -1, -2, -3, 1, -1});
gpu::Batch *batch_cylinder = GPU_batch_create_procedural(GPU_PRIM_TRI_STRIP, (3 * 2 + 1) * 6);
struct PositionRadius {
float4 pos_rad;
GPU_VERTEX_FORMAT_FUNC(PositionRadius, pos_rad);
};
gpu::VertBuf *pos_rad_buf = GPU_vertbuf_create_with_format_ex(
PositionRadius::format(), GPU_USAGE_FLAG_BUFFER_TEXTURE_ONLY);
pos_rad_buf->allocate(8);
pos_rad_buf->data<float4>().copy_from({float4{1.0f},
float4{0.75f},
float4{0.5f},
float4{0.25f},
float4{0.0f},
float4{0.0f},
float4{1.0f},
float4{2.0f}});
UniformBuffer<CurvesInfos> curves_info_buf;
curves_info_buf.is_point_attribute[0].x = 0;
curves_info_buf.is_point_attribute[1].x = 1;
/* Ribbon. */
curves_info_buf.vertex_per_segment = 2;
curves_info_buf.half_cylinder_face_count = 1;
curves_info_buf.push_update();
Framebuffer fb;
fb.ensure(int2(1, 1));
{
StorageArrayBuffer<float, 512> result_pos;
StorageArrayBuffer<int4, 512> result_idx;
result_pos.clear_to_zero();
result_idx.clear_to_zero();
PassSimple pass("Ribbon Curves");
pass.init();
pass.framebuffer_set(&fb);
pass.shader_set(sh);
pass.bind_ubo("drw_curves", curves_info_buf);
pass.bind_texture("curves_pos_rad_buf", pos_rad_buf);
pass.bind_texture("curves_indirection_buf", indirection_ribbon_buf);
pass.bind_ssbo("result_pos_buf", result_pos);
pass.bind_ssbo("result_indices_buf", result_idx);
pass.draw(batch_ribbon);
pass.barrier(GPU_BARRIER_BUFFER_UPDATE);
manager.submit(pass);
/* Note: Expected values follows diagram shown in #142969. */
result_pos.read();
EXPECT_EQ(result_pos[0], 1.0f);
EXPECT_EQ(result_pos[1], 1.0f);
EXPECT_EQ(result_pos[2], 0.75f);
EXPECT_EQ(result_pos[3], 0.75f);
EXPECT_EQ(result_pos[4], 0.5f);
EXPECT_EQ(result_pos[5], 0.5f);
EXPECT_EQ(result_pos[6], 0.25f);
EXPECT_EQ(result_pos[7], 0.25f);
EXPECT_EQ(result_pos[8], 0.0f);
EXPECT_EQ(result_pos[9], 0.0f);
EXPECT_TRUE(isnan(result_pos[10]));
EXPECT_TRUE(isnan(result_pos[11]));
EXPECT_EQ(result_pos[12], 0.0f);
EXPECT_EQ(result_pos[13], 0.0f);
EXPECT_EQ(result_pos[14], 1.0f);
EXPECT_EQ(result_pos[15], 1.0f);
EXPECT_EQ(result_pos[16], 2.0f);
EXPECT_EQ(result_pos[17], 2.0f);
result_idx.read();
/* x: point_id, y: curve_id, z: curve_segment, w: azimuthal_offset */
EXPECT_EQ(result_idx[0], int4(0, 0, 0, -1));
EXPECT_EQ(result_idx[1], int4(0, 0, 0, 1));
EXPECT_EQ(result_idx[2], int4(1, 0, 1, -1));
EXPECT_EQ(result_idx[3], int4(1, 0, 1, 1));
EXPECT_EQ(result_idx[4], int4(2, 0, 2, -1));
EXPECT_EQ(result_idx[5], int4(2, 0, 2, 1));
EXPECT_EQ(result_idx[6], int4(3, 0, 3, -1));
EXPECT_EQ(result_idx[7], int4(3, 0, 3, 1));
EXPECT_EQ(result_idx[8], int4(4, 0, 4, -1));
EXPECT_EQ(result_idx[9], int4(4, 0, 4, 1));
EXPECT_EQ(result_idx[10], int4(5, 0, 0, -1)); /* End Of Curve */
EXPECT_EQ(result_idx[11], int4(5, 0, 0, 1)); /* End Of Curve */
EXPECT_EQ(result_idx[12], int4(5, 1, 0, -1));
EXPECT_EQ(result_idx[13], int4(5, 1, 0, 1));
EXPECT_EQ(result_idx[14], int4(6, 1, 1, -1));
EXPECT_EQ(result_idx[15], int4(6, 1, 1, 1));
EXPECT_EQ(result_idx[16], int4(7, 1, 2, -1));
EXPECT_EQ(result_idx[17], int4(7, 1, 2, 1));
}
/* Cylinder. */
curves_info_buf.vertex_per_segment = 7;
curves_info_buf.half_cylinder_face_count = 2;
curves_info_buf.push_update();
{
StorageArrayBuffer<float, 512> result_pos;
StorageArrayBuffer<int4, 512> result_idx;
result_pos.clear_to_zero();
result_idx.clear_to_zero();
PassSimple pass("Cylinder Curves");
pass.init();
pass.framebuffer_set(&fb);
pass.shader_set(sh);
pass.bind_ubo("drw_curves", curves_info_buf);
pass.bind_texture("curves_pos_rad_buf", pos_rad_buf);
pass.bind_texture("curves_indirection_buf", indirection_cylinder_buf);
pass.bind_ssbo("result_pos_buf", result_pos);
pass.bind_ssbo("result_indices_buf", result_idx);
pass.draw(batch_cylinder);
pass.barrier(GPU_BARRIER_BUFFER_UPDATE);
manager.submit(pass);
/* Note: Expected values follows diagram shown in #142969. */
result_pos.read();
EXPECT_EQ(result_pos[0], 0.75f);
EXPECT_EQ(result_pos[1], 1.0f);
EXPECT_EQ(result_pos[2], 0.75f);
EXPECT_EQ(result_pos[3], 1.0f);
EXPECT_EQ(result_pos[4], 0.75f);
EXPECT_EQ(result_pos[5], 1.0f);
EXPECT_TRUE(isnan(result_pos[6]));
EXPECT_EQ(result_pos[7], 0.75f);
EXPECT_EQ(result_pos[8], 0.5f);
EXPECT_EQ(result_pos[9], 0.75f);
EXPECT_EQ(result_pos[10], 0.5f);
EXPECT_EQ(result_pos[11], 0.75f);
EXPECT_EQ(result_pos[12], 0.5f);
EXPECT_TRUE(isnan(result_pos[13]));
EXPECT_EQ(result_pos[14], 0.25f);
EXPECT_EQ(result_pos[15], 0.5f);
EXPECT_EQ(result_pos[16], 0.25f);
EXPECT_EQ(result_pos[17], 0.5f);
EXPECT_EQ(result_pos[18], 0.25f);
EXPECT_EQ(result_pos[19], 0.5f);
EXPECT_TRUE(isnan(result_pos[20]));
EXPECT_EQ(result_pos[21], 0.25f);
EXPECT_EQ(result_pos[22], 0.0f);
EXPECT_EQ(result_pos[23], 0.25f);
EXPECT_EQ(result_pos[24], 0.0f);
EXPECT_EQ(result_pos[25], 0.25f);
EXPECT_EQ(result_pos[26], 0.0f);
EXPECT_TRUE(isnan(result_pos[27]));
EXPECT_EQ(result_pos[28], 1.0f);
EXPECT_EQ(result_pos[29], 0.0f);
EXPECT_EQ(result_pos[30], 1.0f);
EXPECT_EQ(result_pos[31], 0.0f);
EXPECT_EQ(result_pos[32], 1.0f);
EXPECT_EQ(result_pos[33], 0.0f);
EXPECT_TRUE(isnan(result_pos[34]));
EXPECT_EQ(result_pos[35], 1.0f);
EXPECT_EQ(result_pos[36], 2.0f);
EXPECT_EQ(result_pos[37], 1.0f);
EXPECT_EQ(result_pos[38], 2.0f);
EXPECT_EQ(result_pos[39], 1.0f);
EXPECT_EQ(result_pos[40], 2.0f);
EXPECT_TRUE(isnan(result_pos[41]));
result_idx.read();
/* x: point_id, y: curve_id, z: curve_segment, w: azimuthal_offset */
EXPECT_EQ(result_idx[0], int4(1, 0, 1, -1));
EXPECT_EQ(result_idx[1], int4(0, 0, 0, -1));
EXPECT_EQ(result_idx[2], int4(1, 0, 1, 0));
EXPECT_EQ(result_idx[3], int4(0, 0, 0, 0));
EXPECT_EQ(result_idx[4], int4(1, 0, 1, 1));
EXPECT_EQ(result_idx[5], int4(0, 0, 0, 1));
EXPECT_EQ(result_idx[6], int4(0, 0, 0, 2));
EXPECT_EQ(result_idx[7], int4(1, 0, 1, -1));
EXPECT_EQ(result_idx[8], int4(2, 0, 2, -1));
EXPECT_EQ(result_idx[9], int4(1, 0, 1, 0));
EXPECT_EQ(result_idx[10], int4(2, 0, 2, 0));
EXPECT_EQ(result_idx[11], int4(1, 0, 1, 1));
EXPECT_EQ(result_idx[12], int4(2, 0, 2, 1));
EXPECT_EQ(result_idx[13], int4(1, 0, 1, 2));
EXPECT_EQ(result_idx[14], int4(3, 0, 3, -1));
EXPECT_EQ(result_idx[15], int4(2, 0, 2, -1));
EXPECT_EQ(result_idx[16], int4(3, 0, 3, 0));
EXPECT_EQ(result_idx[17], int4(2, 0, 2, 0));
EXPECT_EQ(result_idx[18], int4(3, 0, 3, 1));
EXPECT_EQ(result_idx[19], int4(2, 0, 2, 1));
EXPECT_EQ(result_idx[20], int4(2, 0, 2, 2));
EXPECT_EQ(result_idx[21], int4(3, 0, 3, -1));
EXPECT_EQ(result_idx[22], int4(4, 0, 4, -1));
EXPECT_EQ(result_idx[23], int4(3, 0, 3, 0));
EXPECT_EQ(result_idx[24], int4(4, 0, 4, 0));
EXPECT_EQ(result_idx[25], int4(3, 0, 3, 1));
EXPECT_EQ(result_idx[26], int4(4, 0, 4, 1));
EXPECT_EQ(result_idx[27], int4(3, 0, 3, 2));
EXPECT_EQ(result_idx[28], int4(6, 1, 1, -1));
EXPECT_EQ(result_idx[29], int4(5, 1, 0, -1));
EXPECT_EQ(result_idx[30], int4(6, 1, 1, 0));
EXPECT_EQ(result_idx[31], int4(5, 1, 0, 0));
EXPECT_EQ(result_idx[32], int4(6, 1, 1, 1));
EXPECT_EQ(result_idx[33], int4(5, 1, 0, 1));
EXPECT_EQ(result_idx[34], int4(5, 1, 0, 2));
EXPECT_EQ(result_idx[35], int4(6, 1, 1, -1));
EXPECT_EQ(result_idx[36], int4(7, 1, 2, -1));
EXPECT_EQ(result_idx[37], int4(6, 1, 1, 0));
EXPECT_EQ(result_idx[38], int4(7, 1, 2, 0));
EXPECT_EQ(result_idx[39], int4(6, 1, 1, 1));
EXPECT_EQ(result_idx[40], int4(7, 1, 2, 1));
EXPECT_EQ(result_idx[41], int4(6, 1, 1, 2));
}
GPU_shader_unbind();
GPU_SHADER_FREE_SAFE(sh);
GPU_BATCH_DISCARD_SAFE(batch_ribbon);
GPU_BATCH_DISCARD_SAFE(batch_cylinder);
GPU_VERTBUF_DISCARD_SAFE(indirection_ribbon_buf);
GPU_VERTBUF_DISCARD_SAFE(indirection_cylinder_buf);
GPU_VERTBUF_DISCARD_SAFE(pos_rad_buf);
}
DRAW_TEST(draw_curves_lib)
static void test_draw_curves_topology()
{
Manager manager;
gpu::Shader *sh = GPU_shader_create_from_info_name("draw_curves_topology");
struct IntBuf {
int data;
GPU_VERTEX_FORMAT_FUNC(IntBuf, data);
};
gpu::VertBuf *curve_offsets_buf = GPU_vertbuf_create_with_format(IntBuf::format());
curve_offsets_buf->allocate(4);
curve_offsets_buf->data<int>().copy_from({0, 5, 8, 10});
{
StorageArrayBuffer<int, 512> indirection_buf;
indirection_buf.clear_to_zero();
PassSimple pass("Ribbon Curves");
pass.init();
pass.shader_set(sh);
pass.bind_ssbo("evaluated_offsets_buf", curve_offsets_buf);
pass.bind_ssbo("curves_cyclic_buf", curve_offsets_buf);
pass.bind_ssbo("indirection_buf", indirection_buf);
pass.push_constant("curves_start", 0);
pass.push_constant("curves_count", 3);
pass.push_constant("is_ribbon_topology", true);
pass.push_constant("use_cyclic", false);
pass.dispatch(1);
pass.barrier(GPU_BARRIER_BUFFER_UPDATE);
manager.submit(pass);
/* Note: Expected values follows diagram shown in #142969. */
indirection_buf.read();
EXPECT_EQ(indirection_buf[0], 0);
EXPECT_EQ(indirection_buf[1], -1);
EXPECT_EQ(indirection_buf[2], -2);
EXPECT_EQ(indirection_buf[3], -3);
EXPECT_EQ(indirection_buf[4], -4);
EXPECT_EQ(indirection_buf[5], 0x7FFFFFFF);
EXPECT_EQ(indirection_buf[6], 1);
EXPECT_EQ(indirection_buf[7], -1);
EXPECT_EQ(indirection_buf[8], -2);
EXPECT_EQ(indirection_buf[9], 0x7FFFFFFF);
EXPECT_EQ(indirection_buf[10], 2);
EXPECT_EQ(indirection_buf[11], -1);
EXPECT_EQ(indirection_buf[12], 0x7FFFFFFF);
/* Ensure the rest of the buffer is untouched. */
EXPECT_EQ(indirection_buf[13], 0);
EXPECT_EQ(indirection_buf[14], 0);
}
{
StorageArrayBuffer<int, 512> indirection_buf;
indirection_buf.clear_to_zero();
PassSimple pass("Cylinder Curves");
pass.init();
pass.shader_set(sh);
pass.bind_ssbo("evaluated_offsets_buf", curve_offsets_buf);
pass.bind_ssbo("curves_cyclic_buf", curve_offsets_buf);
pass.bind_ssbo("indirection_buf", indirection_buf);
pass.push_constant("curves_start", 0);
pass.push_constant("curves_count", 3);
pass.push_constant("is_ribbon_topology", false);
pass.push_constant("use_cyclic", false);
pass.dispatch(1);
pass.barrier(GPU_BARRIER_BUFFER_UPDATE);
manager.submit(pass);
/* Note: Expected values follows diagram shown in #142969. */
indirection_buf.read();
EXPECT_EQ(indirection_buf[0], 0);
EXPECT_EQ(indirection_buf[1], -1);
EXPECT_EQ(indirection_buf[2], -2);
EXPECT_EQ(indirection_buf[3], -3);
EXPECT_EQ(indirection_buf[4], 1);
EXPECT_EQ(indirection_buf[5], -1);
EXPECT_EQ(indirection_buf[6], 2);
/* Ensure the rest of the buffer is untouched. */
EXPECT_EQ(indirection_buf[7], 0);
EXPECT_EQ(indirection_buf[8], 0);
}
GPU_shader_unbind();
GPU_SHADER_FREE_SAFE(sh);
GPU_VERTBUF_DISCARD_SAFE(curve_offsets_buf);
}
DRAW_TEST(draw_curves_topology)
static void test_draw_curves_interpolate_position()
{
Manager manager;
gpu::Shader *sh = GPU_shader_create_from_info_name("draw_curves_interpolate_position");
gpu::Shader *sh_length = GPU_shader_create_from_info_name(
"draw_curves_evaluate_length_intercept");
const int curve_resolution = 2;
const Vector<int> evaluated_offsets = {0, 5, 8};
struct IntBuf {
int data;
GPU_VERTEX_FORMAT_FUNC(IntBuf, data);
};
gpu::VertBuf *points_by_curve_buf = GPU_vertbuf_create_with_format(IntBuf::format());
points_by_curve_buf->allocate(3);
points_by_curve_buf->data<int>().copy_from({0, 3, 5});
gpu::VertBuf *curves_type_buf = GPU_vertbuf_create_with_format(IntBuf::format());
curves_type_buf->allocate(1);
curves_type_buf->data<char>().copy_from({CURVE_TYPE_CATMULL_ROM, CURVE_TYPE_CATMULL_ROM, 0, 0});
gpu::VertBuf *curves_resolution_buf = GPU_vertbuf_create_with_format(IntBuf::format());
curves_resolution_buf->allocate(2);
curves_resolution_buf->data<int>().copy_from({curve_resolution, curve_resolution});
gpu::VertBuf *evaluated_points_by_curve_buf = GPU_vertbuf_create_with_format(IntBuf::format());
evaluated_points_by_curve_buf->allocate(3);
evaluated_points_by_curve_buf->data<int>().copy_from(evaluated_offsets);
const Vector<float> points_radius = {1.0f, 0.5f, 0.0f, 0.0f, 2.0f};
const Vector<float3> positions = {
float3{1.0f}, float3{0.5f}, float3{0.0f}, float3{0.0f}, float3{2.0f}};
struct Position {
float3 pos;
GPU_VERTEX_FORMAT_FUNC(Position, pos);
};
gpu::VertBuf *positions_buf = GPU_vertbuf_create_with_format_ex(
Position::format(), GPU_USAGE_FLAG_BUFFER_TEXTURE_ONLY);
positions_buf->allocate(positions.size());
positions_buf->data<float3>().copy_from(positions);
struct Radius {
float rad;
GPU_VERTEX_FORMAT_FUNC(Radius, rad);
};
gpu::VertBuf *radii_buf = GPU_vertbuf_create_with_format_ex(Radius::format(),
GPU_USAGE_FLAG_BUFFER_TEXTURE_ONLY);
radii_buf->allocate(points_radius.size());
radii_buf->data<float>().copy_from(points_radius);
{
StorageArrayBuffer<float4, 512> evaluated_positions_radii_buf;
StorageArrayBuffer<float, 512> evaluated_time_buf;
StorageArrayBuffer<float, 512> curves_length_buf;
evaluated_positions_radii_buf.clear_to_zero();
evaluated_time_buf.clear_to_zero();
curves_length_buf.clear_to_zero();
PassSimple pass("Curves Interpolation Catmull Rom");
pass.init();
pass.specialize_constant(sh, "evaluated_type", int(CURVE_TYPE_CATMULL_ROM));
pass.shader_set(sh);
pass.bind_ssbo(POINTS_BY_CURVES_SLOT, points_by_curve_buf);
pass.bind_ssbo(CURVE_TYPE_SLOT, curves_type_buf);
pass.bind_ssbo(CURVE_RESOLUTION_SLOT, curves_resolution_buf);
pass.bind_ssbo(EVALUATED_POINT_SLOT, evaluated_points_by_curve_buf);
pass.bind_ssbo(POINT_POSITIONS_SLOT, positions_buf);
pass.bind_ssbo(POINT_RADII_SLOT, radii_buf);
pass.bind_ssbo(EVALUATED_POS_RAD_SLOT, evaluated_positions_radii_buf);
pass.push_constant("use_cyclic", false);
pass.bind_ssbo(CURVE_CYCLIC_SLOT, evaluated_points_by_curve_buf); /* Dummy, not used. */
/* Dummy, not used for Catmull-Rom. */
pass.bind_ssbo(HANDLES_POS_LEFT_SLOT, evaluated_points_by_curve_buf);
pass.bind_ssbo(HANDLES_POS_RIGHT_SLOT, evaluated_points_by_curve_buf);
pass.bind_ssbo(BEZIER_OFFSETS_SLOT, evaluated_points_by_curve_buf);
pass.push_constant("curves_start", 0);
pass.push_constant("curves_count", 2);
pass.push_constant("transform", float4x4::identity());
pass.dispatch(1);
pass.barrier(GPU_BARRIER_SHADER_STORAGE);
pass.shader_set(sh_length);
pass.bind_ssbo(EVALUATED_POINT_SLOT, evaluated_points_by_curve_buf);
pass.bind_ssbo(EVALUATED_POS_RAD_SLOT, evaluated_positions_radii_buf);
pass.bind_ssbo(EVALUATED_TIME_SLOT, evaluated_time_buf);
pass.bind_ssbo(CURVES_LENGTH_SLOT, curves_length_buf);
pass.push_constant("curves_start", 0);
pass.push_constant("curves_count", 2);
pass.push_constant("use_cyclic", false);
pass.dispatch(1);
pass.barrier(GPU_BARRIER_BUFFER_UPDATE);
manager.submit(pass);
evaluated_positions_radii_buf.read();
evaluated_time_buf.read();
curves_length_buf.read();
Vector<float> interp_data;
interp_data.resize(8);
bke::curves::catmull_rom::interpolate_to_evaluated(
GSpan(points_radius.as_span().slice(0, 3)),
false,
curve_resolution,
GMutableSpan(interp_data.as_mutable_span().slice(0, 5)));
bke::curves::catmull_rom::interpolate_to_evaluated(
GSpan(points_radius.as_span().slice(3, 2)),
false,
curve_resolution,
GMutableSpan(interp_data.as_mutable_span().slice(5, 3)));
EXPECT_EQ(evaluated_positions_radii_buf[0], float4(interp_data[0]));
EXPECT_EQ(evaluated_positions_radii_buf[1], float4(interp_data[1]));
EXPECT_EQ(evaluated_positions_radii_buf[2], float4(interp_data[2]));
EXPECT_EQ(evaluated_positions_radii_buf[3], float4(interp_data[3]));
EXPECT_EQ(evaluated_positions_radii_buf[4], float4(interp_data[4]));
EXPECT_EQ(evaluated_positions_radii_buf[5], float4(interp_data[5]));
EXPECT_EQ(evaluated_positions_radii_buf[6], float4(interp_data[6]));
EXPECT_EQ(evaluated_positions_radii_buf[7], float4(interp_data[7]));
/* Ensure the rest of the buffer is untouched. */
EXPECT_EQ(evaluated_positions_radii_buf[8], float4(0.0));
EXPECT_FLOAT_EQ(curves_length_buf[0], numbers::sqrt3);
EXPECT_FLOAT_EQ(curves_length_buf[1], 2.0f * numbers::sqrt3);
EXPECT_FLOAT_EQ(evaluated_time_buf[0], 0.0f);
EXPECT_FLOAT_EQ(evaluated_time_buf[1], 0.218749985f);
EXPECT_FLOAT_EQ(evaluated_time_buf[2], 0.5f);
EXPECT_FLOAT_EQ(evaluated_time_buf[3], 0.78125f);
EXPECT_FLOAT_EQ(evaluated_time_buf[4], 1.0f);
EXPECT_FLOAT_EQ(evaluated_time_buf[5], 0.0f);
EXPECT_FLOAT_EQ(evaluated_time_buf[6], 0.5f);
EXPECT_FLOAT_EQ(evaluated_time_buf[7], 1.0f);
/* Ensure the rest of the buffer is untouched. */
EXPECT_EQ(evaluated_time_buf[8], 0.0f);
}
const Vector<float3> handle_pos_left = {
float3{0.0f}, float3{1.0f}, float3{-1.0f}, float3{1.0f}, float3{4.0f}};
const Vector<float3> handle_pos_right = {
float3{0.0f}, float3{-1.0f}, float3{1.0f}, float3{-1.0f}, float3{0.0f}};
gpu::VertBuf *handles_positions_left_buf = GPU_vertbuf_create_with_format_ex(
Position::format(), GPU_USAGE_FLAG_BUFFER_TEXTURE_ONLY);
handles_positions_left_buf->allocate(handle_pos_left.size());
handles_positions_left_buf->data<float3>().copy_from(handle_pos_left);
gpu::VertBuf *handles_positions_right_buf = GPU_vertbuf_create_with_format_ex(
Position::format(), GPU_USAGE_FLAG_BUFFER_TEXTURE_ONLY);
handles_positions_right_buf->allocate(handle_pos_right.size());
handles_positions_right_buf->data<float3>().copy_from(handle_pos_right);
const Vector<int> bezier_offsets = {0, 2, 4, 5, 0, 2, 3};
gpu::VertBuf *bezier_offsets_buf = GPU_vertbuf_create_with_format_ex(
IntBuf::format(), GPU_USAGE_FLAG_BUFFER_TEXTURE_ONLY);
bezier_offsets_buf->allocate(bezier_offsets.size());
bezier_offsets_buf->data<int>().copy_from(bezier_offsets);
gpu::VertBuf *curves_type_bezier_buf = GPU_vertbuf_create_with_format(IntBuf::format());
curves_type_bezier_buf->allocate(1);
curves_type_bezier_buf->data<char>().copy_from({CURVE_TYPE_BEZIER, CURVE_TYPE_BEZIER, 0, 0});
{
StorageArrayBuffer<float4, 512> evaluated_positions_radii_buf;
StorageArrayBuffer<float, 512> evaluated_time_buf;
StorageArrayBuffer<float, 512> curves_length_buf;
evaluated_positions_radii_buf.clear_to_zero();
evaluated_time_buf.clear_to_zero();
curves_length_buf.clear_to_zero();
PassSimple pass("Curves Interpolation Bezier");
pass.init();
pass.specialize_constant(sh, "evaluated_type", int(CURVE_TYPE_BEZIER));
pass.shader_set(sh);
pass.bind_ssbo(POINTS_BY_CURVES_SLOT, points_by_curve_buf);
pass.bind_ssbo(CURVE_TYPE_SLOT, curves_type_bezier_buf);
pass.bind_ssbo(CURVE_RESOLUTION_SLOT, curves_resolution_buf);
pass.bind_ssbo(EVALUATED_POINT_SLOT, evaluated_points_by_curve_buf);
pass.bind_ssbo(POINT_POSITIONS_SLOT, positions_buf);
pass.bind_ssbo(POINT_RADII_SLOT, radii_buf);
pass.bind_ssbo(EVALUATED_POS_RAD_SLOT, evaluated_positions_radii_buf);
pass.push_constant("use_cyclic", false);
pass.bind_ssbo(CURVE_CYCLIC_SLOT, evaluated_points_by_curve_buf); /* Dummy, not used. */
pass.bind_ssbo(HANDLES_POS_LEFT_SLOT, handles_positions_left_buf);
pass.bind_ssbo(HANDLES_POS_RIGHT_SLOT, handles_positions_right_buf);
pass.bind_ssbo(BEZIER_OFFSETS_SLOT, bezier_offsets_buf);
pass.push_constant("curves_start", 0);
pass.push_constant("curves_count", 2);
pass.push_constant("transform", float4x4::identity());
pass.dispatch(1);
pass.barrier(GPU_BARRIER_SHADER_STORAGE);
pass.shader_set(sh_length);
pass.bind_ssbo(EVALUATED_POINT_SLOT, evaluated_points_by_curve_buf);
pass.bind_ssbo(EVALUATED_POS_RAD_SLOT, evaluated_positions_radii_buf);
pass.bind_ssbo(EVALUATED_TIME_SLOT, evaluated_time_buf);
pass.bind_ssbo(CURVES_LENGTH_SLOT, curves_length_buf);
pass.push_constant("curves_start", 0);
pass.push_constant("curves_count", 2);
pass.push_constant("use_cyclic", false);
pass.dispatch(1);
pass.barrier(GPU_BARRIER_BUFFER_UPDATE);
manager.submit(pass);
evaluated_positions_radii_buf.read();
evaluated_time_buf.read();
curves_length_buf.read();
Vector<float3> interp_pos;
interp_pos.resize(8);
Vector<float> interp_rad;
interp_rad.resize(8);
{
const int curve_index = 0;
const IndexRange points(0, 3);
const IndexRange evaluated_points(0, 5);
const IndexRange offsets = bke::curves::per_curve_point_offsets_range(points, curve_index);
bke::curves::bezier::calculate_evaluated_positions(
positions.as_span().slice(points),
handle_pos_left.as_span().slice(points),
handle_pos_right.as_span().slice(points),
bezier_offsets.as_span().slice(offsets),
interp_pos.as_mutable_span().slice(evaluated_points));
bke::curves::bezier::interpolate_to_evaluated(
points_radius.as_span().slice(points),
bezier_offsets.as_span().slice(offsets),
interp_rad.as_mutable_span().slice(evaluated_points));
}
{
const int curve_index = 1;
const IndexRange points(3, 2);
const IndexRange evaluated_points(5, 3);
const IndexRange offsets = bke::curves::per_curve_point_offsets_range(points, curve_index);
bke::curves::bezier::calculate_evaluated_positions(
positions.as_span().slice(points),
handle_pos_left.as_span().slice(points),
handle_pos_right.as_span().slice(points),
bezier_offsets.as_span().slice(offsets),
interp_pos.as_mutable_span().slice(evaluated_points));
bke::curves::bezier::interpolate_to_evaluated(
points_radius.as_span().slice(points),
bezier_offsets.as_span().slice(offsets),
interp_rad.as_mutable_span().slice(evaluated_points));
}
EXPECT_EQ(evaluated_positions_radii_buf[0], float4(interp_pos[0], interp_rad[0]));
EXPECT_EQ(evaluated_positions_radii_buf[1], float4(interp_pos[1], interp_rad[1]));
EXPECT_EQ(evaluated_positions_radii_buf[2], float4(interp_pos[2], interp_rad[2]));
EXPECT_EQ(evaluated_positions_radii_buf[3], float4(interp_pos[3], interp_rad[3]));
EXPECT_EQ(evaluated_positions_radii_buf[4], float4(interp_pos[4], interp_rad[4]));
EXPECT_EQ(evaluated_positions_radii_buf[5], float4(interp_pos[5], interp_rad[5]));
EXPECT_EQ(evaluated_positions_radii_buf[6], float4(interp_pos[6], interp_rad[6]));
EXPECT_EQ(evaluated_positions_radii_buf[7], float4(interp_pos[7], interp_rad[7]));
/* Ensure the rest of the buffer is untouched. */
EXPECT_EQ(evaluated_positions_radii_buf[8], float4(0.0));
float curve_len[2] = {0.0f, 0.0f};
Vector<float> interp_time{0.0f};
interp_time.resize(8);
interp_time[0] = 0.0f;
for (int i : IndexRange(1, 4)) {
curve_len[0] += math::distance(interp_pos[i], interp_pos[i - 1]);
interp_time[i] = curve_len[0];
}
for (int i : IndexRange(1, 4)) {
interp_time[i] /= curve_len[0];
}
interp_time[5] = 0.0f;
for (int i : IndexRange(6, 2)) {
curve_len[1] += math::distance(interp_pos[i], interp_pos[i - 1]);
interp_time[i] = curve_len[1];
}
for (int i : IndexRange(6, 2)) {
interp_time[i] /= curve_len[1];
}
EXPECT_FLOAT_EQ(curves_length_buf[0], curve_len[0]);
EXPECT_FLOAT_EQ(curves_length_buf[1], curve_len[1]);
EXPECT_FLOAT_EQ(evaluated_time_buf[0], interp_time[0]);
EXPECT_FLOAT_EQ(evaluated_time_buf[1], interp_time[1]);
EXPECT_FLOAT_EQ(evaluated_time_buf[2], interp_time[2]);
EXPECT_FLOAT_EQ(evaluated_time_buf[3], interp_time[3]);
EXPECT_FLOAT_EQ(evaluated_time_buf[4], interp_time[4]);
EXPECT_FLOAT_EQ(evaluated_time_buf[5], interp_time[5]);
EXPECT_FLOAT_EQ(evaluated_time_buf[6], interp_time[6]);
EXPECT_FLOAT_EQ(evaluated_time_buf[7], interp_time[7]);
/* Ensure the rest of the buffer is untouched. */
EXPECT_EQ(evaluated_time_buf[8], 0.0f);
}
const bke::curves::nurbs::BasisCache basis_cache_c0 = {
{0.1f, 0.2f, 0.4f, 0.5f, 0.6f, 0.7f, 0.8f, 0.9f, 1.0f, 2.0f, 3.0f, 4.0f, 5.0f, 6.0f, 7.0f},
{0, 0, 0, 0, 0},
false,
};
const bke::curves::nurbs::BasisCache basis_cache_c1 = {
{0.6f, 0.7f, 0.8f, 0.9f, 1.0f, 2.0f},
{0, 0, 0},
false,
};
Vector<int> basis_cache_offset;
Vector<uint32_t> basis_cache_packed;
{
basis_cache_offset.append(basis_cache_c0.invalid ? -1 : basis_cache_packed.size());
basis_cache_packed.extend(
Span{reinterpret_cast<const uint32_t *>(basis_cache_c0.start_indices.data()),
basis_cache_c0.start_indices.size()});
basis_cache_packed.extend(
Span{reinterpret_cast<const uint32_t *>(basis_cache_c0.weights.data()),
basis_cache_c0.weights.size()});
basis_cache_offset.append(basis_cache_c1.invalid ? -1 : basis_cache_packed.size());
basis_cache_packed.extend(
Span{reinterpret_cast<const uint32_t *>(basis_cache_c1.start_indices.data()),
basis_cache_c1.start_indices.size()});
basis_cache_packed.extend(
Span{reinterpret_cast<const uint32_t *>(basis_cache_c1.weights.data()),
basis_cache_c1.weights.size()});
}
/* Raw data. Shader reinterpret as float or int. */
gpu::VertBuf *basis_cache_buf = GPU_vertbuf_create_with_format_ex(
IntBuf::format(), GPU_USAGE_FLAG_BUFFER_TEXTURE_ONLY);
basis_cache_buf->allocate(basis_cache_packed.size());
basis_cache_buf->data<uint>().copy_from(basis_cache_packed);
gpu::VertBuf *basis_cache_offset_buf = GPU_vertbuf_create_with_format_ex(
IntBuf::format(), GPU_USAGE_FLAG_BUFFER_TEXTURE_ONLY);
basis_cache_offset_buf->allocate(basis_cache_offset.size());
basis_cache_offset_buf->data<int>().copy_from(basis_cache_offset);
const Vector<int8_t> curves_order = {3, 2, /* Padding. */ 0, 0};
gpu::VertBuf *curves_order_buf = GPU_vertbuf_create_with_format_ex(
IntBuf::format(), GPU_USAGE_FLAG_BUFFER_TEXTURE_ONLY);
curves_order_buf->allocate(curves_order.size() / 4);
curves_order_buf->data<int>().copy_from(
Span<int>(reinterpret_cast<const int *>(curves_order.data()), curves_order.size() / 4));
const Vector<float> control_weights = {1.0f, 2.0f, 3.0f, 4.0f, 5.0f};
gpu::VertBuf *control_weights_buf = GPU_vertbuf_create_with_format(Radius::format());
control_weights_buf->allocate(control_weights.size());
control_weights_buf->data<float>().copy_from(control_weights);
gpu::VertBuf *curves_type_nurbs_buf = GPU_vertbuf_create_with_format(IntBuf::format());
curves_type_nurbs_buf->allocate(1);
curves_type_nurbs_buf->data<char>().copy_from({CURVE_TYPE_NURBS, CURVE_TYPE_NURBS, 0, 0});
{
StorageArrayBuffer<float4, 512> evaluated_positions_radii_buf;
StorageArrayBuffer<float, 512> evaluated_time_buf;
StorageArrayBuffer<float, 512> curves_length_buf;
evaluated_positions_radii_buf.clear_to_zero();
evaluated_time_buf.clear_to_zero();
curves_length_buf.clear_to_zero();
PassSimple pass("Curves Interpolation Nurbs");
pass.init();
pass.specialize_constant(sh, "evaluated_type", int(CURVE_TYPE_NURBS));
pass.shader_set(sh);
pass.bind_ssbo(POINTS_BY_CURVES_SLOT, points_by_curve_buf);
pass.bind_ssbo(CURVE_TYPE_SLOT, curves_type_nurbs_buf);
pass.bind_ssbo(CURVE_RESOLUTION_SLOT, curves_resolution_buf);
pass.bind_ssbo(EVALUATED_POINT_SLOT, evaluated_points_by_curve_buf);
pass.bind_ssbo(POINT_POSITIONS_SLOT, positions_buf);
pass.bind_ssbo(POINT_RADII_SLOT, radii_buf);
pass.bind_ssbo(EVALUATED_POS_RAD_SLOT, evaluated_positions_radii_buf);
pass.push_constant("use_cyclic", false);
pass.bind_ssbo(CURVE_CYCLIC_SLOT, evaluated_points_by_curve_buf); /* Dummy, not used. */
pass.bind_ssbo(CURVES_ORDER_SLOT, curves_order_buf);
pass.bind_ssbo(BASIS_CACHE_SLOT, basis_cache_buf);
pass.bind_ssbo(CONTROL_WEIGHTS_SLOT, control_weights_buf);
pass.bind_ssbo(BASIS_CACHE_OFFSET_SLOT, basis_cache_offset_buf);
pass.push_constant("curves_start", 0);
pass.push_constant("curves_count", 2);
pass.push_constant("use_point_weight", true);
pass.push_constant("transform", float4x4::identity());
pass.dispatch(1);
pass.barrier(GPU_BARRIER_SHADER_STORAGE);
pass.shader_set(sh_length);
pass.bind_ssbo(EVALUATED_POINT_SLOT, evaluated_points_by_curve_buf);
pass.bind_ssbo(EVALUATED_POS_RAD_SLOT, evaluated_positions_radii_buf);
pass.bind_ssbo(EVALUATED_TIME_SLOT, evaluated_time_buf);
pass.bind_ssbo(CURVES_LENGTH_SLOT, curves_length_buf);
pass.push_constant("curves_start", 0);
pass.push_constant("curves_count", 2);
pass.push_constant("use_cyclic", false);
pass.dispatch(1);
pass.barrier(GPU_BARRIER_BUFFER_UPDATE);
manager.submit(pass);
evaluated_positions_radii_buf.read();
evaluated_time_buf.read();
curves_length_buf.read();
Vector<float3> interp_pos;
interp_pos.resize(8);
Vector<float> interp_rad;
interp_rad.resize(8);
{
const int curve_index = 0;
const IndexRange points(0, 3);
const IndexRange evaluated_points(0, 5);
bke::curves::nurbs::interpolate_to_evaluated(
basis_cache_c0,
curves_order[curve_index],
control_weights.as_span().slice(points),
positions.as_span().slice(points),
interp_pos.as_mutable_span().slice(evaluated_points));
bke::curves::nurbs::interpolate_to_evaluated(
basis_cache_c0,
curves_order[curve_index],
control_weights.as_span().slice(points),
points_radius.as_span().slice(points),
interp_rad.as_mutable_span().slice(evaluated_points));
}
{
const int curve_index = 1;
const IndexRange points(3, 2);
const IndexRange evaluated_points(5, 3);
bke::curves::nurbs::interpolate_to_evaluated(
basis_cache_c1,
curves_order[curve_index],
control_weights.as_span().slice(points),
positions.as_span().slice(points),
interp_pos.as_mutable_span().slice(evaluated_points));
bke::curves::nurbs::interpolate_to_evaluated(
basis_cache_c1,
curves_order[curve_index],
control_weights.as_span().slice(points),
points_radius.as_span().slice(points),
interp_rad.as_mutable_span().slice(evaluated_points));
}
EXPECT_NEAR(evaluated_positions_radii_buf[0].x, interp_pos[0].x, 0.000001f);
EXPECT_NEAR(evaluated_positions_radii_buf[1].x, interp_pos[1].x, 0.000001f);
EXPECT_NEAR(evaluated_positions_radii_buf[2].x, interp_pos[2].x, 0.000001f);
EXPECT_NEAR(evaluated_positions_radii_buf[3].x, interp_pos[3].x, 0.000001f);
EXPECT_NEAR(evaluated_positions_radii_buf[4].x, interp_pos[4].x, 0.000001f);
EXPECT_NEAR(evaluated_positions_radii_buf[5].x, interp_pos[5].x, 0.000001f);
EXPECT_NEAR(evaluated_positions_radii_buf[6].x, interp_pos[6].x, 0.000001f);
EXPECT_NEAR(evaluated_positions_radii_buf[7].x, interp_pos[7].x, 0.000001f);
EXPECT_NEAR(evaluated_positions_radii_buf[0].w, interp_rad[0], 0.000001f);
EXPECT_NEAR(evaluated_positions_radii_buf[1].w, interp_rad[1], 0.000001f);
EXPECT_NEAR(evaluated_positions_radii_buf[2].w, interp_rad[2], 0.000001f);
EXPECT_NEAR(evaluated_positions_radii_buf[3].w, interp_rad[3], 0.000001f);
EXPECT_NEAR(evaluated_positions_radii_buf[4].w, interp_rad[4], 0.000001f);
EXPECT_NEAR(evaluated_positions_radii_buf[5].w, interp_rad[5], 0.000001f);
EXPECT_NEAR(evaluated_positions_radii_buf[6].w, interp_rad[6], 0.000001f);
EXPECT_NEAR(evaluated_positions_radii_buf[7].w, interp_rad[7], 0.000001f);
/* Ensure the rest of the buffer is untouched. */
EXPECT_EQ(evaluated_positions_radii_buf[8], float4(0.0));
float curve_len[2] = {0.0f, 0.0f};
Vector<float> interp_time{0.0f};
interp_time.resize(8);
interp_time[0] = 0.0f;
for (int i : IndexRange(1, 4)) {
curve_len[0] += math::distance(interp_pos[i], interp_pos[i - 1]);
interp_time[i] = curve_len[0];
}
for (int i : IndexRange(1, 4)) {
interp_time[i] /= curve_len[0];
}
interp_time[5] = 0.0f;
for (int i : IndexRange(6, 2)) {
curve_len[1] += math::distance(interp_pos[i], interp_pos[i - 1]);
interp_time[i] = curve_len[1];
}
for (int i : IndexRange(6, 2)) {
interp_time[i] /= curve_len[1];
}
EXPECT_NEAR(curves_length_buf[0], curve_len[0], 0.000001f);
EXPECT_NEAR(curves_length_buf[1], curve_len[1], 0.000001f);
EXPECT_EQ(evaluated_time_buf[0], interp_time[0]);
EXPECT_NEAR(evaluated_time_buf[1], interp_time[1], 0.000001f);
EXPECT_NEAR(evaluated_time_buf[2], interp_time[2], 0.000001f);
EXPECT_NEAR(evaluated_time_buf[3], interp_time[3], 0.000001f);
EXPECT_NEAR(evaluated_time_buf[4], interp_time[4], 0.000001f);
EXPECT_EQ(evaluated_time_buf[5], interp_time[5]);
EXPECT_NEAR(evaluated_time_buf[6], interp_time[6], 0.000001f);
EXPECT_NEAR(evaluated_time_buf[7], interp_time[7], 0.000001f);
/* Ensure the rest of the buffer is untouched. */
EXPECT_EQ(evaluated_time_buf[8], 0.0f);
}
GPU_shader_unbind();
GPU_SHADER_FREE_SAFE(sh);
GPU_SHADER_FREE_SAFE(sh_length);
GPU_VERTBUF_DISCARD_SAFE(points_by_curve_buf);
GPU_VERTBUF_DISCARD_SAFE(curves_type_buf);
GPU_VERTBUF_DISCARD_SAFE(curves_type_bezier_buf);
GPU_VERTBUF_DISCARD_SAFE(curves_type_nurbs_buf);
GPU_VERTBUF_DISCARD_SAFE(curves_resolution_buf);
GPU_VERTBUF_DISCARD_SAFE(evaluated_points_by_curve_buf);
GPU_VERTBUF_DISCARD_SAFE(positions_buf);
GPU_VERTBUF_DISCARD_SAFE(radii_buf);
GPU_VERTBUF_DISCARD_SAFE(handles_positions_left_buf);
GPU_VERTBUF_DISCARD_SAFE(handles_positions_right_buf);
GPU_VERTBUF_DISCARD_SAFE(bezier_offsets_buf);
GPU_VERTBUF_DISCARD_SAFE(basis_cache_buf);
GPU_VERTBUF_DISCARD_SAFE(basis_cache_offset_buf);
GPU_VERTBUF_DISCARD_SAFE(curves_order_buf);
GPU_VERTBUF_DISCARD_SAFE(control_weights_buf);
}
DRAW_TEST(draw_curves_interpolate_position)
static void test_draw_curves_interpolate_attributes()
{
Manager manager;
const int curve_resolution = 2;
const Vector<int> curves_to_point = {0, 3, 5, 7};
const Vector<int> evaluated_offsets = {0, 5, 8, 11};
struct IntBuf {
int data;
GPU_VERTEX_FORMAT_FUNC(IntBuf, data);
};
gpu::VertBuf *points_by_curve_buf = GPU_vertbuf_create_with_format(IntBuf::format());
points_by_curve_buf->allocate(curves_to_point.size());
points_by_curve_buf->data<int>().copy_from(curves_to_point);
gpu::VertBuf *curves_type_buf = GPU_vertbuf_create_with_format(IntBuf::format());
curves_type_buf->allocate(1);
curves_type_buf->data<char>().copy_from(
{CURVE_TYPE_NURBS, CURVE_TYPE_BEZIER, CURVE_TYPE_CATMULL_ROM, 0});
gpu::VertBuf *curves_resolution_buf = GPU_vertbuf_create_with_format(IntBuf::format());
curves_resolution_buf->allocate(3);
curves_resolution_buf->data<int>().copy_from(
{curve_resolution, curve_resolution, curve_resolution});
gpu::VertBuf *evaluated_points_by_curve_buf = GPU_vertbuf_create_with_format(IntBuf::format());
evaluated_points_by_curve_buf->allocate(evaluated_offsets.size());
evaluated_points_by_curve_buf->data<int>().copy_from(evaluated_offsets);
/* Attributes. */
const Vector<float4> attr_float4 = {float4(1.0f, 0.5f, 0.0f, 0.5f),
float4(0.5f, 0.0f, 0.0f, 4.0f),
float4(0.0f, 0.0f, 2.0f, 4.0f),
float4(2.0f, 3.0f, 4.0f, 7.0f),
float4(3.0f, 4.0f, 3.0f, 4.0f),
float4(2.0f, 2.0f, 3.0f, 4.0f),
float4(4.0f, 5.0f, 6.0f, 7.0f)};
const Vector<float3> attr_float3 =
attr_float4.as_span().cast<float>().take_front(attr_float4.size() * 3).cast<float3>();
const Vector<float2> attr_float2 =
attr_float4.as_span().cast<float>().take_front(attr_float4.size() * 2).cast<float2>();
const Vector<float> attr_float = attr_float4.as_span().cast<float>().take_front(
attr_float4.size());
struct Float4 {
float4 value;
GPU_VERTEX_FORMAT_FUNC(Float4, value);
};
gpu::VertBuf *attribute_float4_buf = GPU_vertbuf_create_with_format(Float4::format());
attribute_float4_buf->allocate(attr_float4.size());
attribute_float4_buf->data<float4>().copy_from(attr_float4);
struct Float3 {
float3 value;
GPU_VERTEX_FORMAT_FUNC(Float3, value);
};
gpu::VertBuf *attribute_float3_buf = GPU_vertbuf_create_with_format(Float3::format());
attribute_float3_buf->allocate(attr_float4.size());
attribute_float3_buf->data<float3>().copy_from(attr_float3);
struct Float2 {
float2 value;
GPU_VERTEX_FORMAT_FUNC(Float2, value);
};
gpu::VertBuf *attribute_float2_buf = GPU_vertbuf_create_with_format(Float2::format());
attribute_float2_buf->allocate(attr_float4.size());
attribute_float2_buf->data<float2>().copy_from(attr_float2);
struct Float {
float value;
GPU_VERTEX_FORMAT_FUNC(Float, value);
};
gpu::VertBuf *attribute_float_buf = GPU_vertbuf_create_with_format(Float::format());
attribute_float_buf->allocate(attr_float4.size());
attribute_float_buf->data<float>().copy_from(attr_float);
/* Nurbs. */
const bke::curves::nurbs::BasisCache basis_cache_c0 = {
{0.1f, 0.2f, 0.4f, 0.5f, 0.6f, 0.7f, 0.8f, 0.9f, 1.0f, 2.0f, 3.0f, 4.0f, 5.0f, 6.0f, 7.0f},
{0, 0, 0, 0, 0},
false,
};
Vector<int> basis_cache_offset;
Vector<uint32_t> basis_cache_packed;
{
basis_cache_offset.append(basis_cache_c0.invalid ? -1 : basis_cache_packed.size());
basis_cache_packed.extend(
Span{reinterpret_cast<const uint32_t *>(basis_cache_c0.start_indices.data()),
basis_cache_c0.start_indices.size()});
basis_cache_packed.extend(
Span{reinterpret_cast<const uint32_t *>(basis_cache_c0.weights.data()),
basis_cache_c0.weights.size()});
basis_cache_offset.append(-1);
basis_cache_offset.append(-1);
}
/* Raw data. Shader reinterpret as float or int. */
gpu::VertBuf *basis_cache_buf = GPU_vertbuf_create_with_format(IntBuf::format());
basis_cache_buf->allocate(basis_cache_packed.size());
basis_cache_buf->data<uint>().copy_from(basis_cache_packed);
gpu::VertBuf *basis_cache_offset_buf = GPU_vertbuf_create_with_format(IntBuf::format());
basis_cache_offset_buf->allocate(basis_cache_offset.size());
basis_cache_offset_buf->data<int>().copy_from(basis_cache_offset);
const Vector<int8_t> curves_order = {3, 0, 0, /* Padding. */ 0};
gpu::VertBuf *curves_order_buf = GPU_vertbuf_create_with_format(IntBuf::format());
curves_order_buf->allocate(curves_order.size() / 4);
curves_order_buf->data<int>().copy_from(
Span<int>(reinterpret_cast<const int *>(curves_order.data()), curves_order.size() / 4));
const Vector<float> control_weights = {1.0f, 2.0f, 3.0f, 4.0f, 5.0f};
gpu::VertBuf *control_weights_buf = GPU_vertbuf_create_with_format(Float::format());
control_weights_buf->allocate(control_weights.size());
control_weights_buf->data<float>().copy_from(control_weights);
/* Bezier */
const Vector<float3> handle_pos_left = {
float3{0.0f}, float3{1.0f}, float3{-1.0f}, float3{1.0f}, float3{4.0f}};
const Vector<float3> handle_pos_right = {
float3{0.0f}, float3{-1.0f}, float3{1.0f}, float3{-1.0f}, float3{0.0f}};
gpu::VertBuf *handles_positions_left_buf = GPU_vertbuf_create_with_format(Float3::format());
handles_positions_left_buf->allocate(handle_pos_left.size());
handles_positions_left_buf->data<float3>().copy_from(handle_pos_left);
gpu::VertBuf *handles_positions_right_buf = GPU_vertbuf_create_with_format(Float3::format());
handles_positions_right_buf->allocate(handle_pos_right.size());
handles_positions_right_buf->data<float3>().copy_from(handle_pos_right);
const Vector<int> bezier_offsets = {0, 2, 4, 5, 0, 2, 3};
gpu::VertBuf *bezier_offsets_buf = GPU_vertbuf_create_with_format(IntBuf::format());
bezier_offsets_buf->allocate(bezier_offsets.size());
bezier_offsets_buf->data<int>().copy_from(bezier_offsets);
auto dispatch =
[&](const char *attr_type, gpu::VertBuf *attr_buf, gpu::StorageBuf *evaluated_attr_buf) {
std::string pass_name = std::string("Curves ") + attr_type + " Interpolation";
std::string sh_name = std::string("draw_curves_interpolate_") + attr_type + "_attribute";
/* Make sure all references to the strings are deleted before the strings themselves. */
{
gpu::Shader *sh = GPU_shader_create_from_info_name(sh_name.c_str());
PassSimple pass(pass_name.c_str());
pass.init();
pass.specialize_constant(sh, "evaluated_type", int(CURVE_TYPE_CATMULL_ROM));
pass.shader_set(sh);
pass.bind_ssbo(POINTS_BY_CURVES_SLOT, points_by_curve_buf);
pass.bind_ssbo(CURVE_TYPE_SLOT, curves_type_buf);
pass.bind_ssbo(CURVE_CYCLIC_SLOT, curves_type_buf); /* Dummy, not used */
pass.bind_ssbo(CURVE_RESOLUTION_SLOT, curves_resolution_buf);
pass.bind_ssbo(EVALUATED_POINT_SLOT, evaluated_points_by_curve_buf);
pass.bind_ssbo(POINT_ATTR_SLOT, attr_buf);
pass.bind_ssbo(EVALUATED_ATTR_SLOT, evaluated_attr_buf);
/* Dummy, not used for Catmull-Rom. */
pass.bind_ssbo(HANDLES_POS_LEFT_SLOT, evaluated_points_by_curve_buf);
pass.bind_ssbo(HANDLES_POS_RIGHT_SLOT, evaluated_points_by_curve_buf);
pass.bind_ssbo(BEZIER_OFFSETS_SLOT, evaluated_points_by_curve_buf);
pass.push_constant("use_cyclic", false);
pass.push_constant("curves_start", 0);
pass.push_constant("curves_count", 3);
pass.dispatch(1);
pass.specialize_constant(sh, "evaluated_type", int(CURVE_TYPE_BEZIER));
pass.shader_set(sh);
pass.bind_ssbo(HANDLES_POS_LEFT_SLOT, handles_positions_left_buf);
pass.bind_ssbo(HANDLES_POS_RIGHT_SLOT, handles_positions_right_buf);
pass.bind_ssbo(BEZIER_OFFSETS_SLOT, bezier_offsets_buf);
pass.push_constant("use_cyclic", false);
pass.push_constant("curves_start", 0);
pass.push_constant("curves_count", 3);
pass.dispatch(1);
pass.specialize_constant(sh, "evaluated_type", int(CURVE_TYPE_NURBS));
pass.shader_set(sh);
pass.bind_ssbo(CURVES_ORDER_SLOT, curves_order_buf);
pass.bind_ssbo(BASIS_CACHE_SLOT, basis_cache_buf);
pass.bind_ssbo(CONTROL_WEIGHTS_SLOT, control_weights_buf);
pass.bind_ssbo(BASIS_CACHE_OFFSET_SLOT, basis_cache_offset_buf);
pass.push_constant("use_cyclic", false);
pass.push_constant("curves_start", 0);
pass.push_constant("curves_count", 3);
pass.push_constant("use_point_weight", true);
pass.dispatch(1);
pass.barrier(GPU_BARRIER_BUFFER_UPDATE);
manager.submit(pass);
GPU_shader_unbind();
GPU_SHADER_FREE_SAFE(sh);
}
};
{
StorageArrayBuffer<float4, 512> evaluated_float4_buf;
evaluated_float4_buf.clear_to_zero();
dispatch("float4", attribute_float4_buf, evaluated_float4_buf);
evaluated_float4_buf.read();
Vector<float4> interp_data;
interp_data.resize(11);
OffsetIndices<int> curves_to_point_indices(curves_to_point.as_span());
OffsetIndices<int> curves_to_eval_indices(evaluated_offsets.as_span());
Span<ColorGeometry4f> in_attr = attr_float4.as_span().cast<ColorGeometry4f>();
MutableSpan<ColorGeometry4f> out_attr = interp_data.as_mutable_span().cast<ColorGeometry4f>();
{
const int curve_index = 0;
const IndexRange points = curves_to_point_indices[curve_index];
const IndexRange evaluated_points = curves_to_eval_indices[curve_index];
bke::curves::nurbs::interpolate_to_evaluated(basis_cache_c0,
curves_order[curve_index],
control_weights.as_span().slice(points),
in_attr.slice(points),
out_attr.slice(evaluated_points));
}
{
const int curve_index = 1;
const IndexRange points = curves_to_point_indices[curve_index];
const IndexRange evaluated_points = curves_to_eval_indices[curve_index];
const IndexRange offsets = bke::curves::per_curve_point_offsets_range(points, curve_index);
bke::curves::bezier::interpolate_to_evaluated(in_attr.slice(points),
bezier_offsets.as_span().slice(offsets),
out_attr.slice(evaluated_points));
}
{
const int curve_index = 2;
const IndexRange points = curves_to_point_indices[curve_index];
const IndexRange evaluated_points = curves_to_eval_indices[curve_index];
bke::curves::catmull_rom::interpolate_to_evaluated(
in_attr.slice(points), false, curve_resolution, out_attr.slice(evaluated_points));
}
EXPECT_EQ(evaluated_float4_buf[0], float4(interp_data[0]));
EXPECT_EQ(evaluated_float4_buf[1], float4(interp_data[1]));
EXPECT_EQ(evaluated_float4_buf[2], float4(interp_data[2]));
EXPECT_EQ(evaluated_float4_buf[3], float4(interp_data[3]));
EXPECT_EQ(evaluated_float4_buf[4], float4(interp_data[4]));
EXPECT_EQ(evaluated_float4_buf[5], float4(interp_data[5]));
EXPECT_EQ(evaluated_float4_buf[6], float4(interp_data[6]));
EXPECT_EQ(evaluated_float4_buf[7], float4(interp_data[7]));
EXPECT_EQ(evaluated_float4_buf[8], float4(interp_data[8]));
EXPECT_EQ(evaluated_float4_buf[9], float4(interp_data[9]));
EXPECT_EQ(evaluated_float4_buf[10], float4(interp_data[10]));
/* Ensure the rest of the buffer is untouched. */
EXPECT_EQ(evaluated_float4_buf[11], float4(0.0));
}
{
StorageArrayBuffer<float3, 512> evaluated_float3_buf;
evaluated_float3_buf.clear_to_zero();
dispatch("float3", attribute_float3_buf, evaluated_float3_buf);
evaluated_float3_buf.read();
Vector<float3> interp_data;
interp_data.resize(11);
OffsetIndices<int> curves_to_point_indices(curves_to_point.as_span());
OffsetIndices<int> curves_to_eval_indices(evaluated_offsets.as_span());
Span<float3> in_attr = attr_float3.as_span();
MutableSpan<float3> out_attr = interp_data.as_mutable_span();
{
const int curve_index = 0;
const IndexRange points = curves_to_point_indices[curve_index];
const IndexRange evaluated_points = curves_to_eval_indices[curve_index];
bke::curves::nurbs::interpolate_to_evaluated(basis_cache_c0,
curves_order[curve_index],
control_weights.as_span().slice(points),
in_attr.slice(points),
out_attr.slice(evaluated_points));
}
{
const int curve_index = 1;
const IndexRange points = curves_to_point_indices[curve_index];
const IndexRange evaluated_points = curves_to_eval_indices[curve_index];
const IndexRange offsets = bke::curves::per_curve_point_offsets_range(points, curve_index);
bke::curves::bezier::interpolate_to_evaluated(in_attr.slice(points),
bezier_offsets.as_span().slice(offsets),
out_attr.slice(evaluated_points));
}
{
const int curve_index = 2;
const IndexRange points = curves_to_point_indices[curve_index];
const IndexRange evaluated_points = curves_to_eval_indices[curve_index];
bke::curves::catmull_rom::interpolate_to_evaluated(
in_attr.slice(points), false, curve_resolution, out_attr.slice(evaluated_points));
}
EXPECT_EQ(evaluated_float3_buf[0], float3(interp_data[0]));
EXPECT_EQ(evaluated_float3_buf[1], float3(interp_data[1]));
EXPECT_EQ(evaluated_float3_buf[2], float3(interp_data[2]));
EXPECT_EQ(evaluated_float3_buf[3], float3(interp_data[3]));
EXPECT_EQ(evaluated_float3_buf[4], float3(interp_data[4]));
EXPECT_EQ(evaluated_float3_buf[5], float3(interp_data[5]));
EXPECT_EQ(evaluated_float3_buf[6], float3(interp_data[6]));
EXPECT_EQ(evaluated_float3_buf[7], float3(interp_data[7]));
EXPECT_EQ(evaluated_float3_buf[8], float3(interp_data[8]));
EXPECT_EQ(evaluated_float3_buf[9], float3(interp_data[9]));
EXPECT_EQ(evaluated_float3_buf[10], float3(interp_data[10]));
/* Ensure the rest of the buffer is untouched. */
EXPECT_EQ(evaluated_float3_buf[11], float3(0.0));
}
{
StorageArrayBuffer<float2, 512> evaluated_float2_buf;
evaluated_float2_buf.clear_to_zero();
dispatch("float2", attribute_float2_buf, evaluated_float2_buf);
evaluated_float2_buf.read();
Vector<float2> interp_data;
interp_data.resize(11);
OffsetIndices<int> curves_to_point_indices(curves_to_point.as_span());
OffsetIndices<int> curves_to_eval_indices(evaluated_offsets.as_span());
Span<float2> in_attr = attr_float2.as_span();
MutableSpan<float2> out_attr = interp_data.as_mutable_span();
{
const int curve_index = 0;
const IndexRange points = curves_to_point_indices[curve_index];
const IndexRange evaluated_points = curves_to_eval_indices[curve_index];
bke::curves::nurbs::interpolate_to_evaluated(basis_cache_c0,
curves_order[curve_index],
control_weights.as_span().slice(points),
in_attr.slice(points),
out_attr.slice(evaluated_points));
}
{
const int curve_index = 1;
const IndexRange points = curves_to_point_indices[curve_index];
const IndexRange evaluated_points = curves_to_eval_indices[curve_index];
const IndexRange offsets = bke::curves::per_curve_point_offsets_range(points, curve_index);
bke::curves::bezier::interpolate_to_evaluated(in_attr.slice(points),
bezier_offsets.as_span().slice(offsets),
out_attr.slice(evaluated_points));
}
{
const int curve_index = 2;
const IndexRange points = curves_to_point_indices[curve_index];
const IndexRange evaluated_points = curves_to_eval_indices[curve_index];
bke::curves::catmull_rom::interpolate_to_evaluated(
in_attr.slice(points), false, curve_resolution, out_attr.slice(evaluated_points));
}
EXPECT_EQ(evaluated_float2_buf[0], float2(interp_data[0]));
EXPECT_EQ(evaluated_float2_buf[1], float2(interp_data[1]));
EXPECT_EQ(evaluated_float2_buf[2], float2(interp_data[2]));
EXPECT_EQ(evaluated_float2_buf[3], float2(interp_data[3]));
EXPECT_EQ(evaluated_float2_buf[4], float2(interp_data[4]));
EXPECT_EQ(evaluated_float2_buf[5], float2(interp_data[5]));
EXPECT_EQ(evaluated_float2_buf[6], float2(interp_data[6]));
EXPECT_EQ(evaluated_float2_buf[7], float2(interp_data[7]));
EXPECT_EQ(evaluated_float2_buf[8], float2(interp_data[8]));
EXPECT_EQ(evaluated_float2_buf[9], float2(interp_data[9]));
EXPECT_EQ(evaluated_float2_buf[10], float2(interp_data[10]));
/* Ensure the rest of the buffer is untouched. */
EXPECT_EQ(evaluated_float2_buf[11], float2(0.0));
}
{
StorageArrayBuffer<float, 512> evaluated_float_buf;
evaluated_float_buf.clear_to_zero();
dispatch("float", attribute_float_buf, evaluated_float_buf);
evaluated_float_buf.read();
Vector<float> interp_data;
interp_data.resize(11);
OffsetIndices<int> curves_to_point_indices(curves_to_point.as_span());
OffsetIndices<int> curves_to_eval_indices(evaluated_offsets.as_span());
Span<float> in_attr = attr_float.as_span();
MutableSpan<float> out_attr = interp_data.as_mutable_span();
{
const int curve_index = 0;
const IndexRange points = curves_to_point_indices[curve_index];
const IndexRange evaluated_points = curves_to_eval_indices[curve_index];
bke::curves::nurbs::interpolate_to_evaluated(basis_cache_c0,
curves_order[curve_index],
control_weights.as_span().slice(points),
in_attr.slice(points),
out_attr.slice(evaluated_points));
}
{
const int curve_index = 1;
const IndexRange points = curves_to_point_indices[curve_index];
const IndexRange evaluated_points = curves_to_eval_indices[curve_index];
const IndexRange offsets = bke::curves::per_curve_point_offsets_range(points, curve_index);
bke::curves::bezier::interpolate_to_evaluated(in_attr.slice(points),
bezier_offsets.as_span().slice(offsets),
out_attr.slice(evaluated_points));
}
{
const int curve_index = 2;
const IndexRange points = curves_to_point_indices[curve_index];
const IndexRange evaluated_points = curves_to_eval_indices[curve_index];
bke::curves::catmull_rom::interpolate_to_evaluated(
in_attr.slice(points), false, curve_resolution, out_attr.slice(evaluated_points));
}
EXPECT_EQ(evaluated_float_buf[0], float(interp_data[0]));
EXPECT_EQ(evaluated_float_buf[1], float(interp_data[1]));
EXPECT_EQ(evaluated_float_buf[2], float(interp_data[2]));
EXPECT_EQ(evaluated_float_buf[3], float(interp_data[3]));
EXPECT_EQ(evaluated_float_buf[4], float(interp_data[4]));
EXPECT_EQ(evaluated_float_buf[5], float(interp_data[5]));
EXPECT_EQ(evaluated_float_buf[6], float(interp_data[6]));
EXPECT_EQ(evaluated_float_buf[7], float(interp_data[7]));
EXPECT_EQ(evaluated_float_buf[8], float(interp_data[8]));
EXPECT_EQ(evaluated_float_buf[9], float(interp_data[9]));
EXPECT_EQ(evaluated_float_buf[10], float(interp_data[10]));
/* Ensure the rest of the buffer is untouched. */
EXPECT_EQ(evaluated_float_buf[11], float(0.0));
}
GPU_VERTBUF_DISCARD_SAFE(points_by_curve_buf);
GPU_VERTBUF_DISCARD_SAFE(curves_type_buf);
GPU_VERTBUF_DISCARD_SAFE(curves_resolution_buf);
GPU_VERTBUF_DISCARD_SAFE(evaluated_points_by_curve_buf);
GPU_VERTBUF_DISCARD_SAFE(handles_positions_left_buf);
GPU_VERTBUF_DISCARD_SAFE(handles_positions_right_buf);
GPU_VERTBUF_DISCARD_SAFE(bezier_offsets_buf);
GPU_VERTBUF_DISCARD_SAFE(basis_cache_buf);
GPU_VERTBUF_DISCARD_SAFE(basis_cache_offset_buf);
GPU_VERTBUF_DISCARD_SAFE(curves_order_buf);
GPU_VERTBUF_DISCARD_SAFE(control_weights_buf);
GPU_VERTBUF_DISCARD_SAFE(attribute_float4_buf);
GPU_VERTBUF_DISCARD_SAFE(attribute_float3_buf);
GPU_VERTBUF_DISCARD_SAFE(attribute_float2_buf);
GPU_VERTBUF_DISCARD_SAFE(attribute_float_buf);
}
DRAW_TEST(draw_curves_interpolate_attributes)
} // namespace blender::draw

View File

@@ -447,9 +447,12 @@ if(WITH_VULKAN_BACKEND)
add_definitions(-DWITH_VULKAN_BACKEND)
endif()
set(GLSL_SRC_RAW
shaders/opengl/glsl_shader_defines.glsl
)
set(GLSL_SRC
GPU_shader_shared.hh
shaders/opengl/glsl_shader_defines.glsl
shaders/gpu_shader_depth_only_frag.glsl
shaders/gpu_shader_uniform_color_frag.glsl
@@ -528,11 +531,13 @@ set(GLSL_SRC
shaders/common/gpu_shader_debug_gradients_lib.glsl
shaders/common/gpu_shader_fullscreen_vert.glsl
shaders/common/gpu_shader_index_load_lib.glsl
shaders/common/gpu_shader_index_range_lib.glsl
shaders/common/gpu_shader_math_base_lib.glsl
shaders/common/gpu_shader_math_fast_lib.glsl
shaders/common/gpu_shader_math_matrix_lib.glsl
shaders/common/gpu_shader_math_rotation_lib.glsl
shaders/common/gpu_shader_math_vector_lib.glsl
shaders/common/gpu_shader_offset_indices_lib.glsl
shaders/common/gpu_shader_print_lib.glsl
shaders/common/gpu_shader_sequencer_lib.glsl
shaders/common/gpu_shader_shared_exponent_lib.glsl
@@ -706,6 +711,10 @@ foreach(GLSL_FILE ${GLSL_SRC})
glsl_to_c(${GLSL_FILE} GLSL_C)
endforeach()
foreach(GLSL_FILE ${GLSL_SRC_RAW})
data_to_c_simple(${GLSL_FILE} GLSL_C)
endforeach()
set(SHADER_C)
list(APPEND SHADER_C ${GLSL_C})
if(WITH_METAL_BACKEND)

View File

@@ -84,7 +84,7 @@ class Batch {
blender::gpu::VertBuf *inst[GPU_BATCH_INST_VBO_MAX_LEN];
/** nullptr if element list not needed */
blender::gpu::IndexBuf *elem;
/** Number of vertices to draw for procedural drawcalls. */
/** Number of vertices to draw for procedural drawcalls. -1 otherwise. */
int32_t procedural_vertices;
/** Bookkeeping. */
eGPUBatchFlag flag;

View File

@@ -227,6 +227,10 @@ struct GPUMaterialAttribute {
* If true, the attribute is the length of hair particles and curves.
*/
bool is_hair_length;
/**
* If true, the attribute is the intercept of hair particles and curves.
*/
bool is_hair_intercept;
};
struct GPUMaterialTexture {
@@ -306,6 +310,7 @@ GPUNodeLink *GPU_attribute_default_color(GPUMaterial *mat);
* Add a GPU attribute that refers to the approximate length of curves/hairs.
*/
GPUNodeLink *GPU_attribute_hair_length(GPUMaterial *mat);
GPUNodeLink *GPU_attribute_hair_intercept(GPUMaterial *mat);
GPUNodeLink *GPU_attribute_with_default(GPUMaterial *mat,
eCustomDataType type,
const char *name,

View File

@@ -10,8 +10,10 @@
#pragma once
#include "BLI_math_base.h"
#include "BLI_span.hh"
#include "BLI_utildefines.h"
#include "BLI_virtual_array.hh"
#include "GPU_common.hh"
#include "GPU_vertex_format.hh"
@@ -54,6 +56,31 @@ enum GPUUsageType {
ENUM_OPERATORS(GPUUsageType, GPU_USAGE_FLAG_BUFFER_TEXTURE_ONLY);
namespace blender::gpu {
class VertBuf;
} // namespace blender::gpu
void GPU_vertbuf_discard(blender::gpu::VertBuf *);
blender::gpu::VertBuf *GPU_vertbuf_calloc();
blender::gpu::VertBuf *GPU_vertbuf_create_with_format_ex(const GPUVertFormat &format,
GPUUsageType usage);
static inline blender::gpu::VertBuf *GPU_vertbuf_create_with_format(const GPUVertFormat &format)
{
return GPU_vertbuf_create_with_format_ex(format, GPU_USAGE_STATIC);
}
namespace blender::gpu {
class VertBufDeleter {
public:
void operator()(VertBuf *vbo)
{
GPU_vertbuf_discard(vbo);
}
};
using VertBufPtr = std::unique_ptr<gpu::VertBuf, gpu::VertBufDeleter>;
/**
* Implementation of Vertex Buffers.
@@ -91,6 +118,48 @@ class VertBuf {
VertBuf();
virtual ~VertBuf();
template<typename T> static VertBufPtr from_size(const int size)
{
BLI_assert(size > 0);
VertBufPtr buf = VertBufPtr(GPU_vertbuf_create_with_format(GenericVertexFormat<T>::format()));
/* GPU formats needs to be aligned to 4 bytes. */
buf->allocate(ceil_to_multiple_u(size * sizeof(T), 4) / sizeof(GenericVertexFormat<T>));
return buf;
}
template<typename T> static VertBufPtr from_span(const Span<T> data)
{
BLI_assert(!data.is_empty());
VertBufPtr buf = VertBufPtr(GPU_vertbuf_create_with_format_ex(
GenericVertexFormat<T>::format(), GPU_USAGE_STATIC | GPU_USAGE_FLAG_BUFFER_TEXTURE_ONLY));
/* GPU formats needs to be aligned to 4 bytes. */
buf->allocate(ceil_to_multiple_u(data.size_in_bytes(), 4) / sizeof(GenericVertexFormat<T>));
buf->data<T>().slice(0, data.size()).copy_from(data);
return buf;
}
template<typename T> static VertBufPtr from_varray(const VArray<T> &array)
{
BLI_assert(!array.is_empty());
VertBufPtr buf = VertBufPtr(GPU_vertbuf_create_with_format_ex(
GenericVertexFormat<T>::format(), GPU_USAGE_STATIC | GPU_USAGE_FLAG_BUFFER_TEXTURE_ONLY));
/* GPU formats needs to be aligned to 4 bytes. */
buf->allocate(ceil_to_multiple_u(array.size() * sizeof(T), 4) /
sizeof(GenericVertexFormat<T>));
array.materialize(buf->data<T>().slice(0, array.size()));
return buf;
}
template<typename T> static VertBufPtr device_only(uint size)
{
BLI_assert(size > 0);
VertBufPtr buf = VertBufPtr(GPU_vertbuf_create_with_format_ex(
GenericVertexFormat<T>::format(),
GPU_USAGE_DEVICE_ONLY | GPU_USAGE_FLAG_BUFFER_TEXTURE_ONLY));
buf->allocate(size);
return buf;
}
void init(const GPUVertFormat &format, GPUUsageType usage);
void clear();
@@ -155,13 +224,6 @@ class VertBuf {
} // namespace blender::gpu
blender::gpu::VertBuf *GPU_vertbuf_calloc();
blender::gpu::VertBuf *GPU_vertbuf_create_with_format_ex(const GPUVertFormat &format,
GPUUsageType usage);
#define GPU_vertbuf_create_with_format(format) \
GPU_vertbuf_create_with_format_ex(format, GPU_USAGE_STATIC)
/**
* (Download and) fill data with the data from the vertex buffer.
* NOTE: caller is responsible to reserve enough memory of the data parameter.
@@ -169,7 +231,6 @@ blender::gpu::VertBuf *GPU_vertbuf_create_with_format_ex(const GPUVertFormat &fo
void GPU_vertbuf_read(const blender::gpu::VertBuf *verts, void *data);
/** Same as discard but does not free. */
void GPU_vertbuf_clear(blender::gpu::VertBuf *verts);
void GPU_vertbuf_discard(blender::gpu::VertBuf *);
/**
* Avoid blender::gpu::VertBuf data-block being free but not its data.
@@ -292,17 +353,3 @@ uint GPU_vertbuf_get_memory_usage();
verts = nullptr; \
} \
} while (0)
namespace blender::gpu {
class VertBufDeleter {
public:
void operator()(VertBuf *vbo)
{
GPU_vertbuf_discard(vbo);
}
};
using VertBufPtr = std::unique_ptr<gpu::VertBuf, gpu::VertBufDeleter>;
} // namespace blender::gpu

View File

@@ -306,6 +306,40 @@ struct GPUVertFormat {
return format; \
}
namespace blender::gpu {
/** Generic vertex format for single attribute buffers. */
template<typename T> struct GenericVertexFormat {
T attr;
GPU_VERTEX_FORMAT_FUNC(GenericVertexFormat, attr);
};
template<> struct GenericVertexFormat<int8_t> {
/* This is a workaround to reinterpret int8_t into padded vertex format to be able to upload it
* on any GPU. The shaders then need to read uint32_t and use shifts and mask to decode in
* individual bytes. */
uint32_t attr;
GPU_VERTEX_FORMAT_FUNC(GenericVertexFormat, attr);
};
template<> struct GenericVertexFormat<uint8_t> {
/* This is a workaround to reinterpret uint8_t into padded vertex format to be able to upload it
* on any GPU. The shaders then need to read uint32_t and use shifts and mask to decode in
* individual bytes. */
uint32_t attr;
GPU_VERTEX_FORMAT_FUNC(GenericVertexFormat, attr);
};
template<> struct GenericVertexFormat<bool> {
/* This is a workaround to reinterpret bool into padded vertex format to be able to upload it
* on any GPU. The shaders then need to read uint32_t and use shifts and mask to decode in
* individual bytes. */
uint32_t attr;
GPU_VERTEX_FORMAT_FUNC(GenericVertexFormat, attr);
};
} // namespace blender::gpu
void GPU_vertformat_clear(GPUVertFormat *);
void GPU_vertformat_copy(GPUVertFormat *dest, const GPUVertFormat &src);
void GPU_vertformat_from_shader(GPUVertFormat *format, const blender::gpu::Shader *shader);

View File

@@ -1481,11 +1481,11 @@ class Preprocessor {
});
}
/* `*this` -> `this` */
/* `*this` -> `this_` */
scope.foreach_match("*T", [&](const std::vector<Token> &tokens) {
fn_parser.replace(tokens[0], tokens[1], "this_");
});
/* `this->` -> `this.` */
/* `this->` -> `this_.` */
scope.foreach_match("TD", [&](const std::vector<Token> &tokens) {
fn_parser.replace(tokens[0], tokens[1], "this_.");
});
@@ -1976,11 +1976,19 @@ class Preprocessor {
return str;
}
if (value.find("(") != string::npos) {
report_error(line_number(match),
char_number(match),
line_str(match),
"Reference definitions cannot contain function calls.");
return str;
if (value.find("specialization_constant_get(") == string::npos &&
value.find("push_constant_get(") == string::npos &&
value.find("interface_get(") == string::npos &&
value.find("attribute_get(") == string::npos &&
value.find("buffer_get(") == string::npos &&
value.find("sampler_get(") == string::npos && value.find("image_get(") == string::npos)
{
report_error(line_number(match),
char_number(match),
line_str(match),
"Reference definitions cannot contain function calls.");
return str;
}
}
if (value.find("[") != string::npos) {
const string index_var = get_content_between_balanced_pair(value, '[', ']');

View File

@@ -113,6 +113,7 @@ enum class ScopeType : char {
Struct = 'S',
Function = 'F',
FunctionArgs = 'f',
FunctionCall = 'c',
Template = 'T',
TemplateArg = 't',
Subscript = 'A',
@@ -849,6 +850,9 @@ inline void ParserData::parse_scopes(report_callback &report_error)
else if (scopes.top().type == ScopeType::Struct) {
enter_scope(ScopeType::Function, tok_id);
}
else if (scopes.top().type == ScopeType::Namespace) {
enter_scope(ScopeType::Function, tok_id);
}
else {
enter_scope(ScopeType::Local, tok_id);
}
@@ -860,6 +864,12 @@ inline void ParserData::parse_scopes(report_callback &report_error)
else if (scopes.top().type == ScopeType::Struct) {
enter_scope(ScopeType::FunctionArgs, tok_id);
}
else if ((scopes.top().type == ScopeType::Function ||
scopes.top().type == ScopeType::Local) &&
(tok_id >= 1 && token_types[tok_id - 1] == Word))
{
enter_scope(ScopeType::FunctionCall, tok_id);
}
else {
enter_scope(ScopeType::Local, tok_id);
}
@@ -1029,6 +1039,14 @@ struct Parser {
matches[8] == Const,
matches[10].scope());
});
foreach_match("m?ww<..>(..)c?{..}", [&](const std::vector<Token> matches) {
callback(matches[0] == Static,
matches[2],
matches[3],
matches[8].scope(),
matches[12] == Const,
matches[14].scope());
});
}
std::string substr_range_inclusive(size_t start, size_t end)

View File

@@ -38,7 +38,7 @@ void GPU_batch_zero(Batch *batch)
batch->flag = eGPUBatchFlag(0);
batch->prim_type = GPUPrimType(0);
batch->shader = nullptr;
batch->procedural_vertices = 0;
batch->procedural_vertices = -1;
}
Batch *GPU_batch_calloc()
@@ -80,7 +80,7 @@ void GPU_batch_init_ex(Batch *batch,
batch->prim_type = primitive_type;
batch->flag = owns_flag | GPU_BATCH_INIT | GPU_BATCH_DIRTY;
batch->shader = nullptr;
batch->procedural_vertices = 0;
batch->procedural_vertices = -1;
}
Batch *GPU_batch_create_procedural(GPUPrimType primitive_type, int32_t vertex_count)
@@ -133,7 +133,7 @@ void GPU_batch_clear(Batch *batch)
}
}
batch->flag = GPU_BATCH_INVALID;
batch->procedural_vertices = 0;
batch->procedural_vertices = -1;
}
void GPU_batch_discard(Batch *batch)
@@ -349,7 +349,12 @@ void GPU_batch_draw_parameter_get(Batch *batch,
int *r_base_index,
int *r_instance_count)
{
if (batch->elem) {
if (batch->procedural_vertices >= 0) {
*r_vertex_count = batch->procedural_vertices;
*r_vertex_first = 0;
*r_base_index = -1;
}
else if (batch->elem) {
*r_vertex_count = batch->elem_()->index_len_get();
*r_vertex_first = batch->elem_()->index_start_get();
*r_base_index = batch->elem_()->index_base_get();

View File

@@ -170,6 +170,8 @@ void GPUCodegen::generate_attribs()
/* Input declaration, loading / assignment to interface and geometry shader passthrough. */
std::stringstream load_ss;
/* Index of the attribute as ordered in graph.attributes. */
int attr_n = 0;
int slot = 15;
LISTBASE_FOREACH (GPUMaterialAttribute *, attr, &graph.attributes) {
if (slot == -1) {
@@ -185,9 +187,10 @@ void GPUCodegen::generate_attribs()
eGPUType input_type, iface_type;
load_ss << "var_attrs." << var_name;
if (attr->is_hair_length) {
if (attr->is_hair_length || attr->is_hair_intercept) {
iface_type = input_type = GPU_FLOAT;
load_ss << " = attr_load_" << input_type << "(" << attr_name << ");\n";
load_ss << " = attr_load_" << input_type << "(domain, " << attr_name << ", " << attr_n
<< ");\n";
}
else {
switch (attr->type) {
@@ -195,18 +198,20 @@ void GPUCodegen::generate_attribs()
/* Need vec4 to detect usage of default attribute. */
input_type = GPU_VEC4;
iface_type = GPU_VEC3;
load_ss << " = attr_load_orco(" << attr_name << ");\n";
load_ss << " = attr_load_orco(domain, " << attr_name << ", " << attr_n << ");\n";
break;
case CD_TANGENT:
iface_type = input_type = GPU_VEC4;
load_ss << " = attr_load_tangent(" << attr_name << ");\n";
load_ss << " = attr_load_tangent(domain, " << attr_name << ", " << attr_n << ");\n";
break;
default:
iface_type = input_type = GPU_VEC4;
load_ss << " = attr_load_" << input_type << "(" << attr_name << ");\n";
load_ss << " = attr_load_" << input_type << "(domain, " << attr_name << ", " << attr_n
<< ");\n";
break;
}
}
attr_n++;
info.vertex_in(slot--, to_type(input_type), attr_name);
iface.smooth(to_type(iface_type), var_name);

View File

@@ -355,6 +355,9 @@ static char attr_prefix_get(const GPUMaterialAttribute *attr)
if (attr->is_hair_length) {
return 'l';
}
if (attr->is_hair_intercept) {
return 'i';
}
switch (attr->type) {
case CD_TANGENT:
return 't';
@@ -388,14 +391,16 @@ static GPUMaterialAttribute *gpu_node_graph_add_attribute(GPUNodeGraph *graph,
eCustomDataType type,
const char *name,
const bool is_default_color,
const bool is_hair_length)
const bool is_hair_length,
const bool is_hair_intercept)
{
/* Find existing attribute. */
int num_attributes = 0;
GPUMaterialAttribute *attr = static_cast<GPUMaterialAttribute *>(graph->attributes.first);
for (; attr; attr = attr->next) {
if (attr->type == type && STREQ(attr->name, name) &&
attr->is_default_color == is_default_color && attr->is_hair_length == is_hair_length)
attr->is_default_color == is_default_color && attr->is_hair_length == is_hair_length &&
attr->is_hair_intercept == is_hair_intercept)
{
break;
}
@@ -407,6 +412,7 @@ static GPUMaterialAttribute *gpu_node_graph_add_attribute(GPUNodeGraph *graph,
attr = MEM_callocN<GPUMaterialAttribute>(__func__);
attr->is_default_color = is_default_color;
attr->is_hair_length = is_hair_length;
attr->is_hair_intercept = is_hair_intercept;
attr->type = type;
STRNCPY(attr->name, name);
attr_input_name(attr);
@@ -530,7 +536,8 @@ static GPUMaterialTexture *gpu_node_graph_add_texture(GPUNodeGraph *graph,
GPUNodeLink *GPU_attribute(GPUMaterial *mat, const eCustomDataType type, const char *name)
{
GPUNodeGraph *graph = gpu_material_node_graph(mat);
GPUMaterialAttribute *attr = gpu_node_graph_add_attribute(graph, type, name, false, false);
GPUMaterialAttribute *attr = gpu_node_graph_add_attribute(
graph, type, name, false, false, false);
if (type == CD_ORCO) {
/* OPTI: orco might be computed from local positions and needs object information. */
@@ -553,7 +560,7 @@ GPUNodeLink *GPU_attribute_default_color(GPUMaterial *mat)
{
GPUNodeGraph *graph = gpu_material_node_graph(mat);
GPUMaterialAttribute *attr = gpu_node_graph_add_attribute(
graph, CD_AUTO_FROM_NAME, "", true, false);
graph, CD_AUTO_FROM_NAME, "", true, false, false);
if (attr == nullptr) {
static const float zero_data[GPU_MAX_CONSTANT_DATA] = {0.0f};
return GPU_constant(zero_data);
@@ -569,7 +576,22 @@ GPUNodeLink *GPU_attribute_hair_length(GPUMaterial *mat)
{
GPUNodeGraph *graph = gpu_material_node_graph(mat);
GPUMaterialAttribute *attr = gpu_node_graph_add_attribute(
graph, CD_AUTO_FROM_NAME, "", false, true);
graph, CD_AUTO_FROM_NAME, "", false, true, false);
if (attr == nullptr) {
static const float zero_data[GPU_MAX_CONSTANT_DATA] = {0.0f};
return GPU_constant(zero_data);
}
GPUNodeLink *link = gpu_node_link_create();
link->link_type = GPU_NODE_LINK_ATTR;
link->attr = attr;
return link;
}
GPUNodeLink *GPU_attribute_hair_intercept(GPUMaterial *mat)
{
GPUNodeGraph *graph = gpu_material_node_graph(mat);
GPUMaterialAttribute *attr = gpu_node_graph_add_attribute(
graph, CD_AUTO_FROM_NAME, "", false, false, true);
if (attr == nullptr) {
static const float zero_data[GPU_MAX_CONSTANT_DATA] = {0.0f};
return GPU_constant(zero_data);

View File

@@ -118,8 +118,8 @@
#include "compositor_z_combine_info.hh"
/* DRW module. */
#include "draw_curves_info.hh"
#include "draw_debug_info.hh"
#include "draw_hair_refine_info.hh"
#include "draw_object_infos_info.hh"
#include "draw_view_info.hh"
#include "gpu_shader_fullscreen_info.hh"

View File

@@ -41,6 +41,12 @@ VertBuf::~VertBuf()
void VertBuf::init(const GPUVertFormat &format, GPUUsageType usage)
{
#if 0 /* Disabled until Grease pencil. Comply to this. */
if (usage & GPU_USAGE_FLAG_BUFFER_TEXTURE_ONLY) {
BLI_assert_msg(format.attr_len == 1,
"Only single attribute format are supported for buffer textures");
}
#endif
/* Strip extended usage flags. */
usage_ = usage & ~GPU_USAGE_FLAG_BUFFER_TEXTURE_ONLY;
#ifndef NDEBUG

View File

@@ -88,3 +88,9 @@ uint4 gpu_attr_decode_uchar4_to_uint4(uint in_data)
/* Assumes _data is declared as an array of uint. */
#define gpu_attr_load_uchar4(_data, _stride_and_offset, _i) \
gpu_attr_decode_uchar4_to_uint4(_data[gpu_attr_load_index(_i, _stride_and_offset)])
/* Assumes _data is declared as an array of uint. */
#define gpu_attr_load_uchar(_data, _i) \
gpu_attr_decode_uchar4_to_uint4( \
_data[gpu_attr_load_index(uint(_i) >> 2u, int2(1, 0))])[uint(_i) & 3u]
/* Assumes _data is declared as an array of uint. */
#define gpu_attr_load_bool(_data, _i) (gpu_attr_load_uchar(_data, _i) != 0u)

View File

@@ -0,0 +1,81 @@
/* SPDX-FileCopyrightText: 2025 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
#pragma once
#include "gpu_glsl_cpp_stubs.hh"
/* WORKAROUND: Workaround include order hell. */
#ifdef GLSL_CPP_STUBS
#elif defined(GPU_SHADER)
# define static
#endif
class IndexRange {
private:
int start_;
int size_;
public:
METAL_CONSTRUCTOR_2(IndexRange, int, start_, int, size_)
static IndexRange from_begin_end(int begin, int end)
{
return IndexRange(begin, end - begin);
}
/**
* Get the first element in the range.
*/
int first() const
{
return this->start_;
}
/**
* Get the first element in the range. The returned value is undefined when the range is empty.
*/
int start() const
{
return this->start_;
}
/**
* Get the nth last element in the range.
*/
int last(int n = 0) const
{
return this->start_ + this->size_ - 1 - n;
}
/**
* Get the amount of numbers in the range.
*/
int size() const
{
return this->size_;
}
/**
* Returns a new range, that contains a sub-interval of the current one.
*/
IndexRange slice(int start, int size) const
{
int new_start = this->start_ + start;
return IndexRange(new_start, size);
}
IndexRange slice(IndexRange range) const
{
return this->slice(range.start(), range.size());
}
/**
* Move the range forward or backward within the larger array. The amount may be negative,
* but its absolute value cannot be greater than the existing start of the range.
*/
IndexRange shift(int n) const
{
return IndexRange(this->start_ + n, this->size_);
}
};

View File

@@ -0,0 +1,33 @@
/* SPDX-FileCopyrightText: 2025 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
#pragma once
#include "gpu_glsl_cpp_stubs.hh"
#include "gpu_shader_index_range_lib.glsl"
/**
* See `OffsetIndices` C++ definition for formal definition.
*
* OffsetIndices cannot be implemented on GPU because of the lack of operator overloading and
* buffer reference in GLSL. So we simply interpret a given integer buffer as a `OffsetIndices`
* buffer and load a specific item as a range.
*/
namespace offset_indices {
#ifdef GLSL_CPP_STUBS
/* Equivalent of `IndexRange OffsetIndices<int>operator[]`.
* Implementation for C++ compilation. */
inline static IndexRange load_range_from_buffer(const int (&buf)[], int i)
{
return IndexRange::from_begin_end(buf[i], buf[i + 1]);
}
#endif
} // namespace offset_indices
/* Shader implementation because of missing buffer reference as argument in GLSL. */
#define offset_indices_load_range_from_buffer(buf_, i_) \
IndexRange::from_begin_end(buf_[i_], buf_[i_ + 1])

View File

@@ -1051,18 +1051,19 @@ void groupMemoryBarrier() {}
#define common common_is_reserved_glsl_keyword_do_not_use
#define partition partition_is_reserved_glsl_keyword_do_not_use
#define active active_is_reserved_glsl_keyword_do_not_use
#define class class_is_reserved_glsl_keyword_do_not_use
// #define class /* Supported. */
#define union union_is_reserved_glsl_keyword_do_not_use
// #define enum /* Supported. */
#define typedef typedef_is_reserved_glsl_keyword_do_not_use
// #define template /* Needed for Stubs. */
#define this this_is_reserved_glsl_keyword_do_not_use
// #define this /* Needed for Stubs. */
#define packed packed_is_reserved_glsl_keyword_do_not_use
#define resource resource_is_reserved_glsl_keyword_do_not_use
#define goto goto_is_reserved_glsl_keyword_do_not_use
// #define inline /* Supported. */
#define noinline noinline_is_reserved_glsl_keyword_do_not_use
#define public public_is_reserved_glsl_keyword_do_not_use
// #define public /* Supported. */
// #define private /* Supported. */
// #define static /* Supported. */
// #define extern /* Needed for Stubs. */
#define external external_is_reserved_glsl_keyword_do_not_use

View File

@@ -33,79 +33,6 @@ float2 calc_barycentric_co(int vertid)
return bary;
}
#ifdef HAIR_SHADER
/* Hairs uv and col attributes are passed by bufferTextures. */
# define DEFINE_ATTR(type, attr) uniform samplerBuffer attr
# define GET_ATTR(type, attr) hair_get_customdata_##type(attr)
# define barycentric_get() hair_get_barycentric()
# define barycentric_resolve(bary) hair_resolve_barycentric(bary)
float3 orco_get(float3 local_pos,
float4x4 modelmatinv,
float4 orco_madd[2],
const samplerBuffer orco_samp)
{
/* TODO: fix ORCO with modifiers. */
float3 orco = (modelmatinv * float4(local_pos, 1.0f)).xyz;
return orco_madd[0].xyz + orco * orco_madd[1].xyz;
}
float hair_len_get(int id, const samplerBuffer len)
{
return texelFetch(len, id).x;
}
float4 tangent_get(const samplerBuffer attr, float3x3 normalmat)
{
/* Unsupported */
return float4(0.0f);
}
#else /* MESH_SHADER */
# define DEFINE_ATTR(type, attr) in type attr
# define GET_ATTR(type, attr) attr
/* Calculated in geom shader later with calc_barycentric_co. */
# define barycentric_get() float2(0)
# define barycentric_resolve(bary) bary
float3 orco_get(float3 local_pos, float4x4 modelmatinv, float4 orco_madd[2], float4 orco)
{
/* If the object does not have any deformation, the orco layer calculation is done on the fly
* using the orco_madd factors.
* We know when there is no orco layer when orco.w is 1.0 because it uses the generic vertex
* attribute (which is [0,0,0,1]). */
if (orco.w == 0.0f) {
return orco.xyz * 0.5f + 0.5f;
}
else {
return orco_madd[0].xyz + local_pos * orco_madd[1].xyz;
}
}
float hair_len_get(int id, const float len)
{
return len;
}
float4 tangent_get(float4 attr, float3x3 normalmat)
{
float4 tangent;
tangent.xyz = normalmat * attr.xyz;
tangent.w = attr.w;
float len_sqr = dot(tangent.xyz, tangent.xyz);
/* Normalize only if vector is not null. */
if (len_sqr > 0.0f) {
tangent.xyz *= inversesqrt(len_sqr);
}
return tangent;
}
#endif
/* Assumes GPU_VEC4 is color data, special case that needs luminance coefficients from OCIO. */
#define float_from_vec4(v, luminance_coefficients) dot(v.rgb, luminance_coefficients)
#define float_from_vec3(v) ((v.r + v.g + v.b) * (1.0f / 3.0f))
@@ -304,12 +231,8 @@ struct GlobalData {
/** Barycentric coordinates. */
packed_float2 barycentric_coords;
packed_float3 barycentric_dists;
/** Hair time along hair length. 0 at base 1 at tip. */
float hair_time;
/** Hair time along width of the hair. */
float hair_time_width;
/** Hair thickness in world space. */
float hair_thickness;
float hair_diameter;
/** Index of the strand for per strand effects. */
int hair_strand_id;
/** Ray properties (approximation). */

View File

@@ -4,18 +4,19 @@
#include "gpu_shader_common_hash.glsl"
void node_hair_info(float hair_length,
void node_hair_info(float hair_intercept,
float hair_length,
out float is_strand,
out float intercept,
out float out_intercept,
out float out_length,
out float thickness,
out float3 normal,
out float random)
{
is_strand = float(g_data.is_strand);
intercept = g_data.hair_time;
thickness = g_data.hair_thickness;
out_intercept = hair_intercept;
out_length = hair_length;
thickness = g_data.hair_diameter;
normal = g_data.curve_N;
/* TODO: could be precomputed per strand instead. */
random = wang_hash_noise(uint(g_data.hair_strand_id));

View File

@@ -164,6 +164,15 @@ RESHAPE(float3x3, mat3x3, mat3x4)
#define _enum_decl(name) constexpr uint
#define _enum_end _enum_dummy;
/* Resource accessor. */
#define specialization_constant_get(create_info, _res) _res
#define push_constant_get(create_info, _res) _res
#define interface_get(create_info, _res) _res
#define attribute_get(create_info, _res) _res
#define buffer_get(create_info, _res) _res
#define sampler_get(create_info, _res) _res
#define image_get(create_info, _res) _res
/* Incompatible keywords. */
#define static
#define inline

View File

@@ -1014,6 +1014,30 @@ uint my_func() {
}
return i;
}
)";
string error;
string output = process_test_string(input, error);
EXPECT_EQ(output, expect);
EXPECT_EQ(error, "");
}
{
/* Guard in template. */
string input = R"(
template<> uint my_func<uint>(uint i) {
return buffer_get(draw_resource_id, resource_id_buf)[i];
}
)";
string expect = R"(
uint my_func_uint_(uint i) {
#if defined(CREATE_INFO_draw_resource_id)
#line 3
return buffer_get(draw_resource_id, resource_id_buf)[i];
#else
#line 3
return uint(0);
#endif
#line 4
}
)";
string error;
string output = process_test_string(input, error);

View File

@@ -985,6 +985,7 @@ typedef enum eQualityOption {
typedef enum eHairType {
SCE_HAIR_SHAPE_STRAND = 0,
SCE_HAIR_SHAPE_STRIP = 1,
SCE_HAIR_SHAPE_CYLINDER = 2,
} eHairType;
/** #RenderData::motion_blur_position */

View File

@@ -6863,6 +6863,7 @@ static void rna_def_scene_render_data(BlenderRNA *brna)
static const EnumPropertyItem hair_shape_type_items[] = {
{SCE_HAIR_SHAPE_STRAND, "STRAND", 0, "Strand", ""},
{SCE_HAIR_SHAPE_STRIP, "STRIP", 0, "Strip", ""},
{SCE_HAIR_SHAPE_CYLINDER, "CYLINDER", 0, "Cylinder", ""},
{0, nullptr, 0, nullptr, nullptr},
};

View File

@@ -9,7 +9,9 @@ namespace blender::nodes::node_shader_hair_info_cc {
static void node_declare(NodeDeclarationBuilder &b)
{
b.add_output<decl::Float>("Is Strand");
#define INTERCEPT_SOCKET_INDEX 1
b.add_output<decl::Float>("Intercept");
#define LENGTH_SOCKET_INDEX 2
b.add_output<decl::Float>("Length");
b.add_output<decl::Float>("Thickness");
b.add_output<decl::Vector>("Tangent Normal");
@@ -24,9 +26,12 @@ static int node_shader_gpu_hair_info(GPUMaterial *mat,
{
/* Length: don't request length if not needed. */
static const float zero = 0;
GPUNodeLink *length_link = out[2].hasoutput ? GPU_attribute_hair_length(mat) :
GPU_constant(&zero);
return GPU_stack_link(mat, node, "node_hair_info", in, out, length_link);
GPUNodeLink *length_link = out[LENGTH_SOCKET_INDEX].hasoutput ? GPU_attribute_hair_length(mat) :
GPU_constant(&zero);
GPUNodeLink *intercept_link = out[INTERCEPT_SOCKET_INDEX].hasoutput ?
GPU_attribute_hair_intercept(mat) :
GPU_constant(&zero);
return GPU_stack_link(mat, node, "node_hair_info", in, out, intercept_link, length_link);
}
NODE_SHADER_MATERIALX_BEGIN

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.