diff options
author | Jeroen Bakker <jeroen@blender.org> | 2022-01-28 10:05:31 +0300 |
---|---|---|
committer | Jeroen Bakker <jeroen@blender.org> | 2022-01-28 10:06:19 +0300 |
commit | 0a32ac02e976a4723802ed09bed64c0625e889a2 (patch) | |
tree | b452db75593f8adbce4c49e6449530ab0bdf9b1d /source/blender/blenkernel/intern/image_gpu.cc | |
parent | 1e0758333d3a8c929772250b67b61f42b6e18882 (diff) |
Image: Partial Update Redesign.
This patch reimplements the image partial updates. Biggest design motivation for the redesign
is that currently GPUTextures must be owned by the image. This reduces flexibility and adds
complexity to a single component especially when we want to have different structures.
The new design is not limited to GPUTextures and can also be used by reducing overhead in image
operations like scaling. Or partial image updating in Cycles.
The usecase in hand is that we want to support virtual images in the image editor so we can
work with images that don't fit in a single GPUTexture.
Using `BKE_image_partial_update_mark_region` or `BKE_image_partial_update_mark_full_update`
a part of an image can be marked as dirty. These regions are stored per ImageTile (UDIM).
When a part of the code wants to receive partial changes it needs to construct a `PartialUpdateUser`
by calling `BKE_image_partial_update_create`. As long as this instance is kept alive the changes can
be received.
When a user wants to update its own data it will call `BKE_image_partial_update_collect_changes`
This will collect the changes since the last time the user called this function. When the partial changes
are available the partial change can be read by calling `BKE_image_partial_update_get_next_change`
It can happen that the introduced mechanism doesn't have the data anymore to construct the
changes since the last time a PartialUpdateUser requested it. In this case it will get a request
to perform a full update.
Maniphest Tasks: T92613
Differential Revision: https://developer.blender.org/D13238
Diffstat (limited to 'source/blender/blenkernel/intern/image_gpu.cc')
-rw-r--r-- | source/blender/blenkernel/intern/image_gpu.cc | 184 |
1 files changed, 81 insertions, 103 deletions
diff --git a/source/blender/blenkernel/intern/image_gpu.cc b/source/blender/blenkernel/intern/image_gpu.cc index c82de02e52a..91937e709da 100644 --- a/source/blender/blenkernel/intern/image_gpu.cc +++ b/source/blender/blenkernel/intern/image_gpu.cc @@ -38,6 +38,7 @@ #include "BKE_global.h" #include "BKE_image.h" +#include "BKE_image_partial_update.hh" #include "BKE_main.h" #include "GPU_capabilities.h" @@ -46,6 +47,10 @@ #include "PIL_time.h" +using namespace blender::bke::image::partial_update; + +extern "C" { + /* Prototypes. */ static void gpu_free_unused_buffers(); static void image_free_gpu(Image *ima, const bool immediate); @@ -337,6 +342,48 @@ static void image_update_reusable_textures(Image *ima, } } +static void image_gpu_texture_partial_update_changes_available( + Image *image, PartialUpdateChecker<ImageTileData>::CollectResult &changes) +{ + while (changes.get_next_change() == ePartialUpdateIterResult::ChangeAvailable) { + const int tile_offset_x = changes.changed_region.region.xmin; + const int tile_offset_y = changes.changed_region.region.ymin; + const int tile_width = min_ii(changes.tile_data.tile_buffer->x, + BLI_rcti_size_x(&changes.changed_region.region)); + const int tile_height = min_ii(changes.tile_data.tile_buffer->y, + BLI_rcti_size_y(&changes.changed_region.region)); + image_update_gputexture_ex(image, + changes.tile_data.tile, + changes.tile_data.tile_buffer, + tile_offset_x, + tile_offset_y, + tile_width, + tile_height); + } +} + +static void image_gpu_texture_try_partial_update(Image *image, ImageUser *iuser) +{ + PartialUpdateChecker<ImageTileData> checker(image, iuser, image->runtime.partial_update_user); + PartialUpdateChecker<ImageTileData>::CollectResult changes = checker.collect_changes(); + switch (changes.get_result_code()) { + case ePartialUpdateCollectResult::FullUpdateNeeded: { + image_free_gpu(image, true); + break; + } + + case ePartialUpdateCollectResult::PartialChangesDetected: { + image_gpu_texture_partial_update_changes_available(image, changes); + break; + } + + case ePartialUpdateCollectResult::NoChangesDetected: { + /* GPUTextures are up to date. */ + break; + } + } +} + static GPUTexture *image_get_gpu_texture(Image *ima, ImageUser *iuser, ImBuf *ibuf, @@ -370,31 +417,20 @@ static GPUTexture *image_get_gpu_texture(Image *ima, } #undef GPU_FLAGS_TO_CHECK - /* Check if image has been updated and tagged to be updated (full or partial). */ - ImageTile *tile = BKE_image_get_tile(ima, 0); - if (((ima->gpuflag & IMA_GPU_REFRESH) != 0) || - ((ibuf == nullptr || tile == nullptr) && ((ima->gpuflag & IMA_GPU_PARTIAL_REFRESH) != 0))) { - image_free_gpu(ima, true); - BLI_freelistN(&ima->gpu_refresh_areas); - ima->gpuflag &= ~(IMA_GPU_REFRESH | IMA_GPU_PARTIAL_REFRESH); - } - else if (ima->gpuflag & IMA_GPU_PARTIAL_REFRESH) { - BLI_assert(ibuf); - BLI_assert(tile); - ImagePartialRefresh *refresh_area; - while (( - refresh_area = static_cast<ImagePartialRefresh *>(BLI_pophead(&ima->gpu_refresh_areas)))) { - const int tile_offset_x = refresh_area->tile_x * IMA_PARTIAL_REFRESH_TILE_SIZE; - const int tile_offset_y = refresh_area->tile_y * IMA_PARTIAL_REFRESH_TILE_SIZE; - const int tile_width = MIN2(IMA_PARTIAL_REFRESH_TILE_SIZE, ibuf->x - tile_offset_x); - const int tile_height = MIN2(IMA_PARTIAL_REFRESH_TILE_SIZE, ibuf->y - tile_offset_y); - image_update_gputexture_ex( - ima, tile, ibuf, tile_offset_x, tile_offset_y, tile_width, tile_height); - MEM_freeN(refresh_area); - } - ima->gpuflag &= ~IMA_GPU_PARTIAL_REFRESH; + /* TODO(jbakker): We should replace the IMA_GPU_REFRESH flag with a call to + * BKE_image-partial_update_mark_full_update. Although the flag is quicker it leads to double + * administration. */ + if ((ima->gpuflag & IMA_GPU_REFRESH) != 0) { + BKE_image_partial_update_mark_full_update(ima); + ima->gpuflag &= ~IMA_GPU_REFRESH; } + if (ima->runtime.partial_update_user == nullptr) { + ima->runtime.partial_update_user = BKE_image_partial_update_create(ima); + } + + image_gpu_texture_try_partial_update(ima, iuser); + /* Tag as in active use for garbage collector. */ BKE_image_tag_time(ima); @@ -417,6 +453,7 @@ static GPUTexture *image_get_gpu_texture(Image *ima, /* Check if we have a valid image. If not, we return a dummy * texture with zero bind-code so we don't keep trying. */ + ImageTile *tile = BKE_image_get_tile(ima, 0); if (tile == nullptr) { *tex = image_gpu_texture_error_create(textarget); return *tex; @@ -427,8 +464,7 @@ static GPUTexture *image_get_gpu_texture(Image *ima, if (ibuf_intern == nullptr) { ibuf_intern = BKE_image_acquire_ibuf(ima, iuser, nullptr); if (ibuf_intern == nullptr) { - *tex = image_gpu_texture_error_create(textarget); - return *tex; + return image_gpu_texture_error_create(textarget); } } @@ -477,15 +513,14 @@ static GPUTexture *image_get_gpu_texture(Image *ima, break; } - /* if `ibuf` was given, we don't own the `ibuf_intern` */ - if (ibuf == nullptr) { - BKE_image_release_ibuf(ima, ibuf_intern, nullptr); - } - if (*tex) { GPU_texture_orig_size_set(*tex, ibuf_intern->x, ibuf_intern->y); } + if (ibuf != ibuf_intern) { + BKE_image_release_ibuf(ima, ibuf_intern, nullptr); + } + return *tex; } @@ -903,87 +938,29 @@ static void image_update_gputexture_ex( void BKE_image_update_gputexture(Image *ima, ImageUser *iuser, int x, int y, int w, int h) { + ImageTile *image_tile = BKE_image_get_tile_from_iuser(ima, iuser); ImBuf *ibuf = BKE_image_acquire_ibuf(ima, iuser, nullptr); - ImageTile *tile = BKE_image_get_tile_from_iuser(ima, iuser); - - if ((ibuf == nullptr) || (w == 0) || (h == 0)) { - /* Full reload of texture. */ - BKE_image_free_gputextures(ima); - } - image_update_gputexture_ex(ima, tile, ibuf, x, y, w, h); + BKE_image_update_gputexture_delayed(ima, image_tile, ibuf, x, y, w, h); BKE_image_release_ibuf(ima, ibuf, nullptr); } -void BKE_image_update_gputexture_delayed( - struct Image *ima, struct ImBuf *ibuf, int x, int y, int w, int h) +void BKE_image_update_gputexture_delayed(struct Image *ima, + struct ImageTile *image_tile, + struct ImBuf *ibuf, + int x, + int y, + int w, + int h) { /* Check for full refresh. */ - if (ibuf && x == 0 && y == 0 && w == ibuf->x && h == ibuf->y) { - ima->gpuflag |= IMA_GPU_REFRESH; - } - /* Check if we can promote partial refresh to a full refresh. */ - if ((ima->gpuflag & (IMA_GPU_REFRESH | IMA_GPU_PARTIAL_REFRESH)) == - (IMA_GPU_REFRESH | IMA_GPU_PARTIAL_REFRESH)) { - ima->gpuflag &= ~IMA_GPU_PARTIAL_REFRESH; - BLI_freelistN(&ima->gpu_refresh_areas); - } - /* Image is already marked for complete refresh. */ - if (ima->gpuflag & IMA_GPU_REFRESH) { - return; - } - - /* Schedule the tiles that covers the requested area. */ - const int start_tile_x = x / IMA_PARTIAL_REFRESH_TILE_SIZE; - const int start_tile_y = y / IMA_PARTIAL_REFRESH_TILE_SIZE; - const int end_tile_x = (x + w) / IMA_PARTIAL_REFRESH_TILE_SIZE; - const int end_tile_y = (y + h) / IMA_PARTIAL_REFRESH_TILE_SIZE; - const int num_tiles_x = (end_tile_x + 1) - (start_tile_x); - const int num_tiles_y = (end_tile_y + 1) - (start_tile_y); - const int num_tiles = num_tiles_x * num_tiles_y; - const bool allocate_on_heap = BLI_BITMAP_SIZE(num_tiles) > 16; - BLI_bitmap *requested_tiles = nullptr; - if (allocate_on_heap) { - requested_tiles = BLI_BITMAP_NEW(num_tiles, __func__); + if (ibuf != nullptr && ima->source != IMA_SRC_TILED && x == 0 && y == 0 && w == ibuf->x && + h == ibuf->y) { + BKE_image_partial_update_mark_full_update(ima); } else { - requested_tiles = BLI_BITMAP_NEW_ALLOCA(num_tiles); - } - - /* Mark the tiles that have already been requested. They don't need to be requested again. */ - int num_tiles_not_scheduled = num_tiles; - LISTBASE_FOREACH (ImagePartialRefresh *, area, &ima->gpu_refresh_areas) { - if (area->tile_x < start_tile_x || area->tile_x > end_tile_x || area->tile_y < start_tile_y || - area->tile_y > end_tile_y) { - continue; - } - int requested_tile_index = (area->tile_x - start_tile_x) + - (area->tile_y - start_tile_y) * num_tiles_x; - BLI_BITMAP_ENABLE(requested_tiles, requested_tile_index); - num_tiles_not_scheduled--; - if (num_tiles_not_scheduled == 0) { - break; - } - } - - /* Schedule the tiles that aren't requested yet. */ - if (num_tiles_not_scheduled) { - int tile_index = 0; - for (int tile_y = start_tile_y; tile_y <= end_tile_y; tile_y++) { - for (int tile_x = start_tile_x; tile_x <= end_tile_x; tile_x++) { - if (!BLI_BITMAP_TEST_BOOL(requested_tiles, tile_index)) { - ImagePartialRefresh *area = static_cast<ImagePartialRefresh *>( - MEM_mallocN(sizeof(ImagePartialRefresh), __func__)); - area->tile_x = tile_x; - area->tile_y = tile_y; - BLI_addtail(&ima->gpu_refresh_areas, area); - } - tile_index++; - } - } - ima->gpuflag |= IMA_GPU_PARTIAL_REFRESH; - } - if (allocate_on_heap) { - MEM_freeN(requested_tiles); + rcti dirty_region; + BLI_rcti_init(&dirty_region, x, x + w, y, y + h); + BKE_image_partial_update_mark_region(ima, image_tile, ibuf, &dirty_region); } } @@ -1016,3 +993,4 @@ void BKE_image_paint_set_mipmap(Main *bmain, bool mipmap) } /** \} */ +} |