Blender V4.3
pbvh_pixels_copy.cc
Go to the documentation of this file.
1/* SPDX-FileCopyrightText: 2023 Blender Authors
2 *
3 * SPDX-License-Identifier: GPL-2.0-or-later */
4
5#include "BLI_array.hh"
6#include "BLI_bit_vector.hh"
7#include "BLI_listbase.h"
8#include "BLI_math_geom.h"
9#include "BLI_math_vector.hh"
10#include "BLI_task.hh"
11#include "BLI_vector.hh"
12
13#include "IMB_imbuf.hh"
14#include "IMB_imbuf_types.hh"
15
16#include "BKE_image_wrappers.hh"
17#include "BKE_pbvh_api.hh"
18#include "BKE_pbvh_pixels.hh"
19
20#include "pbvh_intern.hh"
21#include "pbvh_pixels_copy.hh"
22#include "pbvh_uv_islands.hh"
23
25
26const int THREADING_GRAIN_SIZE = 128;
27
29enum class CoordSpace {
33 UV,
34
41 Tile,
42};
43
44template<CoordSpace Space> struct Vertex {
46};
47
48template<CoordSpace Space> struct Edge {
51};
52
54static rcti get_bounds(const Edge<CoordSpace::Tile> &tile_edge)
55{
56 rcti bounds;
57 BLI_rcti_init_minmax(&bounds);
58 BLI_rcti_do_minmax_v(&bounds, int2(tile_edge.vertex_1.coordinate));
59 BLI_rcti_do_minmax_v(&bounds, int2(tile_edge.vertex_2.coordinate));
60 return bounds;
61}
62
64static void add_margin(rcti &bounds, int margin)
65{
66 bounds.xmin -= margin;
67 bounds.xmax += margin;
68 bounds.ymin -= margin;
69 bounds.ymax += margin;
70}
71
73static void clamp(rcti &bounds, int2 resolution)
74{
75 rcti clamping_bounds;
76 BLI_rcti_init(&clamping_bounds, 0, resolution.x - 1, 0, resolution.y - 1);
77 BLI_rcti_isect(&bounds, &clamping_bounds, &bounds);
78}
79
81 const image::ImageTileWrapper image_tile,
82 const int2 tile_resolution)
83{
84 return Vertex<CoordSpace::Tile>{(uv_vertex.coordinate - float2(image_tile.get_tile_offset())) *
85 float2(tile_resolution)};
86}
87
89 const image::ImageTileWrapper image_tile,
90 const int2 tile_resolution)
91{
93 convert_coord_space(uv_edge.vertex_1, image_tile, tile_resolution),
94 convert_coord_space(uv_edge.vertex_2, image_tile, tile_resolution),
95 };
96}
97
98class NonManifoldTileEdges : public Vector<Edge<CoordSpace::Tile>> {};
99
100class NonManifoldUVEdges : public Vector<Edge<CoordSpace::UV>> {
101 public:
103 {
104 int num_non_manifold_edges = count_non_manifold_edges(mesh_data);
105 reserve(num_non_manifold_edges);
106 for (const int primitive_id : mesh_data.corner_tris.index_range()) {
107 for (const int edge_id : mesh_data.primitive_to_edge_map[primitive_id]) {
108 if (is_manifold(mesh_data, edge_id)) {
109 continue;
110 }
111 const int3 &tri = mesh_data.corner_tris[primitive_id];
112 const int2 mesh_edge = mesh_data.edges[edge_id];
114
115 edge.vertex_1.coordinate = find_uv(mesh_data, tri, mesh_edge[0]);
116 edge.vertex_2.coordinate = find_uv(mesh_data, tri, mesh_edge[1]);
117 append(edge);
118 }
119 }
120 BLI_assert_msg(size() == num_non_manifold_edges,
121 "Incorrect number of non manifold edges added. ");
122 }
123
125 const int2 tile_resolution) const
126 {
128 for (const Edge<CoordSpace::UV> &uv_edge : *this) {
130 uv_edge, image_tile, tile_resolution);
131 result.append(tile_edge);
132 }
133 return result;
134 }
135
136 private:
137 static int64_t count_non_manifold_edges(const uv_islands::MeshData &mesh_data)
138 {
139 int64_t result = 0;
140 for (const int primitive_id : mesh_data.corner_tris.index_range()) {
141 for (const int edge_id : mesh_data.primitive_to_edge_map[primitive_id]) {
142 if (is_manifold(mesh_data, edge_id)) {
143 continue;
144 }
145 result += 1;
146 }
147 }
148 return result;
149 }
150
151 static bool is_manifold(const uv_islands::MeshData &mesh_data, const int edge_id)
152 {
153 return mesh_data.edge_to_primitive_map[edge_id].size() == 2;
154 }
155
156 static float2 find_uv(const uv_islands::MeshData &mesh_data, const int3 &tri, int vertex_i)
157 {
158 for (int i = 0; i < 3; i++) {
159 const int loop_i = tri[i];
160 const int vert = mesh_data.corner_verts[loop_i];
161 if (vert == vertex_i) {
162 return mesh_data.uv_map[loop_i];
163 }
164 }
166 return float2(0.0f);
167 }
168};
169
170class PixelNodesTileData : public Vector<std::reference_wrapper<UDIMTilePixels>> {
171 public:
173 {
174 reserve(count_nodes(pbvh, image_tile));
175
176 std::visit(
177 [&](auto &nodes) {
178 for (blender::bke::pbvh::Node &node : nodes) {
179 if (should_add_node(node, image_tile)) {
180 NodeData &node_data = *static_cast<NodeData *>(node.pixels_);
181 UDIMTilePixels &tile_pixels = *node_data.find_tile_data(image_tile);
182 append(tile_pixels);
183 }
184 }
185 },
186 pbvh.nodes_);
187 }
188
189 private:
190 static bool should_add_node(blender::bke::pbvh::Node &node,
191 const image::ImageTileWrapper &image_tile)
192 {
193 if ((node.flag_ & PBVH_Leaf) == 0) {
194 return false;
195 }
196 if (node.pixels_ == nullptr) {
197 return false;
198 }
199 NodeData &node_data = *static_cast<NodeData *>(node.pixels_);
200 if (node_data.find_tile_data(image_tile) == nullptr) {
201 return false;
202 }
203 return true;
204 }
205
206 static int64_t count_nodes(blender::bke::pbvh::Tree &pbvh,
207 const image::ImageTileWrapper &image_tile)
208 {
209 int64_t result = 0;
210 std::visit(
211 [&](auto &nodes) {
212 for (blender::bke::pbvh::Node &node : nodes) {
213 if (should_add_node(node, image_tile)) {
214 result++;
215 }
216 }
217 },
218 pbvh.nodes_);
219 return result;
220 }
221};
222
228struct Rows {
237
238 struct Pixel {
240 float distance;
249
250 Pixel() = default;
251
252 void init(int2 coordinate)
253 {
254 copy_command.destination = coordinate;
255 copy_command.source_1 = coordinate;
256 copy_command.source_2 = coordinate;
259 distance = std::numeric_limits<float>::max();
260 edge_index = -1;
261 }
262 };
263
267
268 struct RowView {
269 int row_number = 0;
272 RowView() = delete;
275 pixels(
276 MutableSpan<Pixel>(&rows.pixels[row_number * rows.resolution.x], rows.resolution.x))
277 {
278 }
279 };
280
285
287 {
288 int64_t index = 0;
289 for (int y : IndexRange(resolution.y)) {
290 for (int64_t x : IndexRange(resolution.x)) {
291 int2 position(x, y);
292 pixels[index++].init(position);
293 }
294 }
295 }
296
302 {
303 for (const UDIMTilePixels &tile_pixels : nodes_tile_pixels) {
305 tile_pixels.pixel_rows, [&](const PackedPixelRow &encoded_pixels) {
306 for (int x = encoded_pixels.start_image_coordinate.x;
307 x < encoded_pixels.start_image_coordinate.x + encoded_pixels.num_pixels;
308 x++)
309 {
310 int64_t index = encoded_pixels.start_image_coordinate.y * resolution.x + x;
311 pixels[index].type = PixelType::Brush;
312 pixels[index].distance = 0.0f;
313 }
314 });
315 }
316 }
317
328 int2 find_second_source(int2 destination, int2 first_source)
329 {
330 rcti search_bounds;
331 BLI_rcti_init(&search_bounds,
332 max_ii(first_source.x - 1, 0),
333 min_ii(first_source.x + 1, resolution.x - 1),
334 max_ii(first_source.y - 1, 0),
335 min_ii(first_source.y + 1, resolution.y - 1));
336 /* Initialize to the first source, so when no other source could be found it will use the
337 * first_source. */
338 int2 found_source = first_source;
339 float found_distance = std::numeric_limits<float>().max();
340 for (int sy : IndexRange(search_bounds.ymin, BLI_rcti_size_y(&search_bounds) + 1)) {
341 for (int sx : IndexRange(search_bounds.xmin, BLI_rcti_size_x(&search_bounds) + 1)) {
342 int2 source(sx, sy);
343 /* Skip first source as it should be the closest and already selected. */
344 if (source == first_source) {
345 continue;
346 }
347 int pixel_index = sy * resolution.y + sx;
348 if (pixels[pixel_index].type != PixelType::Brush) {
349 continue;
350 }
351
352 float new_distance = math::distance(float2(destination), float2(source));
353 if (new_distance < found_distance) {
354 found_distance = new_distance;
355 found_source = source;
356 }
357 }
358 }
359 return found_source;
360 }
361
362 float determine_mix_factor(const int2 destination,
363 const int2 source_1,
364 const int2 source_2,
365 const Edge<CoordSpace::Tile> &edge)
366 {
367 /* Use stable result when both sources are the same. */
368 if (source_1 == source_2) {
369 return 0.0f;
370 }
371
372 float2 clamped_to_edge;
373 float destination_lambda = closest_to_line_v2(
374 clamped_to_edge, float2(destination), edge.vertex_1.coordinate, edge.vertex_2.coordinate);
375 float source_1_lambda = closest_to_line_v2(
376 clamped_to_edge, float2(source_1), edge.vertex_1.coordinate, edge.vertex_2.coordinate);
377 float source_2_lambda = closest_to_line_v2(
378 clamped_to_edge, float2(source_2), edge.vertex_1.coordinate, edge.vertex_2.coordinate);
379
380 return clamp_f(
381 (destination_lambda - source_1_lambda) / (source_2_lambda - source_1_lambda), 0.0f, 1.0f);
382 }
383
384 void find_copy_source(Pixel &pixel, const NonManifoldTileEdges &tile_edges)
385 {
386 BLI_assert(pixel.type == PixelType::SelectedForCloserExamination);
387
388 rcti bounds;
389 BLI_rcti_init(&bounds,
390 pixel.copy_command.destination.x,
391 pixel.copy_command.destination.x,
392 pixel.copy_command.destination.y,
393 pixel.copy_command.destination.y);
394 add_margin(bounds, margin);
395 clamp(bounds, resolution);
396
397 float found_distance = std::numeric_limits<float>().max();
398 int2 found_source(0);
399
400 for (int sy : IndexRange(bounds.ymin, BLI_rcti_size_y(&bounds))) {
401 int pixel_index = sy * resolution.x;
402 for (int sx : IndexRange(bounds.xmin, BLI_rcti_size_x(&bounds))) {
403 Pixel &source = pixels[pixel_index + sx];
404 if (source.type != PixelType::Brush) {
405 continue;
406 }
407 float new_distance = math::distance(float2(sx, sy),
408 float2(pixel.copy_command.destination));
409 if (new_distance < found_distance) {
410 found_source = int2(sx, sy);
411 found_distance = new_distance;
412 }
413 }
414 }
415
416 if (found_distance == std::numeric_limits<float>().max()) {
417 return;
418 }
419 pixel.type = PixelType::CopyFromClosestEdge;
420 pixel.distance = found_distance;
421 pixel.copy_command.source_1 = found_source;
422 pixel.copy_command.source_2 = find_second_source(pixel.copy_command.destination, found_source);
423 pixel.copy_command.mix_factor = determine_mix_factor(pixel.copy_command.destination,
424 pixel.copy_command.source_1,
425 pixel.copy_command.source_2,
426 tile_edges[pixel.edge_index]);
427 }
428
429 void find_copy_source(Vector<std::reference_wrapper<Pixel>> &selected_pixels,
430 const NonManifoldTileEdges &tile_edges)
431 {
433 IndexRange(selected_pixels.size()), THREADING_GRAIN_SIZE, [&](IndexRange range) {
434 for (int selected_pixel_index : range) {
435 Pixel &current_pixel = selected_pixels[selected_pixel_index];
436 find_copy_source(current_pixel, tile_edges);
437 }
438 });
439 }
440
442 const NonManifoldTileEdges &tile_edges)
443 {
445 selected_pixels.reserve(10000);
446
447 for (int tile_edge_index : tile_edges.index_range()) {
448 const Edge<CoordSpace::Tile> &tile_edge = tile_edges[tile_edge_index];
449 rcti edge_bounds = get_bounds(tile_edge);
450 add_margin(edge_bounds, margin);
451 clamp(edge_bounds, resolution);
452
453 for (const int64_t sy : IndexRange(edge_bounds.ymin, BLI_rcti_size_y(&edge_bounds))) {
454 for (const int64_t sx : IndexRange(edge_bounds.xmin, BLI_rcti_size_x(&edge_bounds))) {
455 const int64_t index = sy * resolution.x + sx;
456 Pixel &pixel = pixels[index];
457 if (pixel.type == PixelType::Brush) {
458 continue;
459 }
460 BLI_assert_msg(pixel.type != PixelType::CopyFromClosestEdge,
461 "PixelType::CopyFromClosestEdge isn't allowed to be set as it is set "
462 "when finding the pixels to copy.");
463
464 const float2 point(sx, sy);
465 float2 closest_edge_point;
466 closest_to_line_segment_v2(closest_edge_point,
467 point,
468 tile_edge.vertex_1.coordinate,
469 tile_edge.vertex_2.coordinate);
470 float distance_to_edge = math::distance(closest_edge_point, point);
471 if (distance_to_edge < margin && distance_to_edge < pixel.distance) {
472 if (pixel.type != PixelType::SelectedForCloserExamination) {
473 selected_pixels.append(std::reference_wrapper<Pixel>(pixel));
474 }
475 pixel.type = PixelType::SelectedForCloserExamination;
476 pixel.distance = distance_to_edge;
477 pixel.edge_index = tile_edge_index;
478 }
479 }
480 }
481 }
482 return selected_pixels;
483 }
484
485 void pack_into(const Span<std::reference_wrapper<Pixel>> selected_pixels,
486 CopyPixelTile &copy_tile) const
487 {
488 std::optional<std::reference_wrapper<CopyPixelGroup>> last_group = std::nullopt;
489 std::optional<CopyPixelCommand> last_command = std::nullopt;
490
491 for (const Pixel &elem : selected_pixels) {
492 if (elem.type == PixelType::CopyFromClosestEdge) {
493 if (!last_command.has_value() || !last_command->can_be_extended(elem.copy_command)) {
494 CopyPixelGroup new_group = {elem.copy_command.destination - int2(1, 0),
495 elem.copy_command.source_1,
496 copy_tile.command_deltas.size(),
497 0};
498 copy_tile.groups.append(new_group);
499 last_group = copy_tile.groups.last();
500 last_command = CopyPixelCommand(*last_group);
501 }
502
503 DeltaCopyPixelCommand delta_command = last_command->encode_delta(elem.copy_command);
504 copy_tile.command_deltas.append(delta_command);
505 last_group->get().num_deltas++;
506 last_command = elem.copy_command;
507 }
508 }
509 }
510};
511
513 Image &image,
514 ImageUser &image_user,
515 const uv_islands::MeshData &mesh_data)
516{
517 PBVHData &pbvh_data = data_get(pbvh);
518 pbvh_data.tiles_copy_pixels.clear();
519 const NonManifoldUVEdges non_manifold_edges(mesh_data);
520 if (non_manifold_edges.is_empty()) {
521 /* Early exit: No non manifold edges detected. */
522 return;
523 }
524
525 ImageUser tile_user = image_user;
526 LISTBASE_FOREACH (ImageTile *, tile, &image.tiles) {
528 tile_user.tile = image_tile.get_tile_number();
529
530 ImBuf *tile_buffer = BKE_image_acquire_ibuf(&image, &tile_user, nullptr);
531 if (tile_buffer == nullptr) {
532 continue;
533 }
534 const PixelNodesTileData nodes_tile_pixels(pbvh, image_tile);
535
536 int2 tile_resolution(tile_buffer->x, tile_buffer->y);
537 BKE_image_release_ibuf(&image, tile_buffer, nullptr);
538
539 NonManifoldTileEdges tile_edges = non_manifold_edges.extract_tile_edges(image_tile,
540 tile_resolution);
541 CopyPixelTile copy_tile(image_tile.get_tile_number());
542
543 Rows rows(tile_resolution, image.seam_margin);
544 rows.init_pixels();
545 rows.mark_pixels_effected_by_brush(nodes_tile_pixels);
546
549 rows.find_copy_source(selected_pixels, tile_edges);
550 rows.pack_into(selected_pixels, copy_tile);
551
552 copy_tile.print_compression_rate();
553 pbvh_data.tiles_copy_pixels.tiles.append(copy_tile);
554 }
555}
556
558 Image &image,
559 ImageUser &image_user,
560 image::TileNumber tile_number)
561{
562 PBVHData &pbvh_data = data_get(pbvh);
563 std::optional<std::reference_wrapper<CopyPixelTile>> pixel_tile =
564 pbvh_data.tiles_copy_pixels.find_tile(tile_number);
565 if (!pixel_tile.has_value()) {
566 /* No pixels need to be copied. */
567 return;
568 }
569
570 ImageUser tile_user = image_user;
571 tile_user.tile = tile_number;
572 ImBuf *tile_buffer = BKE_image_acquire_ibuf(&image, &tile_user, nullptr);
573 if (tile_buffer == nullptr) {
574 /* No tile buffer found to copy. */
575 return;
576 }
577
578 CopyPixelTile &tile = pixel_tile->get();
579 threading::parallel_for(tile.groups.index_range(), THREADING_GRAIN_SIZE, [&](IndexRange range) {
580 tile.copy_pixels(*tile_buffer, range);
581 });
582
583 BKE_image_release_ibuf(&image, tile_buffer, nullptr);
584}
585
586} // namespace blender::bke::pbvh::pixels
ImBuf * BKE_image_acquire_ibuf(Image *ima, ImageUser *iuser, void **r_lock)
void BKE_image_release_ibuf(Image *ima, ImBuf *ibuf, void *lock)
@ PBVH_Leaf
Definition BKE_pbvh.hh:30
A BVH for high poly meshes.
#define BLI_assert_unreachable()
Definition BLI_assert.h:97
#define BLI_assert(a)
Definition BLI_assert.h:50
#define BLI_assert_msg(a, msg)
Definition BLI_assert.h:57
#define LISTBASE_FOREACH(type, var, list)
MINLINE int min_ii(int a, int b)
MINLINE float clamp_f(float value, float min, float max)
MINLINE int max_ii(int a, int b)
float closest_to_line_segment_v2(float r_close[2], const float p[2], const float l1[2], const float l2[2])
Definition math_geom.cc:363
float closest_to_line_v2(float r_close[2], const float p[2], const float l1[2], const float l2[2])
BLI_INLINE int BLI_rcti_size_y(const struct rcti *rct)
Definition BLI_rect.h:193
void BLI_rcti_init_minmax(struct rcti *rect)
Definition rct.c:478
void BLI_rcti_init(struct rcti *rect, int xmin, int xmax, int ymin, int ymax)
Definition rct.c:418
bool BLI_rcti_isect(const struct rcti *src1, const struct rcti *src2, struct rcti *dest)
BLI_INLINE int BLI_rcti_size_x(const struct rcti *rct)
Definition BLI_rect.h:189
void BLI_rcti_do_minmax_v(struct rcti *rect, const int xy[2])
Definition rct.c:490
Contains defines and structs used throughout the imbuf module.
in reality light always falls off quadratically Particle Retrieve the data of the particle that spawned the object for example to give variation to multiple instances of an object Point Retrieve information about points in a point cloud Retrieve the edges of an object as it appears to Cycles topology will always appear triangulated Convert a blackbody temperature to an RGB value Normal Generate a perturbed normal from an RGB normal map image Typically used for faking highly detailed surfaces Generate an OSL shader from a file or text data block Image Sample an image file as a texture Gabor Generate Gabor noise Gradient Generate interpolated color and intensity values based on the input vector Magic Generate a psychedelic color texture Voronoi Generate Worley noise based on the distance to random points Typically used to generate textures such as or biological cells Brick Generate a procedural texture producing bricks Texture Retrieve multiple types of texture coordinates nTypically used as inputs for texture nodes Vector Convert a point
constexpr IndexRange index_range() const
Definition BLI_span.hh:402
void append(const T &value)
bool is_empty() const
void reserve(const int64_t min_capacity)
std::variant< Vector< MeshNode >, Vector< GridsNode >, Vector< BMeshNode > > nodes_
NonManifoldTileEdges extract_tile_edges(const image::ImageTileWrapper image_tile, const int2 tile_resolution) const
NonManifoldUVEdges(const uv_islands::MeshData &mesh_data)
PixelNodesTileData(blender::bke::pbvh::Tree &pbvh, const image::ImageTileWrapper &image_tile)
append
ccl_global const KernelWorkTile * tile
static void clamp(rcti &bounds, int2 resolution)
static void add_margin(rcti &bounds, int margin)
void copy_update(blender::bke::pbvh::Tree &pbvh, Image &image, ImageUser &image_user, const uv_islands::MeshData &mesh_data)
void copy_pixels(blender::bke::pbvh::Tree &pbvh, Image &image, ImageUser &image_user, image::TileNumber tile_number)
static const Vertex< CoordSpace::Tile > convert_coord_space(const Vertex< CoordSpace::UV > &uv_vertex, const image::ImageTileWrapper image_tile, const int2 tile_resolution)
static rcti get_bounds(const Edge< CoordSpace::Tile > &tile_edge)
PBVHData & data_get(blender::bke::pbvh::Tree &pbvh)
T distance(const T &a, const T &b)
void parallel_for_each(Range &&range, const Function &function)
Definition BLI_task.hh:58
void parallel_for(const IndexRange range, const int64_t grain_size, const Function &function, const TaskSizeHints &size_hints=detail::TaskSizeHints_Static(1))
Definition BLI_task.hh:95
VecBase< int32_t, 2 > int2
VecBase< float, 2 > float2
__int64 int64_t
Definition stdint.h:89
Vector< DeltaCopyPixelCommand > command_deltas
std::optional< std::reference_wrapper< CopyPixelTile > > find_tile(image::TileNumber tile_number)
UDIMTilePixels * find_tile_data(const image::ImageTileWrapper &image_tile)
RowView(Rows &rows, int64_t row_number)
void pack_into(const Span< std::reference_wrapper< Pixel > > selected_pixels, CopyPixelTile &copy_tile) const
Vector< std::reference_wrapper< Pixel > > filter_pixels_for_closer_examination(const NonManifoldTileEdges &tile_edges)
void mark_pixels_effected_by_brush(const PixelNodesTileData &nodes_tile_pixels)
Rows(int2 resolution, int margin)
void find_copy_source(Pixel &pixel, const NonManifoldTileEdges &tile_edges)
void find_copy_source(Vector< std::reference_wrapper< Pixel > > &selected_pixels, const NonManifoldTileEdges &tile_edges)
float determine_mix_factor(const int2 destination, const int2 source_1, const int2 source_2, const Edge< CoordSpace::Tile > &edge)
int2 find_second_source(int2 destination, int2 first_source)
int ymin
int ymax
int xmin
int xmax
float max
ccl_device_inline int clamp(int a, int mn, int mx)
Definition util/math.h:379