Compare commits
118 Commits
bevelv2
...
temp-gpu-i
Author | SHA1 | Date | |
---|---|---|---|
b6ac95145c | |||
79c90bd49f | |||
6016dbb1a7 | |||
c453aaa0b2 | |||
d8b811cfe9 | |||
647896bc60 | |||
1557fa77d0 | |||
302fe4f3ad | |||
1b52be9893 | |||
26c3ded368 | |||
efa4023e0a | |||
eeee85017e | |||
ef40b0f136 | |||
7f0f7287d2 | |||
b72f383ad5 | |||
b95d1b308f | |||
![]() |
38084d7df1 | ||
2a31772198 | |||
85d966808f | |||
da9e50646c | |||
320639740d | |||
5f9a6ac183 | |||
42ae2e4c6c | |||
edac72ebb9 | |||
a48b3012d2 | |||
6d15b4465d | |||
40f35851f5 | |||
45f2800e2d | |||
1fbcb2ba85 | |||
73ab5e5b63 | |||
1a85d2762a | |||
ef602a1ff9 | |||
d8c08f3633 | |||
a3a646feb9 | |||
b36c6f7f32 | |||
30290efaeb | |||
c5edefe496 | |||
315e51ad77 | |||
8c71b6b9ec | |||
08bd773383 | |||
6739b4756d | |||
bc77184563 | |||
cb54fae36c | |||
86ca5f5e94 | |||
a27bc7c372 | |||
a37147347d | |||
4399a01de2 | |||
a5ed6f9898 | |||
484eb6b6cb | |||
915464b697 | |||
51f9c2fdd4 | |||
cbf56b5916 | |||
401ad85078 | |||
230565fbf0 | |||
a9ef394f6e | |||
84869b551f | |||
39e4dd079b | |||
2c914197c2 | |||
6a37dd9622 | |||
fddaa022af | |||
b717d0711c | |||
1d8c7b5fec | |||
1d30d3d57e | |||
d231f4059a | |||
b7328397aa | |||
23ac11c0c7 | |||
acd8cf549e | |||
05a60c5727 | |||
d3f10f768a | |||
c776f91480 | |||
387dbd65ac | |||
b07a6c2067 | |||
482b8fb879 | |||
decdde81ff | |||
da855bfa2e | |||
36a1694e0e | |||
160720764d | |||
d5663dfce1 | |||
cb4c62eee6 | |||
63b96f4f0e | |||
496cd70b02 | |||
f94e3ba72f | |||
f11a80d83b | |||
8f36a19cc0 | |||
6cda987f59 | |||
b30f101fc0 | |||
24a5f4d6f8 | |||
7ffe9d56a1 | |||
aac6bf9938 | |||
664adf9e76 | |||
378db2e805 | |||
ea768ff210 | |||
a3e662ea91 | |||
006cf67f5c | |||
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
|
||||
@@ -561,6 +563,7 @@ struct GPUTexture *BKE_image_get_gpu_tilemap(struct Image *image,
|
||||
* Is the alpha of the `GPUTexture` for a given image/ibuf premultiplied.
|
||||
*/
|
||||
bool BKE_image_has_gpu_texture_premultiplied_alpha(struct Image *image, struct ImBuf *ibuf);
|
||||
|
||||
/**
|
||||
* Partial update of texture for texture painting.
|
||||
* This is often much quicker than fully updating the texture for high resolution images.
|
||||
@@ -572,8 +575,14 @@ void BKE_image_update_gputexture(
|
||||
* 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);
|
||||
|
||||
/**
|
||||
* Called on entering and exiting texture paint mode,
|
||||
* temporary disabling/enabling mipmapping on all images for quick texture
|
||||
@@ -591,6 +600,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
|
||||
|
298
source/blender/blenkernel/BKE_image_partial_update.hh
Normal file
298
source/blender/blenkernel/BKE_image_partial_update.hh
Normal file
@@ -0,0 +1,298 @@
|
||||
/*
|
||||
* 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 = {0};
|
||||
|
||||
/**
|
||||
* \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)
|
||||
{
|
||||
if (image_user != nullptr) {
|
||||
this->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
|
@@ -165,6 +165,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
|
||||
@@ -822,6 +823,7 @@ if(WITH_GTESTS)
|
||||
intern/cryptomatte_test.cc
|
||||
intern/fcurve_test.cc
|
||||
intern/idprop_serialize_test.cc
|
||||
intern/image_partial_update_test.cc
|
||||
intern/lattice_deform_test.cc
|
||||
intern/layer_test.cc
|
||||
intern/lib_id_remapper_test.cc
|
||||
|
@@ -134,6 +134,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)
|
||||
@@ -213,10 +229,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,
|
||||
@@ -321,7 +335,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++) {
|
||||
@@ -401,7 +416,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;
|
||||
};
|
||||
|
||||
bool BKE_image_has_gpu_texture_premultiplied_alpha(Image *image, ImBuf *ibuf)
|
||||
{
|
||||
if (image) {
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -903,87 +930,33 @@ 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)
|
||||
/* Mark areas on the GPUTexture that needs to be updated. The areas are marked in chunks.
|
||||
* 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 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 +989,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 = MEM_new<PartialUpdateRegisterImpl>(
|
||||
__func__);
|
||||
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 = MEM_new<PartialUpdateUserImpl>(__func__);
|
||||
|
||||
#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);
|
||||
MEM_delete(user_impl);
|
||||
}
|
||||
|
||||
/* --- 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) {
|
||||
MEM_delete(partial_update_register);
|
||||
}
|
||||
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
|
@@ -404,6 +404,7 @@ void invert_m4_m4_safe_ortho(float Ainv[4][4], const float A[4][4]);
|
||||
|
||||
void scale_m3_fl(float R[3][3], float scale);
|
||||
void scale_m4_fl(float R[4][4], float scale);
|
||||
void scale_m4_v2(float R[4][4], const float scale[2]);
|
||||
|
||||
/**
|
||||
* This computes the overall volume scale factor of a transformation matrix.
|
||||
|
@@ -2296,6 +2296,17 @@ void scale_m4_fl(float R[4][4], float scale)
|
||||
R[3][0] = R[3][1] = R[3][2] = 0.0;
|
||||
}
|
||||
|
||||
void scale_m4_v2(float R[4][4], const float scale[2])
|
||||
{
|
||||
R[0][0] = scale[0];
|
||||
R[1][1] = scale[1];
|
||||
R[2][2] = R[3][3] = 1.0;
|
||||
R[0][1] = R[0][2] = R[0][3] = 0.0;
|
||||
R[1][0] = R[1][2] = R[1][3] = 0.0;
|
||||
R[2][0] = R[2][1] = R[2][3] = 0.0;
|
||||
R[3][0] = R[3][1] = R[3][2] = 0.0;
|
||||
}
|
||||
|
||||
void translate_m4(float mat[4][4], float Tx, float Ty, float Tz)
|
||||
{
|
||||
mat[3][0] += (Tx * mat[0][0] + Ty * mat[1][0] + Tz * mat[2][0]);
|
||||
|
@@ -227,11 +227,17 @@ set(SRC
|
||||
engines/eevee/eevee_lut.h
|
||||
engines/eevee/eevee_private.h
|
||||
engines/external/external_engine.h
|
||||
engines/image/image_drawing_mode_image_space.hh
|
||||
engines/image/image_batches.hh
|
||||
engines/image/image_drawing_mode.hh
|
||||
engines/image/image_engine.h
|
||||
engines/image/image_instance_data.hh
|
||||
engines/image/image_partial_updater.hh
|
||||
engines/image/image_private.hh
|
||||
engines/image/image_shader_params.hh
|
||||
engines/image/image_space_image.hh
|
||||
engines/image/image_space_node.hh
|
||||
engines/image/image_space.hh
|
||||
engines/image/image_wrappers.hh
|
||||
engines/workbench/workbench_engine.h
|
||||
engines/workbench/workbench_private.h
|
||||
engines/workbench/workbench_shader_shared.h
|
||||
|
106
source/blender/draw/engines/image/image_batches.hh
Normal file
106
source/blender/draw/engines/image/image_batches.hh
Normal file
@@ -0,0 +1,106 @@
|
||||
/*
|
||||
* 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 draw_engine
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "image_texture_info.hh"
|
||||
|
||||
/** \brief Create GPUBatch for a IMAGE_ScreenSpaceTextureInfo. */
|
||||
class BatchUpdater {
|
||||
TextureInfo &info;
|
||||
|
||||
GPUVertFormat format = {0};
|
||||
int pos_id;
|
||||
int uv_id;
|
||||
|
||||
public:
|
||||
BatchUpdater(TextureInfo &info) : info(info)
|
||||
{
|
||||
}
|
||||
|
||||
void update_batch()
|
||||
{
|
||||
ensure_clear_batch();
|
||||
ensure_format();
|
||||
init_batch();
|
||||
}
|
||||
|
||||
void discard_batch()
|
||||
{
|
||||
GPU_BATCH_DISCARD_SAFE(info.batch);
|
||||
}
|
||||
|
||||
private:
|
||||
void ensure_clear_batch()
|
||||
{
|
||||
GPU_BATCH_CLEAR_SAFE(info.batch);
|
||||
if (info.batch == nullptr) {
|
||||
info.batch = GPU_batch_calloc();
|
||||
}
|
||||
}
|
||||
|
||||
void init_batch()
|
||||
{
|
||||
GPUVertBuf *vbo = create_vbo();
|
||||
GPU_batch_init_ex(info.batch, GPU_PRIM_TRI_FAN, vbo, nullptr, GPU_BATCH_OWNS_VBO);
|
||||
}
|
||||
|
||||
GPUVertBuf *create_vbo()
|
||||
{
|
||||
GPUVertBuf *vbo = GPU_vertbuf_create_with_format(&format);
|
||||
GPU_vertbuf_data_alloc(vbo, 4);
|
||||
float pos[4][2];
|
||||
fill_tri_fan_from_rctf(pos, info.clipping_bounds);
|
||||
float uv[4][2];
|
||||
fill_tri_fan_from_rctf(uv, info.uv_bounds);
|
||||
|
||||
for (int i = 0; i < 4; i++) {
|
||||
GPU_vertbuf_attr_set(vbo, pos_id, i, pos[i]);
|
||||
GPU_vertbuf_attr_set(vbo, uv_id, i, uv[i]);
|
||||
}
|
||||
|
||||
return vbo;
|
||||
}
|
||||
|
||||
static void fill_tri_fan_from_rctf(float result[4][2], rctf &rect)
|
||||
{
|
||||
result[0][0] = rect.xmin;
|
||||
result[0][1] = rect.ymin;
|
||||
result[1][0] = rect.xmax;
|
||||
result[1][1] = rect.ymin;
|
||||
result[2][0] = rect.xmax;
|
||||
result[2][1] = rect.ymax;
|
||||
result[3][0] = rect.xmin;
|
||||
result[3][1] = rect.ymax;
|
||||
}
|
||||
|
||||
void ensure_format()
|
||||
{
|
||||
if (format.attr_len == 0) {
|
||||
GPU_vertformat_attr_add(&format, "pos", GPU_COMP_F32, 2, GPU_FETCH_FLOAT);
|
||||
GPU_vertformat_attr_add(&format, "uv", GPU_COMP_F32, 2, GPU_FETCH_FLOAT);
|
||||
|
||||
pos_id = GPU_vertformat_attr_id_get(&format, "pos");
|
||||
uv_id = GPU_vertformat_attr_id_get(&format, "uv");
|
||||
}
|
||||
}
|
||||
};
|
422
source/blender/draw/engines/image/image_drawing_mode.hh
Normal file
422
source/blender/draw/engines/image/image_drawing_mode.hh
Normal file
@@ -0,0 +1,422 @@
|
||||
/*
|
||||
* 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 draw_engine
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "BKE_image_partial_update.hh"
|
||||
|
||||
#include "IMB_imbuf_types.h"
|
||||
|
||||
#include "image_batches.hh"
|
||||
#include "image_private.hh"
|
||||
#include "image_wrappers.hh"
|
||||
|
||||
namespace blender::draw::image_engine {
|
||||
|
||||
constexpr float EPSILON_UV_BOUNDS = 0.00001f;
|
||||
|
||||
/**
|
||||
* \brief Screen space method using a single texture spawning the whole screen.
|
||||
*/
|
||||
struct OneTextureMethod {
|
||||
IMAGE_InstanceData *instance_data;
|
||||
|
||||
OneTextureMethod(IMAGE_InstanceData *instance_data) : instance_data(instance_data)
|
||||
{
|
||||
}
|
||||
|
||||
/** \brief Update the texture slot uv and screen space bounds. */
|
||||
void update_screen_space_bounds(const ARegion *region)
|
||||
{
|
||||
/* Create a single texture that covers the visible screen space. */
|
||||
BLI_rctf_init(
|
||||
&instance_data->texture_infos[0].clipping_bounds, 0, region->winx, 0, region->winy);
|
||||
instance_data->texture_infos[0].visible = true;
|
||||
|
||||
/* Mark the other textures as invalid. */
|
||||
for (int i = 1; i < SCREEN_SPACE_DRAWING_MODE_TEXTURE_LEN; i++) {
|
||||
BLI_rctf_init_minmax(&instance_data->texture_infos[i].clipping_bounds);
|
||||
instance_data->texture_infos[i].visible = false;
|
||||
}
|
||||
}
|
||||
|
||||
void update_uv_bounds(const ARegion *region)
|
||||
{
|
||||
TextureInfo &info = instance_data->texture_infos[0];
|
||||
if (!BLI_rctf_compare(&info.uv_bounds, ®ion->v2d.cur, EPSILON_UV_BOUNDS)) {
|
||||
info.uv_bounds = region->v2d.cur;
|
||||
info.dirty = true;
|
||||
}
|
||||
|
||||
/* Mark the other textures as invalid. */
|
||||
for (int i = 1; i < SCREEN_SPACE_DRAWING_MODE_TEXTURE_LEN; i++) {
|
||||
BLI_rctf_init_minmax(&instance_data->texture_infos[i].clipping_bounds);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
using namespace blender::bke::image::partial_update;
|
||||
|
||||
template<typename TextureMethod> class ScreenSpaceDrawingMode : public AbstractDrawingMode {
|
||||
private:
|
||||
DRWPass *create_image_pass() const
|
||||
{
|
||||
/* Write depth is needed for background overlay rendering. Near depth is used for
|
||||
* transparency checker and Far depth is used for indicating the image size. */
|
||||
DRWState state = static_cast<DRWState>(DRW_STATE_WRITE_COLOR | DRW_STATE_WRITE_DEPTH |
|
||||
DRW_STATE_DEPTH_ALWAYS | DRW_STATE_BLEND_ALPHA_PREMUL);
|
||||
return DRW_pass_create("Image", state);
|
||||
}
|
||||
|
||||
void add_shgroups(const IMAGE_InstanceData *instance_data) const
|
||||
{
|
||||
const ShaderParameters &sh_params = instance_data->sh_params;
|
||||
GPUShader *shader = IMAGE_shader_image_get(false);
|
||||
|
||||
DRWShadingGroup *shgrp = DRW_shgroup_create(shader, instance_data->passes.image_pass);
|
||||
DRW_shgroup_uniform_vec2_copy(shgrp, "farNearDistances", sh_params.far_near);
|
||||
DRW_shgroup_uniform_vec4_copy(shgrp, "color", ShaderParameters::color);
|
||||
DRW_shgroup_uniform_vec4_copy(shgrp, "shuffle", sh_params.shuffle);
|
||||
DRW_shgroup_uniform_int_copy(shgrp, "drawFlags", sh_params.flags);
|
||||
DRW_shgroup_uniform_bool_copy(shgrp, "imgPremultiplied", sh_params.use_premul_alpha);
|
||||
DRW_shgroup_uniform_vec2_copy(shgrp, "maxUv", instance_data->max_uv);
|
||||
float image_mat[4][4];
|
||||
unit_m4(image_mat);
|
||||
for (int i = 0; i < SCREEN_SPACE_DRAWING_MODE_TEXTURE_LEN; i++) {
|
||||
const TextureInfo &info = instance_data->texture_infos[i];
|
||||
if (!info.visible) {
|
||||
continue;
|
||||
}
|
||||
/*
|
||||
Should be space relative translation.
|
||||
image_mat[0][0] = info.clipping_bounds.xmax;
|
||||
image_mat[1][1] = info.clipping_bounds.ymax;
|
||||
*/
|
||||
|
||||
DRWShadingGroup *shgrp_sub = DRW_shgroup_create_sub(shgrp);
|
||||
DRW_shgroup_uniform_texture_ex(shgrp_sub, "imageTexture", info.texture, GPU_SAMPLER_DEFAULT);
|
||||
DRW_shgroup_call_obmat(shgrp_sub, info.batch, image_mat);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* \brief Update GPUTextures for drawing the image.
|
||||
*
|
||||
* GPUTextures that are marked dirty are rebuild. GPUTextures that aren't marked dirty are
|
||||
* updated with changed region of the image.
|
||||
*/
|
||||
void update_textures(IMAGE_InstanceData &instance_data,
|
||||
Image *image,
|
||||
ImageUser *image_user) const
|
||||
{
|
||||
PartialUpdateChecker<ImageTileData> checker(
|
||||
image, image_user, instance_data.partial_update.user);
|
||||
PartialUpdateChecker<ImageTileData>::CollectResult changes = checker.collect_changes();
|
||||
|
||||
switch (changes.get_result_code()) {
|
||||
case ePartialUpdateCollectResult::FullUpdateNeeded:
|
||||
instance_data.mark_all_texture_slots_dirty();
|
||||
break;
|
||||
case ePartialUpdateCollectResult::NoChangesDetected:
|
||||
break;
|
||||
case ePartialUpdateCollectResult::PartialChangesDetected:
|
||||
/* Partial update when wrap repeat is enabled is not supported. */
|
||||
if (instance_data.flags.do_tile_drawing) {
|
||||
instance_data.mark_all_texture_slots_dirty();
|
||||
}
|
||||
else {
|
||||
do_partial_update(changes, instance_data);
|
||||
}
|
||||
break;
|
||||
}
|
||||
do_full_update_for_dirty_textures(instance_data, image_user);
|
||||
}
|
||||
|
||||
void do_partial_update(PartialUpdateChecker<ImageTileData>::CollectResult &iterator,
|
||||
IMAGE_InstanceData &instance_data) const
|
||||
{
|
||||
while (iterator.get_next_change() == ePartialUpdateIterResult::ChangeAvailable) {
|
||||
/* Quick exit when tile_buffer isn't availble. */
|
||||
if (iterator.tile_data.tile_buffer == nullptr) {
|
||||
continue;
|
||||
}
|
||||
ensure_float_buffer(*iterator.tile_data.tile_buffer);
|
||||
const float tile_width = static_cast<float>(iterator.tile_data.tile_buffer->x);
|
||||
const float tile_height = static_cast<float>(iterator.tile_data.tile_buffer->y);
|
||||
|
||||
for (int i = 0; i < SCREEN_SPACE_DRAWING_MODE_TEXTURE_LEN; i++) {
|
||||
const TextureInfo &info = instance_data.texture_infos[i];
|
||||
/* Dirty images will receive a full update. No need to do a partial one now. */
|
||||
if (info.dirty) {
|
||||
continue;
|
||||
}
|
||||
if (!info.visible) {
|
||||
continue;
|
||||
}
|
||||
GPUTexture *texture = info.texture;
|
||||
const float texture_width = GPU_texture_width(texture);
|
||||
const float texture_height = GPU_texture_height(texture);
|
||||
// TODO
|
||||
// early bound check.
|
||||
ImageTileWrapper tile_accessor(iterator.tile_data.tile);
|
||||
float tile_offset_x = static_cast<float>(tile_accessor.get_tile_x_offset());
|
||||
float tile_offset_y = static_cast<float>(tile_accessor.get_tile_y_offset());
|
||||
rcti *changed_region_in_texel_space = &iterator.changed_region.region;
|
||||
rctf changed_region_in_uv_space;
|
||||
BLI_rctf_init(&changed_region_in_uv_space,
|
||||
static_cast<float>(changed_region_in_texel_space->xmin) /
|
||||
static_cast<float>(iterator.tile_data.tile_buffer->x) +
|
||||
tile_offset_x,
|
||||
static_cast<float>(changed_region_in_texel_space->xmax) /
|
||||
static_cast<float>(iterator.tile_data.tile_buffer->x) +
|
||||
tile_offset_x,
|
||||
static_cast<float>(changed_region_in_texel_space->ymin) /
|
||||
static_cast<float>(iterator.tile_data.tile_buffer->y) +
|
||||
tile_offset_y,
|
||||
static_cast<float>(changed_region_in_texel_space->ymax) /
|
||||
static_cast<float>(iterator.tile_data.tile_buffer->y) +
|
||||
tile_offset_y);
|
||||
rctf changed_overlapping_region_in_uv_space;
|
||||
const bool region_overlap = BLI_rctf_isect(
|
||||
&info.uv_bounds, &changed_region_in_uv_space, &changed_overlapping_region_in_uv_space);
|
||||
if (!region_overlap) {
|
||||
continue;
|
||||
}
|
||||
// convert the overlapping region to texel space and to ss_pixel space...
|
||||
// TODO: first convert to ss_pixel space as integer based. and from there go back to texel
|
||||
// space. But perhaps this isn't needed and we could use an extraction offset somehow.
|
||||
rcti gpu_texture_region_to_update;
|
||||
BLI_rcti_init(&gpu_texture_region_to_update,
|
||||
floor((changed_overlapping_region_in_uv_space.xmin - info.uv_bounds.xmin) *
|
||||
texture_width / BLI_rctf_size_x(&info.uv_bounds)),
|
||||
floor((changed_overlapping_region_in_uv_space.xmax - info.uv_bounds.xmin) *
|
||||
texture_width / BLI_rctf_size_x(&info.uv_bounds)),
|
||||
ceil((changed_overlapping_region_in_uv_space.ymin - info.uv_bounds.ymin) *
|
||||
texture_height / BLI_rctf_size_y(&info.uv_bounds)),
|
||||
ceil((changed_overlapping_region_in_uv_space.ymax - info.uv_bounds.ymin) *
|
||||
texture_height / BLI_rctf_size_y(&info.uv_bounds)));
|
||||
|
||||
rcti tile_region_to_extract;
|
||||
BLI_rcti_init(
|
||||
&tile_region_to_extract,
|
||||
floor((changed_overlapping_region_in_uv_space.xmin - tile_offset_x) * tile_width),
|
||||
floor((changed_overlapping_region_in_uv_space.xmax - tile_offset_x) * tile_width),
|
||||
ceil((changed_overlapping_region_in_uv_space.ymin - tile_offset_y) * tile_height),
|
||||
ceil((changed_overlapping_region_in_uv_space.ymax - tile_offset_y) * tile_height));
|
||||
|
||||
// Create an image buffer with a size
|
||||
// extract and scale into an imbuf
|
||||
const int texture_region_width = BLI_rcti_size_x(&gpu_texture_region_to_update);
|
||||
const int texture_region_height = BLI_rcti_size_y(&gpu_texture_region_to_update);
|
||||
|
||||
ImBuf extracted_buffer;
|
||||
IMB_initImBuf(
|
||||
&extracted_buffer, texture_region_width, texture_region_height, 32, IB_rectfloat);
|
||||
|
||||
int offset = 0;
|
||||
ImBuf *tile_buffer = iterator.tile_data.tile_buffer;
|
||||
for (int y = gpu_texture_region_to_update.ymin; y < gpu_texture_region_to_update.ymax;
|
||||
y++) {
|
||||
float yf = y / (float)texture_height;
|
||||
float v = info.uv_bounds.ymax * yf + info.uv_bounds.ymin * (1.0 - yf) - tile_offset_y;
|
||||
for (int x = gpu_texture_region_to_update.xmin; x < gpu_texture_region_to_update.xmax;
|
||||
x++) {
|
||||
float xf = x / (float)texture_width;
|
||||
float u = info.uv_bounds.xmax * xf + info.uv_bounds.xmin * (1.0 - xf) - tile_offset_x;
|
||||
nearest_interpolation_color(tile_buffer,
|
||||
nullptr,
|
||||
&extracted_buffer.rect_float[offset * 4],
|
||||
u * tile_buffer->x,
|
||||
v * tile_buffer->y);
|
||||
offset++;
|
||||
}
|
||||
}
|
||||
|
||||
GPU_texture_update_sub(texture,
|
||||
GPU_DATA_FLOAT,
|
||||
extracted_buffer.rect_float,
|
||||
gpu_texture_region_to_update.xmin,
|
||||
gpu_texture_region_to_update.ymin,
|
||||
0,
|
||||
extracted_buffer.x,
|
||||
extracted_buffer.y,
|
||||
0);
|
||||
imb_freerectImbuf_all(&extracted_buffer);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void do_full_update_for_dirty_textures(IMAGE_InstanceData &instance_data,
|
||||
const ImageUser *image_user) const
|
||||
{
|
||||
for (int i = 0; i < SCREEN_SPACE_DRAWING_MODE_TEXTURE_LEN; i++) {
|
||||
TextureInfo &info = instance_data.texture_infos[i];
|
||||
if (!info.dirty) {
|
||||
continue;
|
||||
}
|
||||
if (!info.visible) {
|
||||
continue;
|
||||
}
|
||||
do_full_update_gpu_texture(info, instance_data, image_user);
|
||||
}
|
||||
}
|
||||
|
||||
void do_full_update_gpu_texture(TextureInfo &info,
|
||||
IMAGE_InstanceData &instance_data,
|
||||
const ImageUser *image_user) const
|
||||
{
|
||||
|
||||
ImBuf texture_buffer;
|
||||
const int texture_width = GPU_texture_width(info.texture);
|
||||
const int texture_height = GPU_texture_height(info.texture);
|
||||
IMB_initImBuf(&texture_buffer, texture_width, texture_height, 0, IB_rectfloat);
|
||||
ImageUser tile_user = {0};
|
||||
if (image_user) {
|
||||
tile_user = *image_user;
|
||||
}
|
||||
|
||||
void *lock;
|
||||
|
||||
Image *image = instance_data.image;
|
||||
LISTBASE_FOREACH (ImageTile *, image_tile_ptr, &image->tiles) {
|
||||
const ImageTileWrapper image_tile(image_tile_ptr);
|
||||
tile_user.tile = image_tile.get_tile_number();
|
||||
|
||||
ImBuf *tile_buffer = BKE_image_acquire_ibuf(image, &tile_user, &lock);
|
||||
if (tile_buffer == nullptr) {
|
||||
/* Couldn't load the image buffer of the tile. */
|
||||
continue;
|
||||
}
|
||||
do_full_update_texture_slot(instance_data, info, texture_buffer, *tile_buffer, image_tile);
|
||||
BKE_image_release_ibuf(image, tile_buffer, lock);
|
||||
}
|
||||
GPU_texture_update(info.texture, GPU_DATA_FLOAT, texture_buffer.rect_float);
|
||||
imb_freerectImbuf_all(&texture_buffer);
|
||||
}
|
||||
|
||||
/**
|
||||
* \brief Ensure that the float buffer of the given image buffer is available.
|
||||
*/
|
||||
void ensure_float_buffer(ImBuf &image_buffer) const
|
||||
{
|
||||
if (image_buffer.rect_float == nullptr) {
|
||||
IMB_float_from_rect(&image_buffer);
|
||||
}
|
||||
}
|
||||
|
||||
void do_full_update_texture_slot(const IMAGE_InstanceData &instance_data,
|
||||
const TextureInfo &texture_info,
|
||||
ImBuf &texture_buffer,
|
||||
ImBuf &tile_buffer,
|
||||
const ImageTileWrapper &image_tile) const
|
||||
{
|
||||
const int texture_width = texture_buffer.x;
|
||||
const int texture_height = texture_buffer.y;
|
||||
ensure_float_buffer(tile_buffer);
|
||||
|
||||
/* IMB_transform works in a non-consistent space. This should be documented or fixed!.
|
||||
* Construct a variant of the info_uv_to_texture that adds the texel space
|
||||
* transformation.*/
|
||||
float uv_to_texel[4][4];
|
||||
copy_m4_m4(uv_to_texel, instance_data.ss_to_texture);
|
||||
float scale[3] = {static_cast<float>(texture_width) / static_cast<float>(tile_buffer.x),
|
||||
static_cast<float>(texture_height) / static_cast<float>(tile_buffer.y),
|
||||
1.0f};
|
||||
rescale_m4(uv_to_texel, scale);
|
||||
uv_to_texel[3][0] += image_tile.get_tile_x_offset() / BLI_rctf_size_x(&texture_info.uv_bounds);
|
||||
uv_to_texel[3][1] += image_tile.get_tile_y_offset() / BLI_rctf_size_y(&texture_info.uv_bounds);
|
||||
uv_to_texel[3][0] *= texture_width;
|
||||
uv_to_texel[3][1] *= texture_height;
|
||||
invert_m4(uv_to_texel);
|
||||
|
||||
rctf crop_rect;
|
||||
rctf *crop_rect_ptr = nullptr;
|
||||
eIMBTransformMode transform_mode;
|
||||
if (instance_data.flags.do_tile_drawing) {
|
||||
transform_mode = IMB_TRANSFORM_MODE_WRAP_REPEAT;
|
||||
}
|
||||
else {
|
||||
BLI_rctf_init(&crop_rect, 0.0, tile_buffer.x, 0.0, tile_buffer.y);
|
||||
crop_rect_ptr = &crop_rect;
|
||||
transform_mode = IMB_TRANSFORM_MODE_CROP_SRC;
|
||||
}
|
||||
|
||||
IMB_transform(&tile_buffer,
|
||||
&texture_buffer,
|
||||
transform_mode,
|
||||
IMB_FILTER_NEAREST,
|
||||
uv_to_texel,
|
||||
crop_rect_ptr);
|
||||
}
|
||||
|
||||
public:
|
||||
void cache_init(IMAGE_Data *vedata) const override
|
||||
{
|
||||
IMAGE_InstanceData *instance_data = vedata->instance_data;
|
||||
instance_data->passes.image_pass = create_image_pass();
|
||||
}
|
||||
|
||||
void cache_image(IMAGE_Data *vedata, Image *image, ImageUser *iuser) const override
|
||||
{
|
||||
const DRWContextState *draw_ctx = DRW_context_state_get();
|
||||
IMAGE_InstanceData *instance_data = vedata->instance_data;
|
||||
TextureMethod method(instance_data);
|
||||
|
||||
instance_data->partial_update.ensure_image(image);
|
||||
instance_data->max_uv_update();
|
||||
instance_data->clear_dirty_flag();
|
||||
|
||||
// Step: Find out which screen space textures are needed to draw on the screen. Remove the
|
||||
// screen space textures that aren't needed.
|
||||
const ARegion *region = draw_ctx->region;
|
||||
method.update_screen_space_bounds(region);
|
||||
method.update_uv_bounds(region);
|
||||
|
||||
// Step: Update the GPU textures based on the changes in the image.
|
||||
instance_data->update_gpu_texture_allocations();
|
||||
update_textures(*instance_data, image, iuser);
|
||||
|
||||
// Step: Add the GPU textures to the shgroup.
|
||||
instance_data->update_batches();
|
||||
add_shgroups(instance_data);
|
||||
}
|
||||
|
||||
void draw_finish(IMAGE_Data *UNUSED(vedata)) const override
|
||||
{
|
||||
}
|
||||
|
||||
void draw_scene(IMAGE_Data *vedata) const override
|
||||
{
|
||||
IMAGE_InstanceData *instance_data = vedata->instance_data;
|
||||
|
||||
DefaultFramebufferList *dfbl = DRW_viewport_framebuffer_list_get();
|
||||
GPU_framebuffer_bind(dfbl->default_fb);
|
||||
static float clear_col[4] = {0.0f, 0.0f, 0.0f, 0.0f};
|
||||
GPU_framebuffer_clear_color_depth(dfbl->default_fb, clear_col, 1.0);
|
||||
|
||||
DRW_view_set_active(instance_data->view);
|
||||
DRW_draw_pass(instance_data->passes.image_pass);
|
||||
DRW_view_set_active(nullptr);
|
||||
}
|
||||
}; // namespace clipping
|
||||
|
||||
} // namespace blender::draw::image_engine
|
@@ -1,147 +0,0 @@
|
||||
/*
|
||||
* 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 draw_engine
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "image_private.hh"
|
||||
|
||||
namespace blender::draw::image_engine {
|
||||
|
||||
class ImageSpaceDrawingMode : public AbstractDrawingMode {
|
||||
private:
|
||||
DRWPass *create_image_pass() const
|
||||
{
|
||||
/* Write depth is needed for background overlay rendering. Near depth is used for
|
||||
* transparency checker and Far depth is used for indicating the image size. */
|
||||
DRWState state = static_cast<DRWState>(DRW_STATE_WRITE_COLOR | DRW_STATE_WRITE_DEPTH |
|
||||
DRW_STATE_DEPTH_ALWAYS | DRW_STATE_BLEND_ALPHA_PREMUL);
|
||||
return DRW_pass_create("Image", state);
|
||||
}
|
||||
|
||||
void add_to_shgroup(AbstractSpaceAccessor *space,
|
||||
DRWShadingGroup *grp,
|
||||
const Image *image,
|
||||
const ImBuf *image_buffer) const
|
||||
{
|
||||
float image_mat[4][4];
|
||||
|
||||
const DRWContextState *draw_ctx = DRW_context_state_get();
|
||||
const ARegion *region = draw_ctx->region;
|
||||
space->get_image_mat(image_buffer, region, image_mat);
|
||||
|
||||
GPUBatch *geom = DRW_cache_quad_get();
|
||||
|
||||
const float translate_x = image_mat[3][0];
|
||||
const float translate_y = image_mat[3][1];
|
||||
LISTBASE_FOREACH (ImageTile *, tile, &image->tiles) {
|
||||
const int tile_x = ((tile->tile_number - 1001) % 10);
|
||||
const int tile_y = ((tile->tile_number - 1001) / 10);
|
||||
image_mat[3][0] = (float)tile_x + translate_x;
|
||||
image_mat[3][1] = (float)tile_y + translate_y;
|
||||
DRW_shgroup_call_obmat(grp, geom, image_mat);
|
||||
}
|
||||
}
|
||||
|
||||
public:
|
||||
void cache_init(IMAGE_Data *vedata) const override
|
||||
{
|
||||
IMAGE_PassList *psl = vedata->psl;
|
||||
|
||||
psl->image_pass = create_image_pass();
|
||||
}
|
||||
|
||||
void cache_image(AbstractSpaceAccessor *space,
|
||||
IMAGE_Data *vedata,
|
||||
Image *image,
|
||||
ImageUser *iuser,
|
||||
ImBuf *image_buffer) const override
|
||||
{
|
||||
IMAGE_PassList *psl = vedata->psl;
|
||||
IMAGE_StorageList *stl = vedata->stl;
|
||||
IMAGE_PrivateData *pd = stl->pd;
|
||||
|
||||
GPUTexture *tex_tile_data = nullptr;
|
||||
space->get_gpu_textures(
|
||||
image, iuser, image_buffer, &pd->texture, &pd->owns_texture, &tex_tile_data);
|
||||
if (pd->texture == nullptr) {
|
||||
return;
|
||||
}
|
||||
const bool is_tiled_texture = tex_tile_data != nullptr;
|
||||
|
||||
ShaderParameters sh_params;
|
||||
sh_params.use_premul_alpha = BKE_image_has_gpu_texture_premultiplied_alpha(image,
|
||||
image_buffer);
|
||||
const DRWContextState *draw_ctx = DRW_context_state_get();
|
||||
const Scene *scene = draw_ctx->scene;
|
||||
if (scene->camera && scene->camera->type == OB_CAMERA) {
|
||||
Camera *camera = static_cast<Camera *>(scene->camera->data);
|
||||
copy_v2_fl2(sh_params.far_near, camera->clip_end, camera->clip_start);
|
||||
}
|
||||
space->get_shader_parameters(sh_params, image_buffer, is_tiled_texture);
|
||||
|
||||
GPUShader *shader = IMAGE_shader_image_get(is_tiled_texture);
|
||||
DRWShadingGroup *shgrp = DRW_shgroup_create(shader, psl->image_pass);
|
||||
if (is_tiled_texture) {
|
||||
DRW_shgroup_uniform_texture_ex(shgrp, "imageTileArray", pd->texture, GPU_SAMPLER_DEFAULT);
|
||||
DRW_shgroup_uniform_texture(shgrp, "imageTileData", tex_tile_data);
|
||||
}
|
||||
else {
|
||||
DRW_shgroup_uniform_texture_ex(shgrp, "imageTexture", pd->texture, GPU_SAMPLER_DEFAULT);
|
||||
}
|
||||
DRW_shgroup_uniform_vec2_copy(shgrp, "farNearDistances", sh_params.far_near);
|
||||
DRW_shgroup_uniform_vec4_copy(shgrp, "color", ShaderParameters::color);
|
||||
DRW_shgroup_uniform_vec4_copy(shgrp, "shuffle", sh_params.shuffle);
|
||||
DRW_shgroup_uniform_int_copy(shgrp, "drawFlags", sh_params.flags);
|
||||
DRW_shgroup_uniform_bool_copy(shgrp, "imgPremultiplied", sh_params.use_premul_alpha);
|
||||
|
||||
add_to_shgroup(space, shgrp, image, image_buffer);
|
||||
}
|
||||
|
||||
void draw_finish(IMAGE_Data *vedata) const override
|
||||
{
|
||||
IMAGE_StorageList *stl = vedata->stl;
|
||||
IMAGE_PrivateData *pd = stl->pd;
|
||||
|
||||
if (pd->texture && pd->owns_texture) {
|
||||
GPU_texture_free(pd->texture);
|
||||
pd->owns_texture = false;
|
||||
}
|
||||
pd->texture = nullptr;
|
||||
}
|
||||
|
||||
void draw_scene(IMAGE_Data *vedata) const override
|
||||
{
|
||||
IMAGE_PassList *psl = vedata->psl;
|
||||
IMAGE_PrivateData *pd = vedata->stl->pd;
|
||||
|
||||
DefaultFramebufferList *dfbl = DRW_viewport_framebuffer_list_get();
|
||||
GPU_framebuffer_bind(dfbl->default_fb);
|
||||
static float clear_col[4] = {0.0f, 0.0f, 0.0f, 0.0f};
|
||||
GPU_framebuffer_clear_color_depth(dfbl->default_fb, clear_col, 1.0);
|
||||
|
||||
DRW_view_set_active(pd->view);
|
||||
DRW_draw_pass(psl->image_pass);
|
||||
DRW_view_set_active(nullptr);
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace blender::draw::image_engine
|
@@ -41,7 +41,7 @@
|
||||
|
||||
#include "GPU_batch.h"
|
||||
|
||||
#include "image_drawing_mode_image_space.hh"
|
||||
#include "image_drawing_mode.hh"
|
||||
#include "image_engine.h"
|
||||
#include "image_private.hh"
|
||||
#include "image_space_image.hh"
|
||||
@@ -68,7 +68,7 @@ template<
|
||||
*
|
||||
* Useful during development to switch between drawing implementations.
|
||||
*/
|
||||
typename DrawingMode = ImageSpaceDrawingMode>
|
||||
typename DrawingMode = ScreenSpaceDrawingMode<OneTextureMethod>>
|
||||
class ImageEngine {
|
||||
private:
|
||||
const DRWContextState *draw_ctx;
|
||||
@@ -86,48 +86,58 @@ class ImageEngine {
|
||||
|
||||
void cache_init()
|
||||
{
|
||||
IMAGE_StorageList *stl = vedata->stl;
|
||||
IMAGE_PrivateData *pd = stl->pd;
|
||||
|
||||
IMAGE_InstanceData *instance_data = vedata->instance_data;
|
||||
drawing_mode.cache_init(vedata);
|
||||
pd->view = nullptr;
|
||||
if (space->has_view_override()) {
|
||||
const ARegion *region = draw_ctx->region;
|
||||
pd->view = space->create_view_override(region);
|
||||
}
|
||||
|
||||
/* Setup full screen view matrix. */
|
||||
const ARegion *region = draw_ctx->region;
|
||||
float winmat[4][4], viewmat[4][4];
|
||||
orthographic_m4(viewmat, 0.0, region->winx, 0.0, region->winy, 0.0, 1.0);
|
||||
unit_m4(winmat);
|
||||
instance_data->view = DRW_view_create(viewmat, winmat, nullptr, nullptr, nullptr);
|
||||
}
|
||||
|
||||
void cache_populate()
|
||||
{
|
||||
IMAGE_StorageList *stl = vedata->stl;
|
||||
IMAGE_PrivateData *pd = stl->pd;
|
||||
IMAGE_InstanceData *instance_data = vedata->instance_data;
|
||||
Main *bmain = CTX_data_main(draw_ctx->evil_C);
|
||||
pd->image = space->get_image(bmain);
|
||||
if (pd->image == nullptr) {
|
||||
instance_data->image = space->get_image(bmain);
|
||||
if (instance_data->image == nullptr) {
|
||||
/* Early exit, nothing to draw. */
|
||||
return;
|
||||
}
|
||||
pd->ibuf = space->acquire_image_buffer(pd->image, &pd->lock);
|
||||
instance_data->flags.do_tile_drawing = instance_data->image->source != IMA_SRC_TILED &&
|
||||
space->use_tile_drawing();
|
||||
void *lock;
|
||||
ImBuf *image_buffer = space->acquire_image_buffer(instance_data->image, &lock);
|
||||
|
||||
/* Setup the matrix to go from screen UV coordinates to UV texture space coordinates. */
|
||||
float image_resolution[2] = {image_buffer ? image_buffer->x : 1024.0f,
|
||||
image_buffer ? image_buffer->y : 1024.0f};
|
||||
space->init_ss_to_texture_matrix(
|
||||
draw_ctx->region, image_resolution, instance_data->ss_to_texture);
|
||||
|
||||
const Scene *scene = DRW_context_state_get()->scene;
|
||||
instance_data->sh_params.update(space.get(), scene, instance_data->image, image_buffer);
|
||||
space->release_buffer(instance_data->image, image_buffer, lock);
|
||||
|
||||
ImageUser *iuser = space->get_image_user();
|
||||
drawing_mode.cache_image(space.get(), vedata, pd->image, iuser, pd->ibuf);
|
||||
drawing_mode.cache_image(vedata, instance_data->image, iuser);
|
||||
}
|
||||
|
||||
void draw_finish()
|
||||
{
|
||||
drawing_mode.draw_finish(vedata);
|
||||
|
||||
IMAGE_StorageList *stl = vedata->stl;
|
||||
IMAGE_PrivateData *pd = stl->pd;
|
||||
space->release_buffer(pd->image, pd->ibuf, pd->lock);
|
||||
pd->image = nullptr;
|
||||
pd->ibuf = nullptr;
|
||||
IMAGE_InstanceData *instance_data = vedata->instance_data;
|
||||
instance_data->image = nullptr;
|
||||
}
|
||||
|
||||
void draw_scene()
|
||||
{
|
||||
drawing_mode.draw_scene(vedata);
|
||||
}
|
||||
};
|
||||
}; // namespace blender::draw::image_engine
|
||||
|
||||
/* -------------------------------------------------------------------- */
|
||||
/** \name Engine Callbacks
|
||||
@@ -137,15 +147,9 @@ static void IMAGE_engine_init(void *ved)
|
||||
{
|
||||
IMAGE_shader_library_ensure();
|
||||
IMAGE_Data *vedata = (IMAGE_Data *)ved;
|
||||
IMAGE_StorageList *stl = vedata->stl;
|
||||
if (!stl->pd) {
|
||||
stl->pd = static_cast<IMAGE_PrivateData *>(MEM_callocN(sizeof(IMAGE_PrivateData), __func__));
|
||||
if (vedata->instance_data == nullptr) {
|
||||
vedata->instance_data = MEM_new<IMAGE_InstanceData>(__func__);
|
||||
}
|
||||
IMAGE_PrivateData *pd = stl->pd;
|
||||
|
||||
pd->ibuf = nullptr;
|
||||
pd->lock = nullptr;
|
||||
pd->texture = nullptr;
|
||||
}
|
||||
|
||||
static void IMAGE_cache_init(void *vedata)
|
||||
@@ -174,6 +178,12 @@ static void IMAGE_engine_free()
|
||||
IMAGE_shader_free();
|
||||
}
|
||||
|
||||
static void IMAGE_instance_free(void *_instance_data)
|
||||
{
|
||||
IMAGE_InstanceData *instance_data = reinterpret_cast<IMAGE_InstanceData *>(_instance_data);
|
||||
MEM_delete(instance_data);
|
||||
}
|
||||
|
||||
/** \} */
|
||||
|
||||
static const DrawEngineDataSize IMAGE_data_size = DRW_VIEWPORT_DATA_SIZE(IMAGE_Data);
|
||||
@@ -191,7 +201,7 @@ DrawEngineType draw_engine_image_type = {
|
||||
&IMAGE_data_size, /* vedata_size */
|
||||
&IMAGE_engine_init, /* engine_init */
|
||||
&IMAGE_engine_free, /* engine_free */
|
||||
nullptr, /* instance_free */
|
||||
&IMAGE_instance_free, /* instance_free */
|
||||
&IMAGE_cache_init, /* cache_init */
|
||||
&IMAGE_cache_populate, /* cache_populate */
|
||||
nullptr, /* cache_finish */
|
||||
|
134
source/blender/draw/engines/image/image_instance_data.hh
Normal file
134
source/blender/draw/engines/image/image_instance_data.hh
Normal file
@@ -0,0 +1,134 @@
|
||||
/*
|
||||
* 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 draw_engine
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "image_batches.hh"
|
||||
#include "image_partial_updater.hh"
|
||||
#include "image_private.hh"
|
||||
#include "image_shader_params.hh"
|
||||
#include "image_texture_info.hh"
|
||||
#include "image_wrappers.hh"
|
||||
|
||||
#include "DRW_render.h"
|
||||
|
||||
/**
|
||||
* \brief max allowed textures to use by the ScreenSpaceDrawingMode.
|
||||
*
|
||||
* 4 textures are used to reduce uploading screen space textures when translating the image.
|
||||
*/
|
||||
constexpr int SCREEN_SPACE_DRAWING_MODE_TEXTURE_LEN = 4;
|
||||
|
||||
struct IMAGE_InstanceData {
|
||||
struct Image *image;
|
||||
|
||||
PartialImageUpdater partial_update;
|
||||
|
||||
struct DRWView *view;
|
||||
ShaderParameters sh_params;
|
||||
struct {
|
||||
/**
|
||||
* \brief should we perform tiled drawing (wrap repeat).
|
||||
*
|
||||
* Option is true when image is capable of tile drawing (image is not tile) and the tiled
|
||||
* option is set in the space.
|
||||
*/
|
||||
bool do_tile_drawing : 1;
|
||||
} flags;
|
||||
|
||||
struct {
|
||||
DRWPass *image_pass;
|
||||
} passes;
|
||||
|
||||
/** \brief Transform matrix to convert a normalized screen space coordinates to texture space. */
|
||||
float ss_to_texture[4][4];
|
||||
TextureInfo texture_infos[SCREEN_SPACE_DRAWING_MODE_TEXTURE_LEN];
|
||||
|
||||
/**
|
||||
* \brief Maximum uv's that are on the border of the image.
|
||||
*
|
||||
* Larger UV coordinates would be drawn as a border. */
|
||||
float max_uv[2];
|
||||
|
||||
public:
|
||||
void max_uv_update()
|
||||
{
|
||||
copy_v2_fl2(max_uv, 1.0f, 1.0);
|
||||
LISTBASE_FOREACH (ImageTile *, image_tile_ptr, &image->tiles) {
|
||||
ImageTileWrapper image_tile(image_tile_ptr);
|
||||
max_uv[0] = max_ii(max_uv[0], image_tile.get_tile_x_offset() + 1);
|
||||
max_uv[1] = max_ii(max_uv[1], image_tile.get_tile_y_offset() + 1);
|
||||
}
|
||||
}
|
||||
|
||||
void clear_dirty_flag()
|
||||
{
|
||||
reset_dirty_flag(false);
|
||||
}
|
||||
void mark_all_texture_slots_dirty()
|
||||
{
|
||||
reset_dirty_flag(true);
|
||||
}
|
||||
|
||||
void update_gpu_texture_allocations()
|
||||
{
|
||||
for (int i = 0; i < SCREEN_SPACE_DRAWING_MODE_TEXTURE_LEN; i++) {
|
||||
TextureInfo &info = texture_infos[i];
|
||||
const bool is_allocated = info.texture != nullptr;
|
||||
const bool is_visible = info.visible;
|
||||
const bool should_be_freed = !is_visible && is_allocated;
|
||||
const bool should_be_created = is_visible && !is_allocated;
|
||||
|
||||
if (should_be_freed) {
|
||||
GPU_texture_free(info.texture);
|
||||
info.texture = nullptr;
|
||||
}
|
||||
|
||||
if (should_be_created) {
|
||||
DRW_texture_ensure_fullscreen_2d(
|
||||
&info.texture, GPU_RGBA16F, static_cast<DRWTextureFlag>(0));
|
||||
}
|
||||
info.dirty |= should_be_created;
|
||||
}
|
||||
}
|
||||
|
||||
void update_batches()
|
||||
{
|
||||
for (int i = 0; i < SCREEN_SPACE_DRAWING_MODE_TEXTURE_LEN; i++) {
|
||||
TextureInfo &info = texture_infos[i];
|
||||
if (!info.dirty) {
|
||||
continue;
|
||||
}
|
||||
BatchUpdater batch_updater(info);
|
||||
batch_updater.update_batch();
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
/** \brief Set dirty flag of all texture slots to the given value. */
|
||||
void reset_dirty_flag(bool new_value)
|
||||
{
|
||||
for (int i = 0; i < SCREEN_SPACE_DRAWING_MODE_TEXTURE_LEN; i++) {
|
||||
texture_infos[i].dirty = new_value;
|
||||
}
|
||||
}
|
||||
};
|
78
source/blender/draw/engines/image/image_partial_updater.hh
Normal file
78
source/blender/draw/engines/image/image_partial_updater.hh
Normal file
@@ -0,0 +1,78 @@
|
||||
/*
|
||||
* 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 draw_engine
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "BKE_image.h"
|
||||
#include "BKE_image_partial_update.hh"
|
||||
|
||||
struct PartialImageUpdater {
|
||||
struct PartialUpdateUser *user;
|
||||
const struct Image *image;
|
||||
|
||||
/**
|
||||
* \brief Ensure that there is a partial update user for the given image.
|
||||
*/
|
||||
void ensure_image(const Image *image)
|
||||
{
|
||||
if (!is_valid(image)) {
|
||||
free();
|
||||
create(image);
|
||||
}
|
||||
}
|
||||
|
||||
virtual ~PartialImageUpdater()
|
||||
{
|
||||
free();
|
||||
}
|
||||
|
||||
private:
|
||||
/**
|
||||
* \brief check if the partial update user can still be used for the given image.
|
||||
*
|
||||
* When switching to a different image the partial update user should be recreated.
|
||||
*/
|
||||
bool is_valid(const Image *new_image) const
|
||||
{
|
||||
if (image != new_image) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return user != nullptr;
|
||||
}
|
||||
|
||||
void create(const Image *image)
|
||||
{
|
||||
BLI_assert(user == nullptr);
|
||||
user = BKE_image_partial_update_create(image);
|
||||
image = image;
|
||||
}
|
||||
|
||||
void free()
|
||||
{
|
||||
if (user != nullptr) {
|
||||
BKE_image_partial_update_free(user);
|
||||
user = nullptr;
|
||||
image = nullptr;
|
||||
}
|
||||
}
|
||||
};
|
@@ -24,6 +24,11 @@
|
||||
|
||||
#include <optional>
|
||||
|
||||
#include "BKE_image.h"
|
||||
|
||||
#include "image_instance_data.hh"
|
||||
#include "image_texture_info.hh"
|
||||
|
||||
/* Forward declarations */
|
||||
extern "C" {
|
||||
struct GPUTexture;
|
||||
@@ -35,32 +40,13 @@ struct Image;
|
||||
|
||||
namespace blender::draw::image_engine {
|
||||
|
||||
/* GPUViewport.storage
|
||||
* Is freed every time the viewport engine changes. */
|
||||
struct IMAGE_PassList {
|
||||
DRWPass *image_pass;
|
||||
};
|
||||
|
||||
struct IMAGE_PrivateData {
|
||||
void *lock;
|
||||
struct ImBuf *ibuf;
|
||||
struct Image *image;
|
||||
struct DRWView *view;
|
||||
|
||||
struct GPUTexture *texture;
|
||||
bool owns_texture;
|
||||
};
|
||||
|
||||
struct IMAGE_StorageList {
|
||||
IMAGE_PrivateData *pd;
|
||||
};
|
||||
|
||||
struct IMAGE_Data {
|
||||
void *engine_type;
|
||||
DRWViewportEmptyList *fbl;
|
||||
DRWViewportEmptyList *txl;
|
||||
IMAGE_PassList *psl;
|
||||
IMAGE_StorageList *stl;
|
||||
DRWViewportEmptyList *psl;
|
||||
DRWViewportEmptyList *stl;
|
||||
IMAGE_InstanceData *instance_data;
|
||||
};
|
||||
|
||||
/* Shader parameters. */
|
||||
@@ -69,105 +55,6 @@ struct IMAGE_Data {
|
||||
#define IMAGE_DRAW_FLAG_SHUFFLING (1 << 2)
|
||||
#define IMAGE_DRAW_FLAG_DEPTH (1 << 3)
|
||||
#define IMAGE_DRAW_FLAG_DO_REPEAT (1 << 4)
|
||||
#define IMAGE_DRAW_FLAG_USE_WORLD_POS (1 << 5)
|
||||
|
||||
struct ShaderParameters {
|
||||
constexpr static float color[4] = {1.0f, 1.0f, 1.0f, 1.0f};
|
||||
|
||||
int flags = 0;
|
||||
float shuffle[4];
|
||||
float far_near[2];
|
||||
bool use_premul_alpha = false;
|
||||
|
||||
ShaderParameters()
|
||||
{
|
||||
copy_v4_fl(shuffle, 1.0f);
|
||||
copy_v2_fl2(far_near, 100.0f, 0.0f);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Space accessor.
|
||||
*
|
||||
* Image engine is used to draw the images inside multiple spaces \see SpaceLink.
|
||||
* The AbstractSpaceAccessor is an interface to communicate with a space.
|
||||
*/
|
||||
class AbstractSpaceAccessor {
|
||||
public:
|
||||
virtual ~AbstractSpaceAccessor() = default;
|
||||
|
||||
/**
|
||||
* Return the active image of the space.
|
||||
*
|
||||
* The returned image will be drawn in the space.
|
||||
*
|
||||
* The return value is optional.
|
||||
*/
|
||||
virtual Image *get_image(Main *bmain) = 0;
|
||||
|
||||
/**
|
||||
* Return the #ImageUser of the space.
|
||||
*
|
||||
* The return value is optional.
|
||||
*/
|
||||
virtual ImageUser *get_image_user() = 0;
|
||||
|
||||
/**
|
||||
* Acquire the image buffer of the image.
|
||||
*
|
||||
* \param image: Image to get the buffer from. Image is the same as returned from the #get_image
|
||||
* member.
|
||||
* \param lock: pointer to a lock object.
|
||||
* \return Image buffer of the given image.
|
||||
*/
|
||||
virtual ImBuf *acquire_image_buffer(Image *image, void **lock) = 0;
|
||||
|
||||
/**
|
||||
* Release a previous locked image from #acquire_image_buffer.
|
||||
*/
|
||||
virtual void release_buffer(Image *image, ImBuf *image_buffer, void *lock) = 0;
|
||||
|
||||
/**
|
||||
* Update the r_shader_parameters with space specific settings.
|
||||
*
|
||||
* Only update the #ShaderParameters.flags and #ShaderParameters.shuffle. Other parameters
|
||||
* are updated inside the image engine.
|
||||
*/
|
||||
virtual void get_shader_parameters(ShaderParameters &r_shader_parameters,
|
||||
ImBuf *image_buffer,
|
||||
bool is_tiled) = 0;
|
||||
|
||||
/**
|
||||
* Retrieve the gpu textures to draw.
|
||||
*/
|
||||
virtual void get_gpu_textures(Image *image,
|
||||
ImageUser *iuser,
|
||||
ImBuf *image_buffer,
|
||||
GPUTexture **r_gpu_texture,
|
||||
bool *r_owns_texture,
|
||||
GPUTexture **r_tex_tile_data) = 0;
|
||||
|
||||
/**
|
||||
* Does this space override the view.
|
||||
* When so this member should return true and the create_view_override must return the view to
|
||||
* use during drawing.
|
||||
*/
|
||||
virtual bool has_view_override() const = 0;
|
||||
|
||||
/**
|
||||
* Override the view for drawing.
|
||||
* Should match #has_view_override.
|
||||
*/
|
||||
virtual DRWView *create_view_override(const ARegion *UNUSED(region)) = 0;
|
||||
|
||||
/**
|
||||
* Initialize the matrix that will be used to draw the image. The matrix will be send as object
|
||||
* matrix to the drawing pipeline.
|
||||
*/
|
||||
virtual void get_image_mat(const ImBuf *image_buffer,
|
||||
const ARegion *region,
|
||||
float r_mat[4][4]) const = 0;
|
||||
}; // namespace blender::draw::image_engine
|
||||
|
||||
/**
|
||||
* Abstract class for a drawing mode of the image engine.
|
||||
@@ -179,11 +66,7 @@ class AbstractDrawingMode {
|
||||
public:
|
||||
virtual ~AbstractDrawingMode() = default;
|
||||
virtual void cache_init(IMAGE_Data *vedata) const = 0;
|
||||
virtual void cache_image(AbstractSpaceAccessor *space,
|
||||
IMAGE_Data *vedata,
|
||||
Image *image,
|
||||
ImageUser *iuser,
|
||||
ImBuf *image_buffer) const = 0;
|
||||
virtual void cache_image(IMAGE_Data *vedata, Image *image, ImageUser *iuser) const = 0;
|
||||
virtual void draw_scene(IMAGE_Data *vedata) const = 0;
|
||||
virtual void draw_finish(IMAGE_Data *vedata) const = 0;
|
||||
};
|
||||
|
59
source/blender/draw/engines/image/image_shader_params.hh
Normal file
59
source/blender/draw/engines/image/image_shader_params.hh
Normal file
@@ -0,0 +1,59 @@
|
||||
/*
|
||||
* 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 draw_engine
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "DNA_camera_types.h"
|
||||
#include "DNA_image_types.h"
|
||||
#include "DNA_scene_types.h"
|
||||
|
||||
#include "IMB_imbuf_types.h"
|
||||
|
||||
#include "BKE_image.h"
|
||||
|
||||
#include "BLI_math.h"
|
||||
|
||||
#include "image_space.hh"
|
||||
|
||||
struct ShaderParameters {
|
||||
constexpr static float color[4] = {1.0f, 1.0f, 1.0f, 1.0f};
|
||||
|
||||
int flags = 0;
|
||||
float shuffle[4];
|
||||
float far_near[2];
|
||||
bool use_premul_alpha = false;
|
||||
|
||||
void update(AbstractSpaceAccessor *space, const Scene *scene, Image *image, ImBuf *image_buffer)
|
||||
{
|
||||
copy_v4_fl(shuffle, 1.0f);
|
||||
copy_v2_fl2(far_near, 100.0f, 0.0f);
|
||||
|
||||
use_premul_alpha = BKE_image_has_gpu_texture_premultiplied_alpha(image, image_buffer);
|
||||
|
||||
if (scene->camera && scene->camera->type == OB_CAMERA) {
|
||||
Camera *camera = static_cast<Camera *>(scene->camera->data);
|
||||
copy_v2_fl2(far_near, camera->clip_end, camera->clip_start);
|
||||
}
|
||||
const bool is_tiled_image = (image->source == IMA_SRC_TILED);
|
||||
space->get_shader_parameters(*this, image_buffer, is_tiled_image);
|
||||
}
|
||||
};
|
99
source/blender/draw/engines/image/image_space.hh
Normal file
99
source/blender/draw/engines/image/image_space.hh
Normal file
@@ -0,0 +1,99 @@
|
||||
/*
|
||||
* 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 draw_engine
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
class ShaderParameters;
|
||||
|
||||
/**
|
||||
* Space accessor.
|
||||
*
|
||||
* Image engine is used to draw the images inside multiple spaces \see SpaceLink.
|
||||
* The AbstractSpaceAccessor is an interface to communicate with a space.
|
||||
*/
|
||||
class AbstractSpaceAccessor {
|
||||
public:
|
||||
virtual ~AbstractSpaceAccessor() = default;
|
||||
|
||||
/**
|
||||
* Return the active image of the space.
|
||||
*
|
||||
* The returned image will be drawn in the space.
|
||||
*
|
||||
* The return value is optional.
|
||||
*/
|
||||
virtual Image *get_image(Main *bmain) = 0;
|
||||
|
||||
/**
|
||||
* Return the #ImageUser of the space.
|
||||
*
|
||||
* The return value is optional.
|
||||
*/
|
||||
virtual ImageUser *get_image_user() = 0;
|
||||
|
||||
/**
|
||||
* Acquire the image buffer of the image.
|
||||
*
|
||||
* \param image: Image to get the buffer from. Image is the same as returned from the #get_image
|
||||
* member.
|
||||
* \param lock: pointer to a lock object.
|
||||
* \return Image buffer of the given image.
|
||||
*/
|
||||
virtual ImBuf *acquire_image_buffer(Image *image, void **lock) = 0;
|
||||
|
||||
/**
|
||||
* Release a previous locked image from #acquire_image_buffer.
|
||||
*/
|
||||
virtual void release_buffer(Image *image, ImBuf *image_buffer, void *lock) = 0;
|
||||
|
||||
/**
|
||||
* Update the r_shader_parameters with space specific settings.
|
||||
*
|
||||
* Only update the #ShaderParameters.flags and #ShaderParameters.shuffle. Other parameters
|
||||
* are updated inside the image engine.
|
||||
*/
|
||||
virtual void get_shader_parameters(ShaderParameters &r_shader_parameters,
|
||||
ImBuf *image_buffer,
|
||||
bool is_tiled) = 0;
|
||||
|
||||
/**
|
||||
* Retrieve the gpu textures to draw.
|
||||
*/
|
||||
virtual void get_gpu_textures(Image *image,
|
||||
ImageUser *iuser,
|
||||
ImBuf *image_buffer,
|
||||
GPUTexture **r_gpu_texture,
|
||||
bool *r_owns_texture,
|
||||
GPUTexture **r_tex_tile_data) = 0;
|
||||
|
||||
/** \brief Is (wrap) repeat option enabled in the space. */
|
||||
virtual bool use_tile_drawing() const = 0;
|
||||
|
||||
/**
|
||||
* \brief Initialize r_uv_to_texture matrix to transform from normalized screen space coordinates
|
||||
* (0..1) to texture space UV coordinates.
|
||||
*/
|
||||
virtual void init_ss_to_texture_matrix(const ARegion *region,
|
||||
const float image_resolution[2],
|
||||
float r_uv_to_texture[4][4]) const = 0;
|
||||
|
||||
}; // namespace blender::draw::image_engine
|
@@ -61,7 +61,6 @@ class SpaceImageAccessor : public AbstractSpaceAccessor {
|
||||
const int sima_flag = sima->flag & ED_space_image_get_display_channel_mask(image_buffer);
|
||||
const bool do_repeat = (!is_tiled) && ((sima->flag & SI_DRAW_TILE) != 0);
|
||||
SET_FLAG_FROM_TEST(r_shader_parameters.flags, do_repeat, IMAGE_DRAW_FLAG_DO_REPEAT);
|
||||
SET_FLAG_FROM_TEST(r_shader_parameters.flags, is_tiled, IMAGE_DRAW_FLAG_USE_WORLD_POS);
|
||||
if ((sima_flag & SI_USE_ALPHA) != 0) {
|
||||
/* Show RGBA */
|
||||
r_shader_parameters.flags |= IMAGE_DRAW_FLAG_SHOW_ALPHA | IMAGE_DRAW_FLAG_APPLY_ALPHA;
|
||||
@@ -102,15 +101,6 @@ class SpaceImageAccessor : public AbstractSpaceAccessor {
|
||||
}
|
||||
}
|
||||
|
||||
bool has_view_override() const override
|
||||
{
|
||||
return false;
|
||||
}
|
||||
DRWView *create_view_override(const ARegion *UNUSED(region)) override
|
||||
{
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
void get_gpu_textures(Image *image,
|
||||
ImageUser *iuser,
|
||||
ImBuf *image_buffer,
|
||||
@@ -171,11 +161,26 @@ class SpaceImageAccessor : public AbstractSpaceAccessor {
|
||||
}
|
||||
}
|
||||
|
||||
void get_image_mat(const ImBuf *UNUSED(image_buffer),
|
||||
const ARegion *UNUSED(region),
|
||||
float r_mat[4][4]) const override
|
||||
bool use_tile_drawing() const override
|
||||
{
|
||||
unit_m4(r_mat);
|
||||
return (sima->flag & SI_DRAW_TILE) != 0;
|
||||
}
|
||||
|
||||
void init_ss_to_texture_matrix(const ARegion *region,
|
||||
const float UNUSED(image_resolution[2]),
|
||||
float r_uv_to_texture[4][4]) const override
|
||||
{
|
||||
// TODO: I remember that there was a function for this somewhere.
|
||||
unit_m4(r_uv_to_texture);
|
||||
float scale_x = 1.0 / BLI_rctf_size_x(®ion->v2d.cur);
|
||||
float scale_y = 1.0 / BLI_rctf_size_y(®ion->v2d.cur);
|
||||
float translate_x = scale_x * -region->v2d.cur.xmin;
|
||||
float translate_y = scale_y * -region->v2d.cur.ymin;
|
||||
|
||||
r_uv_to_texture[0][0] = scale_x;
|
||||
r_uv_to_texture[1][1] = scale_y;
|
||||
r_uv_to_texture[3][0] = translate_x;
|
||||
r_uv_to_texture[3][1] = translate_y;
|
||||
}
|
||||
};
|
||||
|
||||
|
@@ -54,20 +54,6 @@ class SpaceNodeAccessor : public AbstractSpaceAccessor {
|
||||
BKE_image_release_ibuf(image, ibuf, lock);
|
||||
}
|
||||
|
||||
bool has_view_override() const override
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
DRWView *create_view_override(const ARegion *region) override
|
||||
{
|
||||
/* Setup a screen pixel view. The backdrop of the node editor doesn't follow the region. */
|
||||
float winmat[4][4], viewmat[4][4];
|
||||
orthographic_m4(viewmat, 0.0, region->winx, 0.0, region->winy, 0.0, 1.0);
|
||||
unit_m4(winmat);
|
||||
return DRW_view_create(viewmat, winmat, nullptr, nullptr, nullptr);
|
||||
}
|
||||
|
||||
void get_shader_parameters(ShaderParameters &r_shader_parameters,
|
||||
ImBuf *ibuf,
|
||||
bool UNUSED(is_tiled)) override
|
||||
@@ -120,18 +106,33 @@ class SpaceNodeAccessor : public AbstractSpaceAccessor {
|
||||
*r_tex_tile_data = nullptr;
|
||||
}
|
||||
|
||||
void get_image_mat(const ImBuf *image_buffer,
|
||||
const ARegion *region,
|
||||
float r_mat[4][4]) const override
|
||||
bool use_tile_drawing() const override
|
||||
{
|
||||
unit_m4(r_mat);
|
||||
const float ibuf_width = image_buffer->x;
|
||||
const float ibuf_height = image_buffer->y;
|
||||
return false;
|
||||
}
|
||||
|
||||
r_mat[0][0] = ibuf_width * snode->zoom;
|
||||
r_mat[1][1] = ibuf_height * snode->zoom;
|
||||
r_mat[3][0] = (region->winx - snode->zoom * ibuf_width) / 2 + snode->xof;
|
||||
r_mat[3][1] = (region->winy - snode->zoom * ibuf_height) / 2 + snode->yof;
|
||||
/**
|
||||
* The backdrop of the node editor isn't drawn in screen space UV space. But is locked with the
|
||||
* screen.
|
||||
*/
|
||||
void init_ss_to_texture_matrix(const ARegion *region,
|
||||
const float image_resolution[2],
|
||||
float r_uv_to_texture[4][4]) const override
|
||||
{
|
||||
unit_m4(r_uv_to_texture);
|
||||
float display_resolution[2];
|
||||
mul_v2_v2fl(display_resolution, image_resolution, snode->zoom);
|
||||
const float scale_x = display_resolution[0] / region->winx;
|
||||
const float scale_y = display_resolution[1] / region->winy;
|
||||
const float translate_x = ((region->winx - display_resolution[0]) * 0.5f + snode->xof) /
|
||||
region->winx;
|
||||
const float translate_y = ((region->winy - display_resolution[1]) * 0.5f + snode->yof) /
|
||||
region->winy;
|
||||
|
||||
r_uv_to_texture[0][0] = scale_x;
|
||||
r_uv_to_texture[1][1] = scale_y;
|
||||
r_uv_to_texture[3][0] = translate_x;
|
||||
r_uv_to_texture[3][1] = translate_y;
|
||||
}
|
||||
};
|
||||
|
||||
|
76
source/blender/draw/engines/image/image_texture_info.hh
Normal file
76
source/blender/draw/engines/image/image_texture_info.hh
Normal file
@@ -0,0 +1,76 @@
|
||||
/*
|
||||
* 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 2020, Blender Foundation.
|
||||
*/
|
||||
|
||||
/** \file
|
||||
* \ingroup draw_engine
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "BLI_rect.h"
|
||||
|
||||
#include "GPU_batch.h"
|
||||
#include "GPU_texture.h"
|
||||
|
||||
struct TextureInfo {
|
||||
/**
|
||||
* \brief Is the texture clipped.
|
||||
*
|
||||
* Resources of clipped textures are freed and ignored when performing partial updates.
|
||||
*/
|
||||
bool visible : 1;
|
||||
|
||||
/**
|
||||
* \brief does this texture need a full update.
|
||||
*
|
||||
* When set to false the texture can be updated using a partial update.
|
||||
*/
|
||||
bool dirty : 1;
|
||||
|
||||
/** \brief area of the texture in screen space. */
|
||||
rctf clipping_bounds;
|
||||
/** \brief uv area of the texture. */
|
||||
rctf uv_bounds;
|
||||
|
||||
/**
|
||||
* \brief Batch to draw the associated texton the screen.
|
||||
*
|
||||
* contans a VBO with `pos` and 'uv'.
|
||||
* `pos` (2xF32) is relative to the origin of the space.
|
||||
* `uv` (2xF32) reflect the uv bounds.
|
||||
*/
|
||||
GPUBatch *batch;
|
||||
|
||||
/**
|
||||
* \brief GPU Texture for a partial region of the image editor.
|
||||
*/
|
||||
GPUTexture *texture;
|
||||
|
||||
~TextureInfo()
|
||||
{
|
||||
if (batch != nullptr) {
|
||||
GPU_batch_discard(batch);
|
||||
batch = nullptr;
|
||||
}
|
||||
|
||||
if (texture != nullptr) {
|
||||
GPU_texture_free(texture);
|
||||
texture = nullptr;
|
||||
}
|
||||
}
|
||||
};
|
49
source/blender/draw/engines/image/image_wrappers.hh
Normal file
49
source/blender/draw/engines/image/image_wrappers.hh
Normal file
@@ -0,0 +1,49 @@
|
||||
/*
|
||||
* 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 2020, Blender Foundation.
|
||||
*/
|
||||
|
||||
/** \file
|
||||
* \ingroup draw_engine
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "DNA_image_types.h"
|
||||
|
||||
struct ImageTileWrapper {
|
||||
ImageTile *image_tile;
|
||||
ImageTileWrapper(ImageTile *image_tile) : image_tile(image_tile)
|
||||
{
|
||||
}
|
||||
|
||||
int get_tile_number() const
|
||||
{
|
||||
return image_tile->tile_number;
|
||||
}
|
||||
|
||||
int get_tile_x_offset() const
|
||||
{
|
||||
int tile_number = get_tile_number();
|
||||
return (tile_number - 1001) % 10;
|
||||
}
|
||||
|
||||
int get_tile_y_offset() const
|
||||
{
|
||||
int tile_number = get_tile_number();
|
||||
return (tile_number - 1001) / 10;
|
||||
}
|
||||
};
|
@@ -7,12 +7,7 @@
|
||||
#define IMAGE_DRAW_FLAG_DEPTH (1 << 3)
|
||||
#define IMAGE_DRAW_FLAG_DO_REPEAT (1 << 4)
|
||||
|
||||
#ifdef TILED_IMAGE
|
||||
uniform sampler2DArray imageTileArray;
|
||||
uniform sampler1DArray imageTileData;
|
||||
#else
|
||||
uniform sampler2D imageTexture;
|
||||
#endif
|
||||
|
||||
uniform bool imgPremultiplied;
|
||||
uniform int drawFlags;
|
||||
@@ -20,75 +15,50 @@ uniform vec2 farNearDistances;
|
||||
uniform vec4 color;
|
||||
uniform vec4 shuffle;
|
||||
|
||||
/* Maximum UV range.
|
||||
* Negative UV coordinates and UV coordinates beyond maxUV would draw a border. */
|
||||
uniform vec2 maxUv;
|
||||
|
||||
#define FAR_DISTANCE farNearDistances.x
|
||||
#define NEAR_DISTANCE farNearDistances.y
|
||||
|
||||
in vec2 uvs;
|
||||
#define Z_DEPTH_BORDER 1.0
|
||||
#define Z_DEPTH_IMAGE 0.75
|
||||
|
||||
in vec2 uv_screen;
|
||||
in vec2 uv_image;
|
||||
|
||||
out vec4 fragColor;
|
||||
|
||||
#ifdef TILED_IMAGE
|
||||
/* TODO(fclem): deduplicate code. */
|
||||
bool node_tex_tile_lookup(inout vec3 co, sampler2DArray ima, sampler1DArray map)
|
||||
bool is_border(vec2 uv)
|
||||
{
|
||||
vec2 tile_pos = floor(co.xy);
|
||||
|
||||
if (tile_pos.x < 0 || tile_pos.y < 0 || tile_pos.x >= 10) {
|
||||
return false;
|
||||
}
|
||||
|
||||
float tile = 10.0 * tile_pos.y + tile_pos.x;
|
||||
if (tile >= textureSize(map, 0).x) {
|
||||
return false;
|
||||
}
|
||||
|
||||
/* Fetch tile information. */
|
||||
float tile_layer = texelFetch(map, ivec2(tile, 0), 0).x;
|
||||
if (tile_layer < 0.0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
vec4 tile_info = texelFetch(map, ivec2(tile, 1), 0);
|
||||
|
||||
co = vec3(((co.xy - tile_pos) * tile_info.zw) + tile_info.xy, tile_layer);
|
||||
return true;
|
||||
// TODO: should use bvec to reduce branching?
|
||||
return (uv.x < 0.0 || uv.y < 0.0 || uv.x > maxUv.x || uv.y > maxUv.y);
|
||||
}
|
||||
#endif
|
||||
|
||||
void main()
|
||||
{
|
||||
vec4 tex_color;
|
||||
/* Read texture */
|
||||
#ifdef TILED_IMAGE
|
||||
vec3 co = vec3(uvs, 0.0);
|
||||
if (node_tex_tile_lookup(co, imageTileArray, imageTileData)) {
|
||||
tex_color = texture(imageTileArray, co);
|
||||
}
|
||||
else {
|
||||
tex_color = vec4(1.0, 0.0, 1.0, 1.0);
|
||||
}
|
||||
#else
|
||||
vec2 uvs_clamped = ((drawFlags & IMAGE_DRAW_FLAG_DO_REPEAT) != 0) ?
|
||||
fract(uvs) :
|
||||
clamp(uvs, vec2(0.0), vec2(1.0));
|
||||
tex_color = texture(imageTexture, uvs_clamped);
|
||||
#endif
|
||||
ivec2 uvs_clamped = ivec2(uv_screen);
|
||||
vec4 tex_color = texelFetch(imageTexture, uvs_clamped, 0);
|
||||
|
||||
if ((drawFlags & IMAGE_DRAW_FLAG_APPLY_ALPHA) != 0) {
|
||||
if (!imgPremultiplied) {
|
||||
tex_color.rgb *= tex_color.a;
|
||||
bool border = is_border(uv_image);
|
||||
if (!border) {
|
||||
if ((drawFlags & IMAGE_DRAW_FLAG_APPLY_ALPHA) != 0) {
|
||||
if (!imgPremultiplied) {
|
||||
tex_color.rgb *= tex_color.a;
|
||||
}
|
||||
}
|
||||
if ((drawFlags & IMAGE_DRAW_FLAG_DEPTH) != 0) {
|
||||
tex_color = smoothstep(FAR_DISTANCE, NEAR_DISTANCE, tex_color);
|
||||
}
|
||||
|
||||
if ((drawFlags & IMAGE_DRAW_FLAG_SHUFFLING) != 0) {
|
||||
tex_color = color * dot(tex_color, shuffle);
|
||||
}
|
||||
if ((drawFlags & IMAGE_DRAW_FLAG_SHOW_ALPHA) == 0) {
|
||||
tex_color.a = 1.0;
|
||||
}
|
||||
}
|
||||
if ((drawFlags & IMAGE_DRAW_FLAG_DEPTH) != 0) {
|
||||
tex_color = smoothstep(FAR_DISTANCE, NEAR_DISTANCE, tex_color);
|
||||
}
|
||||
|
||||
if ((drawFlags & IMAGE_DRAW_FLAG_SHUFFLING) != 0) {
|
||||
tex_color = color * dot(tex_color, shuffle);
|
||||
}
|
||||
if ((drawFlags & IMAGE_DRAW_FLAG_SHOW_ALPHA) == 0) {
|
||||
tex_color.a = 1.0;
|
||||
}
|
||||
|
||||
fragColor = tex_color;
|
||||
gl_FragDepth = border ? Z_DEPTH_BORDER : Z_DEPTH_IMAGE;
|
||||
}
|
||||
|
@@ -1,34 +1,24 @@
|
||||
#pragma BLENDER_REQUIRE(common_view_lib.glsl)
|
||||
|
||||
#define IMAGE_DRAW_FLAG_DO_REPEAT (1 << 4)
|
||||
#define IMAGE_DRAW_FLAG_USE_WORLD_POS (1 << 5)
|
||||
#define IMAGE_Z_DEPTH 0.75
|
||||
|
||||
uniform int drawFlags;
|
||||
|
||||
in vec3 pos;
|
||||
out vec2 uvs;
|
||||
in vec2 pos;
|
||||
in vec2 uv;
|
||||
|
||||
/* Normalized screen space uv coordinates. */
|
||||
out vec2 uv_screen;
|
||||
out vec2 uv_image;
|
||||
|
||||
void main()
|
||||
{
|
||||
/* `pos` contains the coordinates of a quad (-1..1). but we need the coordinates of an image
|
||||
* plane (0..1) */
|
||||
vec3 image_pos = pos * 0.5 + 0.5;
|
||||
vec3 image_pos = vec3(pos, 0.0);
|
||||
uv_screen = image_pos.xy;
|
||||
uv_image = uv;
|
||||
|
||||
if ((drawFlags & IMAGE_DRAW_FLAG_DO_REPEAT) != 0) {
|
||||
gl_Position = vec4(pos.xy, IMAGE_Z_DEPTH, 1.0);
|
||||
uvs = point_view_to_object(image_pos).xy;
|
||||
}
|
||||
else {
|
||||
vec3 world_pos = point_object_to_world(image_pos);
|
||||
vec4 position = point_world_to_ndc(world_pos);
|
||||
/* Move drawn pixels to the front. In the overlay engine the depth is used
|
||||
* to detect if a transparency texture or the background color should be drawn.
|
||||
* Vertices are between 0.0 and 0.2, Edges between 0.2 and 0.4
|
||||
* actual pixels are at 0.75, 1.0 is used for the background. */
|
||||
position.z = IMAGE_Z_DEPTH;
|
||||
gl_Position = position;
|
||||
/* UDIM texture uses the world position for tile selection. */
|
||||
uvs = ((drawFlags & IMAGE_DRAW_FLAG_USE_WORLD_POS) != 0) ? world_pos.xy : image_pos.xy;
|
||||
}
|
||||
vec3 world_pos = point_object_to_world(image_pos);
|
||||
vec4 position = point_world_to_ndc(world_pos);
|
||||
gl_Position = position;
|
||||
}
|
||||
|
@@ -616,8 +616,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