DRW: New Curve Drawing
Implementation of the design task #142969. This adds the following: - Exact GPU interpolation of curves of all types. - Radius attribute support. - Cyclic curve support. - Resolution attribute support. - New Cylinder hair shape type.  What changed: - EEVEE doesn't compute random normals for strand hairs anymore. These are considered legacy now. - EEVEE now have an internal shadow bias to avoid self shadowing on hair. - Workbench Curves Strip display option is no longer flat and has better shading. - Legacy Hair particle system evaluates radius at control points before applying additional subdivision. This now matches Cycles. - Color Attribute Node without a name do not fetch the active color attribute anymore. This now matches Cycles. Notes: - This is not 100% matching the CPU implementation for interpolation (see the epsilons in the tests). - Legacy Hair Particle points is now stored in local space after interpolation. The new cylinder shape allows for more correct hair shading in workbench and better intersection in EEVEE. | | Strand | Strip | Cylinder | | ---- | --- | --- | --- | | Main |  |  | N/A | | PR |  |  |  | | | Strand | Strip | Cylinder | | ---- | --- | --- | --- | | Main |  | | N/A | | PR | ||  | Cyclic Curve, Mixed curve type, and proper radius support:  Test file for attribute lookup: [test_attribute_lookup.blend](/attachments/1d54dd06-379b-4480-a1c5-96adc1953f77) Follow Up Tasks: - Correct full tube segments orientation based on tangent and normal attributes - Correct V resolution property per object - More attribute type support (currently only color) TODO: - [x] Attribute Loading Changes - [x] Generic Attributes - [x] Length Attribute - [x] Intercept Attribute - [x] Original Coordinate Attribute - [x] Cyclic Curves - [x] Legacy Hair Particle conversion - [x] Attribute Loading - [x] Additional Subdivision - [x] Move some function to generic headers (VertBuf, OffsetIndices) - [x] Fix default UV/Color attribute assignment Pull Request: https://projects.blender.org/blender/blender/pulls/143180
This commit is contained in:
committed by
Clément Foucault
parent
15da5dbaf8
commit
ba4589e894
@@ -18,7 +18,7 @@ using OpenSubdiv::Osd::PatchArrayVector;
|
||||
namespace blender::opensubdiv {
|
||||
|
||||
// TODO: use Blenlib NonCopyable.
|
||||
class GPUPatchTable : private NonCopyable<GPUPatchTable> {
|
||||
class GPUPatchTable : private OpenSubdiv::Osd::NonCopyable<GPUPatchTable> {
|
||||
public:
|
||||
~GPUPatchTable();
|
||||
|
||||
|
||||
@@ -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):
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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. */
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
/** \} */
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -37,7 +37,7 @@ void main()
|
||||
#endif
|
||||
|
||||
init_globals();
|
||||
attrib_load();
|
||||
attrib_load(MeshVertex(0));
|
||||
|
||||
interp.P += nodetree_displacement();
|
||||
|
||||
|
||||
@@ -47,7 +47,7 @@ void main()
|
||||
#endif
|
||||
|
||||
init_globals();
|
||||
attrib_load();
|
||||
attrib_load(PointCloudPoint(0));
|
||||
|
||||
interp.P += nodetree_displacement();
|
||||
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -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)
|
||||
{
|
||||
|
||||
@@ -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()
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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
|
||||
|
||||
103
source/blender/draw/intern/draw_attribute_shader_shared.hh
Normal file
103
source/blender/draw/intern/draw_attribute_shader_shared.hh
Normal file
@@ -0,0 +1,103 @@
|
||||
/* SPDX-FileCopyrightText: 2025 Blender Authors
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
#pragma once
|
||||
|
||||
#ifndef GPU_SHADER
|
||||
# include "GPU_shader_shared_utils.hh"
|
||||
#endif
|
||||
|
||||
/* Copy of DNA enum in `DNA_curves_types.h`. */
|
||||
enum CurveType : uint32_t {
|
||||
CURVE_TYPE_CATMULL_ROM = 0u,
|
||||
CURVE_TYPE_POLY = 1u,
|
||||
CURVE_TYPE_BEZIER = 2u,
|
||||
CURVE_TYPE_NURBS = 3u,
|
||||
};
|
||||
|
||||
/* -------------------------------------------------------------------- */
|
||||
/** \name Generic Attribute
|
||||
*
|
||||
* These types are necessary to overcome the issue with float3 alignment on GPU.
|
||||
* Having all types using the same interface allows the usage of templates to load and manipulate
|
||||
* them inside the shaders.
|
||||
* \{ */
|
||||
|
||||
struct StoredFloat4 {
|
||||
float x;
|
||||
float y;
|
||||
float z;
|
||||
float w;
|
||||
};
|
||||
|
||||
struct StoredFloat3 {
|
||||
float x;
|
||||
float y;
|
||||
float z;
|
||||
};
|
||||
|
||||
struct StoredFloat2 {
|
||||
float x;
|
||||
float y;
|
||||
};
|
||||
|
||||
struct StoredFloat {
|
||||
float x;
|
||||
};
|
||||
|
||||
float4 load_data(StoredFloat4 data)
|
||||
{
|
||||
return float4(data.x, data.y, data.z, data.w);
|
||||
}
|
||||
|
||||
float3 load_data(StoredFloat3 data)
|
||||
{
|
||||
return float3(data.x, data.y, data.z);
|
||||
}
|
||||
|
||||
float2 load_data(StoredFloat2 data)
|
||||
{
|
||||
return float2(data.x, data.y);
|
||||
}
|
||||
|
||||
float load_data(StoredFloat data)
|
||||
{
|
||||
return float(data.x);
|
||||
}
|
||||
|
||||
StoredFloat4 as_data(float4 interp)
|
||||
{
|
||||
StoredFloat4 data;
|
||||
data.x = interp.x;
|
||||
data.y = interp.y;
|
||||
data.z = interp.z;
|
||||
data.w = interp.w;
|
||||
return data;
|
||||
}
|
||||
|
||||
StoredFloat3 as_data(float3 interp)
|
||||
{
|
||||
StoredFloat3 data;
|
||||
data.x = interp.x;
|
||||
data.y = interp.y;
|
||||
data.z = interp.z;
|
||||
return data;
|
||||
}
|
||||
|
||||
StoredFloat2 as_data(float2 interp)
|
||||
{
|
||||
StoredFloat2 data;
|
||||
data.x = interp.x;
|
||||
data.y = interp.y;
|
||||
return data;
|
||||
}
|
||||
|
||||
StoredFloat as_data(float interp)
|
||||
{
|
||||
StoredFloat data;
|
||||
data.x = interp;
|
||||
return data;
|
||||
}
|
||||
|
||||
/** \} */
|
||||
@@ -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);
|
||||
|
||||
@@ -20,7 +20,6 @@
|
||||
#include "BLI_span.hh"
|
||||
#include "BLI_string_utf8.h"
|
||||
#include "BLI_task.hh"
|
||||
#include "BLI_utildefines.h"
|
||||
|
||||
#include "DNA_curves_types.h"
|
||||
#include "DNA_object_types.h"
|
||||
@@ -44,7 +43,9 @@
|
||||
#include "draw_attributes.hh"
|
||||
#include "draw_cache_impl.hh" /* own include */
|
||||
#include "draw_cache_inline.hh"
|
||||
#include "draw_context_private.hh"
|
||||
#include "draw_curves_private.hh" /* own include */
|
||||
#include "draw_hair_private.hh"
|
||||
|
||||
namespace blender::draw {
|
||||
|
||||
@@ -117,19 +118,6 @@ static void init_batch_cache(Curves &curves)
|
||||
cache->is_dirty = false;
|
||||
}
|
||||
|
||||
static void discard_attributes(CurvesEvalCache &eval_cache)
|
||||
{
|
||||
for (const int i : IndexRange(GPU_MAX_ATTR)) {
|
||||
GPU_VERTBUF_DISCARD_SAFE(eval_cache.proc_attributes_buf[i]);
|
||||
}
|
||||
|
||||
for (const int j : IndexRange(GPU_MAX_ATTR)) {
|
||||
GPU_VERTBUF_DISCARD_SAFE(eval_cache.final.attributes_buf[j]);
|
||||
}
|
||||
|
||||
eval_cache.final.attr_used.clear();
|
||||
}
|
||||
|
||||
static void clear_edit_data(CurvesBatchCache *cache)
|
||||
{
|
||||
/* TODO: more granular update tagging. */
|
||||
@@ -149,26 +137,47 @@ static void clear_edit_data(CurvesBatchCache *cache)
|
||||
GPU_BATCH_DISCARD_SAFE(cache->edit_curves_lines);
|
||||
}
|
||||
|
||||
static void clear_final_data(CurvesEvalFinalCache &final_cache)
|
||||
void CurvesEvalCache::discard_attributes()
|
||||
{
|
||||
GPU_VERTBUF_DISCARD_SAFE(final_cache.proc_buf);
|
||||
GPU_BATCH_DISCARD_SAFE(final_cache.proc_hairs);
|
||||
for (const int j : IndexRange(GPU_MAX_ATTR)) {
|
||||
GPU_VERTBUF_DISCARD_SAFE(final_cache.attributes_buf[j]);
|
||||
for (const int i : IndexRange(GPU_MAX_ATTR)) {
|
||||
this->evaluated_attributes_buf[i].reset();
|
||||
}
|
||||
for (const int i : IndexRange(GPU_MAX_ATTR)) {
|
||||
this->curve_attributes_buf[i].reset();
|
||||
}
|
||||
this->attr_used.clear();
|
||||
}
|
||||
|
||||
static void clear_eval_data(CurvesEvalCache &eval_cache)
|
||||
void CurvesEvalCache::clear()
|
||||
{
|
||||
/* TODO: more granular update tagging. */
|
||||
GPU_VERTBUF_DISCARD_SAFE(eval_cache.proc_point_buf);
|
||||
GPU_VERTBUF_DISCARD_SAFE(eval_cache.proc_length_buf);
|
||||
GPU_VERTBUF_DISCARD_SAFE(eval_cache.proc_strand_buf);
|
||||
GPU_VERTBUF_DISCARD_SAFE(eval_cache.proc_strand_seg_buf);
|
||||
this->evaluated_pos_rad_buf.reset();
|
||||
this->evaluated_time_buf.reset();
|
||||
this->curves_length_buf.reset();
|
||||
|
||||
clear_final_data(eval_cache.final);
|
||||
this->points_by_curve_buf.reset();
|
||||
this->evaluated_points_by_curve_buf.reset();
|
||||
this->curves_type_buf.reset();
|
||||
this->curves_resolution_buf.reset();
|
||||
this->curves_cyclic_buf.reset();
|
||||
|
||||
discard_attributes(eval_cache);
|
||||
this->handles_positions_left_buf.reset();
|
||||
this->handles_positions_right_buf.reset();
|
||||
this->bezier_offsets_buf.reset();
|
||||
|
||||
this->curves_order_buf.reset();
|
||||
this->control_weights_buf.reset();
|
||||
this->basis_cache_buf.reset();
|
||||
this->basis_cache_offset_buf.reset();
|
||||
|
||||
this->indirection_cylinder_buf.reset();
|
||||
this->indirection_ribbon_buf.reset();
|
||||
|
||||
for (gpu::Batch *&batch : this->batch) {
|
||||
GPU_BATCH_DISCARD_SAFE(batch);
|
||||
}
|
||||
|
||||
this->discard_attributes();
|
||||
}
|
||||
|
||||
static void clear_batch_cache(Curves &curves)
|
||||
@@ -178,7 +187,7 @@ static void clear_batch_cache(Curves &curves)
|
||||
return;
|
||||
}
|
||||
|
||||
clear_eval_data(cache->eval_cache);
|
||||
cache->eval_cache.clear();
|
||||
clear_edit_data(cache);
|
||||
}
|
||||
|
||||
@@ -188,69 +197,6 @@ static CurvesBatchCache &get_batch_cache(Curves &curves)
|
||||
return *static_cast<CurvesBatchCache *>(curves.batch_cache);
|
||||
}
|
||||
|
||||
struct PositionAndParameter {
|
||||
float3 position;
|
||||
float parameter;
|
||||
};
|
||||
|
||||
static void fill_points_position_time_vbo(const OffsetIndices<int> points_by_curve,
|
||||
const Span<float3> positions,
|
||||
MutableSpan<PositionAndParameter> posTime_data,
|
||||
MutableSpan<float> hairLength_data)
|
||||
{
|
||||
threading::parallel_for(points_by_curve.index_range(), 1024, [&](const IndexRange range) {
|
||||
for (const int i_curve : range) {
|
||||
const IndexRange points = points_by_curve[i_curve];
|
||||
|
||||
Span<float3> curve_positions = positions.slice(points);
|
||||
MutableSpan<PositionAndParameter> curve_posTime_data = posTime_data.slice(points);
|
||||
|
||||
float total_len = 0.0f;
|
||||
for (const int i_point : curve_positions.index_range()) {
|
||||
if (i_point > 0) {
|
||||
total_len += math::distance(curve_positions[i_point - 1], curve_positions[i_point]);
|
||||
}
|
||||
curve_posTime_data[i_point].position = curve_positions[i_point];
|
||||
curve_posTime_data[i_point].parameter = total_len;
|
||||
}
|
||||
hairLength_data[i_curve] = total_len;
|
||||
|
||||
/* Assign length value. */
|
||||
if (total_len > 0.0f) {
|
||||
const float factor = 1.0f / total_len;
|
||||
/* Divide by total length to have a [0-1] number. */
|
||||
for (const int i_point : curve_positions.index_range()) {
|
||||
curve_posTime_data[i_point].parameter *= factor;
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
static void create_points_position_time_vbo(const bke::CurvesGeometry &curves,
|
||||
CurvesEvalCache &cache)
|
||||
{
|
||||
GPUVertFormat format = {0};
|
||||
GPU_vertformat_attr_add(&format, "posTime", gpu::VertAttrType::SFLOAT_32_32_32_32);
|
||||
|
||||
cache.proc_point_buf = GPU_vertbuf_create_with_format_ex(
|
||||
format, GPU_USAGE_STATIC | GPU_USAGE_FLAG_BUFFER_TEXTURE_ONLY);
|
||||
GPU_vertbuf_data_alloc(*cache.proc_point_buf, cache.points_num);
|
||||
|
||||
GPUVertFormat length_format = {0};
|
||||
GPU_vertformat_attr_add(&length_format, "hairLength", blender::gpu::VertAttrType::SFLOAT_32);
|
||||
|
||||
cache.proc_length_buf = GPU_vertbuf_create_with_format_ex(
|
||||
length_format, GPU_USAGE_STATIC | GPU_USAGE_FLAG_BUFFER_TEXTURE_ONLY);
|
||||
GPU_vertbuf_data_alloc(*cache.proc_length_buf, cache.curves_num);
|
||||
|
||||
/* TODO: Only create hairLength VBO when necessary. */
|
||||
fill_points_position_time_vbo(curves.points_by_curve(),
|
||||
curves.positions(),
|
||||
cache.proc_point_buf->data<PositionAndParameter>(),
|
||||
cache.proc_length_buf->data<float>());
|
||||
}
|
||||
|
||||
static uint32_t bezier_data_value(int8_t handle_type, bool is_active)
|
||||
{
|
||||
return (handle_type << EDIT_CURVES_HANDLE_TYPES_SHIFT) | EDIT_CURVES_BEZIER_HANDLE |
|
||||
@@ -566,21 +512,18 @@ static void calc_edit_handles_ibo(const OffsetIndices<int> points_by_curve,
|
||||
&builder, 0, handles_and_points_num(points_num, bezier_offsets), false, &ibo);
|
||||
}
|
||||
|
||||
static void alloc_final_attribute_vbo(CurvesEvalCache &cache,
|
||||
const GPUVertFormat &format,
|
||||
const int index,
|
||||
const char * /*name*/)
|
||||
static gpu::VertBufPtr alloc_evaluated_point_attribute_vbo(const GPUVertFormat &format,
|
||||
const StringRef /*name*/,
|
||||
int64_t size)
|
||||
{
|
||||
cache.final.attributes_buf[index] = GPU_vertbuf_create_with_format_ex(
|
||||
format, GPU_USAGE_DEVICE_ONLY | GPU_USAGE_FLAG_BUFFER_TEXTURE_ONLY);
|
||||
|
||||
/* Create a destination buffer for the transform feedback. Sized appropriately */
|
||||
/* Those are points! not line segments. */
|
||||
GPU_vertbuf_data_alloc(*cache.final.attributes_buf[index],
|
||||
cache.final.resolution * cache.curves_num);
|
||||
gpu::VertBufPtr buf = gpu::VertBufPtr(GPU_vertbuf_create_with_format_ex(
|
||||
format, GPU_USAGE_DEVICE_ONLY | GPU_USAGE_FLAG_BUFFER_TEXTURE_ONLY));
|
||||
/* Create a destination buffer for the evaluation. Sized appropriately */
|
||||
GPU_vertbuf_data_alloc(*buf, size);
|
||||
return buf;
|
||||
}
|
||||
|
||||
static gpu::VertBufPtr ensure_control_point_attribute(const Curves &curves_id,
|
||||
static gpu::VertBufPtr ensure_control_point_attribute(const bke::CurvesGeometry &curves,
|
||||
const StringRef name,
|
||||
const GPUVertFormat &format,
|
||||
bool &r_is_point_domain)
|
||||
@@ -588,7 +531,6 @@ static gpu::VertBufPtr ensure_control_point_attribute(const Curves &curves_id,
|
||||
gpu::VertBufPtr vbo = gpu::VertBufPtr(GPU_vertbuf_create_with_format_ex(
|
||||
format, GPU_USAGE_STATIC | GPU_USAGE_FLAG_BUFFER_TEXTURE_ONLY));
|
||||
|
||||
const bke::CurvesGeometry &curves = curves_id.geometry.wrap();
|
||||
const bke::AttributeAccessor attributes = curves.wrap().attributes();
|
||||
|
||||
/* TODO(@kevindietrich): float4 is used for scalar attributes as the implicit conversion done
|
||||
@@ -597,7 +539,12 @@ static gpu::VertBufPtr ensure_control_point_attribute(const Curves &curves_id,
|
||||
* similar texture state swizzle to map the attribute correctly as for volume attributes, so we
|
||||
* can control the conversion ourselves. */
|
||||
const bke::AttributeReader<ColorGeometry4f> attribute = attributes.lookup<ColorGeometry4f>(name);
|
||||
|
||||
if (!attribute) {
|
||||
/* Attribute doesn't exist or is of an incompatible type.
|
||||
* Replace it with a black curve domain attribute. */
|
||||
/* TODO(fclem): Eventually, this should become unecessary if merge all attributes in one buffer
|
||||
* and use an indirection table. */
|
||||
GPU_vertbuf_data_alloc(*vbo, curves.curves_num());
|
||||
vbo->data<ColorGeometry4f>().fill({0.0f, 0.0f, 0.0f, 1.0f});
|
||||
r_is_point_domain = false;
|
||||
@@ -610,129 +557,6 @@ static gpu::VertBufPtr ensure_control_point_attribute(const Curves &curves_id,
|
||||
return vbo;
|
||||
}
|
||||
|
||||
static void ensure_final_attribute(const Curves &curves,
|
||||
const StringRef name,
|
||||
const int index,
|
||||
CurvesEvalCache &cache)
|
||||
{
|
||||
char sampler_name[32];
|
||||
drw_curves_get_attribute_sampler_name(name, sampler_name);
|
||||
|
||||
GPUVertFormat format = {0};
|
||||
/* All attributes use float4, see comment below. */
|
||||
GPU_vertformat_attr_add(&format, sampler_name, blender::gpu::VertAttrType::SFLOAT_32_32_32_32);
|
||||
|
||||
if (!cache.proc_attributes_buf[index]) {
|
||||
gpu::VertBufPtr vbo = ensure_control_point_attribute(
|
||||
curves, name, format, cache.proc_attributes_point_domain[index]);
|
||||
cache.proc_attributes_buf[index] = vbo.release();
|
||||
}
|
||||
|
||||
/* Existing final data may have been for a different attribute (with a different name or domain),
|
||||
* free the data. */
|
||||
GPU_VERTBUF_DISCARD_SAFE(cache.final.attributes_buf[index]);
|
||||
|
||||
/* Ensure final data for points. */
|
||||
if (cache.proc_attributes_point_domain[index]) {
|
||||
alloc_final_attribute_vbo(cache, format, index, sampler_name);
|
||||
}
|
||||
}
|
||||
|
||||
static void fill_curve_offsets_vbos(const OffsetIndices<int> points_by_curve,
|
||||
GPUVertBufRaw &data_step,
|
||||
GPUVertBufRaw &seg_step)
|
||||
{
|
||||
for (const int i : points_by_curve.index_range()) {
|
||||
const IndexRange points = points_by_curve[i];
|
||||
|
||||
*(uint *)GPU_vertbuf_raw_step(&data_step) = points.start();
|
||||
*(uint *)GPU_vertbuf_raw_step(&seg_step) = points.size() - 1;
|
||||
}
|
||||
}
|
||||
|
||||
static void create_curve_offsets_vbos(const OffsetIndices<int> points_by_curve,
|
||||
CurvesEvalCache &cache)
|
||||
{
|
||||
GPUVertBufRaw data_step, seg_step;
|
||||
|
||||
GPUVertFormat format_data = {0};
|
||||
uint data_id = GPU_vertformat_attr_add(
|
||||
&format_data, "data", blender::gpu::VertAttrType::UINT_32);
|
||||
|
||||
GPUVertFormat format_seg = {0};
|
||||
uint seg_id = GPU_vertformat_attr_add(&format_seg, "data", blender::gpu::VertAttrType::UINT_32);
|
||||
|
||||
/* Curve Data. */
|
||||
cache.proc_strand_buf = GPU_vertbuf_create_with_format_ex(
|
||||
format_data, GPU_USAGE_STATIC | GPU_USAGE_FLAG_BUFFER_TEXTURE_ONLY);
|
||||
GPU_vertbuf_data_alloc(*cache.proc_strand_buf, cache.curves_num);
|
||||
GPU_vertbuf_attr_get_raw_data(cache.proc_strand_buf, data_id, &data_step);
|
||||
|
||||
cache.proc_strand_seg_buf = GPU_vertbuf_create_with_format_ex(
|
||||
format_seg, GPU_USAGE_STATIC | GPU_USAGE_FLAG_BUFFER_TEXTURE_ONLY);
|
||||
GPU_vertbuf_data_alloc(*cache.proc_strand_seg_buf, cache.curves_num);
|
||||
GPU_vertbuf_attr_get_raw_data(cache.proc_strand_seg_buf, seg_id, &seg_step);
|
||||
|
||||
fill_curve_offsets_vbos(points_by_curve, data_step, seg_step);
|
||||
}
|
||||
|
||||
static void alloc_final_points_vbo(CurvesEvalCache &cache)
|
||||
{
|
||||
/* Same format as proc_point_buf. */
|
||||
GPUVertFormat format = {0};
|
||||
GPU_vertformat_attr_add(&format, "pos", gpu::VertAttrType::SFLOAT_32_32_32_32);
|
||||
|
||||
cache.final.proc_buf = GPU_vertbuf_create_with_format_ex(
|
||||
format, GPU_USAGE_DEVICE_ONLY | GPU_USAGE_FLAG_BUFFER_TEXTURE_ONLY);
|
||||
|
||||
/* Create a destination buffer for the transform feedback. Sized appropriately */
|
||||
|
||||
/* Those are points! not line segments. */
|
||||
uint point_len = cache.final.resolution * cache.curves_num;
|
||||
/* Avoid creating null sized VBO which can lead to crashes on certain platforms. */
|
||||
point_len = max_ii(1, point_len);
|
||||
|
||||
GPU_vertbuf_data_alloc(*cache.final.proc_buf, point_len);
|
||||
}
|
||||
|
||||
static void calc_final_indices(const bke::CurvesGeometry &curves,
|
||||
CurvesEvalCache &cache,
|
||||
const int thickness_res)
|
||||
{
|
||||
BLI_assert(thickness_res <= MAX_THICKRES); /* Cylinder strip not currently supported. */
|
||||
/* Determine prim type and element count.
|
||||
* NOTE: Metal backend uses non-restart prim types for optimal HW performance. */
|
||||
bool use_strip_prims = (GPU_backend_get_type() != GPU_BACKEND_METAL);
|
||||
int verts_per_curve;
|
||||
GPUPrimType prim_type;
|
||||
|
||||
if (use_strip_prims) {
|
||||
/* +1 for primitive restart */
|
||||
verts_per_curve = cache.final.resolution * thickness_res;
|
||||
prim_type = (thickness_res == 1) ? GPU_PRIM_LINE_STRIP : GPU_PRIM_TRI_STRIP;
|
||||
}
|
||||
else {
|
||||
/* Use full primitive type. */
|
||||
prim_type = (thickness_res == 1) ? GPU_PRIM_LINES : GPU_PRIM_TRIS;
|
||||
int verts_per_segment = ((prim_type == GPU_PRIM_LINES) ? 2 : 6);
|
||||
verts_per_curve = (cache.final.resolution - 1) * verts_per_segment;
|
||||
}
|
||||
|
||||
static const GPUVertFormat format = GPU_vertformat_from_attribute("dummy",
|
||||
gpu::VertAttrType::UINT_32);
|
||||
|
||||
gpu::VertBuf *vbo = GPU_vertbuf_create_with_format(format);
|
||||
GPU_vertbuf_data_alloc(*vbo, 1);
|
||||
|
||||
gpu::IndexBuf *ibo = nullptr;
|
||||
eGPUBatchFlag owns_flag = GPU_BATCH_OWNS_VBO;
|
||||
if (curves.curves_num()) {
|
||||
ibo = GPU_indexbuf_build_curves_on_device(prim_type, curves.curves_num(), verts_per_curve);
|
||||
owns_flag |= GPU_BATCH_OWNS_INDEX;
|
||||
}
|
||||
cache.final.proc_hairs = GPU_batch_create_ex(prim_type, vbo, ibo, owns_flag);
|
||||
}
|
||||
|
||||
static std::optional<StringRef> get_first_uv_name(const bke::AttributeAccessor &attributes)
|
||||
{
|
||||
std::optional<StringRef> name;
|
||||
@@ -745,12 +569,83 @@ static std::optional<StringRef> get_first_uv_name(const bke::AttributeAccessor &
|
||||
return name;
|
||||
}
|
||||
|
||||
static bool ensure_attributes(const Curves &curves,
|
||||
CurvesBatchCache &cache,
|
||||
const GPUMaterial *gpu_material)
|
||||
static void request_attribute(Curves &curves, const StringRef name)
|
||||
{
|
||||
const bke::AttributeAccessor attributes = curves.geometry.wrap().attributes();
|
||||
CurvesEvalFinalCache &final_cache = cache.eval_cache.final;
|
||||
CurvesEvalCache &cache = get_batch_cache(curves).eval_cache;
|
||||
|
||||
VectorSet<std::string> attributes{};
|
||||
|
||||
bke::CurvesGeometry &curves_geometry = curves.geometry.wrap();
|
||||
if (!curves_geometry.attributes().contains(name)) {
|
||||
return;
|
||||
}
|
||||
drw_attributes_add_request(&attributes, name);
|
||||
|
||||
drw_attributes_merge(&cache.attr_used, &attributes);
|
||||
}
|
||||
|
||||
void drw_curves_get_attribute_sampler_name(const StringRef layer_name, char r_sampler_name[32])
|
||||
{
|
||||
char attr_safe_name[GPU_MAX_SAFE_ATTR_NAME];
|
||||
GPU_vertformat_safe_attr_name(layer_name, attr_safe_name, GPU_MAX_SAFE_ATTR_NAME);
|
||||
/* Attributes use auto-name. */
|
||||
BLI_snprintf_utf8(r_sampler_name, 32, "a%s", attr_safe_name);
|
||||
}
|
||||
|
||||
void CurvesEvalCache::ensure_attribute(CurvesModule &module,
|
||||
const bke::CurvesGeometry &curves,
|
||||
const StringRef name,
|
||||
const int index)
|
||||
{
|
||||
char sampler_name[32];
|
||||
drw_curves_get_attribute_sampler_name(name, sampler_name);
|
||||
|
||||
GPUVertFormat format = {0};
|
||||
/* All attributes use float4, see comment below. */
|
||||
/* TODO(fclem): Other types. */
|
||||
GPU_vertformat_attr_add(&format, sampler_name, blender::gpu::VertAttrType::SFLOAT_32_32_32_32);
|
||||
|
||||
gpu::VertBufPtr attr_buf = ensure_control_point_attribute(
|
||||
curves, name, format, attributes_point_domain[index]);
|
||||
|
||||
/* Existing final data may have been for a different attribute (with a different name or domain),
|
||||
* free the data. */
|
||||
this->evaluated_attributes_buf[index].reset();
|
||||
|
||||
/* Ensure final data for points. */
|
||||
if (attributes_point_domain[index]) {
|
||||
this->ensure_common(curves);
|
||||
if (curves.has_curve_with_type(CURVE_TYPE_BEZIER)) {
|
||||
this->ensure_bezier(curves);
|
||||
}
|
||||
if (curves.has_curve_with_type(CURVE_TYPE_NURBS)) {
|
||||
this->ensure_nurbs(curves);
|
||||
}
|
||||
|
||||
this->evaluated_attributes_buf[index] = alloc_evaluated_point_attribute_vbo(
|
||||
format, name, evaluated_point_count_with_cyclic(curves));
|
||||
|
||||
module.evaluate_curve_attribute(curves.has_curve_with_type(CURVE_TYPE_CATMULL_ROM),
|
||||
curves.has_curve_with_type(CURVE_TYPE_BEZIER),
|
||||
curves.has_curve_with_type(CURVE_TYPE_POLY),
|
||||
curves.has_curve_with_type(CURVE_TYPE_NURBS),
|
||||
curves.has_cyclic_curve(),
|
||||
curves.curves_num(),
|
||||
*this,
|
||||
CURVES_EVAL_FLOAT4,
|
||||
std::move(attr_buf),
|
||||
this->evaluated_attributes_buf[index]);
|
||||
}
|
||||
else {
|
||||
this->curve_attributes_buf[index] = std::move(attr_buf);
|
||||
}
|
||||
}
|
||||
|
||||
void CurvesEvalCache::ensure_attributes(CurvesModule &module,
|
||||
const bke::CurvesGeometry &curves,
|
||||
const GPUMaterial *gpu_material)
|
||||
{
|
||||
const bke::AttributeAccessor attributes = curves.attributes();
|
||||
|
||||
if (gpu_material) {
|
||||
VectorSet<std::string> attrs_needed;
|
||||
@@ -768,108 +663,210 @@ static bool ensure_attributes(const Curves &curves,
|
||||
drw_attributes_add_request(&attrs_needed, name);
|
||||
}
|
||||
|
||||
if (!drw_attributes_overlap(&final_cache.attr_used, &attrs_needed)) {
|
||||
if (!drw_attributes_overlap(&attr_used, &attrs_needed)) {
|
||||
/* Some new attributes have been added, free all and start over. */
|
||||
for (const int i : IndexRange(GPU_MAX_ATTR)) {
|
||||
GPU_VERTBUF_DISCARD_SAFE(final_cache.attributes_buf[i]);
|
||||
GPU_VERTBUF_DISCARD_SAFE(cache.eval_cache.proc_attributes_buf[i]);
|
||||
this->curve_attributes_buf[i].reset();
|
||||
this->evaluated_attributes_buf[i].reset();
|
||||
}
|
||||
drw_attributes_merge(&final_cache.attr_used, &attrs_needed);
|
||||
drw_attributes_merge(&attr_used, &attrs_needed);
|
||||
}
|
||||
drw_attributes_merge(&final_cache.attr_used_over_time, &attrs_needed);
|
||||
drw_attributes_merge(&attr_used_over_time, &attrs_needed);
|
||||
}
|
||||
|
||||
bool need_tf_update = false;
|
||||
|
||||
for (const int i : final_cache.attr_used.index_range()) {
|
||||
if (cache.eval_cache.final.attributes_buf[i] != nullptr) {
|
||||
for (const int i : attr_used.index_range()) {
|
||||
if (this->curve_attributes_buf[i] || this->evaluated_attributes_buf[i]) {
|
||||
continue;
|
||||
}
|
||||
|
||||
ensure_final_attribute(curves, final_cache.attr_used[i], i, cache.eval_cache);
|
||||
if (cache.eval_cache.proc_attributes_point_domain[i]) {
|
||||
need_tf_update = true;
|
||||
}
|
||||
ensure_attribute(module, curves, attr_used[i], i);
|
||||
}
|
||||
|
||||
return need_tf_update;
|
||||
}
|
||||
|
||||
static void request_attribute(Curves &curves, const StringRef name)
|
||||
void CurvesEvalCache::ensure_common(const bke::CurvesGeometry &curves)
|
||||
{
|
||||
CurvesBatchCache &cache = get_batch_cache(curves);
|
||||
CurvesEvalFinalCache &final_cache = cache.eval_cache.final;
|
||||
|
||||
VectorSet<std::string> attributes{};
|
||||
|
||||
bke::CurvesGeometry &curves_geometry = curves.geometry.wrap();
|
||||
if (!curves_geometry.attributes().contains(name)) {
|
||||
if (this->points_by_curve_buf) {
|
||||
return;
|
||||
}
|
||||
drw_attributes_add_request(&attributes, name);
|
||||
this->points_by_curve_buf = gpu::VertBuf::from_span(curves.points_by_curve().data());
|
||||
this->evaluated_points_by_curve_buf = gpu::VertBuf::from_span(
|
||||
curves.evaluated_points_by_curve().data());
|
||||
|
||||
drw_attributes_merge(&final_cache.attr_used, &attributes);
|
||||
/* TODO(fclem): Optimize shaders to avoid needing to upload this data if data is uniform.
|
||||
* This concerns all varray. */
|
||||
this->curves_type_buf = gpu::VertBuf::from_varray(curves.curve_types());
|
||||
this->curves_resolution_buf = gpu::VertBuf::from_varray(curves.resolution());
|
||||
this->curves_cyclic_buf = gpu::VertBuf::from_varray(curves.cyclic());
|
||||
}
|
||||
|
||||
void drw_curves_get_attribute_sampler_name(const StringRef layer_name, char r_sampler_name[32])
|
||||
void CurvesEvalCache::ensure_bezier(const bke::CurvesGeometry &curves)
|
||||
{
|
||||
char attr_safe_name[GPU_MAX_SAFE_ATTR_NAME];
|
||||
GPU_vertformat_safe_attr_name(layer_name, attr_safe_name, GPU_MAX_SAFE_ATTR_NAME);
|
||||
/* Attributes use auto-name. */
|
||||
BLI_snprintf_utf8(r_sampler_name, 32, "a%s", attr_safe_name);
|
||||
if (this->handles_positions_left_buf) {
|
||||
return;
|
||||
}
|
||||
const Span<float3> left = curves.handle_positions_left().value_or(curves.positions());
|
||||
const Span<float3> right = curves.handle_positions_right().value_or(curves.positions());
|
||||
this->handles_positions_left_buf = gpu::VertBuf::from_span(left);
|
||||
this->handles_positions_right_buf = gpu::VertBuf::from_span(right);
|
||||
this->bezier_offsets_buf = gpu::VertBuf::from_span(
|
||||
curves.runtime->evaluated_offsets_cache.data().all_bezier_offsets.as_span());
|
||||
}
|
||||
|
||||
bool curves_ensure_procedural_data(Curves *curves_id,
|
||||
CurvesEvalCache **r_cache,
|
||||
const GPUMaterial *gpu_material,
|
||||
const int subdiv,
|
||||
const int thickness_res)
|
||||
void CurvesEvalCache::ensure_nurbs(const bke::CurvesGeometry &curves)
|
||||
{
|
||||
const bke::CurvesGeometry &curves = curves_id->geometry.wrap();
|
||||
bool need_ft_update = false;
|
||||
if (curves_order_buf) {
|
||||
return;
|
||||
}
|
||||
using BasisCache = bke::curves::nurbs::BasisCache;
|
||||
|
||||
CurvesBatchCache &cache = get_batch_cache(*curves_id);
|
||||
CurvesEvalCache &eval_cache = cache.eval_cache;
|
||||
|
||||
if (eval_cache.final.hair_subdiv != subdiv || eval_cache.final.thickres != thickness_res) {
|
||||
/* If the subdivision or indexing settings have changed, the evaluation cache is cleared. */
|
||||
clear_final_data(eval_cache.final);
|
||||
eval_cache.final.hair_subdiv = subdiv;
|
||||
eval_cache.final.thickres = thickness_res;
|
||||
/* TODO(fclem): Optimize shaders to avoid needing to upload this data if data is uniform.
|
||||
* This concerns all varray. */
|
||||
this->curves_order_buf = gpu::VertBuf::from_varray(curves.nurbs_orders());
|
||||
if (curves.nurbs_weights().has_value()) {
|
||||
this->control_weights_buf = gpu::VertBuf::from_span(curves.nurbs_weights().value());
|
||||
}
|
||||
|
||||
eval_cache.curves_num = curves.curves_num();
|
||||
eval_cache.points_num = curves.points_num();
|
||||
curves.ensure_can_interpolate_to_evaluated();
|
||||
|
||||
const int steps = 3; /* TODO: don't hard-code? */
|
||||
eval_cache.final.resolution = 1 << (steps + subdiv);
|
||||
const Span<BasisCache> nurbs_basis_cache = curves.runtime->nurbs_basis_cache.data();
|
||||
|
||||
/* Refreshed on combing and simulation. */
|
||||
if (eval_cache.proc_point_buf == nullptr || DRW_vbo_requested(eval_cache.proc_point_buf)) {
|
||||
create_points_position_time_vbo(curves, eval_cache);
|
||||
need_ft_update = true;
|
||||
Vector<int> basis_cache_offset;
|
||||
Vector<uint32_t> basis_cache_packed;
|
||||
for (const BasisCache &cache : nurbs_basis_cache) {
|
||||
basis_cache_offset.append(cache.invalid ? -1 : basis_cache_packed.size());
|
||||
if (!cache.invalid) {
|
||||
basis_cache_packed.extend(cache.start_indices.as_span().cast<uint32_t>());
|
||||
basis_cache_packed.extend(cache.weights.as_span().cast<uint32_t>());
|
||||
}
|
||||
}
|
||||
/* Ensure buffer is not empty. */
|
||||
if (basis_cache_packed.is_empty()) {
|
||||
basis_cache_packed.append(0);
|
||||
}
|
||||
|
||||
/* Refreshed if active layer or custom data changes. */
|
||||
if (eval_cache.proc_strand_buf == nullptr) {
|
||||
create_curve_offsets_vbos(curves.points_by_curve(), eval_cache);
|
||||
this->basis_cache_offset_buf = gpu::VertBuf::from_span(basis_cache_offset.as_span());
|
||||
this->basis_cache_buf = gpu::VertBuf::from_span(basis_cache_packed.as_span());
|
||||
}
|
||||
|
||||
int CurvesEvalCache::evaluated_point_count_with_cyclic(const bke::CurvesGeometry &curves)
|
||||
{
|
||||
if (curves.has_cyclic_curve()) {
|
||||
return curves.evaluated_points_num() + curves.curves_num();
|
||||
}
|
||||
return curves.evaluated_points_num();
|
||||
}
|
||||
|
||||
void CurvesEvalCache::ensure_positions(CurvesModule &module, const bke::CurvesGeometry &curves)
|
||||
{
|
||||
if (evaluated_pos_rad_buf) {
|
||||
return;
|
||||
}
|
||||
|
||||
/* Refreshed only on subdiv count change. */
|
||||
if (eval_cache.final.proc_buf == nullptr) {
|
||||
alloc_final_points_vbo(eval_cache);
|
||||
need_ft_update = true;
|
||||
if (curves.is_empty()) {
|
||||
/* Can happen when called from `curves_pos_buffer_get()`. Caller has to deal with nullptr. */
|
||||
return;
|
||||
}
|
||||
|
||||
if (eval_cache.final.proc_hairs == nullptr) {
|
||||
calc_final_indices(curves, eval_cache, thickness_res);
|
||||
this->ensure_common(curves);
|
||||
if (curves.has_curve_with_type(CURVE_TYPE_BEZIER)) {
|
||||
this->ensure_bezier(curves);
|
||||
}
|
||||
if (curves.has_curve_with_type(CURVE_TYPE_NURBS)) {
|
||||
this->ensure_nurbs(curves);
|
||||
}
|
||||
eval_cache.final.thickres = thickness_res;
|
||||
|
||||
need_ft_update |= ensure_attributes(*curves_id, cache, gpu_material);
|
||||
/* TODO(fclem): Optimize shaders to avoid needing to upload this data if data is uniform.
|
||||
* This concerns all varray. */
|
||||
gpu::VertBufPtr points_pos_buf = gpu::VertBuf::from_span(curves.positions());
|
||||
gpu::VertBufPtr points_rad_buf = gpu::VertBuf::from_varray(curves.radius());
|
||||
|
||||
*r_cache = &eval_cache;
|
||||
return need_ft_update;
|
||||
this->evaluated_pos_rad_buf = gpu::VertBuf::device_only<float4>(
|
||||
evaluated_point_count_with_cyclic(curves));
|
||||
|
||||
module.evaluate_positions(curves.has_curve_with_type(CURVE_TYPE_CATMULL_ROM),
|
||||
curves.has_curve_with_type(CURVE_TYPE_BEZIER),
|
||||
curves.has_curve_with_type(CURVE_TYPE_POLY),
|
||||
curves.has_curve_with_type(CURVE_TYPE_NURBS),
|
||||
curves.has_cyclic_curve(),
|
||||
curves.curves_num(),
|
||||
*this,
|
||||
std::move(points_pos_buf),
|
||||
std::move(points_rad_buf),
|
||||
evaluated_pos_rad_buf);
|
||||
|
||||
/* TODO(fclem): Make time and length optional. */
|
||||
this->evaluated_time_buf = gpu::VertBuf::device_only<float>(
|
||||
evaluated_point_count_with_cyclic(curves));
|
||||
this->curves_length_buf = gpu::VertBuf::device_only<float>(curves.curves_num());
|
||||
|
||||
module.evaluate_curve_length_intercept(curves.has_cyclic_curve(), curves.curves_num(), *this);
|
||||
}
|
||||
|
||||
gpu::VertBufPtr &CurvesEvalCache::indirection_buf_get(CurvesModule &module,
|
||||
const bke::CurvesGeometry &curves,
|
||||
const int face_per_segment)
|
||||
{
|
||||
const bool is_ribbon = face_per_segment < 2;
|
||||
|
||||
gpu::VertBufPtr &indirection_buf = is_ribbon ? this->indirection_ribbon_buf :
|
||||
this->indirection_cylinder_buf;
|
||||
if (indirection_buf) {
|
||||
return indirection_buf;
|
||||
}
|
||||
|
||||
this->ensure_common(curves);
|
||||
|
||||
indirection_buf = module.evaluate_topology_indirection(curves.curves_num(),
|
||||
curves.evaluated_points_num(),
|
||||
*this,
|
||||
is_ribbon,
|
||||
curves.has_cyclic_curve());
|
||||
|
||||
return indirection_buf;
|
||||
}
|
||||
|
||||
gpu::Batch *CurvesEvalCache::batch_get(const int evaluated_point_count,
|
||||
const int curve_count,
|
||||
const int face_per_segment,
|
||||
const bool use_cyclic)
|
||||
{
|
||||
gpu::Batch *&batch = this->batch[face_per_segment];
|
||||
if (batch) {
|
||||
return batch;
|
||||
}
|
||||
|
||||
if (face_per_segment == 0) {
|
||||
/* Add one point per curve to restart the primitive. */
|
||||
int segment_count = evaluated_point_count + curve_count;
|
||||
if (use_cyclic) {
|
||||
segment_count += curve_count;
|
||||
}
|
||||
batch = GPU_batch_create_procedural(GPU_PRIM_LINE_STRIP, segment_count);
|
||||
}
|
||||
else if (face_per_segment == 1) {
|
||||
/* Add one point per curve to restart the primitive. */
|
||||
int segment_count = evaluated_point_count + curve_count;
|
||||
if (use_cyclic) {
|
||||
segment_count += curve_count;
|
||||
}
|
||||
/* Add one point per curve to restart the primitive. */
|
||||
batch = GPU_batch_create_procedural(GPU_PRIM_TRI_STRIP, segment_count * 2);
|
||||
}
|
||||
else if (face_per_segment >= 2) {
|
||||
int segment_count = evaluated_point_count - curve_count;
|
||||
if (use_cyclic) {
|
||||
segment_count += curve_count;
|
||||
}
|
||||
/* Add one vertex per segment to restart the primitive. */
|
||||
int vert_per_segment = (face_per_segment + 1) * 2 + 1;
|
||||
batch = GPU_batch_create_procedural(GPU_PRIM_TRI_STRIP, segment_count * vert_per_segment);
|
||||
}
|
||||
|
||||
return batch;
|
||||
}
|
||||
|
||||
CurvesEvalCache &curves_get_eval_cache(Curves &curves_id)
|
||||
{
|
||||
return get_batch_cache(curves_id).eval_cache;
|
||||
}
|
||||
|
||||
void DRW_curves_batch_cache_dirty_tag(Curves *curves, int mode)
|
||||
@@ -912,20 +909,20 @@ void DRW_curves_batch_cache_free_old(Curves *curves, int ctime)
|
||||
|
||||
bool do_discard = false;
|
||||
|
||||
CurvesEvalFinalCache &final_cache = cache->eval_cache.final;
|
||||
CurvesEvalCache &eval_cache = cache->eval_cache;
|
||||
|
||||
if (drw_attributes_overlap(&final_cache.attr_used_over_time, &final_cache.attr_used)) {
|
||||
final_cache.last_attr_matching_time = ctime;
|
||||
if (drw_attributes_overlap(&eval_cache.attr_used_over_time, &eval_cache.attr_used)) {
|
||||
eval_cache.last_attr_matching_time = ctime;
|
||||
}
|
||||
|
||||
if (ctime - final_cache.last_attr_matching_time > U.vbotimeout) {
|
||||
if (ctime - eval_cache.last_attr_matching_time > U.vbotimeout) {
|
||||
do_discard = true;
|
||||
}
|
||||
|
||||
final_cache.attr_used_over_time.clear();
|
||||
eval_cache.attr_used_over_time.clear();
|
||||
|
||||
if (do_discard) {
|
||||
discard_attributes(cache->eval_cache);
|
||||
cache->eval_cache.discard_attributes();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -953,32 +950,33 @@ gpu::Batch *DRW_curves_batch_cache_get_edit_curves_lines(Curves *curves)
|
||||
return DRW_batch_request(&cache.edit_curves_lines);
|
||||
}
|
||||
|
||||
gpu::VertBuf **DRW_curves_texture_for_evaluated_attribute(Curves *curves,
|
||||
const StringRef name,
|
||||
bool *r_is_point_domain)
|
||||
gpu::VertBufPtr &DRW_curves_texture_for_evaluated_attribute(Curves *curves,
|
||||
const StringRef name,
|
||||
bool &r_is_point_domain,
|
||||
bool &r_valid_attribute)
|
||||
{
|
||||
CurvesBatchCache &cache = get_batch_cache(*curves);
|
||||
CurvesEvalFinalCache &final_cache = cache.eval_cache.final;
|
||||
CurvesEvalCache &cache = get_batch_cache(*curves).eval_cache;
|
||||
|
||||
request_attribute(*curves, name);
|
||||
|
||||
int request_i = -1;
|
||||
for (const int i : final_cache.attr_used.index_range()) {
|
||||
if (final_cache.attr_used[i] == name) {
|
||||
request_i = i;
|
||||
break;
|
||||
/* TODO(fclem): Remove Global access. */
|
||||
CurvesModule &module = *drw_get().data->curves_module;
|
||||
cache.ensure_attributes(module, curves->geometry.wrap(), nullptr);
|
||||
|
||||
for (const int i : cache.attr_used.index_range()) {
|
||||
if (cache.attr_used[i] == name) {
|
||||
r_valid_attribute = true;
|
||||
if (cache.attributes_point_domain[i]) {
|
||||
r_is_point_domain = true;
|
||||
return cache.evaluated_attributes_buf[i];
|
||||
}
|
||||
r_is_point_domain = false;
|
||||
return cache.curve_attributes_buf[i];
|
||||
}
|
||||
}
|
||||
if (request_i == -1) {
|
||||
*r_is_point_domain = false;
|
||||
return nullptr;
|
||||
}
|
||||
if (cache.eval_cache.proc_attributes_point_domain[request_i]) {
|
||||
*r_is_point_domain = true;
|
||||
return &final_cache.attributes_buf[request_i];
|
||||
}
|
||||
*r_is_point_domain = false;
|
||||
return &cache.eval_cache.proc_attributes_buf[request_i];
|
||||
r_valid_attribute = false;
|
||||
r_is_point_domain = false;
|
||||
return cache.evaluated_attributes_buf[0];
|
||||
}
|
||||
|
||||
static void create_edit_points_position_vbo(
|
||||
|
||||
@@ -8,7 +8,9 @@
|
||||
* \brief Particle API for render engines
|
||||
*/
|
||||
|
||||
#include "BLI_color.hh"
|
||||
#include "DNA_collection_types.h"
|
||||
#include "DNA_curves_types.h"
|
||||
#include "DNA_scene_types.h"
|
||||
#include "DRW_render.hh"
|
||||
|
||||
@@ -17,6 +19,7 @@
|
||||
#include "BLI_alloca.h"
|
||||
#include "BLI_math_color.h"
|
||||
#include "BLI_math_vector.h"
|
||||
#include "BLI_offset_indices.hh"
|
||||
#include "BLI_string_utf8.h"
|
||||
#include "BLI_utildefines.h"
|
||||
|
||||
@@ -40,6 +43,7 @@
|
||||
|
||||
#include "DEG_depsgraph_query.hh"
|
||||
|
||||
#include "draw_attributes.hh"
|
||||
#include "draw_cache_impl.hh" /* own include */
|
||||
#include "draw_hair_private.hh"
|
||||
|
||||
@@ -50,6 +54,32 @@ static void particle_batch_cache_clear(ParticleSystem *psys);
|
||||
/* ---------------------------------------------------------------------- */
|
||||
/* Particle gpu::Batch Cache */
|
||||
|
||||
struct ParticleHairFinalCache {
|
||||
/* Output of the subdivision stage: vertex buff sized to subdiv level. */
|
||||
blender::gpu::VertBuf *proc_buf;
|
||||
|
||||
/* Just contains a huge index buffer used to draw the final hair. */
|
||||
blender::gpu::Batch *proc_hairs[MAX_THICKRES];
|
||||
|
||||
int strands_res; /* points per hair, at least 2 */
|
||||
};
|
||||
|
||||
struct ParticleHairCache {
|
||||
blender::gpu::VertBuf *pos;
|
||||
blender::gpu::IndexBuf *indices;
|
||||
blender::gpu::Batch *hairs;
|
||||
int strands_len;
|
||||
int elems_len;
|
||||
int point_len;
|
||||
|
||||
/* Equivalent to the new Curves data structure.
|
||||
* Allows to create the eval cache. */
|
||||
Vector<int> points_by_curve_storage;
|
||||
Vector<int> evaluated_points_by_curve_storage;
|
||||
|
||||
CurvesEvalCache eval_cache;
|
||||
};
|
||||
|
||||
struct ParticlePointCache {
|
||||
gpu::VertBuf *pos;
|
||||
gpu::Batch *points;
|
||||
@@ -132,11 +162,12 @@ static void particle_batch_cache_init(ParticleSystem *psys)
|
||||
ParticleBatchCache *cache = static_cast<ParticleBatchCache *>(psys->batch_cache);
|
||||
|
||||
if (!cache) {
|
||||
cache = static_cast<ParticleBatchCache *>(
|
||||
psys->batch_cache = MEM_callocN(sizeof(*cache), __func__));
|
||||
cache = MEM_new<ParticleBatchCache>(__func__);
|
||||
psys->batch_cache = cache;
|
||||
}
|
||||
else {
|
||||
memset(cache, 0, sizeof(*cache));
|
||||
cache->edit_hair.eval_cache = {};
|
||||
cache->hair.eval_cache = {};
|
||||
}
|
||||
|
||||
cache->is_dirty = false;
|
||||
@@ -175,36 +206,16 @@ static void particle_batch_cache_clear_point(ParticlePointCache *point_cache)
|
||||
static void particle_batch_cache_clear_hair(ParticleHairCache *hair_cache)
|
||||
{
|
||||
/* TODO: more granular update tagging. */
|
||||
GPU_VERTBUF_DISCARD_SAFE(hair_cache->proc_point_buf);
|
||||
GPU_VERTBUF_DISCARD_SAFE(hair_cache->proc_length_buf);
|
||||
|
||||
GPU_VERTBUF_DISCARD_SAFE(hair_cache->proc_strand_buf);
|
||||
GPU_VERTBUF_DISCARD_SAFE(hair_cache->proc_strand_seg_buf);
|
||||
|
||||
for (int i = 0; i < MAX_MTFACE; i++) {
|
||||
GPU_VERTBUF_DISCARD_SAFE(hair_cache->proc_uv_buf[i]);
|
||||
GPU_TEXTURE_FREE_SAFE(hair_cache->uv_tex[i]);
|
||||
}
|
||||
for (int i = 0; i < hair_cache->num_col_layers; i++) {
|
||||
GPU_VERTBUF_DISCARD_SAFE(hair_cache->proc_col_buf[i]);
|
||||
GPU_TEXTURE_FREE_SAFE(hair_cache->col_tex[i]);
|
||||
}
|
||||
|
||||
for (int i = 0; i < MAX_HAIR_SUBDIV; i++) {
|
||||
GPU_VERTBUF_DISCARD_SAFE(hair_cache->final[i].proc_buf);
|
||||
for (int j = 0; j < MAX_THICKRES; j++) {
|
||||
GPU_BATCH_DISCARD_SAFE(hair_cache->final[i].proc_hairs[j]);
|
||||
}
|
||||
}
|
||||
|
||||
/* "Normal" legacy hairs */
|
||||
GPU_BATCH_DISCARD_SAFE(hair_cache->hairs);
|
||||
GPU_VERTBUF_DISCARD_SAFE(hair_cache->pos);
|
||||
GPU_INDEXBUF_DISCARD_SAFE(hair_cache->indices);
|
||||
|
||||
MEM_SAFE_FREE(hair_cache->proc_col_buf);
|
||||
MEM_SAFE_FREE(hair_cache->col_tex);
|
||||
MEM_SAFE_FREE(hair_cache->col_layer_names);
|
||||
hair_cache->evaluated_points_by_curve_storage.clear();
|
||||
hair_cache->points_by_curve_storage.clear();
|
||||
|
||||
hair_cache->eval_cache.clear();
|
||||
}
|
||||
|
||||
static void particle_batch_cache_clear(ParticleSystem *psys)
|
||||
@@ -230,7 +241,74 @@ static void particle_batch_cache_clear(ParticleSystem *psys)
|
||||
void DRW_particle_batch_cache_free(ParticleSystem *psys)
|
||||
{
|
||||
particle_batch_cache_clear(psys);
|
||||
MEM_SAFE_FREE(psys->batch_cache);
|
||||
ParticleBatchCache *batch_cache = static_cast<ParticleBatchCache *>(psys->batch_cache);
|
||||
MEM_delete(batch_cache);
|
||||
psys->batch_cache = nullptr;
|
||||
}
|
||||
|
||||
void ParticleSpans::foreach_strand(FunctionRef<void(Span<ParticleCacheKey>)> callback)
|
||||
{
|
||||
for (const auto &particle : parent) {
|
||||
callback(Span<ParticleCacheKey>(particle, particle->segments + 1));
|
||||
}
|
||||
for (const auto &particle : children) {
|
||||
callback(Span<ParticleCacheKey>(particle, particle->segments + 1));
|
||||
}
|
||||
}
|
||||
|
||||
ParticleSpans ParticleDrawSource::particles_get()
|
||||
{
|
||||
if (edit && edit->pathcache) {
|
||||
/* Edit particles only display their parent. */
|
||||
return {{edit->pathcache, edit->totcached}, {}};
|
||||
}
|
||||
|
||||
ParticleSpans spans;
|
||||
const bool display_parent = !psys->childcache || (psys->part->draw & PART_DRAW_PARENT);
|
||||
if (psys->pathcache && display_parent) {
|
||||
spans.parent = {psys->pathcache, psys->totpart};
|
||||
}
|
||||
|
||||
if (psys->childcache) {
|
||||
spans.children = {psys->childcache, psys->totchild * psys->part->disp / 100};
|
||||
}
|
||||
return spans;
|
||||
}
|
||||
|
||||
OffsetIndices<int> ParticleDrawSource::points_by_curve()
|
||||
{
|
||||
if (!points_by_curve_storage_.is_empty()) {
|
||||
return points_by_curve_storage_.as_span();
|
||||
}
|
||||
|
||||
int total = 0;
|
||||
points_by_curve_storage_.append(total);
|
||||
particles_get().foreach_strand([&](Span<ParticleCacheKey> strand) {
|
||||
total += strand.size();
|
||||
points_by_curve_storage_.append(total);
|
||||
});
|
||||
return points_by_curve_storage_.as_span();
|
||||
}
|
||||
|
||||
OffsetIndices<int> ParticleDrawSource::evaluated_points_by_curve()
|
||||
{
|
||||
if (additional_subdivision_ == 0) {
|
||||
return points_by_curve();
|
||||
}
|
||||
|
||||
if (!evaluated_points_by_curve_storage_.is_empty()) {
|
||||
return evaluated_points_by_curve_storage_.as_span();
|
||||
}
|
||||
int segment_multiplier = this->resolution();
|
||||
|
||||
int total = 0;
|
||||
evaluated_points_by_curve_storage_.append(total);
|
||||
particles_get().foreach_strand([&](Span<ParticleCacheKey> strand) {
|
||||
int size = strand.size();
|
||||
total += (size > 1) ? size * segment_multiplier : 1;
|
||||
evaluated_points_by_curve_storage_.append(total);
|
||||
});
|
||||
return evaluated_points_by_curve_storage_.as_span();
|
||||
}
|
||||
|
||||
static void count_cache_segment_keys(ParticleCacheKey **pathcache,
|
||||
@@ -251,9 +329,7 @@ static void ensure_seg_pt_count(PTCacheEdit *edit,
|
||||
ParticleSystem *psys,
|
||||
ParticleHairCache *hair_cache)
|
||||
{
|
||||
if ((hair_cache->pos != nullptr && hair_cache->indices != nullptr) ||
|
||||
(hair_cache->proc_point_buf != nullptr))
|
||||
{
|
||||
if (hair_cache->pos != nullptr && hair_cache->indices != nullptr) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -625,41 +701,6 @@ static int particle_batch_cache_fill_segments(ParticleSystem *psys,
|
||||
return curr_point;
|
||||
}
|
||||
|
||||
static void particle_batch_cache_fill_segments_proc_pos(ParticleCacheKey **path_cache,
|
||||
const int num_path_keys,
|
||||
GPUVertBufRaw *attr_step,
|
||||
GPUVertBufRaw *length_step)
|
||||
{
|
||||
for (int i = 0; i < num_path_keys; i++) {
|
||||
ParticleCacheKey *path = path_cache[i];
|
||||
if (path->segments <= 0) {
|
||||
continue;
|
||||
}
|
||||
float total_len = 0.0f;
|
||||
float *co_prev = nullptr, *seg_data_first;
|
||||
for (int j = 0; j <= path->segments; j++) {
|
||||
float *seg_data = (float *)GPU_vertbuf_raw_step(attr_step);
|
||||
copy_v3_v3(seg_data, path[j].co);
|
||||
if (co_prev) {
|
||||
total_len += len_v3v3(co_prev, path[j].co);
|
||||
}
|
||||
else {
|
||||
seg_data_first = seg_data;
|
||||
}
|
||||
seg_data[3] = total_len;
|
||||
co_prev = path[j].co;
|
||||
}
|
||||
/* Assign length value. */
|
||||
*(float *)GPU_vertbuf_raw_step(length_step) = total_len;
|
||||
if (total_len > 0.0f) {
|
||||
/* Divide by total length to have a [0-1] number. */
|
||||
for (int j = 0; j <= path->segments; j++, seg_data_first += 4) {
|
||||
seg_data_first[3] /= total_len;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static float particle_key_weight(const ParticleData *particle, int strand, float t)
|
||||
{
|
||||
const ParticleData *part = particle + strand;
|
||||
@@ -714,459 +755,6 @@ static int particle_batch_cache_fill_segments_edit(
|
||||
return curr_point;
|
||||
}
|
||||
|
||||
static int particle_batch_cache_fill_segments_indices(ParticleCacheKey **path_cache,
|
||||
const int start_index,
|
||||
const int num_path_keys,
|
||||
const int res,
|
||||
GPUIndexBufBuilder *elb)
|
||||
{
|
||||
int curr_point = start_index;
|
||||
for (int i = 0; i < num_path_keys; i++) {
|
||||
ParticleCacheKey *path = path_cache[i];
|
||||
if (path->segments <= 0) {
|
||||
continue;
|
||||
}
|
||||
for (int k = 0; k < res; k++) {
|
||||
GPU_indexbuf_add_generic_vert(elb, curr_point++);
|
||||
}
|
||||
GPU_indexbuf_add_primitive_restart(elb);
|
||||
}
|
||||
return curr_point;
|
||||
}
|
||||
|
||||
static int particle_batch_cache_fill_strands_data(ParticleSystem *psys,
|
||||
ParticleSystemModifierData *psmd,
|
||||
ParticleCacheKey **path_cache,
|
||||
const ParticleSource particle_source,
|
||||
const int start_index,
|
||||
const int num_path_keys,
|
||||
GPUVertBufRaw *data_step,
|
||||
GPUVertBufRaw *seg_step,
|
||||
float (***r_parent_uvs)[2],
|
||||
GPUVertBufRaw *uv_step,
|
||||
const MTFace **mtfaces,
|
||||
int num_uv_layers,
|
||||
MCol ***r_parent_mcol,
|
||||
GPUVertBufRaw *col_step,
|
||||
const MCol **mcols,
|
||||
int num_col_layers)
|
||||
{
|
||||
const bool is_simple = (psys->part->childtype == PART_CHILD_PARTICLES);
|
||||
const bool is_child = (particle_source == PARTICLE_SOURCE_CHILDREN);
|
||||
if (is_simple && *r_parent_uvs == nullptr) {
|
||||
/* TODO(sergey): For edit mode it should be edit->totcached. */
|
||||
*r_parent_uvs = static_cast<float(**)[2]>(
|
||||
MEM_callocN(sizeof(*r_parent_uvs) * psys->totpart, "Parent particle UVs"));
|
||||
}
|
||||
if (is_simple && *r_parent_mcol == nullptr) {
|
||||
*r_parent_mcol = static_cast<MCol **>(
|
||||
MEM_callocN(sizeof(*r_parent_mcol) * psys->totpart, "Parent particle MCol"));
|
||||
}
|
||||
int curr_point = start_index;
|
||||
for (int i = 0; i < num_path_keys; i++) {
|
||||
ParticleCacheKey *path = path_cache[i];
|
||||
if (path->segments <= 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
*(uint *)GPU_vertbuf_raw_step(data_step) = curr_point;
|
||||
*(uint *)GPU_vertbuf_raw_step(seg_step) = path->segments;
|
||||
curr_point += path->segments + 1;
|
||||
|
||||
if (psmd != nullptr) {
|
||||
float(*uv)[2] = nullptr;
|
||||
MCol *mcol = nullptr;
|
||||
|
||||
particle_calculate_uvs(psys,
|
||||
psmd,
|
||||
is_simple,
|
||||
num_uv_layers,
|
||||
is_child ? psys->child[i].parent : i,
|
||||
is_child ? i : -1,
|
||||
mtfaces,
|
||||
*r_parent_uvs,
|
||||
&uv);
|
||||
|
||||
particle_calculate_mcol(psys,
|
||||
psmd,
|
||||
is_simple,
|
||||
num_col_layers,
|
||||
is_child ? psys->child[i].parent : i,
|
||||
is_child ? i : -1,
|
||||
mcols,
|
||||
*r_parent_mcol,
|
||||
&mcol);
|
||||
|
||||
for (int k = 0; k < num_uv_layers; k++) {
|
||||
float *t_uv = (float *)GPU_vertbuf_raw_step(uv_step + k);
|
||||
copy_v2_v2(t_uv, uv[k]);
|
||||
}
|
||||
for (int k = 0; k < num_col_layers; k++) {
|
||||
ushort *scol = (ushort *)GPU_vertbuf_raw_step(col_step + k);
|
||||
particle_pack_mcol((is_simple && is_child) ? &(*r_parent_mcol)[psys->child[i].parent][k] :
|
||||
&mcol[k],
|
||||
scol);
|
||||
}
|
||||
if (!is_simple) {
|
||||
MEM_freeN(uv);
|
||||
MEM_freeN(mcol);
|
||||
}
|
||||
}
|
||||
}
|
||||
return curr_point;
|
||||
}
|
||||
|
||||
static void particle_batch_cache_ensure_procedural_final_points(ParticleHairCache *cache,
|
||||
int subdiv)
|
||||
{
|
||||
/* Same format as proc_point_buf. */
|
||||
static const GPUVertFormat format = GPU_vertformat_from_attribute(
|
||||
"pos", gpu::VertAttrType::SFLOAT_32_32_32_32);
|
||||
|
||||
/* Procedural Subdiv buffer only needs to be resident in device memory. */
|
||||
cache->final[subdiv].proc_buf = GPU_vertbuf_create_with_format_ex(
|
||||
format, GPU_USAGE_DEVICE_ONLY | GPU_USAGE_FLAG_BUFFER_TEXTURE_ONLY);
|
||||
|
||||
/* Create a destination buffer for the procedural Subdiv. Sized appropriately */
|
||||
/* Those are points! not line segments. */
|
||||
uint point_len = cache->final[subdiv].strands_res * cache->strands_len;
|
||||
/* Avoid creating null sized VBO which can lead to crashes on certain platforms. */
|
||||
point_len = max_ii(1, point_len);
|
||||
|
||||
GPU_vertbuf_data_alloc(*cache->final[subdiv].proc_buf, point_len);
|
||||
}
|
||||
|
||||
static void particle_batch_cache_ensure_procedural_strand_data(PTCacheEdit *edit,
|
||||
ParticleSystem *psys,
|
||||
ModifierData *md,
|
||||
ParticleHairCache *cache)
|
||||
{
|
||||
int active_uv = 0;
|
||||
int render_uv = 0;
|
||||
int active_col = 0;
|
||||
int render_col = 0;
|
||||
|
||||
ParticleSystemModifierData *psmd = (ParticleSystemModifierData *)md;
|
||||
|
||||
if (psmd != nullptr && psmd->mesh_final != nullptr) {
|
||||
if (CustomData_has_layer(&psmd->mesh_final->corner_data, CD_PROP_FLOAT2)) {
|
||||
cache->num_uv_layers = CustomData_number_of_layers(&psmd->mesh_final->corner_data,
|
||||
CD_PROP_FLOAT2);
|
||||
active_uv = CustomData_get_active_layer(&psmd->mesh_final->corner_data, CD_PROP_FLOAT2);
|
||||
render_uv = CustomData_get_render_layer(&psmd->mesh_final->corner_data, CD_PROP_FLOAT2);
|
||||
}
|
||||
if (CustomData_has_layer(&psmd->mesh_final->corner_data, CD_PROP_BYTE_COLOR)) {
|
||||
cache->num_col_layers = CustomData_number_of_layers(&psmd->mesh_final->corner_data,
|
||||
CD_PROP_BYTE_COLOR);
|
||||
if (psmd->mesh_final->active_color_attribute != nullptr) {
|
||||
active_col = CustomData_get_named_layer(&psmd->mesh_final->corner_data,
|
||||
CD_PROP_BYTE_COLOR,
|
||||
psmd->mesh_final->active_color_attribute);
|
||||
}
|
||||
if (psmd->mesh_final->default_color_attribute != nullptr) {
|
||||
render_col = CustomData_get_named_layer(&psmd->mesh_final->corner_data,
|
||||
CD_PROP_BYTE_COLOR,
|
||||
psmd->mesh_final->default_color_attribute);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
GPUVertBufRaw data_step, seg_step;
|
||||
GPUVertBufRaw uv_step[MAX_MTFACE];
|
||||
GPUVertBufRaw *col_step = BLI_array_alloca(col_step, cache->num_col_layers);
|
||||
|
||||
const MTFace *mtfaces[MAX_MTFACE] = {nullptr};
|
||||
const MCol **mcols = BLI_array_alloca(mcols, cache->num_col_layers);
|
||||
float(**parent_uvs)[2] = nullptr;
|
||||
MCol **parent_mcol = nullptr;
|
||||
|
||||
GPUVertFormat format_data = {0};
|
||||
uint data_id = GPU_vertformat_attr_add(
|
||||
&format_data, "data", blender::gpu::VertAttrType::UINT_32);
|
||||
|
||||
GPUVertFormat format_seg = {0};
|
||||
uint seg_id = GPU_vertformat_attr_add(&format_seg, "data", blender::gpu::VertAttrType::UINT_32);
|
||||
|
||||
GPUVertFormat format_uv = {0};
|
||||
uint uv_id = GPU_vertformat_attr_add(&format_uv, "uv", blender::gpu::VertAttrType::SFLOAT_32_32);
|
||||
|
||||
GPUVertFormat format_col = {0};
|
||||
uint col_id = GPU_vertformat_attr_add(
|
||||
&format_col, "col", blender::gpu::VertAttrType::UNORM_16_16_16_16);
|
||||
|
||||
memset(cache->uv_layer_names, 0, sizeof(cache->uv_layer_names));
|
||||
|
||||
/* Strand Data */
|
||||
cache->proc_strand_buf = GPU_vertbuf_create_with_format_ex(
|
||||
format_data, GPU_USAGE_STATIC | GPU_USAGE_FLAG_BUFFER_TEXTURE_ONLY);
|
||||
GPU_vertbuf_data_alloc(*cache->proc_strand_buf, max_ii(1, cache->strands_len));
|
||||
GPU_vertbuf_attr_get_raw_data(cache->proc_strand_buf, data_id, &data_step);
|
||||
|
||||
cache->proc_strand_seg_buf = GPU_vertbuf_create_with_format_ex(
|
||||
format_seg, GPU_USAGE_STATIC | GPU_USAGE_FLAG_BUFFER_TEXTURE_ONLY);
|
||||
GPU_vertbuf_data_alloc(*cache->proc_strand_seg_buf, max_ii(1, cache->strands_len));
|
||||
GPU_vertbuf_attr_get_raw_data(cache->proc_strand_seg_buf, seg_id, &seg_step);
|
||||
|
||||
/* UV layers */
|
||||
for (int i = 0; i < cache->num_uv_layers; i++) {
|
||||
cache->proc_uv_buf[i] = GPU_vertbuf_create_with_format_ex(
|
||||
format_uv, GPU_USAGE_STATIC | GPU_USAGE_FLAG_BUFFER_TEXTURE_ONLY);
|
||||
GPU_vertbuf_data_alloc(*cache->proc_uv_buf[i], max_ii(1, cache->strands_len));
|
||||
GPU_vertbuf_attr_get_raw_data(cache->proc_uv_buf[i], uv_id, &uv_step[i]);
|
||||
|
||||
char attr_safe_name[GPU_MAX_SAFE_ATTR_NAME];
|
||||
const char *name = CustomData_get_layer_name(
|
||||
&psmd->mesh_final->corner_data, CD_PROP_FLOAT2, i);
|
||||
GPU_vertformat_safe_attr_name(name, attr_safe_name, GPU_MAX_SAFE_ATTR_NAME);
|
||||
|
||||
int n = 0;
|
||||
SNPRINTF_UTF8(cache->uv_layer_names[i][n], "a%s", attr_safe_name);
|
||||
n++;
|
||||
|
||||
if (i == active_uv) {
|
||||
STRNCPY_UTF8(cache->uv_layer_names[i][n], "au");
|
||||
n++;
|
||||
}
|
||||
if (i == render_uv) {
|
||||
STRNCPY_UTF8(cache->uv_layer_names[i][n], "a");
|
||||
n++;
|
||||
}
|
||||
}
|
||||
|
||||
MEM_SAFE_FREE(cache->proc_col_buf);
|
||||
MEM_SAFE_FREE(cache->col_tex);
|
||||
MEM_SAFE_FREE(cache->col_layer_names);
|
||||
|
||||
cache->proc_col_buf = MEM_calloc_arrayN<gpu::VertBuf *>(cache->num_col_layers, "proc_col_buf");
|
||||
cache->col_tex = MEM_calloc_arrayN<gpu::Texture *>(cache->num_col_layers, "col_tex");
|
||||
cache->col_layer_names = MEM_calloc_arrayN<char[4][14]>(cache->num_col_layers,
|
||||
"col_layer_names");
|
||||
|
||||
/* Vertex colors */
|
||||
for (int i = 0; i < cache->num_col_layers; i++) {
|
||||
cache->proc_col_buf[i] = GPU_vertbuf_create_with_format_ex(
|
||||
format_col, GPU_USAGE_STATIC | GPU_USAGE_FLAG_BUFFER_TEXTURE_ONLY);
|
||||
GPU_vertbuf_data_alloc(*cache->proc_col_buf[i], max_ii(1, cache->strands_len));
|
||||
GPU_vertbuf_attr_get_raw_data(cache->proc_col_buf[i], col_id, &col_step[i]);
|
||||
|
||||
char attr_safe_name[GPU_MAX_SAFE_ATTR_NAME];
|
||||
const char *name = CustomData_get_layer_name(
|
||||
&psmd->mesh_final->corner_data, CD_PROP_BYTE_COLOR, i);
|
||||
GPU_vertformat_safe_attr_name(name, attr_safe_name, GPU_MAX_SAFE_ATTR_NAME);
|
||||
|
||||
int n = 0;
|
||||
SNPRINTF_UTF8(cache->col_layer_names[i][n], "a%s", attr_safe_name);
|
||||
n++;
|
||||
|
||||
if (i == active_col) {
|
||||
STRNCPY_UTF8(cache->col_layer_names[i][n], "ac");
|
||||
n++;
|
||||
}
|
||||
if (i == render_col) {
|
||||
STRNCPY_UTF8(cache->col_layer_names[i][n], "c");
|
||||
n++;
|
||||
}
|
||||
}
|
||||
|
||||
if (cache->num_uv_layers || cache->num_col_layers) {
|
||||
BKE_mesh_tessface_ensure(psmd->mesh_final);
|
||||
if (cache->num_uv_layers) {
|
||||
for (int j = 0; j < cache->num_uv_layers; j++) {
|
||||
mtfaces[j] = (const MTFace *)CustomData_get_layer_n(
|
||||
&psmd->mesh_final->fdata_legacy, CD_MTFACE, j);
|
||||
}
|
||||
}
|
||||
if (cache->num_col_layers) {
|
||||
for (int j = 0; j < cache->num_col_layers; j++) {
|
||||
mcols[j] = (const MCol *)CustomData_get_layer_n(
|
||||
&psmd->mesh_final->fdata_legacy, CD_MCOL, j);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (edit != nullptr && edit->pathcache != nullptr) {
|
||||
particle_batch_cache_fill_strands_data(psys,
|
||||
psmd,
|
||||
edit->pathcache,
|
||||
PARTICLE_SOURCE_PARENT,
|
||||
0,
|
||||
edit->totcached,
|
||||
&data_step,
|
||||
&seg_step,
|
||||
&parent_uvs,
|
||||
uv_step,
|
||||
mtfaces,
|
||||
cache->num_uv_layers,
|
||||
&parent_mcol,
|
||||
col_step,
|
||||
mcols,
|
||||
cache->num_col_layers);
|
||||
}
|
||||
else {
|
||||
int curr_point = 0;
|
||||
if ((psys->pathcache != nullptr) &&
|
||||
(!psys->childcache || (psys->part->draw & PART_DRAW_PARENT)))
|
||||
{
|
||||
curr_point = particle_batch_cache_fill_strands_data(psys,
|
||||
psmd,
|
||||
psys->pathcache,
|
||||
PARTICLE_SOURCE_PARENT,
|
||||
0,
|
||||
psys->totpart,
|
||||
&data_step,
|
||||
&seg_step,
|
||||
&parent_uvs,
|
||||
uv_step,
|
||||
mtfaces,
|
||||
cache->num_uv_layers,
|
||||
&parent_mcol,
|
||||
col_step,
|
||||
mcols,
|
||||
cache->num_col_layers);
|
||||
}
|
||||
if (psys->childcache) {
|
||||
const int child_count = psys->totchild * psys->part->disp / 100;
|
||||
curr_point = particle_batch_cache_fill_strands_data(psys,
|
||||
psmd,
|
||||
psys->childcache,
|
||||
PARTICLE_SOURCE_CHILDREN,
|
||||
curr_point,
|
||||
child_count,
|
||||
&data_step,
|
||||
&seg_step,
|
||||
&parent_uvs,
|
||||
uv_step,
|
||||
mtfaces,
|
||||
cache->num_uv_layers,
|
||||
&parent_mcol,
|
||||
col_step,
|
||||
mcols,
|
||||
cache->num_col_layers);
|
||||
}
|
||||
}
|
||||
/* Cleanup. */
|
||||
if (parent_uvs != nullptr) {
|
||||
/* TODO(sergey): For edit mode it should be edit->totcached. */
|
||||
for (int i = 0; i < psys->totpart; i++) {
|
||||
MEM_SAFE_FREE(parent_uvs[i]);
|
||||
}
|
||||
MEM_freeN(parent_uvs);
|
||||
}
|
||||
if (parent_mcol != nullptr) {
|
||||
for (int i = 0; i < psys->totpart; i++) {
|
||||
MEM_SAFE_FREE(parent_mcol[i]);
|
||||
}
|
||||
MEM_freeN(parent_mcol);
|
||||
}
|
||||
|
||||
for (int i = 0; i < cache->num_uv_layers; i++) {
|
||||
GPU_vertbuf_use(cache->proc_uv_buf[i]);
|
||||
cache->uv_tex[i] = GPU_texture_create_from_vertbuf("part_uv", cache->proc_uv_buf[i]);
|
||||
}
|
||||
for (int i = 0; i < cache->num_col_layers; i++) {
|
||||
GPU_vertbuf_use(cache->proc_col_buf[i]);
|
||||
cache->col_tex[i] = GPU_texture_create_from_vertbuf("part_col", cache->proc_col_buf[i]);
|
||||
}
|
||||
}
|
||||
|
||||
static void particle_batch_cache_ensure_procedural_indices(PTCacheEdit *edit,
|
||||
ParticleSystem *psys,
|
||||
ParticleHairCache *cache,
|
||||
int thickness_res,
|
||||
int subdiv)
|
||||
{
|
||||
BLI_assert(thickness_res <= MAX_THICKRES); /* Cylinder strip not currently supported. */
|
||||
|
||||
if (cache->final[subdiv].proc_hairs[thickness_res - 1] != nullptr) {
|
||||
return;
|
||||
}
|
||||
|
||||
int verts_per_hair = cache->final[subdiv].strands_res * thickness_res;
|
||||
/* +1 for primitive restart */
|
||||
int element_count = (verts_per_hair + 1) * cache->strands_len;
|
||||
GPUPrimType prim_type = (thickness_res == 1) ? GPU_PRIM_LINE_STRIP : GPU_PRIM_TRI_STRIP;
|
||||
|
||||
static const GPUVertFormat format = GPU_vertformat_from_attribute("dummy",
|
||||
gpu::VertAttrType::UINT_32);
|
||||
|
||||
gpu::VertBuf *vbo = GPU_vertbuf_create_with_format(format);
|
||||
GPU_vertbuf_data_alloc(*vbo, 1);
|
||||
|
||||
GPUIndexBufBuilder elb;
|
||||
GPU_indexbuf_init_ex(&elb, prim_type, element_count, element_count);
|
||||
|
||||
if (edit != nullptr && edit->pathcache != nullptr) {
|
||||
particle_batch_cache_fill_segments_indices(
|
||||
edit->pathcache, 0, edit->totcached, verts_per_hair, &elb);
|
||||
}
|
||||
else {
|
||||
int curr_point = 0;
|
||||
if ((psys->pathcache != nullptr) &&
|
||||
(!psys->childcache || (psys->part->draw & PART_DRAW_PARENT)))
|
||||
{
|
||||
curr_point = particle_batch_cache_fill_segments_indices(
|
||||
psys->pathcache, 0, psys->totpart, verts_per_hair, &elb);
|
||||
}
|
||||
if (psys->childcache) {
|
||||
const int child_count = psys->totchild * psys->part->disp / 100;
|
||||
curr_point = particle_batch_cache_fill_segments_indices(
|
||||
psys->childcache, curr_point, child_count, verts_per_hair, &elb);
|
||||
}
|
||||
}
|
||||
|
||||
cache->final[subdiv].proc_hairs[thickness_res - 1] = GPU_batch_create_ex(
|
||||
prim_type, vbo, GPU_indexbuf_build(&elb), GPU_BATCH_OWNS_VBO | GPU_BATCH_OWNS_INDEX);
|
||||
}
|
||||
|
||||
static void particle_batch_cache_ensure_procedural_pos(PTCacheEdit *edit,
|
||||
ParticleSystem *psys,
|
||||
ParticleHairCache *cache,
|
||||
GPUMaterial * /*gpu_material*/)
|
||||
{
|
||||
if (cache->proc_point_buf == nullptr) {
|
||||
/* initialize vertex format */
|
||||
GPUVertFormat pos_format = {0};
|
||||
uint pos_id = GPU_vertformat_attr_add(
|
||||
&pos_format, "posTime", blender::gpu::VertAttrType::SFLOAT_32_32_32_32);
|
||||
|
||||
cache->proc_point_buf = GPU_vertbuf_create_with_format_ex(
|
||||
pos_format, GPU_USAGE_STATIC | GPU_USAGE_FLAG_BUFFER_TEXTURE_ONLY);
|
||||
GPU_vertbuf_data_alloc(*cache->proc_point_buf, cache->point_len);
|
||||
|
||||
GPUVertBufRaw pos_step;
|
||||
GPU_vertbuf_attr_get_raw_data(cache->proc_point_buf, pos_id, &pos_step);
|
||||
|
||||
GPUVertFormat length_format = {0};
|
||||
uint length_id = GPU_vertformat_attr_add(
|
||||
&length_format, "hairLength", blender::gpu::VertAttrType::SFLOAT_32);
|
||||
|
||||
cache->proc_length_buf = GPU_vertbuf_create_with_format_ex(
|
||||
length_format, GPU_USAGE_STATIC | GPU_USAGE_FLAG_BUFFER_TEXTURE_ONLY);
|
||||
GPU_vertbuf_data_alloc(*cache->proc_length_buf, cache->strands_len);
|
||||
|
||||
GPUVertBufRaw length_step;
|
||||
GPU_vertbuf_attr_get_raw_data(cache->proc_length_buf, length_id, &length_step);
|
||||
|
||||
if (edit != nullptr && edit->pathcache != nullptr) {
|
||||
particle_batch_cache_fill_segments_proc_pos(
|
||||
edit->pathcache, edit->totcached, &pos_step, &length_step);
|
||||
}
|
||||
else {
|
||||
if ((psys->pathcache != nullptr) &&
|
||||
(!psys->childcache || (psys->part->draw & PART_DRAW_PARENT)))
|
||||
{
|
||||
particle_batch_cache_fill_segments_proc_pos(
|
||||
psys->pathcache, psys->totpart, &pos_step, &length_step);
|
||||
}
|
||||
if (psys->childcache) {
|
||||
const int child_count = psys->totchild * psys->part->disp / 100;
|
||||
particle_batch_cache_fill_segments_proc_pos(
|
||||
psys->childcache, child_count, &pos_step, &length_step);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void particle_batch_cache_ensure_pos_and_seg(PTCacheEdit *edit,
|
||||
ParticleSystem *psys,
|
||||
ModifierData *md,
|
||||
@@ -1466,7 +1054,7 @@ static void drw_particle_update_ptcache_edit(Object *object_eval,
|
||||
}
|
||||
}
|
||||
|
||||
static void drw_particle_update_ptcache(Object *object_eval, ParticleSystem *psys)
|
||||
void drw_particle_update_ptcache(Object *object_eval, ParticleSystem *psys)
|
||||
{
|
||||
if ((object_eval->mode & OB_MODE_PARTICLE_EDIT) == 0) {
|
||||
return;
|
||||
@@ -1480,28 +1068,27 @@ static void drw_particle_update_ptcache(Object *object_eval, ParticleSystem *psy
|
||||
}
|
||||
}
|
||||
|
||||
struct ParticleDrawSource {
|
||||
Object *object;
|
||||
ParticleSystem *psys;
|
||||
ModifierData *md;
|
||||
PTCacheEdit *edit;
|
||||
};
|
||||
|
||||
static void drw_particle_get_hair_source(Object *object,
|
||||
ParticleSystem *psys,
|
||||
ModifierData *md,
|
||||
PTCacheEdit *edit,
|
||||
ParticleDrawSource *r_draw_source)
|
||||
ParticleDrawSource drw_particle_get_hair_source(Object *object,
|
||||
ParticleSystem *psys,
|
||||
ModifierData *md,
|
||||
PTCacheEdit *edit,
|
||||
const int additional_subdivision)
|
||||
{
|
||||
const DRWContext *draw_ctx = DRW_context_get();
|
||||
r_draw_source->object = object;
|
||||
r_draw_source->psys = psys;
|
||||
r_draw_source->md = md;
|
||||
r_draw_source->edit = edit;
|
||||
if (psys_in_edit_mode(draw_ctx->depsgraph, psys)) {
|
||||
r_draw_source->object = DEG_get_original(object);
|
||||
r_draw_source->psys = psys_orig_get(psys);
|
||||
object = DEG_get_original(object);
|
||||
psys = psys_orig_get(psys);
|
||||
}
|
||||
ParticleBatchCache *cache = particle_batch_cache_get(psys);
|
||||
|
||||
ParticleDrawSource src = ParticleDrawSource(cache->hair.points_by_curve_storage,
|
||||
cache->hair.evaluated_points_by_curve_storage,
|
||||
math::clamp(additional_subdivision, 0, 3));
|
||||
src.object = object;
|
||||
src.psys = psys;
|
||||
src.md = md;
|
||||
src.edit = edit;
|
||||
return src;
|
||||
}
|
||||
|
||||
gpu::Batch *DRW_particles_batch_cache_get_hair(Object *object,
|
||||
@@ -1511,8 +1098,7 @@ gpu::Batch *DRW_particles_batch_cache_get_hair(Object *object,
|
||||
ParticleBatchCache *cache = particle_batch_cache_get(psys);
|
||||
if (cache->hair.hairs == nullptr) {
|
||||
drw_particle_update_ptcache(object, psys);
|
||||
ParticleDrawSource source;
|
||||
drw_particle_get_hair_source(object, psys, md, nullptr, &source);
|
||||
ParticleDrawSource source = drw_particle_get_hair_source(object, psys, md, nullptr, 0);
|
||||
ensure_seg_pt_count(source.edit, source.psys, &cache->hair);
|
||||
particle_batch_cache_ensure_pos_and_seg(source.edit, source.psys, source.md, &cache->hair);
|
||||
cache->hair.hairs = GPU_batch_create(
|
||||
@@ -1705,54 +1291,377 @@ gpu::Batch *DRW_particles_batch_cache_get_edit_tip_points(Object *object,
|
||||
return cache->edit_tip_points;
|
||||
}
|
||||
|
||||
bool particles_ensure_procedural_data(Object *object,
|
||||
ParticleSystem *psys,
|
||||
ModifierData *md,
|
||||
ParticleHairCache **r_hair_cache,
|
||||
GPUMaterial *gpu_material,
|
||||
int subdiv,
|
||||
int thickness_res)
|
||||
/* Can return DMCACHE_NOTFOUND in case of invalid mapping. */
|
||||
static int particle_mface_index(const ParticleData &particle, int face_count_legacy)
|
||||
{
|
||||
bool need_ft_update = false;
|
||||
if (!ELEM(particle.num_dmcache, DMCACHE_NOTFOUND, DMCACHE_ISCHILD)) {
|
||||
return particle.num_dmcache;
|
||||
}
|
||||
if (particle.num < face_count_legacy) {
|
||||
return (particle.num == DMCACHE_ISCHILD) ? DMCACHE_NOTFOUND : particle.num;
|
||||
}
|
||||
return DMCACHE_NOTFOUND;
|
||||
}
|
||||
static int particle_mface_index(const ChildParticle &particle, int /*face_count_legacy*/)
|
||||
{
|
||||
return particle.num;
|
||||
}
|
||||
|
||||
drw_particle_update_ptcache(object, psys);
|
||||
static float4 particle_mcol_convert(const MCol &mcol)
|
||||
{
|
||||
float4 col;
|
||||
/* Convert to linear ushort and swizzle */
|
||||
col[0] = BLI_color_from_srgb_table[mcol.b];
|
||||
col[1] = BLI_color_from_srgb_table[mcol.g];
|
||||
col[2] = BLI_color_from_srgb_table[mcol.r];
|
||||
col[3] = mcol.a / 255.0f;
|
||||
return col;
|
||||
}
|
||||
|
||||
ParticleDrawSource source;
|
||||
drw_particle_get_hair_source(object, psys, md, nullptr, &source);
|
||||
template<typename ParticleDataT>
|
||||
static float4 interpolate(const ParticleDataT &particle, Span<MFace> mfaces, Span<MCol> mcols)
|
||||
{
|
||||
int num = particle_mface_index(particle, mfaces.size());
|
||||
if (num == DMCACHE_NOTFOUND) {
|
||||
return float4(0, 0, 0, 1);
|
||||
}
|
||||
/* CustomDataLayer CD_MCOL has 4 structs per face. */
|
||||
MCol mcol;
|
||||
psys_interpolate_mcol(mcols.slice(num * 4, 4).data(), mfaces[num].v4, particle.fuv, &mcol);
|
||||
return particle_mcol_convert(mcol);
|
||||
}
|
||||
|
||||
ParticleSettings *part = source.psys->part;
|
||||
ParticleBatchCache *cache = particle_batch_cache_get(source.psys);
|
||||
*r_hair_cache = &cache->hair;
|
||||
template<typename ParticleDataT>
|
||||
static float2 interpolate(const ParticleDataT &particle, Span<MFace> mfaces, Span<MTFace> mtfaces)
|
||||
{
|
||||
int num = particle_mface_index(particle, mfaces.size());
|
||||
if (num == DMCACHE_NOTFOUND) {
|
||||
return float2(0);
|
||||
}
|
||||
float2 uv;
|
||||
psys_interpolate_uvs(&mtfaces[num], mfaces[num].v4, particle.fuv, uv);
|
||||
return uv;
|
||||
}
|
||||
|
||||
(*r_hair_cache)->final[subdiv].strands_res = 1 << (part->draw_step + subdiv);
|
||||
static std::optional<StringRef> get_first_uv_name(const bke::AttributeAccessor &attributes)
|
||||
{
|
||||
std::optional<StringRef> name;
|
||||
attributes.foreach_attribute([&](const bke::AttributeIter &iter) {
|
||||
if (iter.data_type == bke::AttrType::Float2) {
|
||||
name = iter.name;
|
||||
iter.stop();
|
||||
}
|
||||
});
|
||||
return name;
|
||||
}
|
||||
|
||||
/* Refreshed on combing and simulation. */
|
||||
if ((*r_hair_cache)->proc_point_buf == nullptr ||
|
||||
(gpu_material && (*r_hair_cache)->proc_length_buf == nullptr))
|
||||
{
|
||||
ensure_seg_pt_count(source.edit, source.psys, &cache->hair);
|
||||
particle_batch_cache_ensure_procedural_pos(
|
||||
source.edit, source.psys, &cache->hair, gpu_material);
|
||||
need_ft_update = true;
|
||||
template<typename T>
|
||||
Span<T> span_from_custom_data_layer(const Mesh &mesh,
|
||||
const eCustomDataType type,
|
||||
const StringRef name)
|
||||
{
|
||||
int layer_id = CustomData_get_named_layer(&mesh.fdata_legacy, type, name);
|
||||
return {static_cast<const T *>(CustomData_get_layer_n(&mesh.fdata_legacy, type, layer_id)),
|
||||
/* There is 4 MCol per face. */
|
||||
mesh.totface_legacy * (std::is_same_v<T, MCol> ? 4 : 1)};
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
Span<T> span_from_custom_data_layer(const Mesh &mesh, const eCustomDataType type)
|
||||
{
|
||||
return {static_cast<const T *>(CustomData_get_layer(&mesh.fdata_legacy, type)),
|
||||
mesh.totface_legacy};
|
||||
}
|
||||
|
||||
template<typename InputT, typename OutputT, eCustomDataType data_type>
|
||||
static gpu::VertBufPtr interpolate_face_corner_attribute_to_curve(ParticleDrawSource &src,
|
||||
const StringRef name)
|
||||
{
|
||||
ParticleSystemModifierData *psmd = (ParticleSystemModifierData *)src.md;
|
||||
Mesh &mesh = *psmd->mesh_final;
|
||||
|
||||
/* TODO(fclem): Use normalized integer format. */
|
||||
gpu::VertBufPtr vbo = gpu::VertBufPtr(
|
||||
GPU_vertbuf_create_with_format_ex(gpu::GenericVertexFormat<OutputT>::format(),
|
||||
GPU_USAGE_STATIC | GPU_USAGE_FLAG_BUFFER_TEXTURE_ONLY));
|
||||
vbo->allocate(src.curves_num());
|
||||
MutableSpan<OutputT> data = vbo->data<OutputT>();
|
||||
|
||||
const int emit_from = psmd->psys->part->from;
|
||||
/* True if no interpolation for child particle. */
|
||||
const bool is_simple = (src.psys->part->childtype == PART_CHILD_PARTICLES) ||
|
||||
!ELEM(emit_from, PART_FROM_FACE, PART_FROM_VOLUME);
|
||||
|
||||
BKE_mesh_tessface_ensure(&mesh);
|
||||
Span<InputT> attr = span_from_custom_data_layer<InputT>(mesh, data_type, name);
|
||||
Span<MFace> mfaces = span_from_custom_data_layer<MFace>(mesh, CD_MFACE);
|
||||
Span<ChildParticle> children(src.psys->child, src.psys->totchild);
|
||||
Span<ParticleData> particles(src.psys->particles, src.psys->totpart);
|
||||
|
||||
/* Index of the particle/hair curve. Note that the order of the loops matter. */
|
||||
int curve_index = 0;
|
||||
|
||||
ParticleSpans part_spans = src.particles_get();
|
||||
for (const int particle_index : part_spans.parent.index_range()) {
|
||||
data[curve_index++] = interpolate(particles[particle_index], mfaces, attr);
|
||||
}
|
||||
|
||||
/* Refreshed if active layer or custom data changes. */
|
||||
if ((*r_hair_cache)->proc_strand_buf == nullptr) {
|
||||
particle_batch_cache_ensure_procedural_strand_data(
|
||||
source.edit, source.psys, source.md, &cache->hair);
|
||||
if (is_simple) {
|
||||
/* Fallback array if parent particles are not displayed. */
|
||||
Vector<OutputT> parent_data;
|
||||
if (part_spans.parent.is_empty()) {
|
||||
parent_data.reserve(src.psys->totpart);
|
||||
for (int particle_index : IndexRange(src.psys->totpart)) {
|
||||
parent_data.append(interpolate(particles[particle_index], mfaces, attr));
|
||||
}
|
||||
}
|
||||
|
||||
Span<OutputT> data_parent(part_spans.parent.is_empty() ? parent_data.data() : data.data(),
|
||||
src.psys->totpart);
|
||||
|
||||
for (const int particle_index : part_spans.children.index_range()) {
|
||||
/* Simple copy of the parent data. */
|
||||
data[curve_index++] = data_parent[children[particle_index].parent];
|
||||
}
|
||||
}
|
||||
else {
|
||||
for (const int particle_index : part_spans.children.index_range()) {
|
||||
data[curve_index++] = interpolate(children[particle_index], mfaces, attr);
|
||||
}
|
||||
}
|
||||
return vbo;
|
||||
}
|
||||
|
||||
static gpu::VertBufPtr ensure_curve_attribute(ParticleDrawSource &src,
|
||||
const Mesh &mesh,
|
||||
const StringRef name,
|
||||
bool &r_is_point_domain)
|
||||
{
|
||||
using namespace bke;
|
||||
/* Note: All legacy hair attributes come from the emitter mesh and are on per curve domain. */
|
||||
r_is_point_domain = false;
|
||||
|
||||
const AttributeAccessor attributes = mesh.attributes();
|
||||
|
||||
auto meta_data = attributes.lookup_meta_data(name);
|
||||
if (meta_data && meta_data->domain == bke::AttrDomain::Corner) {
|
||||
if (meta_data->data_type == AttrType::ColorByte) {
|
||||
return interpolate_face_corner_attribute_to_curve<MCol, float4, CD_MCOL>(src, name);
|
||||
}
|
||||
if (meta_data->data_type == AttrType::Float2) {
|
||||
return interpolate_face_corner_attribute_to_curve<MTFace, float2, CD_MTFACE>(src, name);
|
||||
}
|
||||
}
|
||||
/* Attribute doesn't exist or is of an incompatible type.
|
||||
* Replace it with a black curve domain attribute. */
|
||||
return gpu::VertBuf::from_varray(VArray<float>::from_single(1, src.curves_num()));
|
||||
}
|
||||
|
||||
void CurvesEvalCache::ensure_attribute(CurvesModule & /*module*/,
|
||||
ParticleDrawSource &src,
|
||||
const Mesh &mesh,
|
||||
const StringRef name,
|
||||
const int index)
|
||||
{
|
||||
char sampler_name[32];
|
||||
drw_curves_get_attribute_sampler_name(name, sampler_name);
|
||||
|
||||
gpu::VertBufPtr attr_buf = ensure_curve_attribute(
|
||||
src, mesh, name, attributes_point_domain[index]);
|
||||
|
||||
/* Existing final data may have been for a different attribute (with a different name or domain),
|
||||
* free the data. */
|
||||
this->curve_attributes_buf[index].reset();
|
||||
|
||||
/* Ensure final data for points. */
|
||||
if (attributes_point_domain[index]) {
|
||||
BLI_assert_unreachable();
|
||||
}
|
||||
else {
|
||||
this->curve_attributes_buf[index] = std::move(attr_buf);
|
||||
}
|
||||
}
|
||||
|
||||
void CurvesEvalCache::ensure_attributes(CurvesModule &module,
|
||||
ParticleDrawSource &src,
|
||||
const GPUMaterial *gpu_material)
|
||||
{
|
||||
ParticleSystemModifierData *psmd = (ParticleSystemModifierData *)src.md;
|
||||
if (psmd == nullptr || psmd->mesh_final == nullptr || src.curves_num() == 0) {
|
||||
return;
|
||||
}
|
||||
const Mesh &mesh = *psmd->mesh_final;
|
||||
const bke::AttributeAccessor attributes = mesh.attributes();
|
||||
|
||||
if (gpu_material) {
|
||||
VectorSet<std::string> attrs_needed;
|
||||
ListBase gpu_attrs = GPU_material_attributes(gpu_material);
|
||||
LISTBASE_FOREACH (GPUMaterialAttribute *, gpu_attr, &gpu_attrs) {
|
||||
StringRef name = gpu_attr->name;
|
||||
if (name.is_empty()) {
|
||||
if (std::optional<StringRef> uv_name = get_first_uv_name(attributes)) {
|
||||
drw_attributes_add_request(&attrs_needed, *uv_name);
|
||||
}
|
||||
}
|
||||
if (!attributes.contains(name)) {
|
||||
continue;
|
||||
}
|
||||
drw_attributes_add_request(&attrs_needed, name);
|
||||
}
|
||||
|
||||
if (!drw_attributes_overlap(&attr_used, &attrs_needed)) {
|
||||
/* Some new attributes have been added, free all and start over. */
|
||||
for (const int i : IndexRange(GPU_MAX_ATTR)) {
|
||||
this->curve_attributes_buf[i].reset();
|
||||
}
|
||||
drw_attributes_merge(&attr_used, &attrs_needed);
|
||||
}
|
||||
drw_attributes_merge(&attr_used_over_time, &attrs_needed);
|
||||
}
|
||||
|
||||
/* Refreshed only on subdiv count change. */
|
||||
if ((*r_hair_cache)->final[subdiv].proc_buf == nullptr) {
|
||||
particle_batch_cache_ensure_procedural_final_points(&cache->hair, subdiv);
|
||||
need_ft_update = true;
|
||||
for (const int i : attr_used.index_range()) {
|
||||
if (this->curve_attributes_buf[i]) {
|
||||
continue;
|
||||
}
|
||||
ensure_attribute(module, src, mesh, attr_used[i], i);
|
||||
}
|
||||
if ((*r_hair_cache)->final[subdiv].proc_hairs[thickness_res - 1] == nullptr) {
|
||||
particle_batch_cache_ensure_procedural_indices(
|
||||
source.edit, source.psys, &cache->hair, thickness_res, subdiv);
|
||||
}
|
||||
|
||||
void CurvesEvalCache::ensure_common(ParticleDrawSource &src)
|
||||
{
|
||||
if (points_by_curve_buf) {
|
||||
return;
|
||||
}
|
||||
|
||||
return need_ft_update;
|
||||
this->points_by_curve_buf = gpu::VertBuf::from_span(src.points_by_curve().data());
|
||||
this->evaluated_points_by_curve_buf = gpu::VertBuf::from_span(
|
||||
src.evaluated_points_by_curve().data());
|
||||
|
||||
/* Use the same type for all curves. */
|
||||
auto type_varray = VArray<int8_t>::from_single(CURVE_TYPE_CATMULL_ROM, src.curves_num());
|
||||
auto resolution_varray = VArray<int32_t>::from_single(src.resolution(), src.curves_num());
|
||||
/* Not used. */
|
||||
auto cyclic_offsets_varray = VArray<int32_t>::from_single(0, 2);
|
||||
/* TODO(fclem): Optimize shaders to avoid needing to upload this data if data is uniform.
|
||||
* This concerns all varray. */
|
||||
this->curves_type_buf = gpu::VertBuf::from_varray(type_varray);
|
||||
this->curves_resolution_buf = gpu::VertBuf::from_varray(resolution_varray);
|
||||
this->curves_cyclic_buf = gpu::VertBuf::from_varray(cyclic_offsets_varray);
|
||||
}
|
||||
|
||||
/* Copied from cycles. */
|
||||
static float hair_shape_radius(float shape, float root, float tip, float time)
|
||||
{
|
||||
BLI_assert(time >= 0.0f);
|
||||
BLI_assert(time <= 1.0f);
|
||||
float radius = 1.0f - time;
|
||||
if (shape < 0.0f) {
|
||||
radius = powf(radius, 1.0f + shape);
|
||||
}
|
||||
else {
|
||||
radius = powf(radius, 1.0f / (1.0f - shape));
|
||||
}
|
||||
return (radius * (root - tip)) + tip;
|
||||
}
|
||||
|
||||
void CurvesEvalCache::ensure_positions(CurvesModule &module, ParticleDrawSource &src)
|
||||
{
|
||||
if (evaluated_pos_rad_buf) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (src.curves_num() == 0) {
|
||||
/* Garbage data. */
|
||||
this->evaluated_pos_rad_buf = gpu::VertBuf::device_only<float4>(1);
|
||||
this->evaluated_time_buf = gpu::VertBuf::device_only<float>(4);
|
||||
this->curves_length_buf = gpu::VertBuf::device_only<float>(4);
|
||||
return;
|
||||
}
|
||||
|
||||
ensure_common(src);
|
||||
|
||||
gpu::VertBufPtr points_pos_buf = gpu::VertBuf::from_size<float3>(src.points_num());
|
||||
gpu::VertBufPtr points_rad_buf = gpu::VertBuf::from_size<float>(src.points_num());
|
||||
|
||||
MutableSpan<float3> points_pos = points_pos_buf->data<float3>();
|
||||
MutableSpan<float> points_rad = points_rad_buf->data<float>();
|
||||
|
||||
const ParticleSettings &part = *src.psys->part;
|
||||
const float hair_rad_shape = part.shape;
|
||||
const float hair_rad_root = part.rad_root * part.rad_scale * 0.5f;
|
||||
const float hair_rad_tip = part.rad_tip * part.rad_scale * 0.5f;
|
||||
const bool hair_close_tip = (part.shape_flag & PART_SHAPE_CLOSE_TIP) != 0;
|
||||
|
||||
int i = 0;
|
||||
src.particles_get().foreach_strand([&](Span<ParticleCacheKey> strand) {
|
||||
int j = 0;
|
||||
for (const ParticleCacheKey &point : strand) {
|
||||
points_pos[i] = point.co;
|
||||
points_rad[i] = (hair_close_tip && (j == strand.index_range().last())) ?
|
||||
0.0f :
|
||||
hair_shape_radius(
|
||||
hair_rad_shape, hair_rad_root, hair_rad_tip, point.time);
|
||||
i++, j++;
|
||||
}
|
||||
});
|
||||
|
||||
this->evaluated_pos_rad_buf = gpu::VertBuf::device_only<float4>(src.evaluated_points_num());
|
||||
|
||||
float4x4 transform = src.object->world_to_object();
|
||||
|
||||
module.evaluate_positions(true,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
src.curves_num(),
|
||||
*this,
|
||||
std::move(points_pos_buf),
|
||||
std::move(points_rad_buf),
|
||||
evaluated_pos_rad_buf,
|
||||
transform);
|
||||
|
||||
/* TODO(fclem): Make time and length optional. */
|
||||
this->evaluated_time_buf = gpu::VertBuf::device_only<float>(src.evaluated_points_num());
|
||||
this->curves_length_buf = gpu::VertBuf::device_only<float>(src.curves_num());
|
||||
|
||||
module.evaluate_curve_length_intercept(false, src.curves_num(), *this);
|
||||
}
|
||||
|
||||
gpu::VertBufPtr &CurvesEvalCache::indirection_buf_get(CurvesModule &module,
|
||||
ParticleDrawSource &src,
|
||||
int face_per_segment)
|
||||
{
|
||||
const bool is_ribbon = face_per_segment < 2;
|
||||
|
||||
gpu::VertBufPtr &indirection_buf = is_ribbon ? this->indirection_ribbon_buf :
|
||||
this->indirection_cylinder_buf;
|
||||
if (indirection_buf) {
|
||||
return indirection_buf;
|
||||
}
|
||||
|
||||
if (src.curves_num() == 0) {
|
||||
/* Garbage data. */
|
||||
indirection_buf = gpu::VertBuf::device_only<int>(4);
|
||||
return indirection_buf;
|
||||
}
|
||||
|
||||
ensure_common(src);
|
||||
|
||||
indirection_buf = module.evaluate_topology_indirection(
|
||||
src.curves_num(), src.evaluated_points_num(), *this, is_ribbon, false);
|
||||
|
||||
return indirection_buf;
|
||||
}
|
||||
|
||||
CurvesEvalCache &hair_particle_get_eval_cache(ParticleDrawSource &src)
|
||||
{
|
||||
ParticleBatchCache *cache = particle_batch_cache_get(src.psys);
|
||||
CurvesEvalCache &eval_cache = cache->hair.eval_cache;
|
||||
if (assign_if_different(eval_cache.resolution, src.resolution())) {
|
||||
particle_batch_cache_clear_hair(&cache->hair);
|
||||
}
|
||||
return eval_cache;
|
||||
}
|
||||
|
||||
} // namespace blender::draw
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -8,13 +8,12 @@
|
||||
* \brief Contains procedural GPU hair drawing methods.
|
||||
*/
|
||||
|
||||
#include "BLI_utildefines.h"
|
||||
|
||||
#include "DNA_curves_types.h"
|
||||
|
||||
#include "BLI_math_base.h"
|
||||
|
||||
#include "BKE_attribute.hh"
|
||||
#include "BKE_curves.hh"
|
||||
#include "BKE_customdata.hh"
|
||||
|
||||
#include "GPU_batch.hh"
|
||||
#include "GPU_capabilities.hh"
|
||||
@@ -29,6 +28,7 @@
|
||||
#include "draw_cache_impl.hh"
|
||||
#include "draw_common.hh"
|
||||
#include "draw_context_private.hh"
|
||||
#include "draw_curves_defines.hh"
|
||||
#include "draw_curves_private.hh"
|
||||
#include "draw_hair_private.hh"
|
||||
#include "draw_shader.hh"
|
||||
@@ -88,84 +88,208 @@ void DRW_curves_module_free(CurvesModule *curves_module)
|
||||
MEM_delete(curves_module);
|
||||
}
|
||||
|
||||
static void drw_curves_cache_update_compute(CurvesEvalCache *cache,
|
||||
const int curves_num,
|
||||
gpu::VertBuf *output_buf,
|
||||
gpu::VertBuf *input_buf)
|
||||
void CurvesModule::dispatch(const int curve_count, PassSimple::Sub &pass)
|
||||
{
|
||||
BLI_assert(input_buf != nullptr);
|
||||
BLI_assert(output_buf != nullptr);
|
||||
gpu::Shader *shader = DRW_shader_curves_refine_get(CURVES_EVAL_CATMULL_ROM);
|
||||
|
||||
/* TODO(fclem): Remove Global access. */
|
||||
PassSimple &pass = drw_get().data->curves_module->refine;
|
||||
pass.shader_set(shader);
|
||||
pass.bind_texture("hairPointBuffer", input_buf);
|
||||
pass.bind_texture("hairStrandBuffer", cache->proc_strand_buf);
|
||||
pass.bind_texture("hairStrandSegBuffer", cache->proc_strand_seg_buf);
|
||||
pass.push_constant("hairStrandsRes", &cache->final.resolution);
|
||||
pass.bind_ssbo("posTime", output_buf);
|
||||
|
||||
const int max_strands_per_call = GPU_max_work_group_count(0);
|
||||
/* Note that the GPU_max_work_group_count can be INT_MAX.
|
||||
* Promote to 64bit int to avoid overflow. */
|
||||
const int64_t max_strands_per_call = int64_t(GPU_max_work_group_count(0)) *
|
||||
CURVES_PER_THREADGROUP;
|
||||
int strands_start = 0;
|
||||
while (strands_start < curves_num) {
|
||||
int batch_strands_len = std::min(curves_num - strands_start, max_strands_per_call);
|
||||
pass.push_constant("hairStrandOffset", strands_start);
|
||||
pass.dispatch(int3(batch_strands_len, cache->final.resolution, 1));
|
||||
while (strands_start < curve_count) {
|
||||
int batch_strands_len = std::min(int64_t(curve_count - strands_start), max_strands_per_call);
|
||||
pass.push_constant("curves_start", strands_start);
|
||||
pass.push_constant("curves_count", batch_strands_len);
|
||||
pass.dispatch(divide_ceil_u(batch_strands_len, CURVES_PER_THREADGROUP));
|
||||
strands_start += batch_strands_len;
|
||||
}
|
||||
}
|
||||
|
||||
static void drw_curves_cache_update_compute(CurvesEvalCache *cache)
|
||||
gpu::VertBufPtr CurvesModule::evaluate_topology_indirection(const int curve_count,
|
||||
const int point_count,
|
||||
CurvesEvalCache &cache,
|
||||
bool is_ribbon,
|
||||
bool has_cyclic)
|
||||
{
|
||||
const int curves_num = cache->curves_num;
|
||||
const int final_points_len = cache->final.resolution * curves_num;
|
||||
if (final_points_len == 0) {
|
||||
return;
|
||||
int element_count = is_ribbon ? (point_count + curve_count) : (point_count - curve_count);
|
||||
if (has_cyclic) {
|
||||
element_count += curve_count;
|
||||
}
|
||||
gpu::VertBufPtr indirection_buf = gpu::VertBuf::device_only<int>(element_count);
|
||||
|
||||
drw_curves_cache_update_compute(cache, curves_num, cache->final.proc_buf, cache->proc_point_buf);
|
||||
PassSimple::Sub &pass = refine.sub("Topology");
|
||||
pass.shader_set(DRW_shader_curves_topology_get());
|
||||
pass.bind_ssbo("evaluated_offsets_buf", cache.evaluated_points_by_curve_buf);
|
||||
pass.bind_ssbo("curves_cyclic_buf", cache.curves_cyclic_buf);
|
||||
pass.bind_ssbo("indirection_buf", indirection_buf);
|
||||
pass.push_constant("is_ribbon_topology", is_ribbon);
|
||||
pass.push_constant("use_cyclic", has_cyclic);
|
||||
dispatch(curve_count, pass);
|
||||
|
||||
const VectorSet<std::string> &attrs = cache->final.attr_used;
|
||||
for (const int i : attrs.index_range()) {
|
||||
if (!cache->proc_attributes_point_domain[i]) {
|
||||
continue;
|
||||
}
|
||||
drw_curves_cache_update_compute(
|
||||
cache, curves_num, cache->final.attributes_buf[i], cache->proc_attributes_buf[i]);
|
||||
}
|
||||
return indirection_buf;
|
||||
}
|
||||
|
||||
static CurvesEvalCache *drw_curves_cache_get(Curves &curves,
|
||||
GPUMaterial *gpu_material,
|
||||
int subdiv,
|
||||
int thickness_res)
|
||||
void CurvesModule::evaluate_curve_attribute(const bool has_catmull,
|
||||
const bool has_bezier,
|
||||
const bool has_poly,
|
||||
const bool has_nurbs,
|
||||
const bool has_cyclic,
|
||||
const int curve_count,
|
||||
CurvesEvalCache &cache,
|
||||
CurvesEvalShader shader_type,
|
||||
gpu::VertBufPtr input_buf,
|
||||
gpu::VertBufPtr &output_buf,
|
||||
gpu::VertBuf *input2_buf /* = nullptr */,
|
||||
float4x4 transform /* = float4x4::identity() */)
|
||||
{
|
||||
CurvesEvalCache *cache;
|
||||
const bool update = curves_ensure_procedural_data(
|
||||
&curves, &cache, gpu_material, subdiv, thickness_res);
|
||||
BLI_assert(input_buf != nullptr);
|
||||
BLI_assert(output_buf != nullptr);
|
||||
|
||||
if (update) {
|
||||
drw_curves_cache_update_compute(cache);
|
||||
gpu::Shader *shader = DRW_shader_curves_refine_get(shader_type);
|
||||
|
||||
const char *pass_name = nullptr;
|
||||
|
||||
switch (shader_type) {
|
||||
case CURVES_EVAL_POSITION:
|
||||
pass_name = "Position";
|
||||
break;
|
||||
case CURVES_EVAL_FLOAT:
|
||||
pass_name = "Float Attribute";
|
||||
break;
|
||||
case CURVES_EVAL_FLOAT2:
|
||||
pass_name = "Float2 Attribute";
|
||||
break;
|
||||
case CURVES_EVAL_FLOAT3:
|
||||
pass_name = "Float3 Attribute";
|
||||
break;
|
||||
case CURVES_EVAL_FLOAT4:
|
||||
pass_name = "Float4 Attribute";
|
||||
break;
|
||||
case CURVES_EVAL_LENGTH_INTERCEPT:
|
||||
pass_name = "Length-Intercept Attributes";
|
||||
break;
|
||||
}
|
||||
return cache;
|
||||
|
||||
PassSimple::Sub &pass = refine.sub(pass_name);
|
||||
pass.bind_ssbo(POINTS_BY_CURVES_SLOT, cache.points_by_curve_buf);
|
||||
pass.bind_ssbo(CURVE_TYPE_SLOT, cache.curves_type_buf);
|
||||
pass.bind_ssbo(CURVE_CYCLIC_SLOT, cache.curves_cyclic_buf);
|
||||
pass.bind_ssbo(CURVE_RESOLUTION_SLOT, cache.curves_resolution_buf);
|
||||
pass.bind_ssbo(EVALUATED_POINT_SLOT, cache.evaluated_points_by_curve_buf);
|
||||
|
||||
switch (shader_type) {
|
||||
case CURVES_EVAL_POSITION:
|
||||
pass.bind_ssbo(POINT_POSITIONS_SLOT, input_buf);
|
||||
pass.bind_ssbo(POINT_RADII_SLOT, input2_buf);
|
||||
pass.bind_ssbo(EVALUATED_POS_RAD_SLOT, cache.evaluated_pos_rad_buf);
|
||||
/* Move ownership of the radius input vbo to the module. */
|
||||
this->transient_buffers.append(gpu::VertBufPtr(input2_buf));
|
||||
break;
|
||||
case CURVES_EVAL_FLOAT:
|
||||
case CURVES_EVAL_FLOAT2:
|
||||
case CURVES_EVAL_FLOAT3:
|
||||
case CURVES_EVAL_FLOAT4:
|
||||
pass.bind_ssbo(POINT_ATTR_SLOT, input_buf);
|
||||
pass.bind_ssbo(EVALUATED_ATTR_SLOT, output_buf);
|
||||
break;
|
||||
case CURVES_EVAL_LENGTH_INTERCEPT:
|
||||
pass.bind_ssbo(EVALUATED_POS_RAD_SLOT, cache.evaluated_pos_rad_buf);
|
||||
pass.bind_ssbo(EVALUATED_TIME_SLOT, cache.evaluated_time_buf);
|
||||
pass.bind_ssbo(CURVES_LENGTH_SLOT, cache.curves_length_buf);
|
||||
/* Synchronize positions reads. */
|
||||
pass.barrier(GPU_BARRIER_SHADER_STORAGE);
|
||||
break;
|
||||
}
|
||||
|
||||
if (has_catmull) {
|
||||
PassSimple::Sub &sub = pass.sub("Catmull-Rom");
|
||||
sub.specialize_constant(shader, "evaluated_type", int(CURVE_TYPE_CATMULL_ROM));
|
||||
sub.shader_set(shader);
|
||||
/* Dummy, not used for Catmull-Rom. */
|
||||
sub.bind_ssbo("handles_positions_left_buf", this->dummy_vbo);
|
||||
sub.bind_ssbo("handles_positions_right_buf", this->dummy_vbo);
|
||||
sub.bind_ssbo("bezier_offsets_buf", this->dummy_vbo);
|
||||
/* Bake object transform for legacy hair particle. */
|
||||
sub.push_constant("transform", transform);
|
||||
sub.push_constant("use_cyclic", has_cyclic);
|
||||
dispatch(curve_count, sub);
|
||||
}
|
||||
|
||||
if (has_bezier) {
|
||||
PassSimple::Sub &sub = pass.sub("Bezier");
|
||||
sub.specialize_constant(shader, "evaluated_type", int(CURVE_TYPE_BEZIER));
|
||||
sub.shader_set(shader);
|
||||
sub.bind_ssbo("handles_positions_left_buf", cache.handles_positions_left_buf);
|
||||
sub.bind_ssbo("handles_positions_right_buf", cache.handles_positions_right_buf);
|
||||
sub.bind_ssbo("bezier_offsets_buf", cache.bezier_offsets_buf);
|
||||
/* Bake object transform for legacy hair particle. */
|
||||
sub.push_constant("transform", transform);
|
||||
sub.push_constant("use_cyclic", has_cyclic);
|
||||
dispatch(curve_count, sub);
|
||||
}
|
||||
|
||||
if (has_nurbs) {
|
||||
PassSimple::Sub &sub = pass.sub("Nurbs");
|
||||
sub.specialize_constant(shader, "evaluated_type", int(CURVE_TYPE_NURBS));
|
||||
sub.shader_set(shader);
|
||||
sub.bind_ssbo("curves_resolution_buf", cache.curves_order_buf);
|
||||
sub.bind_ssbo("handles_positions_left_buf", cache.basis_cache_buf);
|
||||
sub.bind_ssbo("handles_positions_right_buf",
|
||||
cache.control_weights_buf.get() ? cache.control_weights_buf :
|
||||
cache.basis_cache_buf);
|
||||
sub.bind_ssbo("bezier_offsets_buf", cache.basis_cache_offset_buf);
|
||||
sub.push_constant("use_point_weight", cache.control_weights_buf.get() != nullptr);
|
||||
/* Bake object transform for legacy hair particle. */
|
||||
sub.push_constant("transform", transform);
|
||||
sub.push_constant("use_cyclic", has_cyclic);
|
||||
dispatch(curve_count, sub);
|
||||
}
|
||||
|
||||
if (has_poly) {
|
||||
PassSimple::Sub &sub = pass.sub("Poly");
|
||||
sub.specialize_constant(shader, "evaluated_type", int(CURVE_TYPE_POLY));
|
||||
sub.shader_set(shader);
|
||||
/* Dummy, not used for Poly. */
|
||||
sub.bind_ssbo("curves_resolution_buf", this->dummy_vbo);
|
||||
sub.bind_ssbo("handles_positions_left_buf", this->dummy_vbo);
|
||||
sub.bind_ssbo("handles_positions_right_buf", this->dummy_vbo);
|
||||
sub.bind_ssbo("bezier_offsets_buf", this->dummy_vbo);
|
||||
/* Bake object transform for legacy hair particle. */
|
||||
sub.push_constant("transform", transform);
|
||||
sub.push_constant("use_cyclic", has_cyclic);
|
||||
dispatch(curve_count, sub);
|
||||
}
|
||||
|
||||
/* Move ownership of the input vbo to the module. */
|
||||
this->transient_buffers.append(std::move(input_buf));
|
||||
}
|
||||
|
||||
gpu::VertBuf *DRW_curves_pos_buffer_get(Object *object)
|
||||
void CurvesModule::evaluate_curve_length_intercept(const bool has_cyclic,
|
||||
const int curve_count,
|
||||
CurvesEvalCache &cache)
|
||||
{
|
||||
const DRWContext *draw_ctx = DRW_context_get();
|
||||
const Scene *scene = draw_ctx->scene;
|
||||
gpu::Shader *shader = DRW_shader_curves_refine_get(CURVES_EVAL_LENGTH_INTERCEPT);
|
||||
|
||||
const int subdiv = scene->r.hair_subdiv;
|
||||
const int thickness_res = (scene->r.hair_type == SCE_HAIR_SHAPE_STRAND) ? 1 : 2;
|
||||
PassSimple::Sub &pass = refine.sub("Length-Intercept Attributes");
|
||||
pass.shader_set(shader);
|
||||
pass.bind_ssbo(POINTS_BY_CURVES_SLOT, cache.points_by_curve_buf);
|
||||
pass.bind_ssbo(CURVE_TYPE_SLOT, cache.curves_type_buf);
|
||||
pass.bind_ssbo(CURVE_CYCLIC_SLOT, cache.curves_cyclic_buf);
|
||||
pass.bind_ssbo(CURVE_RESOLUTION_SLOT, cache.curves_resolution_buf);
|
||||
pass.bind_ssbo(EVALUATED_POINT_SLOT, cache.evaluated_points_by_curve_buf);
|
||||
|
||||
Curves &curves = DRW_object_get_data_for_drawing<Curves>(*object);
|
||||
CurvesEvalCache *cache = drw_curves_cache_get(curves, nullptr, subdiv, thickness_res);
|
||||
|
||||
return cache->final.proc_buf;
|
||||
pass.bind_ssbo(EVALUATED_POS_RAD_SLOT, cache.evaluated_pos_rad_buf);
|
||||
pass.bind_ssbo(EVALUATED_TIME_SLOT, cache.evaluated_time_buf);
|
||||
pass.bind_ssbo(CURVES_LENGTH_SLOT, cache.curves_length_buf);
|
||||
pass.barrier(GPU_BARRIER_SHADER_STORAGE);
|
||||
/* Bake object transform for legacy hair particle. */
|
||||
pass.push_constant("use_cyclic", has_cyclic);
|
||||
dispatch(curve_count, pass);
|
||||
}
|
||||
|
||||
static int attribute_index_in_material(GPUMaterial *gpu_material, const StringRef name)
|
||||
static int attribute_index_in_material(const GPUMaterial *gpu_material,
|
||||
const StringRef name,
|
||||
bool is_curve_length = false,
|
||||
bool is_curve_intercept = false)
|
||||
{
|
||||
if (!gpu_material) {
|
||||
return -1;
|
||||
@@ -175,10 +299,19 @@ static int attribute_index_in_material(GPUMaterial *gpu_material, const StringRe
|
||||
|
||||
ListBase gpu_attrs = GPU_material_attributes(gpu_material);
|
||||
LISTBASE_FOREACH (GPUMaterialAttribute *, gpu_attr, &gpu_attrs) {
|
||||
if (gpu_attr->name == name) {
|
||||
if (gpu_attr->is_hair_length == true) {
|
||||
if (gpu_attr->is_hair_length == is_curve_length) {
|
||||
return index;
|
||||
}
|
||||
}
|
||||
else if (gpu_attr->is_hair_intercept == true) {
|
||||
if (gpu_attr->is_hair_intercept == is_curve_intercept) {
|
||||
return index;
|
||||
}
|
||||
}
|
||||
else if (gpu_attr->name == name) {
|
||||
return index;
|
||||
}
|
||||
|
||||
index++;
|
||||
}
|
||||
|
||||
@@ -190,84 +323,31 @@ void DRW_curves_update(draw::Manager &manager)
|
||||
DRW_submission_start();
|
||||
|
||||
/* TODO(fclem): Remove Global access. */
|
||||
PassSimple &pass = drw_get().data->curves_module->refine;
|
||||
CurvesModule &module = *drw_get().data->curves_module;
|
||||
|
||||
/* NOTE: This also update legacy hairs too as they populate the same pass. */
|
||||
manager.submit(pass);
|
||||
manager.submit(module.refine);
|
||||
GPU_memory_barrier(GPU_BARRIER_SHADER_STORAGE);
|
||||
|
||||
module.transient_buffers.clear();
|
||||
|
||||
/* Make sure calling this function again will not subdivide the same data. */
|
||||
pass.init();
|
||||
module.refine.init();
|
||||
|
||||
DRW_submission_end();
|
||||
}
|
||||
|
||||
/* New Draw Manager. */
|
||||
|
||||
static CurvesEvalCache *curves_cache_get(Curves &curves,
|
||||
GPUMaterial *gpu_material,
|
||||
int subdiv,
|
||||
int thickness_res)
|
||||
gpu::VertBuf *curves_pos_buffer_get(Object *object)
|
||||
{
|
||||
CurvesEvalCache *cache;
|
||||
const bool update = curves_ensure_procedural_data(
|
||||
&curves, &cache, gpu_material, subdiv, thickness_res);
|
||||
|
||||
if (!update) {
|
||||
return cache;
|
||||
}
|
||||
|
||||
const int curves_num = cache->curves_num;
|
||||
const int final_points_len = cache->final.resolution * curves_num;
|
||||
|
||||
CurvesModule &module = *drw_get().data->curves_module;
|
||||
|
||||
auto cache_update = [&](gpu::VertBuf *output_buf, gpu::VertBuf *input_buf) {
|
||||
PassSimple::Sub &ob_ps = module.refine.sub("Object Pass");
|
||||
|
||||
ob_ps.shader_set(DRW_shader_curves_refine_get(CURVES_EVAL_CATMULL_ROM));
|
||||
|
||||
ob_ps.bind_texture("hairPointBuffer", input_buf);
|
||||
ob_ps.bind_texture("hairStrandBuffer", cache->proc_strand_buf);
|
||||
ob_ps.bind_texture("hairStrandSegBuffer", cache->proc_strand_seg_buf);
|
||||
ob_ps.push_constant("hairStrandsRes", &cache->final.resolution);
|
||||
ob_ps.bind_ssbo("posTime", output_buf);
|
||||
|
||||
const int max_strands_per_call = GPU_max_work_group_count(0);
|
||||
int strands_start = 0;
|
||||
while (strands_start < curves_num) {
|
||||
int batch_strands_len = std::min(curves_num - strands_start, max_strands_per_call);
|
||||
PassSimple::Sub &sub_ps = ob_ps.sub("Sub Pass");
|
||||
sub_ps.push_constant("hairStrandOffset", strands_start);
|
||||
sub_ps.dispatch(int3(batch_strands_len, cache->final.resolution, 1));
|
||||
strands_start += batch_strands_len;
|
||||
}
|
||||
};
|
||||
|
||||
if (final_points_len > 0) {
|
||||
cache_update(cache->final.proc_buf, cache->proc_point_buf);
|
||||
|
||||
const VectorSet<std::string> &attrs = cache->final.attr_used;
|
||||
for (const int i : attrs.index_range()) {
|
||||
/* Only refine point attributes. */
|
||||
if (cache->proc_attributes_point_domain[i]) {
|
||||
cache_update(cache->final.attributes_buf[i], cache->proc_attributes_buf[i]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return cache;
|
||||
}
|
||||
|
||||
gpu::VertBuf *curves_pos_buffer_get(Scene *scene, Object *object)
|
||||
{
|
||||
const int subdiv = scene->r.hair_subdiv;
|
||||
const int thickness_res = (scene->r.hair_type == SCE_HAIR_SHAPE_STRAND) ? 1 : 2;
|
||||
|
||||
Curves &curves = DRW_object_get_data_for_drawing<Curves>(*object);
|
||||
CurvesEvalCache *cache = curves_cache_get(curves, nullptr, subdiv, thickness_res);
|
||||
|
||||
return cache->final.proc_buf;
|
||||
CurvesEvalCache &cache = curves_get_eval_cache(curves);
|
||||
cache.ensure_positions(module, curves.geometry.wrap());
|
||||
|
||||
return cache.evaluated_pos_rad_buf.get();
|
||||
}
|
||||
|
||||
static std::optional<StringRef> get_first_uv_name(const bke::AttributeAccessor &attributes)
|
||||
@@ -282,25 +362,33 @@ static std::optional<StringRef> get_first_uv_name(const bke::AttributeAccessor &
|
||||
return name;
|
||||
}
|
||||
|
||||
template<typename PassT>
|
||||
gpu::Batch *curves_sub_pass_setup_implementation(PassT &sub_ps,
|
||||
const Scene *scene,
|
||||
Object *ob,
|
||||
GPUMaterial *gpu_material)
|
||||
/* Return true if attribute exists in shader. */
|
||||
static bool set_attribute_type(const GPUMaterial *gpu_material,
|
||||
const StringRef name,
|
||||
CurvesInfosBuf &curves_infos,
|
||||
const bool is_point_domain)
|
||||
{
|
||||
/** NOTE: This still relies on the old DRW_curves implementation. */
|
||||
|
||||
CurvesModule &module = *drw_get().data->curves_module;
|
||||
CurvesInfosBuf &curves_infos = module.ubo_pool.alloc();
|
||||
BLI_assert(ob->type == OB_CURVES);
|
||||
Curves &curves_id = DRW_object_get_data_for_drawing<Curves>(*ob);
|
||||
|
||||
const int subdiv = scene->r.hair_subdiv;
|
||||
const int thickness_res = (scene->r.hair_type == SCE_HAIR_SHAPE_STRAND) ? 1 : 2;
|
||||
|
||||
CurvesEvalCache *curves_cache = drw_curves_cache_get(
|
||||
curves_id, gpu_material, subdiv, thickness_res);
|
||||
/* Some attributes may not be used in the shader anymore and were not garbage collected yet, so
|
||||
* we need to find the right index for this attribute as uniforms defining the scope of the
|
||||
* attributes are based on attribute loading order, which is itself based on the material's
|
||||
* attributes. */
|
||||
const int index = attribute_index_in_material(gpu_material, name);
|
||||
if (index == -1) {
|
||||
return false;
|
||||
}
|
||||
curves_infos.is_point_attribute[index][0] = is_point_domain;
|
||||
return true;
|
||||
}
|
||||
|
||||
template<typename PassT>
|
||||
void curves_bind_resources_implementation(PassT &sub_ps,
|
||||
CurvesModule &module,
|
||||
CurvesEvalCache &cache,
|
||||
const int face_per_segment,
|
||||
GPUMaterial *gpu_material,
|
||||
gpu::VertBufPtr &indirection_buf,
|
||||
const std::optional<StringRef> uv_name)
|
||||
{
|
||||
/* Ensure we have no unbound resources.
|
||||
* Required for Vulkan.
|
||||
* Fixes issues with certain GL drivers not drawing anything. */
|
||||
@@ -309,6 +397,8 @@ gpu::Batch *curves_sub_pass_setup_implementation(PassT &sub_ps,
|
||||
sub_ps.bind_texture("a", module.dummy_vbo);
|
||||
sub_ps.bind_texture("c", module.dummy_vbo);
|
||||
sub_ps.bind_texture("ac", module.dummy_vbo);
|
||||
sub_ps.bind_texture("l", module.dummy_vbo);
|
||||
sub_ps.bind_texture("i", module.dummy_vbo);
|
||||
if (gpu_material) {
|
||||
ListBase attr_list = GPU_material_attributes(gpu_material);
|
||||
ListBaseWrapper<GPUMaterialAttribute> attrs(attr_list);
|
||||
@@ -317,85 +407,133 @@ gpu::Batch *curves_sub_pass_setup_implementation(PassT &sub_ps,
|
||||
}
|
||||
}
|
||||
|
||||
/* TODO: Generalize radius implementation for curves data type. */
|
||||
float hair_rad_shape = 0.0f;
|
||||
float hair_rad_root = 0.005f;
|
||||
float hair_rad_tip = 0.0f;
|
||||
bool hair_close_tip = true;
|
||||
CurvesInfosBuf &curves_infos = module.ubo_pool.alloc();
|
||||
|
||||
/* Use the radius of the root and tip of the first curve for now. This is a workaround that we
|
||||
* use for now because we can't use a per-point radius yet. */
|
||||
const bke::CurvesGeometry &curves = curves_id.geometry.wrap();
|
||||
if (curves.curves_num() >= 1) {
|
||||
VArray<float> radii = *curves.attributes().lookup_or_default(
|
||||
"radius", bke::AttrDomain::Point, 0.005f);
|
||||
const IndexRange first_curve_points = curves.points_by_curve()[0];
|
||||
const float first_radius = radii[first_curve_points.first()];
|
||||
const float last_radius = radii[first_curve_points.last()];
|
||||
const float middle_radius = radii[first_curve_points.size() / 2];
|
||||
hair_rad_root = radii[first_curve_points.first()];
|
||||
hair_rad_tip = radii[first_curve_points.last()];
|
||||
hair_rad_shape = std::clamp(
|
||||
math::safe_divide(middle_radius - first_radius, last_radius - first_radius) * 2.0f - 1.0f,
|
||||
-1.0f,
|
||||
1.0f);
|
||||
{
|
||||
/* TODO(fclem): Compute only if needed. */
|
||||
const int index = attribute_index_in_material(gpu_material, "", true, false);
|
||||
if (index != -1) {
|
||||
sub_ps.bind_texture("l", cache.curves_length_buf);
|
||||
curves_infos.is_point_attribute[index][0] = false;
|
||||
}
|
||||
}
|
||||
{
|
||||
/* TODO(fclem): Compute only if needed. */
|
||||
const int index = attribute_index_in_material(gpu_material, "", false, true);
|
||||
if (index != -1) {
|
||||
sub_ps.bind_texture("i", cache.evaluated_time_buf);
|
||||
curves_infos.is_point_attribute[index][0] = true;
|
||||
}
|
||||
}
|
||||
|
||||
sub_ps.bind_texture("hairPointBuffer", curves_cache->final.proc_buf);
|
||||
if (curves_cache->proc_length_buf) {
|
||||
sub_ps.bind_texture("l", curves_cache->proc_length_buf);
|
||||
}
|
||||
|
||||
const std::optional<StringRef> uv_name = get_first_uv_name(
|
||||
curves_id.geometry.wrap().attributes());
|
||||
const VectorSet<std::string> &attrs = curves_cache->final.attr_used;
|
||||
const VectorSet<std::string> &attrs = cache.attr_used;
|
||||
for (const int i : attrs.index_range()) {
|
||||
const StringRef name = attrs[i];
|
||||
char sampler_name[32];
|
||||
drw_curves_get_attribute_sampler_name(name, sampler_name);
|
||||
|
||||
if (!curves_cache->proc_attributes_point_domain[i]) {
|
||||
if (!curves_cache->proc_attributes_buf[i]) {
|
||||
if (cache.attributes_point_domain[i]) {
|
||||
if (!cache.evaluated_attributes_buf[i]) {
|
||||
continue;
|
||||
}
|
||||
sub_ps.bind_texture(sampler_name, curves_cache->proc_attributes_buf[i]);
|
||||
if (set_attribute_type(gpu_material, name, curves_infos, true)) {
|
||||
sub_ps.bind_texture(sampler_name, cache.evaluated_attributes_buf[i]);
|
||||
}
|
||||
if (name == uv_name) {
|
||||
sub_ps.bind_texture("a", curves_cache->proc_attributes_buf[i]);
|
||||
if (set_attribute_type(gpu_material, "", curves_infos, true)) {
|
||||
sub_ps.bind_texture("a", cache.evaluated_attributes_buf[i]);
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
if (!curves_cache->final.attributes_buf[i]) {
|
||||
if (!cache.curve_attributes_buf[i]) {
|
||||
continue;
|
||||
}
|
||||
sub_ps.bind_texture(sampler_name, curves_cache->final.attributes_buf[i]);
|
||||
if (set_attribute_type(gpu_material, name, curves_infos, false)) {
|
||||
sub_ps.bind_texture(sampler_name, cache.curve_attributes_buf[i]);
|
||||
}
|
||||
if (name == uv_name) {
|
||||
sub_ps.bind_texture("a", curves_cache->final.attributes_buf[i]);
|
||||
if (set_attribute_type(gpu_material, "", curves_infos, false)) {
|
||||
sub_ps.bind_texture("a", cache.curve_attributes_buf[i]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* Some attributes may not be used in the shader anymore and were not garbage collected yet, so
|
||||
* we need to find the right index for this attribute as uniforms defining the scope of the
|
||||
* attributes are based on attribute loading order, which is itself based on the material's
|
||||
* attributes. */
|
||||
const int index = attribute_index_in_material(gpu_material, name);
|
||||
if (index != -1) {
|
||||
curves_infos.is_point_attribute[index][0] = curves_cache->proc_attributes_point_domain[i];
|
||||
}
|
||||
}
|
||||
|
||||
curves_infos.half_cylinder_face_count = face_per_segment;
|
||||
curves_infos.vertex_per_segment = face_per_segment < 2 ? (face_per_segment + 1) :
|
||||
((face_per_segment + 1) * 2 + 1);
|
||||
|
||||
curves_infos.push_update();
|
||||
|
||||
sub_ps.bind_ubo("drw_curves", curves_infos);
|
||||
sub_ps.bind_texture("curves_pos_rad_buf", cache.evaluated_pos_rad_buf);
|
||||
sub_ps.bind_texture("curves_indirection_buf", indirection_buf);
|
||||
}
|
||||
|
||||
sub_ps.push_constant("hairStrandsRes", &curves_cache->final.resolution, 1);
|
||||
sub_ps.push_constant("hairThicknessRes", thickness_res);
|
||||
sub_ps.push_constant("hairRadShape", hair_rad_shape);
|
||||
sub_ps.push_constant("hairDupliMatrix", ob->object_to_world());
|
||||
sub_ps.push_constant("hairRadRoot", hair_rad_root);
|
||||
sub_ps.push_constant("hairRadTip", hair_rad_tip);
|
||||
sub_ps.push_constant("hairCloseTip", hair_close_tip);
|
||||
void curves_bind_resources(PassMain::Sub &sub_ps,
|
||||
CurvesModule &module,
|
||||
CurvesEvalCache &cache,
|
||||
const int face_per_segment,
|
||||
GPUMaterial *gpu_material,
|
||||
gpu::VertBufPtr &indirection_buf,
|
||||
const std::optional<StringRef> active_uv_name)
|
||||
{
|
||||
curves_bind_resources_implementation(
|
||||
sub_ps, module, cache, face_per_segment, gpu_material, indirection_buf, active_uv_name);
|
||||
}
|
||||
|
||||
return curves_cache->final.proc_hairs;
|
||||
void curves_bind_resources(PassSimple::Sub &sub_ps,
|
||||
CurvesModule &module,
|
||||
CurvesEvalCache &cache,
|
||||
const int face_per_segment,
|
||||
GPUMaterial *gpu_material,
|
||||
gpu::VertBufPtr &indirection_buf,
|
||||
const std::optional<StringRef> active_uv_name)
|
||||
{
|
||||
curves_bind_resources_implementation(
|
||||
sub_ps, module, cache, face_per_segment, gpu_material, indirection_buf, active_uv_name);
|
||||
}
|
||||
|
||||
template<typename PassT>
|
||||
gpu::Batch *curves_sub_pass_setup_implementation(PassT &sub_ps,
|
||||
const Scene *scene,
|
||||
Object *ob,
|
||||
GPUMaterial *gpu_material)
|
||||
{
|
||||
BLI_assert(ob->type == OB_CURVES);
|
||||
Curves &curves_id = DRW_object_get_data_for_drawing<Curves>(*ob);
|
||||
const bke::CurvesGeometry &curves = curves_id.geometry.wrap();
|
||||
|
||||
const int face_per_segment = (scene->r.hair_type == SCE_HAIR_SHAPE_STRAND) ? 0 :
|
||||
(scene->r.hair_type == SCE_HAIR_SHAPE_CYLINDER) ? 3 :
|
||||
1;
|
||||
|
||||
CurvesEvalCache &curves_cache = curves_get_eval_cache(curves_id);
|
||||
|
||||
if (curves.curves_num() == 0) {
|
||||
/* Nothing to draw. Just return an empty drawcall that will be skipped. */
|
||||
return curves_cache.batch_get(0, 0, face_per_segment, false);
|
||||
}
|
||||
|
||||
CurvesModule &module = *drw_get().data->curves_module;
|
||||
|
||||
curves_cache.ensure_positions(module, curves);
|
||||
curves_cache.ensure_attributes(module, curves, gpu_material);
|
||||
|
||||
gpu::VertBufPtr &indirection_buf = curves_cache.indirection_buf_get(
|
||||
module, curves, face_per_segment);
|
||||
|
||||
const std::optional<StringRef> uv_name = get_first_uv_name(
|
||||
curves_id.geometry.wrap().attributes());
|
||||
|
||||
curves_bind_resources(
|
||||
sub_ps, module, curves_cache, face_per_segment, gpu_material, indirection_buf, uv_name);
|
||||
|
||||
return curves_cache.batch_get(curves.evaluated_points_num(),
|
||||
curves.curves_num(),
|
||||
face_per_segment,
|
||||
curves.has_cyclic_curve());
|
||||
}
|
||||
|
||||
gpu::Batch *curves_sub_pass_setup(PassMain::Sub &ps,
|
||||
|
||||
44
source/blender/draw/intern/draw_curves_defines.hh
Normal file
44
source/blender/draw/intern/draw_curves_defines.hh
Normal file
@@ -0,0 +1,44 @@
|
||||
/* SPDX-FileCopyrightText: 2025 Blender Authors
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
/** \file
|
||||
* \ingroup draw
|
||||
*
|
||||
* List of defines that are shared with the GPUShaderCreateInfos. We do this to avoid
|
||||
* dragging larger headers into the createInfo pipeline which would cause problems.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
/** Curves evaluation. */
|
||||
#define CURVES_PER_THREADGROUP 64
|
||||
|
||||
#define POINTS_BY_CURVES_SLOT 0
|
||||
#define CURVE_TYPE_SLOT 1
|
||||
#define CURVE_RESOLUTION_SLOT 2
|
||||
#define EVALUATED_POINT_SLOT 3
|
||||
#define CURVE_CYCLIC_SLOT 4
|
||||
|
||||
#define HANDLES_POS_LEFT_SLOT 5
|
||||
#define HANDLES_POS_RIGHT_SLOT 6
|
||||
#define BEZIER_OFFSETS_SLOT 7
|
||||
|
||||
/* Nurbs (alias of other buffers). */
|
||||
#define CURVES_ORDER_SLOT CURVE_RESOLUTION_SLOT
|
||||
#define BASIS_CACHE_SLOT HANDLES_POS_LEFT_SLOT
|
||||
#define CONTROL_WEIGHTS_SLOT HANDLES_POS_RIGHT_SLOT
|
||||
#define BASIS_CACHE_OFFSET_SLOT BEZIER_OFFSETS_SLOT
|
||||
|
||||
/* Position evaluation. */
|
||||
#define POINT_POSITIONS_SLOT 8
|
||||
#define POINT_RADII_SLOT 9
|
||||
#define EVALUATED_POS_RAD_SLOT 10
|
||||
|
||||
/* Attribute evaluation. */
|
||||
#define POINT_ATTR_SLOT 8
|
||||
#define EVALUATED_ATTR_SLOT 9
|
||||
|
||||
/* Intercept evaluation. */
|
||||
#define EVALUATED_TIME_SLOT 8
|
||||
#define CURVES_LENGTH_SLOT 9
|
||||
@@ -12,10 +12,22 @@
|
||||
#include <string>
|
||||
|
||||
#include "GPU_shader.hh"
|
||||
#include "GPU_vertex_buffer.hh"
|
||||
|
||||
#include "BLI_vector_set.hh"
|
||||
|
||||
#include "draw_pass.hh"
|
||||
|
||||
struct Curves;
|
||||
struct Object;
|
||||
struct ParticleSystem;
|
||||
struct PTCacheEdit;
|
||||
struct ModifierData;
|
||||
struct ParticleCacheKey;
|
||||
namespace blender::bke {
|
||||
class CurvesGeometry;
|
||||
} // namespace blender::bke
|
||||
|
||||
namespace blender::gpu {
|
||||
class Batch;
|
||||
class VertBuf;
|
||||
@@ -24,84 +36,239 @@ struct GPUMaterial;
|
||||
|
||||
namespace blender::draw {
|
||||
|
||||
#define MAX_THICKRES 2 /* see eHairType */
|
||||
struct CurvesModule;
|
||||
|
||||
#define MAX_FACE_PER_SEGMENT 5
|
||||
#define MAX_HAIR_SUBDIV 4 /* see hair_subdiv rna */
|
||||
|
||||
enum CurvesEvalShader {
|
||||
CURVES_EVAL_CATMULL_ROM = 0,
|
||||
CURVES_EVAL_BEZIER = 1,
|
||||
CURVES_EVAL_POSITION = 0,
|
||||
CURVES_EVAL_FLOAT = 1,
|
||||
CURVES_EVAL_FLOAT2 = 2,
|
||||
CURVES_EVAL_FLOAT3 = 3,
|
||||
CURVES_EVAL_FLOAT4 = 4,
|
||||
CURVES_EVAL_LENGTH_INTERCEPT = 5,
|
||||
};
|
||||
#define CURVES_EVAL_SHADER_NUM 3
|
||||
|
||||
struct CurvesEvalFinalCache {
|
||||
/** The "additional subdivision" setting from the scene. See #MAX_HAIR_SUBDIV. */
|
||||
int hair_subdiv;
|
||||
/* The "strand or strip" setting from the scene. See #MAX_THICKRES. */
|
||||
int thickres;
|
||||
/* Legacy Hair Particle. */
|
||||
|
||||
/* Output of the subdivision stage: vertex buffer sized to subdiv level. */
|
||||
gpu::VertBuf *proc_buf;
|
||||
struct ParticleSpans {
|
||||
Span<ParticleCacheKey *> parent;
|
||||
Span<ParticleCacheKey *> children;
|
||||
|
||||
/** Just contains a huge index buffer used to draw the final curves. */
|
||||
gpu::Batch *proc_hairs;
|
||||
void foreach_strand(FunctionRef<void(Span<ParticleCacheKey>)> callback);
|
||||
};
|
||||
|
||||
/** Points per curve, at least 2. */
|
||||
int resolution;
|
||||
struct ParticleDrawSource {
|
||||
public:
|
||||
Object *object = nullptr;
|
||||
ParticleSystem *psys = nullptr;
|
||||
ModifierData *md = nullptr;
|
||||
PTCacheEdit *edit = nullptr;
|
||||
|
||||
private:
|
||||
Vector<int> &points_by_curve_storage_;
|
||||
Vector<int> &evaluated_points_by_curve_storage_;
|
||||
int additional_subdivision_;
|
||||
|
||||
public:
|
||||
ParticleDrawSource(Vector<int> &points_by_curve_storage,
|
||||
Vector<int> &evaluated_points_by_curve_storage,
|
||||
int additional_subdivision)
|
||||
: points_by_curve_storage_(points_by_curve_storage),
|
||||
evaluated_points_by_curve_storage_(evaluated_points_by_curve_storage),
|
||||
additional_subdivision_(additional_subdivision)
|
||||
{
|
||||
}
|
||||
|
||||
int curves_num()
|
||||
{
|
||||
if (points_by_curve_storage_.is_empty()) {
|
||||
points_by_curve();
|
||||
}
|
||||
return points_by_curve_storage_.size() - 1;
|
||||
}
|
||||
|
||||
int points_num()
|
||||
{
|
||||
if (points_by_curve_storage_.is_empty()) {
|
||||
points_by_curve();
|
||||
}
|
||||
return points_by_curve_storage_.last();
|
||||
}
|
||||
|
||||
int evaluated_points_num()
|
||||
{
|
||||
if (additional_subdivision_ == 0) {
|
||||
return points_num();
|
||||
}
|
||||
evaluated_points_by_curve();
|
||||
return evaluated_points_by_curve_storage_.last();
|
||||
}
|
||||
|
||||
int resolution()
|
||||
{
|
||||
return 1 << additional_subdivision_;
|
||||
}
|
||||
|
||||
OffsetIndices<int> points_by_curve();
|
||||
OffsetIndices<int> evaluated_points_by_curve();
|
||||
ParticleSpans particles_get();
|
||||
};
|
||||
|
||||
#define CURVES_EVAL_SHADER_NUM 5
|
||||
|
||||
/* Curves procedural display: Evaluation is done on the GPU. */
|
||||
struct CurvesEvalCache {
|
||||
/* --- Required attributes. --- */
|
||||
|
||||
/** Position and radius per evaluated point. Always evaluated. */
|
||||
gpu::VertBufPtr evaluated_pos_rad_buf;
|
||||
|
||||
/** Intercept time per evaluated point. */
|
||||
/* TODO(fclem): Move it to generic point domain attributes. */
|
||||
gpu::VertBufPtr evaluated_time_buf;
|
||||
/** Intercept time per curve. */
|
||||
/* TODO(fclem): Move it to generic curve domain attributes. */
|
||||
gpu::VertBufPtr curves_length_buf;
|
||||
|
||||
/* --- Indirection buffers. --- */
|
||||
|
||||
/* Map primitive to point ID and curve ID. Contains restart indices for line and triangle strip
|
||||
* primitive. */
|
||||
gpu::VertBufPtr indirection_ribbon_buf;
|
||||
/* Map primitive to point ID and curve ID. Compacted for cylinder primitive. */
|
||||
gpu::VertBufPtr indirection_cylinder_buf;
|
||||
|
||||
/* --- Buffers common to all curve types. --- */
|
||||
|
||||
/** Buffer containing `CurveGeometry::points_by_curve()`. */
|
||||
gpu::VertBufPtr points_by_curve_buf;
|
||||
/** Buffer containing `CurveGeometry::evaluated_points_by_curve()`. */
|
||||
gpu::VertBufPtr evaluated_points_by_curve_buf;
|
||||
/** Buffer containing `CurveGeometry::curve_types()`. */
|
||||
gpu::VertBufPtr curves_type_buf;
|
||||
/** Buffer containing `CurveGeometry::resolution()`. */
|
||||
gpu::VertBufPtr curves_resolution_buf;
|
||||
/** Buffer containing `CurveGeometry::cyclic_offsets()` or dummy data if not needed. */
|
||||
gpu::VertBufPtr curves_cyclic_buf;
|
||||
|
||||
/* --- Buffers only needed if geometry has Bezier curves. Dummy sized otherwise. --- */
|
||||
|
||||
/** Buffer containing `CurveGeometry::handle_positions_left()`. */
|
||||
gpu::VertBufPtr handles_positions_left_buf;
|
||||
/** Buffer containing `CurveGeometry::handle_positions_right()`. */
|
||||
gpu::VertBufPtr handles_positions_right_buf;
|
||||
/** Buffer containing `EvaluatedOffsets::all_bezier_offsets`. */
|
||||
gpu::VertBufPtr bezier_offsets_buf;
|
||||
|
||||
/* --- Buffers only needed if geometry has Nurbs curves. Dummy sized otherwise. --- */
|
||||
|
||||
/** Buffer containing `CurveGeometry::nurbs_orders()`. */
|
||||
gpu::VertBufPtr curves_order_buf;
|
||||
/** Buffer containing `CurveGeometry::nurbs_weights()`. */
|
||||
gpu::VertBufPtr control_weights_buf;
|
||||
/** Buffer containing all `nurbs::BasisCache` concatenated. */
|
||||
gpu::VertBufPtr basis_cache_buf;
|
||||
/** Buffer containing offsets to the start of each `nurbs::BasisCache` for each curve. */
|
||||
gpu::VertBufPtr basis_cache_offset_buf;
|
||||
|
||||
/* --- Generic Attributes. --- */
|
||||
|
||||
/** Attributes currently being drawn or about to be drawn. */
|
||||
VectorSet<std::string> attr_used;
|
||||
|
||||
/**
|
||||
* Attributes that were used at some point. This is used for garbage collection, to remove
|
||||
* attributes that are not used in shaders anymore due to user edits.
|
||||
*/
|
||||
VectorSet<std::string> attr_used_over_time;
|
||||
|
||||
/**
|
||||
* The last time in seconds that the `attr_used` and `attr_used_over_time` were exactly the same.
|
||||
* If the delta between this time and the current scene time is greater than the timeout set in
|
||||
* user preferences (`U.vbotimeout`) then garbage collection is performed.
|
||||
*/
|
||||
int last_attr_matching_time;
|
||||
/* Attributes stored per curve. Nullptr if attribute is not from this domain. */
|
||||
gpu::VertBufPtr curve_attributes_buf[GPU_MAX_ATTR];
|
||||
/* Output of the evaluation stage. This is only used by attributes on point domain. */
|
||||
gpu::VertBufPtr evaluated_attributes_buf[GPU_MAX_ATTR];
|
||||
/* If attribute is point domain, use evaluated_attributes_buf. Otherwise curve_attributes_buf. */
|
||||
std::array<bool, GPU_MAX_ATTR> attributes_point_domain;
|
||||
|
||||
/* Output of the subdivision stage: vertex buffers sized to subdiv level. This is only attributes
|
||||
* on point domain. */
|
||||
gpu::VertBuf *attributes_buf[GPU_MAX_ATTR];
|
||||
/* --- Procedural Drawcalls. --- */
|
||||
std::array<gpu::Batch *, MAX_FACE_PER_SEGMENT> batch;
|
||||
|
||||
void ensure_attribute(struct CurvesModule &module,
|
||||
const bke::CurvesGeometry &curves,
|
||||
StringRef name,
|
||||
int index);
|
||||
void ensure_attributes(struct CurvesModule &module,
|
||||
const bke::CurvesGeometry &curves,
|
||||
const GPUMaterial *gpu_material);
|
||||
|
||||
void ensure_common(const bke::CurvesGeometry &curves);
|
||||
void ensure_bezier(const bke::CurvesGeometry &curves);
|
||||
void ensure_nurbs(const bke::CurvesGeometry &curves);
|
||||
|
||||
void ensure_positions(CurvesModule &module, const bke::CurvesGeometry &curves);
|
||||
|
||||
gpu::VertBufPtr &indirection_buf_get(CurvesModule &module,
|
||||
const bke::CurvesGeometry &curves,
|
||||
int face_per_segment);
|
||||
|
||||
gpu::Batch *batch_get(int evaluated_point_count,
|
||||
int curve_count,
|
||||
int face_per_segment,
|
||||
bool use_cyclic);
|
||||
|
||||
void discard_attributes();
|
||||
void clear();
|
||||
|
||||
/* --- Legacy Hair Particle system. --- */
|
||||
|
||||
int resolution = 0;
|
||||
|
||||
void ensure_attribute(CurvesModule &module,
|
||||
ParticleDrawSource &src,
|
||||
const Mesh &mesh,
|
||||
const StringRef name,
|
||||
const int index);
|
||||
void ensure_attributes(CurvesModule &module,
|
||||
ParticleDrawSource &src,
|
||||
const GPUMaterial *gpu_material);
|
||||
|
||||
void ensure_common(ParticleDrawSource &src);
|
||||
|
||||
void ensure_positions(CurvesModule &module, ParticleDrawSource &src);
|
||||
|
||||
gpu::VertBufPtr &indirection_buf_get(CurvesModule &module,
|
||||
ParticleDrawSource &src,
|
||||
int face_per_segment);
|
||||
|
||||
private:
|
||||
/* In the case where there is cyclic curves, add one padding point per curve to ensure easy
|
||||
* indexing in the drawing shader. */
|
||||
int evaluated_point_count_with_cyclic(const bke::CurvesGeometry &curves);
|
||||
};
|
||||
|
||||
/* Curves procedural display: Evaluation is done on the GPU. */
|
||||
struct CurvesEvalCache {
|
||||
/* Control point positions on evaluated data-block combined with parameter data. */
|
||||
gpu::VertBuf *proc_point_buf;
|
||||
|
||||
/** Info of control points strands (segment count and base index) */
|
||||
gpu::VertBuf *proc_strand_buf;
|
||||
|
||||
/* Curve length data. */
|
||||
gpu::VertBuf *proc_length_buf;
|
||||
|
||||
gpu::VertBuf *proc_strand_seg_buf;
|
||||
|
||||
CurvesEvalFinalCache final;
|
||||
|
||||
/* For point attributes, which need subdivision, these buffers contain the input data.
|
||||
* For curve domain attributes, which do not need subdivision, these are the final data. */
|
||||
gpu::VertBuf *proc_attributes_buf[GPU_MAX_ATTR];
|
||||
std::array<bool, GPU_MAX_ATTR> proc_attributes_point_domain;
|
||||
|
||||
int curves_num;
|
||||
int points_num;
|
||||
};
|
||||
|
||||
/**
|
||||
* Ensure all necessary textures and buffers exist for GPU accelerated drawing.
|
||||
*/
|
||||
bool curves_ensure_procedural_data(Curves *curves_id,
|
||||
CurvesEvalCache **r_cache,
|
||||
const GPUMaterial *gpu_material,
|
||||
int subdiv,
|
||||
int thickness_res);
|
||||
CurvesEvalCache &curves_get_eval_cache(Curves &curves_id);
|
||||
|
||||
void drw_curves_get_attribute_sampler_name(StringRef layer_name, char r_sampler_name[32]);
|
||||
|
||||
void curves_bind_resources(draw::PassMain::Sub &sub_ps,
|
||||
CurvesModule &module,
|
||||
CurvesEvalCache &cache,
|
||||
const int face_per_segment,
|
||||
GPUMaterial *gpu_material,
|
||||
gpu::VertBufPtr &indirection_buf,
|
||||
std::optional<StringRef> active_uv_name);
|
||||
|
||||
void curves_bind_resources(draw::PassSimple::Sub &sub_ps,
|
||||
CurvesModule &module,
|
||||
CurvesEvalCache &cache,
|
||||
const int face_per_segment,
|
||||
GPUMaterial *gpu_material,
|
||||
gpu::VertBufPtr &indirection_buf,
|
||||
std::optional<StringRef> active_uv_name);
|
||||
|
||||
} // namespace blender::draw
|
||||
|
||||
@@ -8,158 +8,47 @@
|
||||
* \brief Contains procedural GPU hair drawing methods.
|
||||
*/
|
||||
|
||||
#include "DNA_scene_types.h"
|
||||
#include "DRW_render.hh"
|
||||
#include "BKE_customdata.hh"
|
||||
|
||||
#include "BLI_math_matrix.h"
|
||||
#include "BLI_math_vector.h"
|
||||
|
||||
#include "DNA_collection_types.h"
|
||||
#include "DNA_modifier_types.h"
|
||||
#include "DNA_particle_types.h"
|
||||
#include "DNA_scene_types.h"
|
||||
|
||||
#include "BKE_duplilist.hh"
|
||||
#include "DRW_render.hh"
|
||||
|
||||
#include "GPU_batch.hh"
|
||||
#include "GPU_capabilities.hh"
|
||||
#include "GPU_material.hh"
|
||||
#include "GPU_shader.hh"
|
||||
#include "GPU_texture.hh"
|
||||
#include "GPU_vertex_buffer.hh"
|
||||
|
||||
#include "DRW_gpu_wrapper.hh"
|
||||
|
||||
#include "draw_common_c.hh"
|
||||
#include "draw_context_private.hh"
|
||||
#include "draw_hair_private.hh"
|
||||
#include "draw_manager.hh"
|
||||
#include "draw_shader.hh"
|
||||
#include "draw_shader_shared.hh"
|
||||
|
||||
static void drw_hair_particle_cache_update_compute(ParticleHairCache *cache, const int subdiv)
|
||||
{
|
||||
const int strands_len = cache->strands_len;
|
||||
const int final_points_len = cache->final[subdiv].strands_res * strands_len;
|
||||
if (final_points_len > 0) {
|
||||
using namespace blender::draw;
|
||||
blender::gpu::Shader *shader = DRW_shader_hair_refine_get(PART_REFINE_CATMULL_ROM);
|
||||
|
||||
/* TODO(fclem): Remove Global access. */
|
||||
PassSimple &pass = drw_get().data->curves_module->refine;
|
||||
pass.shader_set(shader);
|
||||
pass.bind_texture("hairPointBuffer", cache->proc_point_buf);
|
||||
pass.bind_texture("hairStrandBuffer", cache->proc_strand_buf);
|
||||
pass.bind_texture("hairStrandSegBuffer", cache->proc_strand_seg_buf);
|
||||
pass.push_constant("hairStrandsRes", &cache->final[subdiv].strands_res);
|
||||
pass.bind_ssbo("posTime", cache->final[subdiv].proc_buf);
|
||||
|
||||
const int max_strands_per_call = GPU_max_work_group_count(0);
|
||||
int strands_start = 0;
|
||||
while (strands_start < strands_len) {
|
||||
int batch_strands_len = std::min(strands_len - strands_start, max_strands_per_call);
|
||||
pass.push_constant("hairStrandOffset", strands_start);
|
||||
pass.dispatch(int3(batch_strands_len, cache->final[subdiv].strands_res, 1));
|
||||
strands_start += batch_strands_len;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static ParticleHairCache *drw_hair_particle_cache_get(Object *object,
|
||||
ParticleSystem *psys,
|
||||
ModifierData *md,
|
||||
GPUMaterial *gpu_material,
|
||||
int subdiv,
|
||||
int thickness_res)
|
||||
{
|
||||
using namespace blender::draw;
|
||||
ParticleHairCache *cache;
|
||||
bool update = particles_ensure_procedural_data(
|
||||
object, psys, md, &cache, gpu_material, subdiv, thickness_res);
|
||||
|
||||
if (update) {
|
||||
drw_hair_particle_cache_update_compute(cache, subdiv);
|
||||
}
|
||||
return cache;
|
||||
}
|
||||
|
||||
blender::gpu::VertBuf *DRW_hair_pos_buffer_get(Object *object,
|
||||
ParticleSystem *psys,
|
||||
ModifierData *md)
|
||||
{
|
||||
const DRWContext *draw_ctx = DRW_context_get();
|
||||
Scene *scene = draw_ctx->scene;
|
||||
|
||||
int subdiv = scene->r.hair_subdiv;
|
||||
int thickness_res = (scene->r.hair_type == SCE_HAIR_SHAPE_STRAND) ? 1 : 2;
|
||||
|
||||
ParticleHairCache *cache = drw_hair_particle_cache_get(
|
||||
object, psys, md, nullptr, subdiv, thickness_res);
|
||||
|
||||
return cache->final[subdiv].proc_buf;
|
||||
}
|
||||
|
||||
/* New Draw Manager. */
|
||||
#include "draw_common.hh"
|
||||
|
||||
namespace blender::draw {
|
||||
|
||||
static ParticleHairCache *hair_particle_cache_get(Object *object,
|
||||
ParticleSystem *psys,
|
||||
ModifierData *md,
|
||||
GPUMaterial *gpu_material,
|
||||
int subdiv,
|
||||
int thickness_res)
|
||||
{
|
||||
using namespace blender::draw;
|
||||
ParticleHairCache *cache;
|
||||
bool update = particles_ensure_procedural_data(
|
||||
object, psys, md, &cache, gpu_material, subdiv, thickness_res);
|
||||
|
||||
if (!update) {
|
||||
return cache;
|
||||
}
|
||||
|
||||
CurvesModule &module = *drw_get().data->curves_module;
|
||||
|
||||
const int strands_len = cache->strands_len;
|
||||
const int final_points_len = cache->final[subdiv].strands_res * strands_len;
|
||||
if (final_points_len > 0) {
|
||||
PassSimple::Sub &ob_ps = module.refine.sub("Object Pass");
|
||||
|
||||
ob_ps.shader_set(DRW_shader_hair_refine_get(PART_REFINE_CATMULL_ROM));
|
||||
|
||||
ob_ps.bind_texture("hairPointBuffer", cache->proc_point_buf);
|
||||
ob_ps.bind_texture("hairStrandBuffer", cache->proc_strand_buf);
|
||||
ob_ps.bind_texture("hairStrandSegBuffer", cache->proc_strand_seg_buf);
|
||||
ob_ps.push_constant("hairStrandsRes", &cache->final[subdiv].strands_res);
|
||||
ob_ps.bind_ssbo("posTime", cache->final[subdiv].proc_buf);
|
||||
|
||||
const int max_strands_per_call = GPU_max_work_group_count(0);
|
||||
int strands_start = 0;
|
||||
while (strands_start < strands_len) {
|
||||
int batch_strands_len = std::min(strands_len - strands_start, max_strands_per_call);
|
||||
PassSimple::Sub &sub_ps = ob_ps.sub("Sub Pass");
|
||||
sub_ps.push_constant("hairStrandOffset", strands_start);
|
||||
sub_ps.dispatch(int3(batch_strands_len, cache->final[subdiv].strands_res, 1));
|
||||
strands_start += batch_strands_len;
|
||||
}
|
||||
}
|
||||
|
||||
return cache;
|
||||
}
|
||||
|
||||
blender::gpu::VertBuf *hair_pos_buffer_get(Scene *scene,
|
||||
Object *object,
|
||||
ParticleSystem *psys,
|
||||
ModifierData *md)
|
||||
{
|
||||
int subdiv = scene->r.hair_subdiv;
|
||||
int thickness_res = (scene->r.hair_type == SCE_HAIR_SHAPE_STRAND) ? 1 : 2;
|
||||
/* TODO(fclem): Remove Global access. */
|
||||
CurvesModule &module = *drw_get().data->curves_module;
|
||||
|
||||
ParticleHairCache *cache = hair_particle_cache_get(
|
||||
object, psys, md, nullptr, subdiv, thickness_res);
|
||||
drw_particle_update_ptcache(object, psys);
|
||||
ParticleDrawSource source = drw_particle_get_hair_source(
|
||||
object, psys, md, nullptr, scene->r.hair_subdiv);
|
||||
|
||||
return cache->final[subdiv].proc_buf;
|
||||
CurvesEvalCache &cache = hair_particle_get_eval_cache(source);
|
||||
cache.ensure_positions(module, source);
|
||||
|
||||
return cache.evaluated_pos_rad_buf.get();
|
||||
}
|
||||
|
||||
template<typename PassT>
|
||||
@@ -173,66 +62,41 @@ blender::gpu::Batch *hair_sub_pass_setup_implementation(PassT &sub_ps,
|
||||
/** NOTE: This still relies on the old DRW_hair implementation. */
|
||||
Object *object = ob_ref.object;
|
||||
|
||||
int subdiv = scene->r.hair_subdiv;
|
||||
int thickness_res = (scene->r.hair_type == SCE_HAIR_SHAPE_STRAND) ? 1 : 2;
|
||||
ParticleHairCache *hair_cache = drw_hair_particle_cache_get(
|
||||
object, psys, md, gpu_material, subdiv, thickness_res);
|
||||
drw_particle_update_ptcache(object, psys);
|
||||
|
||||
ParticleDrawSource source = drw_particle_get_hair_source(
|
||||
object, psys, md, nullptr, scene->r.hair_subdiv);
|
||||
|
||||
CurvesEvalCache &cache = hair_particle_get_eval_cache(source);
|
||||
|
||||
const int face_per_segment = (scene->r.hair_type == SCE_HAIR_SHAPE_STRAND) ? 0 :
|
||||
(scene->r.hair_type == SCE_HAIR_SHAPE_CYLINDER) ? 3 :
|
||||
1;
|
||||
|
||||
if (source.evaluated_points_num() == 0) {
|
||||
/* Nothing to draw. Just return an empty drawcall that will be skipped. */
|
||||
return cache.batch_get(0, 0, face_per_segment, false);
|
||||
}
|
||||
|
||||
/* TODO(fclem): Remove Global access. */
|
||||
CurvesModule &module = *drw_get().data->curves_module;
|
||||
|
||||
/* Ensure we have no unbound resources.
|
||||
* Required for Vulkan.
|
||||
* Fixes issues with certain GL drivers not drawing anything. */
|
||||
sub_ps.bind_texture("u", module.dummy_vbo);
|
||||
sub_ps.bind_texture("au", module.dummy_vbo);
|
||||
sub_ps.bind_texture("a", module.dummy_vbo);
|
||||
sub_ps.bind_texture("c", module.dummy_vbo);
|
||||
sub_ps.bind_texture("ac", module.dummy_vbo);
|
||||
if (gpu_material) {
|
||||
ListBase attr_list = GPU_material_attributes(gpu_material);
|
||||
ListBaseWrapper<GPUMaterialAttribute> attrs(attr_list);
|
||||
for (const GPUMaterialAttribute *attr : attrs) {
|
||||
sub_ps.bind_texture(attr->input_name, module.dummy_vbo);
|
||||
}
|
||||
cache.ensure_positions(module, source);
|
||||
cache.ensure_attributes(module, source, gpu_material);
|
||||
|
||||
gpu::VertBufPtr &indirection_buf = cache.indirection_buf_get(module, source, face_per_segment);
|
||||
|
||||
{
|
||||
ParticleSystemModifierData *psmd = (ParticleSystemModifierData *)source.md;
|
||||
Mesh &mesh = *psmd->mesh_final;
|
||||
const StringRef active_uv = CustomData_get_active_layer_name(&mesh.corner_data,
|
||||
CD_PROP_FLOAT2);
|
||||
curves_bind_resources(
|
||||
sub_ps, module, cache, face_per_segment, gpu_material, indirection_buf, active_uv);
|
||||
}
|
||||
|
||||
/* TODO: optimize this. Only bind the ones #GPUMaterial needs. */
|
||||
for (int i : IndexRange(hair_cache->num_uv_layers)) {
|
||||
for (int n = 0; n < MAX_LAYER_NAME_CT && hair_cache->uv_layer_names[i][n][0] != '\0'; n++) {
|
||||
sub_ps.bind_texture(hair_cache->uv_layer_names[i][n], hair_cache->uv_tex[i]);
|
||||
}
|
||||
}
|
||||
for (int i : IndexRange(hair_cache->num_col_layers)) {
|
||||
for (int n = 0; n < MAX_LAYER_NAME_CT && hair_cache->col_layer_names[i][n][0] != '\0'; n++) {
|
||||
sub_ps.bind_texture(hair_cache->col_layer_names[i][n], hair_cache->col_tex[i]);
|
||||
}
|
||||
}
|
||||
|
||||
float4x4 dupli_mat = ob_ref.particles_matrix();
|
||||
|
||||
/* Get hair shape parameters. */
|
||||
ParticleSettings *part = psys->part;
|
||||
float hair_rad_shape = part->shape;
|
||||
float hair_rad_root = part->rad_root * part->rad_scale * 0.5f;
|
||||
float hair_rad_tip = part->rad_tip * part->rad_scale * 0.5f;
|
||||
bool hair_close_tip = (part->shape_flag & PART_SHAPE_CLOSE_TIP) != 0;
|
||||
|
||||
sub_ps.bind_texture("hairPointBuffer", hair_cache->final[subdiv].proc_buf);
|
||||
if (hair_cache->proc_length_buf) {
|
||||
sub_ps.bind_texture("l", hair_cache->proc_length_buf);
|
||||
}
|
||||
|
||||
sub_ps.bind_ubo("drw_curves", module.ubo_pool.dummy_get());
|
||||
sub_ps.push_constant("hairStrandsRes", &hair_cache->final[subdiv].strands_res, 1);
|
||||
sub_ps.push_constant("hairThicknessRes", thickness_res);
|
||||
sub_ps.push_constant("hairRadShape", hair_rad_shape);
|
||||
sub_ps.push_constant("hairDupliMatrix", dupli_mat);
|
||||
sub_ps.push_constant("hairRadRoot", hair_rad_root);
|
||||
sub_ps.push_constant("hairRadTip", hair_rad_tip);
|
||||
sub_ps.push_constant("hairCloseTip", hair_close_tip);
|
||||
|
||||
return hair_cache->final[subdiv].proc_hairs[thickness_res - 1];
|
||||
return cache.batch_get(
|
||||
source.evaluated_points_num(), source.curves_num(), face_per_segment, false);
|
||||
}
|
||||
|
||||
blender::gpu::Batch *hair_sub_pass_setup(PassMain::Sub &sub_ps,
|
||||
|
||||
@@ -8,10 +8,17 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "draw_curves_private.hh"
|
||||
#include "draw_pass.hh"
|
||||
|
||||
namespace blender::bke {
|
||||
class CurvesGeometry;
|
||||
}
|
||||
|
||||
namespace blender::draw {
|
||||
|
||||
struct CurvesEvalCache;
|
||||
|
||||
class CurveRefinePass : public PassSimple {
|
||||
public:
|
||||
CurveRefinePass(const char *name) : PassSimple(name){};
|
||||
@@ -42,6 +49,9 @@ struct CurvesUniformBufPool {
|
||||
struct CurvesModule {
|
||||
CurvesUniformBufPool ubo_pool;
|
||||
CurveRefinePass refine = {"CurvesEvalPass"};
|
||||
/* Contains all transient input buffers contained inside `refine`.
|
||||
* Cleared after update. */
|
||||
Vector<gpu::VertBufPtr> transient_buffers;
|
||||
|
||||
gpu::VertBuf *dummy_vbo = drw_curves_ensure_dummy_vbo();
|
||||
|
||||
@@ -58,8 +68,62 @@ struct CurvesModule {
|
||||
refine.state_set(DRW_STATE_NO_DRAW);
|
||||
}
|
||||
|
||||
/* Record evaluation inside `refine`.
|
||||
* Output will be ready once `refine` pass has been submitted. */
|
||||
void evaluate_curve_attribute(bool has_catmull,
|
||||
bool has_bezier,
|
||||
bool has_poly,
|
||||
bool has_nurbs,
|
||||
bool has_cyclic,
|
||||
int curve_count,
|
||||
CurvesEvalCache &cache,
|
||||
CurvesEvalShader shader_type,
|
||||
gpu::VertBufPtr input_buf,
|
||||
gpu::VertBufPtr &output_buf,
|
||||
/* For radius during position evaluation. */
|
||||
gpu::VertBuf *input2_buf = nullptr,
|
||||
/* For baking a transform during position evaluation. */
|
||||
float4x4 transform = float4x4::identity());
|
||||
|
||||
void evaluate_positions(bool has_catmull,
|
||||
bool has_bezier,
|
||||
bool has_poly,
|
||||
bool has_nurbs,
|
||||
bool has_cyclic,
|
||||
int curve_count,
|
||||
CurvesEvalCache &cache,
|
||||
gpu::VertBufPtr input_pos_buf,
|
||||
gpu::VertBufPtr input_rad_buf,
|
||||
gpu::VertBufPtr &output_pos_buf,
|
||||
float4x4 transform = float4x4::identity())
|
||||
{
|
||||
evaluate_curve_attribute(has_catmull,
|
||||
has_bezier,
|
||||
has_poly,
|
||||
has_nurbs,
|
||||
has_cyclic,
|
||||
curve_count,
|
||||
cache,
|
||||
CURVES_EVAL_POSITION,
|
||||
std::move(input_pos_buf),
|
||||
output_pos_buf,
|
||||
/* Transfer ownership through optional argument. */
|
||||
input_rad_buf.release(),
|
||||
transform);
|
||||
}
|
||||
|
||||
void evaluate_curve_length_intercept(bool has_cyclic, int curve_count, CurvesEvalCache &cache);
|
||||
|
||||
gpu::VertBufPtr evaluate_topology_indirection(const int curve_count,
|
||||
const int point_count,
|
||||
CurvesEvalCache &cache,
|
||||
bool is_ribbon,
|
||||
bool has_cyclic);
|
||||
|
||||
private:
|
||||
gpu::VertBuf *drw_curves_ensure_dummy_vbo();
|
||||
|
||||
void dispatch(int curve_count, PassSimple::Sub &pass);
|
||||
};
|
||||
|
||||
} // namespace blender::draw
|
||||
@@ -79,61 +143,15 @@ struct Object;
|
||||
struct ParticleHairCache;
|
||||
struct ParticleSystem;
|
||||
|
||||
struct ParticleHairFinalCache {
|
||||
/* Output of the subdivision stage: vertex buff sized to subdiv level. */
|
||||
blender::gpu::VertBuf *proc_buf;
|
||||
|
||||
/* Just contains a huge index buffer used to draw the final hair. */
|
||||
blender::gpu::Batch *proc_hairs[MAX_THICKRES];
|
||||
|
||||
int strands_res; /* points per hair, at least 2 */
|
||||
};
|
||||
|
||||
struct ParticleHairCache {
|
||||
blender::gpu::VertBuf *pos;
|
||||
blender::gpu::IndexBuf *indices;
|
||||
blender::gpu::Batch *hairs;
|
||||
|
||||
/* Hair Procedural display: Interpolation is done on the GPU. */
|
||||
blender::gpu::VertBuf *proc_point_buf; /* Input control points */
|
||||
|
||||
/** Information of control points strands (segment count and base index) */
|
||||
blender::gpu::VertBuf *proc_strand_buf;
|
||||
|
||||
/* Hair Length */
|
||||
blender::gpu::VertBuf *proc_length_buf;
|
||||
|
||||
blender::gpu::VertBuf *proc_strand_seg_buf;
|
||||
|
||||
blender::gpu::VertBuf *proc_uv_buf[MAX_MTFACE];
|
||||
blender::gpu::Texture *uv_tex[MAX_MTFACE];
|
||||
char uv_layer_names[MAX_MTFACE][MAX_LAYER_NAME_CT][MAX_LAYER_NAME_LEN];
|
||||
|
||||
blender::gpu::VertBuf **proc_col_buf;
|
||||
blender::gpu::Texture **col_tex;
|
||||
char (*col_layer_names)[MAX_LAYER_NAME_CT][MAX_LAYER_NAME_LEN];
|
||||
|
||||
int num_uv_layers;
|
||||
int num_col_layers;
|
||||
|
||||
ParticleHairFinalCache final[MAX_HAIR_SUBDIV];
|
||||
|
||||
int strands_len;
|
||||
int elems_len;
|
||||
int point_len;
|
||||
};
|
||||
|
||||
namespace blender::draw {
|
||||
|
||||
/**
|
||||
* Ensure all textures and buffers needed for GPU accelerated drawing.
|
||||
*/
|
||||
bool particles_ensure_procedural_data(Object *object,
|
||||
ParticleSystem *psys,
|
||||
ModifierData *md,
|
||||
ParticleHairCache **r_hair_cache,
|
||||
GPUMaterial *gpu_material,
|
||||
int subdiv,
|
||||
int thickness_res);
|
||||
void drw_particle_update_ptcache(Object *object_eval, ParticleSystem *psys);
|
||||
ParticleDrawSource drw_particle_get_hair_source(Object *object,
|
||||
ParticleSystem *psys,
|
||||
ModifierData *md,
|
||||
PTCacheEdit *edit,
|
||||
int additional_subdivision);
|
||||
|
||||
CurvesEvalCache &hair_particle_get_eval_cache(ParticleDrawSource &src);
|
||||
|
||||
} // namespace blender::draw
|
||||
|
||||
@@ -356,10 +356,12 @@ class PassBase {
|
||||
GPUSamplerState state = sampler_auto);
|
||||
void bind_texture(const char *name, gpu::VertBuf *buffer);
|
||||
void bind_texture(const char *name, gpu::VertBuf **buffer);
|
||||
void bind_texture(const char *name, gpu::VertBufPtr &buffer);
|
||||
void bind_texture(int slot, gpu::Texture *texture, GPUSamplerState state = sampler_auto);
|
||||
void bind_texture(int slot, gpu::Texture **texture, GPUSamplerState state = sampler_auto);
|
||||
void bind_texture(int slot, gpu::VertBuf *buffer);
|
||||
void bind_texture(int slot, gpu::VertBuf **buffer);
|
||||
void bind_texture(int slot, gpu::VertBufPtr &buffer);
|
||||
void bind_ssbo(const char *name, gpu::StorageBuf *buffer);
|
||||
void bind_ssbo(const char *name, gpu::StorageBuf **buffer);
|
||||
void bind_ssbo(int slot, gpu::StorageBuf *buffer);
|
||||
@@ -370,8 +372,10 @@ class PassBase {
|
||||
void bind_ssbo(int slot, gpu::UniformBuf **buffer);
|
||||
void bind_ssbo(const char *name, gpu::VertBuf *buffer);
|
||||
void bind_ssbo(const char *name, gpu::VertBuf **buffer);
|
||||
void bind_ssbo(const char *name, gpu::VertBufPtr &buffer);
|
||||
void bind_ssbo(int slot, gpu::VertBuf *buffer);
|
||||
void bind_ssbo(int slot, gpu::VertBuf **buffer);
|
||||
void bind_ssbo(int slot, gpu::VertBufPtr &buffer);
|
||||
void bind_ssbo(const char *name, gpu::IndexBuf *buffer);
|
||||
void bind_ssbo(const char *name, gpu::IndexBuf **buffer);
|
||||
void bind_ssbo(int slot, gpu::IndexBuf *buffer);
|
||||
@@ -893,6 +897,7 @@ inline void PassBase<T>::draw(gpu::Batch *batch,
|
||||
if (instance_len == 0 || vertex_len == 0) {
|
||||
return;
|
||||
}
|
||||
BLI_assert(batch);
|
||||
BLI_assert(shader_);
|
||||
draw_commands_buf_.append_draw(headers_,
|
||||
commands_,
|
||||
@@ -1224,6 +1229,12 @@ template<class T> inline void PassBase<T>::bind_ssbo(const char *name, gpu::Vert
|
||||
this->bind_ssbo(GPU_shader_get_ssbo_binding(shader_, name), buffer);
|
||||
}
|
||||
|
||||
template<class T> inline void PassBase<T>::bind_ssbo(const char *name, gpu::VertBufPtr &buffer)
|
||||
{
|
||||
BLI_assert(buffer.get() != nullptr);
|
||||
this->bind_ssbo(GPU_shader_get_ssbo_binding(shader_, name), buffer.get());
|
||||
}
|
||||
|
||||
template<class T> inline void PassBase<T>::bind_ssbo(const char *name, gpu::IndexBuf *buffer)
|
||||
{
|
||||
BLI_assert(buffer != nullptr);
|
||||
@@ -1263,6 +1274,12 @@ template<class T> inline void PassBase<T>::bind_texture(const char *name, gpu::V
|
||||
this->bind_texture(GPU_shader_get_sampler_binding(shader_, name), buffer);
|
||||
}
|
||||
|
||||
template<class T> inline void PassBase<T>::bind_texture(const char *name, gpu::VertBufPtr &buffer)
|
||||
{
|
||||
BLI_assert(buffer.get() != nullptr);
|
||||
this->bind_texture(GPU_shader_get_sampler_binding(shader_, name), buffer.get());
|
||||
}
|
||||
|
||||
template<class T> inline void PassBase<T>::bind_image(const char *name, gpu::Texture *image)
|
||||
{
|
||||
BLI_assert(image != nullptr);
|
||||
@@ -1303,6 +1320,13 @@ template<class T> inline void PassBase<T>::bind_ssbo(int slot, gpu::VertBuf **bu
|
||||
slot, buffer, ResourceBind::Type::VertexAsStorageBuf};
|
||||
}
|
||||
|
||||
template<class T> inline void PassBase<T>::bind_ssbo(int slot, gpu::VertBufPtr &buffer)
|
||||
{
|
||||
BLI_assert(buffer.get() != nullptr);
|
||||
create_command(Type::ResourceBind).resource_bind = {
|
||||
slot, buffer.get(), ResourceBind::Type::VertexAsStorageBuf};
|
||||
}
|
||||
|
||||
template<class T> inline void PassBase<T>::bind_ssbo(int slot, gpu::IndexBuf *buffer)
|
||||
{
|
||||
BLI_assert(buffer != nullptr);
|
||||
@@ -1342,6 +1366,12 @@ template<class T> inline void PassBase<T>::bind_texture(int slot, gpu::VertBuf *
|
||||
create_command(Type::ResourceBind).resource_bind = {slot, buffer};
|
||||
}
|
||||
|
||||
template<class T> inline void PassBase<T>::bind_texture(int slot, gpu::VertBufPtr &buffer)
|
||||
{
|
||||
BLI_assert(buffer.get() != nullptr);
|
||||
create_command(Type::ResourceBind).resource_bind = {slot, buffer.get()};
|
||||
}
|
||||
|
||||
template<class T> inline void PassBase<T>::bind_image(int slot, gpu::Texture *image)
|
||||
{
|
||||
BLI_assert(image != nullptr);
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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)
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
133
source/blender/draw/intern/shaders/draw_curves_info.hh
Normal file
133
source/blender/draw/intern/shaders/draw_curves_info.hh
Normal file
@@ -0,0 +1,133 @@
|
||||
/* SPDX-FileCopyrightText: 2025 Blender Authors
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
/** \file
|
||||
* \ingroup draw
|
||||
*/
|
||||
|
||||
#ifdef GPU_SHADER
|
||||
# pragma once
|
||||
# include "gpu_glsl_cpp_stubs.hh"
|
||||
|
||||
# include "draw_attribute_shader_shared.hh"
|
||||
# include "draw_object_infos_info.hh"
|
||||
|
||||
# define DRW_HAIR_INFO
|
||||
#endif
|
||||
|
||||
#include "draw_curves_defines.hh"
|
||||
|
||||
#include "gpu_shader_create_info.hh"
|
||||
|
||||
GPU_SHADER_CREATE_INFO(draw_curves_topology)
|
||||
LOCAL_GROUP_SIZE(CURVES_PER_THREADGROUP)
|
||||
/* Offsets giving the start and end of the curve. */
|
||||
STORAGE_BUF(0, read, int, evaluated_offsets_buf[])
|
||||
STORAGE_BUF(1, read, uint, curves_cyclic_buf[]) /* Actually bool (1 byte). */
|
||||
STORAGE_BUF(2, write, int, indirection_buf[])
|
||||
PUSH_CONSTANT(int, curves_start)
|
||||
PUSH_CONSTANT(int, curves_count)
|
||||
PUSH_CONSTANT(bool, is_ribbon_topology)
|
||||
PUSH_CONSTANT(bool, use_cyclic)
|
||||
COMPUTE_SOURCE("draw_curves_topology_comp.glsl")
|
||||
DO_STATIC_COMPILATION()
|
||||
GPU_SHADER_CREATE_END()
|
||||
|
||||
GPU_SHADER_CREATE_INFO(draw_curves_data)
|
||||
LOCAL_GROUP_SIZE(CURVES_PER_THREADGROUP)
|
||||
/* Offsets giving the start and end of the curve. */
|
||||
STORAGE_BUF(EVALUATED_POINT_SLOT, read, int, evaluated_points_by_curve_buf[])
|
||||
STORAGE_BUF(POINTS_BY_CURVES_SLOT, read, int, points_by_curve_buf[])
|
||||
STORAGE_BUF(CURVE_RESOLUTION_SLOT, read, uint, curves_resolution_buf[])
|
||||
STORAGE_BUF(CURVE_TYPE_SLOT, read, uint, curves_type_buf[]) /* Actually int8_t. */
|
||||
STORAGE_BUF(CURVE_CYCLIC_SLOT, read, uint, curves_cyclic_buf[]) /* Actually bool (1 byte). */
|
||||
/* Bezier handles (if needed). */
|
||||
STORAGE_BUF(HANDLES_POS_LEFT_SLOT, read, float, handles_positions_left_buf[])
|
||||
STORAGE_BUF(HANDLES_POS_RIGHT_SLOT, read, float, handles_positions_right_buf[])
|
||||
STORAGE_BUF(BEZIER_OFFSETS_SLOT, read, int, bezier_offsets_buf[])
|
||||
/* Nurbs (alias of other buffers). */
|
||||
// STORAGE_BUF(CURVES_ORDER_SLOT, read, uint, curves_order_buf[]) /* Actually int8_t. */
|
||||
// STORAGE_BUF(BASIS_CACHE_SLOT, read, float, basis_cache_buf[])
|
||||
// STORAGE_BUF(CONTROL_WEIGHTS_SLOT, read, float, control_weights_buf[])
|
||||
// STORAGE_BUF(BASIS_CACHE_OFFSET_SLOT, read, int, basis_cache_offset_buf[])
|
||||
PUSH_CONSTANT(int, curves_start)
|
||||
PUSH_CONSTANT(int, curves_count)
|
||||
PUSH_CONSTANT(bool, use_point_weight)
|
||||
PUSH_CONSTANT(bool, use_cyclic)
|
||||
/** IMPORTANT: For very dumb reasons, on GL the default specialization is compiled and used for
|
||||
* creating the shader interface. If this happens to optimize out some push_constants that are
|
||||
* valid in other specialization, we will never be able to set them. So choose the specialization
|
||||
* that uses all push_constants. */
|
||||
SPECIALIZATION_CONSTANT(int, evaluated_type, 3) /* CURVE_TYPE_NURBS */
|
||||
TYPEDEF_SOURCE("draw_attribute_shader_shared.hh")
|
||||
COMPUTE_SOURCE("draw_curves_interpolation_comp.glsl")
|
||||
GPU_SHADER_CREATE_END()
|
||||
|
||||
GPU_SHADER_CREATE_INFO(draw_curves_interpolate_position)
|
||||
ADDITIONAL_INFO(draw_curves_data)
|
||||
/* Attributes. */
|
||||
STORAGE_BUF(POINT_POSITIONS_SLOT, read, float, positions_buf[])
|
||||
STORAGE_BUF(POINT_RADII_SLOT, read, float, radii_buf[])
|
||||
/* Outputs. */
|
||||
STORAGE_BUF(EVALUATED_POS_RAD_SLOT, read_write, float4, evaluated_positions_radii_buf[])
|
||||
PUSH_CONSTANT(float4x4, transform)
|
||||
COMPUTE_FUNCTION("evaluate_position_radius")
|
||||
DO_STATIC_COMPILATION()
|
||||
GPU_SHADER_CREATE_END()
|
||||
|
||||
GPU_SHADER_CREATE_INFO(draw_curves_interpolate_float4_attribute)
|
||||
ADDITIONAL_INFO(draw_curves_data)
|
||||
STORAGE_BUF(POINT_ATTR_SLOT, read, StoredFloat4, attribute_float4_buf[])
|
||||
STORAGE_BUF(EVALUATED_ATTR_SLOT, read_write, StoredFloat4, evaluated_float4_buf[])
|
||||
COMPUTE_FUNCTION("evaluate_attribute_float4")
|
||||
DO_STATIC_COMPILATION()
|
||||
GPU_SHADER_CREATE_END()
|
||||
|
||||
GPU_SHADER_CREATE_INFO(draw_curves_interpolate_float3_attribute)
|
||||
ADDITIONAL_INFO(draw_curves_data)
|
||||
STORAGE_BUF(POINT_ATTR_SLOT, read, StoredFloat3, attribute_float3_buf[])
|
||||
STORAGE_BUF(EVALUATED_ATTR_SLOT, read_write, StoredFloat3, evaluated_float3_buf[])
|
||||
COMPUTE_FUNCTION("evaluate_attribute_float3")
|
||||
DO_STATIC_COMPILATION()
|
||||
GPU_SHADER_CREATE_END()
|
||||
|
||||
GPU_SHADER_CREATE_INFO(draw_curves_interpolate_float2_attribute)
|
||||
ADDITIONAL_INFO(draw_curves_data)
|
||||
STORAGE_BUF(POINT_ATTR_SLOT, read, StoredFloat2, attribute_float2_buf[])
|
||||
STORAGE_BUF(EVALUATED_ATTR_SLOT, read_write, StoredFloat2, evaluated_float2_buf[])
|
||||
COMPUTE_FUNCTION("evaluate_attribute_float2")
|
||||
DO_STATIC_COMPILATION()
|
||||
GPU_SHADER_CREATE_END()
|
||||
|
||||
GPU_SHADER_CREATE_INFO(draw_curves_interpolate_float_attribute)
|
||||
ADDITIONAL_INFO(draw_curves_data)
|
||||
STORAGE_BUF(POINT_ATTR_SLOT, read, StoredFloat, attribute_float_buf[])
|
||||
STORAGE_BUF(EVALUATED_ATTR_SLOT, read_write, StoredFloat, evaluated_float_buf[])
|
||||
COMPUTE_FUNCTION("evaluate_attribute_float")
|
||||
DO_STATIC_COMPILATION()
|
||||
GPU_SHADER_CREATE_END()
|
||||
|
||||
GPU_SHADER_CREATE_INFO(draw_curves_evaluate_length_intercept)
|
||||
LOCAL_GROUP_SIZE(CURVES_PER_THREADGROUP)
|
||||
STORAGE_BUF(EVALUATED_POINT_SLOT, read, int, evaluated_points_by_curve_buf[])
|
||||
STORAGE_BUF(EVALUATED_POS_RAD_SLOT, read, float4, evaluated_positions_radii_buf[])
|
||||
STORAGE_BUF(EVALUATED_TIME_SLOT, read_write, float, evaluated_time_buf[])
|
||||
STORAGE_BUF(CURVES_LENGTH_SLOT, write, float, curves_length_buf[])
|
||||
PUSH_CONSTANT(int, curves_start)
|
||||
PUSH_CONSTANT(int, curves_count)
|
||||
PUSH_CONSTANT(bool, use_cyclic)
|
||||
COMPUTE_FUNCTION("evaluate_length_intercept")
|
||||
COMPUTE_SOURCE("draw_curves_length_intercept_comp.glsl")
|
||||
DO_STATIC_COMPILATION()
|
||||
GPU_SHADER_CREATE_END()
|
||||
|
||||
GPU_SHADER_CREATE_INFO(draw_curves_test)
|
||||
STORAGE_BUF(0, write, float, result_pos_buf[])
|
||||
STORAGE_BUF(1, write, int4, result_indices_buf[])
|
||||
VERTEX_SOURCE("draw_curves_test.glsl")
|
||||
FRAGMENT_SOURCE("draw_curves_test.glsl")
|
||||
ADDITIONAL_INFO(draw_curves_infos)
|
||||
ADDITIONAL_INFO(draw_curves)
|
||||
DO_STATIC_COMPILATION()
|
||||
GPU_SHADER_CREATE_END()
|
||||
@@ -0,0 +1,575 @@
|
||||
/* SPDX-FileCopyrightText: 2025 Blender Authors
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
/**
|
||||
* GPU generated interpolated position and radius. Updated on attribute change.
|
||||
* One thread processes one curve.
|
||||
*
|
||||
* Equivalent of `CurvesGeometry::evaluated_positions()`.
|
||||
*/
|
||||
|
||||
#include "draw_curves_info.hh"
|
||||
|
||||
#include "gpu_shader_attribute_load_lib.glsl"
|
||||
#include "gpu_shader_math_base_lib.glsl"
|
||||
#include "gpu_shader_math_matrix_lib.glsl"
|
||||
#include "gpu_shader_offset_indices_lib.glsl"
|
||||
|
||||
/* We workaround the lack of function pointers by using different type to overload the attribute
|
||||
* implementation. */
|
||||
struct InterpPosition {
|
||||
/* Position, Radius. */
|
||||
float4 data;
|
||||
|
||||
METAL_CONSTRUCTOR_1(InterpPosition, float4, data)
|
||||
|
||||
static InterpPosition zero()
|
||||
{
|
||||
return InterpPosition(float4(0));
|
||||
}
|
||||
};
|
||||
|
||||
/** Input Load. */
|
||||
|
||||
/* Template this function to be able to call it with only no extra argument. */
|
||||
template<typename T> T input_load(int point_index)
|
||||
{
|
||||
return T(0);
|
||||
}
|
||||
template<> InterpPosition input_load<InterpPosition>(int point_index)
|
||||
{
|
||||
const auto &transform = push_constant_get(draw_curves_interpolate_position, transform);
|
||||
const auto &positions = buffer_get(draw_curves_interpolate_position, positions_buf);
|
||||
InterpPosition interp;
|
||||
interp.data.xyz = gpu_attr_load_float3(positions, int2(3, 0), point_index);
|
||||
interp.data.w = buffer_get(draw_curves_interpolate_position, radii_buf)[point_index];
|
||||
/* Bake object transform for legacy hair particle. */
|
||||
interp.data.xyz = transform_point(transform, interp.data.xyz);
|
||||
return interp;
|
||||
}
|
||||
template<> float4 input_load<float4>(int point_index)
|
||||
{
|
||||
StoredFloat4 data = buffer_get(draw_curves_interpolate_float4_attribute,
|
||||
attribute_float4_buf)[point_index];
|
||||
return load_data(data);
|
||||
}
|
||||
template<> float3 input_load<float3>(int point_index)
|
||||
{
|
||||
StoredFloat3 data = buffer_get(draw_curves_interpolate_float3_attribute,
|
||||
attribute_float3_buf)[point_index];
|
||||
return load_data(data);
|
||||
}
|
||||
template<> float2 input_load<float2>(int point_index)
|
||||
{
|
||||
StoredFloat2 data = buffer_get(draw_curves_interpolate_float2_attribute,
|
||||
attribute_float2_buf)[point_index];
|
||||
return load_data(data);
|
||||
}
|
||||
template<> float input_load<float>(int point_index)
|
||||
{
|
||||
StoredFloat data = buffer_get(draw_curves_interpolate_float_attribute,
|
||||
attribute_float_buf)[point_index];
|
||||
return load_data(data);
|
||||
}
|
||||
|
||||
/** Output Load. */
|
||||
|
||||
/* Template this function to be able to call it with only no extra argument. */
|
||||
template<typename T> T output_load(int evaluated_point_index)
|
||||
{
|
||||
return T(0);
|
||||
}
|
||||
template<> InterpPosition output_load<InterpPosition>(int evaluated_point_index)
|
||||
{
|
||||
InterpPosition data;
|
||||
data.data = buffer_get(draw_curves_interpolate_position,
|
||||
evaluated_positions_radii_buf)[evaluated_point_index];
|
||||
return data;
|
||||
}
|
||||
template<> float4 output_load<float4>(int evaluated_point_index)
|
||||
{
|
||||
StoredFloat4 data = buffer_get(draw_curves_interpolate_float4_attribute,
|
||||
evaluated_float4_buf)[evaluated_point_index];
|
||||
return load_data(data);
|
||||
}
|
||||
template<> float3 output_load<float3>(int evaluated_point_index)
|
||||
{
|
||||
StoredFloat3 data = buffer_get(draw_curves_interpolate_float3_attribute,
|
||||
evaluated_float3_buf)[evaluated_point_index];
|
||||
return load_data(data);
|
||||
}
|
||||
template<> float2 output_load<float2>(int evaluated_point_index)
|
||||
{
|
||||
StoredFloat2 data = buffer_get(draw_curves_interpolate_float2_attribute,
|
||||
evaluated_float2_buf)[evaluated_point_index];
|
||||
return load_data(data);
|
||||
}
|
||||
template<> float output_load<float>(int evaluated_point_index)
|
||||
{
|
||||
StoredFloat data = buffer_get(draw_curves_interpolate_float_attribute,
|
||||
evaluated_float_buf)[evaluated_point_index];
|
||||
return load_data(data);
|
||||
}
|
||||
|
||||
/** Output Write. */
|
||||
|
||||
void output_write(int evaluated_point_index, InterpPosition interp)
|
||||
{
|
||||
/* Clamp radius to 0 to avoid negative radius due to interpolation. */
|
||||
interp.data.w = max(0.0, interp.data.w);
|
||||
buffer_get(draw_curves_interpolate_position,
|
||||
evaluated_positions_radii_buf)[evaluated_point_index] = interp.data;
|
||||
}
|
||||
void output_write(int evaluated_point_index, const float4 interp)
|
||||
{
|
||||
buffer_get(draw_curves_interpolate_float4_attribute,
|
||||
evaluated_float4_buf)[evaluated_point_index] = as_data(interp);
|
||||
}
|
||||
void output_write(int evaluated_point_index, const float3 interp)
|
||||
{
|
||||
buffer_get(draw_curves_interpolate_float3_attribute,
|
||||
evaluated_float3_buf)[evaluated_point_index] = as_data(interp);
|
||||
}
|
||||
void output_write(int evaluated_point_index, const float2 interp)
|
||||
{
|
||||
buffer_get(draw_curves_interpolate_float2_attribute,
|
||||
evaluated_float2_buf)[evaluated_point_index] = as_data(interp);
|
||||
}
|
||||
void output_write(int evaluated_point_index, const float interp)
|
||||
{
|
||||
buffer_get(draw_curves_interpolate_float_attribute,
|
||||
evaluated_float_buf)[evaluated_point_index] = as_data(interp);
|
||||
}
|
||||
|
||||
/** Output Weighted Add. */
|
||||
|
||||
void output_weighted_add(int evaluated_point_index, float w, const InterpPosition src)
|
||||
{
|
||||
buffer_get(draw_curves_interpolate_position,
|
||||
evaluated_positions_radii_buf)[evaluated_point_index] += src.data * w;
|
||||
}
|
||||
|
||||
template<typename InterpType>
|
||||
void output_weighted_add(int evaluated_point_index, float w, const InterpType src)
|
||||
{
|
||||
InterpType dst = output_load<InterpType>(evaluated_point_index);
|
||||
dst += src * w;
|
||||
output_write(evaluated_point_index, dst);
|
||||
}
|
||||
template void output_weighted_add<float4>(int, float, float4);
|
||||
template void output_weighted_add<float3>(int, float, float3);
|
||||
template void output_weighted_add<float2>(int, float, float2);
|
||||
template void output_weighted_add<float>(int, float, float);
|
||||
|
||||
/** Output Mul. */
|
||||
|
||||
template<typename InterpType> void output_mul(int evaluated_point_index, float w)
|
||||
{
|
||||
InterpType dst = output_load<InterpType>(evaluated_point_index);
|
||||
dst *= w;
|
||||
output_write(evaluated_point_index, dst);
|
||||
}
|
||||
template void output_mul<float4>(int, float);
|
||||
template void output_mul<float3>(int, float);
|
||||
template void output_mul<float2>(int, float);
|
||||
template void output_mul<float>(int, float);
|
||||
template<> void output_mul<InterpPosition>(int evaluated_point_index, float w)
|
||||
{
|
||||
buffer_get(draw_curves_interpolate_position,
|
||||
evaluated_positions_radii_buf)[evaluated_point_index] *= w;
|
||||
}
|
||||
|
||||
/** Output Set To Zero. */
|
||||
|
||||
template<typename InterpType> void output_set_zero(int evaluated_point_index)
|
||||
{
|
||||
output_write(evaluated_point_index, InterpType(0.0f));
|
||||
}
|
||||
template void output_set_zero<float4>(int);
|
||||
template void output_set_zero<float3>(int);
|
||||
template void output_set_zero<float2>(int);
|
||||
template void output_set_zero<float>(int);
|
||||
template<> void output_set_zero<InterpPosition>(int evaluated_point_index)
|
||||
{
|
||||
buffer_get(draw_curves_interpolate_position,
|
||||
evaluated_positions_radii_buf)[evaluated_point_index] = float4(0.0);
|
||||
}
|
||||
|
||||
/** Mix 4. */
|
||||
|
||||
InterpPosition mix4(
|
||||
InterpPosition v0, InterpPosition v1, InterpPosition v2, InterpPosition v3, float4 w)
|
||||
{
|
||||
v0.data = v0.data * w.x + v1.data * w.y + v2.data * w.z + v3.data * w.w;
|
||||
return v0;
|
||||
}
|
||||
|
||||
template<typename DataT> DataT mix4(DataT v0, DataT v1, DataT v2, DataT v3, float4 w)
|
||||
{
|
||||
v0 = v0 * w.x + v1 * w.y + v2 * w.z + v3 * w.w;
|
||||
return v0;
|
||||
}
|
||||
template float4 mix4<float4>(float4, float4, float4, float4, float4);
|
||||
template float3 mix4<float3>(float3, float3, float3, float3, float4);
|
||||
template float2 mix4<float2>(float2, float2, float2, float2, float4);
|
||||
template float mix4<float>(float, float, float, float, float4);
|
||||
|
||||
/** Utilities. */
|
||||
|
||||
bool curve_cyclic_get(int curve_index)
|
||||
{
|
||||
const auto &use_cyclic = push_constant_get(draw_curves_data, use_cyclic);
|
||||
const auto &curves_cyclic_buf = buffer_get(draw_curves_data, curves_cyclic_buf);
|
||||
if (use_cyclic) {
|
||||
return gpu_attr_load_bool(curves_cyclic_buf, curve_index);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
namespace catmull_rom {
|
||||
|
||||
float4 calculate_basis(const float parameter)
|
||||
{
|
||||
/* Adapted from Cycles #catmull_rom_basis_eval function. */
|
||||
const float t = parameter;
|
||||
const float s = 1.0f - parameter;
|
||||
return 0.5f * float4(-t * s * s,
|
||||
2.0f + t * t * (3.0f * t - 5.0f),
|
||||
2.0f + s * s * (3.0f * s - 5.0f),
|
||||
-s * t * t);
|
||||
}
|
||||
|
||||
int4 get_points(uint point_id, IndexRange points, const bool cyclic)
|
||||
{
|
||||
int4 point_ids = int(point_id) + int4(-1, +0, +1, +2);
|
||||
if (cyclic) {
|
||||
/* Wrap around. Note the offset by size to avoid modulo with negative values. */
|
||||
point_ids = ((point_ids + points.size()) % points.size());
|
||||
}
|
||||
else {
|
||||
point_ids = clamp(point_ids, int4(0), int4(points.size() - 1));
|
||||
}
|
||||
return points.start() + point_ids;
|
||||
}
|
||||
|
||||
template<typename InterpType>
|
||||
void evaluate_curve(const IndexRange points,
|
||||
const IndexRange evaluated_points,
|
||||
const int curve_index)
|
||||
{
|
||||
const auto &curves_resolution_buf = buffer_get(draw_curves_data, curves_resolution_buf);
|
||||
const uint curve_resolution = curves_resolution_buf[curve_index];
|
||||
const bool is_curve_cyclic = curve_cyclic_get(curve_index);
|
||||
|
||||
for (uint i = 0; i < evaluated_points.size(); i++) {
|
||||
const int evaluated_point_id = evaluated_points.start() + int(i);
|
||||
const uint point_id = i / curve_resolution;
|
||||
const float parameter = float(i % curve_resolution) / float(curve_resolution);
|
||||
const float4 weights = calculate_basis(parameter);
|
||||
const int4 point_ids = get_points(point_id, points, is_curve_cyclic);
|
||||
|
||||
InterpType p0 = input_load<InterpType>(point_ids.x);
|
||||
InterpType p1 = input_load<InterpType>(point_ids.y);
|
||||
InterpType p2 = input_load<InterpType>(point_ids.z);
|
||||
InterpType p3 = input_load<InterpType>(point_ids.w);
|
||||
InterpType result = mix4(p0, p1, p2, p3, weights);
|
||||
|
||||
output_write(evaluated_point_id, result);
|
||||
}
|
||||
}
|
||||
|
||||
template void evaluate_curve<InterpPosition>(IndexRange, IndexRange, int);
|
||||
template void evaluate_curve<float>(IndexRange, IndexRange, int);
|
||||
template void evaluate_curve<float2>(IndexRange, IndexRange, int);
|
||||
template void evaluate_curve<float3>(IndexRange, IndexRange, int);
|
||||
template void evaluate_curve<float4>(IndexRange, IndexRange, int);
|
||||
|
||||
} // namespace catmull_rom
|
||||
|
||||
namespace bezier {
|
||||
|
||||
template<typename InterpType> void evaluate_segment(const int2 points, const IndexRange result)
|
||||
{
|
||||
InterpType p0 = input_load<InterpType>(points.x);
|
||||
InterpType p1 = input_load<InterpType>(points.y);
|
||||
|
||||
const float step = 1.0f / float(result.size());
|
||||
for (int i = 0; i < result.size(); i++) {
|
||||
output_write(result.start() + i, mix(p0, p1, float(i) * step));
|
||||
}
|
||||
}
|
||||
|
||||
template<> void evaluate_segment<InterpPosition>(const int2 points, const IndexRange result)
|
||||
{
|
||||
const auto &handles_right = buffer_get(draw_curves_interpolate_position,
|
||||
handles_positions_right_buf);
|
||||
const auto &handles_left = buffer_get(draw_curves_interpolate_position,
|
||||
handles_positions_left_buf);
|
||||
|
||||
InterpPosition p0 = input_load<InterpPosition>(points.x);
|
||||
InterpPosition p1 = input_load<InterpPosition>(points.y);
|
||||
|
||||
const float3 point_0 = p0.data.xyz;
|
||||
const float3 point_1 = gpu_attr_load_float3(handles_right, int2(3, 0), points.x);
|
||||
const float3 point_2 = gpu_attr_load_float3(handles_left, int2(3, 0), points.y);
|
||||
const float3 point_3 = p1.data.xyz;
|
||||
|
||||
const float rad_0 = p0.data.w;
|
||||
const float rad_1 = p1.data.w;
|
||||
|
||||
assert(result.size > 0);
|
||||
const float inv_len = 1.0f / float(result.size());
|
||||
const float inv_len_squared = inv_len * inv_len;
|
||||
const float inv_len_cubed = inv_len_squared * inv_len;
|
||||
|
||||
const float3 rt1 = 3.0f * (point_1 - point_0) * inv_len;
|
||||
const float3 rt2 = 3.0f * (point_0 - 2.0f * point_1 + point_2) * inv_len_squared;
|
||||
const float3 rt3 = (point_3 - point_0 + 3.0f * (point_1 - point_2)) * inv_len_cubed;
|
||||
|
||||
float3 q0 = point_0;
|
||||
float3 q1 = rt1 + rt2 + rt3;
|
||||
float3 q2 = 2.0f * rt2 + 6.0f * rt3;
|
||||
float3 q3 = 6.0f * rt3;
|
||||
for (int i = 0; i < result.size(); i++) {
|
||||
float rad = mix(rad_0, rad_1, float(i) * inv_len);
|
||||
InterpPosition interp;
|
||||
interp.data = float4(q0, rad);
|
||||
output_write(result.start() + i, interp);
|
||||
q0 += q1;
|
||||
q1 += q2;
|
||||
q2 += q3;
|
||||
}
|
||||
}
|
||||
|
||||
template void evaluate_segment<float>(int2, IndexRange);
|
||||
template void evaluate_segment<float2>(int2, IndexRange);
|
||||
template void evaluate_segment<float3>(int2, IndexRange);
|
||||
template void evaluate_segment<float4>(int2, IndexRange);
|
||||
|
||||
IndexRange per_curve_point_offsets_range(const IndexRange points, const int curve_index)
|
||||
{
|
||||
return IndexRange(curve_index + points.start(), points.size() + 1);
|
||||
}
|
||||
|
||||
int2 get_points(uint point_id, IndexRange points, const bool cyclic)
|
||||
{
|
||||
int2 point_ids = int(point_id) + int2(+0, +1);
|
||||
if (cyclic) {
|
||||
/* Wrap around. Note the offset by size to avoid modulo with negative values. */
|
||||
point_ids = ((point_ids + points.size()) % points.size());
|
||||
}
|
||||
else {
|
||||
point_ids = clamp(point_ids, int2(0), int2(points.size() - 1));
|
||||
}
|
||||
return points.start() + point_ids;
|
||||
}
|
||||
|
||||
template<typename InterpType>
|
||||
void evaluate_curve(const IndexRange points,
|
||||
const IndexRange evaluated_points,
|
||||
const int curve_index)
|
||||
{
|
||||
const auto &bezier_offsets_buf = buffer_get(draw_curves_data, bezier_offsets_buf);
|
||||
/* Range used for indexing bezier offsets. */
|
||||
const IndexRange offsets = per_curve_point_offsets_range(points, curve_index);
|
||||
const bool is_curve_cyclic = curve_cyclic_get(curve_index);
|
||||
|
||||
for (int i = 0; i < points.size(); i++) {
|
||||
/* Bezier curves can have different number of evaluated segment per curve segment. */
|
||||
const IndexRange segment_range = offset_indices::load_range_from_buffer(bezier_offsets_buf,
|
||||
offsets.start() + i);
|
||||
const IndexRange evaluated_segment_range = evaluated_points.slice(segment_range);
|
||||
const int2 point_ids = get_points(i, points, is_curve_cyclic);
|
||||
|
||||
evaluate_segment<InterpType>(point_ids, evaluated_segment_range);
|
||||
}
|
||||
|
||||
if (is_curve_cyclic) {
|
||||
/* The closing point is not contained inside `bezier_offsets_buf` so we do manual copy. */
|
||||
output_write(evaluated_points.last(), output_load<InterpType>(evaluated_points.first()));
|
||||
}
|
||||
}
|
||||
|
||||
template void evaluate_curve<InterpPosition>(IndexRange, IndexRange, int);
|
||||
template void evaluate_curve<float>(IndexRange, IndexRange, int);
|
||||
template void evaluate_curve<float2>(IndexRange, IndexRange, int);
|
||||
template void evaluate_curve<float3>(IndexRange, IndexRange, int);
|
||||
template void evaluate_curve<float4>(IndexRange, IndexRange, int);
|
||||
|
||||
} // namespace bezier
|
||||
|
||||
template<typename InterpType>
|
||||
void copy_curve_data(const IndexRange points,
|
||||
const IndexRange evaluated_points,
|
||||
const int curve_index)
|
||||
{
|
||||
assert(points.size == evaluated_points.size);
|
||||
const bool is_curve_cyclic = curve_cyclic_get(curve_index);
|
||||
|
||||
for (int i = 0; i < points.size(); i++) {
|
||||
output_write(evaluated_points.start() + i, input_load<InterpType>(points.start() + i));
|
||||
}
|
||||
|
||||
if (is_curve_cyclic) {
|
||||
/* The closing point is not contained inside `bezier_offsets_buf` so we do manual copy. */
|
||||
output_write(evaluated_points.last(), output_load<InterpType>(evaluated_points.first()));
|
||||
}
|
||||
}
|
||||
|
||||
template void copy_curve_data<InterpPosition>(IndexRange, IndexRange, int);
|
||||
template void copy_curve_data<float>(IndexRange, IndexRange, int);
|
||||
template void copy_curve_data<float2>(IndexRange, IndexRange, int);
|
||||
template void copy_curve_data<float3>(IndexRange, IndexRange, int);
|
||||
template void copy_curve_data<float4>(IndexRange, IndexRange, int);
|
||||
|
||||
namespace nurbs {
|
||||
|
||||
template<typename InterpType>
|
||||
void evaluate_curve(const IndexRange points,
|
||||
const IndexRange evaluated_points_padded,
|
||||
const int curve_index)
|
||||
{
|
||||
const auto &use_cyclic = push_constant_get(draw_curves_data, use_cyclic);
|
||||
/* Buffer aliasing to same bind point. We cannot dispatch with different type of curve. */
|
||||
const auto &curves_order_buf = buffer_get(draw_curves_data, curves_resolution_buf);
|
||||
const auto &basis_cache_offset_buf = buffer_get(draw_curves_data, bezier_offsets_buf);
|
||||
|
||||
const int order = int(gpu_attr_load_uchar(curves_order_buf, curve_index));
|
||||
|
||||
const int basis_cache_start = basis_cache_offset_buf[curve_index];
|
||||
const bool invalid = basis_cache_start < 0;
|
||||
|
||||
if (invalid) {
|
||||
copy_curve_data<InterpType>(points, evaluated_points_padded, curve_index);
|
||||
return;
|
||||
}
|
||||
|
||||
const auto &use_point_weight = push_constant_get(draw_curves_data, use_point_weight);
|
||||
const bool is_curve_cyclic = curve_cyclic_get(curve_index);
|
||||
|
||||
/* Recover original points range without closing cyclic point. */
|
||||
const IndexRange evaluated_points = IndexRange(evaluated_points_padded.start(),
|
||||
evaluated_points_padded.size() - int(use_cyclic));
|
||||
|
||||
const int start_indices_range_start = basis_cache_start;
|
||||
const int weights_range_start = basis_cache_start + evaluated_points.size();
|
||||
|
||||
/* Buffer aliasing to same bind point. We cannot dispatch with different type of curve. */
|
||||
const auto &basis_cache_buf = buffer_get(draw_curves_data, handles_positions_left_buf);
|
||||
const auto &control_weights_buf = buffer_get(draw_curves_data, handles_positions_right_buf);
|
||||
|
||||
for (int i = 0; i < evaluated_points.size(); i++) {
|
||||
int evaluated_point_index = evaluated_points.start() + i;
|
||||
/* Equivalent to `attribute_math::DefaultMixer<T> mixer{dst}`. */
|
||||
output_set_zero<InterpType>(evaluated_point_index);
|
||||
float total_weight = 0.0f;
|
||||
|
||||
const IndexRange point_weights = IndexRange(weights_range_start + i * order, order);
|
||||
const int start_index = floatBitsToInt(basis_cache_buf[start_indices_range_start + i]);
|
||||
|
||||
for (int j = 0; j < point_weights.size(); j++) {
|
||||
const int point_index = points.start() + (start_index + j) % points.size();
|
||||
const float point_weight = basis_cache_buf[point_weights.start() + j];
|
||||
const float control_weight = use_point_weight ? control_weights_buf[point_index] : 1.0f;
|
||||
const float weight = point_weight * control_weight;
|
||||
/* Equivalent to `mixer.mix_in()`. */
|
||||
output_weighted_add(evaluated_point_index, weight, input_load<InterpType>(point_index));
|
||||
total_weight += weight;
|
||||
}
|
||||
/* Equivalent to `mixer.finalize()` */
|
||||
output_mul<InterpType>(evaluated_point_index, safe_rcp(total_weight));
|
||||
}
|
||||
|
||||
if (is_curve_cyclic) {
|
||||
/* The closing point is not contained inside the NURBS data structure so we do manual copy. */
|
||||
output_write(evaluated_points_padded.last(),
|
||||
output_load<InterpType>(evaluated_points_padded.first()));
|
||||
}
|
||||
}
|
||||
|
||||
template void evaluate_curve<InterpPosition>(IndexRange, IndexRange, int);
|
||||
template void evaluate_curve<float>(IndexRange, IndexRange, int);
|
||||
template void evaluate_curve<float2>(IndexRange, IndexRange, int);
|
||||
template void evaluate_curve<float3>(IndexRange, IndexRange, int);
|
||||
template void evaluate_curve<float4>(IndexRange, IndexRange, int);
|
||||
|
||||
} // namespace nurbs
|
||||
|
||||
template<typename InterpType> void evaluate_curve()
|
||||
{
|
||||
const auto &use_cyclic = push_constant_get(draw_curves_data, use_cyclic);
|
||||
const auto &curves_count = push_constant_get(draw_curves_data, curves_count);
|
||||
const auto &curves_start = push_constant_get(draw_curves_data, curves_start);
|
||||
const auto &evaluated_type = push_constant_get(draw_curves_data, evaluated_type);
|
||||
const auto &curves_type_buf = buffer_get(draw_curves_data, curves_type_buf);
|
||||
const auto &points_by_curve_buf = buffer_get(draw_curves_data, points_by_curve_buf);
|
||||
const auto &evaluated_points_by_curve_buf = buffer_get(draw_curves_data,
|
||||
evaluated_points_by_curve_buf);
|
||||
|
||||
/* Only for gl_GlobalInvocationID. To be removed. */
|
||||
COMPUTE_SHADER_CREATE_INFO(draw_curves_interpolate_position)
|
||||
|
||||
if (gl_GlobalInvocationID.x >= uint(curves_count)) {
|
||||
return;
|
||||
}
|
||||
int curve_index = int(gl_GlobalInvocationID.x) + curves_start;
|
||||
|
||||
const CurveType curve_type = CurveType(gpu_attr_load_uchar(curves_type_buf, curve_index));
|
||||
if (curve_type != CurveType(evaluated_type)) {
|
||||
return;
|
||||
}
|
||||
IndexRange points = offset_indices::load_range_from_buffer(points_by_curve_buf, curve_index);
|
||||
IndexRange evaluated_points = offset_indices::load_range_from_buffer(
|
||||
evaluated_points_by_curve_buf, curve_index);
|
||||
|
||||
if (use_cyclic) {
|
||||
evaluated_points = IndexRange(evaluated_points.start() + curve_index,
|
||||
evaluated_points.size() + 1);
|
||||
}
|
||||
|
||||
if (CurveType(evaluated_type) == CURVE_TYPE_CATMULL_ROM) {
|
||||
catmull_rom::evaluate_curve<InterpType>(points, evaluated_points, curve_index);
|
||||
}
|
||||
else if (CurveType(evaluated_type) == CURVE_TYPE_BEZIER) {
|
||||
bezier::evaluate_curve<InterpType>(points, evaluated_points, curve_index);
|
||||
}
|
||||
else if (CurveType(evaluated_type) == CURVE_TYPE_NURBS) {
|
||||
nurbs::evaluate_curve<InterpType>(points, evaluated_points, curve_index);
|
||||
}
|
||||
else if (CurveType(evaluated_type) == CURVE_TYPE_POLY) {
|
||||
/* Simple copy. */
|
||||
copy_curve_data<InterpType>(points, evaluated_points, curve_index);
|
||||
}
|
||||
}
|
||||
|
||||
template void evaluate_curve<InterpPosition>();
|
||||
template void evaluate_curve<float>();
|
||||
template void evaluate_curve<float2>();
|
||||
template void evaluate_curve<float3>();
|
||||
template void evaluate_curve<float4>();
|
||||
|
||||
void evaluate_position_radius()
|
||||
{
|
||||
evaluate_curve<InterpPosition>();
|
||||
}
|
||||
|
||||
void evaluate_attribute_float()
|
||||
{
|
||||
evaluate_curve<float>();
|
||||
}
|
||||
|
||||
void evaluate_attribute_float2()
|
||||
{
|
||||
evaluate_curve<float2>();
|
||||
}
|
||||
|
||||
void evaluate_attribute_float3()
|
||||
{
|
||||
evaluate_curve<float3>();
|
||||
}
|
||||
|
||||
void evaluate_attribute_float4()
|
||||
{
|
||||
evaluate_curve<float4>();
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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
|
||||
|
||||
24
source/blender/draw/intern/shaders/draw_curves_test.glsl
Normal file
24
source/blender/draw/intern/shaders/draw_curves_test.glsl
Normal file
@@ -0,0 +1,24 @@
|
||||
/* SPDX-FileCopyrightText: 2025 Blender Authors
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
#include "draw_curves_info.hh"
|
||||
|
||||
VERTEX_SHADER_CREATE_INFO(draw_curves_test)
|
||||
|
||||
#include "draw_curves_lib.glsl"
|
||||
|
||||
void main()
|
||||
{
|
||||
#ifdef GPU_VERTEX_SHADER
|
||||
curves::Point pt = curves::point_get(uint(gl_VertexID));
|
||||
|
||||
result_pos_buf[gl_VertexID] = pt.P.x;
|
||||
result_indices_buf[gl_VertexID].x = pt.point_id;
|
||||
result_indices_buf[gl_VertexID].y = pt.curve_id;
|
||||
result_indices_buf[gl_VertexID].z = pt.curve_segment;
|
||||
result_indices_buf[gl_VertexID].w = int(pt.azimuthal_offset);
|
||||
|
||||
gl_Position = float4(0);
|
||||
#endif
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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()
|
||||
@@ -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)
|
||||
|
||||
1343
source/blender/draw/tests/draw_curves_test.cc
Normal file
1343
source/blender/draw/tests/draw_curves_test.cc
Normal file
@@ -0,0 +1,1343 @@
|
||||
/* SPDX-FileCopyrightText: 2025 Blender Authors
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0 */
|
||||
|
||||
#include "DNA_curves_types.h"
|
||||
|
||||
#include "BKE_curves.hh"
|
||||
|
||||
#include "GPU_batch.hh"
|
||||
#include "GPU_shader.hh"
|
||||
|
||||
#include "draw_curves_defines.hh"
|
||||
#include "draw_manager.hh"
|
||||
#include "draw_pass.hh"
|
||||
#include "draw_testing.hh"
|
||||
|
||||
#include "draw_shader_shared.hh"
|
||||
|
||||
namespace blender::draw {
|
||||
|
||||
static void test_draw_curves_lib()
|
||||
{
|
||||
Manager manager;
|
||||
|
||||
gpu::Shader *sh = GPU_shader_create_from_info_name("draw_curves_test");
|
||||
|
||||
struct Indirection {
|
||||
int index;
|
||||
GPU_VERTEX_FORMAT_FUNC(Indirection, index);
|
||||
};
|
||||
gpu::VertBuf *indirection_ribbon_buf = GPU_vertbuf_create_with_format_ex(
|
||||
Indirection::format(), GPU_USAGE_FLAG_BUFFER_TEXTURE_ONLY);
|
||||
indirection_ribbon_buf->allocate(9);
|
||||
indirection_ribbon_buf->data<int>().copy_from({0, -1, -2, -3, -4, 0x7FFFFFFF, 1, -1, -2});
|
||||
gpu::Batch *batch_ribbon = GPU_batch_create_procedural(GPU_PRIM_TRI_STRIP, 2 * 9);
|
||||
|
||||
gpu::VertBuf *indirection_cylinder_buf = GPU_vertbuf_create_with_format_ex(
|
||||
Indirection::format(), GPU_USAGE_FLAG_BUFFER_TEXTURE_ONLY);
|
||||
indirection_cylinder_buf->allocate(6);
|
||||
indirection_cylinder_buf->data<int>().copy_from({0, -1, -2, -3, 1, -1});
|
||||
gpu::Batch *batch_cylinder = GPU_batch_create_procedural(GPU_PRIM_TRI_STRIP, (3 * 2 + 1) * 6);
|
||||
|
||||
struct PositionRadius {
|
||||
float4 pos_rad;
|
||||
GPU_VERTEX_FORMAT_FUNC(PositionRadius, pos_rad);
|
||||
};
|
||||
gpu::VertBuf *pos_rad_buf = GPU_vertbuf_create_with_format_ex(
|
||||
PositionRadius::format(), GPU_USAGE_FLAG_BUFFER_TEXTURE_ONLY);
|
||||
pos_rad_buf->allocate(8);
|
||||
pos_rad_buf->data<float4>().copy_from({float4{1.0f},
|
||||
float4{0.75f},
|
||||
float4{0.5f},
|
||||
float4{0.25f},
|
||||
float4{0.0f},
|
||||
float4{0.0f},
|
||||
float4{1.0f},
|
||||
float4{2.0f}});
|
||||
|
||||
UniformBuffer<CurvesInfos> curves_info_buf;
|
||||
curves_info_buf.is_point_attribute[0].x = 0;
|
||||
curves_info_buf.is_point_attribute[1].x = 1;
|
||||
/* Ribbon. */
|
||||
curves_info_buf.vertex_per_segment = 2;
|
||||
curves_info_buf.half_cylinder_face_count = 1;
|
||||
curves_info_buf.push_update();
|
||||
|
||||
Framebuffer fb;
|
||||
fb.ensure(int2(1, 1));
|
||||
|
||||
{
|
||||
StorageArrayBuffer<float, 512> result_pos;
|
||||
StorageArrayBuffer<int4, 512> result_idx;
|
||||
result_pos.clear_to_zero();
|
||||
result_idx.clear_to_zero();
|
||||
|
||||
PassSimple pass("Ribbon Curves");
|
||||
pass.init();
|
||||
pass.framebuffer_set(&fb);
|
||||
pass.shader_set(sh);
|
||||
pass.bind_ubo("drw_curves", curves_info_buf);
|
||||
pass.bind_texture("curves_pos_rad_buf", pos_rad_buf);
|
||||
pass.bind_texture("curves_indirection_buf", indirection_ribbon_buf);
|
||||
pass.bind_ssbo("result_pos_buf", result_pos);
|
||||
pass.bind_ssbo("result_indices_buf", result_idx);
|
||||
pass.draw(batch_ribbon);
|
||||
pass.barrier(GPU_BARRIER_BUFFER_UPDATE);
|
||||
|
||||
manager.submit(pass);
|
||||
|
||||
/* Note: Expected values follows diagram shown in #142969. */
|
||||
|
||||
result_pos.read();
|
||||
EXPECT_EQ(result_pos[0], 1.0f);
|
||||
EXPECT_EQ(result_pos[1], 1.0f);
|
||||
EXPECT_EQ(result_pos[2], 0.75f);
|
||||
EXPECT_EQ(result_pos[3], 0.75f);
|
||||
EXPECT_EQ(result_pos[4], 0.5f);
|
||||
EXPECT_EQ(result_pos[5], 0.5f);
|
||||
EXPECT_EQ(result_pos[6], 0.25f);
|
||||
EXPECT_EQ(result_pos[7], 0.25f);
|
||||
EXPECT_EQ(result_pos[8], 0.0f);
|
||||
EXPECT_EQ(result_pos[9], 0.0f);
|
||||
EXPECT_TRUE(isnan(result_pos[10]));
|
||||
EXPECT_TRUE(isnan(result_pos[11]));
|
||||
EXPECT_EQ(result_pos[12], 0.0f);
|
||||
EXPECT_EQ(result_pos[13], 0.0f);
|
||||
EXPECT_EQ(result_pos[14], 1.0f);
|
||||
EXPECT_EQ(result_pos[15], 1.0f);
|
||||
EXPECT_EQ(result_pos[16], 2.0f);
|
||||
EXPECT_EQ(result_pos[17], 2.0f);
|
||||
|
||||
result_idx.read();
|
||||
/* x: point_id, y: curve_id, z: curve_segment, w: azimuthal_offset */
|
||||
EXPECT_EQ(result_idx[0], int4(0, 0, 0, -1));
|
||||
EXPECT_EQ(result_idx[1], int4(0, 0, 0, 1));
|
||||
EXPECT_EQ(result_idx[2], int4(1, 0, 1, -1));
|
||||
EXPECT_EQ(result_idx[3], int4(1, 0, 1, 1));
|
||||
EXPECT_EQ(result_idx[4], int4(2, 0, 2, -1));
|
||||
EXPECT_EQ(result_idx[5], int4(2, 0, 2, 1));
|
||||
EXPECT_EQ(result_idx[6], int4(3, 0, 3, -1));
|
||||
EXPECT_EQ(result_idx[7], int4(3, 0, 3, 1));
|
||||
EXPECT_EQ(result_idx[8], int4(4, 0, 4, -1));
|
||||
EXPECT_EQ(result_idx[9], int4(4, 0, 4, 1));
|
||||
EXPECT_EQ(result_idx[10], int4(5, 0, 0, -1)); /* End Of Curve */
|
||||
EXPECT_EQ(result_idx[11], int4(5, 0, 0, 1)); /* End Of Curve */
|
||||
EXPECT_EQ(result_idx[12], int4(5, 1, 0, -1));
|
||||
EXPECT_EQ(result_idx[13], int4(5, 1, 0, 1));
|
||||
EXPECT_EQ(result_idx[14], int4(6, 1, 1, -1));
|
||||
EXPECT_EQ(result_idx[15], int4(6, 1, 1, 1));
|
||||
EXPECT_EQ(result_idx[16], int4(7, 1, 2, -1));
|
||||
EXPECT_EQ(result_idx[17], int4(7, 1, 2, 1));
|
||||
}
|
||||
|
||||
/* Cylinder. */
|
||||
curves_info_buf.vertex_per_segment = 7;
|
||||
curves_info_buf.half_cylinder_face_count = 2;
|
||||
curves_info_buf.push_update();
|
||||
|
||||
{
|
||||
StorageArrayBuffer<float, 512> result_pos;
|
||||
StorageArrayBuffer<int4, 512> result_idx;
|
||||
result_pos.clear_to_zero();
|
||||
result_idx.clear_to_zero();
|
||||
|
||||
PassSimple pass("Cylinder Curves");
|
||||
pass.init();
|
||||
pass.framebuffer_set(&fb);
|
||||
pass.shader_set(sh);
|
||||
pass.bind_ubo("drw_curves", curves_info_buf);
|
||||
pass.bind_texture("curves_pos_rad_buf", pos_rad_buf);
|
||||
pass.bind_texture("curves_indirection_buf", indirection_cylinder_buf);
|
||||
pass.bind_ssbo("result_pos_buf", result_pos);
|
||||
pass.bind_ssbo("result_indices_buf", result_idx);
|
||||
pass.draw(batch_cylinder);
|
||||
pass.barrier(GPU_BARRIER_BUFFER_UPDATE);
|
||||
|
||||
manager.submit(pass);
|
||||
|
||||
/* Note: Expected values follows diagram shown in #142969. */
|
||||
|
||||
result_pos.read();
|
||||
EXPECT_EQ(result_pos[0], 0.75f);
|
||||
EXPECT_EQ(result_pos[1], 1.0f);
|
||||
EXPECT_EQ(result_pos[2], 0.75f);
|
||||
EXPECT_EQ(result_pos[3], 1.0f);
|
||||
EXPECT_EQ(result_pos[4], 0.75f);
|
||||
EXPECT_EQ(result_pos[5], 1.0f);
|
||||
EXPECT_TRUE(isnan(result_pos[6]));
|
||||
EXPECT_EQ(result_pos[7], 0.75f);
|
||||
EXPECT_EQ(result_pos[8], 0.5f);
|
||||
EXPECT_EQ(result_pos[9], 0.75f);
|
||||
EXPECT_EQ(result_pos[10], 0.5f);
|
||||
EXPECT_EQ(result_pos[11], 0.75f);
|
||||
EXPECT_EQ(result_pos[12], 0.5f);
|
||||
EXPECT_TRUE(isnan(result_pos[13]));
|
||||
EXPECT_EQ(result_pos[14], 0.25f);
|
||||
EXPECT_EQ(result_pos[15], 0.5f);
|
||||
EXPECT_EQ(result_pos[16], 0.25f);
|
||||
EXPECT_EQ(result_pos[17], 0.5f);
|
||||
EXPECT_EQ(result_pos[18], 0.25f);
|
||||
EXPECT_EQ(result_pos[19], 0.5f);
|
||||
EXPECT_TRUE(isnan(result_pos[20]));
|
||||
EXPECT_EQ(result_pos[21], 0.25f);
|
||||
EXPECT_EQ(result_pos[22], 0.0f);
|
||||
EXPECT_EQ(result_pos[23], 0.25f);
|
||||
EXPECT_EQ(result_pos[24], 0.0f);
|
||||
EXPECT_EQ(result_pos[25], 0.25f);
|
||||
EXPECT_EQ(result_pos[26], 0.0f);
|
||||
EXPECT_TRUE(isnan(result_pos[27]));
|
||||
EXPECT_EQ(result_pos[28], 1.0f);
|
||||
EXPECT_EQ(result_pos[29], 0.0f);
|
||||
EXPECT_EQ(result_pos[30], 1.0f);
|
||||
EXPECT_EQ(result_pos[31], 0.0f);
|
||||
EXPECT_EQ(result_pos[32], 1.0f);
|
||||
EXPECT_EQ(result_pos[33], 0.0f);
|
||||
EXPECT_TRUE(isnan(result_pos[34]));
|
||||
EXPECT_EQ(result_pos[35], 1.0f);
|
||||
EXPECT_EQ(result_pos[36], 2.0f);
|
||||
EXPECT_EQ(result_pos[37], 1.0f);
|
||||
EXPECT_EQ(result_pos[38], 2.0f);
|
||||
EXPECT_EQ(result_pos[39], 1.0f);
|
||||
EXPECT_EQ(result_pos[40], 2.0f);
|
||||
EXPECT_TRUE(isnan(result_pos[41]));
|
||||
|
||||
result_idx.read();
|
||||
/* x: point_id, y: curve_id, z: curve_segment, w: azimuthal_offset */
|
||||
EXPECT_EQ(result_idx[0], int4(1, 0, 1, -1));
|
||||
EXPECT_EQ(result_idx[1], int4(0, 0, 0, -1));
|
||||
EXPECT_EQ(result_idx[2], int4(1, 0, 1, 0));
|
||||
EXPECT_EQ(result_idx[3], int4(0, 0, 0, 0));
|
||||
EXPECT_EQ(result_idx[4], int4(1, 0, 1, 1));
|
||||
EXPECT_EQ(result_idx[5], int4(0, 0, 0, 1));
|
||||
EXPECT_EQ(result_idx[6], int4(0, 0, 0, 2));
|
||||
|
||||
EXPECT_EQ(result_idx[7], int4(1, 0, 1, -1));
|
||||
EXPECT_EQ(result_idx[8], int4(2, 0, 2, -1));
|
||||
EXPECT_EQ(result_idx[9], int4(1, 0, 1, 0));
|
||||
EXPECT_EQ(result_idx[10], int4(2, 0, 2, 0));
|
||||
EXPECT_EQ(result_idx[11], int4(1, 0, 1, 1));
|
||||
EXPECT_EQ(result_idx[12], int4(2, 0, 2, 1));
|
||||
EXPECT_EQ(result_idx[13], int4(1, 0, 1, 2));
|
||||
|
||||
EXPECT_EQ(result_idx[14], int4(3, 0, 3, -1));
|
||||
EXPECT_EQ(result_idx[15], int4(2, 0, 2, -1));
|
||||
EXPECT_EQ(result_idx[16], int4(3, 0, 3, 0));
|
||||
EXPECT_EQ(result_idx[17], int4(2, 0, 2, 0));
|
||||
EXPECT_EQ(result_idx[18], int4(3, 0, 3, 1));
|
||||
EXPECT_EQ(result_idx[19], int4(2, 0, 2, 1));
|
||||
EXPECT_EQ(result_idx[20], int4(2, 0, 2, 2));
|
||||
|
||||
EXPECT_EQ(result_idx[21], int4(3, 0, 3, -1));
|
||||
EXPECT_EQ(result_idx[22], int4(4, 0, 4, -1));
|
||||
EXPECT_EQ(result_idx[23], int4(3, 0, 3, 0));
|
||||
EXPECT_EQ(result_idx[24], int4(4, 0, 4, 0));
|
||||
EXPECT_EQ(result_idx[25], int4(3, 0, 3, 1));
|
||||
EXPECT_EQ(result_idx[26], int4(4, 0, 4, 1));
|
||||
EXPECT_EQ(result_idx[27], int4(3, 0, 3, 2));
|
||||
|
||||
EXPECT_EQ(result_idx[28], int4(6, 1, 1, -1));
|
||||
EXPECT_EQ(result_idx[29], int4(5, 1, 0, -1));
|
||||
EXPECT_EQ(result_idx[30], int4(6, 1, 1, 0));
|
||||
EXPECT_EQ(result_idx[31], int4(5, 1, 0, 0));
|
||||
EXPECT_EQ(result_idx[32], int4(6, 1, 1, 1));
|
||||
EXPECT_EQ(result_idx[33], int4(5, 1, 0, 1));
|
||||
EXPECT_EQ(result_idx[34], int4(5, 1, 0, 2));
|
||||
|
||||
EXPECT_EQ(result_idx[35], int4(6, 1, 1, -1));
|
||||
EXPECT_EQ(result_idx[36], int4(7, 1, 2, -1));
|
||||
EXPECT_EQ(result_idx[37], int4(6, 1, 1, 0));
|
||||
EXPECT_EQ(result_idx[38], int4(7, 1, 2, 0));
|
||||
EXPECT_EQ(result_idx[39], int4(6, 1, 1, 1));
|
||||
EXPECT_EQ(result_idx[40], int4(7, 1, 2, 1));
|
||||
EXPECT_EQ(result_idx[41], int4(6, 1, 1, 2));
|
||||
}
|
||||
|
||||
GPU_shader_unbind();
|
||||
|
||||
GPU_SHADER_FREE_SAFE(sh);
|
||||
GPU_BATCH_DISCARD_SAFE(batch_ribbon);
|
||||
GPU_BATCH_DISCARD_SAFE(batch_cylinder);
|
||||
GPU_VERTBUF_DISCARD_SAFE(indirection_ribbon_buf);
|
||||
GPU_VERTBUF_DISCARD_SAFE(indirection_cylinder_buf);
|
||||
GPU_VERTBUF_DISCARD_SAFE(pos_rad_buf);
|
||||
}
|
||||
DRAW_TEST(draw_curves_lib)
|
||||
|
||||
static void test_draw_curves_topology()
|
||||
{
|
||||
Manager manager;
|
||||
|
||||
gpu::Shader *sh = GPU_shader_create_from_info_name("draw_curves_topology");
|
||||
|
||||
struct IntBuf {
|
||||
int data;
|
||||
GPU_VERTEX_FORMAT_FUNC(IntBuf, data);
|
||||
};
|
||||
gpu::VertBuf *curve_offsets_buf = GPU_vertbuf_create_with_format(IntBuf::format());
|
||||
curve_offsets_buf->allocate(4);
|
||||
curve_offsets_buf->data<int>().copy_from({0, 5, 8, 10});
|
||||
|
||||
{
|
||||
StorageArrayBuffer<int, 512> indirection_buf;
|
||||
indirection_buf.clear_to_zero();
|
||||
|
||||
PassSimple pass("Ribbon Curves");
|
||||
pass.init();
|
||||
pass.shader_set(sh);
|
||||
pass.bind_ssbo("evaluated_offsets_buf", curve_offsets_buf);
|
||||
pass.bind_ssbo("curves_cyclic_buf", curve_offsets_buf);
|
||||
pass.bind_ssbo("indirection_buf", indirection_buf);
|
||||
pass.push_constant("curves_start", 0);
|
||||
pass.push_constant("curves_count", 3);
|
||||
pass.push_constant("is_ribbon_topology", true);
|
||||
pass.push_constant("use_cyclic", false);
|
||||
pass.dispatch(1);
|
||||
pass.barrier(GPU_BARRIER_BUFFER_UPDATE);
|
||||
|
||||
manager.submit(pass);
|
||||
|
||||
/* Note: Expected values follows diagram shown in #142969. */
|
||||
indirection_buf.read();
|
||||
|
||||
EXPECT_EQ(indirection_buf[0], 0);
|
||||
EXPECT_EQ(indirection_buf[1], -1);
|
||||
EXPECT_EQ(indirection_buf[2], -2);
|
||||
EXPECT_EQ(indirection_buf[3], -3);
|
||||
EXPECT_EQ(indirection_buf[4], -4);
|
||||
EXPECT_EQ(indirection_buf[5], 0x7FFFFFFF);
|
||||
EXPECT_EQ(indirection_buf[6], 1);
|
||||
EXPECT_EQ(indirection_buf[7], -1);
|
||||
EXPECT_EQ(indirection_buf[8], -2);
|
||||
EXPECT_EQ(indirection_buf[9], 0x7FFFFFFF);
|
||||
EXPECT_EQ(indirection_buf[10], 2);
|
||||
EXPECT_EQ(indirection_buf[11], -1);
|
||||
EXPECT_EQ(indirection_buf[12], 0x7FFFFFFF);
|
||||
/* Ensure the rest of the buffer is untouched. */
|
||||
EXPECT_EQ(indirection_buf[13], 0);
|
||||
EXPECT_EQ(indirection_buf[14], 0);
|
||||
}
|
||||
|
||||
{
|
||||
StorageArrayBuffer<int, 512> indirection_buf;
|
||||
indirection_buf.clear_to_zero();
|
||||
|
||||
PassSimple pass("Cylinder Curves");
|
||||
pass.init();
|
||||
pass.shader_set(sh);
|
||||
pass.bind_ssbo("evaluated_offsets_buf", curve_offsets_buf);
|
||||
pass.bind_ssbo("curves_cyclic_buf", curve_offsets_buf);
|
||||
pass.bind_ssbo("indirection_buf", indirection_buf);
|
||||
pass.push_constant("curves_start", 0);
|
||||
pass.push_constant("curves_count", 3);
|
||||
pass.push_constant("is_ribbon_topology", false);
|
||||
pass.push_constant("use_cyclic", false);
|
||||
pass.dispatch(1);
|
||||
pass.barrier(GPU_BARRIER_BUFFER_UPDATE);
|
||||
|
||||
manager.submit(pass);
|
||||
|
||||
/* Note: Expected values follows diagram shown in #142969. */
|
||||
indirection_buf.read();
|
||||
|
||||
EXPECT_EQ(indirection_buf[0], 0);
|
||||
EXPECT_EQ(indirection_buf[1], -1);
|
||||
EXPECT_EQ(indirection_buf[2], -2);
|
||||
EXPECT_EQ(indirection_buf[3], -3);
|
||||
EXPECT_EQ(indirection_buf[4], 1);
|
||||
EXPECT_EQ(indirection_buf[5], -1);
|
||||
EXPECT_EQ(indirection_buf[6], 2);
|
||||
/* Ensure the rest of the buffer is untouched. */
|
||||
EXPECT_EQ(indirection_buf[7], 0);
|
||||
EXPECT_EQ(indirection_buf[8], 0);
|
||||
}
|
||||
|
||||
GPU_shader_unbind();
|
||||
|
||||
GPU_SHADER_FREE_SAFE(sh);
|
||||
GPU_VERTBUF_DISCARD_SAFE(curve_offsets_buf);
|
||||
}
|
||||
DRAW_TEST(draw_curves_topology)
|
||||
|
||||
static void test_draw_curves_interpolate_position()
|
||||
{
|
||||
Manager manager;
|
||||
|
||||
gpu::Shader *sh = GPU_shader_create_from_info_name("draw_curves_interpolate_position");
|
||||
gpu::Shader *sh_length = GPU_shader_create_from_info_name(
|
||||
"draw_curves_evaluate_length_intercept");
|
||||
|
||||
const int curve_resolution = 2;
|
||||
|
||||
const Vector<int> evaluated_offsets = {0, 5, 8};
|
||||
|
||||
struct IntBuf {
|
||||
int data;
|
||||
GPU_VERTEX_FORMAT_FUNC(IntBuf, data);
|
||||
};
|
||||
gpu::VertBuf *points_by_curve_buf = GPU_vertbuf_create_with_format(IntBuf::format());
|
||||
points_by_curve_buf->allocate(3);
|
||||
points_by_curve_buf->data<int>().copy_from({0, 3, 5});
|
||||
|
||||
gpu::VertBuf *curves_type_buf = GPU_vertbuf_create_with_format(IntBuf::format());
|
||||
curves_type_buf->allocate(1);
|
||||
curves_type_buf->data<char>().copy_from({CURVE_TYPE_CATMULL_ROM, CURVE_TYPE_CATMULL_ROM, 0, 0});
|
||||
|
||||
gpu::VertBuf *curves_resolution_buf = GPU_vertbuf_create_with_format(IntBuf::format());
|
||||
curves_resolution_buf->allocate(2);
|
||||
curves_resolution_buf->data<int>().copy_from({curve_resolution, curve_resolution});
|
||||
|
||||
gpu::VertBuf *evaluated_points_by_curve_buf = GPU_vertbuf_create_with_format(IntBuf::format());
|
||||
evaluated_points_by_curve_buf->allocate(3);
|
||||
evaluated_points_by_curve_buf->data<int>().copy_from(evaluated_offsets);
|
||||
|
||||
const Vector<float> points_radius = {1.0f, 0.5f, 0.0f, 0.0f, 2.0f};
|
||||
const Vector<float3> positions = {
|
||||
float3{1.0f}, float3{0.5f}, float3{0.0f}, float3{0.0f}, float3{2.0f}};
|
||||
|
||||
struct Position {
|
||||
float3 pos;
|
||||
GPU_VERTEX_FORMAT_FUNC(Position, pos);
|
||||
};
|
||||
gpu::VertBuf *positions_buf = GPU_vertbuf_create_with_format_ex(
|
||||
Position::format(), GPU_USAGE_FLAG_BUFFER_TEXTURE_ONLY);
|
||||
positions_buf->allocate(positions.size());
|
||||
positions_buf->data<float3>().copy_from(positions);
|
||||
|
||||
struct Radius {
|
||||
float rad;
|
||||
GPU_VERTEX_FORMAT_FUNC(Radius, rad);
|
||||
};
|
||||
gpu::VertBuf *radii_buf = GPU_vertbuf_create_with_format_ex(Radius::format(),
|
||||
GPU_USAGE_FLAG_BUFFER_TEXTURE_ONLY);
|
||||
radii_buf->allocate(points_radius.size());
|
||||
radii_buf->data<float>().copy_from(points_radius);
|
||||
|
||||
{
|
||||
StorageArrayBuffer<float4, 512> evaluated_positions_radii_buf;
|
||||
StorageArrayBuffer<float, 512> evaluated_time_buf;
|
||||
StorageArrayBuffer<float, 512> curves_length_buf;
|
||||
evaluated_positions_radii_buf.clear_to_zero();
|
||||
evaluated_time_buf.clear_to_zero();
|
||||
curves_length_buf.clear_to_zero();
|
||||
|
||||
PassSimple pass("Curves Interpolation Catmull Rom");
|
||||
pass.init();
|
||||
pass.specialize_constant(sh, "evaluated_type", int(CURVE_TYPE_CATMULL_ROM));
|
||||
pass.shader_set(sh);
|
||||
pass.bind_ssbo(POINTS_BY_CURVES_SLOT, points_by_curve_buf);
|
||||
pass.bind_ssbo(CURVE_TYPE_SLOT, curves_type_buf);
|
||||
pass.bind_ssbo(CURVE_RESOLUTION_SLOT, curves_resolution_buf);
|
||||
pass.bind_ssbo(EVALUATED_POINT_SLOT, evaluated_points_by_curve_buf);
|
||||
pass.bind_ssbo(POINT_POSITIONS_SLOT, positions_buf);
|
||||
pass.bind_ssbo(POINT_RADII_SLOT, radii_buf);
|
||||
pass.bind_ssbo(EVALUATED_POS_RAD_SLOT, evaluated_positions_radii_buf);
|
||||
pass.push_constant("use_cyclic", false);
|
||||
pass.bind_ssbo(CURVE_CYCLIC_SLOT, evaluated_points_by_curve_buf); /* Dummy, not used. */
|
||||
/* Dummy, not used for Catmull-Rom. */
|
||||
pass.bind_ssbo(HANDLES_POS_LEFT_SLOT, evaluated_points_by_curve_buf);
|
||||
pass.bind_ssbo(HANDLES_POS_RIGHT_SLOT, evaluated_points_by_curve_buf);
|
||||
pass.bind_ssbo(BEZIER_OFFSETS_SLOT, evaluated_points_by_curve_buf);
|
||||
pass.push_constant("curves_start", 0);
|
||||
pass.push_constant("curves_count", 2);
|
||||
pass.push_constant("transform", float4x4::identity());
|
||||
pass.dispatch(1);
|
||||
pass.barrier(GPU_BARRIER_SHADER_STORAGE);
|
||||
pass.shader_set(sh_length);
|
||||
pass.bind_ssbo(EVALUATED_POINT_SLOT, evaluated_points_by_curve_buf);
|
||||
pass.bind_ssbo(EVALUATED_POS_RAD_SLOT, evaluated_positions_radii_buf);
|
||||
pass.bind_ssbo(EVALUATED_TIME_SLOT, evaluated_time_buf);
|
||||
pass.bind_ssbo(CURVES_LENGTH_SLOT, curves_length_buf);
|
||||
pass.push_constant("curves_start", 0);
|
||||
pass.push_constant("curves_count", 2);
|
||||
pass.push_constant("use_cyclic", false);
|
||||
pass.dispatch(1);
|
||||
pass.barrier(GPU_BARRIER_BUFFER_UPDATE);
|
||||
|
||||
manager.submit(pass);
|
||||
|
||||
evaluated_positions_radii_buf.read();
|
||||
evaluated_time_buf.read();
|
||||
curves_length_buf.read();
|
||||
|
||||
Vector<float> interp_data;
|
||||
interp_data.resize(8);
|
||||
|
||||
bke::curves::catmull_rom::interpolate_to_evaluated(
|
||||
GSpan(points_radius.as_span().slice(0, 3)),
|
||||
false,
|
||||
curve_resolution,
|
||||
GMutableSpan(interp_data.as_mutable_span().slice(0, 5)));
|
||||
|
||||
bke::curves::catmull_rom::interpolate_to_evaluated(
|
||||
GSpan(points_radius.as_span().slice(3, 2)),
|
||||
false,
|
||||
curve_resolution,
|
||||
GMutableSpan(interp_data.as_mutable_span().slice(5, 3)));
|
||||
|
||||
EXPECT_EQ(evaluated_positions_radii_buf[0], float4(interp_data[0]));
|
||||
EXPECT_EQ(evaluated_positions_radii_buf[1], float4(interp_data[1]));
|
||||
EXPECT_EQ(evaluated_positions_radii_buf[2], float4(interp_data[2]));
|
||||
EXPECT_EQ(evaluated_positions_radii_buf[3], float4(interp_data[3]));
|
||||
EXPECT_EQ(evaluated_positions_radii_buf[4], float4(interp_data[4]));
|
||||
EXPECT_EQ(evaluated_positions_radii_buf[5], float4(interp_data[5]));
|
||||
EXPECT_EQ(evaluated_positions_radii_buf[6], float4(interp_data[6]));
|
||||
EXPECT_EQ(evaluated_positions_radii_buf[7], float4(interp_data[7]));
|
||||
/* Ensure the rest of the buffer is untouched. */
|
||||
EXPECT_EQ(evaluated_positions_radii_buf[8], float4(0.0));
|
||||
|
||||
EXPECT_FLOAT_EQ(curves_length_buf[0], numbers::sqrt3);
|
||||
EXPECT_FLOAT_EQ(curves_length_buf[1], 2.0f * numbers::sqrt3);
|
||||
|
||||
EXPECT_FLOAT_EQ(evaluated_time_buf[0], 0.0f);
|
||||
EXPECT_FLOAT_EQ(evaluated_time_buf[1], 0.218749985f);
|
||||
EXPECT_FLOAT_EQ(evaluated_time_buf[2], 0.5f);
|
||||
EXPECT_FLOAT_EQ(evaluated_time_buf[3], 0.78125f);
|
||||
EXPECT_FLOAT_EQ(evaluated_time_buf[4], 1.0f);
|
||||
EXPECT_FLOAT_EQ(evaluated_time_buf[5], 0.0f);
|
||||
EXPECT_FLOAT_EQ(evaluated_time_buf[6], 0.5f);
|
||||
EXPECT_FLOAT_EQ(evaluated_time_buf[7], 1.0f);
|
||||
/* Ensure the rest of the buffer is untouched. */
|
||||
EXPECT_EQ(evaluated_time_buf[8], 0.0f);
|
||||
}
|
||||
|
||||
const Vector<float3> handle_pos_left = {
|
||||
float3{0.0f}, float3{1.0f}, float3{-1.0f}, float3{1.0f}, float3{4.0f}};
|
||||
const Vector<float3> handle_pos_right = {
|
||||
float3{0.0f}, float3{-1.0f}, float3{1.0f}, float3{-1.0f}, float3{0.0f}};
|
||||
|
||||
gpu::VertBuf *handles_positions_left_buf = GPU_vertbuf_create_with_format_ex(
|
||||
Position::format(), GPU_USAGE_FLAG_BUFFER_TEXTURE_ONLY);
|
||||
handles_positions_left_buf->allocate(handle_pos_left.size());
|
||||
handles_positions_left_buf->data<float3>().copy_from(handle_pos_left);
|
||||
|
||||
gpu::VertBuf *handles_positions_right_buf = GPU_vertbuf_create_with_format_ex(
|
||||
Position::format(), GPU_USAGE_FLAG_BUFFER_TEXTURE_ONLY);
|
||||
handles_positions_right_buf->allocate(handle_pos_right.size());
|
||||
handles_positions_right_buf->data<float3>().copy_from(handle_pos_right);
|
||||
|
||||
const Vector<int> bezier_offsets = {0, 2, 4, 5, 0, 2, 3};
|
||||
|
||||
gpu::VertBuf *bezier_offsets_buf = GPU_vertbuf_create_with_format_ex(
|
||||
IntBuf::format(), GPU_USAGE_FLAG_BUFFER_TEXTURE_ONLY);
|
||||
bezier_offsets_buf->allocate(bezier_offsets.size());
|
||||
bezier_offsets_buf->data<int>().copy_from(bezier_offsets);
|
||||
|
||||
gpu::VertBuf *curves_type_bezier_buf = GPU_vertbuf_create_with_format(IntBuf::format());
|
||||
curves_type_bezier_buf->allocate(1);
|
||||
curves_type_bezier_buf->data<char>().copy_from({CURVE_TYPE_BEZIER, CURVE_TYPE_BEZIER, 0, 0});
|
||||
|
||||
{
|
||||
StorageArrayBuffer<float4, 512> evaluated_positions_radii_buf;
|
||||
StorageArrayBuffer<float, 512> evaluated_time_buf;
|
||||
StorageArrayBuffer<float, 512> curves_length_buf;
|
||||
evaluated_positions_radii_buf.clear_to_zero();
|
||||
evaluated_time_buf.clear_to_zero();
|
||||
curves_length_buf.clear_to_zero();
|
||||
|
||||
PassSimple pass("Curves Interpolation Bezier");
|
||||
pass.init();
|
||||
pass.specialize_constant(sh, "evaluated_type", int(CURVE_TYPE_BEZIER));
|
||||
pass.shader_set(sh);
|
||||
pass.bind_ssbo(POINTS_BY_CURVES_SLOT, points_by_curve_buf);
|
||||
pass.bind_ssbo(CURVE_TYPE_SLOT, curves_type_bezier_buf);
|
||||
pass.bind_ssbo(CURVE_RESOLUTION_SLOT, curves_resolution_buf);
|
||||
pass.bind_ssbo(EVALUATED_POINT_SLOT, evaluated_points_by_curve_buf);
|
||||
pass.bind_ssbo(POINT_POSITIONS_SLOT, positions_buf);
|
||||
pass.bind_ssbo(POINT_RADII_SLOT, radii_buf);
|
||||
pass.bind_ssbo(EVALUATED_POS_RAD_SLOT, evaluated_positions_radii_buf);
|
||||
pass.push_constant("use_cyclic", false);
|
||||
pass.bind_ssbo(CURVE_CYCLIC_SLOT, evaluated_points_by_curve_buf); /* Dummy, not used. */
|
||||
pass.bind_ssbo(HANDLES_POS_LEFT_SLOT, handles_positions_left_buf);
|
||||
pass.bind_ssbo(HANDLES_POS_RIGHT_SLOT, handles_positions_right_buf);
|
||||
pass.bind_ssbo(BEZIER_OFFSETS_SLOT, bezier_offsets_buf);
|
||||
pass.push_constant("curves_start", 0);
|
||||
pass.push_constant("curves_count", 2);
|
||||
pass.push_constant("transform", float4x4::identity());
|
||||
pass.dispatch(1);
|
||||
pass.barrier(GPU_BARRIER_SHADER_STORAGE);
|
||||
pass.shader_set(sh_length);
|
||||
pass.bind_ssbo(EVALUATED_POINT_SLOT, evaluated_points_by_curve_buf);
|
||||
pass.bind_ssbo(EVALUATED_POS_RAD_SLOT, evaluated_positions_radii_buf);
|
||||
pass.bind_ssbo(EVALUATED_TIME_SLOT, evaluated_time_buf);
|
||||
pass.bind_ssbo(CURVES_LENGTH_SLOT, curves_length_buf);
|
||||
pass.push_constant("curves_start", 0);
|
||||
pass.push_constant("curves_count", 2);
|
||||
pass.push_constant("use_cyclic", false);
|
||||
pass.dispatch(1);
|
||||
pass.barrier(GPU_BARRIER_BUFFER_UPDATE);
|
||||
|
||||
manager.submit(pass);
|
||||
|
||||
evaluated_positions_radii_buf.read();
|
||||
evaluated_time_buf.read();
|
||||
curves_length_buf.read();
|
||||
|
||||
Vector<float3> interp_pos;
|
||||
interp_pos.resize(8);
|
||||
|
||||
Vector<float> interp_rad;
|
||||
interp_rad.resize(8);
|
||||
|
||||
{
|
||||
const int curve_index = 0;
|
||||
const IndexRange points(0, 3);
|
||||
const IndexRange evaluated_points(0, 5);
|
||||
const IndexRange offsets = bke::curves::per_curve_point_offsets_range(points, curve_index);
|
||||
|
||||
bke::curves::bezier::calculate_evaluated_positions(
|
||||
positions.as_span().slice(points),
|
||||
handle_pos_left.as_span().slice(points),
|
||||
handle_pos_right.as_span().slice(points),
|
||||
bezier_offsets.as_span().slice(offsets),
|
||||
interp_pos.as_mutable_span().slice(evaluated_points));
|
||||
|
||||
bke::curves::bezier::interpolate_to_evaluated(
|
||||
points_radius.as_span().slice(points),
|
||||
bezier_offsets.as_span().slice(offsets),
|
||||
interp_rad.as_mutable_span().slice(evaluated_points));
|
||||
}
|
||||
{
|
||||
const int curve_index = 1;
|
||||
const IndexRange points(3, 2);
|
||||
const IndexRange evaluated_points(5, 3);
|
||||
const IndexRange offsets = bke::curves::per_curve_point_offsets_range(points, curve_index);
|
||||
|
||||
bke::curves::bezier::calculate_evaluated_positions(
|
||||
positions.as_span().slice(points),
|
||||
handle_pos_left.as_span().slice(points),
|
||||
handle_pos_right.as_span().slice(points),
|
||||
bezier_offsets.as_span().slice(offsets),
|
||||
interp_pos.as_mutable_span().slice(evaluated_points));
|
||||
|
||||
bke::curves::bezier::interpolate_to_evaluated(
|
||||
points_radius.as_span().slice(points),
|
||||
bezier_offsets.as_span().slice(offsets),
|
||||
interp_rad.as_mutable_span().slice(evaluated_points));
|
||||
}
|
||||
|
||||
EXPECT_EQ(evaluated_positions_radii_buf[0], float4(interp_pos[0], interp_rad[0]));
|
||||
EXPECT_EQ(evaluated_positions_radii_buf[1], float4(interp_pos[1], interp_rad[1]));
|
||||
EXPECT_EQ(evaluated_positions_radii_buf[2], float4(interp_pos[2], interp_rad[2]));
|
||||
EXPECT_EQ(evaluated_positions_radii_buf[3], float4(interp_pos[3], interp_rad[3]));
|
||||
EXPECT_EQ(evaluated_positions_radii_buf[4], float4(interp_pos[4], interp_rad[4]));
|
||||
EXPECT_EQ(evaluated_positions_radii_buf[5], float4(interp_pos[5], interp_rad[5]));
|
||||
EXPECT_EQ(evaluated_positions_radii_buf[6], float4(interp_pos[6], interp_rad[6]));
|
||||
EXPECT_EQ(evaluated_positions_radii_buf[7], float4(interp_pos[7], interp_rad[7]));
|
||||
/* Ensure the rest of the buffer is untouched. */
|
||||
EXPECT_EQ(evaluated_positions_radii_buf[8], float4(0.0));
|
||||
|
||||
float curve_len[2] = {0.0f, 0.0f};
|
||||
Vector<float> interp_time{0.0f};
|
||||
interp_time.resize(8);
|
||||
interp_time[0] = 0.0f;
|
||||
for (int i : IndexRange(1, 4)) {
|
||||
curve_len[0] += math::distance(interp_pos[i], interp_pos[i - 1]);
|
||||
interp_time[i] = curve_len[0];
|
||||
}
|
||||
for (int i : IndexRange(1, 4)) {
|
||||
interp_time[i] /= curve_len[0];
|
||||
}
|
||||
interp_time[5] = 0.0f;
|
||||
for (int i : IndexRange(6, 2)) {
|
||||
curve_len[1] += math::distance(interp_pos[i], interp_pos[i - 1]);
|
||||
interp_time[i] = curve_len[1];
|
||||
}
|
||||
for (int i : IndexRange(6, 2)) {
|
||||
interp_time[i] /= curve_len[1];
|
||||
}
|
||||
|
||||
EXPECT_FLOAT_EQ(curves_length_buf[0], curve_len[0]);
|
||||
EXPECT_FLOAT_EQ(curves_length_buf[1], curve_len[1]);
|
||||
|
||||
EXPECT_FLOAT_EQ(evaluated_time_buf[0], interp_time[0]);
|
||||
EXPECT_FLOAT_EQ(evaluated_time_buf[1], interp_time[1]);
|
||||
EXPECT_FLOAT_EQ(evaluated_time_buf[2], interp_time[2]);
|
||||
EXPECT_FLOAT_EQ(evaluated_time_buf[3], interp_time[3]);
|
||||
EXPECT_FLOAT_EQ(evaluated_time_buf[4], interp_time[4]);
|
||||
EXPECT_FLOAT_EQ(evaluated_time_buf[5], interp_time[5]);
|
||||
EXPECT_FLOAT_EQ(evaluated_time_buf[6], interp_time[6]);
|
||||
EXPECT_FLOAT_EQ(evaluated_time_buf[7], interp_time[7]);
|
||||
/* Ensure the rest of the buffer is untouched. */
|
||||
EXPECT_EQ(evaluated_time_buf[8], 0.0f);
|
||||
}
|
||||
|
||||
const bke::curves::nurbs::BasisCache basis_cache_c0 = {
|
||||
{0.1f, 0.2f, 0.4f, 0.5f, 0.6f, 0.7f, 0.8f, 0.9f, 1.0f, 2.0f, 3.0f, 4.0f, 5.0f, 6.0f, 7.0f},
|
||||
{0, 0, 0, 0, 0},
|
||||
false,
|
||||
};
|
||||
const bke::curves::nurbs::BasisCache basis_cache_c1 = {
|
||||
{0.6f, 0.7f, 0.8f, 0.9f, 1.0f, 2.0f},
|
||||
{0, 0, 0},
|
||||
false,
|
||||
};
|
||||
|
||||
Vector<int> basis_cache_offset;
|
||||
Vector<uint32_t> basis_cache_packed;
|
||||
{
|
||||
basis_cache_offset.append(basis_cache_c0.invalid ? -1 : basis_cache_packed.size());
|
||||
basis_cache_packed.extend(
|
||||
Span{reinterpret_cast<const uint32_t *>(basis_cache_c0.start_indices.data()),
|
||||
basis_cache_c0.start_indices.size()});
|
||||
basis_cache_packed.extend(
|
||||
Span{reinterpret_cast<const uint32_t *>(basis_cache_c0.weights.data()),
|
||||
basis_cache_c0.weights.size()});
|
||||
|
||||
basis_cache_offset.append(basis_cache_c1.invalid ? -1 : basis_cache_packed.size());
|
||||
basis_cache_packed.extend(
|
||||
Span{reinterpret_cast<const uint32_t *>(basis_cache_c1.start_indices.data()),
|
||||
basis_cache_c1.start_indices.size()});
|
||||
basis_cache_packed.extend(
|
||||
Span{reinterpret_cast<const uint32_t *>(basis_cache_c1.weights.data()),
|
||||
basis_cache_c1.weights.size()});
|
||||
}
|
||||
|
||||
/* Raw data. Shader reinterpret as float or int. */
|
||||
gpu::VertBuf *basis_cache_buf = GPU_vertbuf_create_with_format_ex(
|
||||
IntBuf::format(), GPU_USAGE_FLAG_BUFFER_TEXTURE_ONLY);
|
||||
basis_cache_buf->allocate(basis_cache_packed.size());
|
||||
basis_cache_buf->data<uint>().copy_from(basis_cache_packed);
|
||||
|
||||
gpu::VertBuf *basis_cache_offset_buf = GPU_vertbuf_create_with_format_ex(
|
||||
IntBuf::format(), GPU_USAGE_FLAG_BUFFER_TEXTURE_ONLY);
|
||||
basis_cache_offset_buf->allocate(basis_cache_offset.size());
|
||||
basis_cache_offset_buf->data<int>().copy_from(basis_cache_offset);
|
||||
|
||||
const Vector<int8_t> curves_order = {3, 2, /* Padding. */ 0, 0};
|
||||
|
||||
gpu::VertBuf *curves_order_buf = GPU_vertbuf_create_with_format_ex(
|
||||
IntBuf::format(), GPU_USAGE_FLAG_BUFFER_TEXTURE_ONLY);
|
||||
curves_order_buf->allocate(curves_order.size() / 4);
|
||||
curves_order_buf->data<int>().copy_from(
|
||||
Span<int>(reinterpret_cast<const int *>(curves_order.data()), curves_order.size() / 4));
|
||||
|
||||
const Vector<float> control_weights = {1.0f, 2.0f, 3.0f, 4.0f, 5.0f};
|
||||
|
||||
gpu::VertBuf *control_weights_buf = GPU_vertbuf_create_with_format(Radius::format());
|
||||
control_weights_buf->allocate(control_weights.size());
|
||||
control_weights_buf->data<float>().copy_from(control_weights);
|
||||
|
||||
gpu::VertBuf *curves_type_nurbs_buf = GPU_vertbuf_create_with_format(IntBuf::format());
|
||||
curves_type_nurbs_buf->allocate(1);
|
||||
curves_type_nurbs_buf->data<char>().copy_from({CURVE_TYPE_NURBS, CURVE_TYPE_NURBS, 0, 0});
|
||||
|
||||
{
|
||||
StorageArrayBuffer<float4, 512> evaluated_positions_radii_buf;
|
||||
StorageArrayBuffer<float, 512> evaluated_time_buf;
|
||||
StorageArrayBuffer<float, 512> curves_length_buf;
|
||||
evaluated_positions_radii_buf.clear_to_zero();
|
||||
evaluated_time_buf.clear_to_zero();
|
||||
curves_length_buf.clear_to_zero();
|
||||
|
||||
PassSimple pass("Curves Interpolation Nurbs");
|
||||
pass.init();
|
||||
pass.specialize_constant(sh, "evaluated_type", int(CURVE_TYPE_NURBS));
|
||||
pass.shader_set(sh);
|
||||
pass.bind_ssbo(POINTS_BY_CURVES_SLOT, points_by_curve_buf);
|
||||
pass.bind_ssbo(CURVE_TYPE_SLOT, curves_type_nurbs_buf);
|
||||
pass.bind_ssbo(CURVE_RESOLUTION_SLOT, curves_resolution_buf);
|
||||
pass.bind_ssbo(EVALUATED_POINT_SLOT, evaluated_points_by_curve_buf);
|
||||
pass.bind_ssbo(POINT_POSITIONS_SLOT, positions_buf);
|
||||
pass.bind_ssbo(POINT_RADII_SLOT, radii_buf);
|
||||
pass.bind_ssbo(EVALUATED_POS_RAD_SLOT, evaluated_positions_radii_buf);
|
||||
pass.push_constant("use_cyclic", false);
|
||||
pass.bind_ssbo(CURVE_CYCLIC_SLOT, evaluated_points_by_curve_buf); /* Dummy, not used. */
|
||||
pass.bind_ssbo(CURVES_ORDER_SLOT, curves_order_buf);
|
||||
pass.bind_ssbo(BASIS_CACHE_SLOT, basis_cache_buf);
|
||||
pass.bind_ssbo(CONTROL_WEIGHTS_SLOT, control_weights_buf);
|
||||
pass.bind_ssbo(BASIS_CACHE_OFFSET_SLOT, basis_cache_offset_buf);
|
||||
pass.push_constant("curves_start", 0);
|
||||
pass.push_constant("curves_count", 2);
|
||||
pass.push_constant("use_point_weight", true);
|
||||
pass.push_constant("transform", float4x4::identity());
|
||||
pass.dispatch(1);
|
||||
pass.barrier(GPU_BARRIER_SHADER_STORAGE);
|
||||
pass.shader_set(sh_length);
|
||||
pass.bind_ssbo(EVALUATED_POINT_SLOT, evaluated_points_by_curve_buf);
|
||||
pass.bind_ssbo(EVALUATED_POS_RAD_SLOT, evaluated_positions_radii_buf);
|
||||
pass.bind_ssbo(EVALUATED_TIME_SLOT, evaluated_time_buf);
|
||||
pass.bind_ssbo(CURVES_LENGTH_SLOT, curves_length_buf);
|
||||
pass.push_constant("curves_start", 0);
|
||||
pass.push_constant("curves_count", 2);
|
||||
pass.push_constant("use_cyclic", false);
|
||||
pass.dispatch(1);
|
||||
pass.barrier(GPU_BARRIER_BUFFER_UPDATE);
|
||||
|
||||
manager.submit(pass);
|
||||
|
||||
evaluated_positions_radii_buf.read();
|
||||
evaluated_time_buf.read();
|
||||
curves_length_buf.read();
|
||||
|
||||
Vector<float3> interp_pos;
|
||||
interp_pos.resize(8);
|
||||
|
||||
Vector<float> interp_rad;
|
||||
interp_rad.resize(8);
|
||||
|
||||
{
|
||||
const int curve_index = 0;
|
||||
const IndexRange points(0, 3);
|
||||
const IndexRange evaluated_points(0, 5);
|
||||
|
||||
bke::curves::nurbs::interpolate_to_evaluated(
|
||||
basis_cache_c0,
|
||||
curves_order[curve_index],
|
||||
control_weights.as_span().slice(points),
|
||||
positions.as_span().slice(points),
|
||||
interp_pos.as_mutable_span().slice(evaluated_points));
|
||||
|
||||
bke::curves::nurbs::interpolate_to_evaluated(
|
||||
basis_cache_c0,
|
||||
curves_order[curve_index],
|
||||
control_weights.as_span().slice(points),
|
||||
points_radius.as_span().slice(points),
|
||||
interp_rad.as_mutable_span().slice(evaluated_points));
|
||||
}
|
||||
{
|
||||
const int curve_index = 1;
|
||||
const IndexRange points(3, 2);
|
||||
const IndexRange evaluated_points(5, 3);
|
||||
|
||||
bke::curves::nurbs::interpolate_to_evaluated(
|
||||
basis_cache_c1,
|
||||
curves_order[curve_index],
|
||||
control_weights.as_span().slice(points),
|
||||
positions.as_span().slice(points),
|
||||
interp_pos.as_mutable_span().slice(evaluated_points));
|
||||
|
||||
bke::curves::nurbs::interpolate_to_evaluated(
|
||||
basis_cache_c1,
|
||||
curves_order[curve_index],
|
||||
control_weights.as_span().slice(points),
|
||||
points_radius.as_span().slice(points),
|
||||
interp_rad.as_mutable_span().slice(evaluated_points));
|
||||
}
|
||||
|
||||
EXPECT_NEAR(evaluated_positions_radii_buf[0].x, interp_pos[0].x, 0.000001f);
|
||||
EXPECT_NEAR(evaluated_positions_radii_buf[1].x, interp_pos[1].x, 0.000001f);
|
||||
EXPECT_NEAR(evaluated_positions_radii_buf[2].x, interp_pos[2].x, 0.000001f);
|
||||
EXPECT_NEAR(evaluated_positions_radii_buf[3].x, interp_pos[3].x, 0.000001f);
|
||||
EXPECT_NEAR(evaluated_positions_radii_buf[4].x, interp_pos[4].x, 0.000001f);
|
||||
EXPECT_NEAR(evaluated_positions_radii_buf[5].x, interp_pos[5].x, 0.000001f);
|
||||
EXPECT_NEAR(evaluated_positions_radii_buf[6].x, interp_pos[6].x, 0.000001f);
|
||||
EXPECT_NEAR(evaluated_positions_radii_buf[7].x, interp_pos[7].x, 0.000001f);
|
||||
|
||||
EXPECT_NEAR(evaluated_positions_radii_buf[0].w, interp_rad[0], 0.000001f);
|
||||
EXPECT_NEAR(evaluated_positions_radii_buf[1].w, interp_rad[1], 0.000001f);
|
||||
EXPECT_NEAR(evaluated_positions_radii_buf[2].w, interp_rad[2], 0.000001f);
|
||||
EXPECT_NEAR(evaluated_positions_radii_buf[3].w, interp_rad[3], 0.000001f);
|
||||
EXPECT_NEAR(evaluated_positions_radii_buf[4].w, interp_rad[4], 0.000001f);
|
||||
EXPECT_NEAR(evaluated_positions_radii_buf[5].w, interp_rad[5], 0.000001f);
|
||||
EXPECT_NEAR(evaluated_positions_radii_buf[6].w, interp_rad[6], 0.000001f);
|
||||
EXPECT_NEAR(evaluated_positions_radii_buf[7].w, interp_rad[7], 0.000001f);
|
||||
/* Ensure the rest of the buffer is untouched. */
|
||||
EXPECT_EQ(evaluated_positions_radii_buf[8], float4(0.0));
|
||||
|
||||
float curve_len[2] = {0.0f, 0.0f};
|
||||
Vector<float> interp_time{0.0f};
|
||||
interp_time.resize(8);
|
||||
interp_time[0] = 0.0f;
|
||||
for (int i : IndexRange(1, 4)) {
|
||||
curve_len[0] += math::distance(interp_pos[i], interp_pos[i - 1]);
|
||||
interp_time[i] = curve_len[0];
|
||||
}
|
||||
for (int i : IndexRange(1, 4)) {
|
||||
interp_time[i] /= curve_len[0];
|
||||
}
|
||||
interp_time[5] = 0.0f;
|
||||
for (int i : IndexRange(6, 2)) {
|
||||
curve_len[1] += math::distance(interp_pos[i], interp_pos[i - 1]);
|
||||
interp_time[i] = curve_len[1];
|
||||
}
|
||||
for (int i : IndexRange(6, 2)) {
|
||||
interp_time[i] /= curve_len[1];
|
||||
}
|
||||
|
||||
EXPECT_NEAR(curves_length_buf[0], curve_len[0], 0.000001f);
|
||||
EXPECT_NEAR(curves_length_buf[1], curve_len[1], 0.000001f);
|
||||
|
||||
EXPECT_EQ(evaluated_time_buf[0], interp_time[0]);
|
||||
EXPECT_NEAR(evaluated_time_buf[1], interp_time[1], 0.000001f);
|
||||
EXPECT_NEAR(evaluated_time_buf[2], interp_time[2], 0.000001f);
|
||||
EXPECT_NEAR(evaluated_time_buf[3], interp_time[3], 0.000001f);
|
||||
EXPECT_NEAR(evaluated_time_buf[4], interp_time[4], 0.000001f);
|
||||
EXPECT_EQ(evaluated_time_buf[5], interp_time[5]);
|
||||
EXPECT_NEAR(evaluated_time_buf[6], interp_time[6], 0.000001f);
|
||||
EXPECT_NEAR(evaluated_time_buf[7], interp_time[7], 0.000001f);
|
||||
/* Ensure the rest of the buffer is untouched. */
|
||||
EXPECT_EQ(evaluated_time_buf[8], 0.0f);
|
||||
}
|
||||
|
||||
GPU_shader_unbind();
|
||||
|
||||
GPU_SHADER_FREE_SAFE(sh);
|
||||
GPU_SHADER_FREE_SAFE(sh_length);
|
||||
GPU_VERTBUF_DISCARD_SAFE(points_by_curve_buf);
|
||||
GPU_VERTBUF_DISCARD_SAFE(curves_type_buf);
|
||||
GPU_VERTBUF_DISCARD_SAFE(curves_type_bezier_buf);
|
||||
GPU_VERTBUF_DISCARD_SAFE(curves_type_nurbs_buf);
|
||||
GPU_VERTBUF_DISCARD_SAFE(curves_resolution_buf);
|
||||
GPU_VERTBUF_DISCARD_SAFE(evaluated_points_by_curve_buf);
|
||||
GPU_VERTBUF_DISCARD_SAFE(positions_buf);
|
||||
GPU_VERTBUF_DISCARD_SAFE(radii_buf);
|
||||
GPU_VERTBUF_DISCARD_SAFE(handles_positions_left_buf);
|
||||
GPU_VERTBUF_DISCARD_SAFE(handles_positions_right_buf);
|
||||
GPU_VERTBUF_DISCARD_SAFE(bezier_offsets_buf);
|
||||
GPU_VERTBUF_DISCARD_SAFE(basis_cache_buf);
|
||||
GPU_VERTBUF_DISCARD_SAFE(basis_cache_offset_buf);
|
||||
GPU_VERTBUF_DISCARD_SAFE(curves_order_buf);
|
||||
GPU_VERTBUF_DISCARD_SAFE(control_weights_buf);
|
||||
}
|
||||
DRAW_TEST(draw_curves_interpolate_position)
|
||||
|
||||
static void test_draw_curves_interpolate_attributes()
|
||||
{
|
||||
Manager manager;
|
||||
|
||||
const int curve_resolution = 2;
|
||||
|
||||
const Vector<int> curves_to_point = {0, 3, 5, 7};
|
||||
const Vector<int> evaluated_offsets = {0, 5, 8, 11};
|
||||
|
||||
struct IntBuf {
|
||||
int data;
|
||||
GPU_VERTEX_FORMAT_FUNC(IntBuf, data);
|
||||
};
|
||||
gpu::VertBuf *points_by_curve_buf = GPU_vertbuf_create_with_format(IntBuf::format());
|
||||
points_by_curve_buf->allocate(curves_to_point.size());
|
||||
points_by_curve_buf->data<int>().copy_from(curves_to_point);
|
||||
|
||||
gpu::VertBuf *curves_type_buf = GPU_vertbuf_create_with_format(IntBuf::format());
|
||||
curves_type_buf->allocate(1);
|
||||
curves_type_buf->data<char>().copy_from(
|
||||
{CURVE_TYPE_NURBS, CURVE_TYPE_BEZIER, CURVE_TYPE_CATMULL_ROM, 0});
|
||||
|
||||
gpu::VertBuf *curves_resolution_buf = GPU_vertbuf_create_with_format(IntBuf::format());
|
||||
curves_resolution_buf->allocate(3);
|
||||
curves_resolution_buf->data<int>().copy_from(
|
||||
{curve_resolution, curve_resolution, curve_resolution});
|
||||
|
||||
gpu::VertBuf *evaluated_points_by_curve_buf = GPU_vertbuf_create_with_format(IntBuf::format());
|
||||
evaluated_points_by_curve_buf->allocate(evaluated_offsets.size());
|
||||
evaluated_points_by_curve_buf->data<int>().copy_from(evaluated_offsets);
|
||||
|
||||
/* Attributes. */
|
||||
|
||||
const Vector<float4> attr_float4 = {float4(1.0f, 0.5f, 0.0f, 0.5f),
|
||||
float4(0.5f, 0.0f, 0.0f, 4.0f),
|
||||
float4(0.0f, 0.0f, 2.0f, 4.0f),
|
||||
float4(2.0f, 3.0f, 4.0f, 7.0f),
|
||||
float4(3.0f, 4.0f, 3.0f, 4.0f),
|
||||
float4(2.0f, 2.0f, 3.0f, 4.0f),
|
||||
float4(4.0f, 5.0f, 6.0f, 7.0f)};
|
||||
const Vector<float3> attr_float3 =
|
||||
attr_float4.as_span().cast<float>().take_front(attr_float4.size() * 3).cast<float3>();
|
||||
const Vector<float2> attr_float2 =
|
||||
attr_float4.as_span().cast<float>().take_front(attr_float4.size() * 2).cast<float2>();
|
||||
const Vector<float> attr_float = attr_float4.as_span().cast<float>().take_front(
|
||||
attr_float4.size());
|
||||
|
||||
struct Float4 {
|
||||
float4 value;
|
||||
GPU_VERTEX_FORMAT_FUNC(Float4, value);
|
||||
};
|
||||
gpu::VertBuf *attribute_float4_buf = GPU_vertbuf_create_with_format(Float4::format());
|
||||
attribute_float4_buf->allocate(attr_float4.size());
|
||||
attribute_float4_buf->data<float4>().copy_from(attr_float4);
|
||||
|
||||
struct Float3 {
|
||||
float3 value;
|
||||
GPU_VERTEX_FORMAT_FUNC(Float3, value);
|
||||
};
|
||||
gpu::VertBuf *attribute_float3_buf = GPU_vertbuf_create_with_format(Float3::format());
|
||||
attribute_float3_buf->allocate(attr_float4.size());
|
||||
attribute_float3_buf->data<float3>().copy_from(attr_float3);
|
||||
|
||||
struct Float2 {
|
||||
float2 value;
|
||||
GPU_VERTEX_FORMAT_FUNC(Float2, value);
|
||||
};
|
||||
gpu::VertBuf *attribute_float2_buf = GPU_vertbuf_create_with_format(Float2::format());
|
||||
attribute_float2_buf->allocate(attr_float4.size());
|
||||
attribute_float2_buf->data<float2>().copy_from(attr_float2);
|
||||
|
||||
struct Float {
|
||||
float value;
|
||||
GPU_VERTEX_FORMAT_FUNC(Float, value);
|
||||
};
|
||||
gpu::VertBuf *attribute_float_buf = GPU_vertbuf_create_with_format(Float::format());
|
||||
attribute_float_buf->allocate(attr_float4.size());
|
||||
attribute_float_buf->data<float>().copy_from(attr_float);
|
||||
|
||||
/* Nurbs. */
|
||||
|
||||
const bke::curves::nurbs::BasisCache basis_cache_c0 = {
|
||||
{0.1f, 0.2f, 0.4f, 0.5f, 0.6f, 0.7f, 0.8f, 0.9f, 1.0f, 2.0f, 3.0f, 4.0f, 5.0f, 6.0f, 7.0f},
|
||||
{0, 0, 0, 0, 0},
|
||||
false,
|
||||
};
|
||||
|
||||
Vector<int> basis_cache_offset;
|
||||
Vector<uint32_t> basis_cache_packed;
|
||||
{
|
||||
basis_cache_offset.append(basis_cache_c0.invalid ? -1 : basis_cache_packed.size());
|
||||
basis_cache_packed.extend(
|
||||
Span{reinterpret_cast<const uint32_t *>(basis_cache_c0.start_indices.data()),
|
||||
basis_cache_c0.start_indices.size()});
|
||||
basis_cache_packed.extend(
|
||||
Span{reinterpret_cast<const uint32_t *>(basis_cache_c0.weights.data()),
|
||||
basis_cache_c0.weights.size()});
|
||||
|
||||
basis_cache_offset.append(-1);
|
||||
basis_cache_offset.append(-1);
|
||||
}
|
||||
|
||||
/* Raw data. Shader reinterpret as float or int. */
|
||||
gpu::VertBuf *basis_cache_buf = GPU_vertbuf_create_with_format(IntBuf::format());
|
||||
basis_cache_buf->allocate(basis_cache_packed.size());
|
||||
basis_cache_buf->data<uint>().copy_from(basis_cache_packed);
|
||||
|
||||
gpu::VertBuf *basis_cache_offset_buf = GPU_vertbuf_create_with_format(IntBuf::format());
|
||||
basis_cache_offset_buf->allocate(basis_cache_offset.size());
|
||||
basis_cache_offset_buf->data<int>().copy_from(basis_cache_offset);
|
||||
|
||||
const Vector<int8_t> curves_order = {3, 0, 0, /* Padding. */ 0};
|
||||
|
||||
gpu::VertBuf *curves_order_buf = GPU_vertbuf_create_with_format(IntBuf::format());
|
||||
curves_order_buf->allocate(curves_order.size() / 4);
|
||||
curves_order_buf->data<int>().copy_from(
|
||||
Span<int>(reinterpret_cast<const int *>(curves_order.data()), curves_order.size() / 4));
|
||||
|
||||
const Vector<float> control_weights = {1.0f, 2.0f, 3.0f, 4.0f, 5.0f};
|
||||
|
||||
gpu::VertBuf *control_weights_buf = GPU_vertbuf_create_with_format(Float::format());
|
||||
control_weights_buf->allocate(control_weights.size());
|
||||
control_weights_buf->data<float>().copy_from(control_weights);
|
||||
|
||||
/* Bezier */
|
||||
|
||||
const Vector<float3> handle_pos_left = {
|
||||
float3{0.0f}, float3{1.0f}, float3{-1.0f}, float3{1.0f}, float3{4.0f}};
|
||||
const Vector<float3> handle_pos_right = {
|
||||
float3{0.0f}, float3{-1.0f}, float3{1.0f}, float3{-1.0f}, float3{0.0f}};
|
||||
|
||||
gpu::VertBuf *handles_positions_left_buf = GPU_vertbuf_create_with_format(Float3::format());
|
||||
handles_positions_left_buf->allocate(handle_pos_left.size());
|
||||
handles_positions_left_buf->data<float3>().copy_from(handle_pos_left);
|
||||
|
||||
gpu::VertBuf *handles_positions_right_buf = GPU_vertbuf_create_with_format(Float3::format());
|
||||
handles_positions_right_buf->allocate(handle_pos_right.size());
|
||||
handles_positions_right_buf->data<float3>().copy_from(handle_pos_right);
|
||||
|
||||
const Vector<int> bezier_offsets = {0, 2, 4, 5, 0, 2, 3};
|
||||
|
||||
gpu::VertBuf *bezier_offsets_buf = GPU_vertbuf_create_with_format(IntBuf::format());
|
||||
bezier_offsets_buf->allocate(bezier_offsets.size());
|
||||
bezier_offsets_buf->data<int>().copy_from(bezier_offsets);
|
||||
|
||||
auto dispatch =
|
||||
[&](const char *attr_type, gpu::VertBuf *attr_buf, gpu::StorageBuf *evaluated_attr_buf) {
|
||||
std::string pass_name = std::string("Curves ") + attr_type + " Interpolation";
|
||||
std::string sh_name = std::string("draw_curves_interpolate_") + attr_type + "_attribute";
|
||||
/* Make sure all references to the strings are deleted before the strings themselves. */
|
||||
{
|
||||
gpu::Shader *sh = GPU_shader_create_from_info_name(sh_name.c_str());
|
||||
|
||||
PassSimple pass(pass_name.c_str());
|
||||
pass.init();
|
||||
pass.specialize_constant(sh, "evaluated_type", int(CURVE_TYPE_CATMULL_ROM));
|
||||
pass.shader_set(sh);
|
||||
pass.bind_ssbo(POINTS_BY_CURVES_SLOT, points_by_curve_buf);
|
||||
pass.bind_ssbo(CURVE_TYPE_SLOT, curves_type_buf);
|
||||
pass.bind_ssbo(CURVE_CYCLIC_SLOT, curves_type_buf); /* Dummy, not used */
|
||||
pass.bind_ssbo(CURVE_RESOLUTION_SLOT, curves_resolution_buf);
|
||||
pass.bind_ssbo(EVALUATED_POINT_SLOT, evaluated_points_by_curve_buf);
|
||||
pass.bind_ssbo(POINT_ATTR_SLOT, attr_buf);
|
||||
pass.bind_ssbo(EVALUATED_ATTR_SLOT, evaluated_attr_buf);
|
||||
/* Dummy, not used for Catmull-Rom. */
|
||||
pass.bind_ssbo(HANDLES_POS_LEFT_SLOT, evaluated_points_by_curve_buf);
|
||||
pass.bind_ssbo(HANDLES_POS_RIGHT_SLOT, evaluated_points_by_curve_buf);
|
||||
pass.bind_ssbo(BEZIER_OFFSETS_SLOT, evaluated_points_by_curve_buf);
|
||||
pass.push_constant("use_cyclic", false);
|
||||
pass.push_constant("curves_start", 0);
|
||||
pass.push_constant("curves_count", 3);
|
||||
pass.dispatch(1);
|
||||
pass.specialize_constant(sh, "evaluated_type", int(CURVE_TYPE_BEZIER));
|
||||
pass.shader_set(sh);
|
||||
pass.bind_ssbo(HANDLES_POS_LEFT_SLOT, handles_positions_left_buf);
|
||||
pass.bind_ssbo(HANDLES_POS_RIGHT_SLOT, handles_positions_right_buf);
|
||||
pass.bind_ssbo(BEZIER_OFFSETS_SLOT, bezier_offsets_buf);
|
||||
pass.push_constant("use_cyclic", false);
|
||||
pass.push_constant("curves_start", 0);
|
||||
pass.push_constant("curves_count", 3);
|
||||
pass.dispatch(1);
|
||||
pass.specialize_constant(sh, "evaluated_type", int(CURVE_TYPE_NURBS));
|
||||
pass.shader_set(sh);
|
||||
pass.bind_ssbo(CURVES_ORDER_SLOT, curves_order_buf);
|
||||
pass.bind_ssbo(BASIS_CACHE_SLOT, basis_cache_buf);
|
||||
pass.bind_ssbo(CONTROL_WEIGHTS_SLOT, control_weights_buf);
|
||||
pass.bind_ssbo(BASIS_CACHE_OFFSET_SLOT, basis_cache_offset_buf);
|
||||
pass.push_constant("use_cyclic", false);
|
||||
pass.push_constant("curves_start", 0);
|
||||
pass.push_constant("curves_count", 3);
|
||||
pass.push_constant("use_point_weight", true);
|
||||
pass.dispatch(1);
|
||||
pass.barrier(GPU_BARRIER_BUFFER_UPDATE);
|
||||
|
||||
manager.submit(pass);
|
||||
|
||||
GPU_shader_unbind();
|
||||
|
||||
GPU_SHADER_FREE_SAFE(sh);
|
||||
}
|
||||
};
|
||||
|
||||
{
|
||||
StorageArrayBuffer<float4, 512> evaluated_float4_buf;
|
||||
evaluated_float4_buf.clear_to_zero();
|
||||
|
||||
dispatch("float4", attribute_float4_buf, evaluated_float4_buf);
|
||||
|
||||
evaluated_float4_buf.read();
|
||||
|
||||
Vector<float4> interp_data;
|
||||
interp_data.resize(11);
|
||||
|
||||
OffsetIndices<int> curves_to_point_indices(curves_to_point.as_span());
|
||||
OffsetIndices<int> curves_to_eval_indices(evaluated_offsets.as_span());
|
||||
Span<ColorGeometry4f> in_attr = attr_float4.as_span().cast<ColorGeometry4f>();
|
||||
MutableSpan<ColorGeometry4f> out_attr = interp_data.as_mutable_span().cast<ColorGeometry4f>();
|
||||
{
|
||||
const int curve_index = 0;
|
||||
const IndexRange points = curves_to_point_indices[curve_index];
|
||||
const IndexRange evaluated_points = curves_to_eval_indices[curve_index];
|
||||
bke::curves::nurbs::interpolate_to_evaluated(basis_cache_c0,
|
||||
curves_order[curve_index],
|
||||
control_weights.as_span().slice(points),
|
||||
in_attr.slice(points),
|
||||
out_attr.slice(evaluated_points));
|
||||
}
|
||||
{
|
||||
const int curve_index = 1;
|
||||
const IndexRange points = curves_to_point_indices[curve_index];
|
||||
const IndexRange evaluated_points = curves_to_eval_indices[curve_index];
|
||||
const IndexRange offsets = bke::curves::per_curve_point_offsets_range(points, curve_index);
|
||||
bke::curves::bezier::interpolate_to_evaluated(in_attr.slice(points),
|
||||
bezier_offsets.as_span().slice(offsets),
|
||||
out_attr.slice(evaluated_points));
|
||||
}
|
||||
{
|
||||
const int curve_index = 2;
|
||||
const IndexRange points = curves_to_point_indices[curve_index];
|
||||
const IndexRange evaluated_points = curves_to_eval_indices[curve_index];
|
||||
bke::curves::catmull_rom::interpolate_to_evaluated(
|
||||
in_attr.slice(points), false, curve_resolution, out_attr.slice(evaluated_points));
|
||||
}
|
||||
|
||||
EXPECT_EQ(evaluated_float4_buf[0], float4(interp_data[0]));
|
||||
EXPECT_EQ(evaluated_float4_buf[1], float4(interp_data[1]));
|
||||
EXPECT_EQ(evaluated_float4_buf[2], float4(interp_data[2]));
|
||||
EXPECT_EQ(evaluated_float4_buf[3], float4(interp_data[3]));
|
||||
EXPECT_EQ(evaluated_float4_buf[4], float4(interp_data[4]));
|
||||
EXPECT_EQ(evaluated_float4_buf[5], float4(interp_data[5]));
|
||||
EXPECT_EQ(evaluated_float4_buf[6], float4(interp_data[6]));
|
||||
EXPECT_EQ(evaluated_float4_buf[7], float4(interp_data[7]));
|
||||
EXPECT_EQ(evaluated_float4_buf[8], float4(interp_data[8]));
|
||||
EXPECT_EQ(evaluated_float4_buf[9], float4(interp_data[9]));
|
||||
EXPECT_EQ(evaluated_float4_buf[10], float4(interp_data[10]));
|
||||
/* Ensure the rest of the buffer is untouched. */
|
||||
EXPECT_EQ(evaluated_float4_buf[11], float4(0.0));
|
||||
}
|
||||
|
||||
{
|
||||
StorageArrayBuffer<float3, 512> evaluated_float3_buf;
|
||||
evaluated_float3_buf.clear_to_zero();
|
||||
|
||||
dispatch("float3", attribute_float3_buf, evaluated_float3_buf);
|
||||
|
||||
evaluated_float3_buf.read();
|
||||
|
||||
Vector<float3> interp_data;
|
||||
interp_data.resize(11);
|
||||
|
||||
OffsetIndices<int> curves_to_point_indices(curves_to_point.as_span());
|
||||
OffsetIndices<int> curves_to_eval_indices(evaluated_offsets.as_span());
|
||||
Span<float3> in_attr = attr_float3.as_span();
|
||||
MutableSpan<float3> out_attr = interp_data.as_mutable_span();
|
||||
{
|
||||
const int curve_index = 0;
|
||||
const IndexRange points = curves_to_point_indices[curve_index];
|
||||
const IndexRange evaluated_points = curves_to_eval_indices[curve_index];
|
||||
bke::curves::nurbs::interpolate_to_evaluated(basis_cache_c0,
|
||||
curves_order[curve_index],
|
||||
control_weights.as_span().slice(points),
|
||||
in_attr.slice(points),
|
||||
out_attr.slice(evaluated_points));
|
||||
}
|
||||
{
|
||||
const int curve_index = 1;
|
||||
const IndexRange points = curves_to_point_indices[curve_index];
|
||||
const IndexRange evaluated_points = curves_to_eval_indices[curve_index];
|
||||
const IndexRange offsets = bke::curves::per_curve_point_offsets_range(points, curve_index);
|
||||
bke::curves::bezier::interpolate_to_evaluated(in_attr.slice(points),
|
||||
bezier_offsets.as_span().slice(offsets),
|
||||
out_attr.slice(evaluated_points));
|
||||
}
|
||||
{
|
||||
const int curve_index = 2;
|
||||
const IndexRange points = curves_to_point_indices[curve_index];
|
||||
const IndexRange evaluated_points = curves_to_eval_indices[curve_index];
|
||||
bke::curves::catmull_rom::interpolate_to_evaluated(
|
||||
in_attr.slice(points), false, curve_resolution, out_attr.slice(evaluated_points));
|
||||
}
|
||||
|
||||
EXPECT_EQ(evaluated_float3_buf[0], float3(interp_data[0]));
|
||||
EXPECT_EQ(evaluated_float3_buf[1], float3(interp_data[1]));
|
||||
EXPECT_EQ(evaluated_float3_buf[2], float3(interp_data[2]));
|
||||
EXPECT_EQ(evaluated_float3_buf[3], float3(interp_data[3]));
|
||||
EXPECT_EQ(evaluated_float3_buf[4], float3(interp_data[4]));
|
||||
EXPECT_EQ(evaluated_float3_buf[5], float3(interp_data[5]));
|
||||
EXPECT_EQ(evaluated_float3_buf[6], float3(interp_data[6]));
|
||||
EXPECT_EQ(evaluated_float3_buf[7], float3(interp_data[7]));
|
||||
EXPECT_EQ(evaluated_float3_buf[8], float3(interp_data[8]));
|
||||
EXPECT_EQ(evaluated_float3_buf[9], float3(interp_data[9]));
|
||||
EXPECT_EQ(evaluated_float3_buf[10], float3(interp_data[10]));
|
||||
/* Ensure the rest of the buffer is untouched. */
|
||||
EXPECT_EQ(evaluated_float3_buf[11], float3(0.0));
|
||||
}
|
||||
|
||||
{
|
||||
StorageArrayBuffer<float2, 512> evaluated_float2_buf;
|
||||
evaluated_float2_buf.clear_to_zero();
|
||||
|
||||
dispatch("float2", attribute_float2_buf, evaluated_float2_buf);
|
||||
|
||||
evaluated_float2_buf.read();
|
||||
|
||||
Vector<float2> interp_data;
|
||||
interp_data.resize(11);
|
||||
|
||||
OffsetIndices<int> curves_to_point_indices(curves_to_point.as_span());
|
||||
OffsetIndices<int> curves_to_eval_indices(evaluated_offsets.as_span());
|
||||
Span<float2> in_attr = attr_float2.as_span();
|
||||
MutableSpan<float2> out_attr = interp_data.as_mutable_span();
|
||||
{
|
||||
const int curve_index = 0;
|
||||
const IndexRange points = curves_to_point_indices[curve_index];
|
||||
const IndexRange evaluated_points = curves_to_eval_indices[curve_index];
|
||||
bke::curves::nurbs::interpolate_to_evaluated(basis_cache_c0,
|
||||
curves_order[curve_index],
|
||||
control_weights.as_span().slice(points),
|
||||
in_attr.slice(points),
|
||||
out_attr.slice(evaluated_points));
|
||||
}
|
||||
{
|
||||
const int curve_index = 1;
|
||||
const IndexRange points = curves_to_point_indices[curve_index];
|
||||
const IndexRange evaluated_points = curves_to_eval_indices[curve_index];
|
||||
const IndexRange offsets = bke::curves::per_curve_point_offsets_range(points, curve_index);
|
||||
bke::curves::bezier::interpolate_to_evaluated(in_attr.slice(points),
|
||||
bezier_offsets.as_span().slice(offsets),
|
||||
out_attr.slice(evaluated_points));
|
||||
}
|
||||
{
|
||||
const int curve_index = 2;
|
||||
const IndexRange points = curves_to_point_indices[curve_index];
|
||||
const IndexRange evaluated_points = curves_to_eval_indices[curve_index];
|
||||
bke::curves::catmull_rom::interpolate_to_evaluated(
|
||||
in_attr.slice(points), false, curve_resolution, out_attr.slice(evaluated_points));
|
||||
}
|
||||
|
||||
EXPECT_EQ(evaluated_float2_buf[0], float2(interp_data[0]));
|
||||
EXPECT_EQ(evaluated_float2_buf[1], float2(interp_data[1]));
|
||||
EXPECT_EQ(evaluated_float2_buf[2], float2(interp_data[2]));
|
||||
EXPECT_EQ(evaluated_float2_buf[3], float2(interp_data[3]));
|
||||
EXPECT_EQ(evaluated_float2_buf[4], float2(interp_data[4]));
|
||||
EXPECT_EQ(evaluated_float2_buf[5], float2(interp_data[5]));
|
||||
EXPECT_EQ(evaluated_float2_buf[6], float2(interp_data[6]));
|
||||
EXPECT_EQ(evaluated_float2_buf[7], float2(interp_data[7]));
|
||||
EXPECT_EQ(evaluated_float2_buf[8], float2(interp_data[8]));
|
||||
EXPECT_EQ(evaluated_float2_buf[9], float2(interp_data[9]));
|
||||
EXPECT_EQ(evaluated_float2_buf[10], float2(interp_data[10]));
|
||||
/* Ensure the rest of the buffer is untouched. */
|
||||
EXPECT_EQ(evaluated_float2_buf[11], float2(0.0));
|
||||
}
|
||||
|
||||
{
|
||||
StorageArrayBuffer<float, 512> evaluated_float_buf;
|
||||
evaluated_float_buf.clear_to_zero();
|
||||
|
||||
dispatch("float", attribute_float_buf, evaluated_float_buf);
|
||||
|
||||
evaluated_float_buf.read();
|
||||
|
||||
Vector<float> interp_data;
|
||||
interp_data.resize(11);
|
||||
|
||||
OffsetIndices<int> curves_to_point_indices(curves_to_point.as_span());
|
||||
OffsetIndices<int> curves_to_eval_indices(evaluated_offsets.as_span());
|
||||
Span<float> in_attr = attr_float.as_span();
|
||||
MutableSpan<float> out_attr = interp_data.as_mutable_span();
|
||||
{
|
||||
const int curve_index = 0;
|
||||
const IndexRange points = curves_to_point_indices[curve_index];
|
||||
const IndexRange evaluated_points = curves_to_eval_indices[curve_index];
|
||||
bke::curves::nurbs::interpolate_to_evaluated(basis_cache_c0,
|
||||
curves_order[curve_index],
|
||||
control_weights.as_span().slice(points),
|
||||
in_attr.slice(points),
|
||||
out_attr.slice(evaluated_points));
|
||||
}
|
||||
{
|
||||
const int curve_index = 1;
|
||||
const IndexRange points = curves_to_point_indices[curve_index];
|
||||
const IndexRange evaluated_points = curves_to_eval_indices[curve_index];
|
||||
const IndexRange offsets = bke::curves::per_curve_point_offsets_range(points, curve_index);
|
||||
bke::curves::bezier::interpolate_to_evaluated(in_attr.slice(points),
|
||||
bezier_offsets.as_span().slice(offsets),
|
||||
out_attr.slice(evaluated_points));
|
||||
}
|
||||
{
|
||||
const int curve_index = 2;
|
||||
const IndexRange points = curves_to_point_indices[curve_index];
|
||||
const IndexRange evaluated_points = curves_to_eval_indices[curve_index];
|
||||
bke::curves::catmull_rom::interpolate_to_evaluated(
|
||||
in_attr.slice(points), false, curve_resolution, out_attr.slice(evaluated_points));
|
||||
}
|
||||
|
||||
EXPECT_EQ(evaluated_float_buf[0], float(interp_data[0]));
|
||||
EXPECT_EQ(evaluated_float_buf[1], float(interp_data[1]));
|
||||
EXPECT_EQ(evaluated_float_buf[2], float(interp_data[2]));
|
||||
EXPECT_EQ(evaluated_float_buf[3], float(interp_data[3]));
|
||||
EXPECT_EQ(evaluated_float_buf[4], float(interp_data[4]));
|
||||
EXPECT_EQ(evaluated_float_buf[5], float(interp_data[5]));
|
||||
EXPECT_EQ(evaluated_float_buf[6], float(interp_data[6]));
|
||||
EXPECT_EQ(evaluated_float_buf[7], float(interp_data[7]));
|
||||
EXPECT_EQ(evaluated_float_buf[8], float(interp_data[8]));
|
||||
EXPECT_EQ(evaluated_float_buf[9], float(interp_data[9]));
|
||||
EXPECT_EQ(evaluated_float_buf[10], float(interp_data[10]));
|
||||
/* Ensure the rest of the buffer is untouched. */
|
||||
EXPECT_EQ(evaluated_float_buf[11], float(0.0));
|
||||
}
|
||||
|
||||
GPU_VERTBUF_DISCARD_SAFE(points_by_curve_buf);
|
||||
GPU_VERTBUF_DISCARD_SAFE(curves_type_buf);
|
||||
GPU_VERTBUF_DISCARD_SAFE(curves_resolution_buf);
|
||||
GPU_VERTBUF_DISCARD_SAFE(evaluated_points_by_curve_buf);
|
||||
GPU_VERTBUF_DISCARD_SAFE(handles_positions_left_buf);
|
||||
GPU_VERTBUF_DISCARD_SAFE(handles_positions_right_buf);
|
||||
GPU_VERTBUF_DISCARD_SAFE(bezier_offsets_buf);
|
||||
GPU_VERTBUF_DISCARD_SAFE(basis_cache_buf);
|
||||
GPU_VERTBUF_DISCARD_SAFE(basis_cache_offset_buf);
|
||||
GPU_VERTBUF_DISCARD_SAFE(curves_order_buf);
|
||||
GPU_VERTBUF_DISCARD_SAFE(control_weights_buf);
|
||||
GPU_VERTBUF_DISCARD_SAFE(attribute_float4_buf);
|
||||
GPU_VERTBUF_DISCARD_SAFE(attribute_float3_buf);
|
||||
GPU_VERTBUF_DISCARD_SAFE(attribute_float2_buf);
|
||||
GPU_VERTBUF_DISCARD_SAFE(attribute_float_buf);
|
||||
}
|
||||
DRAW_TEST(draw_curves_interpolate_attributes)
|
||||
|
||||
} // namespace blender::draw
|
||||
@@ -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)
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -10,8 +10,10 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "BLI_math_base.h"
|
||||
#include "BLI_span.hh"
|
||||
#include "BLI_utildefines.h"
|
||||
#include "BLI_virtual_array.hh"
|
||||
|
||||
#include "GPU_common.hh"
|
||||
#include "GPU_vertex_format.hh"
|
||||
@@ -54,6 +56,31 @@ enum GPUUsageType {
|
||||
ENUM_OPERATORS(GPUUsageType, GPU_USAGE_FLAG_BUFFER_TEXTURE_ONLY);
|
||||
|
||||
namespace blender::gpu {
|
||||
class VertBuf;
|
||||
} // namespace blender::gpu
|
||||
|
||||
void GPU_vertbuf_discard(blender::gpu::VertBuf *);
|
||||
|
||||
blender::gpu::VertBuf *GPU_vertbuf_calloc();
|
||||
blender::gpu::VertBuf *GPU_vertbuf_create_with_format_ex(const GPUVertFormat &format,
|
||||
GPUUsageType usage);
|
||||
|
||||
static inline blender::gpu::VertBuf *GPU_vertbuf_create_with_format(const GPUVertFormat &format)
|
||||
{
|
||||
return GPU_vertbuf_create_with_format_ex(format, GPU_USAGE_STATIC);
|
||||
}
|
||||
|
||||
namespace blender::gpu {
|
||||
|
||||
class VertBufDeleter {
|
||||
public:
|
||||
void operator()(VertBuf *vbo)
|
||||
{
|
||||
GPU_vertbuf_discard(vbo);
|
||||
}
|
||||
};
|
||||
|
||||
using VertBufPtr = std::unique_ptr<gpu::VertBuf, gpu::VertBufDeleter>;
|
||||
|
||||
/**
|
||||
* Implementation of Vertex Buffers.
|
||||
@@ -91,6 +118,48 @@ class VertBuf {
|
||||
VertBuf();
|
||||
virtual ~VertBuf();
|
||||
|
||||
template<typename T> static VertBufPtr from_size(const int size)
|
||||
{
|
||||
BLI_assert(size > 0);
|
||||
VertBufPtr buf = VertBufPtr(GPU_vertbuf_create_with_format(GenericVertexFormat<T>::format()));
|
||||
/* GPU formats needs to be aligned to 4 bytes. */
|
||||
buf->allocate(ceil_to_multiple_u(size * sizeof(T), 4) / sizeof(GenericVertexFormat<T>));
|
||||
return buf;
|
||||
}
|
||||
|
||||
template<typename T> static VertBufPtr from_span(const Span<T> data)
|
||||
{
|
||||
BLI_assert(!data.is_empty());
|
||||
VertBufPtr buf = VertBufPtr(GPU_vertbuf_create_with_format_ex(
|
||||
GenericVertexFormat<T>::format(), GPU_USAGE_STATIC | GPU_USAGE_FLAG_BUFFER_TEXTURE_ONLY));
|
||||
/* GPU formats needs to be aligned to 4 bytes. */
|
||||
buf->allocate(ceil_to_multiple_u(data.size_in_bytes(), 4) / sizeof(GenericVertexFormat<T>));
|
||||
buf->data<T>().slice(0, data.size()).copy_from(data);
|
||||
return buf;
|
||||
}
|
||||
|
||||
template<typename T> static VertBufPtr from_varray(const VArray<T> &array)
|
||||
{
|
||||
BLI_assert(!array.is_empty());
|
||||
VertBufPtr buf = VertBufPtr(GPU_vertbuf_create_with_format_ex(
|
||||
GenericVertexFormat<T>::format(), GPU_USAGE_STATIC | GPU_USAGE_FLAG_BUFFER_TEXTURE_ONLY));
|
||||
/* GPU formats needs to be aligned to 4 bytes. */
|
||||
buf->allocate(ceil_to_multiple_u(array.size() * sizeof(T), 4) /
|
||||
sizeof(GenericVertexFormat<T>));
|
||||
array.materialize(buf->data<T>().slice(0, array.size()));
|
||||
return buf;
|
||||
}
|
||||
|
||||
template<typename T> static VertBufPtr device_only(uint size)
|
||||
{
|
||||
BLI_assert(size > 0);
|
||||
VertBufPtr buf = VertBufPtr(GPU_vertbuf_create_with_format_ex(
|
||||
GenericVertexFormat<T>::format(),
|
||||
GPU_USAGE_DEVICE_ONLY | GPU_USAGE_FLAG_BUFFER_TEXTURE_ONLY));
|
||||
buf->allocate(size);
|
||||
return buf;
|
||||
}
|
||||
|
||||
void init(const GPUVertFormat &format, GPUUsageType usage);
|
||||
void clear();
|
||||
|
||||
@@ -155,13 +224,6 @@ class VertBuf {
|
||||
|
||||
} // namespace blender::gpu
|
||||
|
||||
blender::gpu::VertBuf *GPU_vertbuf_calloc();
|
||||
blender::gpu::VertBuf *GPU_vertbuf_create_with_format_ex(const GPUVertFormat &format,
|
||||
GPUUsageType usage);
|
||||
|
||||
#define GPU_vertbuf_create_with_format(format) \
|
||||
GPU_vertbuf_create_with_format_ex(format, GPU_USAGE_STATIC)
|
||||
|
||||
/**
|
||||
* (Download and) fill data with the data from the vertex buffer.
|
||||
* NOTE: caller is responsible to reserve enough memory of the data parameter.
|
||||
@@ -169,7 +231,6 @@ blender::gpu::VertBuf *GPU_vertbuf_create_with_format_ex(const GPUVertFormat &fo
|
||||
void GPU_vertbuf_read(const blender::gpu::VertBuf *verts, void *data);
|
||||
/** Same as discard but does not free. */
|
||||
void GPU_vertbuf_clear(blender::gpu::VertBuf *verts);
|
||||
void GPU_vertbuf_discard(blender::gpu::VertBuf *);
|
||||
|
||||
/**
|
||||
* Avoid blender::gpu::VertBuf data-block being free but not its data.
|
||||
@@ -292,17 +353,3 @@ uint GPU_vertbuf_get_memory_usage();
|
||||
verts = nullptr; \
|
||||
} \
|
||||
} while (0)
|
||||
|
||||
namespace blender::gpu {
|
||||
|
||||
class VertBufDeleter {
|
||||
public:
|
||||
void operator()(VertBuf *vbo)
|
||||
{
|
||||
GPU_vertbuf_discard(vbo);
|
||||
}
|
||||
};
|
||||
|
||||
using VertBufPtr = std::unique_ptr<gpu::VertBuf, gpu::VertBufDeleter>;
|
||||
|
||||
} // namespace blender::gpu
|
||||
|
||||
@@ -306,6 +306,40 @@ struct GPUVertFormat {
|
||||
return format; \
|
||||
}
|
||||
|
||||
namespace blender::gpu {
|
||||
|
||||
/** Generic vertex format for single attribute buffers. */
|
||||
template<typename T> struct GenericVertexFormat {
|
||||
T attr;
|
||||
GPU_VERTEX_FORMAT_FUNC(GenericVertexFormat, attr);
|
||||
};
|
||||
|
||||
template<> struct GenericVertexFormat<int8_t> {
|
||||
/* This is a workaround to reinterpret int8_t into padded vertex format to be able to upload it
|
||||
* on any GPU. The shaders then need to read uint32_t and use shifts and mask to decode in
|
||||
* individual bytes. */
|
||||
uint32_t attr;
|
||||
GPU_VERTEX_FORMAT_FUNC(GenericVertexFormat, attr);
|
||||
};
|
||||
|
||||
template<> struct GenericVertexFormat<uint8_t> {
|
||||
/* This is a workaround to reinterpret uint8_t into padded vertex format to be able to upload it
|
||||
* on any GPU. The shaders then need to read uint32_t and use shifts and mask to decode in
|
||||
* individual bytes. */
|
||||
uint32_t attr;
|
||||
GPU_VERTEX_FORMAT_FUNC(GenericVertexFormat, attr);
|
||||
};
|
||||
|
||||
template<> struct GenericVertexFormat<bool> {
|
||||
/* This is a workaround to reinterpret bool into padded vertex format to be able to upload it
|
||||
* on any GPU. The shaders then need to read uint32_t and use shifts and mask to decode in
|
||||
* individual bytes. */
|
||||
uint32_t attr;
|
||||
GPU_VERTEX_FORMAT_FUNC(GenericVertexFormat, attr);
|
||||
};
|
||||
|
||||
} // namespace blender::gpu
|
||||
|
||||
void GPU_vertformat_clear(GPUVertFormat *);
|
||||
void GPU_vertformat_copy(GPUVertFormat *dest, const GPUVertFormat &src);
|
||||
void GPU_vertformat_from_shader(GPUVertFormat *format, const blender::gpu::Shader *shader);
|
||||
|
||||
@@ -1481,11 +1481,11 @@ class Preprocessor {
|
||||
});
|
||||
}
|
||||
|
||||
/* `*this` -> `this` */
|
||||
/* `*this` -> `this_` */
|
||||
scope.foreach_match("*T", [&](const std::vector<Token> &tokens) {
|
||||
fn_parser.replace(tokens[0], tokens[1], "this_");
|
||||
});
|
||||
/* `this->` -> `this.` */
|
||||
/* `this->` -> `this_.` */
|
||||
scope.foreach_match("TD", [&](const std::vector<Token> &tokens) {
|
||||
fn_parser.replace(tokens[0], tokens[1], "this_.");
|
||||
});
|
||||
@@ -1976,11 +1976,19 @@ class Preprocessor {
|
||||
return str;
|
||||
}
|
||||
if (value.find("(") != string::npos) {
|
||||
report_error(line_number(match),
|
||||
char_number(match),
|
||||
line_str(match),
|
||||
"Reference definitions cannot contain function calls.");
|
||||
return str;
|
||||
if (value.find("specialization_constant_get(") == string::npos &&
|
||||
value.find("push_constant_get(") == string::npos &&
|
||||
value.find("interface_get(") == string::npos &&
|
||||
value.find("attribute_get(") == string::npos &&
|
||||
value.find("buffer_get(") == string::npos &&
|
||||
value.find("sampler_get(") == string::npos && value.find("image_get(") == string::npos)
|
||||
{
|
||||
report_error(line_number(match),
|
||||
char_number(match),
|
||||
line_str(match),
|
||||
"Reference definitions cannot contain function calls.");
|
||||
return str;
|
||||
}
|
||||
}
|
||||
if (value.find("[") != string::npos) {
|
||||
const string index_var = get_content_between_balanced_pair(value, '[', ']');
|
||||
|
||||
@@ -113,6 +113,7 @@ enum class ScopeType : char {
|
||||
Struct = 'S',
|
||||
Function = 'F',
|
||||
FunctionArgs = 'f',
|
||||
FunctionCall = 'c',
|
||||
Template = 'T',
|
||||
TemplateArg = 't',
|
||||
Subscript = 'A',
|
||||
@@ -849,6 +850,9 @@ inline void ParserData::parse_scopes(report_callback &report_error)
|
||||
else if (scopes.top().type == ScopeType::Struct) {
|
||||
enter_scope(ScopeType::Function, tok_id);
|
||||
}
|
||||
else if (scopes.top().type == ScopeType::Namespace) {
|
||||
enter_scope(ScopeType::Function, tok_id);
|
||||
}
|
||||
else {
|
||||
enter_scope(ScopeType::Local, tok_id);
|
||||
}
|
||||
@@ -860,6 +864,12 @@ inline void ParserData::parse_scopes(report_callback &report_error)
|
||||
else if (scopes.top().type == ScopeType::Struct) {
|
||||
enter_scope(ScopeType::FunctionArgs, tok_id);
|
||||
}
|
||||
else if ((scopes.top().type == ScopeType::Function ||
|
||||
scopes.top().type == ScopeType::Local) &&
|
||||
(tok_id >= 1 && token_types[tok_id - 1] == Word))
|
||||
{
|
||||
enter_scope(ScopeType::FunctionCall, tok_id);
|
||||
}
|
||||
else {
|
||||
enter_scope(ScopeType::Local, tok_id);
|
||||
}
|
||||
@@ -1029,6 +1039,14 @@ struct Parser {
|
||||
matches[8] == Const,
|
||||
matches[10].scope());
|
||||
});
|
||||
foreach_match("m?ww<..>(..)c?{..}", [&](const std::vector<Token> matches) {
|
||||
callback(matches[0] == Static,
|
||||
matches[2],
|
||||
matches[3],
|
||||
matches[8].scope(),
|
||||
matches[12] == Const,
|
||||
matches[14].scope());
|
||||
});
|
||||
}
|
||||
|
||||
std::string substr_range_inclusive(size_t start, size_t end)
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -355,6 +355,9 @@ static char attr_prefix_get(const GPUMaterialAttribute *attr)
|
||||
if (attr->is_hair_length) {
|
||||
return 'l';
|
||||
}
|
||||
if (attr->is_hair_intercept) {
|
||||
return 'i';
|
||||
}
|
||||
switch (attr->type) {
|
||||
case CD_TANGENT:
|
||||
return 't';
|
||||
@@ -388,14 +391,16 @@ static GPUMaterialAttribute *gpu_node_graph_add_attribute(GPUNodeGraph *graph,
|
||||
eCustomDataType type,
|
||||
const char *name,
|
||||
const bool is_default_color,
|
||||
const bool is_hair_length)
|
||||
const bool is_hair_length,
|
||||
const bool is_hair_intercept)
|
||||
{
|
||||
/* Find existing attribute. */
|
||||
int num_attributes = 0;
|
||||
GPUMaterialAttribute *attr = static_cast<GPUMaterialAttribute *>(graph->attributes.first);
|
||||
for (; attr; attr = attr->next) {
|
||||
if (attr->type == type && STREQ(attr->name, name) &&
|
||||
attr->is_default_color == is_default_color && attr->is_hair_length == is_hair_length)
|
||||
attr->is_default_color == is_default_color && attr->is_hair_length == is_hair_length &&
|
||||
attr->is_hair_intercept == is_hair_intercept)
|
||||
{
|
||||
break;
|
||||
}
|
||||
@@ -407,6 +412,7 @@ static GPUMaterialAttribute *gpu_node_graph_add_attribute(GPUNodeGraph *graph,
|
||||
attr = MEM_callocN<GPUMaterialAttribute>(__func__);
|
||||
attr->is_default_color = is_default_color;
|
||||
attr->is_hair_length = is_hair_length;
|
||||
attr->is_hair_intercept = is_hair_intercept;
|
||||
attr->type = type;
|
||||
STRNCPY(attr->name, name);
|
||||
attr_input_name(attr);
|
||||
@@ -530,7 +536,8 @@ static GPUMaterialTexture *gpu_node_graph_add_texture(GPUNodeGraph *graph,
|
||||
GPUNodeLink *GPU_attribute(GPUMaterial *mat, const eCustomDataType type, const char *name)
|
||||
{
|
||||
GPUNodeGraph *graph = gpu_material_node_graph(mat);
|
||||
GPUMaterialAttribute *attr = gpu_node_graph_add_attribute(graph, type, name, false, false);
|
||||
GPUMaterialAttribute *attr = gpu_node_graph_add_attribute(
|
||||
graph, type, name, false, false, false);
|
||||
|
||||
if (type == CD_ORCO) {
|
||||
/* OPTI: orco might be computed from local positions and needs object information. */
|
||||
@@ -553,7 +560,7 @@ GPUNodeLink *GPU_attribute_default_color(GPUMaterial *mat)
|
||||
{
|
||||
GPUNodeGraph *graph = gpu_material_node_graph(mat);
|
||||
GPUMaterialAttribute *attr = gpu_node_graph_add_attribute(
|
||||
graph, CD_AUTO_FROM_NAME, "", true, false);
|
||||
graph, CD_AUTO_FROM_NAME, "", true, false, false);
|
||||
if (attr == nullptr) {
|
||||
static const float zero_data[GPU_MAX_CONSTANT_DATA] = {0.0f};
|
||||
return GPU_constant(zero_data);
|
||||
@@ -569,7 +576,22 @@ GPUNodeLink *GPU_attribute_hair_length(GPUMaterial *mat)
|
||||
{
|
||||
GPUNodeGraph *graph = gpu_material_node_graph(mat);
|
||||
GPUMaterialAttribute *attr = gpu_node_graph_add_attribute(
|
||||
graph, CD_AUTO_FROM_NAME, "", false, true);
|
||||
graph, CD_AUTO_FROM_NAME, "", false, true, false);
|
||||
if (attr == nullptr) {
|
||||
static const float zero_data[GPU_MAX_CONSTANT_DATA] = {0.0f};
|
||||
return GPU_constant(zero_data);
|
||||
}
|
||||
GPUNodeLink *link = gpu_node_link_create();
|
||||
link->link_type = GPU_NODE_LINK_ATTR;
|
||||
link->attr = attr;
|
||||
return link;
|
||||
}
|
||||
|
||||
GPUNodeLink *GPU_attribute_hair_intercept(GPUMaterial *mat)
|
||||
{
|
||||
GPUNodeGraph *graph = gpu_material_node_graph(mat);
|
||||
GPUMaterialAttribute *attr = gpu_node_graph_add_attribute(
|
||||
graph, CD_AUTO_FROM_NAME, "", false, false, true);
|
||||
if (attr == nullptr) {
|
||||
static const float zero_data[GPU_MAX_CONSTANT_DATA] = {0.0f};
|
||||
return GPU_constant(zero_data);
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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_);
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,33 @@
|
||||
/* SPDX-FileCopyrightText: 2025 Blender Authors
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "gpu_glsl_cpp_stubs.hh"
|
||||
|
||||
#include "gpu_shader_index_range_lib.glsl"
|
||||
|
||||
/**
|
||||
* See `OffsetIndices` C++ definition for formal definition.
|
||||
*
|
||||
* OffsetIndices cannot be implemented on GPU because of the lack of operator overloading and
|
||||
* buffer reference in GLSL. So we simply interpret a given integer buffer as a `OffsetIndices`
|
||||
* buffer and load a specific item as a range.
|
||||
*/
|
||||
namespace offset_indices {
|
||||
|
||||
#ifdef GLSL_CPP_STUBS
|
||||
/* Equivalent of `IndexRange OffsetIndices<int>operator[]`.
|
||||
* Implementation for C++ compilation. */
|
||||
inline static IndexRange load_range_from_buffer(const int (&buf)[], int i)
|
||||
{
|
||||
return IndexRange::from_begin_end(buf[i], buf[i + 1]);
|
||||
}
|
||||
#endif
|
||||
|
||||
} // namespace offset_indices
|
||||
|
||||
/* Shader implementation because of missing buffer reference as argument in GLSL. */
|
||||
#define offset_indices_load_range_from_buffer(buf_, i_) \
|
||||
IndexRange::from_begin_end(buf_[i_], buf_[i_ + 1])
|
||||
@@ -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
|
||||
|
||||
@@ -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). */
|
||||
|
||||
@@ -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));
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -1014,6 +1014,30 @@ uint my_func() {
|
||||
}
|
||||
return i;
|
||||
}
|
||||
)";
|
||||
string error;
|
||||
string output = process_test_string(input, error);
|
||||
EXPECT_EQ(output, expect);
|
||||
EXPECT_EQ(error, "");
|
||||
}
|
||||
{
|
||||
/* Guard in template. */
|
||||
string input = R"(
|
||||
template<> uint my_func<uint>(uint i) {
|
||||
return buffer_get(draw_resource_id, resource_id_buf)[i];
|
||||
}
|
||||
)";
|
||||
string expect = R"(
|
||||
uint my_func_uint_(uint i) {
|
||||
#if defined(CREATE_INFO_draw_resource_id)
|
||||
#line 3
|
||||
return buffer_get(draw_resource_id, resource_id_buf)[i];
|
||||
#else
|
||||
#line 3
|
||||
return uint(0);
|
||||
#endif
|
||||
#line 4
|
||||
}
|
||||
)";
|
||||
string error;
|
||||
string output = process_test_string(input, error);
|
||||
|
||||
@@ -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 */
|
||||
|
||||
@@ -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},
|
||||
};
|
||||
|
||||
|
||||
@@ -9,7 +9,9 @@ namespace blender::nodes::node_shader_hair_info_cc {
|
||||
static void node_declare(NodeDeclarationBuilder &b)
|
||||
{
|
||||
b.add_output<decl::Float>("Is Strand");
|
||||
#define INTERCEPT_SOCKET_INDEX 1
|
||||
b.add_output<decl::Float>("Intercept");
|
||||
#define LENGTH_SOCKET_INDEX 2
|
||||
b.add_output<decl::Float>("Length");
|
||||
b.add_output<decl::Float>("Thickness");
|
||||
b.add_output<decl::Vector>("Tangent Normal");
|
||||
@@ -24,9 +26,12 @@ static int node_shader_gpu_hair_info(GPUMaterial *mat,
|
||||
{
|
||||
/* Length: don't request length if not needed. */
|
||||
static const float zero = 0;
|
||||
GPUNodeLink *length_link = out[2].hasoutput ? GPU_attribute_hair_length(mat) :
|
||||
GPU_constant(&zero);
|
||||
return GPU_stack_link(mat, node, "node_hair_info", in, out, length_link);
|
||||
GPUNodeLink *length_link = out[LENGTH_SOCKET_INDEX].hasoutput ? GPU_attribute_hair_length(mat) :
|
||||
GPU_constant(&zero);
|
||||
GPUNodeLink *intercept_link = out[INTERCEPT_SOCKET_INDEX].hasoutput ?
|
||||
GPU_attribute_hair_intercept(mat) :
|
||||
GPU_constant(&zero);
|
||||
return GPU_stack_link(mat, node, "node_hair_info", in, out, intercept_link, length_link);
|
||||
}
|
||||
|
||||
NODE_SHADER_MATERIALX_BEGIN
|
||||
|
||||
BIN
tests/files/render/hair/eevee_renders/hair_basemesh_intercept.png
(Stored with Git LFS)
BIN
tests/files/render/hair/eevee_renders/hair_basemesh_intercept.png
(Stored with Git LFS)
Binary file not shown.
BIN
tests/files/render/hair/eevee_renders/hair_close_up.png
(Stored with Git LFS)
BIN
tests/files/render/hair/eevee_renders/hair_close_up.png
(Stored with Git LFS)
Binary file not shown.
BIN
tests/files/render/hair/eevee_renders/hair_info.png
(Stored with Git LFS)
BIN
tests/files/render/hair/eevee_renders/hair_info.png
(Stored with Git LFS)
Binary file not shown.
BIN
tests/files/render/hair/eevee_renders/hair_instancer_uv.png
(Stored with Git LFS)
BIN
tests/files/render/hair/eevee_renders/hair_instancer_uv.png
(Stored with Git LFS)
Binary file not shown.
BIN
tests/files/render/hair/eevee_renders/hair_linear_close_up.png
(Stored with Git LFS)
BIN
tests/files/render/hair/eevee_renders/hair_linear_close_up.png
(Stored with Git LFS)
Binary file not shown.
BIN
tests/files/render/hair/eevee_renders/hair_particle_random.png
(Stored with Git LFS)
BIN
tests/files/render/hair/eevee_renders/hair_particle_random.png
(Stored with Git LFS)
Binary file not shown.
BIN
tests/files/render/hair/eevee_renders/hair_ribbon_close_up.png
(Stored with Git LFS)
BIN
tests/files/render/hair/eevee_renders/hair_ribbon_close_up.png
(Stored with Git LFS)
Binary file not shown.
BIN
tests/files/render/hair/eevee_renders/microfacet_hair_orientation.png
(Stored with Git LFS)
BIN
tests/files/render/hair/eevee_renders/microfacet_hair_orientation.png
(Stored with Git LFS)
Binary file not shown.
BIN
tests/files/render/hair/eevee_renders/principled_hair_absorptioncoefficient.png
(Stored with Git LFS)
BIN
tests/files/render/hair/eevee_renders/principled_hair_absorptioncoefficient.png
(Stored with Git LFS)
Binary file not shown.
BIN
tests/files/render/hair/eevee_renders/principled_hair_directcoloring.png
(Stored with Git LFS)
BIN
tests/files/render/hair/eevee_renders/principled_hair_directcoloring.png
(Stored with Git LFS)
Binary file not shown.
BIN
tests/files/render/hair/eevee_renders/principled_hair_melaninconcentration.png
(Stored with Git LFS)
BIN
tests/files/render/hair/eevee_renders/principled_hair_melaninconcentration.png
(Stored with Git LFS)
Binary file not shown.
BIN
tests/files/render/hair/workbench_renders/hair_basemesh_intercept.png
(Stored with Git LFS)
BIN
tests/files/render/hair/workbench_renders/hair_basemesh_intercept.png
(Stored with Git LFS)
Binary file not shown.
BIN
tests/files/render/hair/workbench_renders/hair_close_up.png
(Stored with Git LFS)
BIN
tests/files/render/hair/workbench_renders/hair_close_up.png
(Stored with Git LFS)
Binary file not shown.
BIN
tests/files/render/hair/workbench_renders/hair_info.png
(Stored with Git LFS)
BIN
tests/files/render/hair/workbench_renders/hair_info.png
(Stored with Git LFS)
Binary file not shown.
BIN
tests/files/render/hair/workbench_renders/hair_instancer_uv.png
(Stored with Git LFS)
BIN
tests/files/render/hair/workbench_renders/hair_instancer_uv.png
(Stored with Git LFS)
Binary file not shown.
BIN
tests/files/render/hair/workbench_renders/hair_linear_close_up.png
(Stored with Git LFS)
BIN
tests/files/render/hair/workbench_renders/hair_linear_close_up.png
(Stored with Git LFS)
Binary file not shown.
BIN
tests/files/render/hair/workbench_renders/hair_particle_random.png
(Stored with Git LFS)
BIN
tests/files/render/hair/workbench_renders/hair_particle_random.png
(Stored with Git LFS)
Binary file not shown.
BIN
tests/files/render/hair/workbench_renders/hair_ribbon_close_up.png
(Stored with Git LFS)
BIN
tests/files/render/hair/workbench_renders/hair_ribbon_close_up.png
(Stored with Git LFS)
Binary file not shown.
BIN
tests/files/render/hair/workbench_renders/microfacet_hair_orientation.png
(Stored with Git LFS)
BIN
tests/files/render/hair/workbench_renders/microfacet_hair_orientation.png
(Stored with Git LFS)
Binary file not shown.
BIN
tests/files/render/hair/workbench_renders/principled_hair_absorptioncoefficient.png
(Stored with Git LFS)
BIN
tests/files/render/hair/workbench_renders/principled_hair_absorptioncoefficient.png
(Stored with Git LFS)
Binary file not shown.
BIN
tests/files/render/hair/workbench_renders/principled_hair_directcoloring.png
(Stored with Git LFS)
BIN
tests/files/render/hair/workbench_renders/principled_hair_directcoloring.png
(Stored with Git LFS)
Binary file not shown.
BIN
tests/files/render/hair/workbench_renders/principled_hair_melaninconcentration.png
(Stored with Git LFS)
BIN
tests/files/render/hair/workbench_renders/principled_hair_melaninconcentration.png
(Stored with Git LFS)
Binary file not shown.
BIN
tests/files/render/light_linking/eevee_renders/shadow_link_simple_3D_curve.png
(Stored with Git LFS)
BIN
tests/files/render/light_linking/eevee_renders/shadow_link_simple_3D_curve.png
(Stored with Git LFS)
Binary file not shown.
BIN
tests/files/render/light_linking/eevee_renders/shadow_link_simple_ribbon_curve.png
(Stored with Git LFS)
BIN
tests/files/render/light_linking/eevee_renders/shadow_link_simple_ribbon_curve.png
(Stored with Git LFS)
Binary file not shown.
BIN
tests/files/render/light_linking/workbench_renders/shadow_link_simple_3D_curve.png
(Stored with Git LFS)
BIN
tests/files/render/light_linking/workbench_renders/shadow_link_simple_3D_curve.png
(Stored with Git LFS)
Binary file not shown.
BIN
tests/files/render/light_linking/workbench_renders/shadow_link_simple_ribbon_curve.png
(Stored with Git LFS)
BIN
tests/files/render/light_linking/workbench_renders/shadow_link_simple_ribbon_curve.png
(Stored with Git LFS)
Binary file not shown.
BIN
tests/files/render/motion_blur/eevee_renders/curve_motion_blur.png
(Stored with Git LFS)
BIN
tests/files/render/motion_blur/eevee_renders/curve_motion_blur.png
(Stored with Git LFS)
Binary file not shown.
BIN
tests/files/render/motion_blur/workbench_renders/bvh_steps_curve_segments_0.png
(Stored with Git LFS)
BIN
tests/files/render/motion_blur/workbench_renders/bvh_steps_curve_segments_0.png
(Stored with Git LFS)
Binary file not shown.
BIN
tests/files/render/motion_blur/workbench_renders/bvh_steps_curve_segments_3.png
(Stored with Git LFS)
BIN
tests/files/render/motion_blur/workbench_renders/bvh_steps_curve_segments_3.png
(Stored with Git LFS)
Binary file not shown.
BIN
tests/files/render/motion_blur/workbench_renders/bvh_steps_line_segments_0.png
(Stored with Git LFS)
BIN
tests/files/render/motion_blur/workbench_renders/bvh_steps_line_segments_0.png
(Stored with Git LFS)
Binary file not shown.
BIN
tests/files/render/motion_blur/workbench_renders/bvh_steps_line_segments_3.png
(Stored with Git LFS)
BIN
tests/files/render/motion_blur/workbench_renders/bvh_steps_line_segments_3.png
(Stored with Git LFS)
Binary file not shown.
BIN
tests/files/render/motion_blur/workbench_renders/curve_motion_blur.png
(Stored with Git LFS)
BIN
tests/files/render/motion_blur/workbench_renders/curve_motion_blur.png
(Stored with Git LFS)
Binary file not shown.
BIN
tests/files/render/render_layer/workbench_renders/rlayer_flags_01.png
(Stored with Git LFS)
BIN
tests/files/render/render_layer/workbench_renders/rlayer_flags_01.png
(Stored with Git LFS)
Binary file not shown.
BIN
tests/files/render/render_layer/workbench_renders/rlayer_flags_02.png
(Stored with Git LFS)
BIN
tests/files/render/render_layer/workbench_renders/rlayer_flags_02.png
(Stored with Git LFS)
Binary file not shown.
BIN
tests/files/render/render_layer/workbench_renders/rlayer_flags_03.png
(Stored with Git LFS)
BIN
tests/files/render/render_layer/workbench_renders/rlayer_flags_03.png
(Stored with Git LFS)
Binary file not shown.
BIN
tests/files/render/render_layer/workbench_renders/rlayer_flags_04.png
(Stored with Git LFS)
BIN
tests/files/render/render_layer/workbench_renders/rlayer_flags_04.png
(Stored with Git LFS)
Binary file not shown.
Reference in New Issue
Block a user