Blender V4.3
image_partial_update.cc
Go to the documentation of this file.
1/* SPDX-FileCopyrightText: 2021 Blender Authors
2 *
3 * SPDX-License-Identifier: GPL-2.0-or-later */
50#include <optional>
51
52#include "BKE_image.hh"
54
55#include "DNA_image_types.h"
56
57#include "IMB_imbuf_types.hh"
58
59#include "BLI_bit_vector.hh"
60#include "BLI_listbase.h"
61#include "BLI_vector.hh"
62
64
66constexpr int CHUNK_SIZE = 256;
67
78constexpr int MAX_HISTORY_LEN = 4;
79
85static int chunk_number_for_pixel(int pixel_offset)
86{
87 int chunk_offset = pixel_offset / CHUNK_SIZE;
88 if (pixel_offset < 0) {
89 chunk_offset -= 1;
90 }
91 return chunk_offset;
92}
93
96
100static PartialUpdateUser *wrap(PartialUpdateUserImpl *user)
101{
102 return static_cast<PartialUpdateUser *>(static_cast<void *>(user));
103}
104
108static PartialUpdateUserImpl *unwrap(PartialUpdateUser *user)
109{
110 return static_cast<PartialUpdateUserImpl *>(static_cast<void *>(user));
111}
112
116static PartialUpdateRegister *wrap(PartialUpdateRegisterImpl *partial_update_register)
117{
118 return static_cast<PartialUpdateRegister *>(static_cast<void *>(partial_update_register));
119}
120
124static PartialUpdateRegisterImpl *unwrap(PartialUpdateRegister *partial_update_register)
125{
126 return static_cast<PartialUpdateRegisterImpl *>(static_cast<void *>(partial_update_register));
127}
128
131
135
138
139#ifdef NDEBUG
141 const void *debug_image_;
142#endif
143
151 {
152 updated_regions.clear();
153 }
154};
155
163 private:
165 blender::BitVector<> chunk_dirty_flags_;
167 bool has_dirty_chunks_ = false;
168
169 public:
178
180
181 void clear()
182 {
184 }
185
192 bool update_resolution(const ImBuf *image_buffer)
193 {
194 if (tile_width == image_buffer->x && tile_height == image_buffer->y) {
195 return false;
196 }
197
198 tile_width = image_buffer->x;
199 tile_height = image_buffer->y;
200
204 return true;
205 }
206
207 void mark_region(const rcti *updated_region)
208 {
209 int start_x_chunk = chunk_number_for_pixel(updated_region->xmin);
210 int end_x_chunk = chunk_number_for_pixel(updated_region->xmax - 1);
211 int start_y_chunk = chunk_number_for_pixel(updated_region->ymin);
212 int end_y_chunk = chunk_number_for_pixel(updated_region->ymax - 1);
213
214 /* Clamp tiles to tiles in image. */
215 start_x_chunk = max_ii(0, start_x_chunk);
216 start_y_chunk = max_ii(0, start_y_chunk);
217 end_x_chunk = min_ii(chunk_x_len - 1, end_x_chunk);
218 end_y_chunk = min_ii(chunk_y_len - 1, end_y_chunk);
219
220 /* Early exit when no tiles need to be updated. */
221 if (start_x_chunk >= chunk_x_len) {
222 return;
223 }
224 if (start_y_chunk >= chunk_y_len) {
225 return;
226 }
227 if (end_x_chunk < 0) {
228 return;
229 }
230 if (end_y_chunk < 0) {
231 return;
232 }
233
234 mark_chunks_dirty(start_x_chunk, start_y_chunk, end_x_chunk, end_y_chunk);
235 }
236
237 void mark_chunks_dirty(int start_x_chunk, int start_y_chunk, int end_x_chunk, int end_y_chunk)
238 {
239 for (int chunk_y = start_y_chunk; chunk_y <= end_y_chunk; chunk_y++) {
240 for (int chunk_x = start_x_chunk; chunk_x <= end_x_chunk; chunk_x++) {
241 int chunk_index = chunk_y * chunk_x_len + chunk_x;
242 chunk_dirty_flags_[chunk_index].set();
243 }
244 }
245 has_dirty_chunks_ = true;
246 }
247
248 bool has_dirty_chunks() const
249 {
250 return has_dirty_chunks_;
251 }
252
253 void init_chunks(int chunk_x_len_, int chunk_y_len_)
254 {
255 chunk_x_len = chunk_x_len_;
256 chunk_y_len = chunk_y_len_;
257 const int chunk_len = chunk_x_len * chunk_y_len;
258 const int previous_chunk_len = chunk_dirty_flags_.size();
259
260 chunk_dirty_flags_.resize(chunk_len);
261 /* Fast exit. When the changeset was already empty no need to
262 * re-initialize the chunk_validity. */
263 if (!has_dirty_chunks()) {
264 return;
265 }
266 for (int index = 0; index < min_ii(chunk_len, previous_chunk_len); index++) {
267 chunk_dirty_flags_[index].reset();
268 }
269 has_dirty_chunks_ = false;
270 }
271
273 void merge(const TileChangeset &other)
274 {
275 BLI_assert(chunk_x_len == other.chunk_x_len);
276 BLI_assert(chunk_y_len == other.chunk_y_len);
277 const int chunk_len = chunk_x_len * chunk_y_len;
278
279 for (int chunk_index = 0; chunk_index < chunk_len; chunk_index++) {
280 chunk_dirty_flags_[chunk_index].set(chunk_dirty_flags_[chunk_index] ||
281 other.chunk_dirty_flags_[chunk_index]);
282 }
283 has_dirty_chunks_ |= other.has_dirty_chunks_;
284 }
285
287 bool is_chunk_dirty(int chunk_x, int chunk_y) const
288 {
289 const int chunk_index = chunk_y * chunk_x_len + chunk_x;
290 return chunk_dirty_flags_[chunk_index];
291 }
292};
293
295struct Changeset {
296 private:
298
299 public:
302
309 {
310 for (TileChangeset &tile_changeset : tiles) {
311 if (tile_changeset.tile_number == image_tile->tile_number) {
312 return tile_changeset;
313 }
314 }
315
316 TileChangeset tile_changeset;
317 tile_changeset.tile_number = image_tile->tile_number;
318 tiles.append_as(tile_changeset);
319
320 return tiles.last();
321 }
322
324 bool has_tile(const ImageTile *image_tile)
325 {
326 for (TileChangeset &tile_changeset : tiles) {
327 if (tile_changeset.tile_number == image_tile->tile_number) {
328 return true;
329 }
330 }
331 return false;
332 }
333
335 void clear()
336 {
337 tiles.clear();
338 has_dirty_chunks = false;
339 }
340};
341
353
358
359 void update_resolution(const ImageTile *image_tile, const ImBuf *image_buffer)
360 {
361 TileChangeset &tile_changeset = current_changeset[image_tile];
362 const bool has_dirty_chunks = tile_changeset.has_dirty_chunks();
363 const bool resolution_changed = tile_changeset.update_resolution(image_buffer);
364
365 if (has_dirty_chunks && resolution_changed && !history.is_empty()) {
367 }
368 }
369
377
378 void mark_region(const ImageTile *image_tile, const rcti *updated_region)
379 {
380 TileChangeset &tile_changeset = current_changeset[image_tile];
381 tile_changeset.mark_region(updated_region);
383 }
384
386 {
388 /* No need to create a new changeset when previous changeset does not contain any dirty
389 * tiles. */
390 return;
391 }
394 }
395
398 {
399 history.append_as(std::move(current_changeset));
402 }
403
406 {
407 const int num_items_to_remove = max_ii(history.size() - MAX_HISTORY_LEN, 0);
408 if (num_items_to_remove == 0) {
409 return;
410 }
411 history.remove(0, num_items_to_remove);
412 first_changeset_id += num_items_to_remove;
413 }
414
421 bool can_construct(ChangesetID changeset_id)
422 {
423 if (changeset_id < first_changeset_id) {
424 return false;
425 }
426
427 if (changeset_id > last_changeset_id) {
428 return false;
429 }
430
431 return true;
432 }
433
437 std::optional<TileChangeset> changed_tile_chunks_since(const ImageTile *image_tile,
438 const ChangesetID from_changeset)
439 {
440 std::optional<TileChangeset> changed_chunks = std::nullopt;
441 for (int index = from_changeset - first_changeset_id; index < history.size(); index++) {
442 if (!history[index].has_tile(image_tile)) {
443 continue;
444 }
445
446 TileChangeset &tile_changeset = history[index][image_tile];
447 if (!changed_chunks.has_value()) {
448 changed_chunks = std::make_optional<TileChangeset>();
449 changed_chunks->init_chunks(tile_changeset.chunk_x_len, tile_changeset.chunk_y_len);
450 changed_chunks->tile_number = image_tile->tile_number;
451 }
452
453 changed_chunks->merge(tile_changeset);
454 }
455 return changed_chunks;
456 }
457};
458
459static PartialUpdateRegister *image_partial_update_register_ensure(Image *image)
460{
461 if (image->runtime.partial_update_register == nullptr) {
462 PartialUpdateRegisterImpl *partial_update_register = MEM_new<PartialUpdateRegisterImpl>(
463 __func__);
464 image->runtime.partial_update_register = wrap(partial_update_register);
465 }
466 return image->runtime.partial_update_register;
467}
468
470 PartialUpdateUser *user)
471{
472 PartialUpdateUserImpl *user_impl = unwrap(user);
473#ifdef NDEBUG
474 BLI_assert(image == user_impl->debug_image_);
475#endif
476
477 user_impl->clear_updated_regions();
478
480 partial_updater->ensure_empty_changeset();
481
482 if (!partial_updater->can_construct(user_impl->last_changeset_id)) {
483 user_impl->last_changeset_id = partial_updater->last_changeset_id;
485 }
486
487 /* Check if there are changes since last invocation for the user. */
488 if (user_impl->last_changeset_id == partial_updater->last_changeset_id) {
490 }
491
492 /* Collect changed tiles. */
493 LISTBASE_FOREACH (ImageTile *, tile, &image->tiles) {
494 std::optional<TileChangeset> changed_chunks = partial_updater->changed_tile_chunks_since(
495 tile, user_impl->last_changeset_id);
496 /* Check if chunks of this tile are dirty. */
497 if (!changed_chunks.has_value()) {
498 continue;
499 }
500 if (!changed_chunks->has_dirty_chunks()) {
501 continue;
502 }
503
504 /* Convert tiles in the changeset to rectangles that are dirty. */
505 for (int chunk_y = 0; chunk_y < changed_chunks->chunk_y_len; chunk_y++) {
506 for (int chunk_x = 0; chunk_x < changed_chunks->chunk_x_len; chunk_x++) {
507 if (!changed_chunks->is_chunk_dirty(chunk_x, chunk_y)) {
508 continue;
509 }
510
511 PartialUpdateRegion region;
512 region.tile_number = tile->tile_number;
513 BLI_rcti_init(&region.region,
514 chunk_x * CHUNK_SIZE,
515 (chunk_x + 1) * CHUNK_SIZE,
516 chunk_y * CHUNK_SIZE,
517 (chunk_y + 1) * CHUNK_SIZE);
518 user_impl->updated_regions.append_as(region);
519 }
520 }
521 }
522
523 user_impl->last_changeset_id = partial_updater->last_changeset_id;
525}
526
528 PartialUpdateRegion *r_region)
529{
530 PartialUpdateUserImpl *user_impl = unwrap(user);
531 if (user_impl->updated_regions.is_empty()) {
533 }
534 PartialUpdateRegion region = user_impl->updated_regions.pop_last();
535 *r_region = region;
537}
538
539} // namespace blender::bke::image::partial_update
540
542
543PartialUpdateUser *BKE_image_partial_update_create(const Image *image)
544{
545 /* TODO(@jbakker): cleanup parameter. */
546
547 PartialUpdateUserImpl *user_impl = MEM_new<PartialUpdateUserImpl>(__func__);
548
549#ifdef NDEBUG
550 user_impl->debug_image_ = image;
551#else
552 UNUSED_VARS(image);
553#endif
554
555 return wrap(user_impl);
556}
557
558void BKE_image_partial_update_free(PartialUpdateUser *user)
559{
560 PartialUpdateUserImpl *user_impl = unwrap(user);
561 MEM_delete<PartialUpdateUserImpl>(user_impl);
562}
563
564/* --- Image side --- */
565
567{
568 PartialUpdateRegisterImpl *partial_update_register = unwrap(
569 image->runtime.partial_update_register);
570 if (partial_update_register) {
571 MEM_delete<PartialUpdateRegisterImpl>(partial_update_register);
572 }
573 image->runtime.partial_update_register = nullptr;
574}
575
577 const ImageTile *image_tile,
578 const ImBuf *image_buffer,
579 const rcti *updated_region)
580{
582 partial_updater->update_resolution(image_tile, image_buffer);
583 partial_updater->mark_region(image_tile, updated_region);
584}
585
#define BLI_assert(a)
Definition BLI_assert.h:50
#define LISTBASE_FOREACH(type, var, list)
MINLINE int min_ii(int a, int b)
MINLINE int max_ii(int a, int b)
void BLI_rcti_init(struct rcti *rect, int xmin, int xmax, int ymin, int ymax)
Definition rct.c:418
#define UNUSED_VARS(...)
Contains defines and structs used throughout the imbuf module.
void resize(const int64_t new_size_in_bits, const bool value=false)
input_tx image(0, GPU_RGBA16F, Qualifier::WRITE, ImageType::FLOAT_2D, "preview_img") .compute_source("compositor_compute_preview.glsl") .do_static_compilation(true)
void BKE_image_partial_update_mark_region(Image *image, const ImageTile *image_tile, const ImBuf *image_buffer, const rcti *updated_region)
Mark a region of the image to update.
void BKE_image_partial_update_free(PartialUpdateUser *user)
free a partial update user.
PartialUpdateUser * BKE_image_partial_update_create(const Image *image)
Create a new PartialUpdateUser. An Object that contains data to use partial updates.
void BKE_image_partial_update_mark_full_update(Image *image)
Mark the whole image to be updated.
void BKE_image_partial_update_register_free(Image *image)
ccl_gpu_kernel_postfix ccl_global KernelWorkTile * tiles
ccl_global const KernelWorkTile * tile
ePartialUpdateCollectResult BKE_image_partial_update_collect_changes(Image *image, PartialUpdateUser *user)
collect the partial update since the last request.
static PartialUpdateRegister * image_partial_update_register_ensure(Image *image)
constexpr int MAX_HISTORY_LEN
Max number of changesets to keep in history.
ePartialUpdateIterResult BKE_image_partial_update_get_next_change(PartialUpdateUser *user, PartialUpdateRegion *r_region)
static PartialUpdateUser * wrap(PartialUpdateUserImpl *user)
ePartialUpdateCollectResult
Result codes of BKE_image_partial_update_collect_changes.
@ PartialChangesDetected
Changes detected since the last time requested.
@ FullUpdateNeeded
Unable to construct partial updates. Caller should perform a full update.
@ NoChangesDetected
No changes detected since the last time requested.
constexpr int CHUNK_SIZE
Size of chunks to track changes.
static PartialUpdateUserImpl * unwrap(PartialUpdateUser *user)
static int chunk_number_for_pixel(int pixel_offset)
get the chunk number for the give pixel coordinate.
ePartialUpdateIterResult
Return codes of BKE_image_partial_update_get_next_change.
@ Finished
no tiles left when iterating over tiles.
@ ChangeAvailable
a chunk was available and has been loaded.
__int64 int64_t
Definition stdint.h:89
Changeset keeping track of changes for an image.
TileChangeset & operator[](const ImageTile *image_tile)
Retrieve the TileChangeset for the given ImageTile.
bool has_dirty_chunks
Keep track if any of the tiles have dirty chunks.
bool has_tile(const ImageTile *image_tile)
Does this changeset contain data for the given tile.
TileNumber tile_number
Tile number (UDIM) that this region belongs to.
Partial update changes stored inside the image runtime.
void update_resolution(const ImageTile *image_tile, const ImBuf *image_buffer)
void commit_current_changeset()
Move the current changeset to the history and resets the current changeset.
void mark_region(const ImageTile *image_tile, const rcti *updated_region)
ChangesetID first_changeset_id
changeset id of the first changeset kept in history.
ChangesetID last_changeset_id
changeset id of the top changeset kept in history.
void limit_history()
Limit the number of items in the changeset.
std::optional< TileChangeset > changed_tile_chunks_since(const ImageTile *image_tile, const ChangesetID from_changeset)
collect all historic changes since a given changeset.
Changeset current_changeset
The current changeset. New changes will be added to this changeset.
bool can_construct(ChangesetID changeset_id)
Check if data is available to construct the update tiles for the given changeset_id.
Vector< PartialUpdateRegion > updated_regions
regions that have been updated.
void clear_updated_regions()
Clear the list of updated regions.
ChangesetID last_changeset_id
last changeset id that was seen by this user.
void init_chunks(int chunk_x_len_, int chunk_y_len_)
void merge(const TileChangeset &other)
Merge the given changeset into the receiver.
void mark_chunks_dirty(int start_x_chunk, int start_y_chunk, int end_x_chunk, int end_y_chunk)
bool update_resolution(const ImBuf *image_buffer)
Update the resolution of the tile.
int chunk_y_len
Number of chunks along the y-axis.
bool is_chunk_dirty(int chunk_x, int chunk_y) const
has a chunk changed inside this changeset.
int chunk_x_len
Number of chunks along the x-axis.
int ymin
int ymax
int xmin
int xmax