2023-08-15 16:20:26 +02:00
|
|
|
/* SPDX-FileCopyrightText: 2022 Blender Authors
|
2023-05-31 16:19:06 +02:00
|
|
|
*
|
|
|
|
* SPDX-License-Identifier: GPL-2.0-or-later */
|
2022-04-15 16:39:50 +02:00
|
|
|
|
2023-01-23 19:29:40 +01:00
|
|
|
/* Paint a color made from hash of node pointer. */
|
|
|
|
//#define DEBUG_PIXEL_NODES
|
|
|
|
|
2022-04-15 16:39:50 +02:00
|
|
|
#include "DNA_image_types.h"
|
|
|
|
#include "DNA_object_types.h"
|
|
|
|
|
2023-08-05 02:57:52 +02:00
|
|
|
#include "ED_paint.hh"
|
2022-04-15 16:39:50 +02:00
|
|
|
|
|
|
|
#include "BLI_math_color_blend.h"
|
Cleanup: reduce amount of math-related includes
Using ClangBuildAnalyzer on the whole Blender build, it was pointing
out that BLI_math.h is the heaviest "header hub" (i.e. non tiny file
that is included a lot).
However, there's very little (actually zero) source files in Blender
that need "all the math" (base, colors, vectors, matrices,
quaternions, intersection, interpolation, statistics, solvers and
time). A common use case is source files needing just vectors, or
just vectors & matrices, or just colors etc. Actually, 181 files
were including the whole math thing without needing it at all.
This change removes BLI_math.h completely, and instead in all the
places that need it, includes BLI_math_vector.h or BLI_math_color.h
and so on.
Change from that:
- BLI_math_color.h was included 1399 times -> now 408 (took 114.0sec
to parse -> now 36.3sec)
- BLI_simd.h 1403 -> 418 (109.7sec -> 34.9sec).
Full rebuild of Blender (Apple M1, Xcode, RelWithDebInfo) is not
affected much (342sec -> 334sec). Most of benefit would be when
someone's changing BLI_simd.h or BLI_math_color.h or similar files,
that now there's 3x fewer files result in a recompile.
Pull Request #110944
2023-08-09 10:39:20 +02:00
|
|
|
#include "BLI_math_geom.h"
|
2022-04-15 16:39:50 +02:00
|
|
|
#include "BLI_task.h"
|
2023-01-23 19:29:40 +01:00
|
|
|
#ifdef DEBUG_PIXEL_NODES
|
|
|
|
# include "BLI_hash.h"
|
|
|
|
#endif
|
2022-04-15 16:39:50 +02:00
|
|
|
|
|
|
|
#include "IMB_colormanagement.h"
|
|
|
|
#include "IMB_imbuf.h"
|
|
|
|
|
2023-08-02 22:14:18 +02:00
|
|
|
#include "BKE_brush.hh"
|
2022-04-15 16:39:50 +02:00
|
|
|
#include "BKE_image_wrappers.hh"
|
2023-07-04 04:55:22 +02:00
|
|
|
#include "BKE_pbvh_api.hh"
|
2022-04-15 16:39:50 +02:00
|
|
|
#include "BKE_pbvh_pixels.hh"
|
|
|
|
|
|
|
|
#include "bmesh.h"
|
|
|
|
|
2023-02-09 20:35:50 +01:00
|
|
|
#include "sculpt_intern.hh"
|
2022-04-15 16:39:50 +02:00
|
|
|
|
|
|
|
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;
|
|
|
|
|
2022-04-15 18:59:02 +02:00
|
|
|
~ImageData() = default;
|
2022-04-15 16:39:50 +02:00
|
|
|
|
|
|
|
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;
|
2023-04-14 21:16:42 +02:00
|
|
|
Span<PBVHNode *> nodes;
|
2022-04-15 16:39:50 +02:00
|
|
|
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
|
|
|
|
{
|
2023-05-18 10:19:01 +02:00
|
|
|
return &image_buffer->float_buffer.data[pixel_offset * 4];
|
2022-04-15 16:39:50 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
void write_pixel(ImBuf *image_buffer, const float4 pixel_data) const
|
|
|
|
{
|
2023-05-18 10:19:01 +02:00
|
|
|
copy_v4_v4(&image_buffer->float_buffer.data[pixel_offset * 4], pixel_data);
|
2022-04-15 16:39:50 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
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,
|
2023-05-18 10:19:01 +02:00
|
|
|
static_cast<const uchar *>(static_cast<const void *>(
|
|
|
|
&(image_buffer->byte_buffer.data[4 * pixel_offset]))));
|
2022-04-15 16:39:50 +02:00
|
|
|
return result;
|
|
|
|
}
|
|
|
|
|
|
|
|
void write_pixel(ImBuf *image_buffer, const float4 pixel_data) const
|
|
|
|
{
|
2023-05-18 10:19:01 +02:00
|
|
|
rgba_float_to_uchar(static_cast<uchar *>(static_cast<void *>(
|
|
|
|
&image_buffer->byte_buffer.data[4 * pixel_offset])),
|
|
|
|
pixel_data);
|
2022-04-15 16:39:50 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
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;
|
Mesh: Move positions to a generic attribute
**Changes**
As described in T93602, this patch removes all use of the `MVert`
struct, replacing it with a generic named attribute with the name
`"position"`, consistent with other geometry types.
Variable names have been changed from `verts` to `positions`, to align
with the attribute name and the more generic design (positions are not
vertices, they are just an attribute stored on the point domain).
This change is made possible by previous commits that moved all other
data out of `MVert` to runtime data or other generic attributes. What
remains is mostly a simple type change. Though, the type still shows up
859 times, so the patch is quite large.
One compromise is that now `CD_MASK_BAREMESH` now contains
`CD_PROP_FLOAT3`. With the general move towards generic attributes
over custom data types, we are removing use of these type masks anyway.
**Benefits**
The most obvious benefit is reduced memory usage and the benefits
that brings in memory-bound situations. `float3` is only 3 bytes, in
comparison to `MVert` which was 4. When there are millions of vertices
this starts to matter more.
The other benefits come from using a more generic type. Instead of
writing algorithms specifically for `MVert`, code can just use arrays
of vectors. This will allow eliminating many temporary arrays or
wrappers used to extract positions.
Many possible improvements aren't implemented in this patch, though
I did switch simplify or remove the process of creating temporary
position arrays in a few places.
The design clarity that "positions are just another attribute" brings
allows removing explicit copying of vertices in some procedural
operations-- they are just processed like most other attributes.
**Performance**
This touches so many areas that it's hard to benchmark exhaustively,
but I observed some areas as examples.
* The mesh line node with 4 million count was 1.5x (8ms to 12ms) faster.
* The Spring splash screen went from ~4.3 to ~4.5 fps.
* The subdivision surface modifier/node was slightly faster
RNA access through Python may be slightly slower, since now we need
a name lookup instead of just a custom data type lookup for each index.
**Future Improvements**
* Remove uses of "vert_coords" functions:
* `BKE_mesh_vert_coords_alloc`
* `BKE_mesh_vert_coords_get`
* `BKE_mesh_vert_coords_apply{_with_mat4}`
* Remove more hidden copying of positions
* General simplification now possible in many areas
* Convert more code to C++ to use `float3` instead of `float[3]`
* Currently `reinterpret_cast` is used for those C-API functions
Differential Revision: https://developer.blender.org/D15982
2023-01-10 06:10:43 +01:00
|
|
|
const float3 *vert_positions_;
|
2022-04-15 16:39:50 +02:00
|
|
|
|
|
|
|
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,
|
2023-11-16 18:29:52 +01:00
|
|
|
const Span<float3> positions)
|
|
|
|
: ss(ss), brush(brush), thread_id(thread_id), vert_positions_(positions.data())
|
2022-04-15 16:39:50 +02:00
|
|
|
{
|
|
|
|
init_brush_strength();
|
|
|
|
init_brush_test();
|
|
|
|
}
|
|
|
|
|
2022-11-28 08:31:24 +01:00
|
|
|
bool paint(const PaintGeometryPrimitives &geom_primitives,
|
|
|
|
const PaintUVPrimitives &uv_primitives,
|
2022-09-29 08:21:56 +02:00
|
|
|
const PackedPixelRow &pixel_row,
|
|
|
|
ImBuf *image_buffer,
|
|
|
|
AutomaskingNodeData *automask_data)
|
2022-04-15 16:39:50 +02:00
|
|
|
{
|
|
|
|
image_accessor.set_image_position(image_buffer, pixel_row.start_image_coordinate);
|
2022-11-28 08:31:24 +01:00
|
|
|
const UVPrimitivePaintInput paint_input = uv_primitives.get_paint_input(
|
|
|
|
pixel_row.uv_primitive_index);
|
|
|
|
float3 pixel_pos = get_start_pixel_pos(geom_primitives, paint_input, pixel_row);
|
|
|
|
const float3 delta_pixel_pos = get_delta_pixel_pos(
|
|
|
|
geom_primitives, paint_input, pixel_row, pixel_pos);
|
2022-04-15 16:39:50 +02:00
|
|
|
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;
|
2022-09-29 08:21:56 +02:00
|
|
|
|
2022-04-15 16:39:50 +02:00
|
|
|
const float falloff_strength = SCULPT_brush_strength_factor(
|
2022-07-30 04:01:32 +02:00
|
|
|
ss,
|
|
|
|
brush,
|
|
|
|
pixel_pos,
|
|
|
|
sqrtf(test.dist),
|
|
|
|
normal,
|
|
|
|
face_normal,
|
|
|
|
mask,
|
|
|
|
BKE_pbvh_make_vref(PBVH_REF_NONE),
|
2022-09-29 08:21:56 +02:00
|
|
|
thread_id,
|
|
|
|
automask_data);
|
2022-04-15 16:39:50 +02:00
|
|
|
float4 paint_color = brush_color * falloff_strength * brush_strength;
|
|
|
|
float4 buffer_color;
|
2023-01-23 19:29:40 +01:00
|
|
|
|
|
|
|
#ifdef DEBUG_PIXEL_NODES
|
|
|
|
if ((pixel_row.start_image_coordinate.y >> 3) & 1) {
|
|
|
|
paint_color[0] *= 0.5f;
|
|
|
|
paint_color[1] *= 0.5f;
|
|
|
|
paint_color[2] *= 0.5f;
|
|
|
|
}
|
|
|
|
#endif
|
|
|
|
|
2022-04-15 16:39:50 +02:00
|
|
|
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;
|
|
|
|
}
|
|
|
|
|
2023-01-23 19:29:40 +01:00
|
|
|
void init_brush_color(ImBuf *image_buffer, float in_brush_color[3])
|
2022-04-15 16:39:50 +02:00
|
|
|
{
|
|
|
|
const char *to_colorspace = image_accessor.get_colorspace_name(image_buffer);
|
|
|
|
if (last_used_color_space == to_colorspace) {
|
|
|
|
return;
|
|
|
|
}
|
2023-01-23 19:29:40 +01:00
|
|
|
|
2022-04-15 16:39:50 +02:00
|
|
|
/* 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. */
|
2023-01-23 19:29:40 +01:00
|
|
|
srgb_to_linearrgb_v3_v3(brush_color, in_brush_color);
|
2022-04-15 16:39:50 +02:00
|
|
|
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.
|
|
|
|
*/
|
2022-11-28 08:31:24 +01:00
|
|
|
float3 get_start_pixel_pos(const PaintGeometryPrimitives &geom_primitives,
|
|
|
|
const UVPrimitivePaintInput &paint_input,
|
2022-04-15 16:39:50 +02:00
|
|
|
const PackedPixelRow &encoded_pixels) const
|
|
|
|
{
|
2022-11-28 08:31:24 +01:00
|
|
|
return init_pixel_pos(geom_primitives, paint_input, encoded_pixels.start_barycentric_coord);
|
2022-04-15 16:39:50 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Extract the delta pixel position that will be used to advance a Pixel instance to the next
|
|
|
|
* pixel.
|
|
|
|
*/
|
2022-11-28 08:31:24 +01:00
|
|
|
float3 get_delta_pixel_pos(const PaintGeometryPrimitives &geom_primitives,
|
|
|
|
const UVPrimitivePaintInput &paint_input,
|
2022-04-15 16:39:50 +02:00
|
|
|
const PackedPixelRow &encoded_pixels,
|
|
|
|
const float3 &start_pixel) const
|
|
|
|
{
|
2022-11-28 08:31:24 +01:00
|
|
|
float3 result = init_pixel_pos(geom_primitives,
|
|
|
|
paint_input,
|
|
|
|
encoded_pixels.start_barycentric_coord +
|
|
|
|
paint_input.delta_barycentric_coord_u);
|
2022-04-15 16:39:50 +02:00
|
|
|
return result - start_pixel;
|
|
|
|
}
|
|
|
|
|
2022-11-28 08:31:24 +01:00
|
|
|
float3 init_pixel_pos(const PaintGeometryPrimitives &geom_primitives,
|
|
|
|
const UVPrimitivePaintInput &paint_input,
|
2022-04-15 16:39:50 +02:00
|
|
|
const float2 &barycentric_weights) const
|
|
|
|
{
|
2022-11-28 08:31:24 +01:00
|
|
|
const int3 &vert_indices = geom_primitives.get_vert_indices(
|
|
|
|
paint_input.geometry_primitive_index);
|
2022-04-15 16:39:50 +02:00
|
|
|
float3 result;
|
|
|
|
const float3 barycentric(barycentric_weights.x,
|
|
|
|
barycentric_weights.y,
|
|
|
|
1.0f - barycentric_weights.x - barycentric_weights.y);
|
|
|
|
interp_v3_v3v3v3(result,
|
Mesh: Move positions to a generic attribute
**Changes**
As described in T93602, this patch removes all use of the `MVert`
struct, replacing it with a generic named attribute with the name
`"position"`, consistent with other geometry types.
Variable names have been changed from `verts` to `positions`, to align
with the attribute name and the more generic design (positions are not
vertices, they are just an attribute stored on the point domain).
This change is made possible by previous commits that moved all other
data out of `MVert` to runtime data or other generic attributes. What
remains is mostly a simple type change. Though, the type still shows up
859 times, so the patch is quite large.
One compromise is that now `CD_MASK_BAREMESH` now contains
`CD_PROP_FLOAT3`. With the general move towards generic attributes
over custom data types, we are removing use of these type masks anyway.
**Benefits**
The most obvious benefit is reduced memory usage and the benefits
that brings in memory-bound situations. `float3` is only 3 bytes, in
comparison to `MVert` which was 4. When there are millions of vertices
this starts to matter more.
The other benefits come from using a more generic type. Instead of
writing algorithms specifically for `MVert`, code can just use arrays
of vectors. This will allow eliminating many temporary arrays or
wrappers used to extract positions.
Many possible improvements aren't implemented in this patch, though
I did switch simplify or remove the process of creating temporary
position arrays in a few places.
The design clarity that "positions are just another attribute" brings
allows removing explicit copying of vertices in some procedural
operations-- they are just processed like most other attributes.
**Performance**
This touches so many areas that it's hard to benchmark exhaustively,
but I observed some areas as examples.
* The mesh line node with 4 million count was 1.5x (8ms to 12ms) faster.
* The Spring splash screen went from ~4.3 to ~4.5 fps.
* The subdivision surface modifier/node was slightly faster
RNA access through Python may be slightly slower, since now we need
a name lookup instead of just a custom data type lookup for each index.
**Future Improvements**
* Remove uses of "vert_coords" functions:
* `BKE_mesh_vert_coords_alloc`
* `BKE_mesh_vert_coords_get`
* `BKE_mesh_vert_coords_apply{_with_mat4}`
* Remove more hidden copying of positions
* General simplification now possible in many areas
* Convert more code to C++ to use `float3` instead of `float[3]`
* Currently `reinterpret_cast` is used for those C-API functions
Differential Revision: https://developer.blender.org/D15982
2023-01-10 06:10:43 +01:00
|
|
|
vert_positions_[vert_indices[0]],
|
|
|
|
vert_positions_[vert_indices[1]],
|
|
|
|
vert_positions_[vert_indices[2]],
|
2022-04-15 16:39:50 +02:00
|
|
|
barycentric);
|
|
|
|
return result;
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
2022-11-28 08:31:24 +01:00
|
|
|
static std::vector<bool> init_uv_primitives_brush_test(SculptSession *ss,
|
|
|
|
PaintGeometryPrimitives &geom_primitives,
|
|
|
|
PaintUVPrimitives &uv_primitives,
|
2023-11-16 18:29:52 +01:00
|
|
|
const Span<float3> positions)
|
2022-04-15 16:39:50 +02:00
|
|
|
{
|
2022-11-28 08:31:24 +01:00
|
|
|
std::vector<bool> brush_test(uv_primitives.size());
|
2022-04-15 16:39:50 +02:00
|
|
|
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);
|
2022-11-28 08:31:24 +01:00
|
|
|
for (int uv_prim_index = 0; uv_prim_index < uv_primitives.size(); uv_prim_index++) {
|
|
|
|
const UVPrimitivePaintInput &paint_input = uv_primitives.get_paint_input(uv_prim_index);
|
|
|
|
const int3 &vert_indices = geom_primitives.get_vert_indices(
|
|
|
|
paint_input.geometry_primitive_index);
|
2022-04-15 16:39:50 +02:00
|
|
|
|
Mesh: Move positions to a generic attribute
**Changes**
As described in T93602, this patch removes all use of the `MVert`
struct, replacing it with a generic named attribute with the name
`"position"`, consistent with other geometry types.
Variable names have been changed from `verts` to `positions`, to align
with the attribute name and the more generic design (positions are not
vertices, they are just an attribute stored on the point domain).
This change is made possible by previous commits that moved all other
data out of `MVert` to runtime data or other generic attributes. What
remains is mostly a simple type change. Though, the type still shows up
859 times, so the patch is quite large.
One compromise is that now `CD_MASK_BAREMESH` now contains
`CD_PROP_FLOAT3`. With the general move towards generic attributes
over custom data types, we are removing use of these type masks anyway.
**Benefits**
The most obvious benefit is reduced memory usage and the benefits
that brings in memory-bound situations. `float3` is only 3 bytes, in
comparison to `MVert` which was 4. When there are millions of vertices
this starts to matter more.
The other benefits come from using a more generic type. Instead of
writing algorithms specifically for `MVert`, code can just use arrays
of vectors. This will allow eliminating many temporary arrays or
wrappers used to extract positions.
Many possible improvements aren't implemented in this patch, though
I did switch simplify or remove the process of creating temporary
position arrays in a few places.
The design clarity that "positions are just another attribute" brings
allows removing explicit copying of vertices in some procedural
operations-- they are just processed like most other attributes.
**Performance**
This touches so many areas that it's hard to benchmark exhaustively,
but I observed some areas as examples.
* The mesh line node with 4 million count was 1.5x (8ms to 12ms) faster.
* The Spring splash screen went from ~4.3 to ~4.5 fps.
* The subdivision surface modifier/node was slightly faster
RNA access through Python may be slightly slower, since now we need
a name lookup instead of just a custom data type lookup for each index.
**Future Improvements**
* Remove uses of "vert_coords" functions:
* `BKE_mesh_vert_coords_alloc`
* `BKE_mesh_vert_coords_get`
* `BKE_mesh_vert_coords_apply{_with_mat4}`
* Remove more hidden copying of positions
* General simplification now possible in many areas
* Convert more code to C++ to use `float3` instead of `float[3]`
* Currently `reinterpret_cast` is used for those C-API functions
Differential Revision: https://developer.blender.org/D15982
2023-01-10 06:10:43 +01:00
|
|
|
float3 triangle_min_bounds(positions[vert_indices[0]]);
|
2022-04-15 16:39:50 +02:00
|
|
|
float3 triangle_max_bounds(triangle_min_bounds);
|
|
|
|
for (int i = 1; i < 3; i++) {
|
Mesh: Move positions to a generic attribute
**Changes**
As described in T93602, this patch removes all use of the `MVert`
struct, replacing it with a generic named attribute with the name
`"position"`, consistent with other geometry types.
Variable names have been changed from `verts` to `positions`, to align
with the attribute name and the more generic design (positions are not
vertices, they are just an attribute stored on the point domain).
This change is made possible by previous commits that moved all other
data out of `MVert` to runtime data or other generic attributes. What
remains is mostly a simple type change. Though, the type still shows up
859 times, so the patch is quite large.
One compromise is that now `CD_MASK_BAREMESH` now contains
`CD_PROP_FLOAT3`. With the general move towards generic attributes
over custom data types, we are removing use of these type masks anyway.
**Benefits**
The most obvious benefit is reduced memory usage and the benefits
that brings in memory-bound situations. `float3` is only 3 bytes, in
comparison to `MVert` which was 4. When there are millions of vertices
this starts to matter more.
The other benefits come from using a more generic type. Instead of
writing algorithms specifically for `MVert`, code can just use arrays
of vectors. This will allow eliminating many temporary arrays or
wrappers used to extract positions.
Many possible improvements aren't implemented in this patch, though
I did switch simplify or remove the process of creating temporary
position arrays in a few places.
The design clarity that "positions are just another attribute" brings
allows removing explicit copying of vertices in some procedural
operations-- they are just processed like most other attributes.
**Performance**
This touches so many areas that it's hard to benchmark exhaustively,
but I observed some areas as examples.
* The mesh line node with 4 million count was 1.5x (8ms to 12ms) faster.
* The Spring splash screen went from ~4.3 to ~4.5 fps.
* The subdivision surface modifier/node was slightly faster
RNA access through Python may be slightly slower, since now we need
a name lookup instead of just a custom data type lookup for each index.
**Future Improvements**
* Remove uses of "vert_coords" functions:
* `BKE_mesh_vert_coords_alloc`
* `BKE_mesh_vert_coords_get`
* `BKE_mesh_vert_coords_apply{_with_mat4}`
* Remove more hidden copying of positions
* General simplification now possible in many areas
* Convert more code to C++ to use `float3` instead of `float[3]`
* Currently `reinterpret_cast` is used for those C-API functions
Differential Revision: https://developer.blender.org/D15982
2023-01-10 06:10:43 +01:00
|
|
|
const float3 &pos = positions[vert_indices[i]];
|
2022-04-15 16:39:50 +02:00
|
|
|
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);
|
|
|
|
}
|
2022-11-28 08:31:24 +01:00
|
|
|
brush_test[uv_prim_index] = isect_aabb_aabb_v3(
|
2022-04-15 16:39:50 +02:00
|
|
|
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;
|
2022-11-28 08:31:24 +01:00
|
|
|
PBVH *pbvh = ss->pbvh;
|
2022-04-15 16:39:50 +02:00
|
|
|
PBVHNode *node = data->nodes[n];
|
2022-11-28 08:31:24 +01:00
|
|
|
PBVHData &pbvh_data = BKE_pbvh_pixels_data_get(*pbvh);
|
2022-04-15 16:39:50 +02:00
|
|
|
NodeData &node_data = BKE_pbvh_pixels_node_data_get(*node);
|
|
|
|
const int thread_id = BLI_task_parallel_thread_id(tls);
|
2023-11-16 18:29:52 +01:00
|
|
|
const Span<float3> positions = SCULPT_mesh_deformed_positions_get(ss);
|
2022-04-15 16:39:50 +02:00
|
|
|
|
2022-11-28 08:31:24 +01:00
|
|
|
std::vector<bool> brush_test = init_uv_primitives_brush_test(
|
Mesh: Move positions to a generic attribute
**Changes**
As described in T93602, this patch removes all use of the `MVert`
struct, replacing it with a generic named attribute with the name
`"position"`, consistent with other geometry types.
Variable names have been changed from `verts` to `positions`, to align
with the attribute name and the more generic design (positions are not
vertices, they are just an attribute stored on the point domain).
This change is made possible by previous commits that moved all other
data out of `MVert` to runtime data or other generic attributes. What
remains is mostly a simple type change. Though, the type still shows up
859 times, so the patch is quite large.
One compromise is that now `CD_MASK_BAREMESH` now contains
`CD_PROP_FLOAT3`. With the general move towards generic attributes
over custom data types, we are removing use of these type masks anyway.
**Benefits**
The most obvious benefit is reduced memory usage and the benefits
that brings in memory-bound situations. `float3` is only 3 bytes, in
comparison to `MVert` which was 4. When there are millions of vertices
this starts to matter more.
The other benefits come from using a more generic type. Instead of
writing algorithms specifically for `MVert`, code can just use arrays
of vectors. This will allow eliminating many temporary arrays or
wrappers used to extract positions.
Many possible improvements aren't implemented in this patch, though
I did switch simplify or remove the process of creating temporary
position arrays in a few places.
The design clarity that "positions are just another attribute" brings
allows removing explicit copying of vertices in some procedural
operations-- they are just processed like most other attributes.
**Performance**
This touches so many areas that it's hard to benchmark exhaustively,
but I observed some areas as examples.
* The mesh line node with 4 million count was 1.5x (8ms to 12ms) faster.
* The Spring splash screen went from ~4.3 to ~4.5 fps.
* The subdivision surface modifier/node was slightly faster
RNA access through Python may be slightly slower, since now we need
a name lookup instead of just a custom data type lookup for each index.
**Future Improvements**
* Remove uses of "vert_coords" functions:
* `BKE_mesh_vert_coords_alloc`
* `BKE_mesh_vert_coords_get`
* `BKE_mesh_vert_coords_apply{_with_mat4}`
* Remove more hidden copying of positions
* General simplification now possible in many areas
* Convert more code to C++ to use `float3` instead of `float[3]`
* Currently `reinterpret_cast` is used for those C-API functions
Differential Revision: https://developer.blender.org/D15982
2023-01-10 06:10:43 +01:00
|
|
|
ss, pbvh_data.geom_primitives, node_data.uv_primitives, positions);
|
2022-04-15 16:39:50 +02:00
|
|
|
|
Mesh: Move positions to a generic attribute
**Changes**
As described in T93602, this patch removes all use of the `MVert`
struct, replacing it with a generic named attribute with the name
`"position"`, consistent with other geometry types.
Variable names have been changed from `verts` to `positions`, to align
with the attribute name and the more generic design (positions are not
vertices, they are just an attribute stored on the point domain).
This change is made possible by previous commits that moved all other
data out of `MVert` to runtime data or other generic attributes. What
remains is mostly a simple type change. Though, the type still shows up
859 times, so the patch is quite large.
One compromise is that now `CD_MASK_BAREMESH` now contains
`CD_PROP_FLOAT3`. With the general move towards generic attributes
over custom data types, we are removing use of these type masks anyway.
**Benefits**
The most obvious benefit is reduced memory usage and the benefits
that brings in memory-bound situations. `float3` is only 3 bytes, in
comparison to `MVert` which was 4. When there are millions of vertices
this starts to matter more.
The other benefits come from using a more generic type. Instead of
writing algorithms specifically for `MVert`, code can just use arrays
of vectors. This will allow eliminating many temporary arrays or
wrappers used to extract positions.
Many possible improvements aren't implemented in this patch, though
I did switch simplify or remove the process of creating temporary
position arrays in a few places.
The design clarity that "positions are just another attribute" brings
allows removing explicit copying of vertices in some procedural
operations-- they are just processed like most other attributes.
**Performance**
This touches so many areas that it's hard to benchmark exhaustively,
but I observed some areas as examples.
* The mesh line node with 4 million count was 1.5x (8ms to 12ms) faster.
* The Spring splash screen went from ~4.3 to ~4.5 fps.
* The subdivision surface modifier/node was slightly faster
RNA access through Python may be slightly slower, since now we need
a name lookup instead of just a custom data type lookup for each index.
**Future Improvements**
* Remove uses of "vert_coords" functions:
* `BKE_mesh_vert_coords_alloc`
* `BKE_mesh_vert_coords_get`
* `BKE_mesh_vert_coords_apply{_with_mat4}`
* Remove more hidden copying of positions
* General simplification now possible in many areas
* Convert more code to C++ to use `float3` instead of `float[3]`
* Currently `reinterpret_cast` is used for those C-API functions
Differential Revision: https://developer.blender.org/D15982
2023-01-10 06:10:43 +01:00
|
|
|
PaintingKernel<ImageBufferFloat4> kernel_float4(ss, brush, thread_id, positions);
|
|
|
|
PaintingKernel<ImageBufferByte4> kernel_byte4(ss, brush, thread_id, positions);
|
2022-04-15 16:39:50 +02:00
|
|
|
|
2023-01-23 19:29:40 +01:00
|
|
|
float brush_color[4];
|
|
|
|
|
|
|
|
#ifdef DEBUG_PIXEL_NODES
|
|
|
|
uint hash = BLI_hash_int(POINTER_AS_UINT(node));
|
|
|
|
|
2023-07-14 04:23:37 +02:00
|
|
|
brush_color[0] = float(hash & 255) / 255.0f;
|
|
|
|
brush_color[1] = float((hash >> 8) & 255) / 255.0f;
|
|
|
|
brush_color[2] = float((hash >> 16) & 255) / 255.0f;
|
2023-01-23 19:29:40 +01:00
|
|
|
#else
|
|
|
|
copy_v3_v3(brush_color,
|
|
|
|
ss->cache->invert ? BKE_brush_secondary_color_get(ss->scene, brush) :
|
|
|
|
BKE_brush_color_get(ss->scene, brush));
|
|
|
|
#endif
|
|
|
|
|
|
|
|
brush_color[3] = 1.0f;
|
|
|
|
|
2022-09-29 08:21:56 +02:00
|
|
|
AutomaskingNodeData automask_data;
|
2023-11-09 16:15:54 +01:00
|
|
|
SCULPT_automasking_node_begin(ob, ss->cache->automasking, &automask_data, data->nodes[n]);
|
2022-09-29 08:21:56 +02:00
|
|
|
|
2022-04-15 16:39:50 +02:00
|
|
|
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;
|
|
|
|
}
|
|
|
|
|
2023-05-18 10:19:01 +02:00
|
|
|
if (image_buffer->float_buffer.data != nullptr) {
|
2023-01-23 19:29:40 +01:00
|
|
|
kernel_float4.init_brush_color(image_buffer, brush_color);
|
2022-04-15 16:39:50 +02:00
|
|
|
}
|
|
|
|
else {
|
2023-01-23 19:29:40 +01:00
|
|
|
kernel_byte4.init_brush_color(image_buffer, brush_color);
|
2022-04-15 16:39:50 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
for (const PackedPixelRow &pixel_row : tile_data.pixel_rows) {
|
2022-11-28 08:31:24 +01:00
|
|
|
if (!brush_test[pixel_row.uv_primitive_index]) {
|
2022-04-15 16:39:50 +02:00
|
|
|
continue;
|
|
|
|
}
|
|
|
|
bool pixels_painted = false;
|
2023-05-18 10:19:01 +02:00
|
|
|
if (image_buffer->float_buffer.data != nullptr) {
|
2022-11-29 05:32:28 +01:00
|
|
|
pixels_painted = kernel_float4.paint(pbvh_data.geom_primitives,
|
|
|
|
node_data.uv_primitives,
|
|
|
|
pixel_row,
|
|
|
|
image_buffer,
|
|
|
|
&automask_data);
|
2022-04-15 16:39:50 +02:00
|
|
|
}
|
|
|
|
else {
|
2022-11-29 05:32:28 +01:00
|
|
|
pixels_painted = kernel_byte4.paint(pbvh_data.geom_primitives,
|
|
|
|
node_data.uv_primitives,
|
|
|
|
pixel_row,
|
|
|
|
image_buffer,
|
|
|
|
&automask_data);
|
2022-04-15 16:39:50 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
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;
|
|
|
|
}
|
|
|
|
|
2022-05-16 15:42:43 +02:00
|
|
|
static void undo_region_tiles(
|
|
|
|
ImBuf *ibuf, int x, int y, int w, int h, int *tx, int *ty, int *tw, int *th)
|
|
|
|
{
|
|
|
|
int srcx = 0, srcy = 0;
|
|
|
|
IMB_rectclip(ibuf, nullptr, &x, &y, &srcx, &srcy, &w, &h);
|
|
|
|
*tw = ((x + w - 1) >> ED_IMAGE_UNDO_TILE_BITS);
|
|
|
|
*th = ((y + h - 1) >> ED_IMAGE_UNDO_TILE_BITS);
|
|
|
|
*tx = (x >> ED_IMAGE_UNDO_TILE_BITS);
|
|
|
|
*ty = (y >> ED_IMAGE_UNDO_TILE_BITS);
|
|
|
|
}
|
|
|
|
|
|
|
|
static void push_undo(const NodeData &node_data,
|
|
|
|
Image &image,
|
|
|
|
ImageUser &image_user,
|
|
|
|
const image::ImageTileWrapper &image_tile,
|
|
|
|
ImBuf &image_buffer,
|
|
|
|
ImBuf **tmpibuf)
|
|
|
|
{
|
|
|
|
for (const UDIMTileUndo &tile_undo : node_data.undo_regions) {
|
|
|
|
if (tile_undo.tile_number != image_tile.get_tile_number()) {
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
int tilex, tiley, tilew, tileh;
|
2022-07-25 07:56:17 +02:00
|
|
|
PaintTileMap *undo_tiles = ED_image_paint_tile_map_get();
|
2022-05-16 15:42:43 +02:00
|
|
|
undo_region_tiles(&image_buffer,
|
|
|
|
tile_undo.region.xmin,
|
|
|
|
tile_undo.region.ymin,
|
|
|
|
BLI_rcti_size_x(&tile_undo.region),
|
|
|
|
BLI_rcti_size_y(&tile_undo.region),
|
|
|
|
&tilex,
|
|
|
|
&tiley,
|
|
|
|
&tilew,
|
|
|
|
&tileh);
|
|
|
|
for (int ty = tiley; ty <= tileh; ty++) {
|
|
|
|
for (int tx = tilex; tx <= tilew; tx++) {
|
|
|
|
ED_image_paint_tile_push(undo_tiles,
|
|
|
|
&image,
|
|
|
|
&image_buffer,
|
|
|
|
tmpibuf,
|
|
|
|
&image_user,
|
|
|
|
tx,
|
|
|
|
ty,
|
|
|
|
nullptr,
|
|
|
|
nullptr,
|
|
|
|
true,
|
|
|
|
true);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
static void do_push_undo_tile(void *__restrict userdata,
|
|
|
|
const int n,
|
2022-10-04 00:37:25 +02:00
|
|
|
const TaskParallelTLS *__restrict /*tls*/)
|
2022-05-16 15:42:43 +02:00
|
|
|
{
|
|
|
|
TexturePaintingUserData *data = static_cast<TexturePaintingUserData *>(userdata);
|
|
|
|
PBVHNode *node = data->nodes[n];
|
|
|
|
|
|
|
|
NodeData &node_data = BKE_pbvh_pixels_node_data_get(*node);
|
|
|
|
Image *image = data->image_data.image;
|
|
|
|
ImageUser *image_user = data->image_data.image_user;
|
|
|
|
|
|
|
|
ImBuf *tmpibuf = nullptr;
|
|
|
|
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;
|
|
|
|
}
|
|
|
|
|
|
|
|
push_undo(node_data, *image, *image_user, image_tile, *image_buffer, &tmpibuf);
|
|
|
|
BKE_image_release_ibuf(image, image_buffer, nullptr);
|
|
|
|
}
|
|
|
|
if (tmpibuf) {
|
|
|
|
IMB_freeImBuf(tmpibuf);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-04-15 16:39:50 +02:00
|
|
|
static void do_mark_dirty_regions(void *__restrict userdata,
|
|
|
|
const int n,
|
2022-10-04 00:37:25 +02:00
|
|
|
const TaskParallelTLS *__restrict /*tls*/)
|
2022-04-15 16:39:50 +02:00
|
|
|
{
|
|
|
|
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);
|
|
|
|
}
|
Texture Painting: Fix Seam Bleeding of Non-Manifold Sections of Mesh
Fix seam bleeding of non-manifold sections of the mesh, by copying pixels
that are covered by the brush stroke.
As manifold parts are already handled, the pixel copying solution can be
very straight forward.
* Pixels are copied from the same tile. So we don't need a mechanism that
copies and merges pixels from other tiles.
* Pixels are copied from the closest pixel that is being painted on. We
don't need to consider that that pixel can be in different areas of the
tile.
When we copy a pixel, we find the closest pixel in UV space that is being
directly influenced by a paint brush. We also look for the second closest
pixel, which is still a neighbor from the closest pixel. We can mix both
pixels together and store it in the destination. A mix factor is calculated
using the closest non manifold edge as a guidance.
The result of this step is a list of copy and mix commands that can be
executed to fix the seam bleeding for non-manifold sections of the mesh.
| Destination | Source 1 | Source 2 | Mix factor |
| ----------- | -------- | -------- | ---------- |
| 1780,1811 | 1780,1810| 1779,1810| 0.000000 |
| 1781,1811 | 1781,1810| 1782,1811| 0.168627 |
| 1828,1811 | 1828,1810| 1827,1811| 0.156863 |
| 1829,1811 | 1829,1810| 1828,1810| 0.188235 |
| 1830,1811 | 1830,1810| 1829,1810| 0.188235 |
| 1831,1811 | 1831,1810| 1830,1810| 0.188235 |
| 1832,1811 | 1832,1810| 1831,1810| 0.188235 |
| 1833,1811 | 1832,1810| 1832,1810| 0.000000 |
In the end we go over this list mix the sources and store the result at
the destination.
```
tile_buffer[destination] = mix(tile_buffer[source_1],
tile_buffer[source_2],
mix_factor);
```
**Encoding**
When using a large textures or large seam margins this table can grow
large and reduce performance as data retrieval is slower, than the
operations it has to perform. To improve the performance we encode the
table so less data retrieval needs to be done.
* first `DeltaCopyPixelCommand` is delta encoded from
`CopyPixelGroup#start_destination` and `start_source_1`. The others
are delta encoded from the previous `DeltaCopyPixelCommand`.
* For performance reasons PixelCopyGroup#pixels are ordered from
destination (left to right) for each row a new group would be created
as the delta encoding most likely doesn't fit. When pixels cannot be
delta encoded a new group will also be created.
**Compression rate**
When using Suzanne the compression rate is around 36% when using a seam
margin of 4 pixels. The compression rate may vary depending on seam
margin and model. For Suzanne the compression rate was around 36% for
various resolutions.
| Resolution | Margin | Decoded size | Encoded size | Compression |
| ---------- | ------ | ------------ | ------------ | ----------- |
| 2048x2048 | 4 px | 353.052 | 128.101 | 36% |
| 4096x4096 | 4 px | 700.140 | 255.137 | 36% |
| 8192x8192 | 4 px | 1.419.320 | 513.802 | 36% |
| 2048x2048 | 8 px | 721.084 | 193.629 | 26% |
| 4096x4096 | 8 px | 1.444.968 | 388.110 | 26% |
Pull Request: https://projects.blender.org/blender/blender/pulls/105336
2023-03-09 16:11:01 +01:00
|
|
|
/* -------------------------------------------------------------------- */
|
|
|
|
|
|
|
|
/** \name Fix non-manifold edge bleeding.
|
|
|
|
* \{ */
|
|
|
|
|
|
|
|
static Vector<image::TileNumber> collect_dirty_tiles(Span<PBVHNode *> nodes)
|
|
|
|
{
|
|
|
|
Vector<image::TileNumber> dirty_tiles;
|
|
|
|
for (PBVHNode *node : nodes) {
|
|
|
|
BKE_pbvh_pixels_collect_dirty_tiles(*node, dirty_tiles);
|
|
|
|
}
|
|
|
|
return dirty_tiles;
|
|
|
|
}
|
|
|
|
static void fix_non_manifold_seam_bleeding(PBVH &pbvh,
|
|
|
|
TexturePaintingUserData &user_data,
|
|
|
|
Span<TileNumber> tile_numbers_to_fix)
|
|
|
|
{
|
|
|
|
for (image::TileNumber tile_number : tile_numbers_to_fix) {
|
|
|
|
BKE_pbvh_pixels_copy_pixels(
|
|
|
|
pbvh, *user_data.image_data.image, *user_data.image_data.image_user, tile_number);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-04-14 21:16:42 +02:00
|
|
|
static void fix_non_manifold_seam_bleeding(Object &ob, TexturePaintingUserData &user_data)
|
Texture Painting: Fix Seam Bleeding of Non-Manifold Sections of Mesh
Fix seam bleeding of non-manifold sections of the mesh, by copying pixels
that are covered by the brush stroke.
As manifold parts are already handled, the pixel copying solution can be
very straight forward.
* Pixels are copied from the same tile. So we don't need a mechanism that
copies and merges pixels from other tiles.
* Pixels are copied from the closest pixel that is being painted on. We
don't need to consider that that pixel can be in different areas of the
tile.
When we copy a pixel, we find the closest pixel in UV space that is being
directly influenced by a paint brush. We also look for the second closest
pixel, which is still a neighbor from the closest pixel. We can mix both
pixels together and store it in the destination. A mix factor is calculated
using the closest non manifold edge as a guidance.
The result of this step is a list of copy and mix commands that can be
executed to fix the seam bleeding for non-manifold sections of the mesh.
| Destination | Source 1 | Source 2 | Mix factor |
| ----------- | -------- | -------- | ---------- |
| 1780,1811 | 1780,1810| 1779,1810| 0.000000 |
| 1781,1811 | 1781,1810| 1782,1811| 0.168627 |
| 1828,1811 | 1828,1810| 1827,1811| 0.156863 |
| 1829,1811 | 1829,1810| 1828,1810| 0.188235 |
| 1830,1811 | 1830,1810| 1829,1810| 0.188235 |
| 1831,1811 | 1831,1810| 1830,1810| 0.188235 |
| 1832,1811 | 1832,1810| 1831,1810| 0.188235 |
| 1833,1811 | 1832,1810| 1832,1810| 0.000000 |
In the end we go over this list mix the sources and store the result at
the destination.
```
tile_buffer[destination] = mix(tile_buffer[source_1],
tile_buffer[source_2],
mix_factor);
```
**Encoding**
When using a large textures or large seam margins this table can grow
large and reduce performance as data retrieval is slower, than the
operations it has to perform. To improve the performance we encode the
table so less data retrieval needs to be done.
* first `DeltaCopyPixelCommand` is delta encoded from
`CopyPixelGroup#start_destination` and `start_source_1`. The others
are delta encoded from the previous `DeltaCopyPixelCommand`.
* For performance reasons PixelCopyGroup#pixels are ordered from
destination (left to right) for each row a new group would be created
as the delta encoding most likely doesn't fit. When pixels cannot be
delta encoded a new group will also be created.
**Compression rate**
When using Suzanne the compression rate is around 36% when using a seam
margin of 4 pixels. The compression rate may vary depending on seam
margin and model. For Suzanne the compression rate was around 36% for
various resolutions.
| Resolution | Margin | Decoded size | Encoded size | Compression |
| ---------- | ------ | ------------ | ------------ | ----------- |
| 2048x2048 | 4 px | 353.052 | 128.101 | 36% |
| 4096x4096 | 4 px | 700.140 | 255.137 | 36% |
| 8192x8192 | 4 px | 1.419.320 | 513.802 | 36% |
| 2048x2048 | 8 px | 721.084 | 193.629 | 26% |
| 4096x4096 | 8 px | 1.444.968 | 388.110 | 26% |
Pull Request: https://projects.blender.org/blender/blender/pulls/105336
2023-03-09 16:11:01 +01:00
|
|
|
{
|
2023-04-14 21:16:42 +02:00
|
|
|
Vector<image::TileNumber> dirty_tiles = collect_dirty_tiles(user_data.nodes);
|
Texture Painting: Fix Seam Bleeding of Non-Manifold Sections of Mesh
Fix seam bleeding of non-manifold sections of the mesh, by copying pixels
that are covered by the brush stroke.
As manifold parts are already handled, the pixel copying solution can be
very straight forward.
* Pixels are copied from the same tile. So we don't need a mechanism that
copies and merges pixels from other tiles.
* Pixels are copied from the closest pixel that is being painted on. We
don't need to consider that that pixel can be in different areas of the
tile.
When we copy a pixel, we find the closest pixel in UV space that is being
directly influenced by a paint brush. We also look for the second closest
pixel, which is still a neighbor from the closest pixel. We can mix both
pixels together and store it in the destination. A mix factor is calculated
using the closest non manifold edge as a guidance.
The result of this step is a list of copy and mix commands that can be
executed to fix the seam bleeding for non-manifold sections of the mesh.
| Destination | Source 1 | Source 2 | Mix factor |
| ----------- | -------- | -------- | ---------- |
| 1780,1811 | 1780,1810| 1779,1810| 0.000000 |
| 1781,1811 | 1781,1810| 1782,1811| 0.168627 |
| 1828,1811 | 1828,1810| 1827,1811| 0.156863 |
| 1829,1811 | 1829,1810| 1828,1810| 0.188235 |
| 1830,1811 | 1830,1810| 1829,1810| 0.188235 |
| 1831,1811 | 1831,1810| 1830,1810| 0.188235 |
| 1832,1811 | 1832,1810| 1831,1810| 0.188235 |
| 1833,1811 | 1832,1810| 1832,1810| 0.000000 |
In the end we go over this list mix the sources and store the result at
the destination.
```
tile_buffer[destination] = mix(tile_buffer[source_1],
tile_buffer[source_2],
mix_factor);
```
**Encoding**
When using a large textures or large seam margins this table can grow
large and reduce performance as data retrieval is slower, than the
operations it has to perform. To improve the performance we encode the
table so less data retrieval needs to be done.
* first `DeltaCopyPixelCommand` is delta encoded from
`CopyPixelGroup#start_destination` and `start_source_1`. The others
are delta encoded from the previous `DeltaCopyPixelCommand`.
* For performance reasons PixelCopyGroup#pixels are ordered from
destination (left to right) for each row a new group would be created
as the delta encoding most likely doesn't fit. When pixels cannot be
delta encoded a new group will also be created.
**Compression rate**
When using Suzanne the compression rate is around 36% when using a seam
margin of 4 pixels. The compression rate may vary depending on seam
margin and model. For Suzanne the compression rate was around 36% for
various resolutions.
| Resolution | Margin | Decoded size | Encoded size | Compression |
| ---------- | ------ | ------------ | ------------ | ----------- |
| 2048x2048 | 4 px | 353.052 | 128.101 | 36% |
| 4096x4096 | 4 px | 700.140 | 255.137 | 36% |
| 8192x8192 | 4 px | 1.419.320 | 513.802 | 36% |
| 2048x2048 | 8 px | 721.084 | 193.629 | 26% |
| 4096x4096 | 8 px | 1.444.968 | 388.110 | 26% |
Pull Request: https://projects.blender.org/blender/blender/pulls/105336
2023-03-09 16:11:01 +01:00
|
|
|
fix_non_manifold_seam_bleeding(*ob.sculpt->pbvh, user_data, dirty_tiles);
|
|
|
|
}
|
|
|
|
|
|
|
|
/** \} */
|
2022-04-15 16:39:50 +02:00
|
|
|
|
|
|
|
} // namespace blender::ed::sculpt_paint::paint::image
|
|
|
|
|
|
|
|
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)
|
|
|
|
{
|
2022-05-19 02:14:30 +02:00
|
|
|
*r_image = nullptr;
|
|
|
|
*r_image_user = nullptr;
|
|
|
|
|
2022-04-15 16:39:50 +02:00
|
|
|
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);
|
|
|
|
}
|
|
|
|
|
2023-01-23 19:29:40 +01:00
|
|
|
void SCULPT_do_paint_brush_image(PaintModeSettings *paint_mode_settings,
|
|
|
|
Sculpt *sd,
|
|
|
|
Object *ob,
|
2023-04-14 21:16:42 +02:00
|
|
|
Span<PBVHNode *> texnodes)
|
2022-04-15 16:39:50 +02:00
|
|
|
{
|
|
|
|
Brush *brush = BKE_paint_brush(&sd->paint);
|
|
|
|
|
|
|
|
TexturePaintingUserData data = {nullptr};
|
|
|
|
data.ob = ob;
|
|
|
|
data.brush = brush;
|
2023-01-23 19:29:40 +01:00
|
|
|
data.nodes = texnodes;
|
2022-04-15 16:39:50 +02:00
|
|
|
|
|
|
|
if (!ImageData::init_active_image(ob, &data.image_data, paint_mode_settings)) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
TaskParallelSettings settings;
|
2023-04-14 21:16:42 +02:00
|
|
|
BKE_pbvh_parallel_range_settings(&settings, true, texnodes.size());
|
|
|
|
BLI_task_parallel_range(0, texnodes.size(), &data, do_push_undo_tile, &settings);
|
|
|
|
BLI_task_parallel_range(0, texnodes.size(), &data, do_paint_pixels, &settings);
|
|
|
|
fix_non_manifold_seam_bleeding(*ob, data);
|
2022-04-15 16:39:50 +02:00
|
|
|
|
|
|
|
TaskParallelSettings settings_flush;
|
2023-01-23 19:29:40 +01:00
|
|
|
|
2023-04-14 21:16:42 +02:00
|
|
|
BKE_pbvh_parallel_range_settings(&settings_flush, false, texnodes.size());
|
|
|
|
BLI_task_parallel_range(0, texnodes.size(), &data, do_mark_dirty_regions, &settings_flush);
|
2022-04-15 16:39:50 +02:00
|
|
|
}
|