Blender  V3.3
image_partial_update.cc
Go to the documentation of this file.
1 /* SPDX-License-Identifier: GPL-2.0-or-later
2  * Copyright 2021 Blender Foundation. */
50 #include <optional>
51 
52 #include "BKE_image.h"
54 
55 #include "DNA_image_types.h"
56 
57 #include "IMB_imbuf.h"
58 #include "IMB_imbuf_types.h"
59 
60 #include "BLI_vector.hh"
61 
63 
65 constexpr int CHUNK_SIZE = 256;
66 
77 constexpr int MAX_HISTORY_LEN = 4;
78 
84 static int chunk_number_for_pixel(int pixel_offset)
85 {
86  int chunk_offset = pixel_offset / CHUNK_SIZE;
87  if (pixel_offset < 0) {
88  chunk_offset -= 1;
89  }
90  return chunk_offset;
91 }
92 
95 
99 static struct PartialUpdateUser *wrap(PartialUpdateUserImpl *user)
100 {
101  return static_cast<struct PartialUpdateUser *>(static_cast<void *>(user));
102 }
103 
107 static PartialUpdateUserImpl *unwrap(struct PartialUpdateUser *user)
108 {
109  return static_cast<PartialUpdateUserImpl *>(static_cast<void *>(user));
110 }
111 
115 static struct PartialUpdateRegister *wrap(PartialUpdateRegisterImpl *partial_update_register)
116 {
117  return static_cast<struct PartialUpdateRegister *>(static_cast<void *>(partial_update_register));
118 }
119 
123 static PartialUpdateRegisterImpl *unwrap(struct PartialUpdateRegister *partial_update_register)
124 {
125  return static_cast<PartialUpdateRegisterImpl *>(static_cast<void *>(partial_update_register));
126 }
127 
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  std::vector<bool> 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] = true;
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] = false;
268  }
269  has_dirty_chunks_ = false;
270  }
271 
273  void merge(const TileChangeset &other)
274  {
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] = 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 
295 struct Changeset {
296  private:
297  Vector<TileChangeset> tiles;
298 
299  public:
302 
308  TileChangeset &operator[](const ImageTile *image_tile)
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 
371  {
372  history.clear();
376  }
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  }
393  limit_history();
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 
459 static 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 
541 extern "C" {
542 
544 
545 // TODO(jbakker): cleanup parameter.
546 struct PartialUpdateUser *BKE_image_partial_update_create(const struct Image *image)
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 
559 void 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 
588 {
590  partial_updater->mark_full_update();
591 }
592 }
#define BLI_assert(a)
Definition: BLI_assert.h:46
#define LISTBASE_FOREACH(type, var, list)
Definition: BLI_listbase.h:336
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:417
#define UNUSED_VARS(...)
Contains defines and structs used throughout the imbuf module.
depth_tx normal_tx diffuse_light_tx specular_light_tx volume_light_tx environment_tx ambient_occlusion_tx aov_value_tx in_weight_img image(1, GPU_R32F, Qualifier::WRITE, ImageType::FLOAT_2D_ARRAY, "out_weight_img") .image(3
void BKE_image_partial_update_mark_region(Image *image, const ImageTile *image_tile, const ImBuf *image_buffer, const rcti *updated_region)
struct PartialUpdateUser * BKE_image_partial_update_create(const struct Image *image)
Create a new PartialUpdateUser. An Object that contains data to use partial updates.
void BKE_image_partial_update_free(PartialUpdateUser *user)
free a partial update user.
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
static PartialUpdateUserImpl * unwrap(struct PartialUpdateUser *user)
static PartialUpdateRegister * image_partial_update_register_ensure(Image *image)
constexpr int MAX_HISTORY_LEN
Max number of changesets to keep in history.
ePartialUpdateCollectResult BKE_image_partial_update_collect_changes(struct Image *image, struct PartialUpdateUser *user)
collect the partial update since the last request.
ePartialUpdateIterResult BKE_image_partial_update_get_next_change(struct PartialUpdateUser *user, struct PartialUpdateRegion *r_region)
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.
static struct PartialUpdateUser * wrap(PartialUpdateUserImpl *user)
constexpr int CHUNK_SIZE
Size of chunks to track changes.
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
signed int int32_t
Definition: stdint.h:77
Changeset keeping track of changes for an image.
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.
TileChangeset & operator[](const ImageTile *image_tile)
Retrieve the TileChangeset for the given ImageTile.
struct 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.
Changeset current_changeset
The current changeset. New changes will be added to this changeset.
std::optional< TileChangeset > changed_tile_chunks_since(const ImageTile *image_tile, const ChangesetID from_changeset)
collect all historic changes since a given changeset.
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
Definition: DNA_vec_types.h:64
int ymax
Definition: DNA_vec_types.h:64
int xmin
Definition: DNA_vec_types.h:63
int xmax
Definition: DNA_vec_types.h:63