Blender V5.0
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 */
49
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_rect.h"
62#include "BLI_vector.hh"
63
65
67constexpr int CHUNK_SIZE = 256;
68
79constexpr int MAX_HISTORY_LEN = 4;
80
86static int chunk_number_for_pixel(int pixel_offset)
87{
88 int chunk_offset = pixel_offset / CHUNK_SIZE;
89 if (pixel_offset < 0) {
90 chunk_offset -= 1;
91 }
92 return chunk_offset;
93}
94
97
101static PartialUpdateUser *wrap(PartialUpdateUserImpl *user)
102{
103 return static_cast<PartialUpdateUser *>(static_cast<void *>(user));
104}
105
109static PartialUpdateUserImpl *unwrap(PartialUpdateUser *user)
110{
111 return static_cast<PartialUpdateUserImpl *>(static_cast<void *>(user));
112}
113
117static PartialUpdateRegister *wrap(PartialUpdateRegisterImpl *partial_update_register)
118{
119 return static_cast<PartialUpdateRegister *>(static_cast<void *>(partial_update_register));
120}
121
125static PartialUpdateRegisterImpl *unwrap(PartialUpdateRegister *partial_update_register)
126{
127 return static_cast<PartialUpdateRegisterImpl *>(static_cast<void *>(partial_update_register));
128}
129
132
136
139
140#ifdef NDEBUG
142 const void *debug_image_;
143#endif
144
152 {
153 updated_regions.clear();
154 }
155};
156
164 private:
166 blender::BitVector<> chunk_dirty_flags_;
168 bool has_dirty_chunks_ = false;
169
170 public:
179
181
182 void clear()
183 {
185 }
186
193 bool update_resolution(const ImBuf *image_buffer)
194 {
195 if (tile_width == image_buffer->x && tile_height == image_buffer->y) {
196 return false;
197 }
198
199 tile_width = image_buffer->x;
200 tile_height = image_buffer->y;
201
205 return true;
206 }
207
208 void mark_region(const rcti *updated_region)
209 {
210 int start_x_chunk = chunk_number_for_pixel(updated_region->xmin);
211 int end_x_chunk = chunk_number_for_pixel(updated_region->xmax - 1);
212 int start_y_chunk = chunk_number_for_pixel(updated_region->ymin);
213 int end_y_chunk = chunk_number_for_pixel(updated_region->ymax - 1);
214
215 /* Clamp tiles to tiles in image. */
216 start_x_chunk = max_ii(0, start_x_chunk);
217 start_y_chunk = max_ii(0, start_y_chunk);
218 end_x_chunk = min_ii(chunk_x_len - 1, end_x_chunk);
219 end_y_chunk = min_ii(chunk_y_len - 1, end_y_chunk);
220
221 /* Early exit when no tiles need to be updated. */
222 if (start_x_chunk >= chunk_x_len) {
223 return;
224 }
225 if (start_y_chunk >= chunk_y_len) {
226 return;
227 }
228 if (end_x_chunk < 0) {
229 return;
230 }
231 if (end_y_chunk < 0) {
232 return;
233 }
234
235 mark_chunks_dirty(start_x_chunk, start_y_chunk, end_x_chunk, end_y_chunk);
236 }
237
238 void mark_chunks_dirty(int start_x_chunk, int start_y_chunk, int end_x_chunk, int end_y_chunk)
239 {
240 for (int chunk_y = start_y_chunk; chunk_y <= end_y_chunk; chunk_y++) {
241 for (int chunk_x = start_x_chunk; chunk_x <= end_x_chunk; chunk_x++) {
242 int chunk_index = chunk_y * chunk_x_len + chunk_x;
243 chunk_dirty_flags_[chunk_index].set();
244 }
245 }
246 has_dirty_chunks_ = true;
247 }
248
249 bool has_dirty_chunks() const
250 {
251 return has_dirty_chunks_;
252 }
253
254 void init_chunks(int chunk_x_len_, int chunk_y_len_)
255 {
256 chunk_x_len = chunk_x_len_;
257 chunk_y_len = chunk_y_len_;
258 const int chunk_len = chunk_x_len * chunk_y_len;
259 const int previous_chunk_len = chunk_dirty_flags_.size();
260
261 chunk_dirty_flags_.resize(chunk_len);
262 /* Fast exit. When the changeset was already empty no need to
263 * re-initialize the chunk_validity. */
264 if (!has_dirty_chunks()) {
265 return;
266 }
267 for (int index = 0; index < min_ii(chunk_len, previous_chunk_len); index++) {
268 chunk_dirty_flags_[index].reset();
269 }
270 has_dirty_chunks_ = false;
271 }
272
274 void merge(const TileChangeset &other)
275 {
278 const int chunk_len = chunk_x_len * chunk_y_len;
279
280 for (int chunk_index = 0; chunk_index < chunk_len; chunk_index++) {
281 chunk_dirty_flags_[chunk_index].set(chunk_dirty_flags_[chunk_index] ||
282 other.chunk_dirty_flags_[chunk_index]);
283 }
284 has_dirty_chunks_ |= other.has_dirty_chunks_;
285 }
286
288 bool is_chunk_dirty(int chunk_x, int chunk_y) const
289 {
290 const int chunk_index = chunk_y * chunk_x_len + chunk_x;
291 return chunk_dirty_flags_[chunk_index];
292 }
293};
294
296struct Changeset {
297 private:
299
300 public:
303
310 {
311 for (TileChangeset &tile_changeset : tiles) {
312 if (tile_changeset.tile_number == image_tile->tile_number) {
313 return tile_changeset;
314 }
315 }
316
317 TileChangeset tile_changeset;
318 tile_changeset.tile_number = image_tile->tile_number;
319 tiles.append_as(tile_changeset);
320
321 return tiles.last();
322 }
323
325 bool has_tile(const ImageTile *image_tile)
326 {
327 for (TileChangeset &tile_changeset : tiles) {
328 if (tile_changeset.tile_number == image_tile->tile_number) {
329 return true;
330 }
331 }
332 return false;
333 }
334
336 void clear()
337 {
338 tiles.clear();
339 has_dirty_chunks = false;
340 }
341};
342
354
359
360 void update_resolution(const ImageTile *image_tile, const ImBuf *image_buffer)
361 {
362 TileChangeset &tile_changeset = current_changeset[image_tile];
363 const bool has_dirty_chunks = tile_changeset.has_dirty_chunks();
364 const bool resolution_changed = tile_changeset.update_resolution(image_buffer);
365
366 if (has_dirty_chunks && resolution_changed && !history.is_empty()) {
368 }
369 }
370
372 {
373 history.clear();
375 current_changeset.clear();
377 }
378
379 void mark_region(const ImageTile *image_tile, const rcti *updated_region)
380 {
381 TileChangeset &tile_changeset = current_changeset[image_tile];
382 tile_changeset.mark_region(updated_region);
383 current_changeset.has_dirty_chunks |= tile_changeset.has_dirty_chunks();
384 }
385
387 {
388 if (!current_changeset.has_dirty_chunks) {
389 /* No need to create a new changeset when previous changeset does not contain any dirty
390 * tiles. */
391 return;
392 }
395 }
396
399 {
400 history.append_as(std::move(current_changeset));
401 current_changeset.clear();
403 }
404
407 {
408 const int num_items_to_remove = max_ii(history.size() - MAX_HISTORY_LEN, 0);
409 if (num_items_to_remove == 0) {
410 return;
411 }
412 history.remove(0, num_items_to_remove);
413 first_changeset_id += num_items_to_remove;
414 }
415
422 bool can_construct(ChangesetID changeset_id)
423 {
424 if (changeset_id < first_changeset_id) {
425 return false;
426 }
427
428 if (changeset_id > last_changeset_id) {
429 return false;
430 }
431
432 return true;
433 }
434
438 std::optional<TileChangeset> changed_tile_chunks_since(const ImageTile *image_tile,
439 const ChangesetID from_changeset)
440 {
441 std::optional<TileChangeset> changed_chunks = std::nullopt;
442 for (int index = from_changeset - first_changeset_id; index < history.size(); index++) {
443 if (!history[index].has_tile(image_tile)) {
444 continue;
445 }
446
447 TileChangeset &tile_changeset = history[index][image_tile];
448 if (!changed_chunks.has_value()) {
449 changed_chunks = std::make_optional<TileChangeset>();
450 changed_chunks->init_chunks(tile_changeset.chunk_x_len, tile_changeset.chunk_y_len);
451 changed_chunks->tile_number = image_tile->tile_number;
452 }
453
454 changed_chunks->merge(tile_changeset);
455 }
456 return changed_chunks;
457 }
458};
459
460static PartialUpdateRegister *image_partial_update_register_ensure(Image *image)
461{
462 if (image->runtime->partial_update_register == nullptr) {
463 PartialUpdateRegisterImpl *partial_update_register = MEM_new<PartialUpdateRegisterImpl>(
464 __func__);
465 image->runtime->partial_update_register = wrap(partial_update_register);
466 }
467 return image->runtime->partial_update_register;
468}
469
471 PartialUpdateUser *user)
472{
473 PartialUpdateUserImpl *user_impl = unwrap(user);
474#ifdef NDEBUG
475 BLI_assert(image == user_impl->debug_image_);
476#endif
477
478 user_impl->clear_updated_regions();
479
481 partial_updater->ensure_empty_changeset();
482
483 if (!partial_updater->can_construct(user_impl->last_changeset_id)) {
484 user_impl->last_changeset_id = partial_updater->last_changeset_id;
486 }
487
488 /* Check if there are changes since last invocation for the user. */
489 if (user_impl->last_changeset_id == partial_updater->last_changeset_id) {
491 }
492
493 /* Collect changed tiles. */
494 LISTBASE_FOREACH (ImageTile *, tile, &image->tiles) {
495 std::optional<TileChangeset> changed_chunks = partial_updater->changed_tile_chunks_since(
496 tile, user_impl->last_changeset_id);
497 /* Check if chunks of this tile are dirty. */
498 if (!changed_chunks.has_value()) {
499 continue;
500 }
501 if (!changed_chunks->has_dirty_chunks()) {
502 continue;
503 }
504
505 /* Convert tiles in the changeset to rectangles that are dirty. */
506 for (int chunk_y = 0; chunk_y < changed_chunks->chunk_y_len; chunk_y++) {
507 for (int chunk_x = 0; chunk_x < changed_chunks->chunk_x_len; chunk_x++) {
508 if (!changed_chunks->is_chunk_dirty(chunk_x, chunk_y)) {
509 continue;
510 }
511
512 PartialUpdateRegion region;
513 region.tile_number = tile->tile_number;
514 BLI_rcti_init(&region.region,
515 chunk_x * CHUNK_SIZE,
516 (chunk_x + 1) * CHUNK_SIZE,
517 chunk_y * CHUNK_SIZE,
518 (chunk_y + 1) * CHUNK_SIZE);
519 user_impl->updated_regions.append_as(region);
520 }
521 }
522 }
523
524 user_impl->last_changeset_id = partial_updater->last_changeset_id;
526}
527
529 PartialUpdateRegion *r_region)
530{
531 PartialUpdateUserImpl *user_impl = unwrap(user);
532 if (user_impl->updated_regions.is_empty()) {
534 }
535 PartialUpdateRegion region = user_impl->updated_regions.pop_last();
536 *r_region = region;
538}
539
540} // namespace blender::bke::image::partial_update
541
543
545{
546 /* TODO(@jbakker): cleanup parameter. */
547
548 PartialUpdateUserImpl *user_impl = MEM_new<PartialUpdateUserImpl>(__func__);
549
550#ifdef NDEBUG
551 user_impl->debug_image_ = image;
552#else
554#endif
555
556 return wrap(user_impl);
557}
558
559void BKE_image_partial_update_free(PartialUpdateUser *user)
560{
561 PartialUpdateUserImpl *user_impl = unwrap(user);
562 MEM_delete<PartialUpdateUserImpl>(user_impl);
563}
564
565/* --- Image side --- */
566
568{
569 PartialUpdateRegisterImpl *partial_update_register = unwrap(
570 image->runtime->partial_update_register);
571 if (partial_update_register) {
572 MEM_delete<PartialUpdateRegisterImpl>(partial_update_register);
573 }
574 image->runtime->partial_update_register = nullptr;
575}
576
578 const ImageTile *image_tile,
579 const ImBuf *image_buffer,
580 const rcti *updated_region)
581{
583 partial_updater->update_resolution(image_tile, image_buffer);
584 partial_updater->mark_region(image_tile, updated_region);
585}
586
#define BLI_assert(a)
Definition BLI_assert.h:46
#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.cc:414
#define UNUSED_VARS(...)
long long int int64_t
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)
const ccl_global 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.
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.
rcti region
region of the image that has been updated. Region can be bigger than actual changes.
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