Changeset View
Changeset View
Standalone View
Standalone View
source/blender/editors/sculpt_paint/sculpt_paint_image.cc
| /* SPDX-License-Identifier: GPL-2.0-or-later | /* SPDX-License-Identifier: GPL-2.0-or-later | ||||
| * Copyright 2022 Blender Foundation. All rights reserved. */ | * Copyright 2022 Blender Foundation. All rights reserved. */ | ||||
| #include "DNA_image_types.h" | #include "DNA_image_types.h" | ||||
| #include "DNA_material_types.h" | #include "DNA_material_types.h" | ||||
| #include "DNA_mesh_types.h" | #include "DNA_mesh_types.h" | ||||
| #include "DNA_node_types.h" | #include "DNA_node_types.h" | ||||
| #include "DNA_object_types.h" | #include "DNA_object_types.h" | ||||
| #include "ED_paint.h" | #include "ED_paint.h" | ||||
| #include "ED_uvedit.h" | #include "ED_uvedit.h" | ||||
| #include "BLI_math.h" | #include "BLI_math.h" | ||||
| #include "BLI_math_color_blend.h" | #include "BLI_math_color_blend.h" | ||||
| #include "BLI_task.h" | #include "BLI_task.h" | ||||
| #include "GPU_capabilities.h" | |||||
| #include "GPU_compute.h" | |||||
| #include "GPU_debug.h" | |||||
| #include "GPU_shader.h" | |||||
| #include "GPU_uniform_buffer.h" | |||||
| #include "IMB_colormanagement.h" | #include "IMB_colormanagement.h" | ||||
| #include "IMB_imbuf.h" | #include "IMB_imbuf.h" | ||||
| #include "BKE_brush.h" | #include "BKE_brush.h" | ||||
| #include "BKE_image_wrappers.hh" | #include "BKE_image_wrappers.hh" | ||||
| #include "BKE_material.h" | #include "BKE_material.h" | ||||
| #include "BKE_pbvh.h" | #include "BKE_pbvh.h" | ||||
| #include "BKE_pbvh_pixels.hh" | #include "BKE_pbvh_pixels.hh" | ||||
| Show All 19 Lines | static bool init_active_image(Object *ob, | ||||
| ImageData *r_image_data, | ImageData *r_image_data, | ||||
| PaintModeSettings *paint_mode_settings) | PaintModeSettings *paint_mode_settings) | ||||
| { | { | ||||
| return BKE_paint_canvas_image_get( | return BKE_paint_canvas_image_get( | ||||
| paint_mode_settings, ob, &r_image_data->image, &r_image_data->image_user); | paint_mode_settings, ob, &r_image_data->image, &r_image_data->image_user); | ||||
| } | } | ||||
| }; | }; | ||||
| /* -------------------------------------------------------------------- */ | |||||
| /** \name CPU | |||||
| * \{ */ | |||||
| struct TexturePaintingUserData { | struct TexturePaintingUserData { | ||||
| Object *ob; | Object *ob; | ||||
| Brush *brush; | Brush *brush; | ||||
| PBVHNode **nodes; | PBVHNode **nodes; | ||||
| ImageData image_data; | ImageData image_data; | ||||
| int32_t nodes_len; | |||||
| }; | }; | ||||
| /** Reading and writing to image buffer with 4 float channels. */ | /** Reading and writing to image buffer with 4 float channels. */ | ||||
| class ImageBufferFloat4 { | class ImageBufferFloat4 { | ||||
| private: | private: | ||||
| int pixel_offset; | int pixel_offset; | ||||
| public: | public: | ||||
| ▲ Show 20 Lines • Show All 83 Lines • ▼ Show 20 Lines | explicit PaintingKernel(SculptSession *ss, | ||||
| const int thread_id, | const int thread_id, | ||||
| const MVert *mvert) | const MVert *mvert) | ||||
| : ss(ss), brush(brush), thread_id(thread_id), mvert(mvert) | : ss(ss), brush(brush), thread_id(thread_id), mvert(mvert) | ||||
| { | { | ||||
| init_brush_strength(); | init_brush_strength(); | ||||
| init_brush_test(); | init_brush_test(); | ||||
| } | } | ||||
| bool paint(const Triangles &triangles, const PackedPixelRow &pixel_row, ImBuf *image_buffer) | bool paint(const Triangles &triangles, | ||||
| const PackedPixelRow &pixel_row, | |||||
| ImBuf *image_buffer, | |||||
| AutomaskingNodeData *automask_data) | |||||
| { | { | ||||
| image_accessor.set_image_position(image_buffer, pixel_row.start_image_coordinate); | image_accessor.set_image_position(image_buffer, pixel_row.start_image_coordinate); | ||||
| const TrianglePaintInput triangle = triangles.get_paint_input(pixel_row.triangle_index); | const TrianglePaintInput triangle = triangles.get_paint_input(pixel_row.triangle_index); | ||||
| float3 pixel_pos = get_start_pixel_pos(triangle, pixel_row); | float3 pixel_pos = get_start_pixel_pos(triangle, pixel_row); | ||||
| const float3 delta_pixel_pos = get_delta_pixel_pos(triangle, pixel_row, pixel_pos); | const float3 delta_pixel_pos = get_delta_pixel_pos(triangle, pixel_row, pixel_pos); | ||||
| bool pixels_painted = false; | bool pixels_painted = false; | ||||
| for (int x = 0; x < pixel_row.num_pixels; x++) { | for (int x = 0; x < pixel_row.num_pixels; x++) { | ||||
| if (!brush_test_fn(&test, pixel_pos)) { | if (!brush_test_fn(&test, pixel_pos)) { | ||||
| pixel_pos += delta_pixel_pos; | pixel_pos += delta_pixel_pos; | ||||
| image_accessor.next_pixel(); | image_accessor.next_pixel(); | ||||
| continue; | continue; | ||||
| } | } | ||||
| float4 color = image_accessor.read_pixel(image_buffer); | float4 color = image_accessor.read_pixel(image_buffer); | ||||
| const float3 normal(0.0f, 0.0f, 0.0f); | const float3 normal(0.0f, 0.0f, 0.0f); | ||||
| const float3 face_normal(0.0f, 0.0f, 0.0f); | const float3 face_normal(0.0f, 0.0f, 0.0f); | ||||
| const float mask = 0.0f; | const float mask = 0.0f; | ||||
| const float falloff_strength = SCULPT_brush_strength_factor( | const float falloff_strength = SCULPT_brush_strength_factor( | ||||
| ss, | ss, | ||||
| brush, | brush, | ||||
| pixel_pos, | pixel_pos, | ||||
| sqrtf(test.dist), | sqrtf(test.dist), | ||||
| normal, | normal, | ||||
| face_normal, | face_normal, | ||||
| mask, | mask, | ||||
| BKE_pbvh_make_vref(PBVH_REF_NONE), | BKE_pbvh_make_vref(PBVH_REF_NONE), | ||||
| thread_id); | thread_id, | ||||
| automask_data); | |||||
| float4 paint_color = brush_color * falloff_strength * brush_strength; | float4 paint_color = brush_color * falloff_strength * brush_strength; | ||||
| float4 buffer_color; | float4 buffer_color; | ||||
| blend_color_mix_float(buffer_color, color, paint_color); | blend_color_mix_float(buffer_color, color, paint_color); | ||||
| buffer_color *= brush->alpha; | buffer_color *= brush->alpha; | ||||
| IMB_blend_color_float(color, color, buffer_color, static_cast<IMB_BlendMode>(brush->blend)); | IMB_blend_color_float(color, color, buffer_color, static_cast<IMB_BlendMode>(brush->blend)); | ||||
| image_accessor.write_pixel(image_buffer, color); | image_accessor.write_pixel(image_buffer, color); | ||||
| pixels_painted = true; | pixels_painted = true; | ||||
| ▲ Show 20 Lines • Show All 51 Lines • ▼ Show 20 Lines | private: | ||||
| * Extract the delta pixel position that will be used to advance a Pixel instance to the next | * Extract the delta pixel position that will be used to advance a Pixel instance to the next | ||||
| * pixel. | * pixel. | ||||
| */ | */ | ||||
| float3 get_delta_pixel_pos(const TrianglePaintInput &triangle, | float3 get_delta_pixel_pos(const TrianglePaintInput &triangle, | ||||
| const PackedPixelRow &encoded_pixels, | const PackedPixelRow &encoded_pixels, | ||||
| const float3 &start_pixel) const | const float3 &start_pixel) const | ||||
| { | { | ||||
| float3 result = init_pixel_pos( | float3 result = init_pixel_pos( | ||||
| triangle, encoded_pixels.start_barycentric_coord + triangle.delta_barycentric_coord_u); | triangle, encoded_pixels.start_barycentric_coord + triangle.delta_barycentric_coord); | ||||
| return result - start_pixel; | return result - start_pixel; | ||||
| } | } | ||||
| float3 init_pixel_pos(const TrianglePaintInput &triangle, | float3 init_pixel_pos(const TrianglePaintInput &triangle, | ||||
| const float2 &barycentric_weights) const | const float2 &barycentric_weights) const | ||||
| { | { | ||||
| const int3 &vert_indices = triangle.vert_indices; | const int3 &vert_indices = triangle.vert_indices; | ||||
| float3 result; | float3 result; | ||||
| ▲ Show 20 Lines • Show All 56 Lines • ▼ Show 20 Lines | static void do_paint_pixels(void *__restrict userdata, | ||||
| const int thread_id = BLI_task_parallel_thread_id(tls); | const int thread_id = BLI_task_parallel_thread_id(tls); | ||||
| MVert *mvert = SCULPT_mesh_deformed_mverts_get(ss); | MVert *mvert = SCULPT_mesh_deformed_mverts_get(ss); | ||||
| std::vector<bool> brush_test = init_triangle_brush_test(ss, node_data.triangles, mvert); | std::vector<bool> brush_test = init_triangle_brush_test(ss, node_data.triangles, mvert); | ||||
| PaintingKernel<ImageBufferFloat4> kernel_float4(ss, brush, thread_id, mvert); | PaintingKernel<ImageBufferFloat4> kernel_float4(ss, brush, thread_id, mvert); | ||||
| PaintingKernel<ImageBufferByte4> kernel_byte4(ss, brush, thread_id, mvert); | PaintingKernel<ImageBufferByte4> kernel_byte4(ss, brush, thread_id, mvert); | ||||
| AutomaskingNodeData automask_data; | |||||
| SCULPT_automasking_node_begin(ob, ss, ss->cache->automasking, &automask_data, data->nodes[n]); | |||||
| ImageUser image_user = *data->image_data.image_user; | ImageUser image_user = *data->image_data.image_user; | ||||
| bool pixels_updated = false; | bool pixels_updated = false; | ||||
| for (UDIMTilePixels &tile_data : node_data.tiles) { | for (UDIMTilePixels &tile_data : node_data.tiles) { | ||||
| LISTBASE_FOREACH (ImageTile *, tile, &data->image_data.image->tiles) { | LISTBASE_FOREACH (ImageTile *, tile, &data->image_data.image->tiles) { | ||||
| ImageTileWrapper image_tile(tile); | ImageTileWrapper image_tile(tile); | ||||
| if (image_tile.get_tile_number() == tile_data.tile_number) { | if (image_tile.get_tile_number() == tile_data.tile_number) { | ||||
| image_user.tile = image_tile.get_tile_number(); | image_user.tile = image_tile.get_tile_number(); | ||||
| Show All 10 Lines | LISTBASE_FOREACH (ImageTile *, tile, &data->image_data.image->tiles) { | ||||
| } | } | ||||
| for (const PackedPixelRow &pixel_row : tile_data.pixel_rows) { | for (const PackedPixelRow &pixel_row : tile_data.pixel_rows) { | ||||
| if (!brush_test[pixel_row.triangle_index]) { | if (!brush_test[pixel_row.triangle_index]) { | ||||
| continue; | continue; | ||||
| } | } | ||||
| bool pixels_painted = false; | bool pixels_painted = false; | ||||
| if (image_buffer->rect_float != nullptr) { | if (image_buffer->rect_float != nullptr) { | ||||
| pixels_painted = kernel_float4.paint(node_data.triangles, pixel_row, image_buffer); | pixels_painted = kernel_float4.paint( | ||||
| node_data.triangles, pixel_row, image_buffer, &automask_data); | |||||
| } | } | ||||
| else { | else { | ||||
| pixels_painted = kernel_byte4.paint(node_data.triangles, pixel_row, image_buffer); | pixels_painted = kernel_byte4.paint( | ||||
| node_data.triangles, pixel_row, image_buffer, &automask_data); | |||||
| } | } | ||||
| if (pixels_painted) { | if (pixels_painted) { | ||||
| tile_data.mark_dirty(pixel_row); | tile_data.mark_dirty(pixel_row); | ||||
| } | } | ||||
| } | } | ||||
| BKE_image_release_ibuf(data->image_data.image, image_buffer, nullptr); | BKE_image_release_ibuf(data->image_data.image, image_buffer, nullptr); | ||||
| pixels_updated |= tile_data.flags.dirty; | pixels_updated |= tile_data.flags.dirty; | ||||
| break; | break; | ||||
| } | } | ||||
| } | } | ||||
| } | } | ||||
| node_data.flags.dirty |= pixels_updated; | node_data.flags.dirty |= pixels_updated; | ||||
| } | } | ||||
| /** \} */ | |||||
| /* -------------------------------------------------------------------- */ | |||||
| /** \name Undo | |||||
| * \{ */ | |||||
| static void undo_region_tiles( | static void undo_region_tiles( | ||||
| ImBuf *ibuf, int x, int y, int w, int h, int *tx, int *ty, int *tw, int *th) | ImBuf *ibuf, int x, int y, int w, int h, int *tx, int *ty, int *tw, int *th) | ||||
| { | { | ||||
| int srcx = 0, srcy = 0; | int srcx = 0, srcy = 0; | ||||
| IMB_rectclip(ibuf, nullptr, &x, &y, &srcx, &srcy, &w, &h); | IMB_rectclip(ibuf, nullptr, &x, &y, &srcx, &srcy, &w, &h); | ||||
| *tw = ((x + w - 1) >> ED_IMAGE_UNDO_TILE_BITS); | *tw = ((x + w - 1) >> ED_IMAGE_UNDO_TILE_BITS); | ||||
| *th = ((y + h - 1) >> ED_IMAGE_UNDO_TILE_BITS); | *th = ((y + h - 1) >> ED_IMAGE_UNDO_TILE_BITS); | ||||
| *tx = (x >> ED_IMAGE_UNDO_TILE_BITS); | *tx = (x >> ED_IMAGE_UNDO_TILE_BITS); | ||||
| ▲ Show 20 Lines • Show All 73 Lines • ▼ Show 20 Lines | static void do_mark_dirty_regions(void *__restrict userdata, | ||||
| const int n, | const int n, | ||||
| const TaskParallelTLS *__restrict UNUSED(tls)) | const TaskParallelTLS *__restrict UNUSED(tls)) | ||||
| { | { | ||||
| TexturePaintingUserData *data = static_cast<TexturePaintingUserData *>(userdata); | TexturePaintingUserData *data = static_cast<TexturePaintingUserData *>(userdata); | ||||
| PBVHNode *node = data->nodes[n]; | PBVHNode *node = data->nodes[n]; | ||||
| BKE_pbvh_pixels_mark_image_dirty(*node, *data->image_data.image, *data->image_data.image_user); | BKE_pbvh_pixels_mark_image_dirty(*node, *data->image_data.image, *data->image_data.image_user); | ||||
| } | } | ||||
| /** \} */ | |||||
| /* -------------------------------------------------------------------- */ | |||||
| /** \name GPU | |||||
| * \{ */ | |||||
| struct GPUSculptPaintData { | |||||
| Vector<PaintStepData> steps; | |||||
| }; | |||||
| static void ensure_gpu_buffers(TexturePaintingUserData &data) | |||||
| { | |||||
| SculptSession &ss = *data.ob->sculpt; | |||||
| if (!ss.mode.texture_paint.gpu_data) { | |||||
| ss.mode.texture_paint.gpu_data = MEM_new<GPUSculptPaintData>(__func__); | |||||
| PBVH *pbvh = ss.pbvh; | |||||
| BKE_pbvh_frame_selection_clear(pbvh); | |||||
| } | |||||
| for (PBVHNode *node : MutableSpan<PBVHNode *>(data.nodes, data.nodes_len)) { | |||||
| NodeData &node_data = BKE_pbvh_pixels_node_data_get(*node); | |||||
| node_data.ensure_gpu_buffers(); | |||||
| } | |||||
| } | |||||
| static void gpu_painting_paint_step(TexturePaintingUserData &data, | |||||
| TileNumber tile_number, | |||||
| ImBuf *image_buffer, | |||||
| GPUTexture **tex_ptr, | |||||
| GPUUniformBuf *paint_brush_buf, | |||||
| GPUStorageBuf *paint_step_buf, | |||||
| int2 paint_step_range, | |||||
| GPUStorageBuf *vert_coord_buf) | |||||
| { | |||||
| GPUShader *shader = SCULPT_shader_paint_image_get(); | |||||
| bool texture_needs_clearing = true; | |||||
| GPUTexture *tex = *tex_ptr; | |||||
| /* Ensure that texture size is same as tile size. */ | |||||
| if (tex == nullptr || GPU_texture_width(tex) != image_buffer->x || | |||||
| GPU_texture_height(tex) != image_buffer->y) { | |||||
| if (tex) { | |||||
| GPU_texture_free(tex); | |||||
| tex = nullptr; | |||||
| } | |||||
| tex = GPU_texture_create_2d( | |||||
| __func__, image_buffer->x, image_buffer->y, 1, GPU_RGBA32F, nullptr); | |||||
| *tex_ptr = tex; | |||||
| } | |||||
| /* Dispatch all nodes that paint on the active tile. */ | |||||
| for (PBVHNode *node : MutableSpan<PBVHNode *>(data.nodes, data.nodes_len)) { | |||||
| NodeData &node_data = BKE_pbvh_pixels_node_data_get(*node); | |||||
| /* TODO: Fix this! We should check for nodes that have been tagged. */ | |||||
| if (!node_data.triangles.gpu_buffer) { | |||||
| continue; | |||||
| } | |||||
| for (UDIMTilePixels &tile_pixels : node_data.tiles) { | |||||
| if (tile_pixels.tile_number != tile_number) { | |||||
| continue; | |||||
| } | |||||
| /* Only clear the texture when it is used for the first time. */ | |||||
| if (texture_needs_clearing) { | |||||
| // Copy from image buffer? | |||||
| GPU_texture_clear(tex, GPU_DATA_FLOAT, float4(0.0f, 0.0f, 0.0f, 0.0f)); | |||||
| texture_needs_clearing = false; | |||||
| } | |||||
| GPU_shader_bind(shader); | |||||
| GPU_texture_image_bind(tex, GPU_shader_get_texture_binding(shader, "out_img")); | |||||
| GPU_storagebuf_bind(paint_step_buf, GPU_shader_get_ssbo(shader, "paint_step_buf")); | |||||
| GPU_shader_uniform_2iv(shader, "paint_step_range", paint_step_range); | |||||
| GPU_uniformbuf_bind(paint_brush_buf, | |||||
| GPU_shader_get_uniform_block(shader, "paint_brush_buf")); | |||||
| GPU_storagebuf_bind(vert_coord_buf, GPU_shader_get_ssbo(shader, "vert_coord_buf")); | |||||
| GPU_storagebuf_bind(node_data.triangles.gpu_buffer, | |||||
| GPU_shader_get_ssbo(shader, "paint_input")); | |||||
| GPU_storagebuf_bind(node_data.gpu_buffers.pixels, | |||||
| GPU_shader_get_ssbo(shader, "pixel_row_buf")); | |||||
| GPU_shader_uniform_1i(shader, "pixel_row_offset", tile_pixels.gpu_buffer_offset); | |||||
| GPU_compute_dispatch(shader, tile_pixels.pixel_rows.size(), 1, 1); | |||||
| } | |||||
| node_data.ensure_gpu_buffers(); | |||||
| } | |||||
| } | |||||
| static void gpu_painting_image_merge(TexturePaintingUserData &UNUSED(data), | |||||
| Image &image, | |||||
| ImageUser &image_user, | |||||
| ImBuf &image_buffer, | |||||
| GPUTexture *paint_tex) | |||||
| { | |||||
| GPUTexture *canvas_tex = BKE_image_get_gpu_texture(&image, &image_user, &image_buffer); | |||||
| GPUShader *shader = SCULPT_shader_paint_image_merge_get(); | |||||
| GPU_shader_bind(shader); | |||||
| GPU_texture_image_bind(paint_tex, GPU_shader_get_texture_binding(shader, "in_paint_img")); | |||||
| GPU_texture_image_bind(canvas_tex, GPU_shader_get_texture_binding(shader, "out_img")); | |||||
| GPU_compute_dispatch(shader, image_buffer.x, image_buffer.y, 1); | |||||
| } | |||||
| static void init_paint_brush_color(const SculptSession &ss, | |||||
| const Brush &brush, | |||||
| PaintBrushData &r_paint_brush) | |||||
| { | |||||
| if (ss.cache->invert) { | |||||
| copy_v3_v3(r_paint_brush.color, BKE_brush_secondary_color_get(ss.scene, &brush)); | |||||
| } | |||||
| else { | |||||
| copy_v3_v3(r_paint_brush.color, 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. */ | |||||
| srgb_to_linearrgb_v3_v3(r_paint_brush.color, r_paint_brush.color); | |||||
| r_paint_brush.color[3] = 1.0f; | |||||
| } | |||||
| static void init_paint_brush_strength(const SculptSession &ss, PaintBrushData &r_paint_brush) | |||||
| { | |||||
| r_paint_brush.strength = ss.cache->bstrength; | |||||
| } | |||||
| /* TODO: Currently only spherical is supported. */ | |||||
| static void init_paint_brush_test(const SculptSession &ss, PaintBrushData &r_paint_brush) | |||||
| { | |||||
| r_paint_brush.test.symm_rot_mat_inv = ss.cache->symm_rot_mat_inv; | |||||
| } | |||||
| static void init_paint_brush(const SculptSession &ss, | |||||
| const Brush &brush, | |||||
| PaintBrushData &r_paint_brush) | |||||
| { | |||||
| init_paint_brush_color(ss, brush, r_paint_brush); | |||||
| init_paint_brush_strength(ss, r_paint_brush); | |||||
| init_paint_brush_test(ss, r_paint_brush); | |||||
| } | |||||
| static void init_paint_step(const SculptSession &ss, PaintStepData &r_paint_step) | |||||
| { | |||||
| r_paint_step.location = ss.cache->location; | |||||
| r_paint_step.radius = ss.cache->radius; | |||||
| r_paint_step.mirror_symmetry_pass = ss.cache->mirror_symmetry_pass; | |||||
| } | |||||
| static GPUStorageBuf *gpu_painting_vert_coord_create(SculptSession &ss) | |||||
| { | |||||
| Vector<float4> vert_coords; | |||||
| vert_coords.reserve(ss.totvert); | |||||
| for (const MVert &mvert : Span<MVert>(ss.mvert, ss.totvert)) { | |||||
| float3 co(mvert.co); | |||||
| vert_coords.append(float4(co.x, co.y, co.z, 0.0f)); | |||||
| } | |||||
| GPUStorageBuf *result = GPU_storagebuf_create_ex( | |||||
| sizeof(float4) * ss.totvert, vert_coords.data(), GPU_USAGE_STATIC, __func__); | |||||
| return result; | |||||
| } | |||||
| static void dispatch_gpu_painting(TexturePaintingUserData &data) | |||||
| { | |||||
| SculptSession &ss = *data.ob->sculpt; | |||||
| GPUSculptPaintData &batches = *static_cast<GPUSculptPaintData *>(ss.mode.texture_paint.gpu_data); | |||||
| PaintStepData paint_step; | |||||
| init_paint_step(ss, paint_step); | |||||
| batches.steps.append(paint_step); | |||||
| } | |||||
| static void update_frame_selection(TexturePaintingUserData &data) | |||||
| { | |||||
| for (PBVHNode *node : MutableSpan<PBVHNode *>(data.nodes, data.nodes_len)) { | |||||
| BKE_pbvh_node_frame_selection_mark(node); | |||||
| } | |||||
| } | |||||
| using TileNumbers = Vector<TileNumber, 8>; | |||||
| /* Collect all tile numbers that the node selection is using. This will reduce the read misses | |||||
| * when handling multiple Tiles. Most likely only a small amount of tiles are actually used. */ | |||||
| static TileNumbers collect_active_tile_numbers(const TexturePaintingUserData &data) | |||||
| { | |||||
| Vector<TileNumber, 8> result; | |||||
| for (PBVHNode *node : Span<PBVHNode *>(data.nodes, data.nodes_len)) { | |||||
| NodeData &node_data = BKE_pbvh_pixels_node_data_get(*node); | |||||
| for (const UDIMTilePixels &tile : node_data.tiles) { | |||||
| result.append_non_duplicates(tile.tile_number); | |||||
| } | |||||
| } | |||||
| return result; | |||||
| } | |||||
| static void dispatch_gpu_batches(TexturePaintingUserData &data) | |||||
| { | |||||
| SculptSession &ss = *data.ob->sculpt; | |||||
| if (!ss.mode.texture_paint.gpu_data) { | |||||
| return; | |||||
| } | |||||
| GPUSculptPaintData *batches = static_cast<GPUSculptPaintData *>(ss.mode.texture_paint.gpu_data); | |||||
| const int64_t steps_len = batches->steps.size(); | |||||
| int2 paint_step_range(0, steps_len); | |||||
| GPUStorageBuf *paint_step_buf = GPU_storagebuf_create_ex( | |||||
| sizeof(PaintStepData) * steps_len, batches->steps.data(), GPU_USAGE_STATIC, "PaintStepData"); | |||||
| GPUStorageBuf *vert_coord_buf = gpu_painting_vert_coord_create(ss); | |||||
| PaintBrushData paint_brush; | |||||
| init_paint_brush(ss, *data.brush, paint_brush); | |||||
| GPUUniformBuf *paint_brush_buf = GPU_uniformbuf_create_ex( | |||||
| sizeof(PaintBrushData), &paint_brush, "PaintBrushData"); | |||||
| GPUTexture *tex = nullptr; | |||||
| Image &image = *data.image_data.image; | |||||
| ImageUser local_image_user = *data.image_data.image_user; | |||||
| TileNumbers tile_numbers = collect_active_tile_numbers(data); | |||||
| for (TileNumber tile_number : tile_numbers) { | |||||
| local_image_user.tile = tile_number; | |||||
| ImBuf *image_buffer = BKE_image_acquire_ibuf(&image, &local_image_user, nullptr); | |||||
| if (image_buffer == nullptr) { | |||||
| continue; | |||||
| } | |||||
| GPU_debug_group_begin("Paint tile"); | |||||
| gpu_painting_paint_step(data, | |||||
| tile_number, | |||||
| image_buffer, | |||||
| &tex, | |||||
| paint_brush_buf, | |||||
| paint_step_buf, | |||||
| paint_step_range, | |||||
| vert_coord_buf); | |||||
| gpu_painting_image_merge(data, *data.image_data.image, local_image_user, *image_buffer, tex); | |||||
| GPU_debug_group_end(); | |||||
| BKE_image_release_ibuf(data.image_data.image, image_buffer, nullptr); | |||||
| } | |||||
| /* Clean up temp values. */ | |||||
| GPU_uniformbuf_free(paint_brush_buf); | |||||
| GPU_storagebuf_free(paint_step_buf); | |||||
| if (tex) { | |||||
| GPU_texture_free(tex); | |||||
| tex = nullptr; | |||||
| } | |||||
| GPU_storagebuf_free(vert_coord_buf); | |||||
| MEM_delete(batches); | |||||
| ss.mode.texture_paint.gpu_data = nullptr; | |||||
| } | |||||
| /** \} */ | |||||
| } // namespace blender::ed::sculpt_paint::paint::image | } // namespace blender::ed::sculpt_paint::paint::image | ||||
| extern "C" { | extern "C" { | ||||
| using 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, | bool SCULPT_paint_image_canvas_get(PaintModeSettings *paint_mode_settings, | ||||
| Object *ob, | Object *ob, | ||||
| Show All 21 Lines | bool SCULPT_use_image_paint_brush(PaintModeSettings *settings, Object *ob) | ||||
| if (ob->type != OB_MESH) { | if (ob->type != OB_MESH) { | ||||
| return false; | return false; | ||||
| } | } | ||||
| Image *image; | Image *image; | ||||
| ImageUser *image_user; | ImageUser *image_user; | ||||
| return BKE_paint_canvas_image_get(settings, ob, &image, &image_user); | return BKE_paint_canvas_image_get(settings, ob, &image, &image_user); | ||||
| } | } | ||||
| /** Can the sculpt paint be performed on the GPU? */ | |||||
| static bool SCULPT_use_image_paint_compute() | |||||
| { | |||||
| #if 0 | |||||
| return false; | |||||
| #else | |||||
| return GPU_compute_shader_support() && GPU_shader_storage_buffer_objects_support() && | |||||
| GPU_shader_image_load_store_support(); | |||||
| #endif | |||||
| } | |||||
| void SCULPT_do_paint_brush_image( | void SCULPT_do_paint_brush_image( | ||||
| PaintModeSettings *paint_mode_settings, Sculpt *sd, Object *ob, PBVHNode **nodes, int totnode) | PaintModeSettings *paint_mode_settings, Sculpt *sd, Object *ob, PBVHNode **nodes, int totnode) | ||||
| { | { | ||||
| Brush *brush = BKE_paint_brush(&sd->paint); | Brush *brush = BKE_paint_brush(&sd->paint); | ||||
| TexturePaintingUserData data = {nullptr}; | TexturePaintingUserData data = {nullptr}; | ||||
| data.ob = ob; | data.ob = ob; | ||||
| data.brush = brush; | data.brush = brush; | ||||
| data.nodes = nodes; | data.nodes = nodes; | ||||
| data.nodes_len = totnode; | |||||
| if (!ImageData::init_active_image(ob, &data.image_data, paint_mode_settings)) { | if (!ImageData::init_active_image(ob, &data.image_data, paint_mode_settings)) { | ||||
| return; | return; | ||||
| } | } | ||||
| if (SCULPT_use_image_paint_compute()) { | |||||
| ensure_gpu_buffers(data); | |||||
| update_frame_selection(data); | |||||
| dispatch_gpu_painting(data); | |||||
| } | |||||
| else { | |||||
| TaskParallelSettings settings; | TaskParallelSettings settings; | ||||
| BKE_pbvh_parallel_range_settings(&settings, true, totnode); | BKE_pbvh_parallel_range_settings(&settings, true, totnode); | ||||
| BLI_task_parallel_range(0, totnode, &data, do_push_undo_tile, &settings); | BLI_task_parallel_range(0, totnode, &data, do_push_undo_tile, &settings); | ||||
| BLI_task_parallel_range(0, totnode, &data, do_paint_pixels, &settings); | BLI_task_parallel_range(0, totnode, &data, do_paint_pixels, &settings); | ||||
| TaskParallelSettings settings_flush; | TaskParallelSettings settings_flush; | ||||
| BKE_pbvh_parallel_range_settings(&settings_flush, false, totnode); | BKE_pbvh_parallel_range_settings(&settings_flush, false, totnode); | ||||
| BLI_task_parallel_range(0, totnode, &data, do_mark_dirty_regions, &settings_flush); | BLI_task_parallel_range(0, totnode, &data, do_mark_dirty_regions, &settings_flush); | ||||
| } | } | ||||
| } | } | ||||
| void SCULPT_paint_image_batches_flush(PaintModeSettings *paint_mode_settings, | |||||
| Sculpt *sd, | |||||
| Object *ob) | |||||
| { | |||||
| if (!SCULPT_use_image_paint_compute()) { | |||||
| return; | |||||
| } | |||||
| Brush *brush = BKE_paint_brush(&sd->paint); | |||||
| TexturePaintingUserData data = {nullptr}; | |||||
| data.ob = ob; | |||||
| data.brush = brush; | |||||
| BKE_pbvh_search_gather_frame_selected(ob->sculpt->pbvh, &data.nodes, &data.nodes_len); | |||||
| if (data.nodes_len == 0) { | |||||
| return; | |||||
| } | |||||
| if (ImageData::init_active_image(ob, &data.image_data, paint_mode_settings)) { | |||||
| GPU_debug_group_begin("SCULPT_paint_brush"); | |||||
| dispatch_gpu_batches(data); | |||||
| GPU_debug_group_end(); | |||||
| } | |||||
| MEM_freeN(data.nodes); | |||||
| } | |||||
| } | |||||