Welcome to mirror list, hosted at ThFree Co, Russian Federation.

git.blender.org/blender.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
Diffstat (limited to 'source/blender')
-rw-r--r--source/blender/blenkernel/BKE_paint.h19
-rw-r--r--source/blender/blenkernel/BKE_pbvh.h21
-rw-r--r--source/blender/blenkernel/BKE_pbvh_pixels.hh184
-rw-r--r--source/blender/blenkernel/CMakeLists.txt1
-rw-r--r--source/blender/blenkernel/intern/material.c8
-rw-r--r--source/blender/blenkernel/intern/paint.c20
-rw-r--r--source/blender/blenkernel/intern/paint_canvas.cc51
-rw-r--r--source/blender/blenkernel/intern/pbvh.c14
-rw-r--r--source/blender/blenkernel/intern/pbvh_intern.h13
-rw-r--r--source/blender/blenkernel/intern/pbvh_pixels.cc393
-rw-r--r--source/blender/editors/include/ED_sculpt.h3
-rw-r--r--source/blender/editors/sculpt_paint/CMakeLists.txt1
-rw-r--r--source/blender/editors/sculpt_paint/sculpt.c127
-rw-r--r--source/blender/editors/sculpt_paint/sculpt_intern.h32
-rw-r--r--source/blender/editors/sculpt_paint/sculpt_paint_color.c8
-rw-r--r--source/blender/editors/sculpt_paint/sculpt_paint_image.cc432
-rw-r--r--source/blender/makesdna/DNA_material_types.h2
-rw-r--r--source/blender/makesdna/DNA_scene_types.h1
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;