This is to modernize the API: - Add meaningful name to all textures (except DRW textures). - Remove unused err_out argument: only used for offscreen python. - Add mipmap count to creation functions for future changes. - Clarify the data usage in creation functions. This is a cleanup commit, there is no functional change. # Conflicts: # source/blender/gpu/GPU_texture.h
352 lines
11 KiB
C
352 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("hair_point", 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("hair_strand", cache->proc_strand_buf);
|
|
|
|
GPU_vertbuf_use(cache->proc_strand_seg_buf);
|
|
cache->strand_seg_tex = GPU_texture_create_from_vertbuf("hair_strand_seg",
|
|
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("hair_proc",
|
|
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);
|
|
}
|