Sculpt: PBVH node splitting for texture paint

`PBVH_Leaf` nodes are now split into a new `PBVH_TexLeaf`
node type when using the paint brush.  These nodes are
split by image pixels, not triangles.  This greatly
increases performance when working with large
textures on low-poly meshes.

Reviewed By: Jeroen Bakker
Differential Revision: https://developer.blender.org/D14900
Ref: D14900
This commit is contained in:
Joseph Eagar 2023-01-23 10:29:40 -08:00
parent 8afcecdf1f
commit b6b6e47e1d
Notes: blender-bot 2023-03-17 13:20:00 +01:00
Referenced by issue #97353, PBVH: Split PBVH Pixels into more nodes.
Referenced by issue #105511, When importing Einar v1 from Blenderstudio the rig is borked
15 changed files with 541 additions and 67 deletions

View File

@ -163,7 +163,8 @@ typedef enum {
PBVH_UpdateTopology = 1 << 13,
PBVH_UpdateColor = 1 << 14,
PBVH_RebuildPixels = 1 << 15,
PBVH_TopologyUpdated = 1 << 16, /* Used internally by pbvh_bmesh.c */
PBVH_TexLeaf = 1 << 16,
PBVH_TopologyUpdated = 1 << 17, /* Used internally by pbvh_bmesh.c */
} PBVHNodeFlags;
@ -337,7 +338,12 @@ void BKE_pbvh_search_callback(PBVH *pbvh,
void BKE_pbvh_search_gather(
PBVH *pbvh, BKE_pbvh_SearchCallback scb, void *search_data, PBVHNode ***array, int *tot);
void BKE_pbvh_search_gather_ex(PBVH *pbvh,
BKE_pbvh_SearchCallback scb,
void *search_data,
PBVHNode ***r_array,
int *r_tot,
PBVHNodeFlags leaf_flag);
/* Ray-cast
* the hit callback is called for all leaf nodes intersecting the ray;
* it's up to the callback to find the primitive within the leaves that is

View File

@ -200,6 +200,10 @@ struct NodeData {
{
undo_regions.clear();
for (UDIMTilePixels &tile : tiles) {
if (tile.pixel_rows.size() == 0) {
continue;
}
rcti region;
BLI_rcti_init_minmax(&region);
for (PackedPixelRow &pixel_row : tile.pixel_rows) {

View File

@ -1020,7 +1020,9 @@ void BKE_pbvh_free(PBVH *pbvh)
if (node->bm_other_verts) {
BLI_gset_free(node->bm_other_verts, NULL);
}
}
if (node->flag & (PBVH_Leaf | PBVH_TexLeaf)) {
pbvh_node_pixels_free(node);
}
}
@ -1094,7 +1096,7 @@ static void pbvh_stack_push(PBVHIter *iter, PBVHNode *node, bool revisiting)
iter->stacksize++;
}
static PBVHNode *pbvh_iter_next(PBVHIter *iter)
static PBVHNode *pbvh_iter_next(PBVHIter *iter, PBVHNodeFlags leaf_flag)
{
/* purpose here is to traverse tree, visiting child nodes before their
* parents, this order is necessary for e.g. computing bounding boxes */
@ -1121,7 +1123,7 @@ static PBVHNode *pbvh_iter_next(PBVHIter *iter)
continue; /* don't traverse, outside of search zone */
}
if (node->flag & PBVH_Leaf) {
if (node->flag & leaf_flag) {
/* immediately hit leaf node */
return node;
}
@ -1166,8 +1168,12 @@ static PBVHNode *pbvh_iter_next_occluded(PBVHIter *iter)
return NULL;
}
void BKE_pbvh_search_gather(
PBVH *pbvh, BKE_pbvh_SearchCallback scb, void *search_data, PBVHNode ***r_array, int *r_tot)
void BKE_pbvh_search_gather_ex(PBVH *pbvh,
BKE_pbvh_SearchCallback scb,
void *search_data,
PBVHNode ***r_array,
int *r_tot,
PBVHNodeFlags leaf_flag)
{
PBVHIter iter;
PBVHNode **array = NULL, *node;
@ -1175,8 +1181,8 @@ void BKE_pbvh_search_gather(
pbvh_iter_begin(&iter, pbvh, scb, search_data);
while ((node = pbvh_iter_next(&iter))) {
if (node->flag & PBVH_Leaf) {
while ((node = pbvh_iter_next(&iter, leaf_flag))) {
if (node->flag & leaf_flag) {
if (UNLIKELY(tot == space)) {
/* resize array if needed */
space = (tot == 0) ? 32 : space * 2;
@ -1199,6 +1205,12 @@ void BKE_pbvh_search_gather(
*r_tot = tot;
}
void BKE_pbvh_search_gather(
PBVH *pbvh, BKE_pbvh_SearchCallback scb, void *search_data, PBVHNode ***r_array, int *r_tot)
{
BKE_pbvh_search_gather_ex(pbvh, scb, search_data, r_array, r_tot, PBVH_Leaf);
}
void BKE_pbvh_search_callback(PBVH *pbvh,
BKE_pbvh_SearchCallback scb,
void *search_data,
@ -1210,7 +1222,7 @@ void BKE_pbvh_search_callback(PBVH *pbvh,
pbvh_iter_begin(&iter, pbvh, scb, search_data);
while ((node = pbvh_iter_next(&iter))) {
while ((node = pbvh_iter_next(&iter, PBVH_Leaf))) {
if (node->flag & PBVH_Leaf) {
hcb(node, hit_data);
}
@ -1946,7 +1958,7 @@ void BKE_pbvh_redraw_BB(PBVH *pbvh, float bb_min[3], float bb_max[3])
pbvh_iter_begin(&iter, pbvh, NULL, NULL);
while ((node = pbvh_iter_next(&iter))) {
while ((node = pbvh_iter_next(&iter, PBVH_Leaf))) {
if (node->flag & PBVH_UpdateRedraw) {
BB_expand_with_bb(&bb, &node->vb);
}
@ -1966,7 +1978,7 @@ void BKE_pbvh_get_grid_updates(PBVH *pbvh, bool clear, void ***r_gridfaces, int
pbvh_iter_begin(&iter, pbvh, NULL, NULL);
while ((node = pbvh_iter_next(&iter))) {
while ((node = pbvh_iter_next(&iter, PBVH_Leaf))) {
if (node->flag & PBVH_UpdateNormals) {
for (uint i = 0; i < node->totprim; i++) {
void *face = pbvh->gridfaces[node->prim_indices[i]];
@ -3147,9 +3159,24 @@ void BKE_pbvh_draw_debug_cb(PBVH *pbvh,
PBVHNodeFlags flag),
void *user_data)
{
PBVHNodeFlags flag = PBVH_Leaf;
for (int a = 0; a < pbvh->totnode; a++) {
PBVHNode *node = &pbvh->nodes[a];
if (node->flag & PBVH_TexLeaf) {
flag = PBVH_TexLeaf;
break;
}
}
for (int a = 0; a < pbvh->totnode; a++) {
PBVHNode *node = &pbvh->nodes[a];
if (!(node->flag & flag)) {
continue;
}
draw_fn(node, user_data, node->vb.bmin, node->vb.bmax, node->flag);
}
}

View File

@ -150,6 +150,8 @@ struct PBVH {
int faces_num; /* Do not use directly, use BKE_pbvh_num_faces. */
int leaf_limit;
int pixel_leaf_limit;
int depth_limit;
/* Mesh data */
struct Mesh *mesh;

View File

@ -15,7 +15,9 @@
#include "BLI_math.h"
#include "BLI_task.h"
#include "PIL_time.h"
#include "BKE_global.h"
#include "BKE_image_wrappers.hh"
#include "bmesh.h"
@ -25,12 +27,6 @@
namespace blender::bke::pbvh::pixels {
/**
* During debugging this check could be enabled.
* It will write to each image pixel that is covered by the PBVH.
*/
constexpr bool USE_WATERTIGHT_CHECK = false;
/**
* Calculate the delta of two neighbor UV coordinates in the given image buffer.
*/
@ -57,6 +53,315 @@ static float2 calc_barycentric_delta_x(const ImBuf *image_buffer,
return calc_barycentric_delta(uvs, start_uv, end_uv);
}
static int count_node_pixels(PBVHNode &node)
{
if (!node.pixels.node_data) {
return 0;
}
NodeData &data = BKE_pbvh_pixels_node_data_get(node);
int totpixel = 0;
for (UDIMTilePixels &tile : data.tiles) {
for (PackedPixelRow &row : tile.pixel_rows) {
totpixel += row.num_pixels;
}
}
return totpixel;
}
struct SplitQueueData {
ThreadQueue *new_nodes;
TaskPool *pool;
PBVH *pbvh;
Mesh *mesh;
Image *image;
ImageUser *image_user;
};
struct SplitNodePair {
SplitNodePair *parent;
PBVHNode node;
int children_offset = 0;
int depth = 0;
int source_index = -1;
bool is_old = false;
SplitQueueData *tdata;
SplitNodePair(SplitNodePair *node_parent = nullptr) : parent(node_parent)
{
memset(static_cast<void *>(&node), 0, sizeof(PBVHNode));
}
};
static void split_thread_job(TaskPool *__restrict pool, void *taskdata);
static void split_pixel_node(PBVH *pbvh,
SplitNodePair *split,
Mesh *mesh,
Image *image,
ImageUser *image_user,
SplitQueueData *tdata)
{
BB cb;
PBVHNode *node = &split->node;
cb = node->vb;
if (count_node_pixels(*node) <= pbvh->pixel_leaf_limit || split->depth >= pbvh->depth_limit) {
BKE_pbvh_pixels_node_data_get(split->node).rebuild_undo_regions();
return;
}
/* Find widest axis and its midpoint */
const int axis = BB_widest_axis(&cb);
const float mid = (cb.bmax[axis] + cb.bmin[axis]) * 0.5f;
node->flag = (PBVHNodeFlags)((int)node->flag & (int)~PBVH_TexLeaf);
SplitNodePair *split1 = MEM_new<SplitNodePair>("split_pixel_node split1", split);
SplitNodePair *split2 = MEM_new<SplitNodePair>("split_pixel_node split1", split);
split1->depth = split->depth + 1;
split2->depth = split->depth + 1;
PBVHNode *child1 = &split1->node;
PBVHNode *child2 = &split2->node;
child1->flag = PBVH_TexLeaf;
child2->flag = PBVH_TexLeaf;
child1->vb = cb;
child1->vb.bmax[axis] = mid;
child2->vb = cb;
child2->vb.bmin[axis] = mid;
NodeData &data = BKE_pbvh_pixels_node_data_get(split->node);
NodeData *data1 = MEM_new<NodeData>(__func__);
NodeData *data2 = MEM_new<NodeData>(__func__);
child1->pixels.node_data = static_cast<void *>(data1);
child2->pixels.node_data = static_cast<void *>(data2);
data1->uv_primitives = data.uv_primitives;
data2->uv_primitives = data.uv_primitives;
data1->tiles.resize(data.tiles.size());
data2->tiles.resize(data.tiles.size());
for (int i : IndexRange(data.tiles.size())) {
UDIMTilePixels &tile = data.tiles[i];
UDIMTilePixels &tile1 = data1->tiles[i];
UDIMTilePixels &tile2 = data2->tiles[i];
tile1.tile_number = tile2.tile_number = tile.tile_number;
tile1.flags.dirty = tile2.flags.dirty = 0;
}
ImageUser image_user2 = *image_user;
for (int i : IndexRange(data.tiles.size())) {
const UDIMTilePixels &tile = data.tiles[i];
image_user2.tile = tile.tile_number;
ImBuf *image_buffer = BKE_image_acquire_ibuf(image, &image_user2, nullptr);
if (image_buffer == nullptr) {
continue;
}
const float(*vert_cos)[3] = BKE_pbvh_get_vert_positions(pbvh);
PBVHData &pbvh_data = BKE_pbvh_pixels_data_get(*pbvh);
for (const PackedPixelRow &row : tile.pixel_rows) {
UDIMTilePixels *tile1 = &data1->tiles[i];
UDIMTilePixels *tile2 = &data2->tiles[i];
UVPrimitivePaintInput &uv_prim = data.uv_primitives.paint_input[row.uv_primitive_index];
int3 tri = pbvh_data.geom_primitives.vert_indices[uv_prim.geometry_primitive_index];
float verts[3][3];
copy_v3_v3(verts[0], vert_cos[tri[0]]);
copy_v3_v3(verts[1], vert_cos[tri[1]]);
copy_v3_v3(verts[2], vert_cos[tri[2]]);
float2 delta = uv_prim.delta_barycentric_coord_u;
float2 uv1 = row.start_barycentric_coord;
float2 uv2 = row.start_barycentric_coord + delta * (float)row.num_pixels;
float co1[3];
float co2[3];
interp_barycentric_tri_v3(verts, uv1[0], uv1[1], co1);
interp_barycentric_tri_v3(verts, uv2[0], uv2[1], co2);
/* Are we spanning the midpoint? */
if ((co1[axis] <= mid) != (co2[axis] <= mid)) {
PackedPixelRow row1 = row;
float t;
if (mid < co1[axis]) {
t = 1.0f - (mid - co2[axis]) / (co1[axis] - co2[axis]);
SWAP(UDIMTilePixels *, tile1, tile2);
}
else {
t = (mid - co1[axis]) / (co2[axis] - co1[axis]);
}
int num_pixels = (int)floorf((float)row.num_pixels * t);
if (num_pixels) {
row1.num_pixels = num_pixels;
tile1->pixel_rows.append(row1);
}
if (num_pixels != row.num_pixels) {
PackedPixelRow row2 = row;
row2.num_pixels = row.num_pixels - num_pixels;
row2.start_barycentric_coord = row.start_barycentric_coord +
uv_prim.delta_barycentric_coord_u * (float)num_pixels;
row2.start_image_coordinate = row.start_image_coordinate;
row2.start_image_coordinate[0] += num_pixels;
tile2->pixel_rows.append(row2);
}
}
else if (co1[axis] <= mid && co2[axis] <= mid) {
tile1->pixel_rows.append(row);
}
else {
tile2->pixel_rows.append(row);
}
}
BKE_image_release_ibuf(image, image_buffer, nullptr);
}
data.undo_regions.clear();
if (node->flag & PBVH_Leaf) {
data.clear_data();
}
else {
pbvh_node_pixels_free(node);
}
BLI_thread_queue_push(tdata->new_nodes, static_cast<void *>(split1));
BLI_thread_queue_push(tdata->new_nodes, static_cast<void *>(split2));
BLI_task_pool_push(tdata->pool, split_thread_job, static_cast<void *>(split1), false, nullptr);
BLI_task_pool_push(tdata->pool, split_thread_job, static_cast<void *>(split2), false, nullptr);
}
static void split_flush_final_nodes(SplitQueueData *tdata)
{
PBVH *pbvh = tdata->pbvh;
Vector<SplitNodePair *> splits;
while (!BLI_thread_queue_is_empty(tdata->new_nodes)) {
SplitNodePair *newsplit = static_cast<SplitNodePair *>(BLI_thread_queue_pop(tdata->new_nodes));
splits.append(newsplit);
if (newsplit->is_old) {
continue;
}
if (!newsplit->parent->children_offset) {
newsplit->parent->children_offset = pbvh->totnode;
pbvh_grow_nodes(pbvh, pbvh->totnode + 2);
newsplit->source_index = newsplit->parent->children_offset;
}
else {
newsplit->source_index = newsplit->parent->children_offset + 1;
}
}
for (SplitNodePair *split : splits) {
BLI_assert(split->source_index != -1);
split->node.children_offset = split->children_offset;
pbvh->nodes[split->source_index] = split->node;
}
for (SplitNodePair *split : splits) {
MEM_delete<SplitNodePair>(split);
}
}
static void split_thread_job(TaskPool *__restrict pool, void *taskdata)
{
SplitQueueData *tdata = static_cast<SplitQueueData *>(BLI_task_pool_user_data(pool));
SplitNodePair *split = static_cast<SplitNodePair *>(taskdata);
split_pixel_node(tdata->pbvh, split, tdata->mesh, tdata->image, tdata->image_user, tdata);
}
static void split_pixel_nodes(PBVH *pbvh, Mesh *mesh, Image *image, ImageUser *image_user)
{
if (G.debug_value == 891) {
return;
}
if (!pbvh->depth_limit) {
pbvh->depth_limit = 40; /* TODO: move into a constant */
}
if (!pbvh->pixel_leaf_limit) {
pbvh->pixel_leaf_limit = 256 * 256; /* TODO: move into a constant */
}
SplitQueueData tdata;
TaskPool *pool = BLI_task_pool_create_suspended(&tdata, TASK_PRIORITY_HIGH);
tdata.pool = pool;
tdata.pbvh = pbvh;
tdata.mesh = mesh;
tdata.image = image;
tdata.image_user = image_user;
tdata.new_nodes = BLI_thread_queue_init();
/* Set up initial jobs before initializing threads. */
for (int i : IndexRange(pbvh->totnode)) {
if (pbvh->nodes[i].flag & PBVH_TexLeaf) {
SplitNodePair *split = MEM_new<SplitNodePair>("split_pixel_nodes split");
split->source_index = i;
split->is_old = true;
split->node = pbvh->nodes[i];
split->tdata = &tdata;
BLI_task_pool_push(pool, split_thread_job, static_cast<void *>(split), false, nullptr);
BLI_thread_queue_push(tdata.new_nodes, static_cast<void *>(split));
}
}
BLI_task_pool_work_and_wait(pool);
BLI_task_pool_free(pool);
split_flush_final_nodes(&tdata);
BLI_thread_queue_free(tdata.new_nodes);
}
/**
* During debugging this check could be enabled.
* It will write to each image pixel that is covered by the PBVH.
*/
constexpr bool USE_WATERTIGHT_CHECK = false;
static void extract_barycentric_pixels(UDIMTilePixels &tile_data,
const ImBuf *image_buffer,
const uv_islands::UVIslandsMask &uv_mask,
@ -233,7 +538,10 @@ static void do_encode_pixels(void *__restrict userdata,
static bool should_pixels_be_updated(PBVHNode *node)
{
if ((node->flag & PBVH_Leaf) == 0) {
if ((node->flag & (PBVH_Leaf | PBVH_TexLeaf)) == 0) {
return false;
}
if (node->children_offset != 0) {
return false;
}
if ((node->flag & PBVH_RebuildPixels) != 0) {
@ -349,17 +657,17 @@ static void apply_watertight_check(PBVH *pbvh, Image *image, ImageUser *image_us
BKE_image_partial_update_mark_full_update(image);
}
static void update_pixels(PBVH *pbvh, Mesh *mesh, Image *image, ImageUser *image_user)
static bool update_pixels(PBVH *pbvh, Mesh *mesh, Image *image, ImageUser *image_user)
{
Vector<PBVHNode *> nodes_to_update;
if (!find_nodes_to_update(pbvh, nodes_to_update)) {
return;
return false;
}
const StringRef active_uv_name = CustomData_get_active_layer_name(&mesh->ldata, CD_PROP_FLOAT2);
if (active_uv_name.is_empty()) {
return;
return false;
}
const AttributeAccessor attributes = mesh->attributes();
@ -422,6 +730,15 @@ static void update_pixels(PBVH *pbvh, Mesh *mesh, Image *image, ImageUser *image
node->flag = static_cast<PBVHNodeFlags>(node->flag & ~PBVH_RebuildPixels);
}
/* Add PBVH_TexLeaf flag */
for (int i : IndexRange(pbvh->totnode)) {
PBVHNode &node = pbvh->nodes[i];
if (node.flag & PBVH_Leaf) {
node.flag = (PBVHNodeFlags)((int)node.flag | (int)PBVH_TexLeaf);
}
}
//#define DO_PRINT_STATISTICS
#ifdef DO_PRINT_STATISTICS
/* Print some statistics about compression ratio. */
@ -434,7 +751,6 @@ static void update_pixels(PBVH *pbvh, Mesh *mesh, Image *image, ImageUser *image
continue;
}
NodeData *node_data = static_cast<NodeData *>(node->pixels.node_data);
compressed_data_len += node_data->triangles.mem_size();
for (const UDIMTilePixels &tile_data : node_data->tiles) {
compressed_data_len += tile_data.encoded_pixels.size() * sizeof(PackedPixelRow);
for (const PackedPixelRow &encoded_pixels : tile_data.encoded_pixels) {
@ -448,6 +764,8 @@ static void update_pixels(PBVH *pbvh, Mesh *mesh, Image *image, ImageUser *image
float(compressed_data_len) / num_pixels);
}
#endif
return true;
}
NodeData &BKE_pbvh_pixels_node_data_get(PBVHNode &node)
@ -484,7 +802,6 @@ void BKE_pbvh_pixels_mark_image_dirty(PBVHNode &node, Image &image, ImageUser &i
node_data->flags.dirty = false;
}
}
} // namespace blender::bke::pbvh::pixels
extern "C" {
@ -492,12 +809,19 @@ using namespace blender::bke::pbvh::pixels;
void BKE_pbvh_build_pixels(PBVH *pbvh, Mesh *mesh, Image *image, ImageUser *image_user)
{
update_pixels(pbvh, mesh, image, image_user);
if (update_pixels(pbvh, mesh, image, image_user)) {
split_pixel_nodes(pbvh, mesh, image, image_user);
}
}
void pbvh_node_pixels_free(PBVHNode *node)
{
NodeData *node_data = static_cast<NodeData *>(node->pixels.node_data);
if (!node_data) {
return;
}
MEM_delete(node_data);
node->pixels.node_data = nullptr;
}

View File

@ -10,9 +10,11 @@
#include "DRW_render.h"
#include "BKE_global.h"
#include "BKE_object.h"
#include "BKE_paint.h"
#include "BKE_particle.h"
#include "BKE_pbvh.h"
#include "BLI_alloca.h"
@ -219,6 +221,16 @@ static void basic_cache_populate(void *vedata, Object *ob)
DRW_shgroup_call(shgrp, geom, ob);
}
}
if (G.debug_value == 889 && ob->sculpt && ob->sculpt->pbvh) {
int debug_node_nr = 0;
DRW_debug_modelmat(ob->object_to_world);
BKE_pbvh_draw_debug_cb(
ob->sculpt->pbvh,
(void (*)(void *d, const float min[3], const float max[3], PBVHNodeFlags f))
DRW_sculpt_debug_cb,
&debug_node_nr);
}
}
}

View File

@ -15,6 +15,7 @@
#include "BLI_rand.h"
#include "BLI_string_utils.h"
#include "BKE_global.h"
#include "BKE_paint.h"
#include "BKE_particle.h"
@ -883,6 +884,16 @@ void EEVEE_materials_cache_populate(EEVEE_Data *vedata,
*cast_shadow = *cast_shadow || (matcache[i].shadow_grp != NULL);
}
}
if (G.debug_value == 889 && ob->sculpt && ob->sculpt->pbvh) {
int debug_node_nr = 0;
DRW_debug_modelmat(ob->object_to_world);
BKE_pbvh_draw_debug_cb(
ob->sculpt->pbvh,
(void (*)(void *d, const float min[3], const float max[3], PBVHNodeFlags f))
DRW_sculpt_debug_cb,
&debug_node_nr);
}
}
/* Motion Blur Vectors. */

View File

@ -17,6 +17,7 @@
#include "BKE_context.h"
#include "BKE_layer.h"
#include "BKE_material.h"
#include "BKE_pbvh.h"
#include "BKE_scene.h"
#include "BLT_translation.h"
@ -1007,6 +1008,9 @@ void DRW_mesh_batch_cache_get_attributes(struct Object *object,
struct DRW_Attributes **r_attrs,
struct DRW_MeshCDMask **r_cd_needed);
void DRW_sculpt_debug_cb(
PBVHNode *node, void *user_data, const float bmin[3], const float bmax[3], PBVHNodeFlags flag);
#ifdef __cplusplus
}
#endif

View File

@ -1255,7 +1255,7 @@ static void sculpt_draw_cb(DRWSculptCallbackData *scd,
}
}
static void sculpt_debug_cb(
void DRW_sculpt_debug_cb(
PBVHNode *node, void *user_data, const float bmin[3], const float bmax[3], PBVHNodeFlags flag)
{
int *debug_node_nr = (int *)user_data;
@ -1270,7 +1270,8 @@ static void sculpt_debug_cb(
DRW_debug_bbox(&bb, (float[4]){0.5f, 0.5f, 0.5f, 0.6f});
}
#else /* Color coded leaf bounds. */
if (flag & PBVH_Leaf) {
if (flag & (PBVH_Leaf | PBVH_TexLeaf)) {
DRW_debug_bbox(&bb, SCULPT_DEBUG_COLOR((*debug_node_nr)++));
int color = (*debug_node_nr)++;
color += BKE_pbvh_debug_draw_gen_get(node);
@ -1370,7 +1371,7 @@ static void drw_sculpt_generate_calls(DRWSculptCallbackData *scd)
BKE_pbvh_draw_debug_cb(
pbvh,
(void (*)(PBVHNode * n, void *d, const float min[3], const float max[3], PBVHNodeFlags f))
sculpt_debug_cb,
DRW_sculpt_debug_cb,
&debug_node_nr);
}
}

View File

@ -333,7 +333,7 @@ struct DRWDebugVert {
BLI_STATIC_ASSERT_ALIGN(DRWDebugVert, 16)
/* Take the header (DrawCommand) into account. */
#define DRW_DEBUG_DRAW_VERT_MAX (64 * 1024) - 1
#define DRW_DEBUG_DRAW_VERT_MAX (64 * 8192) - 1
/* The debug draw buffer is laid-out as the following struct.
* But we use plain array in shader code instead because of driver issues. */

View File

@ -2775,15 +2775,21 @@ static PBVHNode **sculpt_pbvh_gather_cursor_update(Object *ob,
return nodes;
}
static PBVHNode **sculpt_pbvh_gather_generic(Object *ob,
Sculpt *sd,
const Brush *brush,
bool use_original,
float radius_scale,
int *r_totnode)
static PBVHNode **sculpt_pbvh_gather_generic_intern(Object *ob,
Sculpt *sd,
const Brush *brush,
bool use_original,
float radius_scale,
int *r_totnode,
PBVHNodeFlags flag)
{
SculptSession *ss = ob->sculpt;
PBVHNode **nodes = nullptr;
PBVHNodeFlags leaf_flag = PBVH_Leaf;
if (flag & PBVH_TexLeaf) {
leaf_flag = PBVH_TexLeaf;
}
/* Build a list of all nodes that are potentially within the cursor or brush's area of influence.
*/
@ -2795,7 +2801,7 @@ static PBVHNode **sculpt_pbvh_gather_generic(Object *ob,
data.original = use_original;
data.ignore_fully_ineffective = brush->sculpt_tool != SCULPT_TOOL_MASK;
data.center = nullptr;
BKE_pbvh_search_gather(ss->pbvh, SCULPT_search_sphere_cb, &data, &nodes, r_totnode);
BKE_pbvh_search_gather_ex(ss->pbvh, SCULPT_search_sphere_cb, &data, &nodes, r_totnode, leaf_flag);
}
else {
DistRayAABB_Precalc dist_ray_to_aabb_precalc;
@ -2809,11 +2815,33 @@ static PBVHNode **sculpt_pbvh_gather_generic(Object *ob,
data.original = use_original;
data.dist_ray_to_aabb_precalc = &dist_ray_to_aabb_precalc;
data.ignore_fully_ineffective = brush->sculpt_tool != SCULPT_TOOL_MASK;
BKE_pbvh_search_gather(ss->pbvh, SCULPT_search_circle_cb, &data, &nodes, r_totnode);
BKE_pbvh_search_gather_ex(ss->pbvh, SCULPT_search_circle_cb, &data, &nodes, r_totnode, leaf_flag);
}
return nodes;
}
static PBVHNode **sculpt_pbvh_gather_generic(Object *ob,
Sculpt *sd,
const Brush *brush,
bool use_original,
float radius_scale,
int *r_totnode)
{
return sculpt_pbvh_gather_generic_intern(
ob, sd, brush, use_original, radius_scale, r_totnode, PBVH_Leaf);
}
static PBVHNode **sculpt_pbvh_gather_texpaint(Object *ob,
Sculpt *sd,
const Brush *brush,
bool use_original,
float radius_scale,
int *r_totnode)
{
return sculpt_pbvh_gather_generic_intern(
ob, sd, brush, use_original, radius_scale, r_totnode, PBVH_TexLeaf);
}
/* Calculate primary direction of movement for many brushes. */
static void calc_sculpt_normal(
Sculpt *sd, Object *ob, PBVHNode **nodes, int totnode, float r_area_no[3])
@ -3440,8 +3468,8 @@ static void do_brush_action(Sculpt *sd,
PaintModeSettings *paint_mode_settings)
{
SculptSession *ss = ob->sculpt;
int totnode;
PBVHNode **nodes;
int totnode, texnodes_num = 0;
PBVHNode **nodes, **texnodes = NULL;
/* Check for unsupported features. */
PBVHType type = BKE_pbvh_type(ss->pbvh);
@ -3454,6 +3482,20 @@ static void do_brush_action(Sculpt *sd,
BKE_pbvh_ensure_node_loops(ss->pbvh);
}
const bool use_original = sculpt_tool_needs_original(brush->sculpt_tool) ? true :
ss->cache->original;
const bool use_pixels = sculpt_needs_pbvh_pixels(paint_mode_settings, brush, ob);
if (sculpt_needs_pbvh_pixels(paint_mode_settings, brush, ob)) {
sculpt_pbvh_update_pixels(paint_mode_settings, ss, ob);
texnodes = sculpt_pbvh_gather_texpaint(ob, sd, brush, use_original, 1.0f, &texnodes_num);
if (!texnodes_num) {
return;
}
}
/* Build a list of all nodes that are potentially within the brush's area of influence */
if (SCULPT_tool_needs_all_pbvh_nodes(brush)) {
@ -3464,8 +3506,6 @@ static void do_brush_action(Sculpt *sd,
nodes = SCULPT_cloth_brush_affected_nodes_gather(ss, brush, &totnode);
}
else {
const bool use_original = sculpt_tool_needs_original(brush->sculpt_tool) ? true :
ss->cache->original;
float radius_scale = 1.0f;
/* Corners of square brushes can go outside the brush radius. */
@ -3480,10 +3520,6 @@ static void do_brush_action(Sculpt *sd,
}
nodes = sculpt_pbvh_gather_generic(ob, sd, brush, use_original, radius_scale, &totnode);
}
const bool use_pixels = sculpt_needs_pbvh_pixels(paint_mode_settings, brush, ob);
if (use_pixels) {
sculpt_pbvh_update_pixels(paint_mode_settings, ss, ob);
}
/* Draw Face Sets in draw mode makes a single undo push, in alt-smooth mode deforms the
* vertices and uses regular coords undo. */
@ -3524,6 +3560,7 @@ static void do_brush_action(Sculpt *sd,
/* Only act if some verts are inside the brush area. */
if (totnode == 0) {
MEM_SAFE_FREE(texnodes);
return;
}
float location[3];
@ -3671,7 +3708,7 @@ static void do_brush_action(Sculpt *sd,
SCULPT_do_displacement_smear_brush(sd, ob, nodes, totnode);
break;
case SCULPT_TOOL_PAINT:
SCULPT_do_paint_brush(paint_mode_settings, sd, ob, nodes, totnode);
SCULPT_do_paint_brush(paint_mode_settings, sd, ob, nodes, totnode, texnodes, texnodes_num);
break;
case SCULPT_TOOL_SMEAR:
SCULPT_do_smear_brush(sd, ob, nodes, totnode);
@ -3715,6 +3752,7 @@ static void do_brush_action(Sculpt *sd,
}
MEM_SAFE_FREE(nodes);
MEM_SAFE_FREE(texnodes);
/* Update average stroke position. */
copy_v3_v3(location, ss->cache->true_location);

View File

@ -1789,7 +1789,9 @@ void SCULPT_do_paint_brush(struct PaintModeSettings *paint_mode_settings,
Sculpt *sd,
Object *ob,
PBVHNode **nodes,
int totnode) ATTR_NONNULL();
int totnode,
PBVHNode **texnodes,
int texnodes_num) ATTR_NONNULL();
/**
* \brief Get the image canvas for painting on the given object.
@ -1806,7 +1808,9 @@ void SCULPT_do_paint_brush_image(struct PaintModeSettings *paint_mode_settings,
Sculpt *sd,
Object *ob,
PBVHNode **nodes,
int totnode) ATTR_NONNULL();
int totnode,
PBVHNode **texnodes,
int texnode_num) ATTR_NONNULL();
bool SCULPT_use_image_paint_brush(struct PaintModeSettings *settings, Object *ob) ATTR_NONNULL();
/* Smear Brush. */

View File

@ -249,11 +249,17 @@ static void sample_wet_paint_reduce(const void *__restrict UNUSED(userdata),
add_v4_v4(join->color, swptd->color);
}
void SCULPT_do_paint_brush(
PaintModeSettings *paint_mode_settings, Sculpt *sd, Object *ob, PBVHNode **nodes, int totnode)
void SCULPT_do_paint_brush(PaintModeSettings *paint_mode_settings,
Sculpt *sd,
Object *ob,
PBVHNode **nodes,
int totnode,
PBVHNode **texnodes,
int texnodes_num)
{
if (SCULPT_use_image_paint_brush(paint_mode_settings, ob)) {
SCULPT_do_paint_brush_image(paint_mode_settings, sd, ob, nodes, totnode);
SCULPT_do_paint_brush_image(
paint_mode_settings, sd, ob, nodes, totnode, texnodes, texnodes_num);
return;
}

View File

@ -1,6 +1,9 @@
/* SPDX-License-Identifier: GPL-2.0-or-later
* Copyright 2022 Blender Foundation. All rights reserved. */
/* Paint a color made from hash of node pointer. */
//#define DEBUG_PIXEL_NODES
#include "DNA_image_types.h"
#include "DNA_object_types.h"
@ -9,6 +12,9 @@
#include "BLI_math.h"
#include "BLI_math_color_blend.h"
#include "BLI_task.h"
#ifdef DEBUG_PIXEL_NODES
# include "BLI_hash.h"
#endif
#include "IMB_colormanagement.h"
#include "IMB_imbuf.h"
@ -187,6 +193,15 @@ template<typename ImageBuffer> class PaintingKernel {
automask_data);
float4 paint_color = brush_color * falloff_strength * brush_strength;
float4 buffer_color;
#ifdef DEBUG_PIXEL_NODES
if ((pixel_row.start_image_coordinate.y >> 3) & 1) {
paint_color[0] *= 0.5f;
paint_color[1] *= 0.5f;
paint_color[2] *= 0.5f;
}
#endif
blend_color_mix_float(buffer_color, color, paint_color);
buffer_color *= brush->alpha;
IMB_blend_color_float(color, color, buffer_color, static_cast<IMB_BlendMode>(brush->blend));
@ -199,20 +214,18 @@ template<typename ImageBuffer> class PaintingKernel {
return pixels_painted;
}
void init_brush_color(ImBuf *image_buffer)
void init_brush_color(ImBuf *image_buffer, float in_brush_color[3])
{
const char *to_colorspace = image_accessor.get_colorspace_name(image_buffer);
if (last_used_color_space == to_colorspace) {
return;
}
copy_v3_v3(brush_color,
ss->cache->invert ? BKE_brush_secondary_color_get(ss->scene, brush) :
BKE_brush_color_get(ss->scene, brush));
/* NOTE: Brush colors are stored in sRGB. We use math color to follow other areas that
* use brush colors. From there on we use IMB_colormanagement to convert the brush color to the
* colorspace of the texture. This isn't ideal, but would need more refactoring to make sure
* that brush colors are stored in scene linear by default. */
srgb_to_linearrgb_v3_v3(brush_color, brush_color);
srgb_to_linearrgb_v3_v3(brush_color, in_brush_color);
brush_color[3] = 1.0f;
const char *from_colorspace = IMB_colormanagement_role_colorspace_name_get(
@ -336,6 +349,22 @@ static void do_paint_pixels(void *__restrict userdata,
PaintingKernel<ImageBufferFloat4> kernel_float4(ss, brush, thread_id, positions);
PaintingKernel<ImageBufferByte4> kernel_byte4(ss, brush, thread_id, positions);
float brush_color[4];
#ifdef DEBUG_PIXEL_NODES
uint hash = BLI_hash_int(POINTER_AS_UINT(node));
brush_color[0] = (float)(hash & 255) / 255.0f;
brush_color[1] = (float)((hash >> 8) & 255) / 255.0f;
brush_color[2] = (float)((hash >> 16) & 255) / 255.0f;
#else
copy_v3_v3(brush_color,
ss->cache->invert ? BKE_brush_secondary_color_get(ss->scene, brush) :
BKE_brush_color_get(ss->scene, brush));
#endif
brush_color[3] = 1.0f;
AutomaskingNodeData automask_data;
SCULPT_automasking_node_begin(ob, ss, ss->cache->automasking, &automask_data, data->nodes[n]);
@ -353,10 +382,10 @@ static void do_paint_pixels(void *__restrict userdata,
}
if (image_buffer->rect_float != nullptr) {
kernel_float4.init_brush_color(image_buffer);
kernel_float4.init_brush_color(image_buffer, brush_color);
}
else {
kernel_byte4.init_brush_color(image_buffer);
kernel_byte4.init_brush_color(image_buffer, brush_color);
}
for (const PackedPixelRow &pixel_row : tile_data.pixel_rows) {
@ -520,27 +549,33 @@ bool SCULPT_use_image_paint_brush(PaintModeSettings *settings, Object *ob)
return BKE_paint_canvas_image_get(settings, ob, &image, &image_user);
}
void SCULPT_do_paint_brush_image(
PaintModeSettings *paint_mode_settings, Sculpt *sd, Object *ob, PBVHNode **nodes, int totnode)
void SCULPT_do_paint_brush_image(PaintModeSettings *paint_mode_settings,
Sculpt *sd,
Object *ob,
PBVHNode **nodes,
int totnode,
PBVHNode **texnodes,
int texnodes_num)
{
Brush *brush = BKE_paint_brush(&sd->paint);
TexturePaintingUserData data = {nullptr};
data.ob = ob;
data.brush = brush;
data.nodes = nodes;
data.nodes = texnodes;
if (!ImageData::init_active_image(ob, &data.image_data, paint_mode_settings)) {
return;
}
TaskParallelSettings settings;
BKE_pbvh_parallel_range_settings(&settings, true, totnode);
BLI_task_parallel_range(0, totnode, &data, do_push_undo_tile, &settings);
BLI_task_parallel_range(0, totnode, &data, do_paint_pixels, &settings);
BKE_pbvh_parallel_range_settings(&settings, true, texnodes_num);
BLI_task_parallel_range(0, texnodes_num, &data, do_push_undo_tile, &settings);
BLI_task_parallel_range(0, texnodes_num, &data, do_paint_pixels, &settings);
TaskParallelSettings settings_flush;
BKE_pbvh_parallel_range_settings(&settings_flush, false, totnode);
BLI_task_parallel_range(0, totnode, &data, do_mark_dirty_regions, &settings_flush);
BKE_pbvh_parallel_range_settings(&settings_flush, false, texnodes_num);
BLI_task_parallel_range(0, texnodes_num, &data, do_mark_dirty_regions, &settings_flush);
}
}

View File

@ -193,7 +193,7 @@ static void print_sculpt_node(Object *ob, SculptUndoNode *node)
printf(" %s:%s {applied=%d}\n", undo_type_to_str(node->type), node->idname, node->applied);
if (node->bm_entry) {
BM_log_print_entry(ob->sculpt ? ob->sculpt->bm : NULL, node->bm_entry);
BM_log_print_entry(ob->sculpt ? ob->sculpt->bm : nullptr, node->bm_entry);
}
}
@ -2090,7 +2090,7 @@ static UndoSculpt *sculpt_undo_get_nodes(void)
{
UndoStack *ustack = ED_undo_stack_get();
UndoStep *us = BKE_undosys_stack_init_or_active_with_type(ustack, BKE_UNDOSYS_TYPE_SCULPT);
return sculpt_undosys_step_get_nodes(us);
return us ? sculpt_undosys_step_get_nodes(us) : NULL;
}
/** \} */