diff --git a/intern/opensubdiv/internal/evaluator/gpu_patch_table.hh b/intern/opensubdiv/internal/evaluator/gpu_patch_table.hh index fa3128c5ab5..46eb077ed78 100644 --- a/intern/opensubdiv/internal/evaluator/gpu_patch_table.hh +++ b/intern/opensubdiv/internal/evaluator/gpu_patch_table.hh @@ -18,7 +18,7 @@ using OpenSubdiv::Osd::PatchArrayVector; namespace blender::opensubdiv { // TODO: use Blenlib NonCopyable. -class GPUPatchTable : private NonCopyable { +class GPUPatchTable : private OpenSubdiv::Osd::NonCopyable { public: ~GPUPatchTable(); diff --git a/scripts/startup/bl_ui/properties_render.py b/scripts/startup/bl_ui/properties_render.py index 1a845315f57..19025d5743c 100644 --- a/scripts/startup/bl_ui/properties_render.py +++ b/scripts/startup/bl_ui/properties_render.py @@ -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): diff --git a/source/blender/draw/CMakeLists.txt b/source/blender/draw/CMakeLists.txt index 88d16cc74a8..ca60ad82810 100644 --- a/source/blender/draw/CMakeLists.txt +++ b/source/blender/draw/CMakeLists.txt @@ -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 diff --git a/source/blender/draw/engines/eevee/eevee_shader.cc b/source/blender/draw/engines/eevee/eevee_shader.cc index 44e8a603cd5..cba645aaffb 100644 --- a/source/blender/draw/engines/eevee/eevee_shader.cc +++ b/source/blender/draw/engines/eevee/eevee_shader.cc @@ -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. */ diff --git a/source/blender/draw/engines/eevee/eevee_velocity.cc b/source/blender/draw/engines/eevee/eevee_velocity.cc index 56f7048732e..a4594294996 100644 --- a/source/blender/draw/engines/eevee/eevee_velocity.cc +++ b/source/blender/draw/engines/eevee/eevee_velocity.cc @@ -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); diff --git a/source/blender/draw/engines/eevee/shaders/CMakeLists.txt b/source/blender/draw/engines/eevee/shaders/CMakeLists.txt index 3e8efe0efd6..c22b491813f 100644 --- a/source/blender/draw/engines/eevee/shaders/CMakeLists.txt +++ b/source/blender/draw/engines/eevee/shaders/CMakeLists.txt @@ -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 diff --git a/source/blender/draw/engines/eevee/shaders/eevee_attributes_curves_lib.glsl b/source/blender/draw/engines/eevee/shaders/eevee_attributes_curves_lib.glsl index 737bfe9a5d8..df79755c217 100644 --- a/source/blender/draw/engines/eevee/shaders/eevee_attributes_curves_lib.glsl +++ b/source/blender/draw/engines/eevee/shaders/eevee_attributes_curves_lib.glsl @@ -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 diff --git a/source/blender/draw/engines/eevee/shaders/eevee_attributes_gpencil_lib.glsl b/source/blender/draw/engines/eevee/shaders/eevee_attributes_gpencil_lib.glsl deleted file mode 100644 index d8adfab3727..00000000000 --- a/source/blender/draw/engines/eevee/shaders/eevee_attributes_gpencil_lib.glsl +++ /dev/null @@ -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; -} - -/** \} */ diff --git a/source/blender/draw/engines/eevee/shaders/eevee_attributes_mesh_lib.glsl b/source/blender/draw/engines/eevee/shaders/eevee_attributes_mesh_lib.glsl index 1ed769a4b46..f34fa904133 100644 --- a/source/blender/draw/engines/eevee/shaders/eevee_attributes_mesh_lib.glsl +++ b/source/blender/draw/engines/eevee/shaders/eevee_attributes_mesh_lib.glsl @@ -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; } diff --git a/source/blender/draw/engines/eevee/shaders/eevee_attributes_pointcloud_lib.glsl b/source/blender/draw/engines/eevee/shaders/eevee_attributes_pointcloud_lib.glsl index f09cc70c222..8765e0f72b9 100644 --- a/source/blender/draw/engines/eevee/shaders/eevee_attributes_pointcloud_lib.glsl +++ b/source/blender/draw/engines/eevee/shaders/eevee_attributes_pointcloud_lib.glsl @@ -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); } diff --git a/source/blender/draw/engines/eevee/shaders/eevee_attributes_volume_lib.glsl b/source/blender/draw/engines/eevee/shaders/eevee_attributes_volume_lib.glsl index 904cb89f517..71fea471f72 100644 --- a/source/blender/draw/engines/eevee/shaders/eevee_attributes_volume_lib.glsl +++ b/source/blender/draw/engines/eevee/shaders/eevee_attributes_volume_lib.glsl @@ -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); } diff --git a/source/blender/draw/engines/eevee/shaders/eevee_attributes_world_lib.glsl b/source/blender/draw/engines/eevee/shaders/eevee_attributes_world_lib.glsl index 129af32747a..b32b0519d48 100644 --- a/source/blender/draw/engines/eevee/shaders/eevee_attributes_world_lib.glsl +++ b/source/blender/draw/engines/eevee/shaders/eevee_attributes_world_lib.glsl @@ -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); } diff --git a/source/blender/draw/engines/eevee/shaders/eevee_geom_curves_vert.glsl b/source/blender/draw/engines/eevee/shaders/eevee_geom_curves_vert.glsl index 2c6e5368da7..1f0c16e69ab 100644 --- a/source/blender/draw/engines/eevee/shaders/eevee_geom_curves_vert.glsl +++ b/source/blender/draw/engines/eevee/shaders/eevee_geom_curves_vert.glsl @@ -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 diff --git a/source/blender/draw/engines/eevee/shaders/eevee_geom_mesh_vert.glsl b/source/blender/draw/engines/eevee/shaders/eevee_geom_mesh_vert.glsl index 090ac3e8b66..c73c147794a 100644 --- a/source/blender/draw/engines/eevee/shaders/eevee_geom_mesh_vert.glsl +++ b/source/blender/draw/engines/eevee/shaders/eevee_geom_mesh_vert.glsl @@ -37,7 +37,7 @@ void main() #endif init_globals(); - attrib_load(); + attrib_load(MeshVertex(0)); interp.P += nodetree_displacement(); diff --git a/source/blender/draw/engines/eevee/shaders/eevee_geom_pointcloud_vert.glsl b/source/blender/draw/engines/eevee/shaders/eevee_geom_pointcloud_vert.glsl index 181147f9fbf..cb7bb4740b5 100644 --- a/source/blender/draw/engines/eevee/shaders/eevee_geom_pointcloud_vert.glsl +++ b/source/blender/draw/engines/eevee/shaders/eevee_geom_pointcloud_vert.glsl @@ -47,7 +47,7 @@ void main() #endif init_globals(); - attrib_load(); + attrib_load(PointCloudPoint(0)); interp.P += nodetree_displacement(); diff --git a/source/blender/draw/engines/eevee/shaders/eevee_nodetree_frag_lib.glsl b/source/blender/draw/engines/eevee/shaders/eevee_nodetree_frag_lib.glsl index 055a936e05b..a0f6370e4ef 100644 --- a/source/blender/draw/engines/eevee/shaders/eevee_nodetree_frag_lib.glsl +++ b/source/blender/draw/engines/eevee/shaders/eevee_nodetree_frag_lib.glsl @@ -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() diff --git a/source/blender/draw/engines/eevee/shaders/eevee_nodetree_lib.glsl b/source/blender/draw/engines/eevee/shaders/eevee_nodetree_lib.glsl index 982a4816ea3..908dfbea0db 100644 --- a/source/blender/draw/engines/eevee/shaders/eevee_nodetree_lib.glsl +++ b/source/blender/draw/engines/eevee/shaders/eevee_nodetree_lib.glsl @@ -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(); diff --git a/source/blender/draw/engines/eevee/shaders/eevee_nodetree_vert_lib.glsl b/source/blender/draw/engines/eevee/shaders/eevee_nodetree_vert_lib.glsl index 055a936e05b..18d0669654f 100644 --- a/source/blender/draw/engines/eevee/shaders/eevee_nodetree_vert_lib.glsl +++ b/source/blender/draw/engines/eevee/shaders/eevee_nodetree_vert_lib.glsl @@ -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() diff --git a/source/blender/draw/engines/eevee/shaders/eevee_surf_lib.glsl b/source/blender/draw/engines/eevee/shaders/eevee_surf_lib.glsl index ce295b67119..10ec5580e05 100644 --- a/source/blender/draw/engines/eevee/shaders/eevee_surf_lib.glsl +++ b/source/blender/draw/engines/eevee/shaders/eevee_surf_lib.glsl @@ -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; diff --git a/source/blender/draw/engines/eevee/shaders/eevee_surf_volume_frag.glsl b/source/blender/draw/engines/eevee/shaders/eevee_surf_volume_frag.glsl index 26bf99710e7..b4b10b941d9 100644 --- a/source/blender/draw/engines/eevee/shaders/eevee_surf_volume_frag.glsl +++ b/source/blender/draw/engines/eevee/shaders/eevee_surf_volume_frag.glsl @@ -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) diff --git a/source/blender/draw/engines/eevee/shaders/eevee_surf_world_frag.glsl b/source/blender/draw/engines/eevee/shaders/eevee_surf_world_frag.glsl index 9cc47b8639a..751a14a2008 100644 --- a/source/blender/draw/engines/eevee/shaders/eevee_surf_world_frag.glsl +++ b/source/blender/draw/engines/eevee/shaders/eevee_surf_world_frag.glsl @@ -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); diff --git a/source/blender/draw/engines/eevee/shaders/eevee_velocity_lib.glsl b/source/blender/draw/engines/eevee/shaders/eevee_velocity_lib.glsl index c5412a581bd..0154bebfb38 100644 --- a/source/blender/draw/engines/eevee/shaders/eevee_velocity_lib.glsl +++ b/source/blender/draw/engines/eevee/shaders/eevee_velocity_lib.glsl @@ -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) { diff --git a/source/blender/draw/engines/eevee/shaders/infos/eevee_material_info.hh b/source/blender/draw/engines/eevee/shaders/infos/eevee_material_info.hh index 24d0e20b24d..79749948712 100644 --- a/source/blender/draw/engines/eevee/shaders/infos/eevee_material_info.hh +++ b/source/blender/draw/engines/eevee/shaders/infos/eevee_material_info.hh @@ -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() diff --git a/source/blender/draw/engines/overlay/overlay_attribute_viewer.hh b/source/blender/draw/engines/overlay/overlay_attribute_viewer.hh index 1712203da9c..1513188e817 100644 --- a/source/blender/draw/engines/overlay/overlay_attribute_viewer.hh +++ b/source/blender/draw/engines/overlay/overlay_attribute_viewer.hh @@ -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; diff --git a/source/blender/draw/engines/overlay/overlay_sculpt.hh b/source/blender/draw/engines/overlay/overlay_sculpt.hh index bcc95268c3d..42824e74645 100644 --- a/source/blender/draw/engines/overlay/overlay_sculpt.hh +++ b/source/blender/draw/engines/overlay/overlay_sculpt.hh @@ -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); } } diff --git a/source/blender/draw/engines/overlay/shaders/infos/overlay_edit_mode_info.hh b/source/blender/draw/engines/overlay/shaders/infos/overlay_edit_mode_info.hh index 75e040edc85..fa5f9b2d2e5 100644 --- a/source/blender/draw/engines/overlay/shaders/infos/overlay_edit_mode_info.hh +++ b/source/blender/draw/engines/overlay/shaders/infos/overlay_edit_mode_info.hh @@ -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() diff --git a/source/blender/draw/engines/overlay/shaders/infos/overlay_outline_info.hh b/source/blender/draw/engines/overlay/shaders/infos/overlay_outline_info.hh index a314883a99f..70431eaf3e1 100644 --- a/source/blender/draw/engines/overlay/shaders/infos/overlay_outline_info.hh +++ b/source/blender/draw/engines/overlay/shaders/infos/overlay_outline_info.hh @@ -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() diff --git a/source/blender/draw/engines/overlay/shaders/infos/overlay_sculpt_curves_info.hh b/source/blender/draw/engines/overlay/shaders/infos/overlay_sculpt_curves_info.hh index 61e07468ef5..04eae8bb4b6 100644 --- a/source/blender/draw/engines/overlay/shaders/infos/overlay_sculpt_curves_info.hh +++ b/source/blender/draw/engines/overlay/shaders/infos/overlay_sculpt_curves_info.hh @@ -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) diff --git a/source/blender/draw/engines/overlay/shaders/infos/overlay_viewer_attribute_info.hh b/source/blender/draw/engines/overlay/shaders/infos/overlay_viewer_attribute_info.hh index 64ed799753e..ab9ba344b41 100644 --- a/source/blender/draw/engines/overlay/shaders/infos/overlay_viewer_attribute_info.hh +++ b/source/blender/draw/engines/overlay/shaders/infos/overlay_viewer_attribute_info.hh @@ -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) diff --git a/source/blender/draw/engines/overlay/shaders/overlay_depth_only_curves_vert.glsl b/source/blender/draw/engines/overlay/shaders/overlay_depth_only_curves_vert.glsl index 25b9dea6598..3ae29748042 100644 --- a/source/blender/draw/engines/overlay/shaders/overlay_depth_only_curves_vert.glsl +++ b/source/blender/draw/engines/overlay/shaders/overlay_depth_only_curves_vert.glsl @@ -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); diff --git a/source/blender/draw/engines/overlay/shaders/overlay_outline_prepass_curves_vert.glsl b/source/blender/draw/engines/overlay/shaders/overlay_outline_prepass_curves_vert.glsl index fb13ce10dd5..916bd593707 100644 --- a/source/blender/draw/engines/overlay/shaders/overlay_outline_prepass_curves_vert.glsl +++ b/source/blender/draw/engines/overlay/shaders/overlay_outline_prepass_curves_vert.glsl @@ -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; diff --git a/source/blender/draw/engines/overlay/shaders/overlay_outline_prepass_vert.glsl b/source/blender/draw/engines/overlay/shaders/overlay_outline_prepass_vert.glsl index ad4d9526549..5e2088ccf78 100644 --- a/source/blender/draw/engines/overlay/shaders/overlay_outline_prepass_vert.glsl +++ b/source/blender/draw/engines/overlay/shaders/overlay_outline_prepass_vert.glsl @@ -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; diff --git a/source/blender/draw/engines/overlay/shaders/overlay_sculpt_curves_selection_vert.glsl b/source/blender/draw/engines/overlay/shaders/overlay_sculpt_curves_selection_vert.glsl index 9edf84f827d..c1413604496 100644 --- a/source/blender/draw/engines/overlay/shaders/overlay_sculpt_curves_selection_vert.glsl +++ b/source/blender/draw/engines/overlay/shaders/overlay_sculpt_curves_selection_vert.glsl @@ -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); } diff --git a/source/blender/draw/engines/overlay/shaders/overlay_viewer_attribute_curves_vert.glsl b/source/blender/draw/engines/overlay/shaders/overlay_viewer_attribute_curves_vert.glsl index 46486fb1c37..5ecd99d2e36 100644 --- a/source/blender/draw/engines/overlay/shaders/overlay_viewer_attribute_curves_vert.glsl +++ b/source/blender/draw/engines/overlay/shaders/overlay_viewer_attribute_curves_vert.glsl @@ -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)); } } diff --git a/source/blender/draw/engines/workbench/shaders/infos/workbench_prepass_info.hh b/source/blender/draw/engines/workbench/shaders/infos/workbench_prepass_info.hh index b7ca7409a23..207d6b25b5e 100644 --- a/source/blender/draw/engines/workbench/shaders/infos/workbench_prepass_info.hh +++ b/source/blender/draw/engines/workbench/shaders/infos/workbench_prepass_info.hh @@ -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) diff --git a/source/blender/draw/engines/workbench/shaders/workbench_prepass_hair_vert.glsl b/source/blender/draw/engines/workbench/shaders/workbench_prepass_hair_vert.glsl index 6056d420136..44996e37d45 100644 --- a/source/blender/draw/engines/workbench/shaders/workbench_prepass_hair_vert.glsl +++ b/source/blender/draw/engines/workbench/shaders/workbench_prepass_hair_vert.glsl @@ -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, diff --git a/source/blender/draw/engines/workbench/workbench_defines.hh b/source/blender/draw/engines/workbench/workbench_defines.hh index 8e7561bdbc1..824f69c27bd 100644 --- a/source/blender/draw/engines/workbench/workbench_defines.hh +++ b/source/blender/draw/engines/workbench/workbench_defines.hh @@ -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 diff --git a/source/blender/draw/intern/draw_attribute_shader_shared.hh b/source/blender/draw/intern/draw_attribute_shader_shared.hh new file mode 100644 index 00000000000..5f6aafc11ea --- /dev/null +++ b/source/blender/draw/intern/draw_attribute_shader_shared.hh @@ -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; +} + +/** \} */ diff --git a/source/blender/draw/intern/draw_cache_impl.hh b/source/blender/draw/intern/draw_cache_impl.hh index 9673f11c401..bb758901859 100644 --- a/source/blender/draw/intern/draw_cache_impl.hh +++ b/source/blender/draw/intern/draw_cache_impl.hh @@ -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); diff --git a/source/blender/draw/intern/draw_cache_impl_curves.cc b/source/blender/draw/intern/draw_cache_impl_curves.cc index 2d6bbf81fad..aac27825b17 100644 --- a/source/blender/draw/intern/draw_cache_impl_curves.cc +++ b/source/blender/draw/intern/draw_cache_impl_curves.cc @@ -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(curves.batch_cache); } -struct PositionAndParameter { - float3 position; - float parameter; -}; - -static void fill_points_position_time_vbo(const OffsetIndices points_by_curve, - const Span positions, - MutableSpan posTime_data, - MutableSpan 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 curve_positions = positions.slice(points); - MutableSpan 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(), - cache.proc_length_buf->data()); -} - 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 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 attribute = attributes.lookup(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().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 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 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 get_first_uv_name(const bke::AttributeAccessor &attributes) { std::optional name; @@ -745,12 +569,83 @@ static std::optional 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 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 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 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 left = curves.handle_positions_left().value_or(curves.positions()); + const Span 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 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 basis_cache_offset; + Vector 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()); + basis_cache_packed.extend(cache.weights.as_span().cast()); + } + } + /* 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( + 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( + evaluated_point_count_with_cyclic(curves)); + this->curves_length_buf = gpu::VertBuf::device_only(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( diff --git a/source/blender/draw/intern/draw_cache_impl_particles.cc b/source/blender/draw/intern/draw_cache_impl_particles.cc index 971906119c3..0ef67ce2906 100644 --- a/source/blender/draw/intern/draw_cache_impl_particles.cc +++ b/source/blender/draw/intern/draw_cache_impl_particles.cc @@ -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 points_by_curve_storage; + Vector 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(psys->batch_cache); if (!cache) { - cache = static_cast( - psys->batch_cache = MEM_callocN(sizeof(*cache), __func__)); + cache = MEM_new(__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(psys->batch_cache); + MEM_delete(batch_cache); + psys->batch_cache = nullptr; +} + +void ParticleSpans::foreach_strand(FunctionRef)> callback) +{ + for (const auto &particle : parent) { + callback(Span(particle, particle->segments + 1)); + } + for (const auto &particle : children) { + callback(Span(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 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 strand) { + total += strand.size(); + points_by_curve_storage_.append(total); + }); + return points_by_curve_storage_.as_span(); +} + +OffsetIndices 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 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( - MEM_callocN(sizeof(*r_parent_uvs) * psys->totpart, "Parent particle UVs")); - } - if (is_simple && *r_parent_mcol == nullptr) { - *r_parent_mcol = static_cast( - 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(cache->num_col_layers, "proc_col_buf"); - cache->col_tex = MEM_calloc_arrayN(cache->num_col_layers, "col_tex"); - cache->col_layer_names = MEM_calloc_arrayN(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 +static float4 interpolate(const ParticleDataT &particle, Span mfaces, Span 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 +static float2 interpolate(const ParticleDataT &particle, Span mfaces, Span 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 get_first_uv_name(const bke::AttributeAccessor &attributes) +{ + std::optional 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 +Span 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(CustomData_get_layer_n(&mesh.fdata_legacy, type, layer_id)), + /* There is 4 MCol per face. */ + mesh.totface_legacy * (std::is_same_v ? 4 : 1)}; +} + +template +Span span_from_custom_data_layer(const Mesh &mesh, const eCustomDataType type) +{ + return {static_cast(CustomData_get_layer(&mesh.fdata_legacy, type)), + mesh.totface_legacy}; +} + +template +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::format(), + GPU_USAGE_STATIC | GPU_USAGE_FLAG_BUFFER_TEXTURE_ONLY)); + vbo->allocate(src.curves_num()); + MutableSpan data = vbo->data(); + + 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 attr = span_from_custom_data_layer(mesh, data_type, name); + Span mfaces = span_from_custom_data_layer(mesh, CD_MFACE); + Span children(src.psys->child, src.psys->totchild); + Span 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 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 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(src, name); + } + if (meta_data->data_type == AttrType::Float2) { + return interpolate_face_corner_attribute_to_curve(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::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 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 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::from_single(CURVE_TYPE_CATMULL_ROM, src.curves_num()); + auto resolution_varray = VArray::from_single(src.resolution(), src.curves_num()); + /* Not used. */ + auto cyclic_offsets_varray = VArray::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(1); + this->evaluated_time_buf = gpu::VertBuf::device_only(4); + this->curves_length_buf = gpu::VertBuf::device_only(4); + return; + } + + ensure_common(src); + + gpu::VertBufPtr points_pos_buf = gpu::VertBuf::from_size(src.points_num()); + gpu::VertBufPtr points_rad_buf = gpu::VertBuf::from_size(src.points_num()); + + MutableSpan points_pos = points_pos_buf->data(); + MutableSpan points_rad = points_rad_buf->data(); + + 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 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(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(src.evaluated_points_num()); + this->curves_length_buf = gpu::VertBuf::device_only(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(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 diff --git a/source/blender/draw/intern/draw_common.hh b/source/blender/draw/intern/draw_common.hh index fdcf9e73042..b05391cd96b 100644 --- a/source/blender/draw/intern/draw_common.hh +++ b/source/blender/draw/intern/draw_common.hh @@ -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, diff --git a/source/blender/draw/intern/draw_common_c.hh b/source/blender/draw/intern/draw_common_c.hh index 8a6c6d565bb..9f496bf8e7c 100644 --- a/source/blender/draw/intern/draw_common_c.hh +++ b/source/blender/draw/intern/draw_common_c.hh @@ -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); diff --git a/source/blender/draw/intern/draw_curves.cc b/source/blender/draw/intern/draw_curves.cc index b4f79340fed..8c859baac3b 100644 --- a/source/blender/draw/intern/draw_curves.cc +++ b/source/blender/draw/intern/draw_curves.cc @@ -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(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 &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(*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 &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(*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 get_first_uv_name(const bke::AttributeAccessor &attributes) @@ -282,25 +362,33 @@ static std::optional get_first_uv_name(const bke::AttributeAccessor & return name; } -template -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(*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 +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 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 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 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 uv_name = get_first_uv_name( - curves_id.geometry.wrap().attributes()); - const VectorSet &attrs = curves_cache->final.attr_used; + const VectorSet &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 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 active_uv_name) +{ + curves_bind_resources_implementation( + sub_ps, module, cache, face_per_segment, gpu_material, indirection_buf, active_uv_name); +} + +template +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(*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 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, diff --git a/source/blender/draw/intern/draw_curves_defines.hh b/source/blender/draw/intern/draw_curves_defines.hh new file mode 100644 index 00000000000..a72a15d8464 --- /dev/null +++ b/source/blender/draw/intern/draw_curves_defines.hh @@ -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 diff --git a/source/blender/draw/intern/draw_curves_private.hh b/source/blender/draw/intern/draw_curves_private.hh index 558b23e0af5..c51c1da706e 100644 --- a/source/blender/draw/intern/draw_curves_private.hh +++ b/source/blender/draw/intern/draw_curves_private.hh @@ -12,10 +12,22 @@ #include #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 parent; + Span children; - /** Just contains a huge index buffer used to draw the final curves. */ - gpu::Batch *proc_hairs; + void foreach_strand(FunctionRef)> 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 &points_by_curve_storage_; + Vector &evaluated_points_by_curve_storage_; + int additional_subdivision_; + + public: + ParticleDrawSource(Vector &points_by_curve_storage, + Vector &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 points_by_curve(); + OffsetIndices 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 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 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 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 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 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 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 active_uv_name); + } // namespace blender::draw diff --git a/source/blender/draw/intern/draw_hair.cc b/source/blender/draw/intern/draw_hair.cc index 5d16837f361..2c08ee56014 100644 --- a/source/blender/draw/intern/draw_hair.cc +++ b/source/blender/draw/intern/draw_hair.cc @@ -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 @@ -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 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, diff --git a/source/blender/draw/intern/draw_hair_private.hh b/source/blender/draw/intern/draw_hair_private.hh index aa2de04b13c..640aa391603 100644 --- a/source/blender/draw/intern/draw_hair_private.hh +++ b/source/blender/draw/intern/draw_hair_private.hh @@ -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 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 diff --git a/source/blender/draw/intern/draw_pass.hh b/source/blender/draw/intern/draw_pass.hh index e81337205b2..ba3d80e242b 100644 --- a/source/blender/draw/intern/draw_pass.hh +++ b/source/blender/draw/intern/draw_pass.hh @@ -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::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 inline void PassBase::bind_ssbo(const char *name, gpu::Vert this->bind_ssbo(GPU_shader_get_ssbo_binding(shader_, name), buffer); } +template inline void PassBase::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 inline void PassBase::bind_ssbo(const char *name, gpu::IndexBuf *buffer) { BLI_assert(buffer != nullptr); @@ -1263,6 +1274,12 @@ template inline void PassBase::bind_texture(const char *name, gpu::V this->bind_texture(GPU_shader_get_sampler_binding(shader_, name), buffer); } +template inline void PassBase::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 inline void PassBase::bind_image(const char *name, gpu::Texture *image) { BLI_assert(image != nullptr); @@ -1303,6 +1320,13 @@ template inline void PassBase::bind_ssbo(int slot, gpu::VertBuf **bu slot, buffer, ResourceBind::Type::VertexAsStorageBuf}; } +template inline void PassBase::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 inline void PassBase::bind_ssbo(int slot, gpu::IndexBuf *buffer) { BLI_assert(buffer != nullptr); @@ -1342,6 +1366,12 @@ template inline void PassBase::bind_texture(int slot, gpu::VertBuf * create_command(Type::ResourceBind).resource_bind = {slot, buffer}; } +template inline void PassBase::bind_texture(int slot, gpu::VertBufPtr &buffer) +{ + BLI_assert(buffer.get() != nullptr); + create_command(Type::ResourceBind).resource_bind = {slot, buffer.get()}; +} + template inline void PassBase::bind_image(int slot, gpu::Texture *image) { BLI_assert(image != nullptr); diff --git a/source/blender/draw/intern/draw_shader.cc b/source/blender/draw/intern/draw_shader.cc index 634e92a0a05..c6181c1acff 100644 --- a/source/blender/draw/intern/draw_shader.cc +++ b/source/blender/draw/intern/draw_shader.cc @@ -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() diff --git a/source/blender/draw/intern/draw_shader.hh b/source/blender/draw/intern/draw_shader.hh index 0371ab28ac9..15478321587 100644 --- a/source/blender/draw/intern/draw_shader.hh +++ b/source/blender/draw/intern/draw_shader.hh @@ -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(); diff --git a/source/blender/draw/intern/draw_shader_shared.hh b/source/blender/draw/intern/draw_shader_shared.hh index 33b6fbdbda5..0a818babef1 100644 --- a/source/blender/draw/intern/draw_shader_shared.hh +++ b/source/blender/draw/intern/draw_shader_shared.hh @@ -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) diff --git a/source/blender/draw/intern/shaders/CMakeLists.txt b/source/blender/draw/intern/shaders/CMakeLists.txt index 2169ab00614..e6cc84bb7fb 100644 --- a/source/blender/draw/intern/shaders/CMakeLists.txt +++ b/source/blender/draw/intern/shaders/CMakeLists.txt @@ -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 diff --git a/source/blender/draw/intern/shaders/draw_curves_info.hh b/source/blender/draw/intern/shaders/draw_curves_info.hh new file mode 100644 index 00000000000..6e34b0653ff --- /dev/null +++ b/source/blender/draw/intern/shaders/draw_curves_info.hh @@ -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() diff --git a/source/blender/draw/intern/shaders/draw_curves_interpolation_comp.glsl b/source/blender/draw/intern/shaders/draw_curves_interpolation_comp.glsl new file mode 100644 index 00000000000..3d71ba77e6e --- /dev/null +++ b/source/blender/draw/intern/shaders/draw_curves_interpolation_comp.glsl @@ -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 T input_load(int point_index) +{ + return T(0); +} +template<> InterpPosition input_load(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(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(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(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(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 T output_load(int evaluated_point_index) +{ + return T(0); +} +template<> InterpPosition output_load(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(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(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(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(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 +void output_weighted_add(int evaluated_point_index, float w, const InterpType src) +{ + InterpType dst = output_load(evaluated_point_index); + dst += src * w; + output_write(evaluated_point_index, dst); +} +template void output_weighted_add(int, float, float4); +template void output_weighted_add(int, float, float3); +template void output_weighted_add(int, float, float2); +template void output_weighted_add(int, float, float); + +/** Output Mul. */ + +template void output_mul(int evaluated_point_index, float w) +{ + InterpType dst = output_load(evaluated_point_index); + dst *= w; + output_write(evaluated_point_index, dst); +} +template void output_mul(int, float); +template void output_mul(int, float); +template void output_mul(int, float); +template void output_mul(int, float); +template<> void output_mul(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 void output_set_zero(int evaluated_point_index) +{ + output_write(evaluated_point_index, InterpType(0.0f)); +} +template void output_set_zero(int); +template void output_set_zero(int); +template void output_set_zero(int); +template void output_set_zero(int); +template<> void output_set_zero(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 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); +template float3 mix4(float3, float3, float3, float3, float4); +template float2 mix4(float2, float2, float2, float2, float4); +template float mix4(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 +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(point_ids.x); + InterpType p1 = input_load(point_ids.y); + InterpType p2 = input_load(point_ids.z); + InterpType p3 = input_load(point_ids.w); + InterpType result = mix4(p0, p1, p2, p3, weights); + + output_write(evaluated_point_id, result); + } +} + +template void evaluate_curve(IndexRange, IndexRange, int); +template void evaluate_curve(IndexRange, IndexRange, int); +template void evaluate_curve(IndexRange, IndexRange, int); +template void evaluate_curve(IndexRange, IndexRange, int); +template void evaluate_curve(IndexRange, IndexRange, int); + +} // namespace catmull_rom + +namespace bezier { + +template void evaluate_segment(const int2 points, const IndexRange result) +{ + InterpType p0 = input_load(points.x); + InterpType p1 = input_load(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(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(points.x); + InterpPosition p1 = input_load(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(int2, IndexRange); +template void evaluate_segment(int2, IndexRange); +template void evaluate_segment(int2, IndexRange); +template void evaluate_segment(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 +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(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(evaluated_points.first())); + } +} + +template void evaluate_curve(IndexRange, IndexRange, int); +template void evaluate_curve(IndexRange, IndexRange, int); +template void evaluate_curve(IndexRange, IndexRange, int); +template void evaluate_curve(IndexRange, IndexRange, int); +template void evaluate_curve(IndexRange, IndexRange, int); + +} // namespace bezier + +template +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(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(evaluated_points.first())); + } +} + +template void copy_curve_data(IndexRange, IndexRange, int); +template void copy_curve_data(IndexRange, IndexRange, int); +template void copy_curve_data(IndexRange, IndexRange, int); +template void copy_curve_data(IndexRange, IndexRange, int); +template void copy_curve_data(IndexRange, IndexRange, int); + +namespace nurbs { + +template +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(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 mixer{dst}`. */ + output_set_zero(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(point_index)); + total_weight += weight; + } + /* Equivalent to `mixer.finalize()` */ + output_mul(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(evaluated_points_padded.first())); + } +} + +template void evaluate_curve(IndexRange, IndexRange, int); +template void evaluate_curve(IndexRange, IndexRange, int); +template void evaluate_curve(IndexRange, IndexRange, int); +template void evaluate_curve(IndexRange, IndexRange, int); +template void evaluate_curve(IndexRange, IndexRange, int); + +} // namespace nurbs + +template 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(points, evaluated_points, curve_index); + } + else if (CurveType(evaluated_type) == CURVE_TYPE_BEZIER) { + bezier::evaluate_curve(points, evaluated_points, curve_index); + } + else if (CurveType(evaluated_type) == CURVE_TYPE_NURBS) { + nurbs::evaluate_curve(points, evaluated_points, curve_index); + } + else if (CurveType(evaluated_type) == CURVE_TYPE_POLY) { + /* Simple copy. */ + copy_curve_data(points, evaluated_points, curve_index); + } +} + +template void evaluate_curve(); +template void evaluate_curve(); +template void evaluate_curve(); +template void evaluate_curve(); +template void evaluate_curve(); + +void evaluate_position_radius() +{ + evaluate_curve(); +} + +void evaluate_attribute_float() +{ + evaluate_curve(); +} + +void evaluate_attribute_float2() +{ + evaluate_curve(); +} + +void evaluate_attribute_float3() +{ + evaluate_curve(); +} + +void evaluate_attribute_float4() +{ + evaluate_curve(); +} diff --git a/source/blender/draw/intern/shaders/draw_curves_length_intercept_comp.glsl b/source/blender/draw/intern/shaders/draw_curves_length_intercept_comp.glsl new file mode 100644 index 00000000000..145576d80a5 --- /dev/null +++ b/source/blender/draw/intern/shaders/draw_curves_length_intercept_comp.glsl @@ -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); +} diff --git a/source/blender/draw/intern/shaders/draw_curves_lib.glsl b/source/blender/draw/intern/shaders/draw_curves_lib.glsl index e10915e17e5..6a2d5998544 100644 --- a/source/blender/draw/intern/shaders/draw_curves_lib.glsl +++ b/source/blender/draw/intern/shaders/draw_curves_lib.glsl @@ -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 diff --git a/source/blender/draw/intern/shaders/draw_curves_test.glsl b/source/blender/draw/intern/shaders/draw_curves_test.glsl new file mode 100644 index 00000000000..f7d87f2e3d5 --- /dev/null +++ b/source/blender/draw/intern/shaders/draw_curves_test.glsl @@ -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 +} diff --git a/source/blender/draw/intern/shaders/draw_curves_topology_comp.glsl b/source/blender/draw/intern/shaders/draw_curves_topology_comp.glsl new file mode 100644 index 00000000000..a2b6320028d --- /dev/null +++ b/source/blender/draw/intern/shaders/draw_curves_topology_comp.glsl @@ -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); + } +} diff --git a/source/blender/draw/intern/shaders/draw_hair_refine_comp.glsl b/source/blender/draw/intern/shaders/draw_hair_refine_comp.glsl deleted file mode 100644 index de88c59fdae..00000000000 --- a/source/blender/draw/intern/shaders/draw_hair_refine_comp.glsl +++ /dev/null @@ -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; -} diff --git a/source/blender/draw/intern/shaders/draw_hair_refine_info.hh b/source/blender/draw/intern/shaders/draw_hair_refine_info.hh deleted file mode 100644 index 8d9543e346e..00000000000 --- a/source/blender/draw/intern/shaders/draw_hair_refine_info.hh +++ /dev/null @@ -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() diff --git a/source/blender/draw/intern/shaders/draw_object_infos_info.hh b/source/blender/draw/intern/shaders/draw_object_infos_info.hh index 6c5b7de83c9..e70ed1e3662 100644 --- a/source/blender/draw/intern/shaders/draw_object_infos_info.hh +++ b/source/blender/draw/intern/shaders/draw_object_infos_info.hh @@ -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) diff --git a/source/blender/draw/tests/draw_curves_test.cc b/source/blender/draw/tests/draw_curves_test.cc new file mode 100644 index 00000000000..f62e3de40c9 --- /dev/null +++ b/source/blender/draw/tests/draw_curves_test.cc @@ -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().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().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().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 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 result_pos; + StorageArrayBuffer 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 result_pos; + StorageArrayBuffer 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().copy_from({0, 5, 8, 10}); + + { + StorageArrayBuffer 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 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 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().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().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().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().copy_from(evaluated_offsets); + + const Vector points_radius = {1.0f, 0.5f, 0.0f, 0.0f, 2.0f}; + const Vector 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().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().copy_from(points_radius); + + { + StorageArrayBuffer evaluated_positions_radii_buf; + StorageArrayBuffer evaluated_time_buf; + StorageArrayBuffer 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 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 handle_pos_left = { + float3{0.0f}, float3{1.0f}, float3{-1.0f}, float3{1.0f}, float3{4.0f}}; + const Vector 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().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().copy_from(handle_pos_right); + + const Vector 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().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().copy_from({CURVE_TYPE_BEZIER, CURVE_TYPE_BEZIER, 0, 0}); + + { + StorageArrayBuffer evaluated_positions_radii_buf; + StorageArrayBuffer evaluated_time_buf; + StorageArrayBuffer 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 interp_pos; + interp_pos.resize(8); + + Vector 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 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 basis_cache_offset; + Vector basis_cache_packed; + { + basis_cache_offset.append(basis_cache_c0.invalid ? -1 : basis_cache_packed.size()); + basis_cache_packed.extend( + Span{reinterpret_cast(basis_cache_c0.start_indices.data()), + basis_cache_c0.start_indices.size()}); + basis_cache_packed.extend( + Span{reinterpret_cast(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(basis_cache_c1.start_indices.data()), + basis_cache_c1.start_indices.size()}); + basis_cache_packed.extend( + Span{reinterpret_cast(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().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().copy_from(basis_cache_offset); + + const Vector 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().copy_from( + Span(reinterpret_cast(curves_order.data()), curves_order.size() / 4)); + + const Vector 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().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().copy_from({CURVE_TYPE_NURBS, CURVE_TYPE_NURBS, 0, 0}); + + { + StorageArrayBuffer evaluated_positions_radii_buf; + StorageArrayBuffer evaluated_time_buf; + StorageArrayBuffer 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 interp_pos; + interp_pos.resize(8); + + Vector 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 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 curves_to_point = {0, 3, 5, 7}; + const Vector 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().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().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().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().copy_from(evaluated_offsets); + + /* Attributes. */ + + const Vector 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 attr_float3 = + attr_float4.as_span().cast().take_front(attr_float4.size() * 3).cast(); + const Vector attr_float2 = + attr_float4.as_span().cast().take_front(attr_float4.size() * 2).cast(); + const Vector attr_float = attr_float4.as_span().cast().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().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().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().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().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 basis_cache_offset; + Vector basis_cache_packed; + { + basis_cache_offset.append(basis_cache_c0.invalid ? -1 : basis_cache_packed.size()); + basis_cache_packed.extend( + Span{reinterpret_cast(basis_cache_c0.start_indices.data()), + basis_cache_c0.start_indices.size()}); + basis_cache_packed.extend( + Span{reinterpret_cast(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().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().copy_from(basis_cache_offset); + + const Vector 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().copy_from( + Span(reinterpret_cast(curves_order.data()), curves_order.size() / 4)); + + const Vector 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().copy_from(control_weights); + + /* Bezier */ + + const Vector handle_pos_left = { + float3{0.0f}, float3{1.0f}, float3{-1.0f}, float3{1.0f}, float3{4.0f}}; + const Vector 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().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().copy_from(handle_pos_right); + + const Vector 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().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 evaluated_float4_buf; + evaluated_float4_buf.clear_to_zero(); + + dispatch("float4", attribute_float4_buf, evaluated_float4_buf); + + evaluated_float4_buf.read(); + + Vector interp_data; + interp_data.resize(11); + + OffsetIndices curves_to_point_indices(curves_to_point.as_span()); + OffsetIndices curves_to_eval_indices(evaluated_offsets.as_span()); + Span in_attr = attr_float4.as_span().cast(); + MutableSpan out_attr = interp_data.as_mutable_span().cast(); + { + 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 evaluated_float3_buf; + evaluated_float3_buf.clear_to_zero(); + + dispatch("float3", attribute_float3_buf, evaluated_float3_buf); + + evaluated_float3_buf.read(); + + Vector interp_data; + interp_data.resize(11); + + OffsetIndices curves_to_point_indices(curves_to_point.as_span()); + OffsetIndices curves_to_eval_indices(evaluated_offsets.as_span()); + Span in_attr = attr_float3.as_span(); + MutableSpan 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 evaluated_float2_buf; + evaluated_float2_buf.clear_to_zero(); + + dispatch("float2", attribute_float2_buf, evaluated_float2_buf); + + evaluated_float2_buf.read(); + + Vector interp_data; + interp_data.resize(11); + + OffsetIndices curves_to_point_indices(curves_to_point.as_span()); + OffsetIndices curves_to_eval_indices(evaluated_offsets.as_span()); + Span in_attr = attr_float2.as_span(); + MutableSpan 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 evaluated_float_buf; + evaluated_float_buf.clear_to_zero(); + + dispatch("float", attribute_float_buf, evaluated_float_buf); + + evaluated_float_buf.read(); + + Vector interp_data; + interp_data.resize(11); + + OffsetIndices curves_to_point_indices(curves_to_point.as_span()); + OffsetIndices curves_to_eval_indices(evaluated_offsets.as_span()); + Span in_attr = attr_float.as_span(); + MutableSpan 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 diff --git a/source/blender/gpu/CMakeLists.txt b/source/blender/gpu/CMakeLists.txt index c54eb2e6abe..e6fb65c9a63 100644 --- a/source/blender/gpu/CMakeLists.txt +++ b/source/blender/gpu/CMakeLists.txt @@ -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) diff --git a/source/blender/gpu/GPU_batch.hh b/source/blender/gpu/GPU_batch.hh index 7bfe78dfe3e..ca850970888 100644 --- a/source/blender/gpu/GPU_batch.hh +++ b/source/blender/gpu/GPU_batch.hh @@ -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; diff --git a/source/blender/gpu/GPU_material.hh b/source/blender/gpu/GPU_material.hh index 3b642755a0c..8cb19d50e70 100644 --- a/source/blender/gpu/GPU_material.hh +++ b/source/blender/gpu/GPU_material.hh @@ -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, diff --git a/source/blender/gpu/GPU_vertex_buffer.hh b/source/blender/gpu/GPU_vertex_buffer.hh index ad969bb2d7f..2157b13ccd6 100644 --- a/source/blender/gpu/GPU_vertex_buffer.hh +++ b/source/blender/gpu/GPU_vertex_buffer.hh @@ -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; /** * Implementation of Vertex Buffers. @@ -91,6 +118,48 @@ class VertBuf { VertBuf(); virtual ~VertBuf(); + template static VertBufPtr from_size(const int size) + { + BLI_assert(size > 0); + VertBufPtr buf = VertBufPtr(GPU_vertbuf_create_with_format(GenericVertexFormat::format())); + /* GPU formats needs to be aligned to 4 bytes. */ + buf->allocate(ceil_to_multiple_u(size * sizeof(T), 4) / sizeof(GenericVertexFormat)); + return buf; + } + + template static VertBufPtr from_span(const Span data) + { + BLI_assert(!data.is_empty()); + VertBufPtr buf = VertBufPtr(GPU_vertbuf_create_with_format_ex( + GenericVertexFormat::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)); + buf->data().slice(0, data.size()).copy_from(data); + return buf; + } + + template static VertBufPtr from_varray(const VArray &array) + { + BLI_assert(!array.is_empty()); + VertBufPtr buf = VertBufPtr(GPU_vertbuf_create_with_format_ex( + GenericVertexFormat::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)); + array.materialize(buf->data().slice(0, array.size())); + return buf; + } + + template static VertBufPtr device_only(uint size) + { + BLI_assert(size > 0); + VertBufPtr buf = VertBufPtr(GPU_vertbuf_create_with_format_ex( + GenericVertexFormat::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; - -} // namespace blender::gpu diff --git a/source/blender/gpu/GPU_vertex_format.hh b/source/blender/gpu/GPU_vertex_format.hh index b0045ec0214..803f640d7c2 100644 --- a/source/blender/gpu/GPU_vertex_format.hh +++ b/source/blender/gpu/GPU_vertex_format.hh @@ -306,6 +306,40 @@ struct GPUVertFormat { return format; \ } +namespace blender::gpu { + +/** Generic vertex format for single attribute buffers. */ +template struct GenericVertexFormat { + T attr; + GPU_VERTEX_FORMAT_FUNC(GenericVertexFormat, attr); +}; + +template<> struct GenericVertexFormat { + /* 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 { + /* 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 { + /* 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); diff --git a/source/blender/gpu/glsl_preprocess/glsl_preprocess.hh b/source/blender/gpu/glsl_preprocess/glsl_preprocess.hh index 23c18d7dfb2..bca8feca4e1 100644 --- a/source/blender/gpu/glsl_preprocess/glsl_preprocess.hh +++ b/source/blender/gpu/glsl_preprocess/glsl_preprocess.hh @@ -1481,11 +1481,11 @@ class Preprocessor { }); } - /* `*this` -> `this` */ + /* `*this` -> `this_` */ scope.foreach_match("*T", [&](const std::vector &tokens) { fn_parser.replace(tokens[0], tokens[1], "this_"); }); - /* `this->` -> `this.` */ + /* `this->` -> `this_.` */ scope.foreach_match("TD", [&](const std::vector &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, '[', ']'); diff --git a/source/blender/gpu/glsl_preprocess/shader_parser.hh b/source/blender/gpu/glsl_preprocess/shader_parser.hh index 19ee2c3c542..e86e494948e 100644 --- a/source/blender/gpu/glsl_preprocess/shader_parser.hh +++ b/source/blender/gpu/glsl_preprocess/shader_parser.hh @@ -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 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) diff --git a/source/blender/gpu/intern/gpu_batch.cc b/source/blender/gpu/intern/gpu_batch.cc index 4ae7b44726d..e3356bb7d78 100644 --- a/source/blender/gpu/intern/gpu_batch.cc +++ b/source/blender/gpu/intern/gpu_batch.cc @@ -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(); diff --git a/source/blender/gpu/intern/gpu_codegen.cc b/source/blender/gpu/intern/gpu_codegen.cc index 73823f7ee3b..8e29199fa54 100644 --- a/source/blender/gpu/intern/gpu_codegen.cc +++ b/source/blender/gpu/intern/gpu_codegen.cc @@ -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); diff --git a/source/blender/gpu/intern/gpu_node_graph.cc b/source/blender/gpu/intern/gpu_node_graph.cc index 5d31cad97b2..59079711dc5 100644 --- a/source/blender/gpu/intern/gpu_node_graph.cc +++ b/source/blender/gpu/intern/gpu_node_graph.cc @@ -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(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(__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); diff --git a/source/blender/gpu/intern/gpu_shader_create_info_list.hh b/source/blender/gpu/intern/gpu_shader_create_info_list.hh index 2d748207da1..b12f3a057be 100644 --- a/source/blender/gpu/intern/gpu_shader_create_info_list.hh +++ b/source/blender/gpu/intern/gpu_shader_create_info_list.hh @@ -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" diff --git a/source/blender/gpu/intern/gpu_vertex_buffer.cc b/source/blender/gpu/intern/gpu_vertex_buffer.cc index 4ce0150a352..d4722fbd9be 100644 --- a/source/blender/gpu/intern/gpu_vertex_buffer.cc +++ b/source/blender/gpu/intern/gpu_vertex_buffer.cc @@ -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 diff --git a/source/blender/gpu/shaders/common/gpu_shader_attribute_load_lib.glsl b/source/blender/gpu/shaders/common/gpu_shader_attribute_load_lib.glsl index d7afd9c7fdf..52c653bc4df 100644 --- a/source/blender/gpu/shaders/common/gpu_shader_attribute_load_lib.glsl +++ b/source/blender/gpu/shaders/common/gpu_shader_attribute_load_lib.glsl @@ -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) diff --git a/source/blender/gpu/shaders/common/gpu_shader_index_range_lib.glsl b/source/blender/gpu/shaders/common/gpu_shader_index_range_lib.glsl new file mode 100644 index 00000000000..1cd2765c8ab --- /dev/null +++ b/source/blender/gpu/shaders/common/gpu_shader_index_range_lib.glsl @@ -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_); + } +}; diff --git a/source/blender/gpu/shaders/common/gpu_shader_offset_indices_lib.glsl b/source/blender/gpu/shaders/common/gpu_shader_offset_indices_lib.glsl new file mode 100644 index 00000000000..a2f3466ad93 --- /dev/null +++ b/source/blender/gpu/shaders/common/gpu_shader_offset_indices_lib.glsl @@ -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 OffsetIndicesoperator[]`. + * 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]) diff --git a/source/blender/gpu/shaders/gpu_glsl_cpp_stubs.hh b/source/blender/gpu/shaders/gpu_glsl_cpp_stubs.hh index b0c09c277cf..72fca5cc11b 100644 --- a/source/blender/gpu/shaders/gpu_glsl_cpp_stubs.hh +++ b/source/blender/gpu/shaders/gpu_glsl_cpp_stubs.hh @@ -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 diff --git a/source/blender/gpu/shaders/gpu_shader_codegen_lib.glsl b/source/blender/gpu/shaders/gpu_shader_codegen_lib.glsl index f10ae47a44a..a3907aa2945 100644 --- a/source/blender/gpu/shaders/gpu_shader_codegen_lib.glsl +++ b/source/blender/gpu/shaders/gpu_shader_codegen_lib.glsl @@ -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). */ diff --git a/source/blender/gpu/shaders/material/gpu_shader_material_hair_info.glsl b/source/blender/gpu/shaders/material/gpu_shader_material_hair_info.glsl index d31f8a6ae6d..61a26c43bbc 100644 --- a/source/blender/gpu/shaders/material/gpu_shader_material_hair_info.glsl +++ b/source/blender/gpu/shaders/material/gpu_shader_material_hair_info.glsl @@ -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)); diff --git a/source/blender/gpu/shaders/opengl/glsl_shader_defines.glsl b/source/blender/gpu/shaders/opengl/glsl_shader_defines.glsl index a12bbecd048..61e0370e8c5 100644 --- a/source/blender/gpu/shaders/opengl/glsl_shader_defines.glsl +++ b/source/blender/gpu/shaders/opengl/glsl_shader_defines.glsl @@ -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 diff --git a/source/blender/gpu/tests/shader_preprocess_test.cc b/source/blender/gpu/tests/shader_preprocess_test.cc index cde0b1c58a3..aabf9e2fb50 100644 --- a/source/blender/gpu/tests/shader_preprocess_test.cc +++ b/source/blender/gpu/tests/shader_preprocess_test.cc @@ -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 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); diff --git a/source/blender/makesdna/DNA_scene_types.h b/source/blender/makesdna/DNA_scene_types.h index fd1f927fa26..b10d75a51d4 100644 --- a/source/blender/makesdna/DNA_scene_types.h +++ b/source/blender/makesdna/DNA_scene_types.h @@ -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 */ diff --git a/source/blender/makesrna/intern/rna_scene.cc b/source/blender/makesrna/intern/rna_scene.cc index ddfd1a36bc8..a169a3973ab 100644 --- a/source/blender/makesrna/intern/rna_scene.cc +++ b/source/blender/makesrna/intern/rna_scene.cc @@ -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}, }; diff --git a/source/blender/nodes/shader/nodes/node_shader_hair_info.cc b/source/blender/nodes/shader/nodes/node_shader_hair_info.cc index bf88e038182..f80b547e87e 100644 --- a/source/blender/nodes/shader/nodes/node_shader_hair_info.cc +++ b/source/blender/nodes/shader/nodes/node_shader_hair_info.cc @@ -9,7 +9,9 @@ namespace blender::nodes::node_shader_hair_info_cc { static void node_declare(NodeDeclarationBuilder &b) { b.add_output("Is Strand"); +#define INTERCEPT_SOCKET_INDEX 1 b.add_output("Intercept"); +#define LENGTH_SOCKET_INDEX 2 b.add_output("Length"); b.add_output("Thickness"); b.add_output("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 diff --git a/tests/files/render/hair/eevee_renders/hair_basemesh_intercept.png b/tests/files/render/hair/eevee_renders/hair_basemesh_intercept.png index 7858a0a4979..88953999e0d 100644 --- a/tests/files/render/hair/eevee_renders/hair_basemesh_intercept.png +++ b/tests/files/render/hair/eevee_renders/hair_basemesh_intercept.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:1327c8802e871d73d95370b4e0933584cac0e27151efc9f765e364d8f5a12c96 -size 6497 +oid sha256:3ec485346ad3c6d0a72a1306101c403fd5495febd34bb593d235fdbc0988f033 +size 6443 diff --git a/tests/files/render/hair/eevee_renders/hair_close_up.png b/tests/files/render/hair/eevee_renders/hair_close_up.png index 819ea004ab9..4f76d07dc26 100644 --- a/tests/files/render/hair/eevee_renders/hair_close_up.png +++ b/tests/files/render/hair/eevee_renders/hair_close_up.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:c883c2c7ca9c9b03c7cd7dcc9a87f84162728333b4cda818efc072be86c6a9be -size 14996 +oid sha256:13b55b5e83a70e92ac08615ef905ab1769e5a16fe7d0b46e6569a7bbce9b60d0 +size 14636 diff --git a/tests/files/render/hair/eevee_renders/hair_info.png b/tests/files/render/hair/eevee_renders/hair_info.png index 4fb1c40bd7b..02a75018851 100644 --- a/tests/files/render/hair/eevee_renders/hair_info.png +++ b/tests/files/render/hair/eevee_renders/hair_info.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:ea3124aa08e3ab8dbf7fe4249b7f57c459dfb57840cfd654286e2b7f3b2301c1 -size 17463 +oid sha256:fb131ec77f0c3af23a7ee3bff1d4b355579294e91c32a33b1f7fe293e9737502 +size 17371 diff --git a/tests/files/render/hair/eevee_renders/hair_instancer_uv.png b/tests/files/render/hair/eevee_renders/hair_instancer_uv.png index 9a20160ec29..f8bf8b97bbe 100644 --- a/tests/files/render/hair/eevee_renders/hair_instancer_uv.png +++ b/tests/files/render/hair/eevee_renders/hair_instancer_uv.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:e177eb92f16ea3304476f6ccde57701189f4683d120a1643c77adff3205059e1 -size 27994 +oid sha256:3b687e7603652899e33953de51df93371e2be2bde5f613ddec213c0dbfbc4d47 +size 29004 diff --git a/tests/files/render/hair/eevee_renders/hair_linear_close_up.png b/tests/files/render/hair/eevee_renders/hair_linear_close_up.png index fb7519d557c..d677a14e5b8 100644 --- a/tests/files/render/hair/eevee_renders/hair_linear_close_up.png +++ b/tests/files/render/hair/eevee_renders/hair_linear_close_up.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:c513bdd910ea8b13ab345675265c725730a691b7c98092ecc1f472351277ad03 -size 15024 +oid sha256:b5826501374760133dcb2bdf1b27456ec32d525cee120dabf15069691c8f6e03 +size 14643 diff --git a/tests/files/render/hair/eevee_renders/hair_particle_random.png b/tests/files/render/hair/eevee_renders/hair_particle_random.png index 3d2e1c5296d..922fdb384c3 100644 --- a/tests/files/render/hair/eevee_renders/hair_particle_random.png +++ b/tests/files/render/hair/eevee_renders/hair_particle_random.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:883cf2c0f1ba28487aaa1c6cd8a1ecbba3ead83ae923fc0c2f8ecbad7b7c73b1 -size 21403 +oid sha256:35a6da6fdfbe3949a52cf34a5ed1532a4c8db28041e5c36f8942f8ca18d9aa70 +size 22269 diff --git a/tests/files/render/hair/eevee_renders/hair_ribbon_close_up.png b/tests/files/render/hair/eevee_renders/hair_ribbon_close_up.png index 72c88d0ec6d..1ad6c9113f5 100644 --- a/tests/files/render/hair/eevee_renders/hair_ribbon_close_up.png +++ b/tests/files/render/hair/eevee_renders/hair_ribbon_close_up.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:4665d515fd1f27debf5a5236699e133c0c4ca92bbe0992ed8ee4a171c6d7a08f -size 15003 +oid sha256:f40392533dfa319dd0ea772a7051fcd4eba4917df755615e296843ee30985032 +size 14643 diff --git a/tests/files/render/hair/eevee_renders/microfacet_hair_orientation.png b/tests/files/render/hair/eevee_renders/microfacet_hair_orientation.png index 74917a8e662..f4f249bd1eb 100644 --- a/tests/files/render/hair/eevee_renders/microfacet_hair_orientation.png +++ b/tests/files/render/hair/eevee_renders/microfacet_hair_orientation.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:0d2d3a1b8fb2781fed0be12947b00be71ff75d9f85f48caaadcb205285fb126f -size 16608 +oid sha256:363d063adb978041756180433ed5ee12e3ddf1733329aab5e106e7bdffc404c9 +size 16840 diff --git a/tests/files/render/hair/eevee_renders/principled_hair_absorptioncoefficient.png b/tests/files/render/hair/eevee_renders/principled_hair_absorptioncoefficient.png index 9fb89a946ff..79e23cebc51 100644 --- a/tests/files/render/hair/eevee_renders/principled_hair_absorptioncoefficient.png +++ b/tests/files/render/hair/eevee_renders/principled_hair_absorptioncoefficient.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:6d0588e2f7ba68a1072bcca68d5102f6cca5cea7b38a2bf351756c435bec0297 -size 4665 +oid sha256:8932c041590ceac6ec401e66d4c82aa33f0a1a4949adefc7db9db564b095cff3 +size 4707 diff --git a/tests/files/render/hair/eevee_renders/principled_hair_directcoloring.png b/tests/files/render/hair/eevee_renders/principled_hair_directcoloring.png index 831d2121526..65f4d6fc370 100644 --- a/tests/files/render/hair/eevee_renders/principled_hair_directcoloring.png +++ b/tests/files/render/hair/eevee_renders/principled_hair_directcoloring.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:02e47442397874dc35baa9ea71b5ad62b6ddb2f93bdf604e4b39be0432b85280 -size 5018 +oid sha256:aac865336abd500ba234efdb0f1a34d277f3217968c42aa624f5c13522f79150 +size 5125 diff --git a/tests/files/render/hair/eevee_renders/principled_hair_melaninconcentration.png b/tests/files/render/hair/eevee_renders/principled_hair_melaninconcentration.png index 6bb05bc9621..e382ad4f864 100644 --- a/tests/files/render/hair/eevee_renders/principled_hair_melaninconcentration.png +++ b/tests/files/render/hair/eevee_renders/principled_hair_melaninconcentration.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:0d2e0db7d29124f8d4a0bbf24b182bf1e032c241ee8612c95cdee8de388dcca2 -size 4664 +oid sha256:ac63e694d7469b270ebdcdd9413cabdc8eb7528ebad2b1247f4b10a999a2d196 +size 4706 diff --git a/tests/files/render/hair/workbench_renders/hair_basemesh_intercept.png b/tests/files/render/hair/workbench_renders/hair_basemesh_intercept.png index a5215aa9a4e..dce62f7d07f 100644 --- a/tests/files/render/hair/workbench_renders/hair_basemesh_intercept.png +++ b/tests/files/render/hair/workbench_renders/hair_basemesh_intercept.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:374f17d11ce3a548a334d63b7eb1134311cd07e696b34de049adc96e12de815e -size 9405 +oid sha256:b016d0b31db6acb2df361973195adb5166a8eb0c1fdc60e3e49ba05539c78b5a +size 9245 diff --git a/tests/files/render/hair/workbench_renders/hair_close_up.png b/tests/files/render/hair/workbench_renders/hair_close_up.png index 8b88587e710..4b394fe0ac4 100644 --- a/tests/files/render/hair/workbench_renders/hair_close_up.png +++ b/tests/files/render/hair/workbench_renders/hair_close_up.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:11b694d2394d38fb386e98f7e1e38c5e815dc7c1a4f6047a533746b95d751796 -size 12315 +oid sha256:a0d301150802c315707aaa1e16ae99943702ea2121bf4a734fe8997fc3b97170 +size 13528 diff --git a/tests/files/render/hair/workbench_renders/hair_info.png b/tests/files/render/hair/workbench_renders/hair_info.png index cefc7c6d738..d22b3475748 100644 --- a/tests/files/render/hair/workbench_renders/hair_info.png +++ b/tests/files/render/hair/workbench_renders/hair_info.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:bed86fce76e2f27223a10bfdfeb4a039e6514cfdb86e08dbcc6694d86b782221 -size 13011 +oid sha256:083580c49ecbb648890ae4ac62e7b0b24caebe5392d12df79c997b5c41c6c8de +size 14218 diff --git a/tests/files/render/hair/workbench_renders/hair_instancer_uv.png b/tests/files/render/hair/workbench_renders/hair_instancer_uv.png index 26cb5df7602..3b0e89e5c3f 100644 --- a/tests/files/render/hair/workbench_renders/hair_instancer_uv.png +++ b/tests/files/render/hair/workbench_renders/hair_instancer_uv.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:c6fd3bcf7fca547bc6135045da86c914848755a0e7ecb62f260b7d92a513625a -size 21578 +oid sha256:521d70f5fc737f7a5473f72d34c22ccc23f98f429a9caa0eb881f9c5d70b8b45 +size 21879 diff --git a/tests/files/render/hair/workbench_renders/hair_linear_close_up.png b/tests/files/render/hair/workbench_renders/hair_linear_close_up.png index a9a87a9fd9e..fb30cb4cd5f 100644 --- a/tests/files/render/hair/workbench_renders/hair_linear_close_up.png +++ b/tests/files/render/hair/workbench_renders/hair_linear_close_up.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:061b4ae88786d7c268954c1598b35995a743ca923f68397f6daf936726f05621 -size 12464 +oid sha256:877f598d33aa55e6918ee6b59a12a68aecd109707c4cb01dfd98a4ef4472a7be +size 13535 diff --git a/tests/files/render/hair/workbench_renders/hair_particle_random.png b/tests/files/render/hair/workbench_renders/hair_particle_random.png index 20b1c718f0d..493892110f4 100644 --- a/tests/files/render/hair/workbench_renders/hair_particle_random.png +++ b/tests/files/render/hair/workbench_renders/hair_particle_random.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:97f3d2a706d090dbe3f4802fd262f543b45284b960a4432450cc555418390800 -size 17067 +oid sha256:22c603c2bf180ddad3b37d4cfe30f67bbdcef91aa578531c6a438674aaa4d0b1 +size 17380 diff --git a/tests/files/render/hair/workbench_renders/hair_ribbon_close_up.png b/tests/files/render/hair/workbench_renders/hair_ribbon_close_up.png index 9af319270a9..3c4aa730015 100644 --- a/tests/files/render/hair/workbench_renders/hair_ribbon_close_up.png +++ b/tests/files/render/hair/workbench_renders/hair_ribbon_close_up.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:549b93059d41930d5289565eef7504f235e78f361a81c8a66f67aaed3babb8c9 -size 12322 +oid sha256:424190a7b1c358ec2b8f12cd2fd589df8bcafa932e3a7b8f26487ebcd90f1a29 +size 13535 diff --git a/tests/files/render/hair/workbench_renders/microfacet_hair_orientation.png b/tests/files/render/hair/workbench_renders/microfacet_hair_orientation.png index 4dd43ccbac4..a3e791c8f8f 100644 --- a/tests/files/render/hair/workbench_renders/microfacet_hair_orientation.png +++ b/tests/files/render/hair/workbench_renders/microfacet_hair_orientation.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:a49b8f56cd208a19d14edb398ebc2bf09640f18bc5ff4bec3658370fbc22a802 -size 15065 +oid sha256:329c5dd33108cdb6b3297ae347dce4cd28b2fb63f8ddd66fd813fcabfe7315fc +size 15031 diff --git a/tests/files/render/hair/workbench_renders/principled_hair_absorptioncoefficient.png b/tests/files/render/hair/workbench_renders/principled_hair_absorptioncoefficient.png index 76492db0b7c..b6da6e622fb 100644 --- a/tests/files/render/hair/workbench_renders/principled_hair_absorptioncoefficient.png +++ b/tests/files/render/hair/workbench_renders/principled_hair_absorptioncoefficient.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:3f669b5a42f745d3c02827f5b584d11996da02059ad9dac91ca720fa7bfcfa6e -size 5938 +oid sha256:885ccfb73f077c9ba6afe77da6bc4cd2e4fe7ff4ef53c51d285745362e8ef6fe +size 6106 diff --git a/tests/files/render/hair/workbench_renders/principled_hair_directcoloring.png b/tests/files/render/hair/workbench_renders/principled_hair_directcoloring.png index 6b9909c256e..ec62b2017e4 100644 --- a/tests/files/render/hair/workbench_renders/principled_hair_directcoloring.png +++ b/tests/files/render/hair/workbench_renders/principled_hair_directcoloring.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:2b342329c5a581e037345d1fe89c4941488e87bd81a68f45c6d976a9e2c0880e -size 5931 +oid sha256:70bbaed2ecb933d5155dfa069bc7047fc3067f8e14e29ed57958e876bf7a8584 +size 6099 diff --git a/tests/files/render/hair/workbench_renders/principled_hair_melaninconcentration.png b/tests/files/render/hair/workbench_renders/principled_hair_melaninconcentration.png index c8d0ed52460..86fba56dd8f 100644 --- a/tests/files/render/hair/workbench_renders/principled_hair_melaninconcentration.png +++ b/tests/files/render/hair/workbench_renders/principled_hair_melaninconcentration.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:332fbe6e6231d1e2ee6d9ad0f9629712298ed96a4d9bbc1e846aa635fe023cb4 -size 5937 +oid sha256:33b568c96971092f6e4dac7de083fa673f9107e280ecad38ef00ba62d308545a +size 6105 diff --git a/tests/files/render/light_linking/eevee_renders/shadow_link_simple_3D_curve.png b/tests/files/render/light_linking/eevee_renders/shadow_link_simple_3D_curve.png index da8b34820db..fa4b5fa3c12 100644 --- a/tests/files/render/light_linking/eevee_renders/shadow_link_simple_3D_curve.png +++ b/tests/files/render/light_linking/eevee_renders/shadow_link_simple_3D_curve.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:bc73ea3ebbd3f6a40f83d9fce52634b76ee8f79631b01248ec741d86d7801843 -size 17041 +oid sha256:9d1be27a94c8d01bdd85d3f63b964e1e469a2ab3baf4d6449260100dd79b6a2c +size 16975 diff --git a/tests/files/render/light_linking/eevee_renders/shadow_link_simple_ribbon_curve.png b/tests/files/render/light_linking/eevee_renders/shadow_link_simple_ribbon_curve.png index dcb473ffc52..10e00a439d1 100644 --- a/tests/files/render/light_linking/eevee_renders/shadow_link_simple_ribbon_curve.png +++ b/tests/files/render/light_linking/eevee_renders/shadow_link_simple_ribbon_curve.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:18913437c43a66de9cb80408d8678b458df5ee2fa651c898228691f4e6c41918 -size 17045 +oid sha256:1226b7fd9c7edd0cf4c69618287f8bb043074fcbb40af55e6409991732d71904 +size 16979 diff --git a/tests/files/render/light_linking/workbench_renders/shadow_link_simple_3D_curve.png b/tests/files/render/light_linking/workbench_renders/shadow_link_simple_3D_curve.png index 2b2c5f4662a..d69f2924854 100644 --- a/tests/files/render/light_linking/workbench_renders/shadow_link_simple_3D_curve.png +++ b/tests/files/render/light_linking/workbench_renders/shadow_link_simple_3D_curve.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:93dc1ae17f801b1e1539c53f64a61cc4eb6cc23b86397e19a21b2b9154781ad1 -size 3628 +oid sha256:f5afce913f4a4eff8571dcd36966ad4adf228fd6471032780f2c7ddc8df8b478 +size 5438 diff --git a/tests/files/render/light_linking/workbench_renders/shadow_link_simple_ribbon_curve.png b/tests/files/render/light_linking/workbench_renders/shadow_link_simple_ribbon_curve.png index b5484d97e10..1ba38d420ec 100644 --- a/tests/files/render/light_linking/workbench_renders/shadow_link_simple_ribbon_curve.png +++ b/tests/files/render/light_linking/workbench_renders/shadow_link_simple_ribbon_curve.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:6e94ca630b24667154cd19e6882cea76e3d1c71e9c893af1beda9a18d1065b49 -size 3632 +oid sha256:7864b49dafdf31dc003c1f7674af82ce887974dd376e046049f245e9455fdb92 +size 5442 diff --git a/tests/files/render/motion_blur/eevee_renders/curve_motion_blur.png b/tests/files/render/motion_blur/eevee_renders/curve_motion_blur.png index 861796d28b9..02ad4bacdf5 100644 --- a/tests/files/render/motion_blur/eevee_renders/curve_motion_blur.png +++ b/tests/files/render/motion_blur/eevee_renders/curve_motion_blur.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:7f35d711b8f3b75b2f07638d6b0ffd4e282acb1033a8de1286b1bc7e7a79a85c -size 19493 +oid sha256:05465bbeeddc950fd55124428e257cf837201a53e4b7c24cb753f3b5b8bde5f6 +size 19298 diff --git a/tests/files/render/motion_blur/workbench_renders/bvh_steps_curve_segments_0.png b/tests/files/render/motion_blur/workbench_renders/bvh_steps_curve_segments_0.png index 68874aa537a..a1fec4050fa 100644 --- a/tests/files/render/motion_blur/workbench_renders/bvh_steps_curve_segments_0.png +++ b/tests/files/render/motion_blur/workbench_renders/bvh_steps_curve_segments_0.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:15a2eefd097fa3e6eed187830863266d24d284c8e8fcb800ce18f912f9b36146 -size 4306 +oid sha256:1fe2926527ea677c3d5f424fdd121f030cac094b4b38fedcf720824a0eca0c5f +size 4431 diff --git a/tests/files/render/motion_blur/workbench_renders/bvh_steps_curve_segments_3.png b/tests/files/render/motion_blur/workbench_renders/bvh_steps_curve_segments_3.png index 1d8102a72c4..ce4b1043d35 100644 --- a/tests/files/render/motion_blur/workbench_renders/bvh_steps_curve_segments_3.png +++ b/tests/files/render/motion_blur/workbench_renders/bvh_steps_curve_segments_3.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:358afaf57954d6fae5d9b6ce297362c1f4ef8bcbed6244e75af4750c7c285421 -size 4306 +oid sha256:5d26140f62cb87d3e88c9f40597252d454cd6a9de4ade1d4cc42b979f6028f4b +size 4431 diff --git a/tests/files/render/motion_blur/workbench_renders/bvh_steps_line_segments_0.png b/tests/files/render/motion_blur/workbench_renders/bvh_steps_line_segments_0.png index db08426d47a..a509431bb77 100644 --- a/tests/files/render/motion_blur/workbench_renders/bvh_steps_line_segments_0.png +++ b/tests/files/render/motion_blur/workbench_renders/bvh_steps_line_segments_0.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:1c257042d9679e32ff101e5ec270339a93fb290cc4a131c49b33b35ee5e3aea0 -size 4305 +oid sha256:34c549e85c9a5e6ddd17d8d38360b1f2f0aad1cd51c03ca4b50dfa1e9389c46d +size 4430 diff --git a/tests/files/render/motion_blur/workbench_renders/bvh_steps_line_segments_3.png b/tests/files/render/motion_blur/workbench_renders/bvh_steps_line_segments_3.png index 8d0707c3faf..5ae044655cb 100644 --- a/tests/files/render/motion_blur/workbench_renders/bvh_steps_line_segments_3.png +++ b/tests/files/render/motion_blur/workbench_renders/bvh_steps_line_segments_3.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:dcc9916f19645a4b86f9196bd986cb226ccd6d17f00a777cf66226aae7f6dc97 -size 4305 +oid sha256:2e66c79c2097497765f6c8f7cefc563bdd2029e1557c1ed6cd67e07b1a327484 +size 4430 diff --git a/tests/files/render/motion_blur/workbench_renders/curve_motion_blur.png b/tests/files/render/motion_blur/workbench_renders/curve_motion_blur.png index 158891390b5..1522447db97 100644 --- a/tests/files/render/motion_blur/workbench_renders/curve_motion_blur.png +++ b/tests/files/render/motion_blur/workbench_renders/curve_motion_blur.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:a87a920d43eeda848e67b57febd9171ea0959074a821a2d6739be0035f39950a -size 7970 +oid sha256:36d3a9b97a3f642b3278322f98773aba357e1ee2dfc68c21ee15c2f17ea81fcd +size 8110 diff --git a/tests/files/render/render_layer/workbench_renders/rlayer_flags_01.png b/tests/files/render/render_layer/workbench_renders/rlayer_flags_01.png index 0040ef78614..221832aa683 100644 --- a/tests/files/render/render_layer/workbench_renders/rlayer_flags_01.png +++ b/tests/files/render/render_layer/workbench_renders/rlayer_flags_01.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:02b3eea25f0655adbda24555211c9a4840a03df92a80fcf6c662fea14cb9ae63 -size 8057 +oid sha256:df1585ed0e10d46da837a498ec1ef116ccb129409d56d2e6253ed82de36c615c +size 8170 diff --git a/tests/files/render/render_layer/workbench_renders/rlayer_flags_02.png b/tests/files/render/render_layer/workbench_renders/rlayer_flags_02.png index 169ce6b246a..dd46ead0b6d 100644 --- a/tests/files/render/render_layer/workbench_renders/rlayer_flags_02.png +++ b/tests/files/render/render_layer/workbench_renders/rlayer_flags_02.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:ebb19b77d0417f6175702b79fb95abb3f2fcc584a0c2ad060b00151b3cf4d1fe -size 8057 +oid sha256:643150a770717b063c655be087c34b4d882c5b3c36a8e85dd1a6938e106cbb9f +size 8170 diff --git a/tests/files/render/render_layer/workbench_renders/rlayer_flags_03.png b/tests/files/render/render_layer/workbench_renders/rlayer_flags_03.png index e0bf1a5ce29..f5c18b2cb44 100644 --- a/tests/files/render/render_layer/workbench_renders/rlayer_flags_03.png +++ b/tests/files/render/render_layer/workbench_renders/rlayer_flags_03.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:8c9e26866fa10c0d6420c0b9d09b9441dbd3330ef1ae177417e89861e17e9255 -size 8057 +oid sha256:fe0625eacc3afaf7221d6588fe3e3fc3f7be2cc01279b1a7a4e1b74e97d0c167 +size 8170 diff --git a/tests/files/render/render_layer/workbench_renders/rlayer_flags_04.png b/tests/files/render/render_layer/workbench_renders/rlayer_flags_04.png index fac25f84fb4..0133cf64f72 100644 --- a/tests/files/render/render_layer/workbench_renders/rlayer_flags_04.png +++ b/tests/files/render/render_layer/workbench_renders/rlayer_flags_04.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:9c38b413887e4f067d60937231de84bf0f6c869b2175f658a51b773311945451 -size 8057 +oid sha256:91e9e010670be322bfecae74bdcfbe82962803228d4e5d8f402c6c0efaee6a6e +size 8170