1
1

Compare commits

...

118 Commits

Author SHA1 Message Date
b6ac95145c Merge branch 'master' into temp-gpu-image-engine 2022-01-27 23:04:17 +01:00
79c90bd49f Fix merge 2022-01-27 14:48:39 +01:00
6016dbb1a7 Merge branch 'master' into temp-gpu-image-engine
# Conflicts:
#	source/blender/blenkernel/CMakeLists.txt
#	source/blender/draw/CMakeLists.txt
2022-01-27 14:37:24 +01:00
c453aaa0b2 Merge branch 'master' into temp-gpu-image-engine 2021-12-15 15:16:11 +01:00
d8b811cfe9 Cleanup: remove disabled code
Originally pointcache wasn't supported when the file wasn't saved.
Remove commented code as this hasn't been the case for a long time.
2021-12-15 14:37:46 +01:00
647896bc60 Cleanup: unused variable warning 2021-12-15 14:37:46 +01:00
1557fa77d0 MetaBall: optimize memory allocation for meta-ball tessellation
Double the allocation size when the limit is reached instead of
increasing by a fixed number.

Also re-allocate to the exact size once complete instead of over
allocating. This gives a minor speedup in my tests ~19% faster
tessellation for ~1million faces.
2021-12-15 14:37:46 +01:00
302fe4f3ad Fix meta-ball bound-box calculation reading past buffer bounds
This broke "test_undo.view3d_multi_mode_select" test in
"lib/tests/ui_simulate" and is likely exposed by recent changes to
bounding box calculation.

The missing check for DL_INDEX4 dates back to code from 2002 which
intended to check this but was checking for DL_INDEX3 twice
which got removed as part of a cleaned up.

This could be hidden from memory checking tools as meta-balls
over-allocate vertex arrays.
2021-12-15 14:37:46 +01:00
1b52be9893 Fix compile errors on windows. 2021-12-15 14:37:46 +01:00
26c3ded368 Cleanup: Use pixel in stead of texels in naming. 2021-12-15 14:37:46 +01:00
efa4023e0a Fix T94082: Curve to point empty evaluated NURBS crash
This is basically the same as rBee4ed99866fbb7ab04, the fix is
simply to check if the spline has evaluated points when deciding
the offsets into the result points array.
2021-12-15 14:37:46 +01:00
eeee85017e Fix T93949: Preview Image Error When No Screen
Fix an error if "File Preview Type" is "Auto" and there is no screen.

See D13574 for details.

Differential Revision: https://developer.blender.org/D13574

Reviewed by Julian Eisel
2021-12-15 14:37:45 +01:00
ef40b0f136 Fix possible use-after-free on error handling during VR view drawing
Whenever an exception happens in VR session code, we cancel the entire
session. Alongside that, we removed the "surface" item used to draw into
an offscreen context. This would mess up the iterator of the surface
draw loop.
Similar to 7afd84df40.
2021-12-15 14:37:45 +01:00
7f0f7287d2 Fix null-pointer dereference on error handling during VR view drawing 2021-12-15 14:37:45 +01:00
b72f383ad5 Fix possible use-after-free on error handling during VR view drawing
Whenever an exception happens in VR session code, we cancel the entire
session. Alongside that, we removed the "surface" item used to draw into
an offscreen context. But this may still be stored as active surface,
leading to a use-after-free when deactivating this active surface, for
example.
2021-12-15 14:37:45 +01:00
b95d1b308f Fix Cycles compilation with CUDA / Optix after recent Map Range additions. 2021-12-15 14:37:45 +01:00
Charlie Jolly
38084d7df1 Nodes: Add vector support to Map Range node
This replaces lost functionality from the old GN Attribute Map Range node.
This also adds vector support to the shader version of the node.

Notes:
This breaks forward compatibility as this node now uses data storage.

Reviewed By: HooglyBoogly, brecht

Differential Revision: https://developer.blender.org/D12760
2021-12-15 14:37:45 +01:00
2a31772198 Cleanup: Remove unused arguments 2021-12-15 14:37:45 +01:00
85d966808f Cleanup: Remove runtime uiBlock pointer from nodes
Code is simpler when the uiBlocks used during drawing are simply
stored in an array. Additionally, looping can be simpler when we use
an vector to hold a temporary copy of the tree's linked list of nodes.

This patch also slightly changes how uiBlocks are "named" in
`node_uiblocks_init`. Now it uses the node name instead of the
pointer, which is helpful so we rely less on the node's address.

Differential Revision: https://developer.blender.org/D13540
2021-12-15 14:37:45 +01:00
da9e50646c Cleanup: Simplify node group input and output socket verification
This commit refactors the way the socket lists for group nodes,
and group input/output nodes are verified to match the group's
interface.

Previously the `bNodeSocket.new_sock` pointer was used to
temporarily mark the new sockets. This made the code confusing
and more complicated than necessary.

Now the old socket list is saved, and sockets are moved directly from
the old list to a new list if they match, or a new socket is created
directly in the new list.

This change is split from D13518, which aims to remove the `new_node`
and `new_sock` pointers. In the future this code might be removed
entirely in favor of using node socket declarations.

Differential Revision: https://developer.blender.org/D13543
2021-12-15 14:37:45 +01:00
320639740d Geometry Nodes: support instance attributes when realizing instances
This patch refactors the instance-realization code and adds new functionality.
* Named and anonymous attributes are propagated from instances to the
  realized geometry. If the same attribute exists on the geometry and on an
  instance, the attribute on the geometry has precedence.
* The id attribute has special handling to avoid creating the same id on many
  output points. This is necessary to make e.g. the Random Value node work
  as expected afterwards.

Realizing instance attributes has an effect on existing files, especially due to the
id attribute. To avoid breaking existing files, the Realize Instances node now has
a legacy option that is enabled for all already existing Realize Instances nodes.
Removing this legacy behavior does affect some existing files (although not many).
We can decide whether it's worth to remove the old behavior as a separate step.

This refactor also improves performance when realizing instances. That is mainly
due to multi-threading. See D13446 to get the file used for benchmarking. The
curve code is not as optimized as it could be yet. That's mainly because the storage
for these attributes might change soonish and it wasn't worth optimizing for the
current storage format right now.

```
1,000,000 x mesh vertex:       530 ms -> 130 ms
1,000,000 x simple cube:      1290 ms -> 190 ms
1,000,000 x point:            1000 ms -> 150 ms
1,000,000 x curve spiral:     1740 ms -> 330 ms
1,000,000 x curve line:       1110 ms -> 210 ms
10,000 x subdivided cylinder:  170 ms ->  40 ms
10 x subdivided spiral:        180 ms -> 180 ms
```

Differential Revision: https://developer.blender.org/D13446
2021-12-15 14:37:45 +01:00
5f9a6ac183 Geometry Nodes: simplify using selection when evaluating fields
We often had to use two `FieldEvaluator` instances to first evaluate
the selection and then the remaining fields. Now both can be done
with a single `FieldEvaluator`. This results in less boilerplate code in
many cases.

Performance is not affected by this change. In a separate patch we
could improve performance by reusing evaluated sub-fields that are
used by the selection and the other fields.

Differential Revision: https://developer.blender.org/D13571
2021-12-15 14:37:45 +01:00
42ae2e4c6c Fix T93920: Wrong field inferencing state with unavailable socket
This commit ignores unavailable sockets in one more place, to fix the
case in T93920.

Differential Revision: https://developer.blender.org/D13562
2021-12-15 14:37:45 +01:00
edac72ebb9 Fix T93649: Blender freezes when saving with active VR session
Dead-lock when VR viewport drawing and depsgraph updates would fight for
the draw-manager GL lock. This didn't usually cause issues because the
depsgraph would be evaluated at this point already, except in rare
exceptions like after file writing.

Fix this by ensuring the XR surface gets its depsgraph updated after
handling notifiers, which is where regular windows also do the depsgraph
updating.
2021-12-15 14:37:45 +01:00
a48b3012d2 Cleanup: consistent naming for the blender file name 2021-12-15 14:37:45 +01:00
6d15b4465d Cleanup: remove oudated comment from early COW development
Added in 161ab6109e
2021-12-15 14:37:45 +01:00
40f35851f5 Cleanup: use typed enum for wmDrag.flags
Also use 'e' prefix for enum type name.
2021-12-15 14:37:45 +01:00
45f2800e2d Cleanup: reorganize doxygen modules
- Nest compositor pages under the compositor module
- Nest GUI, DNA/RNA & externformats modules under Blender.
- Remove modules from intern which no longer exist.
- Add intern modules (atomic, eigen, glew-mx, libc_compat, locale,
  numaapi, rigidbody, sky, utfconv).
- Use 'intern_' prefix for intern modules since some of the modules
  use generic terms such as locale & atomic.
2021-12-15 14:37:45 +01:00
1fbcb2ba85 Cleanup: resolve parameter mis-matches in doc-strings
Renamed or removed parameters which no longer exist.
2021-12-15 14:37:45 +01:00
73ab5e5b63 Documented IMB transform. 2021-12-15 10:47:20 +01:00
1a85d2762a Added documentation. 2021-12-15 08:37:01 +01:00
ef602a1ff9 Sanitized node editor backdrop matrix. 2021-12-15 08:31:23 +01:00
d8c08f3633 Fixed backdrop slipping. 2021-12-14 15:31:36 +01:00
a3a646feb9 Separate space node backdrop from region. 2021-12-14 15:03:22 +01:00
b36c6f7f32 Silenced clang-tidy warnings. 2021-12-14 12:55:38 +01:00
30290efaeb Merge branch 'master' into temp-gpu-image-engine 2021-12-14 12:52:24 +01:00
c5edefe496 Fix uvbounds calculation from the incorrect space. 2021-12-13 16:01:53 +01:00
315e51ad77 Made view override required. 2021-12-13 15:15:32 +01:00
8c71b6b9ec Fix crash when iuser isn't available. 2021-12-13 13:29:42 +01:00
08bd773383 View single channel buffers in grey scale. 2021-12-13 12:59:09 +01:00
6739b4756d Moved UVWrapping to template argument of the sampler.
This adds back wrap repeat linear sampling.
2021-12-13 12:46:37 +01:00
bc77184563 Added support for 1, 2 and 3 channel float buffers. 2021-12-13 12:11:25 +01:00
cb54fae36c Added support for 3 channel source buffers. 2021-12-13 11:05:31 +01:00
86ca5f5e94 ImBuf transform: Added Sampler.
The sampler will hide the interpolation functions and would allow us
to add interpolation functions for none 4 channel buffers.
2021-12-13 09:19:12 +01:00
a27bc7c372 Fix crash in sequencer tests. 2021-12-13 08:21:41 +01:00
a37147347d Revert "Revert recent changes to ImBuf transform"
This reverts commit 5a3d5f751f.
2021-12-13 07:45:05 +01:00
4399a01de2 Merge branch 'master' into temp-gpu-image-engine 2021-12-13 07:43:59 +01:00
a5ed6f9898 Merge branch 'master' into temp-gpu-image-engine 2021-12-10 15:39:23 +01:00
484eb6b6cb Merge branch 'master' into temp-gpu-image-engine 2021-12-10 12:41:12 +01:00
915464b697 Fix typo in cmakelists.txt 2021-12-10 11:48:19 +01:00
51f9c2fdd4 Merge branch 'master' into temp-gpu-image-engine 2021-12-10 10:41:00 +01:00
cbf56b5916 Merge branch 'master' into temp-gpu-image-engine 2021-12-10 09:31:42 +01:00
401ad85078 Cleanup CMakeList.txt. 2021-12-08 15:55:40 +01:00
230565fbf0 Remove global image locking. Buffers are locked per tile. 2021-12-08 15:33:27 +01:00
a9ef394f6e Cleanup: renamed to OneTextureMethod 2021-12-08 14:30:35 +01:00
84869b551f Cleanup: Split in more files. 2021-12-08 14:14:52 +01:00
39e4dd079b Merge branch 'master' into temp-gpu-image-engine 2021-12-08 13:04:01 +01:00
2c914197c2 Merge branch 'master' into temp-gpu-image-engine 2021-12-08 10:02:06 +01:00
6a37dd9622 Merge branch 'master' into temp-gpu-image-engine 2021-12-07 15:45:16 +01:00
fddaa022af Use instance data to solve memory leak. 2021-12-07 15:35:44 +01:00
b717d0711c Use instance data to store data. 2021-12-07 13:43:15 +01:00
1d8c7b5fec Merge branch 'master' into temp-gpu-image-engine 2021-12-07 11:40:47 +01:00
1d30d3d57e Merge branch 'master' into temp-gpu-image-engine 2021-12-07 10:51:16 +01:00
d231f4059a Added a better comment to maxUv; 2021-12-06 15:47:44 +01:00
b7328397aa Fix border for tiled images. 2021-12-06 15:39:22 +01:00
23ac11c0c7 Fix tile image drawing 2021-12-06 14:45:40 +01:00
acd8cf549e Added Border back. 2021-12-06 13:55:06 +01:00
05a60c5727 Removed missing file from cmakelists.txt 2021-12-03 16:36:59 +01:00
d3f10f768a Cleanup use Tile drawing for wrap repeat. 2021-12-03 16:07:09 +01:00
c776f91480 Tiled image drawing. 2021-12-03 16:01:07 +01:00
387dbd65ac Merge branch 'master' into temp-gpu-image-engine 2021-12-03 14:51:06 +01:00
b07a6c2067 Remove repeat code from vert shader. 2021-12-03 11:09:08 +01:00
482b8fb879 Some small cleanups before starting with repeat. 2021-12-03 10:41:16 +01:00
decdde81ff Add support for tiled images. 2021-12-03 10:14:57 +01:00
da855bfa2e Load tile buffers when performing a full update. 2021-12-01 16:05:36 +01:00
36a1694e0e Added sub shading groups. 2021-12-01 15:24:37 +01:00
160720764d Regular texture partial update. 2021-11-30 12:53:42 +01:00
d5663dfce1 Merge branch 'master' into temp-gpu-image-engine 2021-11-29 11:27:30 +01:00
cb4c62eee6 First rendered pixel with the new image engine. 2021-11-29 11:10:45 +01:00
63b96f4f0e Single full screen image. 2021-11-26 16:04:00 +01:00
496cd70b02 Merge branch 'temp-gpu-texture-partial-updates' into temp-gpu-image-engine 2021-11-26 14:50:32 +01:00
f94e3ba72f Fix issue when image user is a nullptr. 2021-11-26 14:47:57 +01:00
f11a80d83b Detected a crash. 2021-11-26 14:17:39 +01:00
8f36a19cc0 Merge branch 'temp-gpu-texture-partial-updates' into temp-gpu-image-engine 2021-11-26 13:45:47 +01:00
6cda987f59 Added API documentation. 2021-11-26 13:29:18 +01:00
b30f101fc0 Use enum class for ePartialUpdateIterResult. 2021-11-26 11:26:57 +01:00
24a5f4d6f8 Use enum class for ePartialUpdateCollectResult. 2021-11-26 11:21:12 +01:00
7ffe9d56a1 Added CPP Api. 2021-11-26 11:13:26 +01:00
aac6bf9938 Merge branch 'master' into temp-gpu-texture-partial-updates 2021-11-26 08:28:52 +01:00
664adf9e76 Merge branch 'master' into temp-gpu-texture-partial-updates 2021-11-26 08:01:32 +01:00
378db2e805 Merge branch 'master' into temp-gpu-texture-partial-updates 2021-11-26 07:46:41 +01:00
ea768ff210 ImageEngine: Added skeleton for screen space drawing mode. 2021-11-24 15:51:35 +01:00
a3e662ea91 Merge branch 'master' into temp-gpu-image-engine 2021-11-24 11:24:57 +01:00
006cf67f5c Merge branch 'master' into temp-gpu-image-engine 2021-11-24 11:03:32 +01:00
495c2302d5 Merge branch 'master' into temp-gpu-texture-partial-updates 2021-11-24 09:06:00 +01:00
f0c5df70a1 Fix CLangTidy warnings. 2021-11-19 16:09:28 +01:00
a9c7ebbef2 Small changes to comment. 2021-11-19 15:40:56 +01:00
65b66c8abe Remove out-dated comment. 2021-11-19 15:25:05 +01:00
f93f579e02 Split functions to reduce complexity. 2021-11-19 15:23:00 +01:00
732e5d98d4 Merge branch 'master' into temp-gpu-texture-partial-updates 2021-11-19 15:04:46 +01:00
d6e52462f9 Limit the tiles to the resolution of the tile buffer. 2021-11-19 14:59:16 +01:00
709b19c1b0 Fix performance issue when requesting gpu textures. 2021-11-19 14:36:34 +01:00
152ad1c208 Clearing history. 2021-11-19 14:06:41 +01:00
ed7d12e711 WIP adding tiles between chunks and images. 2021-11-19 09:54:01 +01:00
4944372320 Merge branch 'master' into temp-gpu-texture-partial-updates 2021-11-19 08:01:02 +01:00
0218dfd17d use chunk where they aren't tiles. 2021-11-17 17:25:25 +01:00
2b128ecf24 Renaming tile to chunk. (Not completed) 2021-11-17 15:29:12 +01:00
9726498525 Add struct between tiles and images. 2021-11-17 14:58:41 +01:00
3e2957ea16 Added correct API usage check. 2021-11-17 14:43:15 +01:00
022c185912 Reduces the requirements of interface. 2021-11-17 14:16:45 +01:00
5e6a37a3e4 Multiple renames. 2021-11-17 13:00:46 +01:00
36fc80f1e7 Removed unused code. 2021-11-17 13:00:46 +01:00
cb05849083 Replaced internal gputexture generation with new partial update. 2021-11-17 13:00:46 +01:00
e167140e5d WIP: partial update image.
Differential Revision: https://developer.blender.org/D13238
2021-11-17 13:00:46 +01:00
7bd6a318e3 More tests and cleanups. 2021-11-17 13:00:46 +01:00
c957c87ff0 Added mark region dirty. 2021-11-17 13:00:46 +01:00
eca92b3fb1 WIP: partial update image.
Adding a basic mechanism that should be able to register tiles and find
changes. Not possible yet to loop over the updated tiles.
Next task would be to include reference to imbuf to get the width/height
of the images.
2021-11-17 13:00:46 +01:00
d918c2c907 WIP: Parial update skeleton. 2021-11-17 13:00:46 +01:00
27 changed files with 2631 additions and 552 deletions

View File

@@ -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

View 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

View File

@@ -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

View File

@@ -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);
}

View File

@@ -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)
}
/** \} */
}

View 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(&region.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();
}
}

View 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(&region, 10, 20, 40, 50);
BKE_image_partial_update_mark_region(image, image_tile, image_buffer, &region);
/* 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, &region), 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(&region_a, 10, 20, 40, 50);
BKE_image_partial_update_mark_region(image, image_tile, image_buffer, &region_a);
rcti region_b;
BLI_rcti_init(&region_b, 710, 720, 740, 750);
BKE_image_partial_update_mark_region(image, image_tile, image_buffer, &region_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, &region_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, &region_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(&region, -100, 0, 50, 100);
BKE_image_partial_update_mark_region(image, image_tile, image_buffer, &region);
result = BKE_image_partial_update_collect_changes(image, partial_update_user);
EXPECT_EQ(result, ePartialUpdateCollectResult::NoChangesDetected);
BLI_rcti_init(&region, 1024, 1100, 50, 100);
BKE_image_partial_update_mark_region(image, image_tile, image_buffer, &region);
result = BKE_image_partial_update_collect_changes(image, partial_update_user);
EXPECT_EQ(result, ePartialUpdateCollectResult::NoChangesDetected);
BLI_rcti_init(&region, 50, 100, -100, 0);
BKE_image_partial_update_mark_region(image, image_tile, image_buffer, &region);
result = BKE_image_partial_update_collect_changes(image, partial_update_user);
EXPECT_EQ(result, ePartialUpdateCollectResult::NoChangesDetected);
BLI_rcti_init(&region, 50, 100, 1024, 1100);
BKE_image_partial_update_mark_region(image, image_tile, image_buffer, &region);
result = BKE_image_partial_update_collect_changes(image, partial_update_user);
EXPECT_EQ(result, ePartialUpdateCollectResult::NoChangesDetected);
/* Diagonals. */
BLI_rcti_init(&region, -100, 0, -100, 0);
BKE_image_partial_update_mark_region(image, image_tile, image_buffer, &region);
result = BKE_image_partial_update_collect_changes(image, partial_update_user);
EXPECT_EQ(result, ePartialUpdateCollectResult::NoChangesDetected);
BLI_rcti_init(&region, -100, 0, 1024, 1100);
BKE_image_partial_update_mark_region(image, image_tile, image_buffer, &region);
result = BKE_image_partial_update_collect_changes(image, partial_update_user);
EXPECT_EQ(result, ePartialUpdateCollectResult::NoChangesDetected);
BLI_rcti_init(&region, 1024, 1100, -100, 0);
BKE_image_partial_update_mark_region(image, image_tile, image_buffer, &region);
result = BKE_image_partial_update_collect_changes(image, partial_update_user);
EXPECT_EQ(result, ePartialUpdateCollectResult::NoChangesDetected);
BLI_rcti_init(&region, 1024, 1100, 1024, 1100);
BKE_image_partial_update_mark_region(image, image_tile, image_buffer, &region);
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(&region, 0, 1, 0, 1);
BKE_image_partial_update_mark_region(image, image_tile, image_buffer, &region);
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(&region, 1023, 1024, 0, 1);
BKE_image_partial_update_mark_region(image, image_tile, image_buffer, &region);
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(&region, 1023, 1024, 1023, 1024);
BKE_image_partial_update_mark_region(image, image_tile, image_buffer, &region);
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(&region, 1023, 1024, 0, 1);
BKE_image_partial_update_mark_region(image, image_tile, image_buffer, &region);
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(&region, 10, 20, 40, 50);
BKE_image_partial_update_mark_region(image, image_tile, image_buffer, &region);
/* 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, &region), 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(&region, 710, 720, 740, 750);
BKE_image_partial_update_mark_region(image, image_tile, image_buffer, &region);
/* 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, &region), 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(&region, 300, 700, 300, 700);
BKE_image_partial_update_mark_region(image, image_tile, image_buffer, &region);
/* 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, &region, 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(&region, 300, 700, 300, 700);
BKE_image_partial_update_mark_region(image, image_tile, image_buffer, &region);
/* 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, &region, nullptr);
num_tiles_found++;
}
EXPECT_EQ(num_tiles_found, 4);
}
} // namespace blender::bke::image::partial_update

View File

@@ -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.

View File

@@ -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]);

View File

@@ -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

View 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");
}
}
};

View 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, &region->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

View File

@@ -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

View File

@@ -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 */

View 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;
}
}
};

View 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;
}
}
};

View File

@@ -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;
};

View 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);
}
};

View 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

View File

@@ -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(&region->v2d.cur);
float scale_y = 1.0 / BLI_rctf_size_y(&region->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;
}
};

View File

@@ -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;
}
};

View 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;
}
}
};

View 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;
}
};

View File

@@ -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;
}

View File

@@ -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;
}

View File

@@ -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;

View File

@@ -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 */