diff options
18 files changed, 1295 insertions, 35 deletions
diff --git a/source/blender/blenkernel/BKE_paint.h b/source/blender/blenkernel/BKE_paint.h index db773d34cdc..4ae37095411 100644 --- a/source/blender/blenkernel/BKE_paint.h +++ b/source/blender/blenkernel/BKE_paint.h @@ -30,7 +30,9 @@ struct EdgeSet; struct EnumPropertyItem; struct GHash; struct GridPaintMask; +struct Image; struct ImagePool; +struct ImageUser; struct ListBase; struct MLoop; struct MLoopTri; @@ -650,6 +652,11 @@ typedef struct SculptSession { */ bool sticky_shading_color; + /** + * Last used painting canvas key. + */ + char *last_paint_canvas_key; + } SculptSession; void BKE_sculptsession_free(struct Object *ob); @@ -727,8 +734,16 @@ enum { }; /* paint_canvas.cc */ -struct Image *BKE_paint_canvas_image_get(const struct PaintModeSettings *settings, - struct Object *ob); +/** + * Create a key that can be used to compare with previous ones to identify changes. + * The resulting 'string' is owned by the caller. + */ +char *BKE_paint_canvas_key_get(struct PaintModeSettings *settings, struct Object *ob); + +bool BKE_paint_canvas_image_get(struct PaintModeSettings *settings, + struct Object *ob, + struct Image **r_image, + struct ImageUser **r_image_user); int BKE_paint_canvas_uvmap_layer_index_get(const struct PaintModeSettings *settings, struct Object *ob); diff --git a/source/blender/blenkernel/BKE_pbvh.h b/source/blender/blenkernel/BKE_pbvh.h index 775847f27f8..bb918fcfdcb 100644 --- a/source/blender/blenkernel/BKE_pbvh.h +++ b/source/blender/blenkernel/BKE_pbvh.h @@ -31,10 +31,13 @@ struct MLoopTri; struct MPoly; struct MVert; struct Mesh; +struct MeshElemMap; struct PBVH; struct PBVHNode; struct SubdivCCG; struct TaskParallelSettings; +struct Image; +struct ImageUser; struct MeshElemMap; typedef struct PBVH PBVH; @@ -48,6 +51,15 @@ typedef struct { float (*color)[4]; } PBVHColorBufferNode; +typedef struct PBVHPixelsNode { + /** + * Contains triangle/pixel data used during texture painting. + * + * Contains #blender::bke::pbvh::pixels::NodeData. + */ + void *node_data; +} PBVHPixelsNode; + typedef enum { PBVH_Leaf = 1 << 0, @@ -66,6 +78,8 @@ typedef enum { PBVH_UpdateTopology = 1 << 13, PBVH_UpdateColor = 1 << 14, + PBVH_RebuildPixels = 1 << 15, + } PBVHNodeFlags; typedef struct PBVHFrustumPlanes { @@ -127,6 +141,12 @@ void BKE_pbvh_build_bmesh(PBVH *pbvh, struct BMLog *log, int cd_vert_node_offset, int cd_face_node_offset); + +void BKE_pbvh_build_pixels(PBVH *pbvh, + const struct MLoop *mloop, + struct CustomData *ldata, + struct Image *image, + struct ImageUser *image_user); void BKE_pbvh_free(PBVH *pbvh); /* Hierarchical Search in the BVH, two methods: @@ -287,6 +307,7 @@ bool BKE_pbvh_node_fully_masked_get(PBVHNode *node); void BKE_pbvh_node_fully_unmasked_set(PBVHNode *node, int fully_masked); bool BKE_pbvh_node_fully_unmasked_get(PBVHNode *node); +void BKE_pbvh_mark_rebuild_pixels(PBVH *pbvh); void BKE_pbvh_vert_mark_update(PBVH *pbvh, int index); void BKE_pbvh_node_get_grids(PBVH *pbvh, diff --git a/source/blender/blenkernel/BKE_pbvh_pixels.hh b/source/blender/blenkernel/BKE_pbvh_pixels.hh new file mode 100644 index 00000000000..35eb340d0a1 --- /dev/null +++ b/source/blender/blenkernel/BKE_pbvh_pixels.hh @@ -0,0 +1,184 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later + * Copyright 2022 Blender Foundation. All rights reserved. */ + +#pragma once + +#include "BLI_math.h" +#include "BLI_math_vec_types.hh" +#include "BLI_rect.h" +#include "BLI_vector.hh" + +#include "DNA_image_types.h" +#include "DNA_meshdata_types.h" + +#include "BKE_image.h" +#include "BKE_image_wrappers.hh" + +#include "IMB_imbuf_types.h" + +namespace blender::bke::pbvh::pixels { + +struct TrianglePaintInput { + int3 vert_indices; + /** + * Delta barycentric coordinates between 2 neighbouring UV's in the U direction. + * + * Only the first two coordinates are stored. The third should be recalculated + */ + float2 delta_barycentric_coord_u; + + /** + * Initially only the vert indices are known. + * + * delta_barycentric_coord_u is initialized in a later stage as it requires image tile + * dimensions. + */ + TrianglePaintInput(const int3 vert_indices) + : vert_indices(vert_indices), delta_barycentric_coord_u(0.0f, 0.0f) + { + } +}; + +/** + * Data shared between pixels that belong to the same triangle. + * + * Data is stored as a list of structs, grouped by usage to improve performance (improves CPU + * cache prefetching). + */ +struct Triangles { + /** Data accessed by the inner loop of the painting brush. */ + Vector<TrianglePaintInput> paint_input; + + public: + void append(const int3 vert_indices) + { + this->paint_input.append(TrianglePaintInput(vert_indices)); + } + + TrianglePaintInput &get_paint_input(const int index) + { + return paint_input[index]; + } + + const TrianglePaintInput &get_paint_input(const int index) const + { + return paint_input[index]; + } + + void clear() + { + paint_input.clear(); + } + + uint64_t size() const + { + return paint_input.size(); + } + + uint64_t mem_size() const + { + return paint_input.size() * sizeof(TrianglePaintInput); + } +}; + +/** + * Encode sequential pixels to reduce memory footprint. + */ +struct PackedPixelRow { + /** Barycentric coordinate of the first pixel. */ + float2 start_barycentric_coord; + /** Image coordinate starting of the first pixel. */ + ushort2 start_image_coordinate; + /** Number of sequential pixels encoded in this package. */ + ushort num_pixels; + /** Reference to the pbvh triangle index. */ + ushort triangle_index; +}; + +/** + * Node pixel data containing the pixels for a single UDIM tile. + */ +struct UDIMTilePixels { + /** UDIM Tile number. */ + short tile_number; + + struct { + bool dirty : 1; + } flags; + + /* Dirty region of the tile in image space. */ + rcti dirty_region; + + Vector<PackedPixelRow> pixel_rows; + + UDIMTilePixels() + { + flags.dirty = false; + BLI_rcti_init_minmax(&dirty_region); + } + + void mark_dirty(const PackedPixelRow &pixel_row) + { + int2 start_image_coord(pixel_row.start_image_coordinate.x, pixel_row.start_image_coordinate.y); + BLI_rcti_do_minmax_v(&dirty_region, start_image_coord); + BLI_rcti_do_minmax_v(&dirty_region, start_image_coord + int2(pixel_row.num_pixels + 1, 0)); + flags.dirty = true; + } + + void clear_dirty() + { + BLI_rcti_init_minmax(&dirty_region); + flags.dirty = false; + } +}; + +struct NodeData { + struct { + bool dirty : 1; + } flags; + + Vector<UDIMTilePixels> tiles; + Triangles triangles; + + NodeData() + { + flags.dirty = false; + } + + UDIMTilePixels *find_tile_data(const image::ImageTileWrapper &image_tile) + { + for (UDIMTilePixels &tile : tiles) { + if (tile.tile_number == image_tile.get_tile_number()) { + return &tile; + } + } + return nullptr; + } + + void mark_region(Image &image, const image::ImageTileWrapper &image_tile, ImBuf &image_buffer) + { + UDIMTilePixels *tile = find_tile_data(image_tile); + if (tile && tile->flags.dirty) { + BKE_image_partial_update_mark_region( + &image, image_tile.image_tile, &image_buffer, &tile->dirty_region); + tile->clear_dirty(); + } + } + + void clear_data() + { + tiles.clear(); + triangles.clear(); + } + + static void free_func(void *instance) + { + NodeData *node_data = static_cast<NodeData *>(instance); + MEM_delete(node_data); + } +}; + +NodeData &BKE_pbvh_pixels_node_data_get(PBVHNode &node); +void BKE_pbvh_pixels_mark_image_dirty(PBVHNode &node, Image &image, ImageUser &image_user); + +} // namespace blender::bke::pbvh::pixels diff --git a/source/blender/blenkernel/CMakeLists.txt b/source/blender/blenkernel/CMakeLists.txt index ce4131a0627..710e2900d78 100644 --- a/source/blender/blenkernel/CMakeLists.txt +++ b/source/blender/blenkernel/CMakeLists.txt @@ -244,6 +244,7 @@ set(SRC intern/pbvh.cc intern/pbvh.c intern/pbvh_bmesh.c + intern/pbvh_pixels.cc intern/pointcache.c intern/pointcloud.cc intern/preferences.c diff --git a/source/blender/blenkernel/intern/material.c b/source/blender/blenkernel/intern/material.c index bc569956f66..4caaf314888 100644 --- a/source/blender/blenkernel/intern/material.c +++ b/source/blender/blenkernel/intern/material.c @@ -1423,13 +1423,15 @@ static bool fill_texpaint_slots_cb(bNode *node, void *userdata) case SH_NODE_TEX_IMAGE: { TexPaintSlot *slot = &ma->texpaintslot[index]; slot->ima = (Image *)node->id; - slot->interp = ((NodeTexImage *)node->storage)->interpolation; + NodeTexImage *storage = (NodeTexImage *)node->storage; + slot->interp = storage->interpolation; + slot->image_user = &storage->iuser; /* for new renderer, we need to traverse the treeback in search of a UV node */ bNode *uvnode = nodetree_uv_node_recursive(node); if (uvnode) { - NodeShaderUVMap *storage = (NodeShaderUVMap *)uvnode->storage; - slot->uvname = storage->uv_map; + NodeShaderUVMap *uv_storage = (NodeShaderUVMap *)uvnode->storage; + slot->uvname = uv_storage->uv_map; /* set a value to index so UI knows that we have a valid pointer for the mesh */ slot->valid = true; } diff --git a/source/blender/blenkernel/intern/paint.c b/source/blender/blenkernel/intern/paint.c index eb3f47760fc..c0195e5abc8 100644 --- a/source/blender/blenkernel/intern/paint.c +++ b/source/blender/blenkernel/intern/paint.c @@ -1511,6 +1511,8 @@ void BKE_sculptsession_free(Object *ob) BKE_sculptsession_free_vwpaint_data(ob->sculpt); + MEM_SAFE_FREE(ss->last_paint_canvas_key); + MEM_freeN(ss); ob->sculpt = NULL; @@ -1771,6 +1773,24 @@ static void sculpt_update_object(Depsgraph *depsgraph, } } + /* + * We should rebuild the PBVH_pixels when painting canvas changes. + * + * The relevant changes are stored/encoded in the paint canvas key. + * These include the active uv map, and resolutions. + */ + if (U.experimental.use_sculpt_texture_paint && ss->pbvh) { + char *paint_canvas_key = BKE_paint_canvas_key_get(&scene->toolsettings->paint_mode, ob); + if (ss->last_paint_canvas_key == NULL || !STREQ(paint_canvas_key, ss->last_paint_canvas_key)) { + MEM_SAFE_FREE(ss->last_paint_canvas_key); + ss->last_paint_canvas_key = paint_canvas_key; + BKE_pbvh_mark_rebuild_pixels(ss->pbvh); + } + else { + MEM_freeN(paint_canvas_key); + } + } + /* We could be more precise when we have access to the active tool. */ const bool use_paint_slots = (ob->mode & OB_MODE_SCULPT) != 0; if (use_paint_slots) { diff --git a/source/blender/blenkernel/intern/paint_canvas.cc b/source/blender/blenkernel/intern/paint_canvas.cc index c1145164642..b72418d88c0 100644 --- a/source/blender/blenkernel/intern/paint_canvas.cc +++ b/source/blender/blenkernel/intern/paint_canvas.cc @@ -6,9 +6,12 @@ #include "DNA_scene_types.h" #include "BKE_customdata.h" +#include "BKE_image.h" #include "BKE_material.h" #include "BKE_paint.h" +#include "IMB_imbuf_types.h" + namespace blender::bke::paint::canvas { static TexPaintSlot *get_active_slot(Object *ob) { @@ -33,22 +36,35 @@ extern "C" { using namespace blender::bke::paint::canvas; -Image *BKE_paint_canvas_image_get(const struct PaintModeSettings *settings, struct Object *ob) +bool BKE_paint_canvas_image_get(PaintModeSettings *settings, + Object *ob, + Image **r_image, + ImageUser **r_image_user) { + *r_image = nullptr; + *r_image_user = nullptr; + switch (settings->canvas_source) { case PAINT_CANVAS_SOURCE_COLOR_ATTRIBUTE: - return nullptr; + break; + case PAINT_CANVAS_SOURCE_IMAGE: - return settings->canvas_image; + *r_image = settings->canvas_image; + *r_image_user = &settings->image_user; + break; + case PAINT_CANVAS_SOURCE_MATERIAL: { TexPaintSlot *slot = get_active_slot(ob); if (slot == nullptr) { break; } - return slot->ima; + + *r_image = slot->ima; + *r_image_user = slot->image_user; + break; } } - return nullptr; + return *r_image != nullptr; } int BKE_paint_canvas_uvmap_layer_index_get(const struct PaintModeSettings *settings, @@ -87,4 +103,29 @@ int BKE_paint_canvas_uvmap_layer_index_get(const struct PaintModeSettings *setti } return -1; } + +char *BKE_paint_canvas_key_get(struct PaintModeSettings *settings, struct Object *ob) +{ + std::stringstream ss; + int active_uv_map_layer_index = BKE_paint_canvas_uvmap_layer_index_get(settings, ob); + ss << "UV_MAP:" << active_uv_map_layer_index; + + Image *image; + ImageUser *image_user; + if (BKE_paint_canvas_image_get(settings, ob, &image, &image_user)) { + ImageUser tile_user = *image_user; + LISTBASE_FOREACH (ImageTile *, image_tile, &image->tiles) { + tile_user.tile = image_tile->tile_number; + ImBuf *image_buffer = BKE_image_acquire_ibuf(image, &tile_user, nullptr); + if (!image_buffer) { + continue; + } + ss << ",TILE_" << image_tile->tile_number; + ss << "(" << image_buffer->x << "," << image_buffer->y << ")"; + BKE_image_release_ibuf(image, image_buffer, nullptr); + } + } + + return BLI_strdup(ss.str().c_str()); +} } diff --git a/source/blender/blenkernel/intern/pbvh.c b/source/blender/blenkernel/intern/pbvh.c index 5d307697208..e91f360ef22 100644 --- a/source/blender/blenkernel/intern/pbvh.c +++ b/source/blender/blenkernel/intern/pbvh.c @@ -686,6 +686,8 @@ void BKE_pbvh_free(PBVH *pbvh) if (node->bm_other_verts) { BLI_gset_free(node->bm_other_verts, NULL); } + + pbvh_pixels_free(node); } } @@ -1769,7 +1771,7 @@ BMesh *BKE_pbvh_get_bmesh(PBVH *pbvh) void BKE_pbvh_node_mark_update(PBVHNode *node) { node->flag |= PBVH_UpdateNormals | PBVH_UpdateBB | PBVH_UpdateOriginalBB | - PBVH_UpdateDrawBuffers | PBVH_UpdateRedraw; + PBVH_UpdateDrawBuffers | PBVH_UpdateRedraw | PBVH_RebuildPixels; } void BKE_pbvh_node_mark_update_mask(PBVHNode *node) @@ -1782,6 +1784,16 @@ void BKE_pbvh_node_mark_update_color(PBVHNode *node) node->flag |= PBVH_UpdateColor | PBVH_UpdateDrawBuffers | PBVH_UpdateRedraw; } +void BKE_pbvh_mark_rebuild_pixels(PBVH *pbvh) +{ + for (int n = 0; n < pbvh->totnode; n++) { + PBVHNode *node = &pbvh->nodes[n]; + if (node->flag & PBVH_Leaf) { + node->flag |= PBVH_RebuildPixels; + } + } +} + void BKE_pbvh_node_mark_update_visibility(PBVHNode *node) { node->flag |= PBVH_UpdateVisibility | PBVH_RebuildDrawBuffers | PBVH_UpdateDrawBuffers | diff --git a/source/blender/blenkernel/intern/pbvh_intern.h b/source/blender/blenkernel/intern/pbvh_intern.h index ea1f0632f32..77bd00da50a 100644 --- a/source/blender/blenkernel/intern/pbvh_intern.h +++ b/source/blender/blenkernel/intern/pbvh_intern.h @@ -6,6 +6,10 @@ * \ingroup bke */ +#ifdef __cplusplus +extern "C" { +#endif + /* Axis-aligned bounding box */ typedef struct { float bmin[3], bmax[3]; @@ -111,6 +115,7 @@ struct PBVHNode { /* Used to store the brush color during a stroke and composite it over the original color */ PBVHColorBufferNode color_buffer; + PBVHPixelsNode pixels; }; typedef enum { @@ -260,3 +265,11 @@ bool pbvh_bmesh_node_nearest_to_ray(PBVHNode *node, bool use_original); void pbvh_bmesh_normals_update(PBVHNode **nodes, int totnode); + +/* pbvh_pixels.hh */ +void pbvh_pixels_free(PBVHNode *node); +void pbvh_pixels_free_brush_test(PBVHNode *node); + +#ifdef __cplusplus +} +#endif diff --git a/source/blender/blenkernel/intern/pbvh_pixels.cc b/source/blender/blenkernel/intern/pbvh_pixels.cc new file mode 100644 index 00000000000..d8dd2f4b382 --- /dev/null +++ b/source/blender/blenkernel/intern/pbvh_pixels.cc @@ -0,0 +1,393 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later + * Copyright 2022 Blender Foundation. All rights reserved. */ + +#include "BKE_customdata.h" +#include "BKE_mesh_mapping.h" +#include "BKE_pbvh.h" +#include "BKE_pbvh_pixels.hh" + +#include "DNA_image_types.h" +#include "DNA_mesh_types.h" +#include "DNA_meshdata_types.h" +#include "DNA_object_types.h" + +#include "BLI_math.h" +#include "BLI_task.h" + +#include "BKE_image_wrappers.hh" + +#include "bmesh.h" + +#include "pbvh_intern.h" + +namespace blender::bke::pbvh::pixels { + +/** Durind 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 neighbour uv coordinates in the given image buffer. + */ +static float2 calc_barycentric_delta(const float2 uvs[3], + const float2 start_uv, + const float2 end_uv) +{ + + float3 start_barycentric; + barycentric_weights_v2(uvs[0], uvs[1], uvs[2], start_uv, start_barycentric); + float3 end_barycentric; + barycentric_weights_v2(uvs[0], uvs[1], uvs[2], end_uv, end_barycentric); + float3 barycentric = end_barycentric - start_barycentric; + return float2(barycentric.x, barycentric.y); +} + +static float2 calc_barycentric_delta_x(const ImBuf *image_buffer, + const float2 uvs[3], + const int x, + const int y) +{ + const float2 start_uv(float(x) / image_buffer->x, float(y) / image_buffer->y); + const float2 end_uv(float(x + 1) / image_buffer->x, float(y) / image_buffer->y); + return calc_barycentric_delta(uvs, start_uv, end_uv); +} + +static void extract_barycentric_pixels(UDIMTilePixels &tile_data, + const ImBuf *image_buffer, + const int triangle_index, + const float2 uvs[3], + const int minx, + const int miny, + const int maxx, + const int maxy) +{ + for (int y = miny; y < maxy; y++) { + bool start_detected = false; + PackedPixelRow pixel_row; + pixel_row.triangle_index = triangle_index; + pixel_row.num_pixels = 0; + int x; + + for (x = minx; x < maxx; x++) { + float2 uv(float(x) / image_buffer->x, float(y) / image_buffer->y); + float3 barycentric_weights; + barycentric_weights_v2(uvs[0], uvs[1], uvs[2], uv, barycentric_weights); + + const bool is_inside = barycentric_inside_triangle_v2(barycentric_weights); + if (!start_detected && is_inside) { + start_detected = true; + pixel_row.start_image_coordinate = ushort2(x, y); + pixel_row.start_barycentric_coord = float2(barycentric_weights.x, barycentric_weights.y); + } + else if (start_detected && !is_inside) { + break; + } + } + + if (!start_detected) { + continue; + } + pixel_row.num_pixels = x - pixel_row.start_image_coordinate.x; + tile_data.pixel_rows.append(pixel_row); + } +} + +static void init_triangles(PBVH *pbvh, PBVHNode *node, NodeData *node_data, const MLoop *mloop) +{ + for (int i = 0; i < node->totprim; i++) { + const MLoopTri *lt = &pbvh->looptri[node->prim_indices[i]]; + node_data->triangles.append( + int3(mloop[lt->tri[0]].v, mloop[lt->tri[1]].v, mloop[lt->tri[2]].v)); + } +} + +struct EncodePixelsUserData { + Image *image; + ImageUser *image_user; + PBVH *pbvh; + Vector<PBVHNode *> *nodes; + MLoopUV *ldata_uv; +}; + +static void do_encode_pixels(void *__restrict userdata, + const int n, + const TaskParallelTLS *__restrict UNUSED(tls)) +{ + EncodePixelsUserData *data = static_cast<EncodePixelsUserData *>(userdata); + Image *image = data->image; + ImageUser image_user = *data->image_user; + PBVH *pbvh = data->pbvh; + PBVHNode *node = (*data->nodes)[n]; + NodeData *node_data = static_cast<NodeData *>(node->pixels.node_data); + LISTBASE_FOREACH (ImageTile *, tile, &data->image->tiles) { + image::ImageTileWrapper image_tile(tile); + image_user.tile = image_tile.get_tile_number(); + ImBuf *image_buffer = BKE_image_acquire_ibuf(image, &image_user, nullptr); + if (image_buffer == nullptr) { + continue; + } + + float2 tile_offset = float2(image_tile.get_tile_offset()); + UDIMTilePixels tile_data; + + Triangles &triangles = node_data->triangles; + for (int triangle_index = 0; triangle_index < triangles.size(); triangle_index++) { + const MLoopTri *lt = &pbvh->looptri[node->prim_indices[triangle_index]]; + float2 uvs[3] = { + float2(data->ldata_uv[lt->tri[0]].uv) - tile_offset, + float2(data->ldata_uv[lt->tri[1]].uv) - tile_offset, + float2(data->ldata_uv[lt->tri[2]].uv) - tile_offset, + }; + + const float minv = clamp_f(min_fff(uvs[0].y, uvs[1].y, uvs[2].y), 0.0f, 1.0f); + const int miny = floor(minv * image_buffer->y); + const float maxv = clamp_f(max_fff(uvs[0].y, uvs[1].y, uvs[2].y), 0.0f, 1.0f); + const int maxy = min_ii(ceil(maxv * image_buffer->y), image_buffer->y); + const float minu = clamp_f(min_fff(uvs[0].x, uvs[1].x, uvs[2].x), 0.0f, 1.0f); + const int minx = floor(minu * image_buffer->x); + const float maxu = clamp_f(max_fff(uvs[0].x, uvs[1].x, uvs[2].x), 0.0f, 1.0f); + const int maxx = min_ii(ceil(maxu * image_buffer->x), image_buffer->x); + + TrianglePaintInput &triangle = triangles.get_paint_input(triangle_index); + triangle.delta_barycentric_coord_u = calc_barycentric_delta_x(image_buffer, uvs, minx, miny); + extract_barycentric_pixels( + tile_data, image_buffer, triangle_index, uvs, minx, miny, maxx, maxy); + } + + BKE_image_release_ibuf(image, image_buffer, nullptr); + + if (tile_data.pixel_rows.is_empty()) { + continue; + } + + tile_data.tile_number = image_tile.get_tile_number(); + node_data->tiles.append(tile_data); + } +} + +static bool should_pixels_be_updated(PBVHNode *node) +{ + if ((node->flag & PBVH_Leaf) == 0) { + return false; + } + if ((node->flag & PBVH_RebuildPixels) != 0) { + return true; + } + NodeData *node_data = static_cast<NodeData *>(node->pixels.node_data); + if (node_data != nullptr) { + return false; + } + return true; +} + +static int64_t count_nodes_to_update(PBVH *pbvh) +{ + int64_t result = 0; + for (int n = 0; n < pbvh->totnode; n++) { + PBVHNode *node = &pbvh->nodes[n]; + if (should_pixels_be_updated(node)) { + result++; + } + } + return result; +} + +/** + * Find the nodes that needs to be updated. + * + * The nodes that require updated are added to the r_nodes_to_update parameter. + * Will fill in r_visited_polygons with polygons that are owned by nodes that do not require + * updates. + * + * returns if there were any nodes found (true). + */ +static bool find_nodes_to_update(PBVH *pbvh, Vector<PBVHNode *> &r_nodes_to_update) +{ + int64_t nodes_to_update_len = count_nodes_to_update(pbvh); + if (nodes_to_update_len == 0) { + return false; + } + + r_nodes_to_update.reserve(nodes_to_update_len); + + for (int n = 0; n < pbvh->totnode; n++) { + PBVHNode *node = &pbvh->nodes[n]; + if (!should_pixels_be_updated(node)) { + continue; + } + r_nodes_to_update.append(node); + node->flag = static_cast<PBVHNodeFlags>(node->flag | PBVH_RebuildPixels); + + if (node->pixels.node_data == nullptr) { + NodeData *node_data = MEM_new<NodeData>(__func__); + node->pixels.node_data = node_data; + } + else { + NodeData *node_data = static_cast<NodeData *>(node->pixels.node_data); + node_data->clear_data(); + } + } + + return true; +} + +static void apply_watertight_check(PBVH *pbvh, Image *image, ImageUser *image_user) +{ + ImageUser watertight = *image_user; + LISTBASE_FOREACH (ImageTile *, tile_data, &image->tiles) { + image::ImageTileWrapper image_tile(tile_data); + watertight.tile = image_tile.get_tile_number(); + ImBuf *image_buffer = BKE_image_acquire_ibuf(image, &watertight, nullptr); + if (image_buffer == nullptr) { + continue; + } + for (int n = 0; n < pbvh->totnode; n++) { + PBVHNode *node = &pbvh->nodes[n]; + if ((node->flag & PBVH_Leaf) == 0) { + continue; + } + NodeData *node_data = static_cast<NodeData *>(node->pixels.node_data); + UDIMTilePixels *tile_node_data = node_data->find_tile_data(image_tile); + if (tile_node_data == nullptr) { + continue; + } + + for (PackedPixelRow &pixel_row : tile_node_data->pixel_rows) { + int pixel_offset = pixel_row.start_image_coordinate.y * image_buffer->x + + pixel_row.start_image_coordinate.x; + for (int x = 0; x < pixel_row.num_pixels; x++) { + if (image_buffer->rect_float) { + copy_v4_fl(&image_buffer->rect_float[pixel_offset * 4], 1.0); + } + if (image_buffer->rect) { + uint8_t *dest = static_cast<uint8_t *>( + static_cast<void *>(&image_buffer->rect[pixel_offset])); + copy_v4_uchar(dest, 255); + } + pixel_offset += 1; + } + } + } + BKE_image_release_ibuf(image, image_buffer, nullptr); + } + BKE_image_partial_update_mark_full_update(image); +} + +static void update_pixels(PBVH *pbvh, + const struct MLoop *mloop, + struct CustomData *ldata, + struct Image *image, + struct ImageUser *image_user) +{ + Vector<PBVHNode *> nodes_to_update; + + if (!find_nodes_to_update(pbvh, nodes_to_update)) { + return; + } + + MLoopUV *ldata_uv = static_cast<MLoopUV *>(CustomData_get_layer(ldata, CD_MLOOPUV)); + if (ldata_uv == nullptr) { + return; + } + + for (PBVHNode *node : nodes_to_update) { + NodeData *node_data = static_cast<NodeData *>(node->pixels.node_data); + init_triangles(pbvh, node, node_data, mloop); + } + + EncodePixelsUserData user_data; + user_data.pbvh = pbvh; + user_data.image = image; + user_data.image_user = image_user; + user_data.ldata_uv = ldata_uv; + user_data.nodes = &nodes_to_update; + + TaskParallelSettings settings; + BKE_pbvh_parallel_range_settings(&settings, true, nodes_to_update.size()); + BLI_task_parallel_range(0, nodes_to_update.size(), &user_data, do_encode_pixels, &settings); + if (USE_WATERTIGHT_CHECK) { + apply_watertight_check(pbvh, image, image_user); + } + + /* Clear the UpdatePixels flag. */ + for (PBVHNode *node : nodes_to_update) { + node->flag = static_cast<PBVHNodeFlags>(node->flag & ~PBVH_RebuildPixels); + } + +//#define DO_PRINT_STATISTICS +#ifdef DO_PRINT_STATISTICS + /* Print some statistics about compression ratio. */ + { + int64_t compressed_data_len = 0; + int64_t num_pixels = 0; + for (int n = 0; n < pbvh->totnode; n++) { + PBVHNode *node = &pbvh->nodes[n]; + if ((node->flag & PBVH_Leaf) == 0) { + 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) { + num_pixels += encoded_pixels.num_pixels; + } + } + } + printf("Encoded %lld pixels in %lld bytes (%f bytes per pixel)\n", + num_pixels, + compressed_data_len, + float(compressed_data_len) / num_pixels); + } +#endif +} + +NodeData &BKE_pbvh_pixels_node_data_get(PBVHNode &node) +{ + BLI_assert(node.pixels.node_data != nullptr); + NodeData *node_data = static_cast<NodeData *>(node.pixels.node_data); + return *node_data; +} + +void BKE_pbvh_pixels_mark_image_dirty(PBVHNode &node, Image &image, ImageUser &image_user) +{ + BLI_assert(node.pixels.node_data != nullptr); + NodeData *node_data = static_cast<NodeData *>(node.pixels.node_data); + if (node_data->flags.dirty) { + ImageUser local_image_user = image_user; + LISTBASE_FOREACH (ImageTile *, tile, &image.tiles) { + image::ImageTileWrapper image_tile(tile); + local_image_user.tile = image_tile.get_tile_number(); + ImBuf *image_buffer = BKE_image_acquire_ibuf(&image, &local_image_user, nullptr); + if (image_buffer == nullptr) { + continue; + } + + node_data->mark_region(image, image_tile, *image_buffer); + BKE_image_release_ibuf(&image, image_buffer, nullptr); + } + node_data->flags.dirty = false; + } +} + +} // namespace blender::bke::pbvh::pixels + +extern "C" { +using namespace blender::bke::pbvh::pixels; + +void BKE_pbvh_build_pixels(PBVH *pbvh, + const struct MLoop *mloop, + struct CustomData *ldata, + struct Image *image, + struct ImageUser *image_user) +{ + update_pixels(pbvh, mloop, ldata, image, image_user); +} + +void pbvh_pixels_free(PBVHNode *node) +{ + NodeData *node_data = static_cast<NodeData *>(node->pixels.node_data); + MEM_delete(node_data); + node->pixels.node_data = nullptr; +} +} diff --git a/source/blender/editors/include/ED_sculpt.h b/source/blender/editors/include/ED_sculpt.h index 3100c135db4..54d67c50d5c 100644 --- a/source/blender/editors/include/ED_sculpt.h +++ b/source/blender/editors/include/ED_sculpt.h @@ -17,6 +17,9 @@ struct UndoType; struct ViewContext; struct bContext; struct rcti; +struct wmMsgSubscribeKey; +struct wmMsgSubscribeValue; +struct wmRegionMessageSubscribeParams; /* sculpt.c */ diff --git a/source/blender/editors/sculpt_paint/CMakeLists.txt b/source/blender/editors/sculpt_paint/CMakeLists.txt index b89cbcf87fa..abdae5c44f9 100644 --- a/source/blender/editors/sculpt_paint/CMakeLists.txt +++ b/source/blender/editors/sculpt_paint/CMakeLists.txt @@ -72,6 +72,7 @@ set(SRC sculpt_multiplane_scrape.c sculpt_ops.c sculpt_paint_color.c + sculpt_paint_image.cc sculpt_pose.c sculpt_smooth.c sculpt_transform.c diff --git a/source/blender/editors/sculpt_paint/sculpt.c b/source/blender/editors/sculpt_paint/sculpt.c index 24c5a9fce52..bbbed32e316 100644 --- a/source/blender/editors/sculpt_paint/sculpt.c +++ b/source/blender/editors/sculpt_paint/sculpt.c @@ -2188,7 +2188,8 @@ void SCULPT_calc_area_normal_and_center( static float brush_strength(const Sculpt *sd, const StrokeCache *cache, const float feather, - const UnifiedPaintSettings *ups) + const UnifiedPaintSettings *ups, + const PaintModeSettings *UNUSED(paint_mode_settings)) { const Scene *scene = cache->vc->scene; const Brush *brush = BKE_paint_brush((Paint *)&sd->paint); @@ -2750,6 +2751,41 @@ static void update_brush_local_mat(Sculpt *sd, Object *ob) /** \} */ /* -------------------------------------------------------------------- */ +/** \name Texture painting + * \{ */ + +static bool sculpt_needs_pbvh_pixels(PaintModeSettings *paint_mode_settings, + const Brush *brush, + Object *ob) +{ + if (brush->sculpt_tool == SCULPT_TOOL_PAINT && U.experimental.use_sculpt_texture_paint) { + Image *image; + ImageUser *image_user; + return SCULPT_paint_image_canvas_get(paint_mode_settings, ob, &image, &image_user); + } + + return false; +} + +static void sculpt_pbvh_update_pixels(PaintModeSettings *paint_mode_settings, + SculptSession *ss, + Object *ob) +{ + BLI_assert(ob->type == OB_MESH); + Mesh *mesh = (Mesh *)ob->data; + + Image *image; + ImageUser *image_user; + if (!SCULPT_paint_image_canvas_get(paint_mode_settings, ob, &image, &image_user)) { + return; + } + + BKE_pbvh_build_pixels(ss->pbvh, mesh->mloop, &mesh->ldata, image, image_user); +} + +/** \} */ + +/* -------------------------------------------------------------------- */ /** \name Generic Brush Plane & Symmetry Utilities * \{ */ @@ -3075,7 +3111,8 @@ void SCULPT_vertcos_to_key(Object *ob, KeyBlock *kb, const float (*vertCos)[3]) static void sculpt_topology_update(Sculpt *sd, Object *ob, Brush *brush, - UnifiedPaintSettings *UNUSED(ups)) + UnifiedPaintSettings *UNUSED(ups), + PaintModeSettings *UNUSED(paint_mode_settings)) { SculptSession *ss = ob->sculpt; @@ -3170,7 +3207,11 @@ static void do_brush_action_task_cb(void *__restrict userdata, } } -static void do_brush_action(Sculpt *sd, Object *ob, Brush *brush, UnifiedPaintSettings *ups) +static void do_brush_action(Sculpt *sd, + Object *ob, + Brush *brush, + UnifiedPaintSettings *ups, + PaintModeSettings *paint_mode_settings) { SculptSession *ss = ob->sculpt; int totnode; @@ -3209,6 +3250,10 @@ static void do_brush_action(Sculpt *sd, Object *ob, Brush *brush, UnifiedPaintSe nodes = sculpt_pbvh_gather_generic(ob, sd, brush, use_original, radius_scale, &totnode); } + if (sculpt_needs_pbvh_pixels(paint_mode_settings, brush, ob)) { + 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. */ /* It also assigns the paint_face_set here as it needs to be done regardless of the stroke type @@ -3399,7 +3444,7 @@ static void do_brush_action(Sculpt *sd, Object *ob, Brush *brush, UnifiedPaintSe SCULPT_do_displacement_smear_brush(sd, ob, nodes, totnode); break; case SCULPT_TOOL_PAINT: - SCULPT_do_paint_brush(sd, ob, nodes, totnode); + SCULPT_do_paint_brush(paint_mode_settings, sd, ob, nodes, totnode); break; case SCULPT_TOOL_SMEAR: SCULPT_do_smear_brush(sd, ob, nodes, totnode); @@ -3704,10 +3749,18 @@ void SCULPT_cache_calc_brushdata_symm(StrokeCache *cache, } } -typedef void (*BrushActionFunc)(Sculpt *sd, Object *ob, Brush *brush, UnifiedPaintSettings *ups); +typedef void (*BrushActionFunc)(Sculpt *sd, + Object *ob, + Brush *brush, + UnifiedPaintSettings *ups, + PaintModeSettings *paint_mode_settings); -static void do_tiled( - Sculpt *sd, Object *ob, Brush *brush, UnifiedPaintSettings *ups, BrushActionFunc action) +static void do_tiled(Sculpt *sd, + Object *ob, + Brush *brush, + UnifiedPaintSettings *ups, + PaintModeSettings *paint_mode_settings, + BrushActionFunc action) { SculptSession *ss = ob->sculpt; StrokeCache *cache = ss->cache; @@ -3741,7 +3794,7 @@ static void do_tiled( /* First do the "un-tiled" position to initialize the stroke for this location. */ cache->tile_pass = 0; - action(sd, ob, brush, ups); + action(sd, ob, brush, ups, paint_mode_settings); /* Now do it for all the tiles. */ copy_v3_v3_int(cur, start); @@ -3760,7 +3813,7 @@ static void do_tiled( cache->plane_offset[dim] = cur[dim] * step[dim]; cache->initial_location[dim] = cur[dim] * step[dim] + original_initial_location[dim]; } - action(sd, ob, brush, ups); + action(sd, ob, brush, ups, paint_mode_settings); } } } @@ -3770,6 +3823,7 @@ static void do_radial_symmetry(Sculpt *sd, Object *ob, Brush *brush, UnifiedPaintSettings *ups, + PaintModeSettings *paint_mode_settings, BrushActionFunc action, const char symm, const int axis, @@ -3781,7 +3835,7 @@ static void do_radial_symmetry(Sculpt *sd, const float angle = 2.0f * M_PI * i / sd->radial_symm[axis - 'X']; ss->cache->radial_symmetry_pass = i; SCULPT_cache_calc_brushdata_symm(ss->cache, symm, axis, angle); - do_tiled(sd, ob, brush, ups, action); + do_tiled(sd, ob, brush, ups, paint_mode_settings, action); } } @@ -3803,7 +3857,8 @@ static void sculpt_fix_noise_tear(Sculpt *sd, Object *ob) static void do_symmetrical_brush_actions(Sculpt *sd, Object *ob, BrushActionFunc action, - UnifiedPaintSettings *ups) + UnifiedPaintSettings *ups, + PaintModeSettings *paint_mode_settings) { Brush *brush = BKE_paint_brush(&sd->paint); SculptSession *ss = ob->sculpt; @@ -3812,7 +3867,7 @@ static void do_symmetrical_brush_actions(Sculpt *sd, float feather = calc_symmetry_feather(sd, ss->cache); - cache->bstrength = brush_strength(sd, cache, feather, ups); + cache->bstrength = brush_strength(sd, cache, feather, ups, paint_mode_settings); cache->symmetry = symm; /* `symm` is a bit combination of XYZ - @@ -3825,11 +3880,11 @@ static void do_symmetrical_brush_actions(Sculpt *sd, cache->radial_symmetry_pass = 0; SCULPT_cache_calc_brushdata_symm(cache, i, 0, 0); - do_tiled(sd, ob, brush, ups, action); + do_tiled(sd, ob, brush, ups, paint_mode_settings, action); - do_radial_symmetry(sd, ob, brush, ups, action, i, 'X', feather); - do_radial_symmetry(sd, ob, brush, ups, action, i, 'Y', feather); - do_radial_symmetry(sd, ob, brush, ups, action, i, 'Z', feather); + do_radial_symmetry(sd, ob, brush, ups, paint_mode_settings, action, i, 'X', feather); + do_radial_symmetry(sd, ob, brush, ups, paint_mode_settings, action, i, 'Y', feather); + do_radial_symmetry(sd, ob, brush, ups, paint_mode_settings, action, i, 'Z', feather); } } @@ -4609,7 +4664,8 @@ static bool sculpt_needs_connectivity_info(const Sculpt *sd, SCULPT_TOOL_NEEDS_COLOR(brush->sculpt_tool) || (brush->sculpt_tool == SCULPT_TOOL_CLOTH) || (brush->sculpt_tool == SCULPT_TOOL_SMEAR) || (brush->sculpt_tool == SCULPT_TOOL_DRAW_FACE_SETS) || - (brush->sculpt_tool == SCULPT_TOOL_DISPLACEMENT_SMEAR)); + (brush->sculpt_tool == SCULPT_TOOL_DISPLACEMENT_SMEAR) || + (brush->sculpt_tool == SCULPT_TOOL_PAINT)); } void SCULPT_stroke_modifiers_check(const bContext *C, Object *ob, const Brush *brush) @@ -5057,6 +5113,15 @@ void SCULPT_flush_update_step(bContext *C, SculptUpdateType update_flags) multires_mark_as_modified(depsgraph, ob, MULTIRES_COORDS_MODIFIED); } + if ((update_flags & SCULPT_UPDATE_IMAGE) != 0) { + ED_region_tag_redraw(region); + if (update_flags == SCULPT_UPDATE_IMAGE) { + /* Early exit when only need to update the images. We don't want to tag any geometry updates + * that would rebuilt the PBVH. */ + return; + } + } + DEG_id_tag_update(&ob->id, ID_RECALC_SHADING); /* Only current viewport matters, slower update for all viewports will @@ -5136,6 +5201,16 @@ void SCULPT_flush_update_done(const bContext *C, Object *ob, SculptUpdateType up } } } + + if (update_flags & SCULPT_UPDATE_IMAGE) { + LISTBASE_FOREACH (ScrArea *, area, &screen->areabase) { + SpaceLink *sl = area->spacedata.first; + if (sl->spacetype != SPACE_IMAGE) { + continue; + } + ED_area_tag_redraw_regiontype(area, RGN_TYPE_WINDOW); + } + } } if (update_flags & SCULPT_UPDATE_COORDS) { @@ -5227,6 +5302,7 @@ static void sculpt_stroke_update_step(bContext *C, Object *ob = CTX_data_active_object(C); SculptSession *ss = ob->sculpt; const Brush *brush = BKE_paint_brush(&sd->paint); + ToolSettings *tool_settings = CTX_data_tool_settings(C); SCULPT_stroke_modifiers_check(C, ob, brush); sculpt_update_cache_variants(C, sd, ob, itemptr); @@ -5246,10 +5322,10 @@ static void sculpt_stroke_update_step(bContext *C, } if (SCULPT_stroke_is_dynamic_topology(ss, brush)) { - do_symmetrical_brush_actions(sd, ob, sculpt_topology_update, ups); + do_symmetrical_brush_actions(sd, ob, sculpt_topology_update, ups, &tool_settings->paint_mode); } - do_symmetrical_brush_actions(sd, ob, do_brush_action, ups); + do_symmetrical_brush_actions(sd, ob, do_brush_action, ups, &tool_settings->paint_mode); sculpt_combine_proxies(sd, ob); /* Hack to fix noise texture tearing mesh. */ @@ -5280,7 +5356,12 @@ static void sculpt_stroke_update_step(bContext *C, SCULPT_flush_update_step(C, SCULPT_UPDATE_MASK); } else if (ELEM(brush->sculpt_tool, SCULPT_TOOL_PAINT, SCULPT_TOOL_SMEAR)) { - SCULPT_flush_update_step(C, SCULPT_UPDATE_COLOR); + if (SCULPT_use_image_paint_brush(&tool_settings->paint_mode, ob)) { + SCULPT_flush_update_step(C, SCULPT_UPDATE_IMAGE); + } + else { + SCULPT_flush_update_step(C, SCULPT_UPDATE_COLOR); + } } else { SCULPT_flush_update_step(C, SCULPT_UPDATE_COORDS); @@ -5302,6 +5383,7 @@ static void sculpt_stroke_done(const bContext *C, struct PaintStroke *UNUSED(str Object *ob = CTX_data_active_object(C); SculptSession *ss = ob->sculpt; Sculpt *sd = CTX_data_tool_settings(C)->sculpt; + ToolSettings *tool_settings = CTX_data_tool_settings(C); /* Finished. */ if (!ss->cache) { @@ -5335,6 +5417,11 @@ static void sculpt_stroke_done(const bContext *C, struct PaintStroke *UNUSED(str if (brush->sculpt_tool == SCULPT_TOOL_MASK) { SCULPT_flush_update_done(C, ob, SCULPT_UPDATE_MASK); } + else if (brush->sculpt_tool == SCULPT_TOOL_PAINT) { + if (SCULPT_use_image_paint_brush(&tool_settings->paint_mode, ob)) { + SCULPT_flush_update_done(C, ob, SCULPT_UPDATE_IMAGE); + } + } else { SCULPT_flush_update_done(C, ob, SCULPT_UPDATE_COORDS); } diff --git a/source/blender/editors/sculpt_paint/sculpt_intern.h b/source/blender/editors/sculpt_paint/sculpt_intern.h index 466432a35ec..3839c0e71e4 100644 --- a/source/blender/editors/sculpt_paint/sculpt_intern.h +++ b/source/blender/editors/sculpt_paint/sculpt_intern.h @@ -11,11 +11,13 @@ #include "DNA_key_types.h" #include "DNA_listBase.h" #include "DNA_meshdata_types.h" +#include "DNA_scene_types.h" #include "DNA_vec_types.h" #include "BKE_paint.h" #include "BKE_pbvh.h" #include "BLI_bitmap.h" +#include "BLI_compiler_attrs.h" #include "BLI_compiler_compat.h" #include "BLI_gsqueue.h" #include "BLI_threads.h" @@ -25,12 +27,13 @@ extern "C" { #endif struct AutomaskingCache; +struct Image; +struct ImageUser; struct KeyBlock; struct Object; struct SculptUndoNode; struct bContext; - -enum ePaintSymmetryFlags; +struct PaintModeSettings; /* Updates */ @@ -43,6 +46,7 @@ typedef enum SculptUpdateType { SCULPT_UPDATE_MASK = 1 << 1, SCULPT_UPDATE_VISIBILITY = 1 << 2, SCULPT_UPDATE_COLOR = 1 << 3, + SCULPT_UPDATE_IMAGE = 1 << 4, } SculptUpdateType; typedef struct SculptCursorGeometryInfo { @@ -1626,7 +1630,29 @@ void SCULPT_multiplane_scrape_preview_draw(uint gpuattr, void SCULPT_do_draw_face_sets_brush(Sculpt *sd, Object *ob, PBVHNode **nodes, int totnode); /* Paint Brush. */ -void SCULPT_do_paint_brush(Sculpt *sd, Object *ob, PBVHNode **nodes, int totnode); +void SCULPT_do_paint_brush(struct PaintModeSettings *paint_mode_settings, + Sculpt *sd, + Object *ob, + PBVHNode **nodes, + int totnode) ATTR_NONNULL(); + +/** + * @brief Get the image canvas for painting on the given object. + * + * @return #true if an image is found. The #r_image and #r_image_user fields are filled with the + * image and image user. Returns false when the image isn't found. In the later case the r_image + * and r_image_user are set to nullptr/NULL. + */ +bool SCULPT_paint_image_canvas_get(struct PaintModeSettings *paint_mode_settings, + struct Object *ob, + struct Image **r_image, + struct ImageUser **r_image_user) ATTR_NONNULL(); +void SCULPT_do_paint_brush_image(struct PaintModeSettings *paint_mode_settings, + Sculpt *sd, + Object *ob, + PBVHNode **nodes, + int totnode) ATTR_NONNULL(); +bool SCULPT_use_image_paint_brush(struct PaintModeSettings *settings, Object *ob) ATTR_NONNULL(); /* Smear Brush. */ void SCULPT_do_smear_brush(Sculpt *sd, Object *ob, PBVHNode **nodes, int totnode); diff --git a/source/blender/editors/sculpt_paint/sculpt_paint_color.c b/source/blender/editors/sculpt_paint/sculpt_paint_color.c index e7a713efa14..7a8a6e8e484 100644 --- a/source/blender/editors/sculpt_paint/sculpt_paint_color.c +++ b/source/blender/editors/sculpt_paint/sculpt_paint_color.c @@ -240,8 +240,14 @@ static void sample_wet_paint_reduce(const void *__restrict UNUSED(userdata), add_v4_v4(join->color, swptd->color); } -void SCULPT_do_paint_brush(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) { + if (SCULPT_use_image_paint_brush(paint_mode_settings, ob)) { + SCULPT_do_paint_brush_image(paint_mode_settings, sd, ob, nodes, totnode); + return; + } + Brush *brush = BKE_paint_brush(&sd->paint); SculptSession *ss = ob->sculpt; diff --git a/source/blender/editors/sculpt_paint/sculpt_paint_image.cc b/source/blender/editors/sculpt_paint/sculpt_paint_image.cc new file mode 100644 index 00000000000..082ff6260c2 --- /dev/null +++ b/source/blender/editors/sculpt_paint/sculpt_paint_image.cc @@ -0,0 +1,432 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later + * Copyright 2022 Blender Foundation. All rights reserved. */ + +#include "DNA_image_types.h" +#include "DNA_material_types.h" +#include "DNA_mesh_types.h" +#include "DNA_node_types.h" +#include "DNA_object_types.h" + +#include "ED_paint.h" +#include "ED_uvedit.h" + +#include "BLI_math.h" +#include "BLI_math_color_blend.h" +#include "BLI_task.h" + +#include "IMB_colormanagement.h" +#include "IMB_imbuf.h" + +#include "BKE_brush.h" +#include "BKE_image_wrappers.hh" +#include "BKE_material.h" +#include "BKE_pbvh.h" +#include "BKE_pbvh_pixels.hh" + +#include "bmesh.h" + +#include "NOD_shader.h" + +#include "sculpt_intern.h" + +namespace blender::ed::sculpt_paint::paint::image { + +using namespace blender::bke::pbvh::pixels; +using namespace blender::bke::image; + +struct ImageData { + Image *image = nullptr; + ImageUser *image_user = nullptr; + + ~ImageData() + { + } + + static bool init_active_image(Object *ob, + ImageData *r_image_data, + PaintModeSettings *paint_mode_settings) + { + return BKE_paint_canvas_image_get( + paint_mode_settings, ob, &r_image_data->image, &r_image_data->image_user); + } +}; + +struct TexturePaintingUserData { + Object *ob; + Brush *brush; + PBVHNode **nodes; + ImageData image_data; +}; + +/** Reading and writing to image buffer with 4 float channels. */ +class ImageBufferFloat4 { + private: + int pixel_offset; + + public: + void set_image_position(ImBuf *image_buffer, ushort2 image_pixel_position) + { + pixel_offset = int(image_pixel_position.y) * image_buffer->x + int(image_pixel_position.x); + } + + void next_pixel() + { + pixel_offset += 1; + } + + float4 read_pixel(ImBuf *image_buffer) const + { + return &image_buffer->rect_float[pixel_offset * 4]; + } + + void write_pixel(ImBuf *image_buffer, const float4 pixel_data) const + { + copy_v4_v4(&image_buffer->rect_float[pixel_offset * 4], pixel_data); + } + + const char *get_colorspace_name(ImBuf *image_buffer) + { + return IMB_colormanagement_get_float_colorspace(image_buffer); + } +}; + +/** Reading and writing to image buffer with 4 byte channels. */ +class ImageBufferByte4 { + private: + int pixel_offset; + + public: + void set_image_position(ImBuf *image_buffer, ushort2 image_pixel_position) + { + pixel_offset = int(image_pixel_position.y) * image_buffer->x + int(image_pixel_position.x); + } + + void next_pixel() + { + pixel_offset += 1; + } + + float4 read_pixel(ImBuf *image_buffer) const + { + float4 result; + rgba_uchar_to_float(result, + static_cast<const uchar *>( + static_cast<const void *>(&(image_buffer->rect[pixel_offset])))); + return result; + } + + void write_pixel(ImBuf *image_buffer, const float4 pixel_data) const + { + rgba_float_to_uchar( + static_cast<uchar *>(static_cast<void *>(&image_buffer->rect[pixel_offset])), pixel_data); + } + + const char *get_colorspace_name(ImBuf *image_buffer) + { + return IMB_colormanagement_get_rect_colorspace(image_buffer); + } +}; + +template<typename ImageBuffer> class PaintingKernel { + ImageBuffer image_accessor; + + SculptSession *ss; + const Brush *brush; + const int thread_id; + const MVert *mvert; + + float4 brush_color; + float brush_strength; + + SculptBrushTestFn brush_test_fn; + SculptBrushTest test; + /* Pointer to the last used image buffer to detect when buffers are switched. */ + void *last_used_image_buffer_ptr = nullptr; + const char *last_used_color_space = nullptr; + + public: + explicit PaintingKernel(SculptSession *ss, + const Brush *brush, + const int thread_id, + const MVert *mvert) + : ss(ss), brush(brush), thread_id(thread_id), mvert(mvert) + { + init_brush_strength(); + init_brush_test(); + } + + bool paint(const Triangles &triangles, const PackedPixelRow &pixel_row, ImBuf *image_buffer) + { + image_accessor.set_image_position(image_buffer, pixel_row.start_image_coordinate); + const TrianglePaintInput triangle = triangles.get_paint_input(pixel_row.triangle_index); + float3 pixel_pos = get_start_pixel_pos(triangle, pixel_row); + const float3 delta_pixel_pos = get_delta_pixel_pos(triangle, pixel_row, pixel_pos); + bool pixels_painted = false; + for (int x = 0; x < pixel_row.num_pixels; x++) { + if (!brush_test_fn(&test, pixel_pos)) { + pixel_pos += delta_pixel_pos; + image_accessor.next_pixel(); + continue; + } + + float4 color = image_accessor.read_pixel(image_buffer); + const float3 normal(0.0f, 0.0f, 0.0f); + const float3 face_normal(0.0f, 0.0f, 0.0f); + const float mask = 0.0f; + const float falloff_strength = SCULPT_brush_strength_factor( + ss, brush, pixel_pos, sqrtf(test.dist), normal, face_normal, mask, 0, thread_id); + float4 paint_color = brush_color * falloff_strength * brush_strength; + float4 buffer_color; + 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)); + image_accessor.write_pixel(image_buffer, color); + pixels_painted = true; + + image_accessor.next_pixel(); + pixel_pos += delta_pixel_pos; + } + return pixels_painted; + } + + void init_brush_color(ImBuf *image_buffer) + { + 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); + brush_color[3] = 1.0f; + + const char *from_colorspace = IMB_colormanagement_role_colorspace_name_get( + COLOR_ROLE_SCENE_LINEAR); + ColormanageProcessor *cm_processor = IMB_colormanagement_colorspace_processor_new( + from_colorspace, to_colorspace); + IMB_colormanagement_processor_apply_v4(cm_processor, brush_color); + IMB_colormanagement_processor_free(cm_processor); + last_used_color_space = to_colorspace; + } + + private: + void init_brush_strength() + { + brush_strength = ss->cache->bstrength; + } + void init_brush_test() + { + brush_test_fn = SCULPT_brush_test_init_with_falloff_shape(ss, &test, brush->falloff_shape); + } + + /** + * Extract the starting pixel position from the given encoded_pixels belonging to the triangle. + */ + float3 get_start_pixel_pos(const TrianglePaintInput &triangle, + const PackedPixelRow &encoded_pixels) const + { + return init_pixel_pos(triangle, encoded_pixels.start_barycentric_coord); + } + + /** + * Extract the delta pixel position that will be used to advance a Pixel instance to the next + * pixel. + */ + float3 get_delta_pixel_pos(const TrianglePaintInput &triangle, + const PackedPixelRow &encoded_pixels, + const float3 &start_pixel) const + { + float3 result = init_pixel_pos( + triangle, encoded_pixels.start_barycentric_coord + triangle.delta_barycentric_coord_u); + return result - start_pixel; + } + + float3 init_pixel_pos(const TrianglePaintInput &triangle, + const float2 &barycentric_weights) const + { + const int3 &vert_indices = triangle.vert_indices; + float3 result; + const float3 barycentric(barycentric_weights.x, + barycentric_weights.y, + 1.0f - barycentric_weights.x - barycentric_weights.y); + interp_v3_v3v3v3(result, + mvert[vert_indices[0]].co, + mvert[vert_indices[1]].co, + mvert[vert_indices[2]].co, + barycentric); + return result; + } +}; + +static std::vector<bool> init_triangle_brush_test(SculptSession *ss, + Triangles &triangles, + const MVert *mvert) +{ + std::vector<bool> brush_test(triangles.size()); + SculptBrushTest test; + SCULPT_brush_test_init(ss, &test); + float3 brush_min_bounds(test.location[0] - test.radius, + test.location[1] - test.radius, + test.location[2] - test.radius); + float3 brush_max_bounds(test.location[0] + test.radius, + test.location[1] + test.radius, + test.location[2] + test.radius); + for (int triangle_index = 0; triangle_index < triangles.size(); triangle_index++) { + TrianglePaintInput &triangle = triangles.get_paint_input(triangle_index); + + float3 triangle_min_bounds(mvert[triangle.vert_indices[0]].co); + float3 triangle_max_bounds(triangle_min_bounds); + for (int i = 1; i < 3; i++) { + const float3 &pos = mvert[triangle.vert_indices[i]].co; + triangle_min_bounds.x = min_ff(triangle_min_bounds.x, pos.x); + triangle_min_bounds.y = min_ff(triangle_min_bounds.y, pos.y); + triangle_min_bounds.z = min_ff(triangle_min_bounds.z, pos.z); + triangle_max_bounds.x = max_ff(triangle_max_bounds.x, pos.x); + triangle_max_bounds.y = max_ff(triangle_max_bounds.y, pos.y); + triangle_max_bounds.z = max_ff(triangle_max_bounds.z, pos.z); + } + brush_test[triangle_index] = isect_aabb_aabb_v3( + brush_min_bounds, brush_max_bounds, triangle_min_bounds, triangle_max_bounds); + } + return brush_test; +} + +static void do_paint_pixels(void *__restrict userdata, + const int n, + const TaskParallelTLS *__restrict tls) +{ + TexturePaintingUserData *data = static_cast<TexturePaintingUserData *>(userdata); + Object *ob = data->ob; + SculptSession *ss = ob->sculpt; + const Brush *brush = data->brush; + PBVHNode *node = data->nodes[n]; + + NodeData &node_data = BKE_pbvh_pixels_node_data_get(*node); + const int thread_id = BLI_task_parallel_thread_id(tls); + MVert *mvert = SCULPT_mesh_deformed_mverts_get(ss); + + std::vector<bool> brush_test = init_triangle_brush_test(ss, node_data.triangles, mvert); + + PaintingKernel<ImageBufferFloat4> kernel_float4(ss, brush, thread_id, mvert); + PaintingKernel<ImageBufferByte4> kernel_byte4(ss, brush, thread_id, mvert); + + ImageUser image_user = *data->image_data.image_user; + bool pixels_updated = false; + for (UDIMTilePixels &tile_data : node_data.tiles) { + LISTBASE_FOREACH (ImageTile *, tile, &data->image_data.image->tiles) { + ImageTileWrapper image_tile(tile); + if (image_tile.get_tile_number() == tile_data.tile_number) { + image_user.tile = image_tile.get_tile_number(); + + ImBuf *image_buffer = BKE_image_acquire_ibuf(data->image_data.image, &image_user, nullptr); + if (image_buffer == nullptr) { + continue; + } + + if (image_buffer->rect_float != nullptr) { + kernel_float4.init_brush_color(image_buffer); + } + else { + kernel_float4.init_brush_color(image_buffer); + } + + for (const PackedPixelRow &pixel_row : tile_data.pixel_rows) { + if (!brush_test[pixel_row.triangle_index]) { + continue; + } + bool pixels_painted = false; + if (image_buffer->rect_float != nullptr) { + pixels_painted = kernel_float4.paint(node_data.triangles, pixel_row, image_buffer); + } + else { + pixels_painted = kernel_byte4.paint(node_data.triangles, pixel_row, image_buffer); + } + + if (pixels_painted) { + tile_data.mark_dirty(pixel_row); + } + } + + BKE_image_release_ibuf(data->image_data.image, image_buffer, nullptr); + pixels_updated |= tile_data.flags.dirty; + break; + } + } + } + + node_data.flags.dirty |= pixels_updated; +} + +static void do_mark_dirty_regions(void *__restrict userdata, + const int n, + const TaskParallelTLS *__restrict UNUSED(tls)) +{ + TexturePaintingUserData *data = static_cast<TexturePaintingUserData *>(userdata); + PBVHNode *node = data->nodes[n]; + BKE_pbvh_pixels_mark_image_dirty(*node, *data->image_data.image, *data->image_data.image_user); +} + +} // namespace blender::ed::sculpt_paint::paint::image + +extern "C" { + +using namespace blender::ed::sculpt_paint::paint::image; + +bool SCULPT_paint_image_canvas_get(PaintModeSettings *paint_mode_settings, + Object *ob, + Image **r_image, + ImageUser **r_image_user) +{ + BLI_assert(r_image); + BLI_assert(r_image_user); + ImageData image_data; + if (!ImageData::init_active_image(ob, &image_data, paint_mode_settings)) { + return false; + } + + *r_image = image_data.image; + *r_image_user = image_data.image_user; + return true; +} + +bool SCULPT_use_image_paint_brush(PaintModeSettings *settings, Object *ob) +{ + if (!U.experimental.use_sculpt_texture_paint) { + return false; + } + if (ob->type != OB_MESH) { + return false; + } + Image *image; + ImageUser *image_user; + 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) +{ + Brush *brush = BKE_paint_brush(&sd->paint); + + TexturePaintingUserData data = {nullptr}; + data.ob = ob; + data.brush = brush; + data.nodes = nodes; + + 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_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); +} +} diff --git a/source/blender/makesdna/DNA_material_types.h b/source/blender/makesdna/DNA_material_types.h index 38e99896ab1..332317142c7 100644 --- a/source/blender/makesdna/DNA_material_types.h +++ b/source/blender/makesdna/DNA_material_types.h @@ -31,6 +31,8 @@ typedef struct TexPaintSlot { /** Image to be painted on. Mutual exclusive with attribute_name. */ struct Image *ima; + struct ImageUser *image_user; + /** Custom-data index for uv layer, #MAX_NAME. */ char *uvname; /** diff --git a/source/blender/makesdna/DNA_scene_types.h b/source/blender/makesdna/DNA_scene_types.h index 9cc4d5ed55b..d6c1040110f 100644 --- a/source/blender/makesdna/DNA_scene_types.h +++ b/source/blender/makesdna/DNA_scene_types.h @@ -939,6 +939,7 @@ typedef struct PaintModeSettings { /** Selected image when canvas_source=PAINT_CANVAS_SOURCE_IMAGE. */ Image *canvas_image; + ImageUser image_user; } PaintModeSettings; |