This evaluator is used in order to evaluate subdivision at render time, allowing for faster renders of meshes with a subdivision surface modifier placed at the last position in the modifier list. When evaluating the subsurf modifier, we detect whether we can delegate evaluation to the draw code. If so, the subdivision is first evaluated on the GPU using our own custom evaluator (only the coarse data needs to be initially sent to the GPU), then, buffers for the final `MeshBufferCache` are filled on the GPU using a set of compute shaders. However, some buffers are still filled on the CPU side, if doing so on the GPU is impractical (e.g. the line adjacency buffer used for x-ray, whose logic is hardly GPU compatible). This is done at the mesh buffer extraction level so that the result can be readily used in the various OpenGL engines, without having to write custom geometry or tesselation shaders. We use our own subdivision evaluation shaders, instead of OpenSubDiv's vanilla one, in order to control the data layout, and interpolation. For example, we store vertex colors as compressed 16-bit integers, while OpenSubDiv's default evaluator only work for float types. In order to still access the modified geometry on the CPU side, for use in modifiers or transform operators, a dedicated wrapper type is added `MESH_WRAPPER_TYPE_SUBD`. Subdivision will be lazily evaluated via `BKE_object_get_evaluated_mesh` which will create such a wrapper if possible. If the final subdivision surface is not needed on the CPU side, `BKE_object_get_evaluated_mesh_no_subsurf` should be used. Enabling or disabling GPU subdivision can be done through the user preferences (under Viewport -> Subdivision). See patch description for benchmarks. Reviewed By: campbellbarton, jbakker, fclem, brecht, #eevee_viewport Differential Revision: https://developer.blender.org/D12406
249 lines
9.1 KiB
C
249 lines
9.1 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) 2019 by Blender Foundation.
|
|
* All rights reserved.
|
|
*/
|
|
|
|
/** \file
|
|
* \ingroup bke
|
|
*/
|
|
|
|
#include "BKE_subdiv_deform.h"
|
|
|
|
#include <string.h>
|
|
|
|
#include "DNA_mesh_types.h"
|
|
#include "DNA_meshdata_types.h"
|
|
|
|
#include "BLI_math_vector.h"
|
|
#include "BLI_utildefines.h"
|
|
|
|
#include "BKE_customdata.h"
|
|
#include "BKE_subdiv.h"
|
|
#include "BKE_subdiv_eval.h"
|
|
#include "BKE_subdiv_foreach.h"
|
|
#include "BKE_subdiv_mesh.h"
|
|
|
|
#include "MEM_guardedalloc.h"
|
|
|
|
/* -------------------------------------------------------------------- */
|
|
/** \name Subdivision context
|
|
* \{ */
|
|
|
|
typedef struct SubdivDeformContext {
|
|
const Mesh *coarse_mesh;
|
|
Subdiv *subdiv;
|
|
|
|
float (*vertex_cos)[3];
|
|
int num_verts;
|
|
|
|
/* Accumulated values.
|
|
*
|
|
* Averaging is happening for vertices which correspond to the coarse ones.
|
|
* This is needed for displacement.
|
|
*
|
|
* Displacement is being accumulated to a vertices coordinates, since those
|
|
* are not needed during traversal of face-vertices vertices. */
|
|
/* Per-subdivided vertex counter of averaged values. */
|
|
int *accumulated_counters;
|
|
|
|
bool have_displacement;
|
|
} SubdivDeformContext;
|
|
|
|
static void subdiv_mesh_prepare_accumulator(SubdivDeformContext *ctx, int num_vertices)
|
|
{
|
|
if (!ctx->have_displacement) {
|
|
return;
|
|
}
|
|
ctx->accumulated_counters = MEM_calloc_arrayN(
|
|
num_vertices, sizeof(*ctx->accumulated_counters), "subdiv accumulated counters");
|
|
}
|
|
|
|
static void subdiv_mesh_context_free(SubdivDeformContext *ctx)
|
|
{
|
|
MEM_SAFE_FREE(ctx->accumulated_counters);
|
|
}
|
|
|
|
/** \} */
|
|
|
|
/* -------------------------------------------------------------------- */
|
|
/** \name Accumulation helpers
|
|
* \{ */
|
|
|
|
static void subdiv_accumulate_vertex_displacement(SubdivDeformContext *ctx,
|
|
const int ptex_face_index,
|
|
const float u,
|
|
const float v,
|
|
int vertex_index)
|
|
{
|
|
Subdiv *subdiv = ctx->subdiv;
|
|
float dummy_P[3], dPdu[3], dPdv[3], D[3];
|
|
BKE_subdiv_eval_limit_point_and_derivatives(subdiv, ptex_face_index, u, v, dummy_P, dPdu, dPdv);
|
|
/* Accumulate displacement if needed. */
|
|
if (ctx->have_displacement) {
|
|
BKE_subdiv_eval_displacement(subdiv, ptex_face_index, u, v, dPdu, dPdv, D);
|
|
/* NOTE: The storage for vertex coordinates is coming from an external world, not necessarily
|
|
* initialized to zeroes. */
|
|
if (ctx->accumulated_counters[vertex_index] == 0) {
|
|
copy_v3_v3(ctx->vertex_cos[vertex_index], D);
|
|
}
|
|
else {
|
|
add_v3_v3(ctx->vertex_cos[vertex_index], D);
|
|
}
|
|
}
|
|
++ctx->accumulated_counters[vertex_index];
|
|
}
|
|
|
|
/** \} */
|
|
|
|
/* -------------------------------------------------------------------- */
|
|
/** \name Subdivision callbacks
|
|
* \{ */
|
|
|
|
static bool subdiv_mesh_topology_info(const SubdivForeachContext *foreach_context,
|
|
const int UNUSED(num_vertices),
|
|
const int UNUSED(num_edges),
|
|
const int UNUSED(num_loops),
|
|
const int UNUSED(num_polygons),
|
|
const int *UNUSED(subdiv_polygon_offset))
|
|
{
|
|
SubdivDeformContext *subdiv_context = foreach_context->user_data;
|
|
subdiv_mesh_prepare_accumulator(subdiv_context, subdiv_context->coarse_mesh->totvert);
|
|
return true;
|
|
}
|
|
|
|
static void subdiv_mesh_vertex_every_corner(const SubdivForeachContext *foreach_context,
|
|
void *UNUSED(tls),
|
|
const int ptex_face_index,
|
|
const float u,
|
|
const float v,
|
|
const int coarse_vertex_index,
|
|
const int UNUSED(coarse_poly_index),
|
|
const int UNUSED(coarse_corner),
|
|
const int UNUSED(subdiv_vertex_index))
|
|
{
|
|
SubdivDeformContext *ctx = foreach_context->user_data;
|
|
subdiv_accumulate_vertex_displacement(ctx, ptex_face_index, u, v, coarse_vertex_index);
|
|
}
|
|
|
|
static void subdiv_mesh_vertex_corner(const SubdivForeachContext *foreach_context,
|
|
void *UNUSED(tls),
|
|
const int ptex_face_index,
|
|
const float u,
|
|
const float v,
|
|
const int coarse_vertex_index,
|
|
const int UNUSED(coarse_poly_index),
|
|
const int UNUSED(coarse_corner),
|
|
const int UNUSED(subdiv_vertex_index))
|
|
{
|
|
SubdivDeformContext *ctx = foreach_context->user_data;
|
|
BLI_assert(coarse_vertex_index != ORIGINDEX_NONE);
|
|
BLI_assert(coarse_vertex_index < ctx->num_verts);
|
|
float inv_num_accumulated = 1.0f;
|
|
if (ctx->accumulated_counters != NULL) {
|
|
inv_num_accumulated = 1.0f / ctx->accumulated_counters[coarse_vertex_index];
|
|
}
|
|
/* Displacement is accumulated in subdiv vertex position.
|
|
* Needs to be backed up before copying data from original vertex. */
|
|
float D[3] = {0.0f, 0.0f, 0.0f};
|
|
float *vertex_co = ctx->vertex_cos[coarse_vertex_index];
|
|
if (ctx->have_displacement) {
|
|
copy_v3_v3(D, vertex_co);
|
|
mul_v3_fl(D, inv_num_accumulated);
|
|
}
|
|
/* Copy custom data and evaluate position. */
|
|
BKE_subdiv_eval_limit_point(ctx->subdiv, ptex_face_index, u, v, vertex_co);
|
|
/* Apply displacement. */
|
|
add_v3_v3(vertex_co, D);
|
|
}
|
|
|
|
/** \} */
|
|
|
|
/* -------------------------------------------------------------------- */
|
|
/** \name Initialization
|
|
* \{ */
|
|
|
|
static void setup_foreach_callbacks(const SubdivDeformContext *subdiv_context,
|
|
SubdivForeachContext *foreach_context)
|
|
{
|
|
memset(foreach_context, 0, sizeof(*foreach_context));
|
|
/* General information. */
|
|
foreach_context->topology_info = subdiv_mesh_topology_info;
|
|
/* Every boundary geometry. Used for displacement and normals averaging. */
|
|
if (subdiv_context->have_displacement) {
|
|
foreach_context->vertex_every_corner = subdiv_mesh_vertex_every_corner;
|
|
}
|
|
foreach_context->vertex_corner = subdiv_mesh_vertex_corner;
|
|
}
|
|
|
|
/** \} */
|
|
|
|
/* -------------------------------------------------------------------- */
|
|
/** \name Public entry point
|
|
* \{ */
|
|
|
|
void BKE_subdiv_deform_coarse_vertices(struct Subdiv *subdiv,
|
|
const struct Mesh *coarse_mesh,
|
|
float (*vertex_cos)[3],
|
|
int num_verts)
|
|
{
|
|
BKE_subdiv_stats_begin(&subdiv->stats, SUBDIV_STATS_SUBDIV_TO_MESH);
|
|
/* Make sure evaluator is up to date with possible new topology, and that
|
|
* is refined for the new positions of coarse vertices. */
|
|
if (!BKE_subdiv_eval_begin_from_mesh(
|
|
subdiv, coarse_mesh, vertex_cos, SUBDIV_EVALUATOR_TYPE_CPU, NULL)) {
|
|
/* This could happen in two situations:
|
|
* - OpenSubdiv is disabled.
|
|
* - Something totally bad happened, and OpenSubdiv rejected our
|
|
* topology.
|
|
* In either way, we can't safely continue. */
|
|
if (coarse_mesh->totpoly) {
|
|
BKE_subdiv_stats_end(&subdiv->stats, SUBDIV_STATS_SUBDIV_TO_MESH);
|
|
return;
|
|
}
|
|
}
|
|
|
|
/* Initialize subdivision mesh creation context. */
|
|
SubdivDeformContext subdiv_context = {0};
|
|
subdiv_context.coarse_mesh = coarse_mesh;
|
|
subdiv_context.subdiv = subdiv;
|
|
subdiv_context.vertex_cos = vertex_cos;
|
|
subdiv_context.num_verts = num_verts;
|
|
subdiv_context.have_displacement = (subdiv->displacement_evaluator != NULL);
|
|
|
|
SubdivForeachContext foreach_context;
|
|
setup_foreach_callbacks(&subdiv_context, &foreach_context);
|
|
foreach_context.user_data = &subdiv_context;
|
|
|
|
/* Dummy mesh rasterization settings. */
|
|
SubdivToMeshSettings mesh_settings;
|
|
mesh_settings.resolution = 1;
|
|
mesh_settings.use_optimal_display = false;
|
|
|
|
/* Multi-threaded traversal/evaluation. */
|
|
BKE_subdiv_stats_begin(&subdiv->stats, SUBDIV_STATS_SUBDIV_TO_MESH_GEOMETRY);
|
|
BKE_subdiv_foreach_subdiv_geometry(subdiv, &foreach_context, &mesh_settings, coarse_mesh);
|
|
BKE_subdiv_stats_end(&subdiv->stats, SUBDIV_STATS_SUBDIV_TO_MESH_GEOMETRY);
|
|
|
|
// BKE_mesh_validate(result, true, true);
|
|
BKE_subdiv_stats_end(&subdiv->stats, SUBDIV_STATS_SUBDIV_TO_MESH);
|
|
|
|
/* Free used memory. */
|
|
subdiv_mesh_context_free(&subdiv_context);
|
|
}
|
|
|
|
/** \} */
|