Compare commits
31 Commits
temp-T9593
...
temp-gpu-t
Author | SHA1 | Date | |
---|---|---|---|
6cda987f59 | |||
b30f101fc0 | |||
24a5f4d6f8 | |||
7ffe9d56a1 | |||
aac6bf9938 | |||
664adf9e76 | |||
378db2e805 | |||
495c2302d5 | |||
f0c5df70a1 | |||
a9c7ebbef2 | |||
65b66c8abe | |||
f93f579e02 | |||
732e5d98d4 | |||
d6e52462f9 | |||
709b19c1b0 | |||
152ad1c208 | |||
ed7d12e711 | |||
4944372320 | |||
0218dfd17d | |||
2b128ecf24 | |||
9726498525 | |||
3e2957ea16 | |||
022c185912 | |||
5e6a37a3e4 | |||
36fc80f1e7 | |||
cb05849083 | |||
e167140e5d | |||
7bd6a318e3 | |||
c957c87ff0 | |||
eca92b3fb1 | |||
d918c2c907 |
@@ -24,6 +24,8 @@
|
||||
|
||||
#include "BLI_utildefines.h"
|
||||
|
||||
#include "BLI_rect.h"
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
@@ -378,8 +380,13 @@ struct GPUTexture *BKE_image_get_gpu_tilemap(struct Image *image,
|
||||
bool BKE_image_has_gpu_texture_premultiplied_alpha(struct Image *image, struct ImBuf *ibuf);
|
||||
void BKE_image_update_gputexture(
|
||||
struct Image *ima, struct ImageUser *iuser, int x, int y, int w, int h);
|
||||
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);
|
||||
void BKE_image_paint_set_mipmap(struct Main *bmain, bool mipmap);
|
||||
|
||||
/* Delayed free of OpenGL buffers by main thread */
|
||||
@@ -390,6 +397,32 @@ bool BKE_image_remove_renderslot(struct Image *ima, struct ImageUser *iuser, int
|
||||
struct RenderSlot *BKE_image_get_renderslot(struct Image *ima, int index);
|
||||
bool BKE_image_clear_renderslot(struct Image *ima, struct ImageUser *iuser, int slot);
|
||||
|
||||
/* --- image_partial_update.cc --- */
|
||||
/** Image partial updates. */
|
||||
struct PartialUpdateUser;
|
||||
|
||||
/**
|
||||
* \brief Create a new PartialUpdateUser. An Object that contains data to use partial updates.
|
||||
*/
|
||||
struct PartialUpdateUser *BKE_image_partial_update_create(const struct Image *image);
|
||||
|
||||
/**
|
||||
* \brief free a partial update user.
|
||||
*/
|
||||
void BKE_image_partial_update_free(struct PartialUpdateUser *user);
|
||||
|
||||
/* --- partial updater (image side) --- */
|
||||
struct PartialUpdateRegister;
|
||||
|
||||
void BKE_image_partial_update_register_free(struct Image *image);
|
||||
/** \brief Mark a region of the image to update. */
|
||||
void BKE_image_partial_update_mark_region(struct Image *image,
|
||||
const struct ImageTile *image_tile,
|
||||
const struct ImBuf *image_buffer,
|
||||
const rcti *updated_region);
|
||||
/** \brief Mark the whole image to be updated. */
|
||||
void BKE_image_partial_update_mark_full_update(struct Image *image);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
295
source/blender/blenkernel/BKE_image_partial_update.hh
Normal file
295
source/blender/blenkernel/BKE_image_partial_update.hh
Normal file
@@ -0,0 +1,295 @@
|
||||
/*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License
|
||||
* as published by the Free Software Foundation; either version 2
|
||||
* of the License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software Foundation,
|
||||
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
*
|
||||
* Copyright 2021, Blender Foundation.
|
||||
*/
|
||||
|
||||
/** \file
|
||||
* \ingroup bke
|
||||
*
|
||||
* To reduce the overhead of image processing this file contains a mechanism to detect areas of the
|
||||
* image that are changed. These areas are organized in chunks. Changes that happen over time are
|
||||
* organized in changesets.
|
||||
*
|
||||
* A common usecase is to update GPUTexture for drawing where only that part is uploaded that only
|
||||
* changed.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "BLI_utildefines.h"
|
||||
|
||||
#include "BLI_rect.h"
|
||||
|
||||
#include "DNA_image_types.h"
|
||||
|
||||
extern "C" {
|
||||
struct PartialUpdateUser;
|
||||
struct PartialUpdateRegister;
|
||||
}
|
||||
|
||||
namespace blender::bke::image {
|
||||
|
||||
using TileNumber = int;
|
||||
|
||||
namespace partial_update {
|
||||
|
||||
/* --- image_partial_update.cc --- */
|
||||
/** Image partial updates. */
|
||||
|
||||
/**
|
||||
* \brief Result codes of #BKE_image_partial_update_collect_changes.
|
||||
*/
|
||||
enum class ePartialUpdateCollectResult {
|
||||
/** \brief Unable to construct partial updates. Caller should perform a full update. */
|
||||
FullUpdateNeeded,
|
||||
|
||||
/** \brief No changes detected since the last time requested. */
|
||||
NoChangesDetected,
|
||||
|
||||
/** \brief Changes detected since the last time requested. */
|
||||
PartialChangesDetected,
|
||||
};
|
||||
|
||||
/**
|
||||
* \brief A region to update.
|
||||
*
|
||||
* Data is organized in tiles. These tiles are in texel space (1 unit is a single texel). When
|
||||
* tiles are requested they are merged with neighboring tiles.
|
||||
*/
|
||||
struct PartialUpdateRegion {
|
||||
/** \brief region of the image that has been updated. Region can be bigger than actual changes.
|
||||
*/
|
||||
struct rcti region;
|
||||
|
||||
/**
|
||||
* \brief Tile number (UDIM) that this region belongs to.
|
||||
*/
|
||||
TileNumber tile_number;
|
||||
};
|
||||
|
||||
/**
|
||||
* \brief Return codes of #BKE_image_partial_update_get_next_change.
|
||||
*/
|
||||
enum class ePartialUpdateIterResult {
|
||||
/** \brief no tiles left when iterating over tiles. */
|
||||
Finished = 0,
|
||||
|
||||
/** \brief a chunk was available and has been loaded. */
|
||||
ChangeAvailable = 1,
|
||||
};
|
||||
|
||||
/**
|
||||
* \brief collect the partial update since the last request.
|
||||
*
|
||||
* Invoke #BKE_image_partial_update_get_next_change to iterate over the collected tiles.
|
||||
*
|
||||
* \returns ePartialUpdateCollectResult::FullUpdateNeeded: called should not use partial updates
|
||||
* but recalculate the full image. This result can be expected when called for the first time for a
|
||||
* user and when it isn't possible to reconstruct the changes as the internal state doesn't have
|
||||
* enough data stored. ePartialUpdateCollectResult::NoChangesDetected: The have been no changes
|
||||
* detected since last invoke for the same user.
|
||||
* ePartialUpdateCollectResult::PartialChangesDetected: Parts of the image has been updated since
|
||||
* last invoke for the same user. The changes can be read by using
|
||||
* #BKE_image_partial_update_get_next_change.
|
||||
*/
|
||||
ePartialUpdateCollectResult BKE_image_partial_update_collect_changes(
|
||||
struct Image *image, struct PartialUpdateUser *user);
|
||||
|
||||
ePartialUpdateIterResult BKE_image_partial_update_get_next_change(
|
||||
struct PartialUpdateUser *user, struct PartialUpdateRegion *r_region);
|
||||
|
||||
/** \brief Abstract class to load tile data when using the PartialUpdateChecker. */
|
||||
class AbstractTileData {
|
||||
protected:
|
||||
virtual ~AbstractTileData() = default;
|
||||
|
||||
public:
|
||||
/**
|
||||
* \brief Load the data for the given tile_number.
|
||||
*
|
||||
* Invoked when changes are on a different tile compared to the previous tile..
|
||||
*/
|
||||
virtual void init_data(TileNumber tile_number) = 0;
|
||||
/**
|
||||
* \brief Unload the data that has been loaded.
|
||||
*
|
||||
* Invoked when changes are on a different tile compared to the previous tile or when finished
|
||||
* iterating over the changes.
|
||||
*/
|
||||
virtual void free_data() = 0;
|
||||
};
|
||||
|
||||
/**
|
||||
* \brief Class to not load any tile specific data when iterating over changes.
|
||||
*/
|
||||
class NoTileData : AbstractTileData {
|
||||
public:
|
||||
NoTileData(Image *UNUSED(image), ImageUser &UNUSED(image_user))
|
||||
{
|
||||
}
|
||||
|
||||
void init_data(TileNumber UNUSED(new_tile_number)) override
|
||||
{
|
||||
}
|
||||
|
||||
void free_data() override
|
||||
{
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* \brief Load the ImageTile and ImBuf associated with the partial change.
|
||||
*/
|
||||
class ImageTileData : AbstractTileData {
|
||||
public:
|
||||
/**
|
||||
* \brief Not owned Image that is being iterated over.
|
||||
*/
|
||||
Image *image;
|
||||
|
||||
/**
|
||||
* \brief Local copy of the image user.
|
||||
*
|
||||
* The local copy is required so we don't change the image user of the caller.
|
||||
* We need to change it in order to request data for a specific tile.
|
||||
*/
|
||||
ImageUser image_user;
|
||||
|
||||
/**
|
||||
* \brief ImageTile associated with the loaded tile.
|
||||
* Data is not owned by this instance but by the `image`.
|
||||
*/
|
||||
ImageTile *tile = nullptr;
|
||||
|
||||
/**
|
||||
* \brief ImBuf of the loaded tile.
|
||||
*
|
||||
* Can be nullptr when the file doesn't exist or when the tile hasn't been initialized.
|
||||
*/
|
||||
ImBuf *tile_buffer = nullptr;
|
||||
|
||||
ImageTileData(Image *image, ImageUser image_user) : image(image), image_user(image_user)
|
||||
{
|
||||
}
|
||||
|
||||
void init_data(TileNumber new_tile_number) override
|
||||
{
|
||||
image_user.tile = new_tile_number;
|
||||
tile = BKE_image_get_tile(image, new_tile_number);
|
||||
tile_buffer = BKE_image_acquire_ibuf(image, &image_user, NULL);
|
||||
}
|
||||
|
||||
void free_data() override
|
||||
{
|
||||
BKE_image_release_ibuf(image, tile_buffer, nullptr);
|
||||
tile = nullptr;
|
||||
tile_buffer = nullptr;
|
||||
}
|
||||
};
|
||||
|
||||
template<typename TileData = NoTileData> struct PartialUpdateChecker {
|
||||
|
||||
/**
|
||||
* \brief Not owned Image that is being iterated over.
|
||||
*/
|
||||
Image *image;
|
||||
ImageUser *image_user;
|
||||
|
||||
/**
|
||||
* \brief the collected changes are stored inside the PartialUpdateUser.
|
||||
*/
|
||||
PartialUpdateUser *user;
|
||||
|
||||
struct CollectResult {
|
||||
PartialUpdateChecker<TileData> *checker;
|
||||
|
||||
/**
|
||||
* \brief Tile specific data.
|
||||
*/
|
||||
TileData tile_data;
|
||||
PartialUpdateRegion changed_region;
|
||||
ePartialUpdateCollectResult result_code;
|
||||
|
||||
private:
|
||||
TileNumber last_tile_number;
|
||||
|
||||
public:
|
||||
CollectResult(PartialUpdateChecker<TileData> *checker, ePartialUpdateCollectResult result_code)
|
||||
: checker(checker),
|
||||
tile_data(checker->image, *checker->image_user),
|
||||
result_code(result_code)
|
||||
{
|
||||
}
|
||||
|
||||
const ePartialUpdateCollectResult get_result_code() const
|
||||
{
|
||||
return result_code;
|
||||
}
|
||||
|
||||
/**
|
||||
* \brief Load the next changed region.
|
||||
*
|
||||
* This member function can only be called when partial changes are detected.
|
||||
* (`get_result_code()` returns `ePartialUpdateCollectResult::PartialChangesDetected`).
|
||||
*
|
||||
* When changes for another tile than the previous tile is loaded the #tile_data will be
|
||||
* updated.
|
||||
*/
|
||||
ePartialUpdateIterResult get_next_change()
|
||||
{
|
||||
BLI_assert(result_code == ePartialUpdateCollectResult::PartialChangesDetected);
|
||||
ePartialUpdateIterResult result = BKE_image_partial_update_get_next_change(checker->user,
|
||||
&changed_region);
|
||||
switch (result) {
|
||||
case ePartialUpdateIterResult::Finished:
|
||||
tile_data.free_data();
|
||||
return result;
|
||||
|
||||
case ePartialUpdateIterResult::ChangeAvailable:
|
||||
if (last_tile_number == changed_region.tile_number) {
|
||||
return result;
|
||||
}
|
||||
tile_data.free_data();
|
||||
tile_data.init_data(changed_region.tile_number);
|
||||
last_tile_number = changed_region.tile_number;
|
||||
return result;
|
||||
|
||||
default:
|
||||
BLI_assert_unreachable();
|
||||
return result;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
public:
|
||||
PartialUpdateChecker(Image *image, ImageUser *image_user, PartialUpdateUser *user)
|
||||
: image(image), image_user(image_user), user(user)
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* \brief Check for new changes since the last time this method was invoked for this #user.
|
||||
*/
|
||||
CollectResult collect_changes()
|
||||
{
|
||||
ePartialUpdateCollectResult collect_result = BKE_image_partial_update_collect_changes(image,
|
||||
user);
|
||||
return CollectResult(this, collect_result);
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace partial_update
|
||||
} // namespace blender::bke::image
|
@@ -164,6 +164,7 @@ set(SRC
|
||||
intern/idprop_utils.c
|
||||
intern/idtype.c
|
||||
intern/image.c
|
||||
intern/image_partial_update.cc
|
||||
intern/image_gen.c
|
||||
intern/image_gpu.cc
|
||||
intern/image_save.c
|
||||
@@ -810,6 +811,7 @@ if(WITH_GTESTS)
|
||||
intern/asset_test.cc
|
||||
intern/cryptomatte_test.cc
|
||||
intern/fcurve_test.cc
|
||||
intern/image_partial_update_test.cc
|
||||
intern/lattice_deform_test.cc
|
||||
intern/layer_test.cc
|
||||
intern/lib_id_test.cc
|
||||
|
@@ -130,6 +130,22 @@ static void image_runtime_reset_on_copy(struct Image *image)
|
||||
{
|
||||
image->runtime.cache_mutex = MEM_mallocN(sizeof(ThreadMutex), "image runtime cache_mutex");
|
||||
BLI_mutex_init(image->runtime.cache_mutex);
|
||||
|
||||
image->runtime.partial_update_register = NULL;
|
||||
image->runtime.partial_update_user = NULL;
|
||||
}
|
||||
|
||||
static void image_runtime_free_data(struct Image *image)
|
||||
{
|
||||
BLI_mutex_end(image->runtime.cache_mutex);
|
||||
MEM_freeN(image->runtime.cache_mutex);
|
||||
image->runtime.cache_mutex = NULL;
|
||||
|
||||
if (image->runtime.partial_update_user != NULL) {
|
||||
BKE_image_partial_update_free(image->runtime.partial_update_user);
|
||||
image->runtime.partial_update_user = NULL;
|
||||
}
|
||||
BKE_image_partial_update_register_free(image);
|
||||
}
|
||||
|
||||
static void image_init_data(ID *id)
|
||||
@@ -209,10 +225,8 @@ static void image_free_data(ID *id)
|
||||
BKE_previewimg_free(&image->preview);
|
||||
|
||||
BLI_freelistN(&image->tiles);
|
||||
BLI_freelistN(&image->gpu_refresh_areas);
|
||||
|
||||
BLI_mutex_end(image->runtime.cache_mutex);
|
||||
MEM_freeN(image->runtime.cache_mutex);
|
||||
image_runtime_free_data(image);
|
||||
}
|
||||
|
||||
static void image_foreach_cache(ID *id,
|
||||
@@ -263,7 +277,8 @@ static void image_blend_write(BlendWriter *writer, ID *id, const void *id_addres
|
||||
ima->cache = NULL;
|
||||
ima->gpuflag = 0;
|
||||
BLI_listbase_clear(&ima->anims);
|
||||
BLI_listbase_clear(&ima->gpu_refresh_areas);
|
||||
ima->runtime.partial_update_register = NULL;
|
||||
ima->runtime.partial_update_user = NULL;
|
||||
for (int i = 0; i < 3; i++) {
|
||||
for (int j = 0; j < 2; j++) {
|
||||
for (int resolution = 0; resolution < IMA_TEXTURE_RESOLUTION_LEN; resolution++) {
|
||||
@@ -343,7 +358,6 @@ static void image_blend_read_data(BlendDataReader *reader, ID *id)
|
||||
|
||||
ima->lastused = 0;
|
||||
ima->gpuflag = 0;
|
||||
BLI_listbase_clear(&ima->gpu_refresh_areas);
|
||||
|
||||
image_runtime_reset(ima);
|
||||
}
|
||||
|
@@ -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);
|
||||
@@ -53,14 +58,6 @@ static void image_free_gpu_limited_scale(Image *ima);
|
||||
static void image_update_gputexture_ex(
|
||||
Image *ima, ImageTile *tile, ImBuf *ibuf, int x, int y, int w, int h);
|
||||
|
||||
/* Internal structs. */
|
||||
#define IMA_PARTIAL_REFRESH_TILE_SIZE 256
|
||||
struct ImagePartialRefresh {
|
||||
struct ImagePartialRefresh *next, *prev;
|
||||
int tile_x;
|
||||
int tile_y;
|
||||
};
|
||||
|
||||
/* Is the alpha of the `GPUTexture` for a given image/ibuf premultiplied. */
|
||||
bool BKE_image_has_gpu_texture_premultiplied_alpha(Image *image, ImBuf *ibuf)
|
||||
{
|
||||
@@ -337,6 +334,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 +409,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);
|
||||
/* 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;
|
||||
}
|
||||
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;
|
||||
|
||||
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 +445,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 +456,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 +505,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;
|
||||
}
|
||||
|
||||
@@ -905,14 +932,9 @@ static void image_update_gputexture_ex(
|
||||
* quicker than fully updating the texture for high resolution images. */
|
||||
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);
|
||||
}
|
||||
|
||||
@@ -920,76 +942,23 @@ void BKE_image_update_gputexture(Image *ima, ImageUser *iuser, int x, int y, int
|
||||
* The next time the GPUTexture is used these tiles will be refreshes. This saves time
|
||||
* when writing to the same place multiple times This happens for during foreground
|
||||
* rendering. */
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1026,3 +995,4 @@ void BKE_image_paint_set_mipmap(Main *bmain, bool mipmap)
|
||||
}
|
||||
|
||||
/** \} */
|
||||
}
|
||||
|
598
source/blender/blenkernel/intern/image_partial_update.cc
Normal file
598
source/blender/blenkernel/intern/image_partial_update.cc
Normal file
@@ -0,0 +1,598 @@
|
||||
/*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License
|
||||
* as published by the Free Software Foundation; either version 2
|
||||
* of the License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software Foundation,
|
||||
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
*
|
||||
* Copyright 2021, Blender Foundation.
|
||||
*/
|
||||
/**
|
||||
* \file image_gpu_partial_update.cc
|
||||
* \ingroup bke
|
||||
*
|
||||
* To reduce the overhead of image processing this file contains a mechanism to detect areas of the
|
||||
* image that are changed. These areas are organized in chunks. Changes that happen over time are
|
||||
* organized in changesets.
|
||||
*
|
||||
* A common usecase is to update GPUTexture for drawing where only that part is uploaded that only
|
||||
* changed.
|
||||
*
|
||||
* Usage:
|
||||
*
|
||||
* ```
|
||||
* Image *image = ...;
|
||||
* ImBuf *image_buffer = ...;
|
||||
*
|
||||
* // Partial_update_user should be kept for the whole session where the changes needs to be
|
||||
* // tracked. Keep this instance alive as long as you need to track image changes.
|
||||
*
|
||||
* PartialUpdateUser *partial_update_user = BKE_image_partial_update_create(image);
|
||||
*
|
||||
* ...
|
||||
*
|
||||
* switch (BKE_image_partial_update_collect_changes(image, image_buffer))
|
||||
* {
|
||||
* case ePartialUpdateCollectResult::FullUpdateNeeded:
|
||||
* // Unable to do partial updates. Perform a full update.
|
||||
* break;
|
||||
* case ePartialUpdateCollectResult::PartialChangesDetected:
|
||||
* PartialUpdateRegion change;
|
||||
* while (BKE_image_partial_update_get_next_change(partial_update_user, &change) ==
|
||||
* ePartialUpdateIterResult::ChangeAvailable){
|
||||
* // Do something with the change.
|
||||
* }
|
||||
* case ePartialUpdateCollectResult::NoChangesDetected:
|
||||
* break;
|
||||
* }
|
||||
*
|
||||
* ...
|
||||
*
|
||||
* // Free partial_update_user.
|
||||
* BKE_image_partial_update_free(partial_update_user);
|
||||
*
|
||||
* ```
|
||||
*/
|
||||
|
||||
#include <optional>
|
||||
|
||||
#include "BKE_image.h"
|
||||
#include "BKE_image_partial_update.hh"
|
||||
|
||||
#include "DNA_image_types.h"
|
||||
|
||||
#include "IMB_imbuf.h"
|
||||
#include "IMB_imbuf_types.h"
|
||||
|
||||
#include "BLI_vector.hh"
|
||||
|
||||
namespace blender::bke::image::partial_update {
|
||||
|
||||
/** \brief Size of chunks to track changes. */
|
||||
constexpr int CHUNK_SIZE = 256;
|
||||
|
||||
/**
|
||||
* \brief Max number of changesets to keep in history.
|
||||
*
|
||||
* A higher number would need more memory and processing
|
||||
* to calculate a changeset, but would lead to do partial updates for requests that don't happen
|
||||
* every frame.
|
||||
*
|
||||
* A to small number would lead to more full updates when changes couldn't be reconstructed from
|
||||
* the available history.
|
||||
*/
|
||||
constexpr int MAX_HISTORY_LEN = 4;
|
||||
|
||||
/**
|
||||
* \brief get the chunk number for the give pixel coordinate.
|
||||
*
|
||||
* As chunks are squares the this member can be used for both x and y axis.
|
||||
*/
|
||||
static int chunk_number_for_pixel(int pixel_offset)
|
||||
{
|
||||
int chunk_offset = pixel_offset / CHUNK_SIZE;
|
||||
if (pixel_offset < 0) {
|
||||
chunk_offset -= 1;
|
||||
}
|
||||
return chunk_offset;
|
||||
}
|
||||
|
||||
struct PartialUpdateUserImpl;
|
||||
struct PartialUpdateRegisterImpl;
|
||||
|
||||
/**
|
||||
* Wrap PartialUpdateUserImpl to its C-struct (PartialUpdateUser).
|
||||
*/
|
||||
static struct PartialUpdateUser *wrap(PartialUpdateUserImpl *user)
|
||||
{
|
||||
return static_cast<struct PartialUpdateUser *>(static_cast<void *>(user));
|
||||
}
|
||||
|
||||
/**
|
||||
* Unwrap the PartialUpdateUser C-struct to its CPP counterpart (PartialUpdateUserImpl).
|
||||
*/
|
||||
static PartialUpdateUserImpl *unwrap(struct PartialUpdateUser *user)
|
||||
{
|
||||
return static_cast<PartialUpdateUserImpl *>(static_cast<void *>(user));
|
||||
}
|
||||
|
||||
/**
|
||||
* Wrap PartialUpdateRegisterImpl to its C-struct (PartialUpdateRegister).
|
||||
*/
|
||||
static struct PartialUpdateRegister *wrap(PartialUpdateRegisterImpl *partial_update_register)
|
||||
{
|
||||
return static_cast<struct PartialUpdateRegister *>(static_cast<void *>(partial_update_register));
|
||||
}
|
||||
|
||||
/**
|
||||
* Unwrap the PartialUpdateRegister C-struct to its CPP counterpart (PartialUpdateRegisterImpl).
|
||||
*/
|
||||
static PartialUpdateRegisterImpl *unwrap(struct PartialUpdateRegister *partial_update_register)
|
||||
{
|
||||
return static_cast<PartialUpdateRegisterImpl *>(static_cast<void *>(partial_update_register));
|
||||
}
|
||||
|
||||
using TileNumber = int32_t;
|
||||
using ChangesetID = int64_t;
|
||||
constexpr ChangesetID UnknownChangesetID = -1;
|
||||
|
||||
struct PartialUpdateUserImpl {
|
||||
/** \brief last changeset id that was seen by this user. */
|
||||
ChangesetID last_changeset_id = UnknownChangesetID;
|
||||
|
||||
/** \brief regions that have been updated. */
|
||||
Vector<PartialUpdateRegion> updated_regions;
|
||||
|
||||
#ifdef NDEBUG
|
||||
/** \brief reference to image to validate correct API usage. */
|
||||
const void *debug_image_;
|
||||
#endif
|
||||
|
||||
/**
|
||||
* \brief Clear the list of updated regions.
|
||||
*
|
||||
* Updated regions should be cleared at the start of #BKE_image_partial_update_collect_changes so
|
||||
* the
|
||||
*/
|
||||
void clear_updated_regions()
|
||||
{
|
||||
updated_regions.clear();
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* \brief Dirty chunks of an ImageTile.
|
||||
*
|
||||
* Internally dirty tiles are grouped together in change sets to make sure that the correct
|
||||
* answer can be built for different users reducing the amount of merges.
|
||||
*/
|
||||
struct TileChangeset {
|
||||
private:
|
||||
/** \brief Dirty flag for each chunk. */
|
||||
std::vector<bool> chunk_dirty_flags_;
|
||||
/** \brief are there dirty/ */
|
||||
bool has_dirty_chunks_ = false;
|
||||
|
||||
public:
|
||||
/** \brief Width of the tile in pixels. */
|
||||
int tile_width;
|
||||
/** \brief Height of the tile in pixels. */
|
||||
int tile_height;
|
||||
/** \brief Number of chunks along the x-axis. */
|
||||
int chunk_x_len;
|
||||
/** \brief Number of chunks along the y-axis. */
|
||||
int chunk_y_len;
|
||||
|
||||
TileNumber tile_number;
|
||||
|
||||
void clear()
|
||||
{
|
||||
init_chunks(chunk_x_len, chunk_y_len);
|
||||
}
|
||||
|
||||
/**
|
||||
* \brief Update the resolution of the tile.
|
||||
*
|
||||
* \returns true: resolution has been updated.
|
||||
* false: resolution was unchanged.
|
||||
*/
|
||||
bool update_resolution(const ImBuf *image_buffer)
|
||||
{
|
||||
if (tile_width == image_buffer->x && tile_height == image_buffer->y) {
|
||||
return false;
|
||||
}
|
||||
|
||||
tile_width = image_buffer->x;
|
||||
tile_height = image_buffer->y;
|
||||
|
||||
int chunk_x_len = tile_width / CHUNK_SIZE;
|
||||
int chunk_y_len = tile_height / CHUNK_SIZE;
|
||||
init_chunks(chunk_x_len, chunk_y_len);
|
||||
return true;
|
||||
}
|
||||
|
||||
void mark_region(const rcti *updated_region)
|
||||
{
|
||||
int start_x_chunk = chunk_number_for_pixel(updated_region->xmin);
|
||||
int end_x_chunk = chunk_number_for_pixel(updated_region->xmax - 1);
|
||||
int start_y_chunk = chunk_number_for_pixel(updated_region->ymin);
|
||||
int end_y_chunk = chunk_number_for_pixel(updated_region->ymax - 1);
|
||||
|
||||
/* Clamp tiles to tiles in image. */
|
||||
start_x_chunk = max_ii(0, start_x_chunk);
|
||||
start_y_chunk = max_ii(0, start_y_chunk);
|
||||
end_x_chunk = min_ii(chunk_x_len - 1, end_x_chunk);
|
||||
end_y_chunk = min_ii(chunk_y_len - 1, end_y_chunk);
|
||||
|
||||
/* Early exit when no tiles need to be updated. */
|
||||
if (start_x_chunk >= chunk_x_len) {
|
||||
return;
|
||||
}
|
||||
if (start_y_chunk >= chunk_y_len) {
|
||||
return;
|
||||
}
|
||||
if (end_x_chunk < 0) {
|
||||
return;
|
||||
}
|
||||
if (end_y_chunk < 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
mark_chunks_dirty(start_x_chunk, start_y_chunk, end_x_chunk, end_y_chunk);
|
||||
}
|
||||
|
||||
void mark_chunks_dirty(int start_x_chunk, int start_y_chunk, int end_x_chunk, int end_y_chunk)
|
||||
{
|
||||
for (int chunk_y = start_y_chunk; chunk_y <= end_y_chunk; chunk_y++) {
|
||||
for (int chunk_x = start_x_chunk; chunk_x <= end_x_chunk; chunk_x++) {
|
||||
int chunk_index = chunk_y * chunk_x_len + chunk_x;
|
||||
chunk_dirty_flags_[chunk_index] = true;
|
||||
}
|
||||
}
|
||||
has_dirty_chunks_ = true;
|
||||
}
|
||||
|
||||
bool has_dirty_chunks() const
|
||||
{
|
||||
return has_dirty_chunks_;
|
||||
}
|
||||
|
||||
void init_chunks(int chunk_x_len_, int chunk_y_len_)
|
||||
{
|
||||
chunk_x_len = chunk_x_len_;
|
||||
chunk_y_len = chunk_y_len_;
|
||||
const int chunk_len = chunk_x_len * chunk_y_len;
|
||||
const int previous_chunk_len = chunk_dirty_flags_.size();
|
||||
|
||||
chunk_dirty_flags_.resize(chunk_len);
|
||||
/* Fast exit. When the changeset was already empty no need to re-init the chunk_validity. */
|
||||
if (!has_dirty_chunks()) {
|
||||
return;
|
||||
}
|
||||
for (int index = 0; index < min_ii(chunk_len, previous_chunk_len); index++) {
|
||||
chunk_dirty_flags_[index] = false;
|
||||
}
|
||||
has_dirty_chunks_ = false;
|
||||
}
|
||||
|
||||
/** \brief Merge the given changeset into the receiver. */
|
||||
void merge(const TileChangeset &other)
|
||||
{
|
||||
BLI_assert(chunk_x_len == other.chunk_x_len);
|
||||
BLI_assert(chunk_y_len == other.chunk_y_len);
|
||||
const int chunk_len = chunk_x_len * chunk_y_len;
|
||||
|
||||
for (int chunk_index = 0; chunk_index < chunk_len; chunk_index++) {
|
||||
chunk_dirty_flags_[chunk_index] = chunk_dirty_flags_[chunk_index] |
|
||||
other.chunk_dirty_flags_[chunk_index];
|
||||
}
|
||||
has_dirty_chunks_ |= other.has_dirty_chunks_;
|
||||
}
|
||||
|
||||
/** \brief has a chunk changed inside this changeset. */
|
||||
bool is_chunk_dirty(int chunk_x, int chunk_y) const
|
||||
{
|
||||
const int chunk_index = chunk_y * chunk_x_len + chunk_x;
|
||||
return chunk_dirty_flags_[chunk_index];
|
||||
}
|
||||
};
|
||||
|
||||
/** \brief Changeset keeping track of changes for an image */
|
||||
struct Changeset {
|
||||
private:
|
||||
Vector<TileChangeset> tiles;
|
||||
|
||||
public:
|
||||
/** \brief Keep track if any of the tiles have dirty chunks. */
|
||||
bool has_dirty_chunks;
|
||||
|
||||
/**
|
||||
* \brief Retrieve the TileChangeset for the given ImageTile.
|
||||
*
|
||||
* When the TileChangeset isn't found, it will be added.
|
||||
*/
|
||||
TileChangeset &operator[](const ImageTile *image_tile)
|
||||
{
|
||||
for (TileChangeset &tile_changeset : tiles) {
|
||||
if (tile_changeset.tile_number == image_tile->tile_number) {
|
||||
return tile_changeset;
|
||||
}
|
||||
}
|
||||
|
||||
TileChangeset tile_changeset;
|
||||
tile_changeset.tile_number = image_tile->tile_number;
|
||||
tiles.append_as(tile_changeset);
|
||||
|
||||
return tiles.last();
|
||||
}
|
||||
|
||||
/** \brief Does this changeset contain data for the given tile. */
|
||||
bool has_tile(const ImageTile *image_tile)
|
||||
{
|
||||
for (TileChangeset &tile_changeset : tiles) {
|
||||
if (tile_changeset.tile_number == image_tile->tile_number) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/** \brief Clear this changeset. */
|
||||
void clear()
|
||||
{
|
||||
tiles.clear();
|
||||
has_dirty_chunks = false;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* \brief Partial update changes stored inside the image runtime.
|
||||
*
|
||||
* The PartialUpdateRegisterImpl will keep track of changes over time. Changes are groups inside
|
||||
* TileChangesets.
|
||||
*/
|
||||
struct PartialUpdateRegisterImpl {
|
||||
/** \brief changeset id of the first changeset kept in #history. */
|
||||
ChangesetID first_changeset_id;
|
||||
/** \brief changeset id of the top changeset kept in #history. */
|
||||
ChangesetID last_changeset_id;
|
||||
|
||||
/** \brief history of changesets. */
|
||||
Vector<Changeset> history;
|
||||
/** \brief The current changeset. New changes will be added to this changeset. */
|
||||
Changeset current_changeset;
|
||||
|
||||
void update_resolution(const ImageTile *image_tile, const ImBuf *image_buffer)
|
||||
{
|
||||
TileChangeset &tile_changeset = current_changeset[image_tile];
|
||||
const bool has_dirty_chunks = tile_changeset.has_dirty_chunks();
|
||||
const bool resolution_changed = tile_changeset.update_resolution(image_buffer);
|
||||
|
||||
if (has_dirty_chunks && resolution_changed && !history.is_empty()) {
|
||||
mark_full_update();
|
||||
}
|
||||
}
|
||||
|
||||
void mark_full_update()
|
||||
{
|
||||
history.clear();
|
||||
last_changeset_id++;
|
||||
current_changeset.clear();
|
||||
first_changeset_id = last_changeset_id;
|
||||
}
|
||||
|
||||
void mark_region(const ImageTile *image_tile, const rcti *updated_region)
|
||||
{
|
||||
TileChangeset &tile_changeset = current_changeset[image_tile];
|
||||
tile_changeset.mark_region(updated_region);
|
||||
current_changeset.has_dirty_chunks |= tile_changeset.has_dirty_chunks();
|
||||
}
|
||||
|
||||
void ensure_empty_changeset()
|
||||
{
|
||||
if (!current_changeset.has_dirty_chunks) {
|
||||
/* No need to create a new changeset when previous changeset does not contain any dirty
|
||||
* tiles. */
|
||||
return;
|
||||
}
|
||||
commit_current_changeset();
|
||||
limit_history();
|
||||
}
|
||||
|
||||
/** \brief Move the current changeset to the history and resets the current changeset. */
|
||||
void commit_current_changeset()
|
||||
{
|
||||
history.append_as(std::move(current_changeset));
|
||||
current_changeset.clear();
|
||||
last_changeset_id++;
|
||||
}
|
||||
|
||||
/** \brief Limit the number of items in the changeset. */
|
||||
void limit_history()
|
||||
{
|
||||
const int num_items_to_remove = max_ii(history.size() - MAX_HISTORY_LEN, 0);
|
||||
if (num_items_to_remove == 0) {
|
||||
return;
|
||||
}
|
||||
history.remove(0, num_items_to_remove);
|
||||
first_changeset_id += num_items_to_remove;
|
||||
}
|
||||
|
||||
/**
|
||||
* /brief Check if data is available to construct the update tiles for the given
|
||||
* changeset_id.
|
||||
*
|
||||
* The update tiles can be created when changeset id is between
|
||||
*/
|
||||
bool can_construct(ChangesetID changeset_id)
|
||||
{
|
||||
return changeset_id >= first_changeset_id;
|
||||
}
|
||||
|
||||
/**
|
||||
* \brief collect all historic changes since a given changeset.
|
||||
*/
|
||||
std::optional<TileChangeset> changed_tile_chunks_since(const ImageTile *image_tile,
|
||||
const ChangesetID from_changeset)
|
||||
{
|
||||
std::optional<TileChangeset> changed_chunks = std::nullopt;
|
||||
for (int index = from_changeset - first_changeset_id; index < history.size(); index++) {
|
||||
if (!history[index].has_tile(image_tile)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
TileChangeset &tile_changeset = history[index][image_tile];
|
||||
if (!changed_chunks.has_value()) {
|
||||
changed_chunks = std::make_optional<TileChangeset>();
|
||||
changed_chunks->init_chunks(tile_changeset.chunk_x_len, tile_changeset.chunk_y_len);
|
||||
changed_chunks->tile_number = image_tile->tile_number;
|
||||
}
|
||||
|
||||
changed_chunks->merge(tile_changeset);
|
||||
}
|
||||
return changed_chunks;
|
||||
}
|
||||
};
|
||||
|
||||
static PartialUpdateRegister *image_partial_update_register_ensure(Image *image)
|
||||
{
|
||||
if (image->runtime.partial_update_register == nullptr) {
|
||||
PartialUpdateRegisterImpl *partial_update_register = OBJECT_GUARDED_NEW(
|
||||
PartialUpdateRegisterImpl);
|
||||
image->runtime.partial_update_register = wrap(partial_update_register);
|
||||
}
|
||||
return image->runtime.partial_update_register;
|
||||
}
|
||||
|
||||
ePartialUpdateCollectResult BKE_image_partial_update_collect_changes(Image *image,
|
||||
PartialUpdateUser *user)
|
||||
{
|
||||
PartialUpdateUserImpl *user_impl = unwrap(user);
|
||||
#ifdef NDEBUG
|
||||
BLI_assert(image == user_impl->debug_image_);
|
||||
#endif
|
||||
|
||||
user_impl->clear_updated_regions();
|
||||
|
||||
PartialUpdateRegisterImpl *partial_updater = unwrap(image_partial_update_register_ensure(image));
|
||||
partial_updater->ensure_empty_changeset();
|
||||
|
||||
if (!partial_updater->can_construct(user_impl->last_changeset_id)) {
|
||||
user_impl->last_changeset_id = partial_updater->last_changeset_id;
|
||||
return ePartialUpdateCollectResult::FullUpdateNeeded;
|
||||
}
|
||||
|
||||
/* Check if there are changes since last invocation for the user. */
|
||||
if (user_impl->last_changeset_id == partial_updater->last_changeset_id) {
|
||||
return ePartialUpdateCollectResult::NoChangesDetected;
|
||||
}
|
||||
|
||||
/* Collect changed tiles. */
|
||||
LISTBASE_FOREACH (ImageTile *, tile, &image->tiles) {
|
||||
std::optional<TileChangeset> changed_chunks = partial_updater->changed_tile_chunks_since(
|
||||
tile, user_impl->last_changeset_id);
|
||||
/* Check if chunks of this tile are dirty. */
|
||||
if (!changed_chunks.has_value()) {
|
||||
continue;
|
||||
}
|
||||
if (!changed_chunks->has_dirty_chunks()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
/* Convert tiles in the changeset to rectangles that are dirty. */
|
||||
for (int chunk_y = 0; chunk_y < changed_chunks->chunk_y_len; chunk_y++) {
|
||||
for (int chunk_x = 0; chunk_x < changed_chunks->chunk_x_len; chunk_x++) {
|
||||
if (!changed_chunks->is_chunk_dirty(chunk_x, chunk_y)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
PartialUpdateRegion region;
|
||||
region.tile_number = tile->tile_number;
|
||||
BLI_rcti_init(®ion.region,
|
||||
chunk_x * CHUNK_SIZE,
|
||||
(chunk_x + 1) * CHUNK_SIZE,
|
||||
chunk_y * CHUNK_SIZE,
|
||||
(chunk_y + 1) * CHUNK_SIZE);
|
||||
user_impl->updated_regions.append_as(region);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
user_impl->last_changeset_id = partial_updater->last_changeset_id;
|
||||
return ePartialUpdateCollectResult::PartialChangesDetected;
|
||||
}
|
||||
|
||||
ePartialUpdateIterResult BKE_image_partial_update_get_next_change(PartialUpdateUser *user,
|
||||
PartialUpdateRegion *r_region)
|
||||
{
|
||||
PartialUpdateUserImpl *user_impl = unwrap(user);
|
||||
if (user_impl->updated_regions.is_empty()) {
|
||||
return ePartialUpdateIterResult::Finished;
|
||||
}
|
||||
PartialUpdateRegion region = user_impl->updated_regions.pop_last();
|
||||
*r_region = region;
|
||||
return ePartialUpdateIterResult::ChangeAvailable;
|
||||
}
|
||||
|
||||
} // namespace blender::bke::image::partial_update
|
||||
|
||||
extern "C" {
|
||||
|
||||
using namespace blender::bke::image::partial_update;
|
||||
|
||||
// TODO(jbakker): cleanup parameter.
|
||||
struct PartialUpdateUser *BKE_image_partial_update_create(const struct Image *image)
|
||||
{
|
||||
PartialUpdateUserImpl *user_impl = OBJECT_GUARDED_NEW(PartialUpdateUserImpl);
|
||||
|
||||
#ifdef NDEBUG
|
||||
user_impl->debug_image_ = image;
|
||||
#else
|
||||
UNUSED_VARS(image);
|
||||
#endif
|
||||
|
||||
return wrap(user_impl);
|
||||
}
|
||||
|
||||
void BKE_image_partial_update_free(PartialUpdateUser *user)
|
||||
{
|
||||
PartialUpdateUserImpl *user_impl = unwrap(user);
|
||||
OBJECT_GUARDED_DELETE(user_impl, PartialUpdateUserImpl);
|
||||
}
|
||||
|
||||
/* --- Image side --- */
|
||||
|
||||
void BKE_image_partial_update_register_free(Image *image)
|
||||
{
|
||||
PartialUpdateRegisterImpl *partial_update_register = unwrap(
|
||||
image->runtime.partial_update_register);
|
||||
if (partial_update_register) {
|
||||
OBJECT_GUARDED_DELETE(partial_update_register, PartialUpdateRegisterImpl);
|
||||
}
|
||||
image->runtime.partial_update_register = nullptr;
|
||||
}
|
||||
|
||||
void BKE_image_partial_update_mark_region(Image *image,
|
||||
const ImageTile *image_tile,
|
||||
const ImBuf *image_buffer,
|
||||
const rcti *updated_region)
|
||||
{
|
||||
PartialUpdateRegisterImpl *partial_updater = unwrap(image_partial_update_register_ensure(image));
|
||||
partial_updater->update_resolution(image_tile, image_buffer);
|
||||
partial_updater->mark_region(image_tile, updated_region);
|
||||
}
|
||||
|
||||
void BKE_image_partial_update_mark_full_update(Image *image)
|
||||
{
|
||||
PartialUpdateRegisterImpl *partial_updater = unwrap(image_partial_update_register_ensure(image));
|
||||
partial_updater->mark_full_update();
|
||||
}
|
||||
}
|
393
source/blender/blenkernel/intern/image_partial_update_test.cc
Normal file
393
source/blender/blenkernel/intern/image_partial_update_test.cc
Normal file
@@ -0,0 +1,393 @@
|
||||
/*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License
|
||||
* as published by the Free Software Foundation; either version 2
|
||||
* of the License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software Foundation,
|
||||
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
*
|
||||
* The Original Code is Copyright (C) 2020 by Blender Foundation.
|
||||
*/
|
||||
#include "testing/testing.h"
|
||||
|
||||
#include "CLG_log.h"
|
||||
|
||||
#include "BKE_appdir.h"
|
||||
#include "BKE_idtype.h"
|
||||
#include "BKE_image.h"
|
||||
#include "BKE_image_partial_update.hh"
|
||||
#include "BKE_main.h"
|
||||
|
||||
#include "IMB_imbuf.h"
|
||||
#include "IMB_moviecache.h"
|
||||
|
||||
#include "DNA_image_types.h"
|
||||
|
||||
#include "MEM_guardedalloc.h"
|
||||
|
||||
namespace blender::bke::image::partial_update {
|
||||
|
||||
constexpr float black_color[4] = {0.0f, 0.0f, 0.0f, 1.0f};
|
||||
|
||||
class ImagePartialUpdateTest : public testing::Test {
|
||||
protected:
|
||||
Main *bmain;
|
||||
Image *image;
|
||||
ImageTile *image_tile;
|
||||
ImageUser image_user = {nullptr};
|
||||
ImBuf *image_buffer;
|
||||
PartialUpdateUser *partial_update_user;
|
||||
|
||||
private:
|
||||
Image *create_test_image(int width, int height)
|
||||
{
|
||||
return BKE_image_add_generated(bmain,
|
||||
width,
|
||||
height,
|
||||
"Test Image",
|
||||
32,
|
||||
true,
|
||||
IMA_GENTYPE_BLANK,
|
||||
black_color,
|
||||
false,
|
||||
false,
|
||||
false);
|
||||
}
|
||||
|
||||
protected:
|
||||
void SetUp() override
|
||||
{
|
||||
CLG_init();
|
||||
BKE_idtype_init();
|
||||
BKE_appdir_init();
|
||||
IMB_init();
|
||||
|
||||
bmain = BKE_main_new();
|
||||
/* Creating an image generates a mem-leak during tests. */
|
||||
image = create_test_image(1024, 1024);
|
||||
image_tile = BKE_image_get_tile(image, 0);
|
||||
image_buffer = BKE_image_acquire_ibuf(image, nullptr, nullptr);
|
||||
|
||||
partial_update_user = BKE_image_partial_update_create(image);
|
||||
}
|
||||
|
||||
void TearDown() override
|
||||
{
|
||||
BKE_image_release_ibuf(image, image_buffer, nullptr);
|
||||
BKE_image_partial_update_free(partial_update_user);
|
||||
BKE_main_free(bmain);
|
||||
|
||||
IMB_moviecache_destruct();
|
||||
IMB_exit();
|
||||
BKE_appdir_exit();
|
||||
CLG_exit();
|
||||
}
|
||||
};
|
||||
|
||||
TEST_F(ImagePartialUpdateTest, mark_full_update)
|
||||
{
|
||||
ePartialUpdateCollectResult result;
|
||||
/* First tile should always return a full update. */
|
||||
result = BKE_image_partial_update_collect_changes(image, partial_update_user);
|
||||
EXPECT_EQ(result, ePartialUpdateCollectResult::FullUpdateNeeded);
|
||||
/* Second invoke should now detect no changes. */
|
||||
result = BKE_image_partial_update_collect_changes(image, partial_update_user);
|
||||
EXPECT_EQ(result, ePartialUpdateCollectResult::NoChangesDetected);
|
||||
|
||||
/* Mark full update */
|
||||
BKE_image_partial_update_mark_full_update(image);
|
||||
|
||||
/* Validate need full update followed by no changes. */
|
||||
result = BKE_image_partial_update_collect_changes(image, partial_update_user);
|
||||
EXPECT_EQ(result, ePartialUpdateCollectResult::FullUpdateNeeded);
|
||||
result = BKE_image_partial_update_collect_changes(image, partial_update_user);
|
||||
EXPECT_EQ(result, ePartialUpdateCollectResult::NoChangesDetected);
|
||||
}
|
||||
|
||||
TEST_F(ImagePartialUpdateTest, mark_single_tile)
|
||||
{
|
||||
ePartialUpdateCollectResult result;
|
||||
/* First tile should always return a full update. */
|
||||
result = BKE_image_partial_update_collect_changes(image, partial_update_user);
|
||||
EXPECT_EQ(result, ePartialUpdateCollectResult::FullUpdateNeeded);
|
||||
/* Second invoke should now detect no changes. */
|
||||
result = BKE_image_partial_update_collect_changes(image, partial_update_user);
|
||||
EXPECT_EQ(result, ePartialUpdateCollectResult::NoChangesDetected);
|
||||
|
||||
/* Mark region. */
|
||||
rcti region;
|
||||
BLI_rcti_init(®ion, 10, 20, 40, 50);
|
||||
BKE_image_partial_update_mark_region(image, image_tile, image_buffer, ®ion);
|
||||
|
||||
/* Partial Update should be available. */
|
||||
result = BKE_image_partial_update_collect_changes(image, partial_update_user);
|
||||
EXPECT_EQ(result, ePartialUpdateCollectResult::PartialChangesDetected);
|
||||
|
||||
/* Check tiles. */
|
||||
PartialUpdateRegion changed_region;
|
||||
ePartialUpdateIterResult iter_result;
|
||||
iter_result = BKE_image_partial_update_get_next_change(partial_update_user, &changed_region);
|
||||
EXPECT_EQ(iter_result, ePartialUpdateIterResult::ChangeAvailable);
|
||||
EXPECT_EQ(BLI_rcti_inside_rcti(&changed_region.region, ®ion), true);
|
||||
iter_result = BKE_image_partial_update_get_next_change(partial_update_user, &changed_region);
|
||||
EXPECT_EQ(iter_result, ePartialUpdateIterResult::Finished);
|
||||
|
||||
result = BKE_image_partial_update_collect_changes(image, partial_update_user);
|
||||
EXPECT_EQ(result, ePartialUpdateCollectResult::NoChangesDetected);
|
||||
}
|
||||
|
||||
TEST_F(ImagePartialUpdateTest, mark_unconnected_tiles)
|
||||
{
|
||||
ePartialUpdateCollectResult result;
|
||||
/* First tile should always return a full update. */
|
||||
result = BKE_image_partial_update_collect_changes(image, partial_update_user);
|
||||
EXPECT_EQ(result, ePartialUpdateCollectResult::FullUpdateNeeded);
|
||||
/* Second invoke should now detect no changes. */
|
||||
result = BKE_image_partial_update_collect_changes(image, partial_update_user);
|
||||
EXPECT_EQ(result, ePartialUpdateCollectResult::NoChangesDetected);
|
||||
|
||||
/* Mark region. */
|
||||
rcti region_a;
|
||||
BLI_rcti_init(®ion_a, 10, 20, 40, 50);
|
||||
BKE_image_partial_update_mark_region(image, image_tile, image_buffer, ®ion_a);
|
||||
rcti region_b;
|
||||
BLI_rcti_init(®ion_b, 710, 720, 740, 750);
|
||||
BKE_image_partial_update_mark_region(image, image_tile, image_buffer, ®ion_b);
|
||||
|
||||
/* Partial Update should be available. */
|
||||
result = BKE_image_partial_update_collect_changes(image, partial_update_user);
|
||||
EXPECT_EQ(result, ePartialUpdateCollectResult::PartialChangesDetected);
|
||||
|
||||
/* Check tiles. */
|
||||
PartialUpdateRegion changed_region;
|
||||
ePartialUpdateIterResult iter_result;
|
||||
iter_result = BKE_image_partial_update_get_next_change(partial_update_user, &changed_region);
|
||||
EXPECT_EQ(iter_result, ePartialUpdateIterResult::ChangeAvailable);
|
||||
EXPECT_EQ(BLI_rcti_inside_rcti(&changed_region.region, ®ion_b), true);
|
||||
iter_result = BKE_image_partial_update_get_next_change(partial_update_user, &changed_region);
|
||||
EXPECT_EQ(iter_result, ePartialUpdateIterResult::ChangeAvailable);
|
||||
EXPECT_EQ(BLI_rcti_inside_rcti(&changed_region.region, ®ion_a), true);
|
||||
iter_result = BKE_image_partial_update_get_next_change(partial_update_user, &changed_region);
|
||||
EXPECT_EQ(iter_result, ePartialUpdateIterResult::Finished);
|
||||
|
||||
result = BKE_image_partial_update_collect_changes(image, partial_update_user);
|
||||
EXPECT_EQ(result, ePartialUpdateCollectResult::NoChangesDetected);
|
||||
}
|
||||
|
||||
TEST_F(ImagePartialUpdateTest, donot_mark_outside_image)
|
||||
{
|
||||
ePartialUpdateCollectResult result;
|
||||
/* First tile should always return a full update. */
|
||||
result = BKE_image_partial_update_collect_changes(image, partial_update_user);
|
||||
EXPECT_EQ(result, ePartialUpdateCollectResult::FullUpdateNeeded);
|
||||
/* Second invoke should now detect no changes. */
|
||||
result = BKE_image_partial_update_collect_changes(image, partial_update_user);
|
||||
EXPECT_EQ(result, ePartialUpdateCollectResult::NoChangesDetected);
|
||||
|
||||
/* Mark region. */
|
||||
rcti region;
|
||||
/* Axis. */
|
||||
BLI_rcti_init(®ion, -100, 0, 50, 100);
|
||||
BKE_image_partial_update_mark_region(image, image_tile, image_buffer, ®ion);
|
||||
result = BKE_image_partial_update_collect_changes(image, partial_update_user);
|
||||
EXPECT_EQ(result, ePartialUpdateCollectResult::NoChangesDetected);
|
||||
|
||||
BLI_rcti_init(®ion, 1024, 1100, 50, 100);
|
||||
BKE_image_partial_update_mark_region(image, image_tile, image_buffer, ®ion);
|
||||
result = BKE_image_partial_update_collect_changes(image, partial_update_user);
|
||||
EXPECT_EQ(result, ePartialUpdateCollectResult::NoChangesDetected);
|
||||
|
||||
BLI_rcti_init(®ion, 50, 100, -100, 0);
|
||||
BKE_image_partial_update_mark_region(image, image_tile, image_buffer, ®ion);
|
||||
result = BKE_image_partial_update_collect_changes(image, partial_update_user);
|
||||
EXPECT_EQ(result, ePartialUpdateCollectResult::NoChangesDetected);
|
||||
|
||||
BLI_rcti_init(®ion, 50, 100, 1024, 1100);
|
||||
BKE_image_partial_update_mark_region(image, image_tile, image_buffer, ®ion);
|
||||
result = BKE_image_partial_update_collect_changes(image, partial_update_user);
|
||||
EXPECT_EQ(result, ePartialUpdateCollectResult::NoChangesDetected);
|
||||
|
||||
/* Diagonals. */
|
||||
BLI_rcti_init(®ion, -100, 0, -100, 0);
|
||||
BKE_image_partial_update_mark_region(image, image_tile, image_buffer, ®ion);
|
||||
result = BKE_image_partial_update_collect_changes(image, partial_update_user);
|
||||
EXPECT_EQ(result, ePartialUpdateCollectResult::NoChangesDetected);
|
||||
|
||||
BLI_rcti_init(®ion, -100, 0, 1024, 1100);
|
||||
BKE_image_partial_update_mark_region(image, image_tile, image_buffer, ®ion);
|
||||
result = BKE_image_partial_update_collect_changes(image, partial_update_user);
|
||||
EXPECT_EQ(result, ePartialUpdateCollectResult::NoChangesDetected);
|
||||
|
||||
BLI_rcti_init(®ion, 1024, 1100, -100, 0);
|
||||
BKE_image_partial_update_mark_region(image, image_tile, image_buffer, ®ion);
|
||||
result = BKE_image_partial_update_collect_changes(image, partial_update_user);
|
||||
EXPECT_EQ(result, ePartialUpdateCollectResult::NoChangesDetected);
|
||||
|
||||
BLI_rcti_init(®ion, 1024, 1100, 1024, 1100);
|
||||
BKE_image_partial_update_mark_region(image, image_tile, image_buffer, ®ion);
|
||||
result = BKE_image_partial_update_collect_changes(image, partial_update_user);
|
||||
EXPECT_EQ(result, ePartialUpdateCollectResult::NoChangesDetected);
|
||||
}
|
||||
|
||||
TEST_F(ImagePartialUpdateTest, mark_inside_image)
|
||||
{
|
||||
ePartialUpdateCollectResult result;
|
||||
/* First tile should always return a full update. */
|
||||
result = BKE_image_partial_update_collect_changes(image, partial_update_user);
|
||||
EXPECT_EQ(result, ePartialUpdateCollectResult::FullUpdateNeeded);
|
||||
/* Second invoke should now detect no changes. */
|
||||
result = BKE_image_partial_update_collect_changes(image, partial_update_user);
|
||||
EXPECT_EQ(result, ePartialUpdateCollectResult::NoChangesDetected);
|
||||
|
||||
/* Mark region. */
|
||||
rcti region;
|
||||
BLI_rcti_init(®ion, 0, 1, 0, 1);
|
||||
BKE_image_partial_update_mark_region(image, image_tile, image_buffer, ®ion);
|
||||
result = BKE_image_partial_update_collect_changes(image, partial_update_user);
|
||||
EXPECT_EQ(result, ePartialUpdateCollectResult::PartialChangesDetected);
|
||||
|
||||
result = BKE_image_partial_update_collect_changes(image, partial_update_user);
|
||||
EXPECT_EQ(result, ePartialUpdateCollectResult::NoChangesDetected);
|
||||
BLI_rcti_init(®ion, 1023, 1024, 0, 1);
|
||||
BKE_image_partial_update_mark_region(image, image_tile, image_buffer, ®ion);
|
||||
result = BKE_image_partial_update_collect_changes(image, partial_update_user);
|
||||
EXPECT_EQ(result, ePartialUpdateCollectResult::PartialChangesDetected);
|
||||
|
||||
result = BKE_image_partial_update_collect_changes(image, partial_update_user);
|
||||
EXPECT_EQ(result, ePartialUpdateCollectResult::NoChangesDetected);
|
||||
BLI_rcti_init(®ion, 1023, 1024, 1023, 1024);
|
||||
BKE_image_partial_update_mark_region(image, image_tile, image_buffer, ®ion);
|
||||
result = BKE_image_partial_update_collect_changes(image, partial_update_user);
|
||||
EXPECT_EQ(result, ePartialUpdateCollectResult::PartialChangesDetected);
|
||||
|
||||
result = BKE_image_partial_update_collect_changes(image, partial_update_user);
|
||||
EXPECT_EQ(result, ePartialUpdateCollectResult::NoChangesDetected);
|
||||
BLI_rcti_init(®ion, 1023, 1024, 0, 1);
|
||||
BKE_image_partial_update_mark_region(image, image_tile, image_buffer, ®ion);
|
||||
result = BKE_image_partial_update_collect_changes(image, partial_update_user);
|
||||
EXPECT_EQ(result, ePartialUpdateCollectResult::PartialChangesDetected);
|
||||
}
|
||||
|
||||
TEST_F(ImagePartialUpdateTest, sequential_mark_region)
|
||||
{
|
||||
ePartialUpdateCollectResult result;
|
||||
/* First tile should always return a full update. */
|
||||
result = BKE_image_partial_update_collect_changes(image, partial_update_user);
|
||||
EXPECT_EQ(result, ePartialUpdateCollectResult::FullUpdateNeeded);
|
||||
/* Second invoke should now detect no changes. */
|
||||
result = BKE_image_partial_update_collect_changes(image, partial_update_user);
|
||||
EXPECT_EQ(result, ePartialUpdateCollectResult::NoChangesDetected);
|
||||
|
||||
{
|
||||
/* Mark region. */
|
||||
rcti region;
|
||||
BLI_rcti_init(®ion, 10, 20, 40, 50);
|
||||
BKE_image_partial_update_mark_region(image, image_tile, image_buffer, ®ion);
|
||||
|
||||
/* Partial Update should be available. */
|
||||
result = BKE_image_partial_update_collect_changes(image, partial_update_user);
|
||||
EXPECT_EQ(result, ePartialUpdateCollectResult::PartialChangesDetected);
|
||||
|
||||
/* Check tiles. */
|
||||
PartialUpdateRegion changed_region;
|
||||
ePartialUpdateIterResult iter_result;
|
||||
iter_result = BKE_image_partial_update_get_next_change(partial_update_user, &changed_region);
|
||||
EXPECT_EQ(iter_result, ePartialUpdateIterResult::ChangeAvailable);
|
||||
EXPECT_EQ(BLI_rcti_inside_rcti(&changed_region.region, ®ion), true);
|
||||
iter_result = BKE_image_partial_update_get_next_change(partial_update_user, &changed_region);
|
||||
EXPECT_EQ(iter_result, ePartialUpdateIterResult::Finished);
|
||||
|
||||
result = BKE_image_partial_update_collect_changes(image, partial_update_user);
|
||||
EXPECT_EQ(result, ePartialUpdateCollectResult::NoChangesDetected);
|
||||
}
|
||||
|
||||
{
|
||||
/* Mark different region. */
|
||||
rcti region;
|
||||
BLI_rcti_init(®ion, 710, 720, 740, 750);
|
||||
BKE_image_partial_update_mark_region(image, image_tile, image_buffer, ®ion);
|
||||
|
||||
/* Partial Update should be available. */
|
||||
result = BKE_image_partial_update_collect_changes(image, partial_update_user);
|
||||
EXPECT_EQ(result, ePartialUpdateCollectResult::PartialChangesDetected);
|
||||
|
||||
/* Check tiles. */
|
||||
PartialUpdateRegion changed_region;
|
||||
ePartialUpdateIterResult iter_result;
|
||||
iter_result = BKE_image_partial_update_get_next_change(partial_update_user, &changed_region);
|
||||
EXPECT_EQ(iter_result, ePartialUpdateIterResult::ChangeAvailable);
|
||||
EXPECT_EQ(BLI_rcti_inside_rcti(&changed_region.region, ®ion), true);
|
||||
iter_result = BKE_image_partial_update_get_next_change(partial_update_user, &changed_region);
|
||||
EXPECT_EQ(iter_result, ePartialUpdateIterResult::Finished);
|
||||
|
||||
result = BKE_image_partial_update_collect_changes(image, partial_update_user);
|
||||
EXPECT_EQ(result, ePartialUpdateCollectResult::NoChangesDetected);
|
||||
}
|
||||
}
|
||||
|
||||
TEST_F(ImagePartialUpdateTest, mark_multiple_chunks)
|
||||
{
|
||||
ePartialUpdateCollectResult result;
|
||||
/* First tile should always return a full update. */
|
||||
result = BKE_image_partial_update_collect_changes(image, partial_update_user);
|
||||
EXPECT_EQ(result, ePartialUpdateCollectResult::FullUpdateNeeded);
|
||||
/* Second invoke should now detect no changes. */
|
||||
result = BKE_image_partial_update_collect_changes(image, partial_update_user);
|
||||
EXPECT_EQ(result, ePartialUpdateCollectResult::NoChangesDetected);
|
||||
|
||||
/* Mark region. */
|
||||
rcti region;
|
||||
BLI_rcti_init(®ion, 300, 700, 300, 700);
|
||||
BKE_image_partial_update_mark_region(image, image_tile, image_buffer, ®ion);
|
||||
|
||||
/* Partial Update should be available. */
|
||||
result = BKE_image_partial_update_collect_changes(image, partial_update_user);
|
||||
EXPECT_EQ(result, ePartialUpdateCollectResult::PartialChangesDetected);
|
||||
|
||||
/* Check tiles. */
|
||||
PartialUpdateRegion changed_region;
|
||||
int num_chunks_found = 0;
|
||||
while (BKE_image_partial_update_get_next_change(partial_update_user, &changed_region) ==
|
||||
ePartialUpdateIterResult::ChangeAvailable) {
|
||||
BLI_rcti_isect(&changed_region.region, ®ion, nullptr);
|
||||
num_chunks_found++;
|
||||
}
|
||||
EXPECT_EQ(num_chunks_found, 4);
|
||||
}
|
||||
|
||||
TEST_F(ImagePartialUpdateTest, iterator)
|
||||
{
|
||||
PartialUpdateChecker<NoTileData> checker(image, &image_user, partial_update_user);
|
||||
/* First tile should always return a full update. */
|
||||
PartialUpdateChecker<NoTileData>::CollectResult changes = checker.collect_changes();
|
||||
EXPECT_EQ(changes.get_result_code(), ePartialUpdateCollectResult::FullUpdateNeeded);
|
||||
/* Second invoke should now detect no changes. */
|
||||
changes = checker.collect_changes();
|
||||
EXPECT_EQ(changes.get_result_code(), ePartialUpdateCollectResult::NoChangesDetected);
|
||||
|
||||
/* Mark region. */
|
||||
rcti region;
|
||||
BLI_rcti_init(®ion, 300, 700, 300, 700);
|
||||
BKE_image_partial_update_mark_region(image, image_tile, image_buffer, ®ion);
|
||||
|
||||
/* Partial Update should be available. */
|
||||
changes = checker.collect_changes();
|
||||
EXPECT_EQ(changes.get_result_code(), ePartialUpdateCollectResult::PartialChangesDetected);
|
||||
|
||||
/* Check tiles. */
|
||||
int num_tiles_found = 0;
|
||||
while (changes.get_next_change() == ePartialUpdateIterResult::ChangeAvailable) {
|
||||
BLI_rcti_isect(&changes.changed_region.region, ®ion, nullptr);
|
||||
num_tiles_found++;
|
||||
}
|
||||
EXPECT_EQ(num_tiles_found, 4);
|
||||
}
|
||||
|
||||
} // namespace blender::bke::image::partial_update
|
@@ -611,8 +611,14 @@ static void image_rect_update(void *rjv, RenderResult *rr, volatile rcti *renrec
|
||||
ED_draw_imbuf_method(ibuf) != IMAGE_DRAW_METHOD_GLSL) {
|
||||
image_buffer_rect_update(rj, rr, ibuf, &rj->iuser, &tile_rect, offset_x, offset_y, viewname);
|
||||
}
|
||||
BKE_image_update_gputexture_delayed(
|
||||
ima, ibuf, offset_x, offset_y, BLI_rcti_size_x(&tile_rect), BLI_rcti_size_y(&tile_rect));
|
||||
ImageTile *image_tile = BKE_image_get_tile(ima, 0);
|
||||
BKE_image_update_gputexture_delayed(ima,
|
||||
image_tile,
|
||||
ibuf,
|
||||
offset_x,
|
||||
offset_y,
|
||||
BLI_rcti_size_x(&tile_rect),
|
||||
BLI_rcti_size_y(&tile_rect));
|
||||
|
||||
/* make jobs timer to send notifier */
|
||||
*(rj->do_update) = true;
|
||||
|
@@ -142,10 +142,20 @@ typedef enum eImageTextureResolution {
|
||||
IMA_TEXTURE_RESOLUTION_LEN
|
||||
} eImageTextureResolution;
|
||||
|
||||
/* Defined in BKE_image.h. */
|
||||
struct PartialUpdateRegister;
|
||||
struct PartialUpdateUser;
|
||||
|
||||
typedef struct Image_Runtime {
|
||||
/* Mutex used to guarantee thread-safe access to the cached ImBuf of the corresponding image ID.
|
||||
*/
|
||||
void *cache_mutex;
|
||||
|
||||
/** \brief Register containing partial updates. */
|
||||
struct PartialUpdateRegister *partial_update_register;
|
||||
/** \brief Partial update user for GPUTextures stored inside the Image. */
|
||||
struct PartialUpdateUser *partial_update_user;
|
||||
|
||||
} Image_Runtime;
|
||||
|
||||
typedef struct Image {
|
||||
@@ -171,8 +181,6 @@ typedef struct Image {
|
||||
int lastframe;
|
||||
|
||||
/* GPU texture flag. */
|
||||
/* Contains `ImagePartialRefresh`. */
|
||||
ListBase gpu_refresh_areas;
|
||||
int gpuframenr;
|
||||
short gpuflag;
|
||||
short gpu_pass;
|
||||
@@ -247,15 +255,13 @@ enum {
|
||||
enum {
|
||||
/** GPU texture needs to be refreshed. */
|
||||
IMA_GPU_REFRESH = (1 << 0),
|
||||
/** GPU texture needs to be partially refreshed. */
|
||||
IMA_GPU_PARTIAL_REFRESH = (1 << 1),
|
||||
/** All mipmap levels in OpenGL texture set? */
|
||||
IMA_GPU_MIPMAP_COMPLETE = (1 << 2),
|
||||
IMA_GPU_MIPMAP_COMPLETE = (1 << 1),
|
||||
/* Reuse the max resolution textures as they fit in the limited scale. */
|
||||
IMA_GPU_REUSE_MAX_RESOLUTION = (1 << 3),
|
||||
IMA_GPU_REUSE_MAX_RESOLUTION = (1 << 2),
|
||||
/* Has any limited scale textures been allocated.
|
||||
* Adds additional checks to reuse max resolution images when they fit inside limited scale. */
|
||||
IMA_GPU_HAS_LIMITED_SCALE_TEXTURES = (1 << 4),
|
||||
IMA_GPU_HAS_LIMITED_SCALE_TEXTURES = (1 << 3),
|
||||
};
|
||||
|
||||
/* Image.source, where the image comes from */
|
||||
|
Reference in New Issue
Block a user