Realtime Compositor: Implement static cache manager

This patch introduces the concept of a Cached Resource that can be
cached across compositor evaluations as well as used by multiple
operations in the same evaluation. Additionally, this patch implements a
new structure for the realtime compositor, the Static Cache Manager,
that manages all the cached resources and deletes them when they are no
longer needed.

This improves responsiveness while adjusting compositor node trees and
also conserves memory usage.

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

Reviewed By: Clement Foucault
This commit is contained in:
Omar Emara 2022-11-04 16:14:22 +02:00
parent 943d574185
commit 85ce488298
17 changed files with 789 additions and 375 deletions

View File

@ -3,6 +3,7 @@
set(INC
.
algorithms
cached_resources
../../blenkernel
../../blenlib
../../gpu
@ -10,6 +11,7 @@ set(INC
../../makesdna
../../makesrna
../../nodes
../../render
../../gpu/intern
../../../../intern/guardedalloc
)
@ -31,6 +33,7 @@ set(SRC
intern/shader_node.cc
intern/shader_operation.cc
intern/simple_operation.cc
intern/static_cache_manager.cc
intern/static_shader_manager.cc
intern/texture_pool.cc
intern/utilities.cc
@ -51,6 +54,7 @@ set(SRC
COM_shader_node.hh
COM_shader_operation.hh
COM_simple_operation.hh
COM_static_cache_manager.hh
COM_static_shader_manager.hh
COM_texture_pool.hh
COM_utilities.hh
@ -58,12 +62,22 @@ set(SRC
algorithms/intern/algorithm_parallel_reduction.cc
algorithms/COM_algorithm_parallel_reduction.hh
cached_resources/intern/morphological_distance_feather_weights.cc
cached_resources/intern/symmetric_blur_weights.cc
cached_resources/intern/symmetric_separable_blur_weights.cc
cached_resources/COM_cached_resource.hh
cached_resources/COM_morphological_distance_feather_weights.hh
cached_resources/COM_symmetric_blur_weights.hh
cached_resources/COM_symmetric_separable_blur_weights.hh
)
set(LIB
bf_gpu
bf_nodes
bf_imbuf
bf_render
bf_blenlib
bf_blenkernel
)

View File

@ -9,6 +9,7 @@
#include "GPU_texture.h"
#include "COM_static_cache_manager.hh"
#include "COM_static_shader_manager.hh"
#include "COM_texture_pool.hh"
@ -22,14 +23,17 @@ namespace blender::realtime_compositor {
* providing input data like render passes and the active scene, as well as references to the data
* where the output of the evaluator will be written. The class also provides a reference to the
* texture pool which should be implemented by the caller and provided during construction.
* Finally, the class have an instance of a static shader manager for convenient shader
* acquisition. */
* Finally, the class have an instance of a static shader manager and a static resource manager
* for acquiring cached shaders and resources efficiently. */
class Context {
private:
/* A texture pool that can be used to allocate textures for the compositor efficiently. */
TexturePool &texture_pool_;
/* A static shader manager that can be used to acquire shaders for the compositor efficiently. */
StaticShaderManager shader_manager_;
/* A static cache manager that can be used to acquire cached resources for the compositor
* efficiently. */
StaticCacheManager cache_manager_;
public:
Context(TexturePool &texture_pool);
@ -67,6 +71,9 @@ class Context {
/* Get a reference to the static shader manager of this context. */
StaticShaderManager &shader_manager();
/* Get a reference to the static cache manager of this context. */
StaticCacheManager &cache_manager();
};
} // namespace blender::realtime_compositor

View File

@ -0,0 +1,77 @@
/* SPDX-License-Identifier: GPL-2.0-or-later */
#pragma once
#include <memory>
#include "BLI_map.hh"
#include "BLI_math_vec_types.hh"
#include "COM_morphological_distance_feather_weights.hh"
#include "COM_symmetric_blur_weights.hh"
#include "COM_symmetric_separable_blur_weights.hh"
namespace blender::realtime_compositor {
/* -------------------------------------------------------------------------------------------------
* Static Cache Manager
*
* A static cache manager is a collection of cached resources that can be retrieved when needed and
* created if not already available. In particular, each cached resource type has its own Map in
* the class, where all instances of that cached resource type are stored and tracked. See the
* CachedResource class for more information.
*
* The manager deletes the cached resources that are no longer needed. A cached resource is said to
* be not needed when it was not used in the previous evaluation. This is done through the
* following mechanism:
*
* - Before every evaluation, do the following:
* 1. All resources whose CachedResource::needed flag is false are deleted.
* 2. The CachedResource::needed flag of all remaining resources is set to false.
* - During evaluation, when retrieving any cached resource, set its CachedResource::needed flag to
* true.
*
* In effect, any resource that was used in the previous evaluation but was not used in the current
* evaluation will be deleted before the next evaluation. This mechanism is implemented in the
* reset() method of the class, which should be called before every evaluation. */
class StaticCacheManager {
private:
/* A map that stores all SymmetricBlurWeights cached resources. */
Map<SymmetricBlurWeightsKey, std::unique_ptr<SymmetricBlurWeights>> symmetric_blur_weights_;
/* A map that stores all SymmetricSeparableBlurWeights cached resources. */
Map<SymmetricSeparableBlurWeightsKey, std::unique_ptr<SymmetricSeparableBlurWeights>>
symmetric_separable_blur_weights_;
/* A map that stores all MorphologicalDistanceFeatherWeights cached resources. */
Map<MorphologicalDistanceFeatherWeightsKey, std::unique_ptr<MorphologicalDistanceFeatherWeights>>
morphological_distance_feather_weights_;
public:
/* Reset the cache manager by deleting the cached resources that are no longer needed because
* they weren't used in the last evaluation and prepare the remaining cached resources to track
* their needed status in the next evaluation. See the class description for more information.
* This should be called before every evaluation. */
void reset();
/* Check if there is an available SymmetricBlurWeights cached resource with the given parameters
* in the manager, if one exists, return it, otherwise, return a newly created one and add it to
* the manager. In both cases, tag the cached resource as needed to keep it cached for the next
* evaluation. */
SymmetricBlurWeights &get_symmetric_blur_weights(int type, float2 radius);
/* Check if there is an available SymmetricSeparableBlurWeights cached resource with the given
* parameters in the manager, if one exists, return it, otherwise, return a newly created one and
* add it to the manager. In both cases, tag the cached resource as needed to keep it cached for
* the next evaluation. */
SymmetricSeparableBlurWeights &get_symmetric_separable_blur_weights(int type, float radius);
/* Check if there is an available MorphologicalDistanceFeatherWeights cached resource with the
* given parameters in the manager, if one exists, return it, otherwise, return a newly created
* one and add it to the manager. In both cases, tag the cached resource as needed to keep it
* cached for the next evaluation. */
MorphologicalDistanceFeatherWeights &get_morphological_distance_feather_weights(int type,
int radius);
};
} // namespace blender::realtime_compositor

View File

@ -0,0 +1,31 @@
/* SPDX-License-Identifier: GPL-2.0-or-later */
#pragma once
namespace blender::realtime_compositor {
/* -------------------------------------------------------------------------------------------------
* Cached Resource.
*
* A cached resource is any resource that can be cached across compositor evaluations and across
* multiple operations. Cached resources are managed by an instance of a StaticCacheManager and are
* freed when they are no longer needed, a state which is represented by the `needed` member in the
* class. For more information on the caching mechanism, see the StaticCacheManager class.
*
* To add a new cached resource:
*
* - Create a derived class from CachedResource to represent the resource.
* - Create a key class that can be used in a Map to identify the resource.
* - Add a new Map to StaticCacheManager mapping the key to the resource.
* - Reset the contents of the added map in StaticCacheManager::reset.
* - Add an appropriate getter method in StaticCacheManager.
*
* See the existing cached resources for reference. */
class CachedResource {
public:
/* A flag that represents the needed status of the cached resource. See the StaticCacheManager
* class for more information on how this member is utilized in the caching mechanism. */
bool needed = true;
};
} // namespace blender::realtime_compositor

View File

@ -0,0 +1,61 @@
/* SPDX-License-Identifier: GPL-2.0-or-later */
#pragma once
#include <cstdint>
#include "GPU_shader.h"
#include "GPU_texture.h"
#include "COM_cached_resource.hh"
namespace blender::realtime_compositor {
/* ------------------------------------------------------------------------------------------------
* Morphological Distance Feather Key.
*/
class MorphologicalDistanceFeatherWeightsKey {
public:
int type;
float radius;
MorphologicalDistanceFeatherWeightsKey(int type, float radius);
uint64_t hash() const;
};
bool operator==(const MorphologicalDistanceFeatherWeightsKey &a,
const MorphologicalDistanceFeatherWeightsKey &b);
/* -------------------------------------------------------------------------------------------------
* Morphological Distance Feather Weights.
*
* A cached resource that computes and caches 1D GPU textures containing the weights of the
* separable Gaussian filter of the given radius as well as an inverse distance falloff of the
* given type and radius. The weights and falloffs are symmetric, because the Gaussian and falloff
* functions are all even functions. Consequently, only the positive half of the filter is computed
* and the shader takes that into consideration. */
class MorphologicalDistanceFeatherWeights : public CachedResource {
private:
GPUTexture *weights_texture_ = nullptr;
GPUTexture *distance_falloffs_texture_ = nullptr;
public:
MorphologicalDistanceFeatherWeights(int type, int radius);
~MorphologicalDistanceFeatherWeights();
void compute_weights(int radius);
void compute_distance_falloffs(int type, int radius);
void bind_weights_as_texture(GPUShader *shader, const char *texture_name) const;
void unbind_weights_as_texture() const;
void bind_distance_falloffs_as_texture(GPUShader *shader, const char *texture_name) const;
void unbind_distance_falloffs_as_texture() const;
};
} // namespace blender::realtime_compositor

View File

@ -0,0 +1,52 @@
/* SPDX-License-Identifier: GPL-2.0-or-later */
#pragma once
#include <cstdint>
#include "BLI_math_vec_types.hh"
#include "GPU_shader.h"
#include "GPU_texture.h"
#include "COM_cached_resource.hh"
namespace blender::realtime_compositor {
/* ------------------------------------------------------------------------------------------------
* Symmetric Blur Weights Key.
*/
class SymmetricBlurWeightsKey {
public:
int type;
float2 radius;
SymmetricBlurWeightsKey(int type, float2 radius);
uint64_t hash() const;
};
bool operator==(const SymmetricBlurWeightsKey &a, const SymmetricBlurWeightsKey &b);
/* -------------------------------------------------------------------------------------------------
* Symmetric Blur Weights.
*
* A cached resource that computes and caches a 2D GPU texture containing the weights of the filter
* of the given type and radius. The filter is assumed to be symmetric, because the filter
* functions are evaluated on the normalized distance to the center. Consequently, only the upper
* right quadrant are computed and the shader takes that into consideration. */
class SymmetricBlurWeights : public CachedResource {
private:
GPUTexture *texture_ = nullptr;
public:
SymmetricBlurWeights(int type, float2 radius);
~SymmetricBlurWeights();
void bind_as_texture(GPUShader *shader, const char *texture_name) const;
void unbind_as_texture() const;
};
} // namespace blender::realtime_compositor

View File

@ -0,0 +1,53 @@
/* SPDX-License-Identifier: GPL-2.0-or-later */
#pragma once
#include <cstdint>
#include "BLI_math_vec_types.hh"
#include "GPU_shader.h"
#include "GPU_texture.h"
#include "COM_cached_resource.hh"
namespace blender::realtime_compositor {
/* ------------------------------------------------------------------------------------------------
* Symmetric Separable Blur Weights Key.
*/
class SymmetricSeparableBlurWeightsKey {
public:
int type;
float radius;
SymmetricSeparableBlurWeightsKey(int type, float radius);
uint64_t hash() const;
};
bool operator==(const SymmetricSeparableBlurWeightsKey &a,
const SymmetricSeparableBlurWeightsKey &b);
/* -------------------------------------------------------------------------------------------------
* Symmetric Separable Blur Weights.
*
* A cached resource that computes and caches a 1D GPU texture containing the weights of the
* separable filter of the given type and radius. The filter is assumed to be symmetric, because
* the filter functions are all even functions. Consequently, only the positive half of the filter
* is computed and the shader takes that into consideration. */
class SymmetricSeparableBlurWeights : public CachedResource {
private:
GPUTexture *texture_ = nullptr;
public:
SymmetricSeparableBlurWeights(int type, float radius);
~SymmetricSeparableBlurWeights();
void bind_as_texture(GPUShader *shader, const char *texture_name) const;
void unbind_as_texture() const;
};
} // namespace blender::realtime_compositor

View File

@ -0,0 +1,159 @@
/* SPDX-License-Identifier: GPL-2.0-or-later */
#include <cmath>
#include <cstdint>
#include "BLI_array.hh"
#include "BLI_hash.hh"
#include "BLI_index_range.hh"
#include "RE_pipeline.h"
#include "DNA_scene_types.h"
#include "GPU_shader.h"
#include "GPU_texture.h"
#include "COM_morphological_distance_feather_weights.hh"
namespace blender::realtime_compositor {
/* --------------------------------------------------------------------
* Morphological Distance Feather Weights Key.
*/
MorphologicalDistanceFeatherWeightsKey::MorphologicalDistanceFeatherWeightsKey(int type,
float radius)
: type(type), radius(radius)
{
}
uint64_t MorphologicalDistanceFeatherWeightsKey::hash() const
{
return get_default_hash_2(type, radius);
}
bool operator==(const MorphologicalDistanceFeatherWeightsKey &a,
const MorphologicalDistanceFeatherWeightsKey &b)
{
return a.type == b.type && a.radius == b.radius;
}
/* --------------------------------------------------------------------
* Morphological Distance Feather Weights.
*/
MorphologicalDistanceFeatherWeights::MorphologicalDistanceFeatherWeights(int type, int radius)
{
compute_weights(radius);
compute_distance_falloffs(type, radius);
}
MorphologicalDistanceFeatherWeights::~MorphologicalDistanceFeatherWeights()
{
GPU_texture_free(weights_texture_);
GPU_texture_free(distance_falloffs_texture_);
}
void MorphologicalDistanceFeatherWeights::compute_weights(int radius)
{
/* The size of filter is double the radius plus 1, but since the filter is symmetric, we only
* compute half of it and no doubling happens. We add 1 to make sure the filter size is always
* odd and there is a center weight. */
const int size = radius + 1;
Array<float> weights(size);
float sum = 0.0f;
/* First, compute the center weight. */
const float center_weight = RE_filter_value(R_FILTER_GAUSS, 0.0f);
weights[0] = center_weight;
sum += center_weight;
/* Second, compute the other weights in the positive direction, making sure to add double the
* weight to the sum of weights because the filter is symmetric and we only loop over half of
* it. Skip the center weight already computed by dropping the front index. */
const float scale = radius > 0.0f ? 1.0f / radius : 0.0f;
for (const int i : weights.index_range().drop_front(1)) {
const float weight = RE_filter_value(R_FILTER_GAUSS, i * scale);
weights[i] = weight;
sum += weight * 2.0f;
}
/* Finally, normalize the weights. */
for (const int i : weights.index_range()) {
weights[i] /= sum;
}
weights_texture_ = GPU_texture_create_1d("Weights", size, 1, GPU_R16F, weights.data());
}
/* Computes a falloff that is equal to 1 at an input of zero and decrease to zero at an input of 1,
* with the rate of decrease depending on the falloff type. */
static float compute_distance_falloff(int type, float x)
{
x = 1.0f - x;
switch (type) {
case PROP_SMOOTH:
return 3.0f * x * x - 2.0f * x * x * x;
case PROP_SPHERE:
return std::sqrt(2.0f * x - x * x);
case PROP_ROOT:
return std::sqrt(x);
case PROP_SHARP:
return x * x;
case PROP_INVSQUARE:
return x * (2.0f - x);
case PROP_LIN:
return x;
default:
BLI_assert_unreachable();
return x;
}
}
void MorphologicalDistanceFeatherWeights::compute_distance_falloffs(int type, int radius)
{
/* The size of the distance falloffs is double the radius plus 1, but since the falloffs are
* symmetric, we only compute half of them and no doubling happens. We add 1 to make sure the
* falloffs size is always odd and there is a center falloff. */
const int size = radius + 1;
Array<float> falloffs(size);
/* Compute the distance falloffs in the positive direction only, because the falloffs are
* symmetric. */
const float scale = radius > 0.0f ? 1.0f / radius : 0.0f;
for (const int i : falloffs.index_range()) {
falloffs[i] = compute_distance_falloff(type, i * scale);
}
distance_falloffs_texture_ = GPU_texture_create_1d(
"Distance Factors", size, 1, GPU_R16F, falloffs.data());
}
void MorphologicalDistanceFeatherWeights::bind_weights_as_texture(GPUShader *shader,
const char *texture_name) const
{
const int texture_image_unit = GPU_shader_get_texture_binding(shader, texture_name);
GPU_texture_bind(weights_texture_, texture_image_unit);
}
void MorphologicalDistanceFeatherWeights::unbind_weights_as_texture() const
{
GPU_texture_unbind(weights_texture_);
}
void MorphologicalDistanceFeatherWeights::bind_distance_falloffs_as_texture(
GPUShader *shader, const char *texture_name) const
{
const int texture_image_unit = GPU_shader_get_texture_binding(shader, texture_name);
GPU_texture_bind(distance_falloffs_texture_, texture_image_unit);
}
void MorphologicalDistanceFeatherWeights::unbind_distance_falloffs_as_texture() const
{
GPU_texture_unbind(distance_falloffs_texture_);
}
} // namespace blender::realtime_compositor

View File

@ -0,0 +1,115 @@
/* SPDX-License-Identifier: GPL-2.0-or-later */
#include <cstdint>
#include "BLI_array.hh"
#include "BLI_hash.hh"
#include "BLI_index_range.hh"
#include "BLI_math_vec_types.hh"
#include "BLI_math_vector.hh"
#include "RE_pipeline.h"
#include "GPU_shader.h"
#include "GPU_texture.h"
#include "COM_symmetric_blur_weights.hh"
namespace blender::realtime_compositor {
/* --------------------------------------------------------------------
* Symmetric Blur Weights Key.
*/
SymmetricBlurWeightsKey::SymmetricBlurWeightsKey(int type, float2 radius)
: type(type), radius(radius)
{
}
uint64_t SymmetricBlurWeightsKey::hash() const
{
return get_default_hash_3(type, radius.x, radius.y);
}
bool operator==(const SymmetricBlurWeightsKey &a, const SymmetricBlurWeightsKey &b)
{
return a.type == b.type && a.radius == b.radius;
}
/* --------------------------------------------------------------------
* Symmetric Blur Weights.
*/
SymmetricBlurWeights::SymmetricBlurWeights(int type, float2 radius)
{
/* The full size of filter is double the radius plus 1, but since the filter is symmetric, we
* only compute a single quadrant of it and so no doubling happens. We add 1 to make sure the
* filter size is always odd and there is a center weight. */
const float2 scale = math::safe_divide(float2(1.0f), radius);
const int2 size = int2(math::ceil(radius)) + int2(1);
Array<float> weights(size.x * size.y);
float sum = 0.0f;
/* First, compute the center weight. */
const float center_weight = RE_filter_value(type, 0.0f);
weights[0] = center_weight;
sum += center_weight;
/* Then, compute the weights along the positive x axis, making sure to add double the weight to
* the sum of weights because the filter is symmetric and we only loop over the positive half
* of the x axis. Skip the center weight already computed by dropping the front index. */
for (const int x : IndexRange(size.x).drop_front(1)) {
const float weight = RE_filter_value(type, x * scale.x);
weights[x] = weight;
sum += weight * 2.0f;
}
/* Then, compute the weights along the positive y axis, making sure to add double the weight to
* the sum of weights because the filter is symmetric and we only loop over the positive half
* of the y axis. Skip the center weight already computed by dropping the front index. */
for (const int y : IndexRange(size.y).drop_front(1)) {
const float weight = RE_filter_value(type, y * scale.y);
weights[size.x * y] = weight;
sum += weight * 2.0f;
}
/* Then, compute the other weights in the upper right quadrant, making sure to add quadruple
* the weight to the sum of weights because the filter is symmetric and we only loop over one
* quadrant of it. Skip the weights along the y and x axis already computed by dropping the
* front index. */
for (const int y : IndexRange(size.y).drop_front(1)) {
for (const int x : IndexRange(size.x).drop_front(1)) {
const float weight = RE_filter_value(type, math::length(float2(x, y) * scale));
weights[size.x * y + x] = weight;
sum += weight * 4.0f;
}
}
/* Finally, normalize the weights. */
for (const int y : IndexRange(size.y)) {
for (const int x : IndexRange(size.x)) {
weights[size.x * y + x] /= sum;
}
}
texture_ = GPU_texture_create_2d("Weights", size.x, size.y, 1, GPU_R16F, weights.data());
}
SymmetricBlurWeights::~SymmetricBlurWeights()
{
GPU_texture_free(texture_);
}
void SymmetricBlurWeights::bind_as_texture(GPUShader *shader, const char *texture_name) const
{
const int texture_image_unit = GPU_shader_get_texture_binding(shader, texture_name);
GPU_texture_bind(texture_, texture_image_unit);
}
void SymmetricBlurWeights::unbind_as_texture() const
{
GPU_texture_unbind(texture_);
}
} // namespace blender::realtime_compositor

View File

@ -0,0 +1,93 @@
/* SPDX-License-Identifier: GPL-2.0-or-later */
#include <cstdint>
#include "BLI_array.hh"
#include "BLI_hash.hh"
#include "BLI_index_range.hh"
#include "BLI_math_base.hh"
#include "RE_pipeline.h"
#include "GPU_shader.h"
#include "GPU_texture.h"
#include "COM_symmetric_separable_blur_weights.hh"
namespace blender::realtime_compositor {
/* --------------------------------------------------------------------
* Symmetric Separable Blur Weights Key.
*/
SymmetricSeparableBlurWeightsKey::SymmetricSeparableBlurWeightsKey(int type, float radius)
: type(type), radius(radius)
{
}
uint64_t SymmetricSeparableBlurWeightsKey::hash() const
{
return get_default_hash_2(type, radius);
}
bool operator==(const SymmetricSeparableBlurWeightsKey &a,
const SymmetricSeparableBlurWeightsKey &b)
{
return a.type == b.type && a.radius == b.radius;
}
/* --------------------------------------------------------------------
* Symmetric Separable Blur Weights.
*/
SymmetricSeparableBlurWeights::SymmetricSeparableBlurWeights(int type, float radius)
{
/* The size of filter is double the radius plus 1, but since the filter is symmetric, we only
* compute half of it and no doubling happens. We add 1 to make sure the filter size is always
* odd and there is a center weight. */
const int size = math::ceil(radius) + 1;
Array<float> weights(size);
float sum = 0.0f;
/* First, compute the center weight. */
const float center_weight = RE_filter_value(type, 0.0f);
weights[0] = center_weight;
sum += center_weight;
/* Second, compute the other weights in the positive direction, making sure to add double the
* weight to the sum of weights because the filter is symmetric and we only loop over half of
* it. Skip the center weight already computed by dropping the front index. */
const float scale = radius > 0.0f ? 1.0f / radius : 0.0f;
for (const int i : weights.index_range().drop_front(1)) {
const float weight = RE_filter_value(type, i * scale);
weights[i] = weight;
sum += weight * 2.0f;
}
/* Finally, normalize the weights. */
for (const int i : weights.index_range()) {
weights[i] /= sum;
}
texture_ = GPU_texture_create_1d("Weights", size, 1, GPU_R16F, weights.data());
}
SymmetricSeparableBlurWeights::~SymmetricSeparableBlurWeights()
{
GPU_texture_free(texture_);
}
void SymmetricSeparableBlurWeights::bind_as_texture(GPUShader *shader,
const char *texture_name) const
{
const int texture_image_unit = GPU_shader_get_texture_binding(shader, texture_name);
GPU_texture_bind(texture_, texture_image_unit);
}
void SymmetricSeparableBlurWeights::unbind_as_texture() const
{
GPU_texture_unbind(texture_);
}
} // namespace blender::realtime_compositor

View File

@ -1,6 +1,7 @@
/* SPDX-License-Identifier: GPL-2.0-or-later */
#include "COM_context.hh"
#include "COM_static_cache_manager.hh"
#include "COM_static_shader_manager.hh"
#include "COM_texture_pool.hh"
@ -32,4 +33,9 @@ StaticShaderManager &Context::shader_manager()
return shader_manager_;
}
StaticCacheManager &Context::cache_manager()
{
return cache_manager_;
}
} // namespace blender::realtime_compositor

View File

@ -28,6 +28,7 @@ Evaluator::Evaluator(Context &context, bNodeTree &node_tree)
void Evaluator::evaluate()
{
context_.cache_manager().reset();
context_.texture_pool().reset();
if (!is_compiled_) {

View File

@ -0,0 +1,74 @@
/* SPDX-License-Identifier: GPL-2.0-or-later */
#include <memory>
#include "BLI_math_vec_types.hh"
#include "COM_morphological_distance_feather_weights.hh"
#include "COM_symmetric_blur_weights.hh"
#include "COM_symmetric_separable_blur_weights.hh"
#include "COM_static_cache_manager.hh"
namespace blender::realtime_compositor {
/* --------------------------------------------------------------------
* Static Cache Manager.
*/
void StaticCacheManager::reset()
{
/* First, delete all resources that are no longer needed. */
symmetric_blur_weights_.remove_if([](auto item) { return !item.value->needed; });
symmetric_separable_blur_weights_.remove_if([](auto item) { return !item.value->needed; });
morphological_distance_feather_weights_.remove_if([](auto item) { return !item.value->needed; });
/* Second, reset the needed status of the remaining resources to false to ready them to track
* their needed status for the next evaluation. */
for (auto &value : symmetric_blur_weights_.values()) {
value->needed = false;
}
for (auto &value : symmetric_separable_blur_weights_.values()) {
value->needed = false;
}
for (auto &value : morphological_distance_feather_weights_.values()) {
value->needed = false;
}
}
SymmetricBlurWeights &StaticCacheManager::get_symmetric_blur_weights(int type, float2 radius)
{
const SymmetricBlurWeightsKey key(type, radius);
auto &weights = *symmetric_blur_weights_.lookup_or_add_cb(
key, [&]() { return std::make_unique<SymmetricBlurWeights>(type, radius); });
weights.needed = true;
return weights;
}
SymmetricSeparableBlurWeights &StaticCacheManager::get_symmetric_separable_blur_weights(
int type, float radius)
{
const SymmetricSeparableBlurWeightsKey key(type, radius);
auto &weights = *symmetric_separable_blur_weights_.lookup_or_add_cb(
key, [&]() { return std::make_unique<SymmetricSeparableBlurWeights>(type, radius); });
weights.needed = true;
return weights;
}
MorphologicalDistanceFeatherWeights &StaticCacheManager::
get_morphological_distance_feather_weights(int type, int radius)
{
const MorphologicalDistanceFeatherWeightsKey key(type, radius);
auto &weights = *morphological_distance_feather_weights_.lookup_or_add_cb(
key, [&]() { return std::make_unique<MorphologicalDistanceFeatherWeights>(type, radius); });
weights.needed = true;
return weights;
}
} // namespace blender::realtime_compositor

View File

@ -24,6 +24,8 @@ set(INC
../render
../render/intern
../compositor/realtime_compositor
../compositor/realtime_compositor/algorithms
../compositor/realtime_compositor/cached_resources
../windowmanager
../../../intern/atomic

View File

@ -19,6 +19,7 @@ set(INC
../../windowmanager
../../compositor/realtime_compositor
../../compositor/realtime_compositor/algorithms
../../compositor/realtime_compositor/cached_resources
../../../../intern/guardedalloc
# dna_type_offsets.h

View File

@ -5,11 +5,7 @@
* \ingroup cmpnodes
*/
#include <cstdint>
#include "BLI_array.hh"
#include "BLI_assert.h"
#include "BLI_index_range.hh"
#include "BLI_math_base.hh"
#include "BLI_math_vec_types.hh"
#include "BLI_math_vector.hh"
@ -19,12 +15,13 @@
#include "UI_interface.h"
#include "UI_resources.h"
#include "RE_pipeline.h"
#include "GPU_shader.h"
#include "GPU_state.h"
#include "GPU_texture.h"
#include "COM_node_operation.hh"
#include "COM_symmetric_blur_weights.hh"
#include "COM_symmetric_separable_blur_weights.hh"
#include "COM_utilities.hh"
#include "node_composite_util.hh"
@ -92,192 +89,7 @@ static void node_composit_buts_blur(uiLayout *layout, bContext * /*C*/, PointerR
using namespace blender::realtime_compositor;
/* A helper class that computes and caches a 1D GPU texture containing the weights of the separable
* filter of the given type and radius. The filter is assumed to be symmetric, because the filter
* functions are all even functions. Consequently, only the positive half of the filter is computed
* and the shader takes that into consideration. */
class SymmetricSeparableBlurWeights {
private:
float radius_ = 1.0f;
int type_ = R_FILTER_GAUSS;
GPUTexture *texture_ = nullptr;
public:
~SymmetricSeparableBlurWeights()
{
if (texture_) {
GPU_texture_free(texture_);
}
}
/* Check if a texture containing the weights was already computed for the given filter type and
* radius. If such texture exists, do nothing, otherwise, free the already computed texture and
* recompute it with the given filter type and radius. */
void update(float radius, int type)
{
if (texture_ && type == type_ && radius == radius_) {
return;
}
if (texture_) {
GPU_texture_free(texture_);
}
/* The size of filter is double the radius plus 1, but since the filter is symmetric, we only
* compute half of it and no doubling happens. We add 1 to make sure the filter size is always
* odd and there is a center weight. */
const int size = math::ceil(radius) + 1;
Array<float> weights(size);
float sum = 0.0f;
/* First, compute the center weight. */
const float center_weight = RE_filter_value(type, 0.0f);
weights[0] = center_weight;
sum += center_weight;
/* Second, compute the other weights in the positive direction, making sure to add double the
* weight to the sum of weights because the filter is symmetric and we only loop over half of
* it. Skip the center weight already computed by dropping the front index. */
const float scale = radius > 0.0f ? 1.0f / radius : 0.0f;
for (const int i : weights.index_range().drop_front(1)) {
const float weight = RE_filter_value(type, i * scale);
weights[i] = weight;
sum += weight * 2.0f;
}
/* Finally, normalize the weights. */
for (const int i : weights.index_range()) {
weights[i] /= sum;
}
texture_ = GPU_texture_create_1d("Weights", size, 1, GPU_R16F, weights.data());
type_ = type;
radius_ = radius;
}
void bind_as_texture(GPUShader *shader, const char *texture_name)
{
const int texture_image_unit = GPU_shader_get_texture_binding(shader, texture_name);
GPU_texture_bind(texture_, texture_image_unit);
}
void unbind_as_texture()
{
GPU_texture_unbind(texture_);
}
};
/* A helper class that computes and caches a 2D GPU texture containing the weights of the filter of
* the given type and radius. The filter is assumed to be symmetric, because the filter functions
* are evaluated on the normalized distance to the center. Consequently, only the upper right
* quadrant are computed and the shader takes that into consideration. */
class SymmetricBlurWeights {
private:
int type_ = R_FILTER_GAUSS;
float2 radius_ = float2(1.0f);
GPUTexture *texture_ = nullptr;
public:
~SymmetricBlurWeights()
{
if (texture_) {
GPU_texture_free(texture_);
}
}
/* Check if a texture containing the weights was already computed for the given filter type and
* radius. If such texture exists, do nothing, otherwise, free the already computed texture and
* recompute it with the given filter type and radius. */
void update(float2 radius, int type)
{
if (texture_ && type == type_ && radius == radius_) {
return;
}
if (texture_) {
GPU_texture_free(texture_);
}
/* The full size of filter is double the radius plus 1, but since the filter is symmetric, we
* only compute a single quadrant of it and so no doubling happens. We add 1 to make sure the
* filter size is always odd and there is a center weight. */
const float2 scale = math::safe_divide(float2(1.0f), radius);
const int2 size = int2(math::ceil(radius)) + int2(1);
Array<float> weights(size.x * size.y);
float sum = 0.0f;
/* First, compute the center weight. */
const float center_weight = RE_filter_value(type, 0.0f);
weights[0] = center_weight;
sum += center_weight;
/* Then, compute the weights along the positive x axis, making sure to add double the weight to
* the sum of weights because the filter is symmetric and we only loop over the positive half
* of the x axis. Skip the center weight already computed by dropping the front index. */
for (const int x : IndexRange(size.x).drop_front(1)) {
const float weight = RE_filter_value(type, x * scale.x);
weights[x] = weight;
sum += weight * 2.0f;
}
/* Then, compute the weights along the positive y axis, making sure to add double the weight to
* the sum of weights because the filter is symmetric and we only loop over the positive half
* of the y axis. Skip the center weight already computed by dropping the front index. */
for (const int y : IndexRange(size.y).drop_front(1)) {
const float weight = RE_filter_value(type, y * scale.y);
weights[size.x * y] = weight;
sum += weight * 2.0f;
}
/* Then, compute the other weights in the upper right quadrant, making sure to add quadruple
* the weight to the sum of weights because the filter is symmetric and we only loop over one
* quadrant of it. Skip the weights along the y and x axis already computed by dropping the
* front index. */
for (const int y : IndexRange(size.y).drop_front(1)) {
for (const int x : IndexRange(size.x).drop_front(1)) {
const float weight = RE_filter_value(type, math::length(float2(x, y) * scale));
weights[size.x * y + x] = weight;
sum += weight * 4.0f;
}
}
/* Finally, normalize the weights. */
for (const int y : IndexRange(size.y)) {
for (const int x : IndexRange(size.x)) {
weights[size.x * y + x] /= sum;
}
}
texture_ = GPU_texture_create_2d("Weights", size.x, size.y, 1, GPU_R16F, weights.data());
type_ = type;
radius_ = radius;
}
void bind_as_texture(GPUShader *shader, const char *texture_name)
{
const int texture_image_unit = GPU_shader_get_texture_binding(shader, texture_name);
GPU_texture_bind(texture_, texture_image_unit);
}
void unbind_as_texture()
{
GPU_texture_unbind(texture_);
}
};
class BlurOperation : public NodeOperation {
private:
/* Cached symmetric blur weights. */
SymmetricBlurWeights blur_weights_;
/* Cached symmetric blur weights for the separable horizontal pass. */
SymmetricSeparableBlurWeights blur_horizontal_weights_;
/* Cached symmetric blur weights for the separable vertical pass. */
SymmetricSeparableBlurWeights blur_vertical_weights_;
public:
using NodeOperation::NodeOperation;
@ -308,13 +120,16 @@ class BlurOperation : public NodeOperation {
const Result &input_image = get_input("Image");
input_image.bind_as_texture(shader, "input_tx");
blur_weights_.update(compute_blur_radius(), node_storage(bnode()).filtertype);
blur_weights_.bind_as_texture(shader, "weights_tx");
const float2 blur_radius = compute_blur_radius();
const SymmetricBlurWeights &weights = context().cache_manager().get_symmetric_blur_weights(
node_storage(bnode()).filtertype, blur_radius);
weights.bind_as_texture(shader, "weights_tx");
Domain domain = compute_domain();
if (get_extend_bounds()) {
/* Add a radius amount of pixels in both sides of the image, hence the multiply by 2. */
domain.size += int2(math::ceil(compute_blur_radius())) * 2;
domain.size += int2(math::ceil(blur_radius)) * 2;
}
Result &output_image = get_result("Image");
@ -326,7 +141,7 @@ class BlurOperation : public NodeOperation {
GPU_shader_unbind();
output_image.unbind_as_image();
input_image.unbind_as_texture();
blur_weights_.unbind_as_texture();
weights.unbind_as_texture();
}
GPUTexture *execute_separable_blur_horizontal_pass()
@ -341,12 +156,16 @@ class BlurOperation : public NodeOperation {
const Result &input_image = get_input("Image");
input_image.bind_as_texture(shader, "input_tx");
blur_horizontal_weights_.update(compute_blur_radius().x, node_storage(bnode()).filtertype);
blur_horizontal_weights_.bind_as_texture(shader, "weights_tx");
const float2 blur_radius = compute_blur_radius();
const SymmetricSeparableBlurWeights &weights =
context().cache_manager().get_symmetric_separable_blur_weights(
node_storage(bnode()).filtertype, blur_radius.x);
weights.bind_as_texture(shader, "weights_tx");
Domain domain = compute_domain();
if (get_extend_bounds()) {
domain.size.x += int(math::ceil(compute_blur_radius().x)) * 2;
domain.size.x += int(math::ceil(blur_radius.x)) * 2;
}
/* We allocate an output image of a transposed size, that is, with a height equivalent to the
@ -367,7 +186,7 @@ class BlurOperation : public NodeOperation {
GPU_shader_unbind();
input_image.unbind_as_texture();
blur_horizontal_weights_.unbind_as_texture();
weights.unbind_as_texture();
GPU_texture_image_unbind(horizontal_pass_result);
return horizontal_pass_result;
@ -386,8 +205,12 @@ class BlurOperation : public NodeOperation {
const int texture_image_unit = GPU_shader_get_texture_binding(shader, "input_tx");
GPU_texture_bind(horizontal_pass_result, texture_image_unit);
blur_vertical_weights_.update(compute_blur_radius().y, node_storage(bnode()).filtertype);
blur_vertical_weights_.bind_as_texture(shader, "weights_tx");
const float2 blur_radius = compute_blur_radius();
const SymmetricSeparableBlurWeights &weights =
context().cache_manager().get_symmetric_separable_blur_weights(
node_storage(bnode()).filtertype, blur_radius.y);
weights.bind_as_texture(shader, "weights_tx");
Domain domain = compute_domain();
if (get_extend_bounds()) {
@ -405,7 +228,7 @@ class BlurOperation : public NodeOperation {
GPU_shader_unbind();
output_image.unbind_as_image();
blur_vertical_weights_.unbind_as_texture();
weights.unbind_as_texture();
GPU_texture_unbind(horizontal_pass_result);
}

View File

@ -5,25 +5,20 @@
* \ingroup cmpnodes
*/
#include <cmath>
#include "BLI_array.hh"
#include "BLI_assert.h"
#include "BLI_math_base.hh"
#include "DNA_scene_types.h"
#include "BLI_math_vec_types.hh"
#include "RNA_access.h"
#include "UI_interface.h"
#include "UI_resources.h"
#include "RE_pipeline.h"
#include "GPU_shader.h"
#include "GPU_state.h"
#include "GPU_texture.h"
#include "COM_morphological_distance_feather_weights.hh"
#include "COM_node_operation.hh"
#include "COM_utilities.hh"
@ -64,161 +59,7 @@ static void node_composit_buts_dilateerode(uiLayout *layout, bContext * /*C*/, P
using namespace blender::realtime_compositor;
/* Computes a falloff that is equal to 1 at an input of zero and decrease to zero at an input of 1,
* with the rate of decrease depending on the falloff type. */
static float compute_distance_falloff(float x, int falloff_type)
{
x = 1.0f - x;
switch (falloff_type) {
case PROP_SMOOTH:
return 3.0f * x * x - 2.0f * x * x * x;
case PROP_SPHERE:
return std::sqrt(2.0f * x - x * x);
case PROP_ROOT:
return std::sqrt(x);
case PROP_SHARP:
return x * x;
case PROP_INVSQUARE:
return x * (2.0f - x);
case PROP_LIN:
return x;
default:
BLI_assert_unreachable();
return x;
}
}
/* A helper class that computes and caches 1D GPU textures containing the weights of the separable
* Gaussian filter of the given radius as well as an inverse distance falloff of the given type and
* radius. The weights and falloffs are symmetric, because the Gaussian and falloff functions are
* all even functions. Consequently, only the positive half of the filter is computed and the
* shader takes that into consideration. */
class SymmetricSeparableMorphologicalDistanceFeatherWeights {
private:
int radius_ = 1;
int falloff_type_ = PROP_SMOOTH;
GPUTexture *weights_texture_ = nullptr;
GPUTexture *distance_falloffs_texture_ = nullptr;
public:
~SymmetricSeparableMorphologicalDistanceFeatherWeights()
{
if (weights_texture_) {
GPU_texture_free(weights_texture_);
}
if (distance_falloffs_texture_) {
GPU_texture_free(distance_falloffs_texture_);
}
}
/* Check if textures containing the weights and distance falloffs were already computed for the
* given distance falloff type and radius. If such textures exists, do nothing, otherwise, free
* the already computed textures and recompute it with the given distance falloff type and
* radius. */
void update(int radius, int falloff_type)
{
if (weights_texture_ && distance_falloffs_texture_ && falloff_type == falloff_type_ &&
radius == radius_) {
return;
}
radius_ = radius;
falloff_type_ = falloff_type;
compute_weights();
compute_distance_falloffs();
}
void compute_weights()
{
if (weights_texture_) {
GPU_texture_free(weights_texture_);
}
/* The size of filter is double the radius plus 1, but since the filter is symmetric, we only
* compute half of it and no doubling happens. We add 1 to make sure the filter size is always
* odd and there is a center weight. */
const int size = radius_ + 1;
Array<float> weights(size);
float sum = 0.0f;
/* First, compute the center weight. */
const float center_weight = RE_filter_value(R_FILTER_GAUSS, 0.0f);
weights[0] = center_weight;
sum += center_weight;
/* Second, compute the other weights in the positive direction, making sure to add double the
* weight to the sum of weights because the filter is symmetric and we only loop over half of
* it. Skip the center weight already computed by dropping the front index. */
const float scale = radius_ > 0.0f ? 1.0f / radius_ : 0.0f;
for (const int i : weights.index_range().drop_front(1)) {
const float weight = RE_filter_value(R_FILTER_GAUSS, i * scale);
weights[i] = weight;
sum += weight * 2.0f;
}
/* Finally, normalize the weights. */
for (const int i : weights.index_range()) {
weights[i] /= sum;
}
weights_texture_ = GPU_texture_create_1d("Weights", size, 1, GPU_R16F, weights.data());
}
void compute_distance_falloffs()
{
if (distance_falloffs_texture_) {
GPU_texture_free(distance_falloffs_texture_);
}
/* The size of the distance falloffs is double the radius plus 1, but since the falloffs are
* symmetric, we only compute half of them and no doubling happens. We add 1 to make sure the
* falloffs size is always odd and there is a center falloff. */
const int size = radius_ + 1;
Array<float> falloffs(size);
/* Compute the distance falloffs in the positive direction only, because the falloffs are
* symmetric. */
const float scale = radius_ > 0.0f ? 1.0f / radius_ : 0.0f;
for (const int i : falloffs.index_range()) {
falloffs[i] = compute_distance_falloff(i * scale, falloff_type_);
}
distance_falloffs_texture_ = GPU_texture_create_1d(
"Distance Factors", size, 1, GPU_R16F, falloffs.data());
}
void bind_weights_as_texture(GPUShader *shader, const char *texture_name)
{
const int texture_image_unit = GPU_shader_get_texture_binding(shader, texture_name);
GPU_texture_bind(weights_texture_, texture_image_unit);
}
void unbind_weights_as_texture()
{
GPU_texture_unbind(weights_texture_);
}
void bind_distance_falloffs_as_texture(GPUShader *shader, const char *texture_name)
{
const int texture_image_unit = GPU_shader_get_texture_binding(shader, texture_name);
GPU_texture_bind(distance_falloffs_texture_, texture_image_unit);
}
void unbind_distance_falloffs_as_texture()
{
GPU_texture_unbind(distance_falloffs_texture_);
}
};
class DilateErodeOperation : public NodeOperation {
private:
/* Cached symmetric blur weights and distance falloffs for the distance feature method. */
SymmetricSeparableMorphologicalDistanceFeatherWeights distance_feather_weights_;
public:
using NodeOperation::NodeOperation;
@ -414,9 +255,11 @@ class DilateErodeOperation : public NodeOperation {
const Result &input_image = get_input("Mask");
input_image.bind_as_texture(shader, "input_tx");
distance_feather_weights_.update(math::abs(get_distance()), node_storage(bnode()).falloff);
distance_feather_weights_.bind_weights_as_texture(shader, "weights_tx");
distance_feather_weights_.bind_distance_falloffs_as_texture(shader, "falloffs_tx");
const MorphologicalDistanceFeatherWeights &weights =
context().cache_manager().get_morphological_distance_feather_weights(
node_storage(bnode()).falloff, math::abs(get_distance()));
weights.bind_weights_as_texture(shader, "weights_tx");
weights.bind_distance_falloffs_as_texture(shader, "falloffs_tx");
/* We allocate an output image of a transposed size, that is, with a height equivalent to the
* width of the input and vice versa. This is done as a performance optimization. The shader
@ -437,8 +280,8 @@ class DilateErodeOperation : public NodeOperation {
GPU_shader_unbind();
input_image.unbind_as_texture();
distance_feather_weights_.unbind_weights_as_texture();
distance_feather_weights_.unbind_distance_falloffs_as_texture();
weights.unbind_weights_as_texture();
weights.unbind_distance_falloffs_as_texture();
GPU_texture_image_unbind(horizontal_pass_result);
return horizontal_pass_result;
@ -453,9 +296,11 @@ class DilateErodeOperation : public NodeOperation {
const int texture_image_unit = GPU_shader_get_texture_binding(shader, "input_tx");
GPU_texture_bind(horizontal_pass_result, texture_image_unit);
distance_feather_weights_.update(math::abs(get_distance()), node_storage(bnode()).falloff);
distance_feather_weights_.bind_weights_as_texture(shader, "weights_tx");
distance_feather_weights_.bind_distance_falloffs_as_texture(shader, "falloffs_tx");
const MorphologicalDistanceFeatherWeights &weights =
context().cache_manager().get_morphological_distance_feather_weights(
node_storage(bnode()).falloff, math::abs(get_distance()));
weights.bind_weights_as_texture(shader, "weights_tx");
weights.bind_distance_falloffs_as_texture(shader, "falloffs_tx");
const Domain domain = compute_domain();
Result &output_image = get_result("Mask");
@ -468,8 +313,8 @@ class DilateErodeOperation : public NodeOperation {
GPU_shader_unbind();
output_image.unbind_as_image();
distance_feather_weights_.unbind_weights_as_texture();
distance_feather_weights_.unbind_distance_falloffs_as_texture();
weights.unbind_weights_as_texture();
weights.unbind_distance_falloffs_as_texture();
GPU_texture_unbind(horizontal_pass_result);
}