Only the volume object is exposed in the user interface. It is based on OpenVDB internally. Drawing and rendering code will follow in another commit. https://wiki.blender.org/wiki/Source/Objects/Volume https://wiki.blender.org/wiki/Reference/Release_Notes/2.83/Volumes Hair and PointCloud object types are hidden behind a WITH_NEW_OBJECT_TYPES build option. These are unfinished, and included only to make it easier to cooperate on development in the future and avoid tricky merges. https://wiki.blender.org/wiki/Source/Objects/New_Object_Types Ref T73201, T68981 Differential Revision: https://developer.blender.org/D6945
		
			
				
	
	
		
			350 lines
		
	
	
		
			11 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
			
		
		
	
	
			350 lines
		
	
	
		
			11 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
/*
 | 
						|
 * This program is free software; you can redistribute it and/or
 | 
						|
 * modify it under the terms of the GNU General Public License
 | 
						|
 * as published by the Free Software Foundation; either version 2
 | 
						|
 * of the License, or (at your option) any later version.
 | 
						|
 *
 | 
						|
 * This program is distributed in the hope that it will be useful,
 | 
						|
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 | 
						|
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 | 
						|
 * GNU General Public License for more details.
 | 
						|
 *
 | 
						|
 * You should have received a copy of the GNU General Public License
 | 
						|
 * along with this program; if not, write to the Free Software Foundation,
 | 
						|
 * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
 | 
						|
 *
 | 
						|
 * The Original Code is Copyright (C) 2017 by Blender Foundation.
 | 
						|
 * All rights reserved.
 | 
						|
 */
 | 
						|
 | 
						|
/** \file
 | 
						|
 * \ingroup draw
 | 
						|
 *
 | 
						|
 * \brief Hair API for render engines
 | 
						|
 */
 | 
						|
 | 
						|
#include <string.h>
 | 
						|
 | 
						|
#include "MEM_guardedalloc.h"
 | 
						|
 | 
						|
#include "BLI_math_base.h"
 | 
						|
#include "BLI_math_vector.h"
 | 
						|
#include "BLI_utildefines.h"
 | 
						|
 | 
						|
#include "DNA_hair_types.h"
 | 
						|
#include "DNA_object_types.h"
 | 
						|
 | 
						|
#include "BKE_hair.h"
 | 
						|
 | 
						|
#include "GPU_batch.h"
 | 
						|
#include "GPU_texture.h"
 | 
						|
 | 
						|
#include "draw_cache_impl.h"   /* own include */
 | 
						|
#include "draw_hair_private.h" /* own include */
 | 
						|
 | 
						|
static void hair_batch_cache_clear(Hair *hair);
 | 
						|
 | 
						|
/* ---------------------------------------------------------------------- */
 | 
						|
/* Hair GPUBatch Cache */
 | 
						|
 | 
						|
typedef struct HairBatchCache {
 | 
						|
  ParticleHairCache hair;
 | 
						|
 | 
						|
  /* settings to determine if cache is invalid */
 | 
						|
  bool is_dirty;
 | 
						|
} HairBatchCache;
 | 
						|
 | 
						|
/* GPUBatch cache management. */
 | 
						|
 | 
						|
static bool hair_batch_cache_valid(Hair *hair)
 | 
						|
{
 | 
						|
  HairBatchCache *cache = hair->batch_cache;
 | 
						|
  return (cache && cache->is_dirty == false);
 | 
						|
}
 | 
						|
 | 
						|
static void hair_batch_cache_init(Hair *hair)
 | 
						|
{
 | 
						|
  HairBatchCache *cache = hair->batch_cache;
 | 
						|
 | 
						|
  if (!cache) {
 | 
						|
    cache = hair->batch_cache = MEM_callocN(sizeof(*cache), __func__);
 | 
						|
  }
 | 
						|
  else {
 | 
						|
    memset(cache, 0, sizeof(*cache));
 | 
						|
  }
 | 
						|
 | 
						|
  cache->is_dirty = false;
 | 
						|
}
 | 
						|
 | 
						|
void DRW_hair_batch_cache_validate(Hair *hair)
 | 
						|
{
 | 
						|
  if (!hair_batch_cache_valid(hair)) {
 | 
						|
    hair_batch_cache_clear(hair);
 | 
						|
    hair_batch_cache_init(hair);
 | 
						|
  }
 | 
						|
}
 | 
						|
 | 
						|
static HairBatchCache *hair_batch_cache_get(Hair *hair)
 | 
						|
{
 | 
						|
  DRW_hair_batch_cache_validate(hair);
 | 
						|
  return hair->batch_cache;
 | 
						|
}
 | 
						|
 | 
						|
void DRW_hair_batch_cache_dirty_tag(Hair *hair, int mode)
 | 
						|
{
 | 
						|
  HairBatchCache *cache = hair->batch_cache;
 | 
						|
  if (cache == NULL) {
 | 
						|
    return;
 | 
						|
  }
 | 
						|
  switch (mode) {
 | 
						|
    case BKE_HAIR_BATCH_DIRTY_ALL:
 | 
						|
      cache->is_dirty = true;
 | 
						|
      break;
 | 
						|
    default:
 | 
						|
      BLI_assert(0);
 | 
						|
  }
 | 
						|
}
 | 
						|
 | 
						|
static void hair_batch_cache_clear(Hair *hair)
 | 
						|
{
 | 
						|
  HairBatchCache *cache = hair->batch_cache;
 | 
						|
  if (!cache) {
 | 
						|
    return;
 | 
						|
  }
 | 
						|
 | 
						|
  particle_batch_cache_clear_hair(&cache->hair);
 | 
						|
}
 | 
						|
 | 
						|
void DRW_hair_batch_cache_free(Hair *hair)
 | 
						|
{
 | 
						|
  hair_batch_cache_clear(hair);
 | 
						|
  MEM_SAFE_FREE(hair->batch_cache);
 | 
						|
}
 | 
						|
 | 
						|
static void ensure_seg_pt_count(Hair *hair, ParticleHairCache *hair_cache)
 | 
						|
{
 | 
						|
  if ((hair_cache->pos != NULL && hair_cache->indices != NULL) ||
 | 
						|
      (hair_cache->proc_point_buf != NULL)) {
 | 
						|
    return;
 | 
						|
  }
 | 
						|
 | 
						|
  hair_cache->strands_len = 0;
 | 
						|
  hair_cache->elems_len = 0;
 | 
						|
  hair_cache->point_len = 0;
 | 
						|
 | 
						|
  HairCurve *curve = hair->curves;
 | 
						|
  int num_curves = hair->totcurve;
 | 
						|
  for (int i = 0; i < num_curves; i++, curve++) {
 | 
						|
    hair_cache->strands_len++;
 | 
						|
    hair_cache->elems_len += curve->numpoints + 1;
 | 
						|
    hair_cache->point_len += curve->numpoints;
 | 
						|
  }
 | 
						|
}
 | 
						|
 | 
						|
static void hair_batch_cache_fill_segments_proc_pos(Hair *hair, GPUVertBufRaw *attr_step)
 | 
						|
{
 | 
						|
  /* TODO: use hair radius layer if available. */
 | 
						|
  HairCurve *curve = hair->curves;
 | 
						|
  int num_curves = hair->totcurve;
 | 
						|
  for (int i = 0; i < num_curves; i++, curve++) {
 | 
						|
    float(*curve_co)[3] = hair->co + curve->firstpoint;
 | 
						|
    float total_len = 0.0f;
 | 
						|
    float *co_prev = NULL, *seg_data_first;
 | 
						|
    for (int j = 0; j < curve->numpoints; j++) {
 | 
						|
      float *seg_data = (float *)GPU_vertbuf_raw_step(attr_step);
 | 
						|
      copy_v3_v3(seg_data, curve_co[j]);
 | 
						|
      if (co_prev) {
 | 
						|
        total_len += len_v3v3(co_prev, curve_co[j]);
 | 
						|
      }
 | 
						|
      else {
 | 
						|
        seg_data_first = seg_data;
 | 
						|
      }
 | 
						|
      seg_data[3] = total_len;
 | 
						|
      co_prev = curve_co[j];
 | 
						|
    }
 | 
						|
    if (total_len > 0.0f) {
 | 
						|
      /* Divide by total length to have a [0-1] number. */
 | 
						|
      for (int j = 0; j < curve->numpoints; j++, seg_data_first += 4) {
 | 
						|
        seg_data_first[3] /= total_len;
 | 
						|
      }
 | 
						|
    }
 | 
						|
  }
 | 
						|
}
 | 
						|
 | 
						|
static void hair_batch_cache_ensure_procedural_pos(Hair *hair, ParticleHairCache *cache)
 | 
						|
{
 | 
						|
  if (cache->proc_point_buf != NULL) {
 | 
						|
    return;
 | 
						|
  }
 | 
						|
 | 
						|
  /* initialize vertex format */
 | 
						|
  GPUVertFormat format = {0};
 | 
						|
  uint pos_id = GPU_vertformat_attr_add(&format, "posTime", GPU_COMP_F32, 4, GPU_FETCH_FLOAT);
 | 
						|
 | 
						|
  cache->proc_point_buf = GPU_vertbuf_create_with_format(&format);
 | 
						|
  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);
 | 
						|
 | 
						|
  hair_batch_cache_fill_segments_proc_pos(hair, &pos_step);
 | 
						|
 | 
						|
  /* Create vbo immediately to bind to texture buffer. */
 | 
						|
  GPU_vertbuf_use(cache->proc_point_buf);
 | 
						|
 | 
						|
  cache->point_tex = GPU_texture_create_from_vertbuf(cache->proc_point_buf);
 | 
						|
}
 | 
						|
 | 
						|
static void hair_batch_cache_fill_strands_data(Hair *hair,
 | 
						|
                                               GPUVertBufRaw *data_step,
 | 
						|
                                               GPUVertBufRaw *seg_step)
 | 
						|
{
 | 
						|
  HairCurve *curve = hair->curves;
 | 
						|
  int num_curves = hair->totcurve;
 | 
						|
  for (int i = 0; i < num_curves; i++, curve++) {
 | 
						|
    *(uint *)GPU_vertbuf_raw_step(data_step) = curve->firstpoint;
 | 
						|
    *(ushort *)GPU_vertbuf_raw_step(seg_step) = curve->numpoints - 1;
 | 
						|
  }
 | 
						|
}
 | 
						|
 | 
						|
static void hair_batch_cache_ensure_procedural_strand_data(Hair *hair, ParticleHairCache *cache)
 | 
						|
{
 | 
						|
  GPUVertBufRaw data_step, seg_step;
 | 
						|
 | 
						|
  GPUVertFormat format_data = {0};
 | 
						|
  uint data_id = GPU_vertformat_attr_add(&format_data, "data", GPU_COMP_U32, 1, GPU_FETCH_INT);
 | 
						|
 | 
						|
  GPUVertFormat format_seg = {0};
 | 
						|
  uint seg_id = GPU_vertformat_attr_add(&format_seg, "data", GPU_COMP_U16, 1, GPU_FETCH_INT);
 | 
						|
 | 
						|
  /* Strand Data */
 | 
						|
  cache->proc_strand_buf = GPU_vertbuf_create_with_format(&format_data);
 | 
						|
  GPU_vertbuf_data_alloc(cache->proc_strand_buf, 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(&format_seg);
 | 
						|
  GPU_vertbuf_data_alloc(cache->proc_strand_seg_buf, cache->strands_len);
 | 
						|
  GPU_vertbuf_attr_get_raw_data(cache->proc_strand_seg_buf, seg_id, &seg_step);
 | 
						|
 | 
						|
  hair_batch_cache_fill_strands_data(hair, &data_step, &seg_step);
 | 
						|
 | 
						|
  /* Create vbo immediately to bind to texture buffer. */
 | 
						|
  GPU_vertbuf_use(cache->proc_strand_buf);
 | 
						|
  cache->strand_tex = GPU_texture_create_from_vertbuf(cache->proc_strand_buf);
 | 
						|
 | 
						|
  GPU_vertbuf_use(cache->proc_strand_seg_buf);
 | 
						|
  cache->strand_seg_tex = GPU_texture_create_from_vertbuf(cache->proc_strand_seg_buf);
 | 
						|
}
 | 
						|
 | 
						|
static void hair_batch_cache_ensure_procedural_final_points(ParticleHairCache *cache, int subdiv)
 | 
						|
{
 | 
						|
  /* Same format as point_tex. */
 | 
						|
  GPUVertFormat format = {0};
 | 
						|
  GPU_vertformat_attr_add(&format, "pos", GPU_COMP_F32, 4, GPU_FETCH_FLOAT);
 | 
						|
 | 
						|
  cache->final[subdiv].proc_buf = GPU_vertbuf_create_with_format(&format);
 | 
						|
 | 
						|
  /* Create a destination buffer for the transform feedback. Sized appropriately */
 | 
						|
  /* Those are points! not line segments. */
 | 
						|
  GPU_vertbuf_data_alloc(cache->final[subdiv].proc_buf,
 | 
						|
                         cache->final[subdiv].strands_res * cache->strands_len);
 | 
						|
 | 
						|
  /* Create vbo immediately to bind to texture buffer. */
 | 
						|
  GPU_vertbuf_use(cache->final[subdiv].proc_buf);
 | 
						|
 | 
						|
  cache->final[subdiv].proc_tex = GPU_texture_create_from_vertbuf(cache->final[subdiv].proc_buf);
 | 
						|
}
 | 
						|
 | 
						|
static void hair_batch_cache_fill_segments_indices(Hair *hair,
 | 
						|
                                                   const int res,
 | 
						|
                                                   GPUIndexBufBuilder *elb)
 | 
						|
{
 | 
						|
  HairCurve *curve = hair->curves;
 | 
						|
  int num_curves = hair->totcurve;
 | 
						|
  uint curr_point = 0;
 | 
						|
  for (int i = 0; i < num_curves; i++, curve++) {
 | 
						|
    for (int k = 0; k < res; k++) {
 | 
						|
      GPU_indexbuf_add_generic_vert(elb, curr_point++);
 | 
						|
    }
 | 
						|
    GPU_indexbuf_add_primitive_restart(elb);
 | 
						|
  }
 | 
						|
}
 | 
						|
 | 
						|
static void hair_batch_cache_ensure_procedural_indices(Hair *hair,
 | 
						|
                                                       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] != NULL) {
 | 
						|
    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 GPUVertFormat format = {0};
 | 
						|
  GPU_vertformat_clear(&format);
 | 
						|
 | 
						|
  /* initialize vertex format */
 | 
						|
  GPU_vertformat_attr_add(&format, "dummy", GPU_COMP_U8, 1, GPU_FETCH_INT_TO_FLOAT_UNIT);
 | 
						|
 | 
						|
  GPUVertBuf *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);
 | 
						|
 | 
						|
  hair_batch_cache_fill_segments_indices(hair, 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);
 | 
						|
}
 | 
						|
 | 
						|
/* Ensure all textures and buffers needed for GPU accelerated drawing. */
 | 
						|
bool hair_ensure_procedural_data(Object *object,
 | 
						|
                                 ParticleHairCache **r_hair_cache,
 | 
						|
                                 int subdiv,
 | 
						|
                                 int thickness_res)
 | 
						|
{
 | 
						|
  bool need_ft_update = false;
 | 
						|
  Hair *hair = object->data;
 | 
						|
 | 
						|
  HairBatchCache *cache = hair_batch_cache_get(hair);
 | 
						|
  *r_hair_cache = &cache->hair;
 | 
						|
 | 
						|
  const int steps = 2; /* TODO: don't hardcode? */
 | 
						|
  (*r_hair_cache)->final[subdiv].strands_res = 1 << (steps + subdiv);
 | 
						|
 | 
						|
  /* Refreshed on combing and simulation. */
 | 
						|
  if ((*r_hair_cache)->proc_point_buf == NULL) {
 | 
						|
    ensure_seg_pt_count(hair, &cache->hair);
 | 
						|
    hair_batch_cache_ensure_procedural_pos(hair, &cache->hair);
 | 
						|
    need_ft_update = true;
 | 
						|
  }
 | 
						|
 | 
						|
  /* Refreshed if active layer or custom data changes. */
 | 
						|
  if ((*r_hair_cache)->strand_tex == NULL) {
 | 
						|
    hair_batch_cache_ensure_procedural_strand_data(hair, &cache->hair);
 | 
						|
  }
 | 
						|
 | 
						|
  /* Refreshed only on subdiv count change. */
 | 
						|
  if ((*r_hair_cache)->final[subdiv].proc_buf == NULL) {
 | 
						|
    hair_batch_cache_ensure_procedural_final_points(&cache->hair, subdiv);
 | 
						|
    need_ft_update = true;
 | 
						|
  }
 | 
						|
  if ((*r_hair_cache)->final[subdiv].proc_hairs[thickness_res - 1] == NULL) {
 | 
						|
    hair_batch_cache_ensure_procedural_indices(hair, &cache->hair, thickness_res, subdiv);
 | 
						|
  }
 | 
						|
 | 
						|
  return need_ft_update;
 | 
						|
}
 | 
						|
 | 
						|
int DRW_hair_material_count_get(Hair *hair)
 | 
						|
{
 | 
						|
  return max_ii(1, hair->totcol);
 | 
						|
}
 |