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

git.blender.org/blender.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
Diffstat (limited to 'source/blender/editors/sculpt_paint/sculpt_paint_image.cc')
-rw-r--r--source/blender/editors/sculpt_paint/sculpt_paint_image.cc340
1 files changed, 289 insertions, 51 deletions
diff --git a/source/blender/editors/sculpt_paint/sculpt_paint_image.cc b/source/blender/editors/sculpt_paint/sculpt_paint_image.cc
index 99353b934fd..a44b9141578 100644
--- a/source/blender/editors/sculpt_paint/sculpt_paint_image.cc
+++ b/source/blender/editors/sculpt_paint/sculpt_paint_image.cc
@@ -526,7 +526,6 @@ static void init_paint_brush_alpha(const Brush &brush, PaintBrushData &r_paint_b
r_paint_brush.alpha = brush.alpha;
}
-/* 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;
@@ -547,6 +546,237 @@ static void init_paint_brush(const SculptSession &ss,
init_paint_brush_falloff(brush, r_paint_brush);
}
+/**
+ * Tiles are split on the GPU in sub-tiles.
+ *
+ * Sub tiles are used to reduce the needed memory on the GPU.
+ * - Only tiles that are painted on are loaded in memory, painted on and merged back to the actual
+ * texture.
+ */
+
+template<int32_t Size, int32_t Depth = 512> class GPUSubTileTexture {
+ struct Info {
+ struct {
+ bool in_use : 1;
+ /* Does this sub tile needs to be updated (CPU->GPU transfer).*/
+ bool needs_update : 1;
+ bool should_be_removed : 1;
+ } flags;
+ };
+ const int32_t LayerIdUnused = -1;
+ const int32_t LayerIdMarkRemoval = -2;
+
+ Vector<PaintTileData> paint_tiles_;
+ Vector<Info> infos_;
+
+ std::array<int32_t, Depth> layer_lookup_;
+
+ GPUTexture *gpu_texture_ = nullptr;
+ GPUStorageBuf *tile_buf_ = nullptr;
+ int64_t tile_buf_size_ = 0;
+
+ public:
+ GPUSubTileTexture()
+ {
+ for (int i = 0; i < Depth; i++) {
+ layer_lookup_[i] = LayerIdUnused;
+ }
+ }
+ ~GPUSubTileTexture()
+ {
+ if (gpu_texture_) {
+ GPU_texture_free(gpu_texture_);
+ gpu_texture_ = nullptr;
+ }
+
+ if (tile_buf_) {
+ GPU_storagebuf_free(tile_buf_);
+ tile_buf_ = nullptr;
+ }
+ }
+
+ void reset_usage()
+ {
+ printf("%s\n", __func__);
+ for (Info &info : infos_) {
+ info.flags.in_use = false;
+ }
+ }
+
+ void mark_usage(TileNumber tile_number, int2 sub_tile_id)
+ {
+ for (int index : paint_tiles_.index_range()) {
+ PaintTileData &tile = paint_tiles_[index];
+ if (tile.tile_number == tile_number && tile.sub_tile_id == sub_tile_id) {
+ Info &info = infos_[index];
+ if (!info.flags.in_use) {
+ printf("%s: mark existing {tile:%d, sub_tile:%d,%d}\n",
+ __func__,
+ tile_number,
+ UNPACK2(sub_tile_id));
+ }
+ info.flags.in_use = true;
+ return;
+ }
+ }
+
+ /* Tile not yet added, add a new one.*/
+ Info info;
+ info.flags.in_use = true;
+ info.flags.needs_update = true;
+ info.flags.should_be_removed = false;
+ infos_.append(info);
+
+ PaintTileData tile;
+ tile.tile_number = tile_number;
+ tile.sub_tile_id = sub_tile_id;
+ tile.layer_id = LayerIdUnused;
+ paint_tiles_.append(tile);
+
+ printf(
+ "%s: mark new {tile:%d, sub_tile:%d,%d}\n", __func__, tile_number, UNPACK2(sub_tile_id));
+ }
+
+ /** Remove all sub tiles that are currently flagged not to be used (flags.in_use = false). */
+ void remove_unused()
+ {
+ for (int i = 0; i < layer_lookup_.size(); i++) {
+ int index = layer_lookup_[i];
+ if (index == -1) {
+ continue;
+ }
+ infos_[index].flags.should_be_removed = false;
+ if (infos_[index].flags.in_use == false) {
+ infos_[index].flags.should_be_removed = true;
+ paint_tiles_[index].layer_id = LayerIdMarkRemoval;
+ printf("%s: remove sub tile at layer %d\n", __func__, i);
+ layer_lookup_[i] = -1;
+ }
+ }
+
+ infos_.remove_if([&](Info &info) { return info.flags.should_be_removed; });
+ paint_tiles_.remove_if(
+ [&](PaintTileData &tile) { return tile.layer_id == LayerIdMarkRemoval; });
+ }
+
+ void assign_layer_ids()
+ {
+ for (int64_t index : paint_tiles_.index_range()) {
+ PaintTileData &tile = paint_tiles_[index];
+
+ if (tile.layer_id != LayerIdUnused) {
+ continue;
+ }
+
+ tile.layer_id = first_empty_layer_id();
+ layer_lookup_[tile.layer_id] = index;
+ printf("%s: assign {tile:%d, sub_tile:%d,%d} to layer %d\n",
+ __func__,
+ tile.tile_number,
+ UNPACK2(tile.sub_tile_id),
+ tile.layer_id);
+ }
+ }
+
+ int first_empty_layer_id() const
+ {
+ for (int i = 0; i < Depth; i++) {
+ if (layer_lookup_[i] == LayerIdUnused) {
+ return i;
+ }
+ }
+
+ BLI_assert_unreachable();
+ return LayerIdUnused;
+ }
+
+ void ensure_gpu_texture()
+ {
+ if (gpu_texture_ != nullptr) {
+ return;
+ }
+ gpu_texture_ = GPU_texture_create_3d(
+ "GPUSubTileTexture", Size, Size, Depth, 1, GPU_RGBA16F, GPU_DATA_FLOAT, nullptr);
+ }
+
+ void update_gpu_texture(TileNumber tile_number, ImBuf &UNUSED(image_buffer))
+ {
+ BLI_assert(gpu_texture_);
+ float *buffer = nullptr;
+ for (int64_t index : infos_.index_range()) {
+ Info &info = infos_[index];
+ PaintTileData &tile = paint_tiles_[index];
+ if (!info.flags.needs_update) {
+ continue;
+ }
+
+ if (tile.tile_number != tile_number) {
+ continue;
+ }
+
+ if (buffer == nullptr) {
+ buffer = static_cast<float *>(MEM_callocN(Size * Size * 4 * sizeof(float), __func__));
+ }
+
+ /* TODO: Copy correct data from ImBuf.*/
+
+ GPU_texture_update_sub(
+ gpu_texture_, GPU_DATA_FLOAT, buffer, 0, 0, tile.layer_id, Size, Size, 1);
+ info.flags.needs_update = false;
+ }
+
+ if (buffer) {
+ MEM_freeN(buffer);
+ }
+ }
+
+ GPUTexture *gpu_texture_get()
+ {
+ return gpu_texture_;
+ }
+
+ void ensure_tile_buf()
+ {
+ int64_t needed_size = paint_tiles_.capacity() * sizeof(PaintTileData);
+
+ /* Reuse previous buffer only when exact size, due to potentional read out of bound errors.*/
+ if (tile_buf_ && tile_buf_size_ == needed_size) {
+ return;
+ }
+
+ if (tile_buf_) {
+ GPU_storagebuf_free(tile_buf_);
+ tile_buf_ = nullptr;
+ }
+ tile_buf_ = GPU_storagebuf_create(needed_size);
+ }
+
+ void update_tile_buf()
+ {
+ BLI_assert(tile_buf_);
+ GPU_storagebuf_update(tile_buf_, paint_tiles_.data());
+ }
+
+ GPUStorageBuf *tile_buf_get()
+ {
+ BLI_assert(tile_buf_);
+ return tile_buf_;
+ }
+
+ int32_t paint_tiles_len()
+ {
+ return paint_tiles_.size();
+ }
+
+ void bind(GPUShader *shader)
+ {
+ GPU_texture_image_bind(gpu_texture_get(),
+ GPU_shader_get_texture_binding(shader, "paint_tiles_img"));
+ GPU_storagebuf_bind(tile_buf_get(), GPU_shader_get_ssbo(shader, "paint_tile_buf"));
+ GPU_shader_uniform_1i(shader, "paint_tile_buf_len", paint_tiles_len());
+ }
+};
+
struct GPUSculptPaintData {
Vector<PaintStepData> steps;
GPUStorageBuf *step_buf = nullptr;
@@ -554,7 +784,7 @@ struct GPUSculptPaintData {
GPUStorageBuf *vert_coord_buf = nullptr;
GPUUniformBuf *paint_brush_buf = nullptr;
- GPUTexture *tile_texture = nullptr;
+ GPUSubTileTexture<TEXTURE_STREAMING_TILE_SIZE> tile_texture;
~GPUSculptPaintData()
{
@@ -572,18 +802,13 @@ struct GPUSculptPaintData {
GPU_storagebuf_free(step_buf);
step_buf = nullptr;
}
-
- if (tile_texture) {
- GPU_texture_free(tile_texture);
- tile_texture = nullptr;
- }
}
void update_step_buf()
{
int requested_size = sizeof(PaintStepData) * steps.size();
- /* Reallocate buffer when it doesn't fit, or is to big to correct reading from uninitialized
- * memory. */
+ /* Reallocate buffer when it doesn't fit, or is to big to correct reading from
+ * uninitialized memory. */
const bool reallocate_buf = (requested_size > step_buf_alloc_size) ||
(sizeof(PaintStepData) * steps.capacity() < step_buf_alloc_size);
@@ -623,24 +848,13 @@ struct GPUSculptPaintData {
GPU_uniformbuf_update(paint_brush_buf, &paint_brush);
}
-
- void ensure_tile_texture(const int2 resolution)
- {
- if (tile_texture == nullptr || GPU_texture_width(tile_texture) != resolution.x ||
- GPU_texture_height(tile_texture) != resolution.y) {
- if (tile_texture) {
- GPU_texture_free(tile_texture);
- tile_texture = nullptr;
- }
- tile_texture = GPU_texture_create_2d(__func__, UNPACK2(resolution), 1, GPU_RGBA16F, nullptr);
- }
- }
};
static void ensure_gpu_buffers(TexturePaintingUserData &data)
{
SculptSession &ss = *data.ob->sculpt;
if (!ss.mode.texture_paint.gpu_data) {
+ printf("%s: new gpu_data\n", __func__);
ss.mode.texture_paint.gpu_data = MEM_new<GPUSculptPaintData>(__func__);
}
@@ -649,6 +863,7 @@ static void ensure_gpu_buffers(TexturePaintingUserData &data)
if (paint_data.steps.is_empty()) {
PBVH *pbvh = ss.pbvh;
BKE_pbvh_frame_selection_clear(pbvh);
+ paint_data.tile_texture.reset_usage();
}
for (PBVHNode *node : MutableSpan<PBVHNode *>(data.nodes, data.nodes_len)) {
@@ -668,20 +883,14 @@ static BrushVariationFlags determine_shader_variation_flags(const Brush &brush)
return result;
}
-// TODO: Currently only working on a copy of the actual data. In most use cases this isn't needed
-// and can we paint directly on the target gpu target.
static void gpu_painting_paint_step(TexturePaintingUserData &data,
GPUSculptPaintData &batches,
TileNumber tile_number,
- ImBuf *image_buffer,
int2 paint_step_range)
{
BrushVariationFlags variation_flags = determine_shader_variation_flags(*data.brush);
GPUShader *shader = SCULPT_shader_paint_image_get(variation_flags);
- batches.ensure_tile_texture(int2(image_buffer->x, image_buffer->y));
- bool texture_needs_clearing = true;
-
/* 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);
@@ -690,16 +899,10 @@ static void gpu_painting_paint_step(TexturePaintingUserData &data,
continue;
}
- /* Only clear the texture when it is used for the first time. */
- if (texture_needs_clearing) {
- // Copy from image buffer?
- GPU_texture_clear(batches.tile_texture, GPU_DATA_FLOAT, float4(0.0f, 0.0f, 0.0f, 1.0f));
- texture_needs_clearing = false;
- }
-
GPU_shader_bind(shader);
- GPU_texture_image_bind(batches.tile_texture,
- GPU_shader_get_texture_binding(shader, "out_img"));
+
+ batches.tile_texture.bind(shader);
+
GPU_storagebuf_bind(batches.step_buf, GPU_shader_get_ssbo(shader, "paint_step_buf"));
GPU_shader_uniform_2iv(shader, "paint_step_range", paint_step_range);
GPU_uniformbuf_bind(batches.paint_brush_buf,
@@ -724,17 +927,16 @@ static void gpu_painting_paint_step(TexturePaintingUserData &data,
}
}
-static void gpu_painting_image_merge(TexturePaintingUserData &UNUSED(data),
+static void gpu_painting_image_merge(GPUSculptPaintData &batches,
Image &image,
ImageUser &image_user,
- ImBuf &image_buffer,
- GPUTexture *paint_tex)
+ ImBuf &image_buffer)
{
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"));
+ batches.tile_texture.bind(shader);
+ GPU_texture_image_bind(canvas_tex, GPU_shader_get_texture_binding(shader, "texture_img"));
GPU_compute_dispatch(shader, image_buffer.x, image_buffer.y, 1);
}
@@ -768,6 +970,28 @@ static void dispatch_gpu_painting(TexturePaintingUserData &data)
batches.steps.append(paint_step);
}
+/* This should be done based on the frame_selection nodes, otherwise we might be over
+ * committing.
+ */
+static void paint_tiles_mark_used(TexturePaintingUserData &data)
+{
+ SculptSession &ss = *data.ob->sculpt;
+ GPUSculptPaintData &batches = *static_cast<GPUSculptPaintData *>(ss.mode.texture_paint.gpu_data);
+
+ for (PBVHNode *node : MutableSpan<PBVHNode *>(data.nodes, data.nodes_len)) {
+ NodeData &node_data = BKE_pbvh_pixels_node_data_get(*node);
+ for (UDIMTilePixels &tile : node_data.tiles) {
+ for (int x = tile.gpu_sub_tiles.xmin; x <= tile.gpu_sub_tiles.xmax; x++) {
+ for (int y = tile.gpu_sub_tiles.ymin; y <= tile.gpu_sub_tiles.ymax; y++) {
+ int2 sub_tile_id(x, y);
+ batches.tile_texture.mark_usage(tile.tile_number, sub_tile_id);
+ }
+ }
+ }
+ }
+}
+
+/** Mark all nodes that are used when drawing this frame. */
static void update_frame_selection(TexturePaintingUserData &data)
{
for (PBVHNode *node : MutableSpan<PBVHNode *>(data.nodes, data.nodes_len)) {
@@ -804,6 +1028,11 @@ static void dispatch_gpu_batches(TexturePaintingUserData &data)
batches.update_step_buf();
batches.ensure_vert_coord_buf(ss);
batches.ensure_paint_brush_buf(ss, *data.brush);
+ batches.tile_texture.ensure_gpu_texture();
+ batches.tile_texture.remove_unused();
+ batches.tile_texture.assign_layer_ids();
+ batches.tile_texture.ensure_tile_buf();
+ batches.tile_texture.update_tile_buf();
Image &image = *data.image_data.image;
ImageUser local_image_user = *data.image_data.image_user;
@@ -817,10 +1046,11 @@ static void dispatch_gpu_batches(TexturePaintingUserData &data)
continue;
}
+ batches.tile_texture.update_gpu_texture(tile_number, *image_buffer);
+
GPU_debug_group_begin("Paint tile");
- gpu_painting_paint_step(data, batches, tile_number, image_buffer, paint_step_range);
- gpu_painting_image_merge(
- data, *data.image_data.image, local_image_user, *image_buffer, batches.tile_texture);
+ gpu_painting_paint_step(data, batches, tile_number, paint_step_range);
+ gpu_painting_image_merge(batches, *data.image_data.image, local_image_user, *image_buffer);
GPU_debug_group_end();
BKE_image_release_ibuf(data.image_data.image, image_buffer, nullptr);
@@ -899,6 +1129,7 @@ void SCULPT_do_paint_brush_image(
ensure_gpu_buffers(data);
update_frame_selection(data);
dispatch_gpu_painting(data);
+ paint_tiles_mark_used(data);
}
else {
TaskParallelSettings settings;
@@ -932,11 +1163,11 @@ void SCULPT_paint_image_batches_flush(PaintModeSettings *paint_mode_settings,
}
if (ImageData::init_active_image(ob, &data.image_data, paint_mode_settings)) {
- TIMEIT_START(paint_image_gpu);
+ // TIMEIT_START(paint_image_gpu);
GPU_debug_group_begin("SCULPT_paint_brush");
dispatch_gpu_batches(data);
GPU_debug_group_end();
- TIMEIT_END(paint_image_gpu);
+ // TIMEIT_END(paint_image_gpu);
}
MEM_freeN(data.nodes);
@@ -944,7 +1175,7 @@ void SCULPT_paint_image_batches_flush(PaintModeSettings *paint_mode_settings,
void SCULPT_paint_image_batches_finalize(PaintModeSettings *UNUSED(paint_mode_settings),
Sculpt *UNUSED(sd),
- Object *ob)
+ Object *UNUSED(ob))
{
if (!SCULPT_use_image_paint_compute()) {
return;
@@ -953,9 +1184,16 @@ void SCULPT_paint_image_batches_finalize(PaintModeSettings *UNUSED(paint_mode_se
// TODO(jbakker): record undo steps.
// TODO(jbakker): download results and update the image data-block.
- SculptSession &ss = *ob->sculpt;
- GPUSculptPaintData *batches = static_cast<GPUSculptPaintData *>(ss.mode.texture_paint.gpu_data);
- MEM_delete(batches);
- ss.mode.texture_paint.gpu_data = nullptr;
+ /* TODO: move this to sculpt tool switch and sculpt session free. */
+ // SCULPT_paint_image_sculpt_data_free(ob->sculpt);
+}
+
+void SCULPT_paint_image_sculpt_data_free(SculptSession *ss)
+{
+ GPUSculptPaintData *batches = static_cast<GPUSculptPaintData *>(ss->mode.texture_paint.gpu_data);
+ if (batches) {
+ MEM_delete(batches);
+ ss->mode.texture_paint.gpu_data = nullptr;
+ }
}
}