1
1

Compare commits

...

99 Commits

Author SHA1 Message Date
eed93aaa07 make dot output more compact 2021-08-22 22:34:25 +02:00
fc0bb6cdee avoid allocating index array in some cases 2021-08-22 20:29:52 +02:00
10f2ad1556 add return instruction and initial procedure validation 2021-08-22 20:16:27 +02:00
6d77b87b13 support span buffer reuse 2021-08-22 15:07:23 +02:00
2101b46802 fix comment 2021-08-22 12:49:22 +02:00
34f6765630 Merge branch 'master' into temp-multi-function-procedure 2021-08-22 12:21:52 +02:00
721fad37a1 Fix Windows builds after Zstandard commits 2021-08-21 23:31:51 +02:00
67c29bc5a2 Use Zstandard compression for the sequencer cache
Reviewed By: campbellbarton, brecht, mont29

Differential Revision: https://developer.blender.org/D5799
2021-08-21 21:39:06 +02:00
2ea66af742 Add support for Zstandard compression for .blend files
Compressing blendfiles can help save a lot of disk space, but the slowdown
while loading and saving is a major annoyance.
Currently Blender uses Zlib (aka gzip aka Deflate) for compression, but there
are now several more modern algorithms that outperform it in every way.

In this patch, I decided for Zstandard aka Zstd for several reasons:
- It is widely supported, both in other programs and libraries as well as in
  general-purpose compression utilities on Unix
- It is extremely flexible - spanning several orders of magnitude of
  compression speeds depending on the level setting.
- It is pretty much on the Pareto frontier for all of its configurations
  (meaning that no other algorithm is both faster and more efficient).

One downside of course is that older versions of Blender will not be able to
read these files, but one can always just re-save them without compression or
decompress the file manually with an external tool.

The implementation here saves additional metadata into the compressed file in
order to allow for efficient seeking when loading. This is standard-compliant
and will be ignored by other tools that support Zstd.
If the metadata is not present (e.g. because you manually compressed a .blend
file with another tool), Blender will fall back to sequential reading.

Saving is multithreaded to improve performance. Loading is currently not
multithreaded since it's not easy to predict the access patterns of the
loading code when seeking is supported.
In the future, we might want to look into making this more predictable or
disabling seeking for the main .blend file, which would then allow for
multiple background threads that decompress data ahead of time.

The compression level was chosen to get sizes comparable to previous versions
at much higher speeds. In the future, this could be exposed as an option.

Reviewed By: campbellbarton, brecht, mont29

Differential Revision: https://developer.blender.org/D5799
2021-08-21 21:39:06 +02:00
2b170f16d6 Refactor low-level blendfile reading into separate files
Instead of handling mmap, compression etc. all directly in readfile.c, refactor
the code to use a generic FileReader.
This makes it easier to add new compression methods or similar, and allows to
reuse the logic in other places (e.g. thumbnail reading).

Reviewed By: campbellbarton, brecht, mont29

Differential Revision: https://developer.blender.org/D5799
2021-08-21 21:38:57 +02:00
34a05f39be Clang: warn about C++20 designated initializers
With the ongoing transition to C++ files, Windows build
breaks often because of designated initializers.
Now we have two compilers to catch the MSVC build error on.

Reviewed By: #platform_macos, brecht, campbellbarton
Differential Revision: https://developer.blender.org/D11940
2021-08-21 14:02:50 +05:30
0b7947e855 Cleanup: minor changes to blf_font.c
- Use early return when kerning isn't used.
- Remove early return that prevented matching acquire/release calls.
2021-08-21 17:46:50 +10:00
47e68537f8 Cleanup: organize blf_font.c functions using doxy-sections
Functions in this file were scattered and not well organized.
2021-08-21 17:41:40 +10:00
c671bfe14e Cleanup: spelling in comments & minor cleanup
Also hyphenate 'mouse-move' use doxy sections in render_update.c &
move function comment from the header to the source.
2021-08-21 13:26:54 +10:00
aed5a27755 Correct build error from 0d7aab2375 2021-08-21 13:22:47 +10:00
0d7aab2375 Refactor: BLF Kerning Cache After Use
Optimization of font kerning by only caching kerning values after a
pair is encountered. Also saves unscaled values so they don't have to
be rebuilt between font size changes.

See D12274 for more details and speed comparison.

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

Reviewed by Campbell Barton
2021-08-20 17:48:42 -07:00
b6a1bf757d DocPy: Cleanup missing newline resulting in wrong html generation 2021-08-20 14:54:46 -04:00
86622467c5 DocPy: Update Dependancies
Updates sphinx and the theme to the latest version along with any of their dependencies.

Note that we will be sticking to sphinx 4.1.1 until sphinx 4.2 for the same reasons listed in:
https://developer.blender.org/rBM8334
2021-08-20 13:46:38 -04:00
Alaska
cd8d9383e7 Fix T90804: small grammatical error in noise threshold description
Differential Revision: https://developer.blender.org/D12277
2021-08-20 17:43:24 +02:00
1b5f17b867 Cleanup, use BKE_scene_uses_cycles_experimental_features 2021-08-20 15:00:58 +02:00
6a404bc633 Cleanup, remove extra code from previous commit
This got accidentally introduced while revising dependencies between
patches for this feature, did not notice until it was too late.
2021-08-20 14:48:47 +02:00
9bfc47c933 Alembic Procedural: basic cache control settings
This adds a setting to enable data caching, and another one to set the
maximum cache size in megabytes.

When caching is enabled we load the data for the entire animation in
memory, as we already do, however, if the data exceeds the memory limit,
render is aborted.

When caching is disabled, we simply load the data for the current frame
in memory.

Ref D10197

Reviewed By: brecht

Differential Revision: https://developer.blender.org/D11163
2021-08-20 14:34:43 +02:00
c58d1acba8 improve naming 2021-08-20 14:29:01 +02:00
accf3045be Fix memory leak while processing mouse event
Assignment missed.
2021-08-20 09:06:19 -03:00
ef502127dd Fix T90795: Moving keys in Grease Pencil Dopesheet crashes Blender
`td->loc` is referenced but not initialized.
2021-08-20 08:40:37 -03:00
0ee79f304e cleanup 2021-08-20 13:38:38 +02:00
e642de3d6f Merge branch 'master' into temp-multi-function-procedure 2021-08-20 13:37:32 +02:00
0081200812 Functions: remove multi-function network
The multi-function network system was able to compose multiple
multi-functions into a new one and to evaluate that efficiently.
This functionality was heavily used by the particle nodes prototype
a year ago. However, since then we only used multi-functions
without the need to compose them in geometry nodes.

The upcoming "fields" in geometry nodes will need a way to
compose multi-functions again. Unfortunately, the code removed
in this commit was not ideal for this different kind of function
composition. I've been working on an alternative that will be added
separately when it becomes needed.

I've had to update all the function nodes, because their interface
depended on the multi-function network data structure a bit.
The actual multi-function implementations are still the same though.
2021-08-20 13:14:39 +02:00
eca5a8b695 Merge branch 'master' into temp-multi-function-procedure 2021-08-20 12:48:18 +02:00
79c79f3c70 cleanup 2021-08-20 11:37:23 +02:00
a9970d3cb9 bring back clamping in math node 2021-08-20 10:44:29 +02:00
b812f289f5 Merge branch 'master' into mf-procedure 2021-08-20 10:36:04 +02:00
215ce0fb57 fix 2021-08-19 18:19:15 +02:00
5154598845 Merge branch 'master' into mf-procedure 2021-08-19 18:11:51 +02:00
1ee80d792c cleanup 2021-08-19 18:09:52 +02:00
95284d2f1e bring back function nodes 2021-08-19 18:07:36 +02:00
7281f3eb56 start bringing back function nodes 2021-08-19 17:28:15 +02:00
607ef8f6c5 pull out multi function network 2021-08-19 16:49:00 +02:00
1ce640cc0b test vector processing 2021-08-19 16:24:32 +02:00
7bed18fdb1 support creating loops with builder 2021-08-19 15:33:35 +02:00
c827b50d40 add dummy instruction type 2021-08-19 14:06:43 +02:00
3c7e3c8e44 improve naming 2021-08-19 13:46:19 +02:00
98e38ce4f3 cleanup 2021-08-19 13:44:15 +02:00
132cf268c0 add comments 2021-08-19 13:36:52 +02:00
fd7edc9b05 cleanup 2021-08-19 13:28:15 +02:00
ecf7c90840 remove redundant utilties 2021-08-19 13:15:11 +02:00
d78a530af1 initial procedure builder 2021-08-19 13:09:41 +02:00
3ebe61db9f support evaluation on one 2021-08-19 11:22:57 +02:00
86c2f139c6 cleanup 2021-08-19 10:09:02 +02:00
3596c348eb cleanup 2021-08-19 10:07:51 +02:00
41a81474e4 refactor procedure executor 2021-08-18 20:19:12 +02:00
aa2822d137 cleanup 2021-08-18 20:18:19 +02:00
55b333d3e3 Merge branch 'master' into mf-procedure 2021-08-18 16:14:32 +02:00
00cfad8578 add utility method 2021-08-18 10:00:51 +02:00
1891c956e5 refactor variable store 2021-08-17 17:24:01 +02:00
249c050757 add single test 2021-08-17 15:27:46 +02:00
a0081046b6 cleanup instruction scheduling 2021-08-17 15:01:27 +02:00
635f73b7f1 fixes after merge 2021-08-17 14:00:06 +02:00
74fcd50e2f Merge branch 'master' into mf-procedure 2021-08-17 13:44:25 +02:00
b04a2a7be7 add utility 2021-06-13 14:42:22 +02:00
083671e8ac progress 2021-06-13 14:25:23 +02:00
78ea401e19 Merge branch 'master' into mf-procedure 2021-06-13 14:13:22 +02:00
f3ca987bce start constructing procedure from node tree 2021-06-11 13:39:38 +02:00
2245add9f8 Merge branch 'master' into mf-procedure 2021-06-11 12:59:12 +02:00
31004d7fac start with creating procedure for node tree 2021-05-31 10:51:34 +02:00
3d3f66ed41 fix merge conflicts 2021-05-29 12:14:48 +02:00
c9c0195da5 Merge branch 'master' into mf-procedure 2021-05-29 12:07:13 +02:00
70c0403858 fixes 2021-03-27 22:58:44 +01:00
8d4de82c7f initial network to procedure 2021-03-27 22:30:53 +01:00
22c51c2d51 cleanup 2021-03-27 21:22:28 +01:00
158bd7c6a0 cleanup 2021-03-27 21:18:48 +01:00
4a28d0b583 another check 2021-03-27 15:52:13 +01:00
0501e6e693 fix memory leak in test 2021-03-27 15:45:59 +01:00
8450ac09c1 comment containing things to check 2021-03-27 15:36:08 +01:00
1af00015e8 initial destruct support 2021-03-27 15:29:02 +01:00
b44c3a3125 count initializations 2021-03-27 15:12:36 +01:00
d729f1ca37 cleanup 2021-03-27 15:08:21 +01:00
0fc9f00c14 start extracting container 2021-03-27 14:53:39 +01:00
6c9b339af7 refactor variable store 2021-03-27 14:46:53 +01:00
b7a976af01 branch test 2021-03-27 14:07:23 +01:00
a689037917 initial branch instruction 2021-03-27 13:48:18 +01:00
313403c1f1 support mutable params 2021-03-27 13:33:42 +01:00
60409b8823 simplify 2021-03-27 13:19:46 +01:00
773dc2ec94 improve dot graph 2021-03-27 13:14:19 +01:00
2a98c5d06b initial execution 2021-03-27 13:06:50 +01:00
6d1b4ce3c6 simplify 2021-03-27 12:06:40 +01:00
0b2d961b70 move executor to separate file 2021-03-27 11:55:51 +01:00
d553b70470 Merge branch 'master' into mf-procedure 2021-03-27 11:52:12 +01:00
6954f2cdd7 dot export 2021-03-24 18:46:17 +01:00
8cc832110a more 2021-03-24 18:23:36 +01:00
7b8c54b5a1 more 2021-03-24 17:51:37 +01:00
e850d175b5 more 2021-03-24 17:42:18 +01:00
326f79d59b more 2021-03-24 17:39:22 +01:00
ec4954ece2 destructor 2021-03-24 17:34:14 +01:00
b30e782c82 more stuff 2021-03-24 17:30:53 +01:00
e34fe5d28e add destruct instruction 2021-03-24 16:49:52 +01:00
8581a062f1 Merge branch 'master' into mf-procedure 2021-03-24 16:47:48 +01:00
b43971e5e9 add executor class 2021-03-23 16:20:40 +01:00
855382170e initial mf procedure data structure 2021-03-23 16:18:23 +01:00
110 changed files with 5730 additions and 5098 deletions

View File

@@ -1598,6 +1598,10 @@ elseif(CMAKE_C_COMPILER_ID MATCHES "Clang")
ADD_CHECK_C_COMPILER_FLAG(C_WARNINGS C_WARN_UNUSED_PARAMETER -Wunused-parameter)
ADD_CHECK_CXX_COMPILER_FLAG(CXX_WARNINGS CXX_WARN_ALL -Wall)
# Designated initializer is a C++20 feature & breaks MSVC build. Dropping MSVC 2019 or
# updating to C++20 allows removing this.
ADD_CHECK_CXX_COMPILER_FLAG(CXX_WARNINGS CXX_CXX20_DESIGNATOR -Wc++20-designator)
ADD_CHECK_CXX_COMPILER_FLAG(CXX_WARNINGS CXX_WARN_NO_AUTOLOGICAL_COMPARE -Wno-tautological-compare)
ADD_CHECK_CXX_COMPILER_FLAG(CXX_WARNINGS CXX_WARN_NO_UNKNOWN_PRAGMAS -Wno-unknown-pragmas)
ADD_CHECK_CXX_COMPILER_FLAG(CXX_WARNINGS CXX_WARN_NO_CHAR_SUBSCRIPTS -Wno-char-subscripts)

View File

@@ -0,0 +1,66 @@
# - Find Zstd library
# Find the native Zstd includes and library
# This module defines
# ZSTD_INCLUDE_DIRS, where to find zstd.h, Set when
# ZSTD_INCLUDE_DIR is found.
# ZSTD_LIBRARIES, libraries to link against to use Zstd.
# ZSTD_ROOT_DIR, The base directory to search for Zstd.
# This can also be an environment variable.
# ZSTD_FOUND, If false, do not try to use Zstd.
#
# also defined, but not for general use are
# ZSTD_LIBRARY, where to find the Zstd library.
#=============================================================================
# Copyright 2019 Blender Foundation.
#
# Distributed under the OSI-approved BSD License (the "License");
# see accompanying file Copyright.txt for details.
#
# This software is distributed WITHOUT ANY WARRANTY; without even the
# implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
# See the License for more information.
#=============================================================================
# If ZSTD_ROOT_DIR was defined in the environment, use it.
IF(NOT ZSTD_ROOT_DIR AND NOT $ENV{ZSTD_ROOT_DIR} STREQUAL "")
SET(ZSTD_ROOT_DIR $ENV{ZSTD_ROOT_DIR})
ENDIF()
SET(_zstd_SEARCH_DIRS
${ZSTD_ROOT_DIR}
)
FIND_PATH(ZSTD_INCLUDE_DIR
NAMES
zstd.h
HINTS
${_zstd_SEARCH_DIRS}
PATH_SUFFIXES
include
)
FIND_LIBRARY(ZSTD_LIBRARY
NAMES
zstd
HINTS
${_zstd_SEARCH_DIRS}
PATH_SUFFIXES
lib64 lib
)
# handle the QUIETLY and REQUIRED arguments and set ZSTD_FOUND to TRUE if
# all listed variables are TRUE
INCLUDE(FindPackageHandleStandardArgs)
FIND_PACKAGE_HANDLE_STANDARD_ARGS(Zstd DEFAULT_MSG
ZSTD_LIBRARY ZSTD_INCLUDE_DIR)
IF(ZSTD_FOUND)
SET(ZSTD_LIBRARIES ${ZSTD_LIBRARY})
SET(ZSTD_INCLUDE_DIRS ${ZSTD_INCLUDE_DIR})
ENDIF()
MARK_AS_ADVANCED(
ZSTD_INCLUDE_DIR
ZSTD_LIBRARY
)

View File

@@ -441,6 +441,9 @@ if(WITH_HARU)
endif()
endif()
set(ZSTD_ROOT_DIR ${LIBDIR}/zstd)
find_package(Zstd REQUIRED)
if(EXISTS ${LIBDIR})
without_system_libs_end()
endif()

View File

@@ -99,6 +99,7 @@ endif()
find_package_wrapper(JPEG REQUIRED)
find_package_wrapper(PNG REQUIRED)
find_package_wrapper(ZLIB REQUIRED)
find_package_wrapper(Zstd REQUIRED)
find_package_wrapper(Freetype REQUIRED)
if(WITH_PYTHON)

View File

@@ -873,3 +873,6 @@ if(WITH_HARU)
set(WITH_HARU OFF)
endif()
endif()
set(ZSTD_INCLUDE_DIRS ${LIBDIR}/zstd/include)
set(ZSTD_LIBRARIES ${LIBDIR}/zstd/lib/zstd_static.lib)

View File

@@ -1,13 +1,13 @@
sphinx==3.5.4
sphinx==4.1.1
# Sphinx dependencies that are important
Jinja2==2.11.3
Pygments==2.9.0
docutils==0.16
Jinja2==3.0.1
Pygments==2.10.0
docutils==0.17.1
snowballstemmer==2.1.0
babel==2.9.1
requests==2.25.1
requests==2.26.0
# Only needed to match the theme used for the official documentation.
# Without this theme, the default theme will be used.
sphinx_rtd_theme==0.5.2
sphinx_rtd_theme==1.0.0rc1

View File

@@ -408,7 +408,7 @@ class CyclesRenderSettings(bpy.types.PropertyGroup):
adaptive_threshold: FloatProperty(
name="Adaptive Sampling Threshold",
description="Noise level step to stop sampling at, lower values reduce noise the cost of render time. Zero for automatic setting based on number of AA samples",
description="Noise level step to stop sampling at, lower values reduce noise at the cost of render time. Zero for automatic setting based on number of AA samples",
min=0.0, max=1.0,
default=0.0,
precision=4,

View File

@@ -523,6 +523,9 @@ void BlenderSync::sync_procedural(BL::Object &b_ob,
procedural->set_scale(cache_file.scale());
procedural->set_use_prefetch(cache_file.use_prefetch());
procedural->set_prefetch_cache_size(cache_file.prefetch_cache_size());
/* create or update existing AlembicObjects */
ustring object_path = ustring(b_mesh_cache.object_path());

View File

@@ -25,6 +25,7 @@
#include "render/shader.h"
#include "util/util_foreach.h"
#include "util/util_logging.h"
#include "util/util_progress.h"
#include "util/util_transform.h"
#include "util/util_vector.h"
@@ -211,6 +212,35 @@ void CachedData::set_time_sampling(TimeSampling time_sampling)
}
}
size_t CachedData::memory_used() const
{
size_t mem_used = 0;
mem_used += curve_first_key.memory_used();
mem_used += curve_keys.memory_used();
mem_used += curve_radius.memory_used();
mem_used += curve_shader.memory_used();
mem_used += num_ngons.memory_used();
mem_used += shader.memory_used();
mem_used += subd_creases_edge.memory_used();
mem_used += subd_creases_weight.memory_used();
mem_used += subd_face_corners.memory_used();
mem_used += subd_num_corners.memory_used();
mem_used += subd_ptex_offset.memory_used();
mem_used += subd_smooth.memory_used();
mem_used += subd_start_corner.memory_used();
mem_used += transforms.memory_used();
mem_used += triangles.memory_used();
mem_used += uv_loops.memory_used();
mem_used += vertices.memory_used();
for (const CachedAttribute &attr : attributes) {
mem_used += attr.data.memory_used();
}
return mem_used;
}
static M44d convert_yup_zup(const M44d &mtx, float scale_mult)
{
V3d scale, shear, rotation, translation;
@@ -706,6 +736,9 @@ NODE_DEFINE(AlembicProcedural)
SOCKET_NODE_ARRAY(objects, "Objects", AlembicObject::get_node_type());
SOCKET_BOOLEAN(use_prefetch, "Use Prefetch", true);
SOCKET_INT(prefetch_cache_size, "Prefetch Cache Size", 4096);
return type;
}
@@ -823,6 +856,30 @@ void AlembicProcedural::generate(Scene *scene, Progress &progress)
}
}
if (use_prefetch_is_modified()) {
if (!use_prefetch) {
for (Node *node : objects) {
AlembicObject *object = static_cast<AlembicObject *>(node);
object->clear_cache();
}
}
}
if (prefetch_cache_size_is_modified()) {
/* Check whether the current memory usage fits in the new requested size,
* abort the render if it is any higher. */
size_t memory_used = 0ul;
for (Node *node : objects) {
AlembicObject *object = static_cast<AlembicObject *>(node);
memory_used += object->get_cached_data().memory_used();
}
if (memory_used > get_prefetch_cache_size_in_bytes()) {
progress.set_error("Error: Alembic Procedural memory limit reached");
return;
}
}
build_caches(progress);
foreach (Node *node, objects) {
@@ -1300,6 +1357,8 @@ void AlembicProcedural::walk_hierarchy(
void AlembicProcedural::build_caches(Progress &progress)
{
size_t memory_used = 0;
for (Node *node : objects) {
AlembicObject *object = static_cast<AlembicObject *>(node);
@@ -1353,7 +1412,18 @@ void AlembicProcedural::build_caches(Progress &progress)
if (scale_is_modified() || object->get_cached_data().transforms.size() == 0) {
object->setup_transform_cache(object->get_cached_data(), scale);
}
memory_used += object->get_cached_data().memory_used();
if (use_prefetch) {
if (memory_used > get_prefetch_cache_size_in_bytes()) {
progress.set_error("Error: Alembic Procedural memory limit reached");
return;
}
}
}
VLOG(1) << "AlembicProcedural memory usage : " << string_human_readable_size(memory_used);
}
CCL_NAMESPACE_END

View File

@@ -272,6 +272,21 @@ template<typename T> class DataStore {
node->set(*socket, value);
}
size_t memory_used() const
{
if constexpr (is_array<T>::value) {
size_t mem_used = 0;
for (const T &array : data) {
mem_used += array.size() * sizeof(array[0]);
}
return mem_used;
}
return data.size() * sizeof(T);
}
private:
const TimeIndexPair &get_index_for_time(double time) const
{
@@ -332,6 +347,8 @@ struct CachedData {
void invalidate_last_loaded_time(bool attributes_only = false);
void set_time_sampling(Alembic::AbcCoreAbstract::TimeSampling time_sampling);
size_t memory_used() const;
};
/* Representation of an Alembic object for the AlembicProcedural.
@@ -482,6 +499,13 @@ class AlembicProcedural : public Procedural {
* software. */
NODE_SOCKET_API(float, scale)
/* Cache controls */
NODE_SOCKET_API(bool, use_prefetch)
/* Memory limit for the cache, if the data does not fit within this limit, rendering is aborted.
*/
NODE_SOCKET_API(int, prefetch_cache_size)
AlembicProcedural();
~AlembicProcedural();
@@ -531,6 +555,12 @@ class AlembicProcedural : public Procedural {
void read_subd(AlembicObject *abc_object, Alembic::AbcGeom::Abc::chrono_t frame_time);
void build_caches(Progress &progress);
size_t get_prefetch_cache_size_in_bytes() const
{
/* prefetch_cache_size is in megabytes, so convert to bytes. */
return static_cast<size_t>(prefetch_cache_size) * 1024 * 1024;
}
};
CCL_NAMESPACE_END

View File

@@ -44,9 +44,19 @@ static set<chrono_t> get_relevant_sample_times(AlembicProcedural *proc,
return result;
}
// load the data for the entire animation
const double start_frame = static_cast<double>(proc->get_start_frame());
const double end_frame = static_cast<double>(proc->get_end_frame());
double start_frame;
double end_frame;
if (proc->get_use_prefetch()) {
// load the data for the entire animation
start_frame = static_cast<double>(proc->get_start_frame());
end_frame = static_cast<double>(proc->get_end_frame());
}
else {
// load the data for the current frame
start_frame = static_cast<double>(proc->get_frame());
end_frame = start_frame;
}
const double frame_rate = static_cast<double>(proc->get_frame_rate());
const double start_time = start_frame / frame_rate;

View File

@@ -32,10 +32,10 @@ class GHOST_IWindow;
/**
* Interface class for events received from GHOST.
* You should not need to inherit this class. The system will pass these events
* to the GHOST_IEventConsumer::processEvent() method of event consumers.<br>
* Use the getType() method to retrieve the type of event and the getData()
* to the #GHOST_IEventConsumer::processEvent() method of event consumers.<br>
* Use the #getType() method to retrieve the type of event and the #getData()
* method to get the event data out. Using the event type you can cast the
* event data to the correct event dat structure.
* event data to the correct event data structure.
* \see GHOST_IEventConsumer#processEvent
* \see GHOST_TEventType
*/

View File

@@ -1741,7 +1741,7 @@ LRESULT WINAPI GHOST_SystemWin32::s_wndProc(HWND hwnd, UINT msg, WPARAM wParam,
case WM_MOUSELEAVE: {
window->m_mousePresent = false;
if (window->getTabletData().Active == GHOST_kTabletModeNone) {
processCursorEvent(window);
event = processCursorEvent(window);
}
GHOST_Wintab *wt = window->getWintab();
if (wt) {

View File

@@ -108,7 +108,6 @@ void BLF_cache_clear(void)
FontBLF *font = global_font[i];
if (font) {
blf_glyph_cache_clear(font);
blf_kerning_cache_clear(font);
}
}
}

View File

@@ -71,6 +71,38 @@ static FT_Library ft_lib;
static SpinLock ft_lib_mutex;
static SpinLock blf_glyph_cache_mutex;
/* -------------------------------------------------------------------- */
/** \name FreeType Utilities (Internal)
* \{ */
/**
* Convert a FreeType 26.6 value representing an unscaled design size to pixels.
* This is an exact copy of the scaling done inside FT_Get_Kerning when called
* with #FT_KERNING_DEFAULT, including arbitrary resizing for small fonts.
*/
static int blf_unscaled_F26Dot6_to_pixels(FontBLF *font, FT_Pos value)
{
/* Scale value by font size using integer-optimized multiplication. */
FT_Long scaled = FT_MulFix(value, font->face->size->metrics.x_scale);
/* FreeType states that this '25' has been determined heuristically. */
if (font->face->size->metrics.x_ppem < 25) {
scaled = FT_MulDiv(scaled, font->face->size->metrics.x_ppem, 25);
}
/* Copies of internal FreeType macros needed here. */
#define FT_PIX_FLOOR(x) ((x) & ~63)
#define FT_PIX_ROUND(x) FT_PIX_FLOOR((x) + 32)
/* Round to even 64ths, then divide by 64. */
return (int)FT_PIX_ROUND(scaled) >> 6;
#undef FT_PIX_FLOOR
#undef FT_PIX_ROUND
}
/** \} */
/* -------------------------------------------------------------------- */
/** \name Glyph Batching
* \{ */
@@ -257,67 +289,8 @@ static void blf_batch_draw_end(void)
/** \} */
/* -------------------------------------------------------------------- */
int blf_font_init(void)
{
memset(&g_batch, 0, sizeof(g_batch));
BLI_spin_init(&ft_lib_mutex);
BLI_spin_init(&blf_glyph_cache_mutex);
return FT_Init_FreeType(&ft_lib);
}
void blf_font_exit(void)
{
FT_Done_FreeType(ft_lib);
BLI_spin_end(&ft_lib_mutex);
BLI_spin_end(&blf_glyph_cache_mutex);
blf_batch_draw_exit();
}
void blf_font_size(FontBLF *font, unsigned int size, unsigned int dpi)
{
GlyphCacheBLF *gc;
FT_Error err;
blf_glyph_cache_acquire(font);
gc = blf_glyph_cache_find(font, size, dpi);
if (gc) {
/* Optimization: do not call FT_Set_Char_Size if size did not change. */
if (font->size == size && font->dpi == dpi) {
blf_glyph_cache_release(font);
return;
}
}
err = FT_Set_Char_Size(font->face, 0, ((FT_F26Dot6)(size)) * 64, dpi, dpi);
if (err) {
/* FIXME: here we can go through the fixed size and choice a close one */
printf("The current font don't support the size, %u and dpi, %u\n", size, dpi);
blf_glyph_cache_release(font);
return;
}
font->size = size;
font->dpi = dpi;
if (!gc) {
blf_glyph_cache_new(font);
}
blf_glyph_cache_release(font);
}
static void blf_font_ensure_ascii_kerning(FontBLF *font, GlyphCacheBLF *gc)
{
if (font->kerning_cache || !FT_HAS_KERNING(font->face)) {
return;
}
font->kerning_cache = blf_kerning_cache_find(font);
if (!font->kerning_cache) {
font->kerning_cache = blf_kerning_cache_new(font, gc);
}
}
/** \name Glyph Stepping Utilities (Internal)
* \{ */
/* Fast path for runs of ASCII characters. Given that common UTF-8
* input will consist of an overwhelming majority of ASCII
@@ -355,22 +328,40 @@ BLI_INLINE void blf_kerning_step_fast(FontBLF *font,
const uint c,
int *pen_x_p)
{
/* `blf_font_ensure_ascii_kerning(font, gc);` must be called before this function. */
BLI_assert(font->kerning_cache != NULL || !FT_HAS_KERNING(font->face));
if (!FT_HAS_KERNING(font->face) || g_prev == NULL) {
return;
}
if (g_prev != NULL && FT_HAS_KERNING(font->face)) {
if ((c_prev < KERNING_CACHE_TABLE_SIZE) && (c < GLYPH_ASCII_TABLE_SIZE)) {
*pen_x_p += font->kerning_cache->ascii_table[c][c_prev];
}
else {
FT_Vector delta;
if (FT_Get_Kerning(font->face, g_prev->idx, g->idx, FT_KERNING_DEFAULT, &delta) == 0) {
*pen_x_p += (int)delta.x >> 6;
}
}
FT_Vector delta = {KERNING_ENTRY_UNSET};
/* Get unscaled kerning value from our cache if ASCII. */
if ((c_prev < KERNING_CACHE_TABLE_SIZE) && (c < GLYPH_ASCII_TABLE_SIZE)) {
delta.x = font->kerning_cache->ascii_table[c][c_prev];
}
/* If not ASCII or not found in cache, ask FreeType for kerning. */
if (UNLIKELY(delta.x == KERNING_ENTRY_UNSET)) {
/* Note that this function sets delta values to zero on any error. */
FT_Get_Kerning(font->face, g_prev->idx, g->idx, FT_KERNING_UNSCALED, &delta);
}
/* If ASCII we save this value to our cache for quicker access next time. */
if ((c_prev < KERNING_CACHE_TABLE_SIZE) && (c < GLYPH_ASCII_TABLE_SIZE)) {
font->kerning_cache->ascii_table[c][c_prev] = (int)delta.x;
}
if (delta.x != 0) {
/* Convert unscaled design units to pixels and move pen. */
*pen_x_p += blf_unscaled_F26Dot6_to_pixels(font, delta.x);
}
}
/** \} */
/* -------------------------------------------------------------------- */
/** \name Text Drawing: GPU
* \{ */
static void blf_font_draw_ex(FontBLF *font,
GlyphCacheBLF *gc,
const char *str,
@@ -388,8 +379,6 @@ static void blf_font_draw_ex(FontBLF *font,
return;
}
blf_font_ensure_ascii_kerning(font, gc);
blf_batch_draw_begin(font);
while ((i < len) && str[i]) {
@@ -435,8 +424,6 @@ static void blf_font_draw_ascii_ex(
GlyphCacheBLF *gc = blf_glyph_cache_acquire(font);
blf_font_ensure_ascii_kerning(font, gc);
blf_batch_draw_begin(font);
while ((c = *(str++)) && len--) {
@@ -515,6 +502,12 @@ int blf_font_draw_mono(FontBLF *font, const char *str, size_t len, int cwidth)
return columns;
}
/** \} */
/* -------------------------------------------------------------------- */
/** \name Text Drawgin: Buffer
* \{ */
/* Sanity checks are done by BLF_draw_buffer() */
static void blf_font_draw_buffer_ex(FontBLF *font,
GlyphCacheBLF *gc,
@@ -536,8 +529,6 @@ static void blf_font_draw_buffer_ex(FontBLF *font,
int chx, chy;
int y, x;
blf_font_ensure_ascii_kerning(font, gc);
/* another buffer specific call for color conversion */
while ((i < len) && str[i]) {
@@ -662,6 +653,16 @@ void blf_font_draw_buffer(FontBLF *font, const char *str, size_t len, struct Res
blf_glyph_cache_release(font);
}
/** \} */
/* -------------------------------------------------------------------- */
/** \name Text Evaluation: Width to Sting Length
*
* Use to implement exported functions:
* - #BLF_width_to_strlen
* - #BLF_width_to_rstrlen
* \{ */
static bool blf_font_width_to_strlen_glyph_process(FontBLF *font,
const uint c_prev,
const uint c,
@@ -694,8 +695,6 @@ size_t blf_font_width_to_strlen(
GlyphCacheBLF *gc = blf_glyph_cache_acquire(font);
const int width_i = (int)width;
blf_font_ensure_ascii_kerning(font, gc);
for (i_prev = i = 0, width_new = pen_x = 0, g_prev = NULL, c_prev = 0; (i < len) && str[i];
i_prev = i, width_new = pen_x, c_prev = c, g_prev = g) {
g = blf_utf8_next_fast(font, gc, str, &i, &c);
@@ -725,8 +724,6 @@ size_t blf_font_width_to_rstrlen(
GlyphCacheBLF *gc = blf_glyph_cache_acquire(font);
const int width_i = (int)width;
blf_font_ensure_ascii_kerning(font, gc);
i = BLI_strnlen(str, len);
s = BLI_str_find_prev_char_utf8(str, &str[i]);
i = (size_t)((s != NULL) ? s - str : 0);
@@ -759,6 +756,12 @@ size_t blf_font_width_to_rstrlen(
return i;
}
/** \} */
/* -------------------------------------------------------------------- */
/** \name Text Evaluation: Glyph Bound Box with Callback
* \{ */
static void blf_font_boundbox_ex(FontBLF *font,
GlyphCacheBLF *gc,
const char *str,
@@ -778,8 +781,6 @@ static void blf_font_boundbox_ex(FontBLF *font,
box->ymin = 32000.0f;
box->ymax = -32000.0f;
blf_font_ensure_ascii_kerning(font, gc);
while ((i < len) && str[i]) {
g = blf_utf8_next_fast(font, gc, str, &i, &c);
@@ -835,8 +836,165 @@ void blf_font_boundbox(
blf_glyph_cache_release(font);
}
void blf_font_width_and_height(FontBLF *font,
const char *str,
size_t len,
float *r_width,
float *r_height,
struct ResultBLF *r_info)
{
float xa, ya;
rctf box;
if (font->flags & BLF_ASPECT) {
xa = font->aspect[0];
ya = font->aspect[1];
}
else {
xa = 1.0f;
ya = 1.0f;
}
if (font->flags & BLF_WORD_WRAP) {
blf_font_boundbox__wrap(font, str, len, &box, r_info);
}
else {
blf_font_boundbox(font, str, len, &box, r_info);
}
*r_width = (BLI_rctf_size_x(&box) * xa);
*r_height = (BLI_rctf_size_y(&box) * ya);
}
float blf_font_width(FontBLF *font, const char *str, size_t len, struct ResultBLF *r_info)
{
float xa;
rctf box;
if (font->flags & BLF_ASPECT) {
xa = font->aspect[0];
}
else {
xa = 1.0f;
}
if (font->flags & BLF_WORD_WRAP) {
blf_font_boundbox__wrap(font, str, len, &box, r_info);
}
else {
blf_font_boundbox(font, str, len, &box, r_info);
}
return BLI_rctf_size_x(&box) * xa;
}
float blf_font_height(FontBLF *font, const char *str, size_t len, struct ResultBLF *r_info)
{
float ya;
rctf box;
if (font->flags & BLF_ASPECT) {
ya = font->aspect[1];
}
else {
ya = 1.0f;
}
if (font->flags & BLF_WORD_WRAP) {
blf_font_boundbox__wrap(font, str, len, &box, r_info);
}
else {
blf_font_boundbox(font, str, len, &box, r_info);
}
return BLI_rctf_size_y(&box) * ya;
}
float blf_font_fixed_width(FontBLF *font)
{
const unsigned int c = ' ';
GlyphCacheBLF *gc = blf_glyph_cache_acquire(font);
GlyphBLF *g = blf_glyph_search(gc, c);
if (!g) {
g = blf_glyph_add(font, gc, FT_Get_Char_Index(font->face, c), c);
/* if we don't find the glyph. */
if (!g) {
blf_glyph_cache_release(font);
return 0.0f;
}
}
blf_glyph_cache_release(font);
return g->advance;
}
static void blf_font_boundbox_foreach_glyph_ex(FontBLF *font,
GlyphCacheBLF *gc,
const char *str,
size_t len,
BLF_GlyphBoundsFn user_fn,
void *user_data,
struct ResultBLF *r_info,
int pen_y)
{
unsigned int c, c_prev = BLI_UTF8_ERR;
GlyphBLF *g, *g_prev = NULL;
int pen_x = 0;
size_t i = 0, i_curr;
rcti gbox;
if (len == 0) {
/* early output. */
return;
}
while ((i < len) && str[i]) {
i_curr = i;
g = blf_utf8_next_fast(font, gc, str, &i, &c);
if (UNLIKELY(c == BLI_UTF8_ERR)) {
break;
}
if (UNLIKELY(g == NULL)) {
continue;
}
blf_kerning_step_fast(font, g_prev, g, c_prev, c, &pen_x);
gbox.xmin = pen_x;
gbox.xmax = gbox.xmin + MIN2(g->advance_i, g->dims[0]);
gbox.ymin = pen_y;
gbox.ymax = gbox.ymin - g->dims[1];
pen_x += g->advance_i;
if (user_fn(str, i_curr, &gbox, g->advance_i, &g->box, g->pos, user_data) == false) {
break;
}
g_prev = g;
c_prev = c;
}
if (r_info) {
r_info->lines = 1;
r_info->width = pen_x;
}
}
void blf_font_boundbox_foreach_glyph(FontBLF *font,
const char *str,
size_t len,
BLF_GlyphBoundsFn user_fn,
void *user_data,
struct ResultBLF *r_info)
{
GlyphCacheBLF *gc = blf_glyph_cache_acquire(font);
blf_font_boundbox_foreach_glyph_ex(font, gc, str, len, user_fn, user_data, r_info, 0);
blf_glyph_cache_release(font);
}
/** \} */
/* -------------------------------------------------------------------- */
/** \name Word-Wrap Support
/** \name Text Evaluation: Word-Wrap with Callback
* \{ */
/**
@@ -869,8 +1027,6 @@ static void blf_font_wrap_apply(FontBLF *font,
GlyphCacheBLF *gc = blf_glyph_cache_acquire(font);
blf_font_ensure_ascii_kerning(font, gc);
struct WordWrapVars {
int wrap_width;
size_t start, last[2];
@@ -1008,169 +1164,10 @@ void blf_font_draw_buffer__wrap(FontBLF *font,
/** \} */
void blf_font_width_and_height(FontBLF *font,
const char *str,
size_t len,
float *r_width,
float *r_height,
struct ResultBLF *r_info)
{
float xa, ya;
rctf box;
if (font->flags & BLF_ASPECT) {
xa = font->aspect[0];
ya = font->aspect[1];
}
else {
xa = 1.0f;
ya = 1.0f;
}
if (font->flags & BLF_WORD_WRAP) {
blf_font_boundbox__wrap(font, str, len, &box, r_info);
}
else {
blf_font_boundbox(font, str, len, &box, r_info);
}
*r_width = (BLI_rctf_size_x(&box) * xa);
*r_height = (BLI_rctf_size_y(&box) * ya);
}
float blf_font_width(FontBLF *font, const char *str, size_t len, struct ResultBLF *r_info)
{
float xa;
rctf box;
if (font->flags & BLF_ASPECT) {
xa = font->aspect[0];
}
else {
xa = 1.0f;
}
if (font->flags & BLF_WORD_WRAP) {
blf_font_boundbox__wrap(font, str, len, &box, r_info);
}
else {
blf_font_boundbox(font, str, len, &box, r_info);
}
return BLI_rctf_size_x(&box) * xa;
}
float blf_font_height(FontBLF *font, const char *str, size_t len, struct ResultBLF *r_info)
{
float ya;
rctf box;
if (font->flags & BLF_ASPECT) {
ya = font->aspect[1];
}
else {
ya = 1.0f;
}
if (font->flags & BLF_WORD_WRAP) {
blf_font_boundbox__wrap(font, str, len, &box, r_info);
}
else {
blf_font_boundbox(font, str, len, &box, r_info);
}
return BLI_rctf_size_y(&box) * ya;
}
float blf_font_fixed_width(FontBLF *font)
{
const unsigned int c = ' ';
GlyphCacheBLF *gc = blf_glyph_cache_acquire(font);
GlyphBLF *g = blf_glyph_search(gc, c);
if (!g) {
g = blf_glyph_add(font, gc, FT_Get_Char_Index(font->face, c), c);
/* if we don't find the glyph. */
if (!g) {
blf_glyph_cache_release(font);
return 0.0f;
}
}
blf_glyph_cache_release(font);
return g->advance;
}
/* -------------------------------------------------------------------- */
/** \name Glyph Bound Box with Callback
/** \name Text Evaluation: Count Missing Characters
* \{ */
static void blf_font_boundbox_foreach_glyph_ex(FontBLF *font,
GlyphCacheBLF *gc,
const char *str,
size_t len,
BLF_GlyphBoundsFn user_fn,
void *user_data,
struct ResultBLF *r_info,
int pen_y)
{
unsigned int c, c_prev = BLI_UTF8_ERR;
GlyphBLF *g, *g_prev = NULL;
int pen_x = 0;
size_t i = 0, i_curr;
rcti gbox;
if (len == 0) {
/* early output. */
return;
}
blf_font_ensure_ascii_kerning(font, gc);
while ((i < len) && str[i]) {
i_curr = i;
g = blf_utf8_next_fast(font, gc, str, &i, &c);
if (UNLIKELY(c == BLI_UTF8_ERR)) {
break;
}
if (UNLIKELY(g == NULL)) {
continue;
}
blf_kerning_step_fast(font, g_prev, g, c_prev, c, &pen_x);
gbox.xmin = pen_x;
gbox.xmax = gbox.xmin + MIN2(g->advance_i, g->dims[0]);
gbox.ymin = pen_y;
gbox.ymax = gbox.ymin - g->dims[1];
pen_x += g->advance_i;
if (user_fn(str, i_curr, &gbox, g->advance_i, &g->box, g->pos, user_data) == false) {
break;
}
g_prev = g;
c_prev = c;
}
if (r_info) {
r_info->lines = 1;
r_info->width = pen_x;
}
}
void blf_font_boundbox_foreach_glyph(FontBLF *font,
const char *str,
size_t len,
BLF_GlyphBoundsFn user_fn,
void *user_data,
struct ResultBLF *r_info)
{
GlyphCacheBLF *gc = blf_glyph_cache_acquire(font);
blf_font_boundbox_foreach_glyph_ex(font, gc, str, len, user_fn, user_data, r_info, 0);
blf_glyph_cache_release(font);
}
/** \} */
int blf_font_count_missing_chars(FontBLF *font,
const char *str,
const size_t len,
@@ -1196,29 +1193,92 @@ int blf_font_count_missing_chars(FontBLF *font,
return missing;
}
void blf_font_free(FontBLF *font)
/** \} */
/* -------------------------------------------------------------------- */
/** \name Font Query: Attributes
* \{ */
int blf_font_height_max(FontBLF *font)
{
BLI_spin_lock(&blf_glyph_cache_mutex);
GlyphCacheBLF *gc;
int height_max;
while ((gc = BLI_pophead(&font->cache))) {
blf_glyph_cache_free(gc);
}
GlyphCacheBLF *gc = blf_glyph_cache_acquire(font);
height_max = gc->glyph_height_max;
blf_kerning_cache_clear(font);
FT_Done_Face(font->face);
if (font->filename) {
MEM_freeN(font->filename);
}
if (font->name) {
MEM_freeN(font->name);
}
MEM_freeN(font);
BLI_spin_unlock(&blf_glyph_cache_mutex);
blf_glyph_cache_release(font);
return height_max;
}
int blf_font_width_max(FontBLF *font)
{
int width_max;
GlyphCacheBLF *gc = blf_glyph_cache_acquire(font);
width_max = gc->glyph_width_max;
blf_glyph_cache_release(font);
return width_max;
}
float blf_font_descender(FontBLF *font)
{
float descender;
GlyphCacheBLF *gc = blf_glyph_cache_acquire(font);
descender = gc->descender;
blf_glyph_cache_release(font);
return descender;
}
float blf_font_ascender(FontBLF *font)
{
float ascender;
GlyphCacheBLF *gc = blf_glyph_cache_acquire(font);
ascender = gc->ascender;
blf_glyph_cache_release(font);
return ascender;
}
char *blf_display_name(FontBLF *font)
{
if (!font->face->family_name) {
return NULL;
}
return BLI_sprintfN("%s %s", font->face->family_name, font->face->style_name);
}
/** \} */
/* -------------------------------------------------------------------- */
/** \name Font Subsystem Init/Exit
* \{ */
int blf_font_init(void)
{
memset(&g_batch, 0, sizeof(g_batch));
BLI_spin_init(&ft_lib_mutex);
BLI_spin_init(&blf_glyph_cache_mutex);
return FT_Init_FreeType(&ft_lib);
}
void blf_font_exit(void)
{
FT_Done_FreeType(ft_lib);
BLI_spin_end(&ft_lib_mutex);
BLI_spin_end(&blf_glyph_cache_mutex);
blf_batch_draw_exit();
}
/** \} */
/* -------------------------------------------------------------------- */
/** \name Font New/Free
* \{ */
static void blf_font_fill(FontBLF *font)
{
font->aspect[0] = 1.0f;
@@ -1246,7 +1306,6 @@ static void blf_font_fill(FontBLF *font)
font->dpi = 0;
font->size = 0;
BLI_listbase_clear(&font->cache);
BLI_listbase_clear(&font->kerning_caches);
font->kerning_cache = NULL;
#if BLF_BLUR_ENABLE
font->blur = 0;
@@ -1307,6 +1366,17 @@ FontBLF *blf_font_new(const char *name, const char *filename)
font->name = BLI_strdup(name);
font->filename = BLI_strdup(filename);
blf_font_fill(font);
if (FT_HAS_KERNING(font->face)) {
/* Create kerning cache table and fill with value indicating "unset". */
font->kerning_cache = MEM_mallocN(sizeof(KerningCacheBLF), __func__);
for (uint i = 0; i < KERNING_CACHE_TABLE_SIZE; i++) {
for (uint j = 0; j < KERNING_CACHE_TABLE_SIZE; j++) {
font->kerning_cache->ascii_table[i][j] = KERNING_ENTRY_UNSET;
}
}
}
return font;
}
@@ -1346,54 +1416,61 @@ FontBLF *blf_font_new_from_mem(const char *name, const unsigned char *mem, int m
return font;
}
int blf_font_height_max(FontBLF *font)
void blf_font_free(FontBLF *font)
{
int height_max;
BLI_spin_lock(&blf_glyph_cache_mutex);
GlyphCacheBLF *gc;
GlyphCacheBLF *gc = blf_glyph_cache_acquire(font);
height_max = gc->glyph_height_max;
blf_glyph_cache_release(font);
return height_max;
}
int blf_font_width_max(FontBLF *font)
{
int width_max;
GlyphCacheBLF *gc = blf_glyph_cache_acquire(font);
width_max = gc->glyph_width_max;
blf_glyph_cache_release(font);
return width_max;
}
float blf_font_descender(FontBLF *font)
{
float descender;
GlyphCacheBLF *gc = blf_glyph_cache_acquire(font);
descender = gc->descender;
blf_glyph_cache_release(font);
return descender;
}
float blf_font_ascender(FontBLF *font)
{
float ascender;
GlyphCacheBLF *gc = blf_glyph_cache_acquire(font);
ascender = gc->ascender;
blf_glyph_cache_release(font);
return ascender;
}
char *blf_display_name(FontBLF *font)
{
if (!font->face->family_name) {
return NULL;
while ((gc = BLI_pophead(&font->cache))) {
blf_glyph_cache_free(gc);
}
return BLI_sprintfN("%s %s", font->face->family_name, font->face->style_name);
if (font->kerning_cache) {
MEM_freeN(font->kerning_cache);
}
FT_Done_Face(font->face);
if (font->filename) {
MEM_freeN(font->filename);
}
if (font->name) {
MEM_freeN(font->name);
}
MEM_freeN(font);
BLI_spin_unlock(&blf_glyph_cache_mutex);
}
/** \} */
/* -------------------------------------------------------------------- */
/** \name Font Configure
* \{ */
void blf_font_size(FontBLF *font, unsigned int size, unsigned int dpi)
{
blf_glyph_cache_acquire(font);
GlyphCacheBLF *gc = blf_glyph_cache_find(font, size, dpi);
if (gc && (font->size == size && font->dpi == dpi)) {
/* Optimization: do not call FT_Set_Char_Size if size did not change. */
}
else {
const FT_Error err = FT_Set_Char_Size(font->face, 0, ((FT_F26Dot6)(size)) * 64, dpi, dpi);
if (err) {
/* FIXME: here we can go through the fixed size and choice a close one */
printf("The current font don't support the size, %u and dpi, %u\n", size, dpi);
}
else {
font->size = size;
font->dpi = dpi;
if (gc == NULL) {
blf_glyph_cache_new(font);
}
}
}
blf_glyph_cache_release(font);
}
/** \} */

View File

@@ -55,56 +55,6 @@
#include "BLI_math_vector.h"
#include "BLI_strict_flags.h"
KerningCacheBLF *blf_kerning_cache_find(FontBLF *font)
{
return (KerningCacheBLF *)font->kerning_caches.first;
}
/* Create a new glyph cache for the current kerning mode. */
KerningCacheBLF *blf_kerning_cache_new(FontBLF *font, GlyphCacheBLF *gc)
{
KerningCacheBLF *kc = MEM_mallocN(sizeof(KerningCacheBLF), __func__);
kc->next = NULL;
kc->prev = NULL;
GlyphBLF *g_table[KERNING_CACHE_TABLE_SIZE];
for (uint i = 0; i < KERNING_CACHE_TABLE_SIZE; i++) {
GlyphBLF *g = blf_glyph_search(gc, i);
if (UNLIKELY(g == NULL)) {
FT_UInt glyph_index = FT_Get_Char_Index(font->face, i);
g = blf_glyph_add(font, gc, glyph_index, i);
}
g_table[i] = g;
}
memset(kc->ascii_table, 0, sizeof(kc->ascii_table));
for (uint i = 0; i < KERNING_CACHE_TABLE_SIZE; i++) {
GlyphBLF *g = g_table[i];
if (g == NULL) {
continue;
}
for (uint j = 0; j < KERNING_CACHE_TABLE_SIZE; j++) {
GlyphBLF *g_prev = g_table[j];
if (g_prev == NULL) {
continue;
}
FT_Vector delta;
if (FT_Get_Kerning(font->face, g_prev->idx, g->idx, FT_KERNING_DEFAULT, &delta) == 0) {
kc->ascii_table[i][j] = (int)delta.x >> 6;
}
}
}
BLI_addhead(&font->kerning_caches, kc);
return kc;
}
void blf_kerning_cache_clear(FontBLF *font)
{
font->kerning_cache = NULL;
BLI_freelistN(&font->kerning_caches);
}
GlyphCacheBLF *blf_glyph_cache_find(FontBLF *font, unsigned int size, unsigned int dpi)
{
GlyphCacheBLF *p;

View File

@@ -121,10 +121,6 @@ int blf_font_count_missing_chars(struct FontBLF *font,
void blf_font_free(struct FontBLF *font);
struct KerningCacheBLF *blf_kerning_cache_find(struct FontBLF *font);
struct KerningCacheBLF *blf_kerning_cache_new(struct FontBLF *font, struct GlyphCacheBLF *gc);
void blf_kerning_cache_clear(struct FontBLF *font);
struct GlyphCacheBLF *blf_glyph_cache_find(struct FontBLF *font,
unsigned int size,
unsigned int dpi);

View File

@@ -34,6 +34,9 @@
/* Number of characters in KerningCacheBLF.table. */
#define KERNING_CACHE_TABLE_SIZE 128
/* A value in the kerning cache that indicates it is not yet set. */
#define KERNING_ENTRY_UNSET INT_MAX
typedef struct BatchBLF {
struct FontBLF *font; /* can only batch glyph from the same font */
struct GPUBatch *batch;
@@ -50,7 +53,6 @@ typedef struct BatchBLF {
extern BatchBLF g_batch;
typedef struct KerningCacheBLF {
struct KerningCacheBLF *next, *prev;
/**
* Cache a ascii glyph pairs. Only store the x offset we are interested in,
* instead of the full #FT_Vector since it's not used for drawing at the moment.
@@ -223,10 +225,7 @@ typedef struct FontBLF {
*/
ListBase cache;
/* list of kerning cache for this font. */
ListBase kerning_caches;
/* current kerning cache for this font and kerning mode. */
/* Cache of unscaled kerning values. Will be NULL if font does not have kerning. */
KerningCacheBLF *kerning_cache;
/* freetype2 lib handle. */

View File

@@ -61,11 +61,6 @@ void BKE_cachefile_reader_open(struct CacheFile *cache_file,
const char *object_path);
void BKE_cachefile_reader_free(struct CacheFile *cache_file, struct CacheReader **reader);
/* Determine whether the CacheFile should use a render engine procedural. If so, data is not read
* from the file and bouding boxes are used to represent the objects in the Scene. Render engines
* will receive the bounding box as a placeholder but can instead load the data directly if they
* support it.
*/
bool BKE_cache_file_uses_render_procedural(const struct CacheFile *cache_file,
struct Scene *scene,
const int dag_eval_mode);

View File

@@ -111,8 +111,7 @@ typedef struct bNodeSocketTemplate {
#ifdef __cplusplus
namespace blender {
namespace nodes {
class SocketMFNetworkBuilder;
class NodeMFNetworkBuilder;
class NodeMultiFunctionBuilder;
class GeoNodeExecParams;
} // namespace nodes
namespace fn {
@@ -121,18 +120,16 @@ class MFDataType;
} // namespace fn
} // namespace blender
using NodeExpandInMFNetworkFunction = void (*)(blender::nodes::NodeMFNetworkBuilder &builder);
using NodeMultiFunctionBuildFunction = void (*)(blender::nodes::NodeMultiFunctionBuilder &builder);
using NodeGeometryExecFunction = void (*)(blender::nodes::GeoNodeExecParams params);
using SocketGetCPPTypeFunction = const blender::fn::CPPType *(*)();
using SocketGetCPPValueFunction = void (*)(const struct bNodeSocket &socket, void *r_value);
using SocketGetGeometryNodesCPPTypeFunction = const blender::fn::CPPType *(*)();
using SocketGetGeometryNodesCPPValueFunction = void (*)(const struct bNodeSocket &socket,
void *r_value);
using SocketExpandInMFNetworkFunction = void (*)(blender::nodes::SocketMFNetworkBuilder &builder);
#else
typedef void *NodeExpandInMFNetworkFunction;
typedef void *SocketExpandInMFNetworkFunction;
typedef void *NodeMultiFunctionBuildFunction;
typedef void *NodeGeometryExecFunction;
typedef void *SocketGetCPPTypeFunction;
typedef void *SocketGetGeometryNodesCPPTypeFunction;
@@ -196,8 +193,6 @@ typedef struct bNodeSocketType {
/* Callback to free the socket type. */
void (*free_self)(struct bNodeSocketType *stype);
/* Expands the socket into a multi-function node that outputs the socket value. */
SocketExpandInMFNetworkFunction expand_in_mf_network;
/* Return the CPPType of this socket. */
SocketGetCPPTypeFunction get_base_cpp_type;
/* Get the value of this socket in a generic way. */
@@ -332,8 +327,8 @@ typedef struct bNodeType {
/* gpu */
NodeGPUExecFunction gpu_fn;
/* Expands the bNode into nodes in a multi-function network, which will be evaluated later on. */
NodeExpandInMFNetworkFunction expand_in_mf_network;
/* Build a multi-function for this node. */
NodeMultiFunctionBuildFunction build_multi_function;
/* Execute a geometry node. */
NodeGeometryExecFunction geometry_node_execute;

View File

@@ -368,7 +368,7 @@ void BKE_cachefile_eval(Main *bmain, Depsgraph *depsgraph, CacheFile *cache_file
#endif
if (DEG_is_active(depsgraph)) {
/* Flush object paths back to original datablock for UI. */
/* Flush object paths back to original data-block for UI. */
CacheFile *cache_file_orig = (CacheFile *)DEG_get_original_id(&cache_file->id);
BLI_freelistN(&cache_file_orig->object_paths);
BLI_duplicatelist(&cache_file_orig->object_paths, &cache_file->object_paths);
@@ -411,6 +411,12 @@ float BKE_cachefile_time_offset(const CacheFile *cache_file, const float time, c
return cache_file->is_sequence ? frame : frame / fps - time_offset;
}
/**
* Determine whether the #CacheFile should use a render engine procedural. If so, data is not read
* from the file and bounding boxes are used to represent the objects in the Scene.
* Render engines will receive the bounding box as a placeholder but can instead
* load the data directly if they support it.
*/
bool BKE_cache_file_uses_render_procedural(const CacheFile *cache_file,
Scene *scene,
const int dag_eval_mode)

View File

@@ -49,14 +49,10 @@
#include "BKE_simulation.h"
#include "NOD_geometry.h"
#include "NOD_node_tree_multi_function.hh"
#include "BLI_map.hh"
#include "BLT_translation.h"
#include "FN_multi_function_network_evaluation.hh"
#include "FN_multi_function_network_optimization.hh"
#include "DEG_depsgraph.h"
#include "DEG_depsgraph_query.h"

View File

@@ -154,18 +154,17 @@ bool BLI_file_is_writable(const char *file) ATTR_WARN_UNUSED_RESULT ATTR_NONNULL
bool BLI_file_touch(const char *file) ATTR_NONNULL();
bool BLI_file_alias_target(const char *filepath, char *r_targetpath) ATTR_WARN_UNUSED_RESULT;
#if 0 /* UNUSED */
int BLI_file_gzip(const char *from, const char *to) ATTR_WARN_UNUSED_RESULT ATTR_NONNULL();
#endif
char *BLI_file_ungzip_to_mem(const char *from_file, int *r_size) ATTR_WARN_UNUSED_RESULT
ATTR_NONNULL();
size_t BLI_gzip_mem_to_file_at_pos(void *buf,
size_t len,
FILE *file,
size_t gz_stream_offset,
int compression_level) ATTR_WARN_UNUSED_RESULT ATTR_NONNULL();
size_t BLI_ungzip_file_to_mem_at_pos(void *buf, size_t len, FILE *file, size_t gz_stream_offset)
bool BLI_file_magic_is_gzip(const char header[4]);
size_t BLI_file_zstd_from_mem_at_pos(void *buf,
size_t len,
FILE *file,
size_t file_offset,
int compression_level) ATTR_WARN_UNUSED_RESULT ATTR_NONNULL();
size_t BLI_file_unzstd_to_mem_at_pos(void *buf, size_t len, FILE *file, size_t file_offset)
ATTR_WARN_UNUSED_RESULT ATTR_NONNULL();
bool BLI_file_magic_is_zstd(const char header[4]);
size_t BLI_file_descriptor_size(int file) ATTR_WARN_UNUSED_RESULT;
size_t BLI_file_size(const char *path) ATTR_WARN_UNUSED_RESULT ATTR_NONNULL();

View File

@@ -0,0 +1,81 @@
/*
* 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) 2001-2002 by NaN Holding BV.
* All rights reserved.
*/
/** \file
* \ingroup bli
* \brief Wrapper for reading from various sources (e.g. raw files, compressed files, memory...).
*/
#pragma once
#ifdef WIN32
# include "BLI_winstuff.h"
#else
# include <sys/types.h>
#endif
#include "BLI_compiler_attrs.h"
#include "BLI_utildefines.h"
#if defined(_MSC_VER) || defined(__APPLE__) || defined(__HAIKU__) || defined(__NetBSD__)
typedef int64_t off64_t;
#endif
#ifdef __cplusplus
extern "C" {
#endif
struct FileReader;
typedef ssize_t (*FileReaderReadFn)(struct FileReader *reader, void *buffer, size_t size);
typedef off64_t (*FileReaderSeekFn)(struct FileReader *reader, off64_t offset, int whence);
typedef void (*FileReaderCloseFn)(struct FileReader *reader);
/* General structure for all FileReaders, implementations add custom fields at the end. */
typedef struct FileReader {
FileReaderReadFn read;
FileReaderSeekFn seek;
FileReaderCloseFn close;
off64_t offset;
} FileReader;
/* Functions for opening the various types of FileReader.
* They either succeed and return a valid FileReader, or fail and return NULL.
*
* If a FileReader is created, it has to be cleaned up and freed by calling
* its close() function unless another FileReader has taken ownership - for example,
* Zstd and Gzip take over the base FileReader and will clean it up when their clean() is called.
*/
/* Create FileReader from raw file descriptor. */
FileReader *BLI_filereader_new_file(int filedes) ATTR_WARN_UNUSED_RESULT;
/* Create FileReader from raw file descriptor using memory-mapped IO. */
FileReader *BLI_filereader_new_mmap(int filedes) ATTR_WARN_UNUSED_RESULT;
/* Create FileReader from a region of memory. */
FileReader *BLI_filereader_new_memory(const void *data, size_t len) ATTR_WARN_UNUSED_RESULT
ATTR_NONNULL();
/* Create FileReader from applying Zstd decompression on an underlying file. */
FileReader *BLI_filereader_new_zstd(FileReader *base) ATTR_WARN_UNUSED_RESULT ATTR_NONNULL();
/* Create FileReader from applying Gzip decompression on an underlying file. */
FileReader *BLI_filereader_new_gzip(FileReader *base) ATTR_WARN_UNUSED_RESULT ATTR_NONNULL();
#ifdef __cplusplus
}
#endif

View File

@@ -31,6 +31,7 @@ set(INC
set(INC_SYS
${ZLIB_INCLUDE_DIRS}
${ZSTD_INCLUDE_DIRS}
${FREETYPE_INCLUDE_DIRS}
${GMP_INCLUDE_DIRS}
)
@@ -75,6 +76,10 @@ set(SRC
intern/endian_switch.c
intern/expr_pylike_eval.c
intern/fileops.c
intern/filereader_file.c
intern/filereader_gzip.c
intern/filereader_memory.c
intern/filereader_zstd.c
intern/fnmatch.c
intern/freetypefont.c
intern/gsqueue.c
@@ -194,6 +199,7 @@ set(SRC
BLI_enumerable_thread_specific.hh
BLI_expr_pylike_eval.h
BLI_fileops.h
BLI_filereader.h
BLI_fileops_types.h
BLI_float2.hh
BLI_float3.hh
@@ -323,6 +329,7 @@ set(LIB
${FREETYPE_LIBRARY}
${ZLIB_LIBRARIES}
${ZSTD_LIBRARIES}
)
if(WITH_MEM_VALGRIND)

View File

@@ -31,6 +31,7 @@
#include <errno.h>
#include "zlib.h"
#include "zstd.h"
#ifdef WIN32
# include "BLI_fileops_types.h"
@@ -61,199 +62,123 @@
#include "BLI_sys_types.h" /* for intptr_t support */
#include "BLI_utildefines.h"
#if 0 /* UNUSED */
/* gzip the file in from and write it to "to".
* return -1 if zlib fails, -2 if the originating file does not exist
* NOTE: will remove the "from" file
*/
int BLI_file_gzip(const char *from, const char *to)
size_t BLI_file_zstd_from_mem_at_pos(
void *buf, size_t len, FILE *file, size_t file_offset, int compression_level)
{
char buffer[10240];
int file;
int readsize = 0;
int rval = 0, err;
gzFile gzfile;
fseek(file, file_offset, SEEK_SET);
/* level 1 is very close to 3 (the default) in terms of file size,
* but about twice as fast, best use for speedy saving - campbell */
gzfile = BLI_gzopen(to, "wb1");
if (gzfile == NULL) {
return -1;
}
file = BLI_open(from, O_BINARY | O_RDONLY, 0);
if (file == -1) {
return -2;
}
ZSTD_CCtx *ctx = ZSTD_createCCtx();
ZSTD_CCtx_setParameter(ctx, ZSTD_c_compressionLevel, compression_level);
while (1) {
readsize = read(file, buffer, sizeof(buffer));
ZSTD_inBuffer input = {buf, len, 0};
if (readsize < 0) {
rval = -2; /* error happened in reading */
fprintf(stderr, "Error reading file %s: %s.\n", from, strerror(errno));
size_t out_len = ZSTD_CStreamOutSize();
void *out_buf = MEM_mallocN(out_len, __func__);
size_t total_written = 0;
/* Compress block and write it out until the input has been consumed. */
while (input.pos < input.size) {
ZSTD_outBuffer output = {out_buf, out_len, 0};
size_t ret = ZSTD_compressStream2(ctx, &output, &input, ZSTD_e_continue);
if (ZSTD_isError(ret)) {
break;
}
else if (readsize == 0) {
break; /* done reading */
}
if (gzwrite(gzfile, buffer, readsize) <= 0) {
rval = -1; /* error happened in writing */
fprintf(stderr, "Error writing gz file %s: %s.\n", to, gzerror(gzfile, &err));
if (fwrite(out_buf, 1, output.pos, file) != output.pos) {
break;
}
total_written += output.pos;
}
gzclose(gzfile);
close(file);
return rval;
}
#endif
/* gzip the file in from_file and write it to memory to_mem, at most size bytes.
* return the unzipped size
*/
char *BLI_file_ungzip_to_mem(const char *from_file, int *r_size)
{
gzFile gzfile;
int readsize, size, alloc_size = 0;
char *mem = NULL;
const int chunk_size = 512 * 1024;
size = 0;
gzfile = BLI_gzopen(from_file, "rb");
for (;;) {
if (mem == NULL) {
mem = MEM_callocN(chunk_size, "BLI_ungzip_to_mem");
alloc_size = chunk_size;
}
else {
mem = MEM_reallocN(mem, size + chunk_size);
alloc_size += chunk_size;
}
readsize = gzread(gzfile, mem + size, chunk_size);
if (readsize > 0) {
size += readsize;
}
else {
/* Finalize the Zstd frame. */
size_t ret = 1;
while (ret != 0) {
ZSTD_outBuffer output = {out_buf, out_len, 0};
ret = ZSTD_compressStream2(ctx, &output, &input, ZSTD_e_end);
if (ZSTD_isError(ret)) {
break;
}
if (fwrite(out_buf, 1, output.pos, file) != output.pos) {
break;
}
total_written += output.pos;
}
gzclose(gzfile);
MEM_freeN(out_buf);
ZSTD_freeCCtx(ctx);
if (size == 0) {
MEM_freeN(mem);
mem = NULL;
}
else if (alloc_size != size) {
mem = MEM_reallocN(mem, size);
}
*r_size = size;
return mem;
return ZSTD_isError(ret) ? 0 : total_written;
}
#define CHUNK (256 * 1024)
/* gzip byte array from memory and write it to file at certain position.
* return size of gzip stream.
*/
size_t BLI_gzip_mem_to_file_at_pos(
void *buf, size_t len, FILE *file, size_t gz_stream_offset, int compression_level)
size_t BLI_file_unzstd_to_mem_at_pos(void *buf, size_t len, FILE *file, size_t file_offset)
{
int ret, flush;
unsigned have;
z_stream strm;
unsigned char out[CHUNK];
fseek(file, file_offset, SEEK_SET);
BLI_fseek(file, gz_stream_offset, 0);
ZSTD_DCtx *ctx = ZSTD_createDCtx();
strm.zalloc = Z_NULL;
strm.zfree = Z_NULL;
strm.opaque = Z_NULL;
ret = deflateInit(&strm, compression_level);
if (ret != Z_OK) {
return 0;
}
size_t in_len = ZSTD_DStreamInSize();
void *in_buf = MEM_mallocN(in_len, __func__);
ZSTD_inBuffer input = {in_buf, in_len, 0};
strm.avail_in = len;
strm.next_in = (Bytef *)buf;
flush = Z_FINISH;
ZSTD_outBuffer output = {buf, len, 0};
do {
strm.avail_out = CHUNK;
strm.next_out = out;
ret = deflate(&strm, flush);
if (ret == Z_STREAM_ERROR) {
return 0;
}
have = CHUNK - strm.avail_out;
if (fwrite(out, 1, have, file) != have || ferror(file)) {
deflateEnd(&strm);
return 0;
}
} while (strm.avail_out == 0);
if (strm.avail_in != 0 || ret != Z_STREAM_END) {
return 0;
}
deflateEnd(&strm);
return (size_t)strm.total_out;
}
/* read and decompress gzip stream from file at certain position to buffer.
* return size of decompressed data.
*/
size_t BLI_ungzip_file_to_mem_at_pos(void *buf, size_t len, FILE *file, size_t gz_stream_offset)
{
int ret;
z_stream strm;
size_t chunk = 256 * 1024;
unsigned char in[CHUNK];
BLI_fseek(file, gz_stream_offset, 0);
strm.zalloc = Z_NULL;
strm.zfree = Z_NULL;
strm.opaque = Z_NULL;
strm.avail_in = 0;
strm.next_in = Z_NULL;
ret = inflateInit(&strm);
if (ret != Z_OK) {
return 0;
}
do {
strm.avail_in = fread(in, 1, chunk, file);
strm.next_in = in;
if (ferror(file)) {
inflateEnd(&strm);
return 0;
size_t ret = 0;
/* Read and decompress chunks of input data until we have enough output. */
while (output.pos < output.size && !ZSTD_isError(ret)) {
input.size = fread(in_buf, 1, in_len, file);
if (input.size == 0) {
break;
}
do {
strm.avail_out = len;
strm.next_out = (Bytef *)buf + strm.total_out;
/* Consume input data until we run out or have enough output. */
input.pos = 0;
while (input.pos < input.size && output.pos < output.size) {
ret = ZSTD_decompressStream(ctx, &output, &input);
ret = inflate(&strm, Z_NO_FLUSH);
if (ret == Z_STREAM_ERROR) {
return 0;
if (ZSTD_isError(ret)) {
break;
}
} while (strm.avail_out == 0);
}
}
} while (ret != Z_STREAM_END);
MEM_freeN(in_buf);
ZSTD_freeDCtx(ctx);
inflateEnd(&strm);
return (size_t)strm.total_out;
return ZSTD_isError(ret) ? 0 : output.pos;
}
#undef CHUNK
bool BLI_file_magic_is_gzip(const char header[4])
{
/* GZIP itself starts with the magic bytes 0x1f 0x8b.
* The third byte indicates the compression method, which is 0x08 for DEFLATE. */
return header[0] == 0x1f && header[1] == 0x8b && header[2] == 0x08;
}
bool BLI_file_magic_is_zstd(const char header[4])
{
/* ZSTD files consist of concatenated frames, each either a Zstd frame or a skippable frame.
* Both types of frames start with a magic number: 0xFD2FB528 for Zstd frames and 0x184D2A5*
* for skippable frames, with the * being anything from 0 to F.
*
* To check whether a file is Zstd-compressed, we just check whether the first frame matches
* either. Seeking through the file until a Zstd frame is found would make things more
* complicated and the probability of a false positive is rather low anyways.
*
* Note that LZ4 uses a compatible format, so even though its compressed frames have a
* different magic number, a valid LZ4 file might also start with a skippable frame matching
* the second check here.
*
* For more details, see https://github.com/facebook/zstd/blob/dev/doc/zstd_compression_format.md
*/
uint32_t magic = *((uint32_t *)header);
if (magic == 0xFD2FB528) {
return true;
}
if ((magic >> 4) == 0x184D2A5) {
return true;
}
return false;
}
/**
* Returns true if the file with the specified name can be written.

View File

@@ -0,0 +1,80 @@
/*
* 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) 2004-2021 Blender Foundation
* All rights reserved.
*/
/** \file
* \ingroup bli
*/
#ifndef WIN32
# include <unistd.h> /* for read close */
#else
# include "BLI_winstuff.h"
# include "winsock2.h"
# include <io.h> /* for open close read */
#endif
#include "BLI_blenlib.h"
#include "BLI_filereader.h"
#include "MEM_guardedalloc.h"
typedef struct {
FileReader reader;
int filedes;
} RawFileReader;
static ssize_t file_read(FileReader *reader, void *buffer, size_t size)
{
RawFileReader *rawfile = (RawFileReader *)reader;
ssize_t readsize = read(rawfile->filedes, buffer, size);
if (readsize >= 0) {
rawfile->reader.offset += readsize;
}
return readsize;
}
static off64_t file_seek(FileReader *reader, off64_t offset, int whence)
{
RawFileReader *rawfile = (RawFileReader *)reader;
rawfile->reader.offset = BLI_lseek(rawfile->filedes, offset, whence);
return rawfile->reader.offset;
}
static void file_close(FileReader *reader)
{
RawFileReader *rawfile = (RawFileReader *)reader;
close(rawfile->filedes);
MEM_freeN(rawfile);
}
FileReader *BLI_filereader_new_file(int filedes)
{
RawFileReader *rawfile = MEM_callocN(sizeof(RawFileReader), __func__);
rawfile->filedes = filedes;
rawfile->reader.read = file_read;
rawfile->reader.seek = file_seek;
rawfile->reader.close = file_close;
return (FileReader *)rawfile;
}

View File

@@ -0,0 +1,108 @@
/*
* 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) 2004-2021 Blender Foundation
* All rights reserved.
*/
/** \file
* \ingroup bli
*/
#include <zlib.h>
#include "BLI_blenlib.h"
#include "BLI_filereader.h"
#include "MEM_guardedalloc.h"
typedef struct {
FileReader reader;
FileReader *base;
z_stream strm;
void *in_buf;
size_t in_size;
} GzipReader;
static ssize_t gzip_read(FileReader *reader, void *buffer, size_t size)
{
GzipReader *gzip = (GzipReader *)reader;
gzip->strm.avail_out = size;
gzip->strm.next_out = buffer;
while (gzip->strm.avail_out > 0) {
if (gzip->strm.avail_in == 0) {
/* Ran out of buffered input data, read some more. */
size_t readsize = gzip->base->read(gzip->base, gzip->in_buf, gzip->in_size);
if (readsize > 0) {
/* We got some data, so mark the buffer as refilled. */
gzip->strm.avail_in = readsize;
gzip->strm.next_in = gzip->in_buf;
}
else {
/* The underlying file is EOF, so return as much as we can. */
break;
}
}
int ret = inflate(&gzip->strm, Z_NO_FLUSH);
if (ret != Z_OK && ret != Z_BUF_ERROR) {
break;
}
}
ssize_t read_len = size - gzip->strm.avail_out;
gzip->reader.offset += read_len;
return read_len;
}
static void gzip_close(FileReader *reader)
{
GzipReader *gzip = (GzipReader *)reader;
if (inflateEnd(&gzip->strm) != Z_OK) {
printf("close gzip stream error\n");
}
MEM_freeN((void *)gzip->in_buf);
gzip->base->close(gzip->base);
MEM_freeN(gzip);
}
FileReader *BLI_filereader_new_gzip(FileReader *base)
{
GzipReader *gzip = MEM_callocN(sizeof(GzipReader), __func__);
gzip->base = base;
if (inflateInit2(&gzip->strm, 16 + MAX_WBITS) != Z_OK) {
MEM_freeN(gzip);
return NULL;
}
gzip->in_size = 256 * 2014;
gzip->in_buf = MEM_mallocN(gzip->in_size, "gzip in buf");
gzip->reader.read = gzip_read;
gzip->reader.seek = NULL;
gzip->reader.close = gzip_close;
return (FileReader *)gzip;
}

View File

@@ -0,0 +1,145 @@
/*
* 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) 2004-2021 Blender Foundation
* All rights reserved.
*/
/** \file
* \ingroup bli
*/
#include <string.h>
#include "BLI_blenlib.h"
#include "BLI_filereader.h"
#include "BLI_mmap.h"
#include "MEM_guardedalloc.h"
/* This file implements both memory-backed and memory-mapped-file-backed reading. */
typedef struct {
FileReader reader;
const char *data;
BLI_mmap_file *mmap;
size_t length;
} MemoryReader;
static ssize_t memory_read_raw(FileReader *reader, void *buffer, size_t size)
{
MemoryReader *mem = (MemoryReader *)reader;
/* Don't read more bytes than there are available in the buffer. */
size_t readsize = MIN2(size, (size_t)(mem->length - mem->reader.offset));
memcpy(buffer, mem->data + mem->reader.offset, readsize);
mem->reader.offset += readsize;
return readsize;
}
static off64_t memory_seek(FileReader *reader, off64_t offset, int whence)
{
MemoryReader *mem = (MemoryReader *)reader;
off64_t new_pos;
if (whence == SEEK_CUR) {
new_pos = mem->reader.offset + offset;
}
else if (whence == SEEK_SET) {
new_pos = offset;
}
else if (whence == SEEK_END) {
new_pos = mem->length + offset;
}
else {
return -1;
}
if (new_pos < 0 || new_pos > mem->length) {
return -1;
}
mem->reader.offset = new_pos;
return mem->reader.offset;
}
static void memory_close_raw(FileReader *reader)
{
MEM_freeN(reader);
}
FileReader *BLI_filereader_new_memory(const void *data, size_t len)
{
MemoryReader *mem = MEM_callocN(sizeof(MemoryReader), __func__);
mem->data = (const char *)data;
mem->length = len;
mem->reader.read = memory_read_raw;
mem->reader.seek = memory_seek;
mem->reader.close = memory_close_raw;
return (FileReader *)mem;
}
/* Memory-mapped file reading.
* By using `mmap()`, we can map a file so that it can be treated like normal memory,
* meaning that we can just read from it with `memcpy()` etc.
* This avoids system call overhead and can significantly speed up file loading.
*/
static ssize_t memory_read_mmap(FileReader *reader, void *buffer, size_t size)
{
MemoryReader *mem = (MemoryReader *)reader;
/* Don't read more bytes than there are available in the buffer. */
size_t readsize = MIN2(size, (size_t)(mem->length - mem->reader.offset));
if (!BLI_mmap_read(mem->mmap, buffer, mem->reader.offset, readsize)) {
return 0;
}
mem->reader.offset += readsize;
return readsize;
}
static void memory_close_mmap(FileReader *reader)
{
MemoryReader *mem = (MemoryReader *)reader;
BLI_mmap_free(mem->mmap);
MEM_freeN(mem);
}
FileReader *BLI_filereader_new_mmap(int filedes)
{
BLI_mmap_file *mmap = BLI_mmap_open(filedes);
if (mmap == NULL) {
return NULL;
}
MemoryReader *mem = MEM_callocN(sizeof(MemoryReader), __func__);
mem->mmap = mmap;
mem->length = BLI_lseek(filedes, 0, SEEK_END);
mem->reader.read = memory_read_mmap;
mem->reader.seek = memory_seek;
mem->reader.close = memory_close_mmap;
return (FileReader *)mem;
}

View File

@@ -0,0 +1,335 @@
/*
* 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) 2021 Blender Foundation
* All rights reserved.
*/
/** \file
* \ingroup bli
*/
#include <string.h>
#include <zstd.h>
#include "BLI_blenlib.h"
#include "BLI_endian_switch.h"
#include "BLI_filereader.h"
#include "BLI_math_base.h"
#include "MEM_guardedalloc.h"
typedef struct {
FileReader reader;
FileReader *base;
ZSTD_DCtx *ctx;
ZSTD_inBuffer in_buf;
size_t in_buf_max_size;
struct {
int num_frames;
size_t *compressed_ofs;
size_t *uncompressed_ofs;
char *cached_content;
int cached_frame;
} seek;
} ZstdReader;
static bool zstd_read_u32(FileReader *base, uint32_t *val)
{
if (base->read(base, val, sizeof(uint32_t)) != sizeof(uint32_t)) {
return false;
}
#ifdef __BIG_ENDIAN__
BLI_endian_switch_uint32(val);
#endif
return true;
}
static bool zstd_read_seek_table(ZstdReader *zstd)
{
FileReader *base = zstd->base;
/* The seek table frame is at the end of the file, so seek there
* and verify that there is enough data. */
if (base->seek(base, -4, SEEK_END) < 13) {
return false;
}
uint32_t magic;
if (!zstd_read_u32(base, &magic) || magic != 0x8F92EAB1) {
return false;
}
uint8_t flags;
if (base->seek(base, -5, SEEK_END) < 0 || base->read(base, &flags, 1) != 1) {
return false;
}
/* Bit 7 indicates checksums. Bits 5 and 6 must be zero. */
bool has_checksums = (flags & 0x80);
if (flags & 0x60) {
return false;
}
uint32_t num_frames;
if (base->seek(base, -9, SEEK_END) < 0 || !zstd_read_u32(base, &num_frames)) {
return false;
}
/* Each frame has either 2 or 3 uint32_t, and after that we have
* num_frames, flags and magic for another 9 bytes. */
uint32_t expected_frame_length = num_frames * (has_checksums ? 12 : 8) + 9;
/* The frame starts with another magic number and its length, but these
* two fields are not included when counting length. */
off64_t frame_start_ofs = 8 + expected_frame_length;
/* Sanity check: Before the start of the seek table frame,
* there must be num_frames frames, each of which at least 8 bytes long. */
off64_t seek_frame_start = base->seek(base, -frame_start_ofs, SEEK_END);
if (seek_frame_start < num_frames * 8) {
return false;
}
if (!zstd_read_u32(base, &magic) || magic != 0x184D2A5E) {
return false;
}
uint32_t frame_length;
if (!zstd_read_u32(base, &frame_length) || frame_length != expected_frame_length) {
return false;
}
zstd->seek.num_frames = num_frames;
zstd->seek.compressed_ofs = MEM_malloc_arrayN(num_frames + 1, sizeof(size_t), __func__);
zstd->seek.uncompressed_ofs = MEM_malloc_arrayN(num_frames + 1, sizeof(size_t), __func__);
size_t compressed_ofs = 0;
size_t uncompressed_ofs = 0;
for (int i = 0; i < num_frames; i++) {
uint32_t compressed_size, uncompressed_size;
if (!zstd_read_u32(base, &compressed_size) || !zstd_read_u32(base, &uncompressed_size)) {
break;
}
if (has_checksums && base->seek(base, 4, SEEK_CUR) < 0) {
break;
}
zstd->seek.compressed_ofs[i] = compressed_ofs;
zstd->seek.uncompressed_ofs[i] = uncompressed_ofs;
compressed_ofs += compressed_size;
uncompressed_ofs += uncompressed_size;
}
zstd->seek.compressed_ofs[num_frames] = compressed_ofs;
zstd->seek.uncompressed_ofs[num_frames] = uncompressed_ofs;
/* Seek to the end of the previous frame for the following BHead frame detection. */
if (seek_frame_start != compressed_ofs || base->seek(base, seek_frame_start, SEEK_SET) < 0) {
MEM_freeN(zstd->seek.compressed_ofs);
MEM_freeN(zstd->seek.uncompressed_ofs);
memset(&zstd->seek, 0, sizeof(zstd->seek));
return false;
}
zstd->seek.cached_frame = -1;
return true;
}
/* Find out which frame contains the given position in the uncompressed stream.
* Basically just bisection. */
static int zstd_frame_from_pos(ZstdReader *zstd, size_t pos)
{
int low = 0, high = zstd->seek.num_frames;
if (pos >= zstd->seek.uncompressed_ofs[zstd->seek.num_frames]) {
return -1;
}
while (low + 1 < high) {
int mid = low + ((high - low) >> 1);
if (zstd->seek.uncompressed_ofs[mid] <= pos) {
low = mid;
}
else {
high = mid;
}
}
return low;
}
/* Ensure that the currently loaded frame is the correct one. */
static const char *zstd_ensure_cache(ZstdReader *zstd, int frame)
{
if (zstd->seek.cached_frame == frame) {
/* Cached frame matches, so just return it. */
return zstd->seek.cached_content;
}
/* Cached frame doesn't match, so discard it and cache the wanted one onstead. */
MEM_SAFE_FREE(zstd->seek.cached_content);
size_t compressed_size = zstd->seek.compressed_ofs[frame + 1] - zstd->seek.compressed_ofs[frame];
size_t uncompressed_size = zstd->seek.uncompressed_ofs[frame + 1] -
zstd->seek.uncompressed_ofs[frame];
char *uncompressed_data = MEM_mallocN(uncompressed_size, __func__);
char *compressed_data = MEM_mallocN(compressed_size, __func__);
if (zstd->base->seek(zstd->base, zstd->seek.compressed_ofs[frame], SEEK_SET) < 0 ||
zstd->base->read(zstd->base, compressed_data, compressed_size) < compressed_size) {
MEM_freeN(compressed_data);
MEM_freeN(uncompressed_data);
return NULL;
}
size_t res = ZSTD_decompressDCtx(
zstd->ctx, uncompressed_data, uncompressed_size, compressed_data, compressed_size);
MEM_freeN(compressed_data);
if (ZSTD_isError(res) || res < uncompressed_size) {
MEM_freeN(uncompressed_data);
return NULL;
}
zstd->seek.cached_frame = frame;
zstd->seek.cached_content = uncompressed_data;
return uncompressed_data;
}
static ssize_t zstd_read_seekable(FileReader *reader, void *buffer, size_t size)
{
ZstdReader *zstd = (ZstdReader *)reader;
size_t end_offset = zstd->reader.offset + size, read_len = 0;
while (zstd->reader.offset < end_offset) {
int frame = zstd_frame_from_pos(zstd, zstd->reader.offset);
if (frame < 0) {
/* EOF is reached, so return as much as we can. */
break;
}
const char *framedata = zstd_ensure_cache(zstd, frame);
if (framedata == NULL) {
/* Error while reading the frame, so return as much as we can. */
break;
}
size_t frame_end_offset = min_zz(zstd->seek.uncompressed_ofs[frame + 1], end_offset);
size_t frame_read_len = frame_end_offset - zstd->reader.offset;
size_t offset_in_frame = zstd->reader.offset - zstd->seek.uncompressed_ofs[frame];
memcpy((char *)buffer + read_len, framedata + offset_in_frame, frame_read_len);
read_len += frame_read_len;
zstd->reader.offset = frame_end_offset;
}
return read_len;
}
static off64_t zstd_seek(FileReader *reader, off64_t offset, int whence)
{
ZstdReader *zstd = (ZstdReader *)reader;
off64_t new_pos;
if (whence == SEEK_SET) {
new_pos = offset;
}
else if (whence == SEEK_END) {
new_pos = zstd->seek.uncompressed_ofs[zstd->seek.num_frames] + offset;
}
else {
new_pos = zstd->reader.offset + offset;
}
if (new_pos < 0 || new_pos > zstd->seek.uncompressed_ofs[zstd->seek.num_frames]) {
return -1;
}
zstd->reader.offset = new_pos;
return zstd->reader.offset;
}
static ssize_t zstd_read(FileReader *reader, void *buffer, size_t size)
{
ZstdReader *zstd = (ZstdReader *)reader;
ZSTD_outBuffer output = {buffer, size, 0};
while (output.pos < output.size) {
if (zstd->in_buf.pos == zstd->in_buf.size) {
/* Ran out of buffered input data, read some more. */
zstd->in_buf.pos = 0;
ssize_t readsize = zstd->base->read(
zstd->base, (char *)zstd->in_buf.src, zstd->in_buf_max_size);
if (readsize > 0) {
/* We got some data, so mark the buffer as refilled. */
zstd->in_buf.size = readsize;
}
else {
/* The underlying file is EOF, so return as much as we can. */
break;
}
}
if (ZSTD_isError(ZSTD_decompressStream(zstd->ctx, &output, &zstd->in_buf))) {
break;
}
}
zstd->reader.offset += output.pos;
return output.pos;
}
static void zstd_close(FileReader *reader)
{
ZstdReader *zstd = (ZstdReader *)reader;
ZSTD_freeDCtx(zstd->ctx);
if (zstd->reader.seek) {
MEM_freeN(zstd->seek.uncompressed_ofs);
MEM_freeN(zstd->seek.compressed_ofs);
MEM_freeN(zstd->seek.cached_content);
}
else {
MEM_freeN((void *)zstd->in_buf.src);
}
zstd->base->close(zstd->base);
MEM_freeN(zstd);
}
FileReader *BLI_filereader_new_zstd(FileReader *base)
{
ZstdReader *zstd = MEM_callocN(sizeof(ZstdReader), __func__);
zstd->ctx = ZSTD_createDCtx();
zstd->base = base;
if (zstd_read_seek_table(zstd)) {
zstd->reader.read = zstd_read_seekable;
zstd->reader.seek = zstd_seek;
}
else {
zstd->reader.read = zstd_read;
zstd->reader.seek = NULL;
zstd->in_buf_max_size = ZSTD_DStreamInSize();
zstd->in_buf.src = MEM_mallocN(zstd->in_buf_max_size, "zstd in buf");
zstd->in_buf.size = zstd->in_buf_max_size;
/* This signals that the buffer has run out,
* which will make the read function refill it on the first call. */
zstd->in_buf.pos = zstd->in_buf_max_size;
}
zstd->reader.close = zstd_close;
return (FileReader *)zstd;
}

View File

@@ -24,6 +24,8 @@
* \ingroup blenloader
*/
#include "BLI_filereader.h"
struct GHash;
struct Scene;
@@ -65,6 +67,16 @@ typedef struct MemFileUndoData {
size_t undo_size;
} MemFileUndoData;
/* FileReader-compatible wrapper for reading MemFiles */
typedef struct {
FileReader reader;
MemFile *memfile;
int undo_direction;
bool memchunk_identical;
} UndoReader;
/* actually only used writefile.c */
void BLO_memfile_write_init(MemFileWriteData *mem_data,
@@ -84,3 +96,5 @@ extern struct Main *BLO_memfile_main_get(struct MemFile *memfile,
struct Main *bmain,
struct Scene **r_scene);
extern bool BLO_memfile_write_file(struct MemFile *memfile, const char *filename);
FileReader *BLO_memfile_new_filereader(MemFile *memfile, int undo_direction);

View File

@@ -42,7 +42,7 @@ set(INC
)
set(INC_SYS
${ZLIB_INCLUDE_DIRS}
${ZSTD_INCLUDE_DIRS}
)
set(SRC

View File

@@ -21,8 +21,6 @@
* \ingroup blenloader
*/
#include "zlib.h"
#include <ctype.h> /* for isdigit. */
#include <fcntl.h> /* for open flags (O_BINARY, O_RDONLY). */
#include <limits.h>
@@ -71,7 +69,6 @@
#include "BLI_math.h"
#include "BLI_memarena.h"
#include "BLI_mempool.h"
#include "BLI_mmap.h"
#include "BLI_threads.h"
#include "PIL_time.h"
@@ -788,7 +785,7 @@ static BHeadN *get_bhead(FileData *fd)
*/
if (fd->flags & FD_FLAGS_FILE_POINTSIZE_IS_4) {
bhead4.code = DATA;
readsize = fd->read(fd, &bhead4, sizeof(bhead4), NULL);
readsize = fd->file->read(fd->file, &bhead4, sizeof(bhead4));
if (readsize == sizeof(bhead4) || bhead4.code == ENDB) {
if (fd->flags & FD_FLAGS_SWITCH_ENDIAN) {
@@ -811,7 +808,7 @@ static BHeadN *get_bhead(FileData *fd)
}
else {
bhead8.code = DATA;
readsize = fd->read(fd, &bhead8, sizeof(bhead8), NULL);
readsize = fd->file->read(fd->file, &bhead8, sizeof(bhead8));
if (readsize == sizeof(bhead8) || bhead8.code == ENDB) {
if (fd->flags & FD_FLAGS_SWITCH_ENDIAN) {
@@ -845,22 +842,22 @@ static BHeadN *get_bhead(FileData *fd)
/* pass */
}
#ifdef USE_BHEAD_READ_ON_DEMAND
else if (fd->seek != NULL && BHEAD_USE_READ_ON_DEMAND(&bhead)) {
else if (fd->file->seek != NULL && BHEAD_USE_READ_ON_DEMAND(&bhead)) {
/* Delay reading bhead content. */
new_bhead = MEM_mallocN(sizeof(BHeadN), "new_bhead");
if (new_bhead) {
new_bhead->next = new_bhead->prev = NULL;
new_bhead->file_offset = fd->file_offset;
new_bhead->file_offset = fd->file->offset;
new_bhead->has_data = false;
new_bhead->is_memchunk_identical = false;
new_bhead->bhead = bhead;
off64_t seek_new = fd->seek(fd, bhead.len, SEEK_CUR);
off64_t seek_new = fd->file->seek(fd->file, bhead.len, SEEK_CUR);
if (seek_new == -1) {
fd->is_eof = true;
MEM_freeN(new_bhead);
new_bhead = NULL;
}
BLI_assert(fd->file_offset == seek_new);
BLI_assert(fd->file->offset == seek_new);
}
else {
fd->is_eof = true;
@@ -878,14 +875,17 @@ static BHeadN *get_bhead(FileData *fd)
new_bhead->is_memchunk_identical = false;
new_bhead->bhead = bhead;
readsize = fd->read(
fd, new_bhead + 1, (size_t)bhead.len, &new_bhead->is_memchunk_identical);
readsize = fd->file->read(fd->file, new_bhead + 1, (size_t)bhead.len);
if (readsize != (ssize_t)bhead.len) {
if (readsize != bhead.len) {
fd->is_eof = true;
MEM_freeN(new_bhead);
new_bhead = NULL;
}
if (fd->flags & FD_FLAGS_IS_MEMFILE) {
new_bhead->is_memchunk_identical = ((UndoReader *)fd->file)->memchunk_identical;
}
}
else {
fd->is_eof = true;
@@ -964,17 +964,19 @@ static bool blo_bhead_read_data(FileData *fd, BHead *thisblock, void *buf)
bool success = true;
BHeadN *new_bhead = BHEADN_FROM_BHEAD(thisblock);
BLI_assert(new_bhead->has_data == false && new_bhead->file_offset != 0);
off64_t offset_backup = fd->file_offset;
if (UNLIKELY(fd->seek(fd, new_bhead->file_offset, SEEK_SET) == -1)) {
off64_t offset_backup = fd->file->offset;
if (UNLIKELY(fd->file->seek(fd->file, new_bhead->file_offset, SEEK_SET) == -1)) {
success = false;
}
else {
if (fd->read(fd, buf, (size_t)new_bhead->bhead.len, &new_bhead->is_memchunk_identical) !=
(ssize_t)new_bhead->bhead.len) {
if (fd->file->read(fd->file, buf, (size_t)new_bhead->bhead.len) != new_bhead->bhead.len) {
success = false;
}
if (fd->flags & FD_FLAGS_IS_MEMFILE) {
new_bhead->is_memchunk_identical = ((UndoReader *)fd->file)->memchunk_identical;
}
}
if (fd->seek(fd, offset_backup, SEEK_SET) == -1) {
if (fd->file->seek(fd->file, offset_backup, SEEK_SET) == -1) {
success = false;
}
return success;
@@ -1017,7 +1019,7 @@ static void decode_blender_header(FileData *fd)
ssize_t readsize;
/* read in the header data */
readsize = fd->read(fd, header, sizeof(header), NULL);
readsize = fd->file->read(fd->file, header, sizeof(header));
if (readsize == sizeof(header) && STREQLEN(header, "BLENDER", 7) && ELEM(header[7], '_', '-') &&
ELEM(header[8], 'v', 'V') &&
@@ -1147,210 +1149,12 @@ static int *read_file_thumbnail(FileData *fd)
/** \} */
/* -------------------------------------------------------------------- */
/** \name File Data API
* \{ */
/* Regular file reading. */
static ssize_t fd_read_data_from_file(FileData *filedata,
void *buffer,
size_t size,
bool *UNUSED(r_is_memchunck_identical))
{
ssize_t readsize = read(filedata->filedes, buffer, size);
if (readsize < 0) {
readsize = EOF;
}
else {
filedata->file_offset += readsize;
}
return readsize;
}
static off64_t fd_seek_data_from_file(FileData *filedata, off64_t offset, int whence)
{
filedata->file_offset = BLI_lseek(filedata->filedes, offset, whence);
return filedata->file_offset;
}
/* GZip file reading. */
static ssize_t fd_read_gzip_from_file(FileData *filedata,
void *buffer,
size_t size,
bool *UNUSED(r_is_memchunck_identical))
{
BLI_assert(size <= INT_MAX);
ssize_t readsize = gzread(filedata->gzfiledes, buffer, (uint)size);
if (readsize < 0) {
readsize = EOF;
}
else {
filedata->file_offset += readsize;
}
return readsize;
}
/* Memory reading. */
static ssize_t fd_read_from_memory(FileData *filedata,
void *buffer,
size_t size,
bool *UNUSED(r_is_memchunck_identical))
{
/* don't read more bytes than there are available in the buffer */
ssize_t readsize = (ssize_t)MIN2(size, filedata->buffersize - (size_t)filedata->file_offset);
memcpy(buffer, filedata->buffer + filedata->file_offset, (size_t)readsize);
filedata->file_offset += readsize;
return readsize;
}
/* Memory-mapped file reading.
* By using mmap(), we can map a file so that it can be treated like normal memory,
* meaning that we can just read from it with memcpy() etc.
* This avoids system call overhead and can significantly speed up file loading.
*/
static ssize_t fd_read_from_mmap(FileData *filedata,
void *buffer,
size_t size,
bool *UNUSED(r_is_memchunck_identical))
{
/* don't read more bytes than there are available in the buffer */
size_t readsize = MIN2(size, (size_t)(filedata->buffersize - filedata->file_offset));
if (!BLI_mmap_read(filedata->mmap_file, buffer, filedata->file_offset, readsize)) {
return 0;
}
filedata->file_offset += readsize;
return readsize;
}
static off64_t fd_seek_from_mmap(FileData *filedata, off64_t offset, int whence)
{
off64_t new_pos;
if (whence == SEEK_CUR) {
new_pos = filedata->file_offset + offset;
}
else if (whence == SEEK_SET) {
new_pos = offset;
}
else if (whence == SEEK_END) {
new_pos = filedata->buffersize + offset;
}
else {
return -1;
}
if (new_pos < 0 || new_pos > filedata->buffersize) {
return -1;
}
filedata->file_offset = new_pos;
return filedata->file_offset;
}
/* MemFile reading. */
static ssize_t fd_read_from_memfile(FileData *filedata,
void *buffer,
size_t size,
bool *r_is_memchunck_identical)
{
static size_t seek = SIZE_MAX; /* the current position */
static size_t offset = 0; /* size of previous chunks */
static MemFileChunk *chunk = NULL;
size_t chunkoffset, readsize, totread;
if (r_is_memchunck_identical != NULL) {
*r_is_memchunck_identical = true;
}
if (size == 0) {
return 0;
}
if (seek != (size_t)filedata->file_offset) {
chunk = filedata->memfile->chunks.first;
seek = 0;
while (chunk) {
if (seek + chunk->size > (size_t)filedata->file_offset) {
break;
}
seek += chunk->size;
chunk = chunk->next;
}
offset = seek;
seek = (size_t)filedata->file_offset;
}
if (chunk) {
totread = 0;
do {
/* first check if it's on the end if current chunk */
if (seek - offset == chunk->size) {
offset += chunk->size;
chunk = chunk->next;
}
/* debug, should never happen */
if (chunk == NULL) {
CLOG_ERROR(&LOG, "Illegal read, got a NULL chunk");
return 0;
}
chunkoffset = seek - offset;
readsize = size - totread;
/* data can be spread over multiple chunks, so clamp size
* to within this chunk, and then it will read further in
* the next chunk */
if (chunkoffset + readsize > chunk->size) {
readsize = chunk->size - chunkoffset;
}
memcpy(POINTER_OFFSET(buffer, totread), chunk->buf + chunkoffset, readsize);
totread += readsize;
filedata->file_offset += readsize;
seek += readsize;
if (r_is_memchunck_identical != NULL) {
/* `is_identical` of current chunk represents whether it changed compared to previous undo
* step. this is fine in redo case, but not in undo case, where we need an extra flag
* defined when saving the next (future) step after the one we want to restore, as we are
* supposed to 'come from' that future undo step, and not the one before current one. */
*r_is_memchunck_identical &= filedata->undo_direction == STEP_REDO ?
chunk->is_identical :
chunk->is_identical_future;
}
} while (totread < size);
return (ssize_t)totread;
}
return 0;
}
static FileData *filedata_new(BlendFileReadReport *reports)
{
BLI_assert(reports != NULL);
FileData *fd = MEM_callocN(sizeof(FileData), "FileData");
fd->filedes = -1;
fd->gzfiledes = NULL;
fd->memsdna = DNA_sdna_current_get();
fd->datamap = oldnewmap_new();
@@ -1387,78 +1191,66 @@ static FileData *blo_decode_and_check(FileData *fd, ReportList *reports)
static FileData *blo_filedata_from_file_descriptor(const char *filepath,
BlendFileReadReport *reports,
int file)
int filedes)
{
FileDataReadFn *read_fn = NULL;
FileDataSeekFn *seek_fn = NULL; /* Optional. */
size_t buffersize = 0;
BLI_mmap_file *mmap_file = NULL;
gzFile gzfile = (gzFile)Z_NULL;
char header[7];
FileReader *rawfile = BLI_filereader_new_file(filedes);
FileReader *file = NULL;
/* Regular file. */
errno = 0;
if (read(file, header, sizeof(header)) != sizeof(header)) {
/* If opening the file failed or we can't read the header, give up. */
if (rawfile == NULL || rawfile->read(rawfile, header, sizeof(header)) != sizeof(header)) {
BKE_reportf(reports->reports,
RPT_WARNING,
"Unable to read '%s': %s",
filepath,
errno ? strerror(errno) : TIP_("insufficient content"));
if (rawfile) {
rawfile->close(rawfile);
}
else {
close(filedes);
}
return NULL;
}
/* Regular file. */
/* Rewind the file after reading the header. */
rawfile->seek(rawfile, 0, SEEK_SET);
/* Check if we have a regular file. */
if (memcmp(header, "BLENDER", sizeof(header)) == 0) {
read_fn = fd_read_data_from_file;
seek_fn = fd_seek_data_from_file;
mmap_file = BLI_mmap_open(file);
if (mmap_file != NULL) {
read_fn = fd_read_from_mmap;
seek_fn = fd_seek_from_mmap;
buffersize = BLI_lseek(file, 0, SEEK_END);
/* Try opening the file with memory-mapped IO. */
file = BLI_filereader_new_mmap(filedes);
if (file == NULL) {
/* mmap failed, so just keep using rawfile. */
file = rawfile;
rawfile = NULL;
}
}
else if (BLI_file_magic_is_gzip(header)) {
file = BLI_filereader_new_gzip(rawfile);
if (file != NULL) {
rawfile = NULL; /* The Gzip FileReader takes ownership of `rawfile`. */
}
}
else if (BLI_file_magic_is_zstd(header)) {
file = BLI_filereader_new_zstd(rawfile);
if (file != NULL) {
rawfile = NULL; /* The Zstd FileReader takes ownership of `rawfile`. */
}
}
BLI_lseek(file, 0, SEEK_SET);
/* Gzip file. */
errno = 0;
if ((read_fn == NULL) &&
/* Check header magic. */
(header[0] == 0x1f && header[1] == 0x8b)) {
gzfile = BLI_gzopen(filepath, "rb");
if (gzfile == (gzFile)Z_NULL) {
BKE_reportf(reports->reports,
RPT_WARNING,
"Unable to open '%s': %s",
filepath,
errno ? strerror(errno) : TIP_("unknown error reading file"));
return NULL;
}
/* 'seek_fn' is too slow for gzip, don't set it. */
read_fn = fd_read_gzip_from_file;
/* Caller must close. */
file = -1;
/* Clean up `rawfile` if it wasn't taken over. */
if (rawfile != NULL) {
rawfile->close(rawfile);
}
if (read_fn == NULL) {
if (file == NULL) {
BKE_reportf(reports->reports, RPT_WARNING, "Unrecognized file format '%s'", filepath);
return NULL;
}
FileData *fd = filedata_new(reports);
fd->filedes = file;
fd->gzfiledes = gzfile;
fd->read = read_fn;
fd->seek = seek_fn;
fd->mmap_file = mmap_file;
fd->buffersize = buffersize;
fd->file = file;
return fd;
}
@@ -1475,11 +1267,7 @@ static FileData *blo_filedata_from_file_open(const char *filepath, BlendFileRead
errno ? strerror(errno) : TIP_("unknown error reading file"));
return NULL;
}
FileData *fd = blo_filedata_from_file_descriptor(filepath, reports, file);
if ((fd == NULL) || (fd->filedes == -1)) {
close(file);
}
return fd;
return blo_filedata_from_file_descriptor(filepath, reports, file);
}
/* cannot be called with relative paths anymore! */
@@ -1513,50 +1301,6 @@ static FileData *blo_filedata_from_file_minimal(const char *filepath)
return NULL;
}
static ssize_t fd_read_gzip_from_memory(FileData *filedata,
void *buffer,
size_t size,
bool *UNUSED(r_is_memchunck_identical))
{
int err;
filedata->strm.next_out = (Bytef *)buffer;
filedata->strm.avail_out = (uint)size;
/* Inflate another chunk. */
err = inflate(&filedata->strm, Z_SYNC_FLUSH);
if (err == Z_STREAM_END) {
return 0;
}
if (err != Z_OK) {
CLOG_ERROR(&LOG, "ZLib error (code %d)", err);
return 0;
}
filedata->file_offset += size;
return (ssize_t)size;
}
static int fd_read_gzip_from_memory_init(FileData *fd)
{
fd->strm.next_in = (Bytef *)fd->buffer;
fd->strm.avail_in = fd->buffersize;
fd->strm.total_out = 0;
fd->strm.zalloc = Z_NULL;
fd->strm.zfree = Z_NULL;
if (inflateInit2(&fd->strm, (16 + MAX_WBITS)) != Z_OK) {
return 0;
}
fd->read = fd_read_gzip_from_memory;
return 1;
}
FileData *blo_filedata_from_memory(const void *mem, int memsize, BlendFileReadReport *reports)
{
if (!mem || memsize < SIZEOFBLENDERHEADER) {
@@ -1565,24 +1309,24 @@ FileData *blo_filedata_from_memory(const void *mem, int memsize, BlendFileReadRe
return NULL;
}
FileReader *mem_file = BLI_filereader_new_memory(mem, memsize);
FileReader *file = mem_file;
if (BLI_file_magic_is_gzip(mem)) {
file = BLI_filereader_new_gzip(mem_file);
}
else if (BLI_file_magic_is_zstd(mem)) {
file = BLI_filereader_new_zstd(mem_file);
}
if (file == NULL) {
/* Compression initialization failed. */
mem_file->close(mem_file);
return NULL;
}
FileData *fd = filedata_new(reports);
const char *cp = mem;
fd->buffer = mem;
fd->buffersize = memsize;
/* test if gzip */
if (cp[0] == 0x1f && cp[1] == 0x8b) {
if (0 == fd_read_gzip_from_memory_init(fd)) {
blo_filedata_free(fd);
return NULL;
}
}
else {
fd->read = fd_read_from_memory;
}
fd->flags |= FD_FLAGS_NOT_MY_BUFFER;
fd->file = file;
return blo_decode_and_check(fd, reports->reports);
}
@@ -1597,11 +1341,9 @@ FileData *blo_filedata_from_memfile(MemFile *memfile,
}
FileData *fd = filedata_new(reports);
fd->memfile = memfile;
fd->file = BLO_memfile_new_filereader(memfile, params->undo_direction);
fd->undo_direction = params->undo_direction;
fd->read = fd_read_from_memfile;
fd->flags |= FD_FLAGS_NOT_MY_BUFFER;
fd->flags |= FD_FLAGS_IS_MEMFILE;
return blo_decode_and_check(fd, reports->reports);
}
@@ -1609,30 +1351,7 @@ FileData *blo_filedata_from_memfile(MemFile *memfile,
void blo_filedata_free(FileData *fd)
{
if (fd) {
if (fd->filedes != -1) {
close(fd->filedes);
}
if (fd->gzfiledes != NULL) {
gzclose(fd->gzfiledes);
}
if (fd->strm.next_in) {
int err = inflateEnd(&fd->strm);
if (err != Z_OK) {
CLOG_ERROR(&LOG, "Close gzip stream error (code %d)", err);
}
}
if (fd->buffer && !(fd->flags & FD_FLAGS_NOT_MY_BUFFER)) {
MEM_freeN((void *)fd->buffer);
fd->buffer = NULL;
}
if (fd->mmap_file) {
BLI_mmap_free(fd->mmap_file);
fd->mmap_file = NULL;
}
fd->file->close(fd->file);
/* Free all BHeadN data blocks */
#ifndef NDEBUG
@@ -1640,7 +1359,7 @@ void blo_filedata_free(FileData *fd)
#else
/* Sanity check we're not keeping memory we don't need. */
LISTBASE_FOREACH_MUTABLE (BHeadN *, new_bhead, &fd->bhead_list) {
if (fd->seek != NULL && BHEAD_USE_READ_ON_DEMAND(&new_bhead->bhead)) {
if (fd->file->seek != NULL && BHEAD_USE_READ_ON_DEMAND(&new_bhead->bhead)) {
BLI_assert(new_bhead->has_data == 0);
}
MEM_freeN(new_bhead);
@@ -2096,7 +1815,7 @@ static void blo_cache_storage_entry_clear_in_old(ID *UNUSED(id),
void blo_cache_storage_init(FileData *fd, Main *bmain)
{
if (fd->memfile != NULL) {
if (fd->flags & FD_FLAGS_IS_MEMFILE) {
BLI_assert(fd->cache_storage == NULL);
fd->cache_storage = MEM_mallocN(sizeof(*fd->cache_storage), __func__);
fd->cache_storage->memarena = BLI_memarena_new(BLI_MEMARENA_STD_BUFSIZE, __func__);
@@ -2261,7 +1980,7 @@ static void *read_struct(FileData *fd, BHead *bh, const char *blockname)
* undo since DNA must match. */
static const void *peek_struct_undo(FileData *fd, BHead *bhead)
{
BLI_assert(fd->memfile != NULL);
BLI_assert(fd->flags & FD_FLAGS_IS_MEMFILE);
UNUSED_VARS_NDEBUG(fd);
return (bhead->len) ? (const void *)(bhead + 1) : NULL;
}
@@ -3679,7 +3398,7 @@ static BHead *read_libblock(FileData *fd,
* When datablocks are changed but still exist, we restore them at the old
* address and inherit recalc flags for the dependency graph. */
ID *id_old = NULL;
if (fd->memfile != NULL) {
if (fd->flags & FD_FLAGS_IS_MEMFILE) {
if (read_libblock_undo_restore(fd, main, bhead, tag, &id_old)) {
if (r_id) {
*r_id = id_old;
@@ -3980,13 +3699,14 @@ static void lib_link_all(FileData *fd, Main *bmain)
continue;
}
if (fd->memfile != NULL && GS(id->name) == ID_WM) {
if ((fd->flags & FD_FLAGS_IS_MEMFILE) && GS(id->name) == ID_WM) {
/* No load UI for undo memfiles.
* Only WM currently, SCR needs it still (see below), and so does WS? */
continue;
}
if (fd->memfile != NULL && do_partial_undo && (id->tag & LIB_TAG_UNDO_OLD_ID_REUSED) != 0) {
if ((fd->flags & FD_FLAGS_IS_MEMFILE) && do_partial_undo &&
(id->tag & LIB_TAG_UNDO_OLD_ID_REUSED) != 0) {
/* This ID has been re-used from 'old' bmain. Since it was therefore unchanged across
* current undo step, and old IDs re-use their old memory address, we do not need to liblink
* it at all. */
@@ -4165,7 +3885,7 @@ BlendFileData *blo_read_file_internal(FileData *fd, const char *filepath)
BlendFileData *bfd;
ListBase mainlist = {NULL, NULL};
if (fd->memfile != NULL) {
if (fd->flags & FD_FLAGS_IS_MEMFILE) {
CLOG_INFO(&LOG_UNDO, 2, "UNDO: read step");
}
@@ -4256,7 +3976,7 @@ BlendFileData *blo_read_file_internal(FileData *fd, const char *filepath)
}
/* do before read_libraries, but skip undo case */
if (fd->memfile == NULL) {
if ((fd->flags & FD_FLAGS_IS_MEMFILE) == 0) {
if ((fd->skip_flags & BLO_READ_SKIP_DATA) == 0) {
do_versions(fd, NULL, bfd->main);
}
@@ -4278,7 +3998,7 @@ BlendFileData *blo_read_file_internal(FileData *fd, const char *filepath)
fd->reports->duration.libraries = PIL_check_seconds_timer() - fd->reports->duration.libraries;
/* Skip in undo case. */
if (fd->memfile == NULL) {
if ((fd->flags & FD_FLAGS_IS_MEMFILE) == 0) {
/* Note that we can't recompute user-counts at this point in undo case, we play too much with
* IDs from different memory realms, and Main database is not in a fully valid state yet.
*/
@@ -4311,7 +4031,7 @@ BlendFileData *blo_read_file_internal(FileData *fd, const char *filepath)
/* Now that all our data-blocks are loaded,
* we can re-generate overrides from their references. */
if (fd->memfile == NULL) {
if ((fd->flags & FD_FLAGS_IS_MEMFILE) == 0) {
/* Do not apply in undo case! */
fd->reports->duration.lib_overrides = PIL_check_seconds_timer();
@@ -4391,7 +4111,7 @@ static void sort_bhead_old_map(FileData *fd)
static BHead *find_previous_lib(FileData *fd, BHead *bhead)
{
/* Skip library data-blocks in undo, see comment in read_libblock. */
if (fd->memfile) {
if (fd->flags & FD_FLAGS_IS_MEMFILE) {
return NULL;
}
@@ -5850,7 +5570,7 @@ void BLO_read_pointer_array(BlendDataReader *reader, void **ptr_p)
bool BLO_read_data_is_undo(BlendDataReader *reader)
{
return reader->fd->memfile != NULL;
return (reader->fd->flags & FD_FLAGS_IS_MEMFILE);
}
void BLO_read_data_globmap_add(BlendDataReader *reader, void *oldaddr, void *newaddr)
@@ -5870,7 +5590,7 @@ BlendFileReadReport *BLO_read_data_reports(BlendDataReader *reader)
bool BLO_read_lib_is_undo(BlendLibReader *reader)
{
return reader->fd->memfile != NULL;
return (reader->fd->flags & FD_FLAGS_IS_MEMFILE);
}
Main *BLO_read_lib_get_main(BlendLibReader *reader)

View File

@@ -28,10 +28,10 @@
# include "BLI_winstuff.h"
#endif
#include "BLI_filereader.h"
#include "DNA_sdna_types.h"
#include "DNA_space_types.h"
#include "DNA_windowmanager_types.h" /* for ReportType */
#include "zlib.h"
struct BLI_mmap_file;
struct BLOCacheStorage;
@@ -50,7 +50,7 @@ enum eFileDataFlag {
FD_FLAGS_FILE_POINTSIZE_IS_4 = 1 << 1,
FD_FLAGS_POINTSIZE_DIFFERS = 1 << 2,
FD_FLAGS_FILE_OK = 1 << 3,
FD_FLAGS_NOT_MY_BUFFER = 1 << 4,
FD_FLAGS_IS_MEMFILE = 1 << 4,
/* XXX Unused in practice (checked once but never set). */
FD_FLAGS_NOT_MY_LIBMAP = 1 << 5,
};
@@ -60,44 +60,18 @@ enum eFileDataFlag {
# pragma GCC poison off_t
#endif
#if defined(_MSC_VER) || defined(__APPLE__) || defined(__HAIKU__) || defined(__NetBSD__)
typedef int64_t off64_t;
#endif
typedef ssize_t(FileDataReadFn)(struct FileData *filedata,
void *buffer,
size_t size,
bool *r_is_memchunk_identical);
typedef off64_t(FileDataSeekFn)(struct FileData *filedata, off64_t offset, int whence);
typedef struct FileData {
/** Linked list of BHeadN's. */
ListBase bhead_list;
enum eFileDataFlag flags;
bool is_eof;
size_t buffersize;
off64_t file_offset;
FileDataReadFn *read;
FileDataSeekFn *seek;
FileReader *file;
/** Regular file reading. */
int filedes;
/** Variables needed for reading from memory / stream / memory-mapped files. */
const char *buffer;
struct BLI_mmap_file *mmap_file;
/** Variables needed for reading from memfile (undo). */
struct MemFile *memfile;
/** Whether we are undoing (< 0) or redoing (> 0), used to choose which 'unchanged' flag to use
* to detect unchanged data from memfile. */
int undo_direction; /* eUndoStepDir */
/** Variables needed for reading from file. */
gzFile gzfiledes;
/** Gzip stream for memory decompression. */
z_stream strm;
/** Now only in use for library appending. */
char relabase[FILE_MAX];

View File

@@ -48,6 +48,7 @@
#include "BKE_lib_id.h"
#include "BKE_main.h"
#include "BKE_undo_system.h"
/* keep last */
#include "BLI_strict_flags.h"
@@ -273,3 +274,97 @@ bool BLO_memfile_write_file(struct MemFile *memfile, const char *filename)
}
return true;
}
static ssize_t undo_read(FileReader *reader, void *buffer, size_t size)
{
UndoReader *undo = (UndoReader *)reader;
static size_t seek = SIZE_MAX; /* The current position. */
static size_t offset = 0; /* Size of previous chunks. */
static MemFileChunk *chunk = NULL;
size_t chunkoffset, readsize, totread;
undo->memchunk_identical = true;
if (size == 0) {
return 0;
}
if (seek != (size_t)undo->reader.offset) {
chunk = undo->memfile->chunks.first;
seek = 0;
while (chunk) {
if (seek + chunk->size > (size_t)undo->reader.offset) {
break;
}
seek += chunk->size;
chunk = chunk->next;
}
offset = seek;
seek = (size_t)undo->reader.offset;
}
if (chunk) {
totread = 0;
do {
/* First check if it's on the end if current chunk. */
if (seek - offset == chunk->size) {
offset += chunk->size;
chunk = chunk->next;
}
/* Debug, should never happen. */
if (chunk == NULL) {
printf("illegal read, chunk zero\n");
return 0;
}
chunkoffset = seek - offset;
readsize = size - totread;
/* Data can be spread over multiple chunks, so clamp size
* to within this chunk, and then it will read further in
* the next chunk. */
if (chunkoffset + readsize > chunk->size) {
readsize = chunk->size - chunkoffset;
}
memcpy(POINTER_OFFSET(buffer, totread), chunk->buf + chunkoffset, readsize);
totread += readsize;
undo->reader.offset += (off64_t)readsize;
seek += readsize;
/* `is_identical` of current chunk represents whether it changed compared to previous undo
* step. this is fine in redo case, but not in undo case, where we need an extra flag
* defined when saving the next (future) step after the one we want to restore, as we are
* supposed to 'come from' that future undo step, and not the one before current one. */
undo->memchunk_identical &= undo->undo_direction == STEP_REDO ? chunk->is_identical :
chunk->is_identical_future;
} while (totread < size);
return (ssize_t)totread;
}
return 0;
}
static void undo_close(FileReader *reader)
{
MEM_freeN(reader);
}
FileReader *BLO_memfile_new_filereader(MemFile *memfile, int undo_direction)
{
UndoReader *undo = MEM_callocN(sizeof(UndoReader), __func__);
undo->memfile = memfile;
undo->undo_direction = undo_direction;
undo->reader.read = undo_read;
undo->reader.seek = NULL;
undo->reader.close = undo_close;
return (FileReader *)undo;
}

View File

@@ -23,8 +23,7 @@
#else
# include "BLI_winstuff.h"
# include "winsock2.h"
# include <io.h> /* for open close read */
# include <zlib.h> /* odd include order-issue */
# include <io.h> /* for open close read */
#endif
/* allow readfile to use deprecated functionality */

View File

@@ -28,8 +28,7 @@
#else
# include "BLI_winstuff.h"
# include "winsock2.h"
# include <io.h> /* for open close read */
# include <zlib.h> /* odd include order-issue */
# include <io.h> /* for open close read */
#endif
/* allow readfile to use deprecated functionality */

View File

@@ -83,7 +83,6 @@
# include "BLI_winstuff.h"
# include "winsock2.h"
# include <io.h>
# include <zlib.h> /* odd include order-issue */
#else
# include <unistd.h> /* FreeBSD, for write() and close(). */
#endif
@@ -101,7 +100,12 @@
#include "BLI_bitmap.h"
#include "BLI_blenlib.h"
#include "BLI_endian_defines.h"
#include "BLI_endian_switch.h"
#include "BLI_link_utils.h"
#include "BLI_linklist.h"
#include "BLI_math_base.h"
#include "BLI_mempool.h"
#include "BLI_threads.h"
#include "MEM_guardedalloc.h" /* MEM_freeN */
#include "BKE_blender_version.h"
@@ -129,14 +133,21 @@
#include <errno.h>
#include <zstd.h>
/* Make preferences read-only. */
#define U (*((const UserDef *)&U))
/* ********* my write, buffered writing with minimum size chunks ************ */
/* Use optimal allocation since blocks of this size are kept in memory for undo. */
#define MYWRITE_BUFFER_SIZE (MEM_SIZE_OPTIMAL(1 << 17)) /* 128kb */
#define MYWRITE_MAX_CHUNK (MEM_SIZE_OPTIMAL(1 << 15)) /* ~32kb */
#define MEM_BUFFER_SIZE (MEM_SIZE_OPTIMAL(1 << 17)) /* 128kb */
#define MEM_CHUNK_SIZE (MEM_SIZE_OPTIMAL(1 << 15)) /* ~32kb */
#define ZSTD_BUFFER_SIZE (1 << 21) /* 2mb */
#define ZSTD_CHUNK_SIZE (1 << 20) /* 1mb */
#define ZSTD_COMPRESSION_LEVEL 3
/** Use if we want to store how many bytes have been written to the file. */
// #define USE_WRITE_DATA_LEN
@@ -147,9 +158,16 @@
typedef enum {
WW_WRAP_NONE = 1,
WW_WRAP_ZLIB,
WW_WRAP_ZSTD,
} eWriteWrapType;
typedef struct ZstdFrame {
struct ZstdFrame *next, *prev;
uint32_t compressed_size;
uint32_t uncompressed_size;
} ZstdFrame;
typedef struct WriteWrap WriteWrap;
struct WriteWrap {
/* callbacks */
@@ -161,15 +179,23 @@ struct WriteWrap {
bool use_buf;
/* internal */
union {
int file_handle;
gzFile gz_handle;
} _user_data;
int file_handle;
struct {
ListBase threadpool;
ListBase tasks;
ThreadMutex mutex;
ThreadCondition condition;
int next_frame;
int num_frames;
int level;
ListBase frames;
bool write_error;
} zstd;
};
/* none */
#define FILE_HANDLE(ww) (ww)->_user_data.file_handle
static bool ww_open_none(WriteWrap *ww, const char *filepath)
{
int file;
@@ -177,7 +203,7 @@ static bool ww_open_none(WriteWrap *ww, const char *filepath)
file = BLI_open(filepath, O_BINARY + O_WRONLY + O_CREAT + O_TRUNC, 0666);
if (file != -1) {
FILE_HANDLE(ww) = file;
ww->file_handle = file;
return true;
}
@@ -185,39 +211,170 @@ static bool ww_open_none(WriteWrap *ww, const char *filepath)
}
static bool ww_close_none(WriteWrap *ww)
{
return (close(FILE_HANDLE(ww)) != -1);
return (close(ww->file_handle) != -1);
}
static size_t ww_write_none(WriteWrap *ww, const char *buf, size_t buf_len)
{
return write(FILE_HANDLE(ww), buf, buf_len);
return write(ww->file_handle, buf, buf_len);
}
#undef FILE_HANDLE
/* zlib */
#define FILE_HANDLE(ww) (ww)->_user_data.gz_handle
/* zstd */
static bool ww_open_zlib(WriteWrap *ww, const char *filepath)
typedef struct {
struct ZstdWriteBlockTask *next, *prev;
void *data;
size_t size;
int frame_number;
WriteWrap *ww;
} ZstdWriteBlockTask;
static void *zstd_write_task(void *userdata)
{
gzFile file;
ZstdWriteBlockTask *task = userdata;
WriteWrap *ww = task->ww;
file = BLI_gzopen(filepath, "wb1");
size_t out_buf_len = ZSTD_compressBound(task->size);
void *out_buf = MEM_mallocN(out_buf_len, "Zstd out buffer");
size_t out_size = ZSTD_compress(
out_buf, out_buf_len, task->data, task->size, ZSTD_COMPRESSION_LEVEL);
if (file != Z_NULL) {
FILE_HANDLE(ww) = file;
return true;
MEM_freeN(task->data);
BLI_mutex_lock(&ww->zstd.mutex);
while (ww->zstd.next_frame != task->frame_number) {
BLI_condition_wait(&ww->zstd.condition, &ww->zstd.mutex);
}
return false;
if (ZSTD_isError(out_size)) {
ww->zstd.write_error = true;
}
else {
if (ww_write_none(ww, out_buf, out_size) == out_size) {
ZstdFrame *frameinfo = MEM_mallocN(sizeof(ZstdFrame), "zstd frameinfo");
frameinfo->uncompressed_size = task->size;
frameinfo->compressed_size = out_size;
BLI_addtail(&ww->zstd.frames, frameinfo);
}
else {
ww->zstd.write_error = true;
}
}
ww->zstd.next_frame++;
BLI_mutex_unlock(&ww->zstd.mutex);
BLI_condition_notify_all(&ww->zstd.condition);
MEM_freeN(out_buf);
return NULL;
}
static bool ww_close_zlib(WriteWrap *ww)
static bool ww_open_zstd(WriteWrap *ww, const char *filepath)
{
return (gzclose(FILE_HANDLE(ww)) == Z_OK);
if (!ww_open_none(ww, filepath)) {
return false;
}
/* Leave one thread open for the main writing logic, unless we only have one HW thread. */
int num_threads = max_ii(1, BLI_system_thread_count() - 1);
BLI_threadpool_init(&ww->zstd.threadpool, zstd_write_task, num_threads);
BLI_mutex_init(&ww->zstd.mutex);
BLI_condition_init(&ww->zstd.condition);
return true;
}
static size_t ww_write_zlib(WriteWrap *ww, const char *buf, size_t buf_len)
static void zstd_write_u32_le(WriteWrap *ww, uint32_t val)
{
return gzwrite(FILE_HANDLE(ww), buf, buf_len);
#ifdef __BIG_ENDIAN__
BLI_endian_switch_uint32(&val);
#endif
ww_write_none(ww, (char *)&val, sizeof(uint32_t));
}
/* In order to implement efficient seeking when reading the .blend, we append
* a skippable frame that encodes information about the other frames present
* in the file.
* The format here follows the upstream spec for seekable files:
* https://github.com/facebook/zstd/blob/master/contrib/seekable_format/zstd_seekable_compression_format.md
* If this information is not present in a file (e.g. if it was compressed
* with external tools), it can still be opened in Blender, but seeking will
* not be supported, so more memory might be needed. */
static void zstd_write_seekable_frames(WriteWrap *ww)
{
/* Write seek table header (magic number and frame size). */
zstd_write_u32_le(ww, 0x184D2A5E);
/* The actual frame number might not match ww->zstd.num_frames if there was a write error. */
const uint32_t num_frames = BLI_listbase_count(&ww->zstd.frames);
/* Each frame consists of two u32, so 8 bytes each.
* After the frames, a footer containing two u32 and one byte (9 bytes total) is written. */
const uint32_t frame_size = num_frames * 8 + 9;
zstd_write_u32_le(ww, frame_size);
/* Write seek table entries. */
LISTBASE_FOREACH (ZstdFrame *, frame, &ww->zstd.frames) {
zstd_write_u32_le(ww, frame->compressed_size);
zstd_write_u32_le(ww, frame->uncompressed_size);
}
/* Write seek table footer (number of frames, option flags and second magic number). */
zstd_write_u32_le(ww, num_frames);
const char flags = 0; /* We don't store checksums for each frame. */
ww_write_none(ww, &flags, 1);
zstd_write_u32_le(ww, 0x8F92EAB1);
}
static bool ww_close_zstd(WriteWrap *ww)
{
BLI_threadpool_end(&ww->zstd.threadpool);
BLI_freelistN(&ww->zstd.tasks);
BLI_mutex_end(&ww->zstd.mutex);
BLI_condition_end(&ww->zstd.condition);
zstd_write_seekable_frames(ww);
BLI_freelistN(&ww->zstd.frames);
return ww_close_none(ww) && !ww->zstd.write_error;
}
static size_t ww_write_zstd(WriteWrap *ww, const char *buf, size_t buf_len)
{
if (ww->zstd.write_error) {
return 0;
}
ZstdWriteBlockTask *task = MEM_mallocN(sizeof(ZstdWriteBlockTask), __func__);
task->data = MEM_mallocN(buf_len, __func__);
memcpy(task->data, buf, buf_len);
task->size = buf_len;
task->frame_number = ww->zstd.num_frames++;
task->ww = ww;
BLI_mutex_lock(&ww->zstd.mutex);
BLI_addtail(&ww->zstd.tasks, task);
/* If there's a free worker thread, just push the block into that thread.
* Otherwise, we wait for the earliest thread to finish.
* We look up the earliest thread while holding the mutex, but release it
* before joining the thread to prevent a deadlock. */
ZstdWriteBlockTask *first_task = ww->zstd.tasks.first;
BLI_mutex_unlock(&ww->zstd.mutex);
if (!BLI_available_threads(&ww->zstd.threadpool)) {
BLI_threadpool_remove(&ww->zstd.threadpool, first_task);
/* If the task list was empty before we pushed our task, there should
* always be a free thread. */
BLI_assert(first_task != task);
BLI_remlink(&ww->zstd.tasks, first_task);
MEM_freeN(first_task);
}
BLI_threadpool_insert(&ww->zstd.threadpool, task);
return buf_len;
}
#undef FILE_HANDLE
/* --- end compression types --- */
@@ -226,11 +383,11 @@ static void ww_handle_init(eWriteWrapType ww_type, WriteWrap *r_ww)
memset(r_ww, 0, sizeof(*r_ww));
switch (ww_type) {
case WW_WRAP_ZLIB: {
r_ww->open = ww_open_zlib;
r_ww->close = ww_close_zlib;
r_ww->write = ww_write_zlib;
r_ww->use_buf = false;
case WW_WRAP_ZSTD: {
r_ww->open = ww_open_zstd;
r_ww->close = ww_close_zstd;
r_ww->write = ww_write_zstd;
r_ww->use_buf = true;
break;
}
default: {
@@ -252,10 +409,17 @@ static void ww_handle_init(eWriteWrapType ww_type, WriteWrap *r_ww)
typedef struct {
const struct SDNA *sdna;
/** Use for file and memory writing (fixed size of #MYWRITE_BUFFER_SIZE). */
uchar *buf;
/** Number of bytes used in #WriteData.buf (flushed when exceeded). */
size_t buf_used_len;
struct {
/** Use for file and memory writing (size stored in max_size). */
uchar *buf;
/** Number of bytes used in #WriteData.buf (flushed when exceeded). */
size_t used_len;
/** Maximum size of the buffer. */
size_t max_size;
/** Threshold above which writes get their own chunk. */
size_t chunk_size;
} buffer;
#ifdef USE_WRITE_DATA_LEN
/** Total number of bytes written. */
@@ -271,7 +435,7 @@ typedef struct {
bool use_memfile;
/**
* Wrap writing, so we can use zlib or
* Wrap writing, so we can use zstd or
* other compression types later, see: G_FILE_COMPRESS
* Will be NULL for UNDO.
*/
@@ -291,7 +455,15 @@ static WriteData *writedata_new(WriteWrap *ww)
wd->ww = ww;
if ((ww == NULL) || (ww->use_buf)) {
wd->buf = MEM_mallocN(MYWRITE_BUFFER_SIZE, "wd->buf");
if (ww == NULL) {
wd->buffer.max_size = MEM_BUFFER_SIZE;
wd->buffer.chunk_size = MEM_CHUNK_SIZE;
}
else {
wd->buffer.max_size = ZSTD_BUFFER_SIZE;
wd->buffer.chunk_size = ZSTD_CHUNK_SIZE;
}
wd->buffer.buf = MEM_mallocN(wd->buffer.max_size, "wd->buffer.buf");
}
return wd;
@@ -325,8 +497,8 @@ static void writedata_do_write(WriteData *wd, const void *mem, size_t memlen)
static void writedata_free(WriteData *wd)
{
if (wd->buf) {
MEM_freeN(wd->buf);
if (wd->buffer.buf) {
MEM_freeN(wd->buffer.buf);
}
MEM_freeN(wd);
}
@@ -343,9 +515,9 @@ static void writedata_free(WriteData *wd)
*/
static void mywrite_flush(WriteData *wd)
{
if (wd->buf_used_len != 0) {
writedata_do_write(wd, wd->buf, wd->buf_used_len);
wd->buf_used_len = 0;
if (wd->buffer.used_len != 0) {
writedata_do_write(wd, wd->buffer.buf, wd->buffer.used_len);
wd->buffer.used_len = 0;
}
}
@@ -369,20 +541,20 @@ static void mywrite(WriteData *wd, const void *adr, size_t len)
wd->write_len += len;
#endif
if (wd->buf == NULL) {
if (wd->buffer.buf == NULL) {
writedata_do_write(wd, adr, len);
}
else {
/* if we have a single big chunk, write existing data in
* buffer and write out big chunk in smaller pieces */
if (len > MYWRITE_MAX_CHUNK) {
if (wd->buf_used_len != 0) {
writedata_do_write(wd, wd->buf, wd->buf_used_len);
wd->buf_used_len = 0;
if (len > wd->buffer.chunk_size) {
if (wd->buffer.used_len != 0) {
writedata_do_write(wd, wd->buffer.buf, wd->buffer.used_len);
wd->buffer.used_len = 0;
}
do {
size_t writelen = MIN2(len, MYWRITE_MAX_CHUNK);
size_t writelen = MIN2(len, wd->buffer.chunk_size);
writedata_do_write(wd, adr, writelen);
adr = (const char *)adr + writelen;
len -= writelen;
@@ -392,14 +564,14 @@ static void mywrite(WriteData *wd, const void *adr, size_t len)
}
/* if data would overflow buffer, write out the buffer */
if (len + wd->buf_used_len > MYWRITE_BUFFER_SIZE - 1) {
writedata_do_write(wd, wd->buf, wd->buf_used_len);
wd->buf_used_len = 0;
if (len + wd->buffer.used_len > wd->buffer.max_size - 1) {
writedata_do_write(wd, wd->buffer.buf, wd->buffer.used_len);
wd->buffer.used_len = 0;
}
/* append data at end of buffer */
memcpy(&wd->buf[wd->buf_used_len], adr, len);
wd->buf_used_len += len;
memcpy(&wd->buffer.buf[wd->buffer.used_len], adr, len);
wd->buffer.used_len += len;
}
}
@@ -430,9 +602,9 @@ static WriteData *mywrite_begin(WriteWrap *ww, MemFile *compare, MemFile *curren
*/
static bool mywrite_end(WriteData *wd)
{
if (wd->buf_used_len != 0) {
writedata_do_write(wd, wd->buf, wd->buf_used_len);
wd->buf_used_len = 0;
if (wd->buffer.used_len != 0) {
writedata_do_write(wd, wd->buffer.buf, wd->buffer.used_len);
wd->buffer.used_len = 0;
}
if (wd->use_memfile) {
@@ -1150,7 +1322,6 @@ bool BLO_write_file(Main *mainvar,
ReportList *reports)
{
char tempname[FILE_MAX + 1];
eWriteWrapType ww_type;
WriteWrap ww;
eBLO_WritePathRemap remap_mode = params->remap_mode;
@@ -1172,14 +1343,7 @@ bool BLO_write_file(Main *mainvar,
/* open temporary file, so we preserve the original in case we crash */
BLI_snprintf(tempname, sizeof(tempname), "%s@", filepath);
if (write_flags & G_FILE_COMPRESS) {
ww_type = WW_WRAP_ZLIB;
}
else {
ww_type = WW_WRAP_NONE;
}
ww_handle_init(ww_type, &ww);
ww_handle_init((write_flags & G_FILE_COMPRESS) ? WW_WRAP_ZSTD : WW_WRAP_NONE, &ww);
if (ww.open(&ww, tempname) == false) {
BKE_reportf(

View File

@@ -246,7 +246,7 @@ void EEVEE_lookdev_cache_init(EEVEE_Data *vedata,
DRW_shgroup_uniform_float_copy(grp, "studioLightIntensity", shading->studiolight_intensity);
BKE_studiolight_ensure_flag(sl, STUDIOLIGHT_EQUIRECT_RADIANCE_GPUTEXTURE);
DRW_shgroup_uniform_texture_ex(grp, "studioLight", sl->equirect_radiance_gputexture, state);
/* Do not fadeout when doing probe rendering, only when drawing the background */
/* Do not fade-out when doing probe rendering, only when drawing the background. */
DRW_shgroup_uniform_float_copy(grp, "backgroundAlpha", 1.0f);
}
else {

View File

@@ -890,9 +890,9 @@ static int gpencil_interpolate_modal(bContext *C, wmOperator *op, const wmEvent
}
case MOUSEMOVE: /* calculate new position */
{
/* only handle mousemove if not doing numinput */
/* Only handle mouse-move if not doing numeric-input. */
if (has_numinput == false) {
/* update shift based on position of mouse */
/* Update shift based on position of mouse. */
gpencil_mouse_update_shift(tgpi, op, event);
/* update screen */

View File

@@ -1944,9 +1944,9 @@ static int gpencil_primitive_modal(bContext *C, wmOperator *op, const wmEvent *e
if (ELEM(tgpi->flag, IN_CURVE_EDIT)) {
break;
}
/* only handle mousemove if not doing numinput */
/* Only handle mouse-move if not doing numeric-input. */
if (has_numinput == false) {
/* update position of mouse */
/* Update position of mouse. */
copy_v2_v2(tgpi->end, tgpi->mval);
copy_v2_v2(tgpi->start, tgpi->origin);
if (tgpi->flag == IDLE) {

View File

@@ -6025,7 +6025,7 @@ static int ui_do_but_BLOCK(bContext *C, uiBut *but, uiHandleButtonData *data, co
* the slot menu fails to switch a second time.
*
* The active state of the button could be maintained some other way
* and remove this mousemove event.
* and remove this mouse-move event.
*/
WM_event_add_mousemove(data->window);
@@ -8364,7 +8364,7 @@ static void button_activate_state(bContext *C, uiBut *but, uiHandleButtonState s
}
}
/* wait for mousemove to enable drag */
/* Wait for mouse-move to enable drag. */
if (state == BUTTON_STATE_WAIT_DRAG) {
but->flag &= ~UI_SELECT;
}
@@ -8631,9 +8631,9 @@ static void button_activate_exit(
ui_but_update(but);
}
/* adds empty mousemove in queue for re-init handler, in case mouse is
/* Adds empty mouse-move in queue for re-initialize handler, in case mouse is
* still over a button. We cannot just check for this ourselves because
* at this point the mouse may be over a button in another region */
* at this point the mouse may be over a button in another region. */
if (mousemove) {
WM_event_add_mousemove(CTX_wm_window(C));
}

View File

@@ -6489,6 +6489,17 @@ void uiTemplateCacheFile(uiLayout *layout,
uiLayoutSetActive(row, engine_supports_procedural);
uiItemR(row, &fileptr, "use_render_procedural", 0, NULL, ICON_NONE);
const bool use_render_procedural = RNA_boolean_get(&fileptr, "use_render_procedural");
const bool use_prefetch = RNA_boolean_get(&fileptr, "use_prefetch");
row = uiLayoutRow(layout, false);
uiLayoutSetEnabled(row, use_render_procedural);
uiItemR(row, &fileptr, "use_prefetch", 0, NULL, ICON_NONE);
sub = uiLayoutRow(layout, false);
uiLayoutSetEnabled(sub, use_prefetch && use_render_procedural);
uiItemR(sub, &fileptr, "prefetch_cache_size", 0, NULL, ICON_NONE);
row = uiLayoutRowWithHeading(layout, true, IFACE_("Override Frame"));
sub = uiLayoutRow(row, true);
uiLayoutSetPropDecorate(sub, false);

View File

@@ -8627,7 +8627,7 @@ static int edbm_point_normals_modal(bContext *C, wmOperator *op, const wmEvent *
RNA_enum_set(op->ptr, "mode", mode);
}
/* Only handle mousemove event in case we are in mouse mode. */
/* Only handle mouse-move event in case we are in mouse mode. */
if (event->type == MOUSEMOVE || force_mousemove) {
if (mode == EDBM_CLNOR_POINTTO_MODE_MOUSE) {
ARegion *region = CTX_wm_region(C);

View File

@@ -4667,7 +4667,7 @@ typedef struct BrushEdit {
int lastmouse[2];
float zfac;
/* optional cached view settings to avoid setting on every mousemove */
/** Optional cached view settings to avoid setting on every mouse-move. */
PEData data;
} BrushEdit;

View File

@@ -64,7 +64,9 @@
#include <stdio.h>
/***************************** Render Engines ********************************/
/* -------------------------------------------------------------------- */
/** \name Render Engines
* \{ */
/* Update 3D viewport render or draw engine on changes to the scene or view settings. */
void ED_render_view3d_update(Depsgraph *depsgraph,
@@ -206,15 +208,15 @@ void ED_render_engine_changed(Main *bmain, const bool update_scene_data)
}
}
/* Update CacheFiles to ensure that procedurals are properly taken into account. */
/* Update #CacheFiles to ensure that procedurals are properly taken into account. */
LISTBASE_FOREACH (CacheFile *, cachefile, &bmain->cachefiles) {
/* Only update cachefiles which are set to use a render procedural. We do not use
* BKE_cachefile_uses_render_procedural here as we need to update regardless of the current
* engine or its settings. */
/* Only update cache-files which are set to use a render procedural.
* We do not use #BKE_cachefile_uses_render_procedural here as we need to update regardless of
* the current engine or its settings. */
if (cachefile->use_render_procedural) {
DEG_id_tag_update(&cachefile->id, ID_RECALC_COPY_ON_WRITE);
/* Rebuild relations so that modifiers are reconnected to or disconnected from the cachefile.
*/
/* Rebuild relations so that modifiers are reconnected to or disconnected from the
* cache-file. */
DEG_relations_tag_update(bmain);
}
}
@@ -227,10 +229,16 @@ void ED_render_view_layer_changed(Main *bmain, bScreen *screen)
}
}
/***************************** Updates ***********************************
* ED_render_id_flush_update gets called from DEG_id_tag_update, to do *
* editor level updates when the ID changes. when these ID blocks are in *
* the dependency graph, we can get rid of the manual dependency checks. */
/** \} */
/* -------------------------------------------------------------------- */
/** \name Updates
*
* #ED_render_id_flush_update gets called from #DEG_id_tag_update,
* to do editor level updates when the ID changes.
* When these ID blocks are in the dependency graph,
* we can get rid of the manual dependency checks.
* \{ */
static void material_changed(Main *UNUSED(bmain), Material *ma)
{
@@ -336,3 +344,5 @@ void ED_render_id_flush_update(const DEGEditorUpdateContext *update_ctx, ID *id)
break;
}
}
/** \} */

View File

@@ -299,7 +299,9 @@ static void view3d_ruler_item_project(RulerInfo *ruler_info, float r_co[3], cons
ED_view3d_win_to_3d_int(ruler_info->area->spacedata.first, ruler_info->region, r_co, xy, r_co);
}
/* use for mousemove events */
/**
* Use for mouse-move events.
*/
static bool view3d_ruler_item_mousemove(struct Depsgraph *depsgraph,
RulerInfo *ruler_info,
RulerItem *ruler_item,

View File

@@ -241,16 +241,15 @@ static int GPLayerToTransData(TransData *td,
for (gpf = gpl->frames.first; gpf; gpf = gpf->next) {
if (is_prop_edit || (gpf->flag & GP_FRAME_SELECT)) {
if (FrameOnMouseSide(side, (float)gpf->framenum, cfra)) {
/* memory is calloc'ed, so that should zero everything nicely for us */
td->val = &tfd->val;
td->ival = (float)gpf->framenum;
tfd->val = (float)gpf->framenum;
tfd->sdata = &gpf->framenum;
td->val = td->loc = &tfd->val; /* XXX: It's not a 3d array. */
td->ival = td->iloc[0] = (float)gpf->framenum;
td->center[0] = td->ival;
td->center[1] = ypos;
tfd->val = (float)gpf->framenum;
tfd->sdata = &gpf->framenum;
/* Advance `td` now. */
td++;
tfd++;

View File

@@ -131,12 +131,12 @@ static void autokeyframe_pose(
ListBase dsources = {NULL, NULL};
/* add datasource override for the camera object */
/* Add data-source override for the camera object. */
ANIM_relative_keyingset_add_source(&dsources, id, &RNA_PoseBone, pchan);
/* only insert into active keyingset? */
if (IS_AUTOKEY_FLAG(scene, ONLYKEYINGSET) && (active_ks)) {
/* run the active Keying Set on the current datasource */
/* Run the active Keying Set on the current data-source. */
ANIM_apply_keyingset(
C, &dsources, NULL, active_ks, MODIFYKEY_MODE_INSERT, anim_eval_context.eval_time);
}

View File

@@ -749,7 +749,7 @@ static void autokeyframe_object(
/* Get flags used for inserting keyframes. */
flag = ANIM_get_keyframing_flags(scene, true);
/* add datasource override for the object */
/* Add data-source override for the object. */
ANIM_relative_keyingset_add_source(&dsources, id, NULL, NULL);
if (IS_AUTOKEY_FLAG(scene, ONLYKEYINGSET) && (active_ks)) {

View File

@@ -35,14 +35,14 @@
#include "transform_snap.h"
/* -------------------------------------------------------------------- */
/** \name Snappint in Anim Editors
/** \name Snapping in Anim Editors
* \{ */
/**
* This function returns the snapping 'mode' for Animation Editors only.
* We cannot use the standard snapping due to NLA-strip scaling complexities.
*
* TODO: these modifier checks should be key-mappable.
* TODO: these modifier checks should be accessible from the key-map.
*/
short getAnimEdit_SnapMode(TransInfo *t)
{

View File

@@ -33,9 +33,9 @@ set(SRC
intern/generic_virtual_vector_array.cc
intern/multi_function.cc
intern/multi_function_builder.cc
intern/multi_function_network.cc
intern/multi_function_network_evaluation.cc
intern/multi_function_network_optimization.cc
intern/multi_function_procedure.cc
intern/multi_function_procedure_builder.cc
intern/multi_function_procedure_executor.cc
FN_cpp_type.hh
FN_cpp_type_make.hh
@@ -49,11 +49,11 @@ set(SRC
FN_multi_function_builder.hh
FN_multi_function_context.hh
FN_multi_function_data_type.hh
FN_multi_function_network.hh
FN_multi_function_network_evaluation.hh
FN_multi_function_network_optimization.hh
FN_multi_function_param_type.hh
FN_multi_function_params.hh
FN_multi_function_procedure.hh
FN_multi_function_procedure_builder.hh
FN_multi_function_procedure_executor.hh
FN_multi_function_signature.hh
)
@@ -68,7 +68,7 @@ if(WITH_GTESTS)
tests/FN_cpp_type_test.cc
tests/FN_generic_span_test.cc
tests/FN_generic_vector_array_test.cc
tests/FN_multi_function_network_test.cc
tests/FN_multi_function_procedure_test.cc
tests/FN_multi_function_test.cc
)
set(TEST_LIB

View File

@@ -129,7 +129,7 @@ class GVArray {
}
/* Same as `get_internal_single`, but `r_value` points to initialized memory. */
void get_single_to_uninitialized(void *r_value) const
void get_internal_single_to_uninitialized(void *r_value) const
{
type_->default_construct(r_value);
this->get_internal_single(r_value);

View File

@@ -1,536 +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.
*/
#pragma once
/** \file
* \ingroup fn
*
* A multi-function network (`MFNetwork`) allows you to connect multiple multi-functions. The
* `MFNetworkEvaluator` is a multi-function that wraps an entire network into a new multi-function
* (which can be used in another network and so on).
*
* A MFNetwork is a graph data structure with two kinds of nodes:
* - MFFunctionNode: Represents a multi-function. Its input and output sockets correspond to
* parameters of the referenced multi-function.
* - MFDummyNode: Does not reference a multi-function. Instead it just has sockets that can be
* used to represent node group inputs and outputs.
*
* Links represent data flow. Unlinked input sockets have no value. In order to execute a function
* node, all its inputs have to be connected to something.
*
* Links are only allowed between sockets with the exact same MFDataType. There are no implicit
* conversions.
*
* Every input and output parameter of a multi-function corresponds to exactly one input or output
* socket respectively. A multiple parameter belongs to exactly one input AND one output socket.
*
* There is an .to_dot() method that generates a graph in dot format for debugging purposes.
*/
#include "FN_multi_function.hh"
#include "BLI_vector_set.hh"
namespace blender::fn {
class MFNode;
class MFFunctionNode;
class MFDummyNode;
class MFSocket;
class MFInputSocket;
class MFOutputSocket;
class MFNetwork;
class MFNode : NonCopyable, NonMovable {
protected:
MFNetwork *network_;
Span<MFInputSocket *> inputs_;
Span<MFOutputSocket *> outputs_;
bool is_dummy_;
int id_;
friend MFNetwork;
public:
StringRefNull name() const;
int id() const;
MFNetwork &network();
const MFNetwork &network() const;
bool is_dummy() const;
bool is_function() const;
MFDummyNode &as_dummy();
const MFDummyNode &as_dummy() const;
MFFunctionNode &as_function();
const MFFunctionNode &as_function() const;
MFInputSocket &input(int index);
const MFInputSocket &input(int index) const;
MFOutputSocket &output(int index);
const MFOutputSocket &output(int index) const;
Span<MFInputSocket *> inputs();
Span<const MFInputSocket *> inputs() const;
Span<MFOutputSocket *> outputs();
Span<const MFOutputSocket *> outputs() const;
bool has_unlinked_inputs() const;
private:
void destruct_sockets();
};
class MFFunctionNode : public MFNode {
private:
const MultiFunction *function_;
Span<int> input_param_indices_;
Span<int> output_param_indices_;
friend MFNetwork;
public:
StringRefNull name() const;
const MultiFunction &function() const;
const MFInputSocket &input_for_param(int param_index) const;
const MFOutputSocket &output_for_param(int param_index) const;
};
class MFDummyNode : public MFNode {
protected:
StringRefNull name_;
MutableSpan<StringRefNull> input_names_;
MutableSpan<StringRefNull> output_names_;
friend MFNetwork;
public:
StringRefNull name() const;
Span<StringRefNull> input_names() const;
Span<StringRefNull> output_names() const;
};
class MFSocket : NonCopyable, NonMovable {
protected:
MFNode *node_;
bool is_output_;
int index_;
MFDataType data_type_;
int id_;
StringRefNull name_;
friend MFNetwork;
public:
StringRefNull name() const;
int id() const;
int index() const;
const MFDataType &data_type() const;
MFNode &node();
const MFNode &node() const;
bool is_input() const;
bool is_output() const;
MFInputSocket &as_input();
const MFInputSocket &as_input() const;
MFOutputSocket &as_output();
const MFOutputSocket &as_output() const;
};
class MFInputSocket : public MFSocket {
private:
MFOutputSocket *origin_;
friend MFNetwork;
public:
MFOutputSocket *origin();
const MFOutputSocket *origin() const;
};
class MFOutputSocket : public MFSocket {
private:
Vector<MFInputSocket *, 1> targets_;
friend MFNetwork;
public:
Span<MFInputSocket *> targets();
Span<const MFInputSocket *> targets() const;
};
class MFNetwork : NonCopyable, NonMovable {
private:
LinearAllocator<> allocator_;
VectorSet<MFFunctionNode *> function_nodes_;
VectorSet<MFDummyNode *> dummy_nodes_;
Vector<MFNode *> node_or_null_by_id_;
Vector<MFSocket *> socket_or_null_by_id_;
public:
MFNetwork() = default;
~MFNetwork();
MFFunctionNode &add_function(const MultiFunction &function);
MFDummyNode &add_dummy(StringRef name,
Span<MFDataType> input_types,
Span<MFDataType> output_types,
Span<StringRef> input_names,
Span<StringRef> output_names);
void add_link(MFOutputSocket &from, MFInputSocket &to);
MFOutputSocket &add_input(StringRef name, MFDataType data_type);
MFInputSocket &add_output(StringRef name, MFDataType data_type);
void relink(MFOutputSocket &old_output, MFOutputSocket &new_output);
void remove(MFNode &node);
void remove(Span<MFNode *> nodes);
int socket_id_amount() const;
int node_id_amount() const;
Span<MFDummyNode *> dummy_nodes();
Span<MFFunctionNode *> function_nodes();
MFNode *node_or_null_by_id(int id);
const MFNode *node_or_null_by_id(int id) const;
MFSocket *socket_or_null_by_id(int id);
const MFSocket *socket_or_null_by_id(int id) const;
void find_dependencies(Span<const MFInputSocket *> sockets,
VectorSet<const MFOutputSocket *> &r_dummy_sockets,
VectorSet<const MFInputSocket *> &r_unlinked_inputs) const;
bool have_dummy_or_unlinked_dependencies(Span<const MFInputSocket *> sockets) const;
std::string to_dot(Span<const MFNode *> marked_nodes = {}) const;
};
/* --------------------------------------------------------------------
* MFNode inline methods.
*/
inline StringRefNull MFNode::name() const
{
if (is_dummy_) {
return this->as_dummy().name();
}
else {
return this->as_function().name();
}
}
inline int MFNode::id() const
{
return id_;
}
inline MFNetwork &MFNode::network()
{
return *network_;
}
inline const MFNetwork &MFNode::network() const
{
return *network_;
}
inline bool MFNode::is_dummy() const
{
return is_dummy_;
}
inline bool MFNode::is_function() const
{
return !is_dummy_;
}
inline MFDummyNode &MFNode::as_dummy()
{
BLI_assert(is_dummy_);
return static_cast<MFDummyNode &>(*this);
}
inline const MFDummyNode &MFNode::as_dummy() const
{
BLI_assert(is_dummy_);
return static_cast<const MFDummyNode &>(*this);
}
inline MFFunctionNode &MFNode::as_function()
{
BLI_assert(!is_dummy_);
return static_cast<MFFunctionNode &>(*this);
}
inline const MFFunctionNode &MFNode::as_function() const
{
BLI_assert(!is_dummy_);
return static_cast<const MFFunctionNode &>(*this);
}
inline MFInputSocket &MFNode::input(int index)
{
return *inputs_[index];
}
inline const MFInputSocket &MFNode::input(int index) const
{
return *inputs_[index];
}
inline MFOutputSocket &MFNode::output(int index)
{
return *outputs_[index];
}
inline const MFOutputSocket &MFNode::output(int index) const
{
return *outputs_[index];
}
inline Span<MFInputSocket *> MFNode::inputs()
{
return inputs_;
}
inline Span<const MFInputSocket *> MFNode::inputs() const
{
return inputs_;
}
inline Span<MFOutputSocket *> MFNode::outputs()
{
return outputs_;
}
inline Span<const MFOutputSocket *> MFNode::outputs() const
{
return outputs_;
}
inline bool MFNode::has_unlinked_inputs() const
{
for (const MFInputSocket *socket : inputs_) {
if (socket->origin() == nullptr) {
return true;
}
}
return false;
}
/* --------------------------------------------------------------------
* MFFunctionNode inline methods.
*/
inline StringRefNull MFFunctionNode::name() const
{
return function_->name();
}
inline const MultiFunction &MFFunctionNode::function() const
{
return *function_;
}
inline const MFInputSocket &MFFunctionNode::input_for_param(int param_index) const
{
return this->input(input_param_indices_.first_index(param_index));
}
inline const MFOutputSocket &MFFunctionNode::output_for_param(int param_index) const
{
return this->output(output_param_indices_.first_index(param_index));
}
/* --------------------------------------------------------------------
* MFDummyNode inline methods.
*/
inline StringRefNull MFDummyNode::name() const
{
return name_;
}
inline Span<StringRefNull> MFDummyNode::input_names() const
{
return input_names_;
}
inline Span<StringRefNull> MFDummyNode::output_names() const
{
return output_names_;
}
/* --------------------------------------------------------------------
* MFSocket inline methods.
*/
inline StringRefNull MFSocket::name() const
{
return name_;
}
inline int MFSocket::id() const
{
return id_;
}
inline int MFSocket::index() const
{
return index_;
}
inline const MFDataType &MFSocket::data_type() const
{
return data_type_;
}
inline MFNode &MFSocket::node()
{
return *node_;
}
inline const MFNode &MFSocket::node() const
{
return *node_;
}
inline bool MFSocket::is_input() const
{
return !is_output_;
}
inline bool MFSocket::is_output() const
{
return is_output_;
}
inline MFInputSocket &MFSocket::as_input()
{
BLI_assert(this->is_input());
return static_cast<MFInputSocket &>(*this);
}
inline const MFInputSocket &MFSocket::as_input() const
{
BLI_assert(this->is_input());
return static_cast<const MFInputSocket &>(*this);
}
inline MFOutputSocket &MFSocket::as_output()
{
BLI_assert(this->is_output());
return static_cast<MFOutputSocket &>(*this);
}
inline const MFOutputSocket &MFSocket::as_output() const
{
BLI_assert(this->is_output());
return static_cast<const MFOutputSocket &>(*this);
}
/* --------------------------------------------------------------------
* MFInputSocket inline methods.
*/
inline MFOutputSocket *MFInputSocket::origin()
{
return origin_;
}
inline const MFOutputSocket *MFInputSocket::origin() const
{
return origin_;
}
/* --------------------------------------------------------------------
* MFOutputSocket inline methods.
*/
inline Span<MFInputSocket *> MFOutputSocket::targets()
{
return targets_;
}
inline Span<const MFInputSocket *> MFOutputSocket::targets() const
{
return targets_;
}
/* --------------------------------------------------------------------
* MFNetwork inline methods.
*/
inline Span<MFDummyNode *> MFNetwork::dummy_nodes()
{
return dummy_nodes_;
}
inline Span<MFFunctionNode *> MFNetwork::function_nodes()
{
return function_nodes_;
}
inline MFNode *MFNetwork::node_or_null_by_id(int id)
{
return node_or_null_by_id_[id];
}
inline const MFNode *MFNetwork::node_or_null_by_id(int id) const
{
return node_or_null_by_id_[id];
}
inline MFSocket *MFNetwork::socket_or_null_by_id(int id)
{
return socket_or_null_by_id_[id];
}
inline const MFSocket *MFNetwork::socket_or_null_by_id(int id) const
{
return socket_or_null_by_id_[id];
}
inline int MFNetwork::socket_id_amount() const
{
return socket_or_null_by_id_.size();
}
inline int MFNetwork::node_id_amount() const
{
return node_or_null_by_id_.size();
}
} // namespace blender::fn

View File

@@ -1,62 +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.
*/
#pragma once
/** \file
* \ingroup fn
*/
#include "FN_multi_function_network.hh"
namespace blender::fn {
class MFNetworkEvaluationStorage;
class MFNetworkEvaluator : public MultiFunction {
private:
MFSignature signature_;
Vector<const MFOutputSocket *> inputs_;
Vector<const MFInputSocket *> outputs_;
public:
MFNetworkEvaluator(Vector<const MFOutputSocket *> inputs, Vector<const MFInputSocket *> outputs);
void call(IndexMask mask, MFParams params, MFContext context) const override;
private:
using Storage = MFNetworkEvaluationStorage;
void copy_inputs_to_storage(MFParams params, Storage &storage) const;
void copy_outputs_to_storage(
MFParams params,
Storage &storage,
Vector<const MFInputSocket *> &outputs_to_initialize_in_the_end) const;
void evaluate_network_to_compute_outputs(MFContext &global_context, Storage &storage) const;
void evaluate_function(MFContext &global_context,
const MFFunctionNode &function_node,
Storage &storage) const;
bool can_do_single_value_evaluation(const MFFunctionNode &function_node, Storage &storage) const;
void initialize_remaining_outputs(MFParams params,
Storage &storage,
Span<const MFInputSocket *> remaining_outputs) const;
};
} // namespace blender::fn

View File

@@ -1,29 +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.
*/
#pragma once
#include "FN_multi_function_network.hh"
#include "BLI_resource_scope.hh"
namespace blender::fn::mf_network_optimization {
void dead_node_removal(MFNetwork &network);
void constant_folding(MFNetwork &network, ResourceScope &scope);
void common_subnetwork_elimination(MFNetwork &network);
} // namespace blender::fn::mf_network_optimization

View File

@@ -0,0 +1,408 @@
/*
* 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.
*/
#pragma once
/** \file
* \ingroup fn
*/
#include "FN_multi_function.hh"
namespace blender::fn {
class MFVariable;
class MFInstruction;
class MFCallInstruction;
class MFBranchInstruction;
class MFDestructInstruction;
class MFDummyInstruction;
class MFReturnInstruction;
class MFProcedure;
enum class MFInstructionType {
Call,
Branch,
Destruct,
Dummy,
Return,
};
class MFVariable : NonCopyable, NonMovable {
private:
MFDataType data_type_;
Vector<MFInstruction *> users_;
std::string name_;
int id_;
friend MFProcedure;
friend MFCallInstruction;
friend MFBranchInstruction;
friend MFDestructInstruction;
public:
MFDataType data_type() const;
Span<MFInstruction *> users();
StringRefNull name() const;
void set_name(std::string name);
int id() const;
};
class MFInstruction : NonCopyable, NonMovable {
protected:
MFInstructionType type_;
Vector<MFInstruction *> prev_;
friend MFProcedure;
friend MFCallInstruction;
friend MFBranchInstruction;
friend MFDestructInstruction;
friend MFDummyInstruction;
friend MFReturnInstruction;
public:
MFInstructionType type() const;
Span<MFInstruction *> prev();
Span<const MFInstruction *> prev() const;
};
class MFCallInstruction : public MFInstruction {
private:
const MultiFunction *fn_ = nullptr;
MFInstruction *next_ = nullptr;
MutableSpan<MFVariable *> params_;
friend MFProcedure;
public:
const MultiFunction &fn() const;
MFInstruction *next();
const MFInstruction *next() const;
void set_next(MFInstruction *instruction);
void set_param_variable(int param_index, MFVariable *variable);
void set_params(Span<MFVariable *> variables);
Span<MFVariable *> params();
Span<const MFVariable *> params() const;
};
class MFBranchInstruction : public MFInstruction {
private:
MFVariable *condition_ = nullptr;
MFInstruction *branch_true_ = nullptr;
MFInstruction *branch_false_ = nullptr;
friend MFProcedure;
public:
MFVariable *condition();
const MFVariable *condition() const;
void set_condition(MFVariable *variable);
MFInstruction *branch_true();
const MFInstruction *branch_true() const;
void set_branch_true(MFInstruction *instruction);
MFInstruction *branch_false();
const MFInstruction *branch_false() const;
void set_branch_false(MFInstruction *instruction);
};
class MFDestructInstruction : public MFInstruction {
private:
MFVariable *variable_ = nullptr;
MFInstruction *next_ = nullptr;
friend MFProcedure;
public:
MFVariable *variable();
const MFVariable *variable() const;
void set_variable(MFVariable *variable);
MFInstruction *next();
const MFInstruction *next() const;
void set_next(MFInstruction *instruction);
};
class MFDummyInstruction : public MFInstruction {
private:
MFInstruction *next_ = nullptr;
friend MFProcedure;
public:
MFInstruction *next();
const MFInstruction *next() const;
void set_next(MFInstruction *instruction);
};
class MFReturnInstruction : public MFInstruction {
};
struct MFParameter {
MFParamType::InterfaceType type;
MFVariable *variable;
};
struct ConstMFParameter {
MFParamType::InterfaceType type;
const MFVariable *variable;
};
class MFProcedure : NonCopyable, NonMovable {
private:
LinearAllocator<> allocator_;
Vector<MFCallInstruction *> call_instructions_;
Vector<MFBranchInstruction *> branch_instructions_;
Vector<MFDestructInstruction *> destruct_instructions_;
Vector<MFDummyInstruction *> dummy_instructions_;
Vector<MFReturnInstruction *> return_instructions_;
Vector<MFVariable *> variables_;
Vector<MFParameter> params_;
MFInstruction *entry_ = nullptr;
public:
MFProcedure() = default;
~MFProcedure();
MFVariable &new_variable(MFDataType data_type, std::string name = "");
MFCallInstruction &new_call_instruction(const MultiFunction &fn);
MFBranchInstruction &new_branch_instruction();
MFDestructInstruction &new_destruct_instruction();
MFDummyInstruction &new_dummy_instruction();
MFReturnInstruction &new_return_instruction();
void add_parameter(MFParamType::InterfaceType interface_type, MFVariable &variable);
Span<ConstMFParameter> params() const;
MFInstruction *entry();
const MFInstruction *entry() const;
void set_entry(MFInstruction &entry);
Span<MFVariable *> variables();
Span<const MFVariable *> variables() const;
void assert_valid() const;
std::string to_dot() const;
bool validate() const;
private:
bool validate_all_instruction_pointers_set() const;
bool validate_all_params_provided() const;
bool validate_same_variables_in_one_call() const;
bool validate_parameters() const;
bool validate_initialization() const;
struct InitState {
bool can_be_initialized = false;
bool can_be_uninitialized = false;
};
InitState find_initialization_state_before_instruction(const MFInstruction &target_instruction,
const MFVariable &variable) const;
};
namespace multi_function_procedure_types {
using MFVariable = fn::MFVariable;
using MFInstruction = fn::MFInstruction;
using MFCallInstruction = fn::MFCallInstruction;
using MFBranchInstruction = fn::MFBranchInstruction;
using MFDestructInstruction = fn::MFDestructInstruction;
using MFProcedure = fn::MFProcedure;
} // namespace multi_function_procedure_types
/* --------------------------------------------------------------------
* MFVariable inline methods.
*/
inline MFDataType MFVariable::data_type() const
{
return data_type_;
}
inline Span<MFInstruction *> MFVariable::users()
{
return users_;
}
inline StringRefNull MFVariable::name() const
{
return name_;
}
inline int MFVariable::id() const
{
return id_;
}
/* --------------------------------------------------------------------
* MFInstruction inline methods.
*/
inline MFInstructionType MFInstruction::type() const
{
return type_;
}
inline Span<MFInstruction *> MFInstruction::prev()
{
return prev_;
}
inline Span<const MFInstruction *> MFInstruction::prev() const
{
return prev_;
}
/* --------------------------------------------------------------------
* MFCallInstruction inline methods.
*/
inline const MultiFunction &MFCallInstruction::fn() const
{
return *fn_;
}
inline MFInstruction *MFCallInstruction::next()
{
return next_;
}
inline const MFInstruction *MFCallInstruction::next() const
{
return next_;
}
inline Span<MFVariable *> MFCallInstruction::params()
{
return params_;
}
inline Span<const MFVariable *> MFCallInstruction::params() const
{
return params_;
}
/* --------------------------------------------------------------------
* MFBranchInstruction inline methods.
*/
inline MFVariable *MFBranchInstruction::condition()
{
return condition_;
}
inline const MFVariable *MFBranchInstruction::condition() const
{
return condition_;
}
inline MFInstruction *MFBranchInstruction::branch_true()
{
return branch_true_;
}
inline const MFInstruction *MFBranchInstruction::branch_true() const
{
return branch_true_;
}
inline MFInstruction *MFBranchInstruction::branch_false()
{
return branch_false_;
}
inline const MFInstruction *MFBranchInstruction::branch_false() const
{
return branch_false_;
}
/* --------------------------------------------------------------------
* MFDestructInstruction inline methods.
*/
inline MFVariable *MFDestructInstruction::variable()
{
return variable_;
}
inline const MFVariable *MFDestructInstruction::variable() const
{
return variable_;
}
inline MFInstruction *MFDestructInstruction::next()
{
return next_;
}
inline const MFInstruction *MFDestructInstruction::next() const
{
return next_;
}
/* --------------------------------------------------------------------
* MFDummyInstruction inline methods.
*/
inline MFInstruction *MFDummyInstruction::next()
{
return next_;
}
inline const MFInstruction *MFDummyInstruction::next() const
{
return next_;
}
/* --------------------------------------------------------------------
* MFProcedure inline methods.
*/
inline Span<ConstMFParameter> MFProcedure::params() const
{
static_assert(sizeof(MFParameter) == sizeof(ConstMFParameter));
return params_.as_span().cast<ConstMFParameter>();
}
inline MFInstruction *MFProcedure::entry()
{
return entry_;
}
inline const MFInstruction *MFProcedure::entry() const
{
return entry_;
}
inline Span<MFVariable *> MFProcedure::variables()
{
return variables_;
}
inline Span<const MFVariable *> MFProcedure::variables() const
{
return variables_;
}
} // namespace blender::fn

View File

@@ -0,0 +1,251 @@
/*
* 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.
*/
#pragma once
/** \file
* \ingroup fn
*/
#include "FN_multi_function_procedure.hh"
namespace blender::fn {
class MFInstructionCursor {
private:
MFInstruction *instruction_ = nullptr;
/* Only used when it is a branch instruction. */
bool branch_output_ = false;
/* Only used when instruction is null. */
bool is_entry_ = false;
public:
MFInstructionCursor() = default;
MFInstructionCursor(MFCallInstruction &instruction);
MFInstructionCursor(MFDestructInstruction &instruction);
MFInstructionCursor(MFBranchInstruction &instruction, bool branch_output);
MFInstructionCursor(MFDummyInstruction &instruction);
static MFInstructionCursor Entry();
void insert(MFProcedure &procedure, MFInstruction *new_instruction);
};
class MFProcedureBuilder {
private:
MFProcedure *procedure_ = nullptr;
Vector<MFInstructionCursor> cursors_;
public:
struct Branch;
struct Loop;
MFProcedureBuilder(MFProcedure &procedure,
MFInstructionCursor initial_cursor = MFInstructionCursor::Entry());
MFProcedureBuilder(Span<MFProcedureBuilder *> builders);
MFProcedureBuilder(Branch &branch);
void set_cursor(const MFInstructionCursor &cursor);
void set_cursor(Span<MFInstructionCursor> cursors);
void set_cursor(Span<MFProcedureBuilder *> builders);
void set_cursor_after_branch(Branch &branch);
void set_cursor_after_loop(Loop &loop);
void add_destruct(MFVariable &variable);
void add_destruct(Span<MFVariable *> variables);
MFReturnInstruction &add_return();
Branch add_branch(MFVariable &condition);
Loop add_loop();
void add_loop_continue(Loop &loop);
void add_loop_break(Loop &loop);
MFCallInstruction &add_call_with_no_variables(const MultiFunction &fn);
MFCallInstruction &add_call_with_all_variables(const MultiFunction &fn,
Span<MFVariable *> param_variables);
Vector<MFVariable *> add_call(const MultiFunction &fn,
Span<MFVariable *> input_and_mutable_variables = {});
template<int OutputN>
std::array<MFVariable *, OutputN> add_call(const MultiFunction &fn,
Span<MFVariable *> input_and_mutable_variables = {});
void add_parameter(MFParamType::InterfaceType interface_type, MFVariable &variable);
MFVariable &add_parameter(MFParamType param_type, std::string name = "");
MFVariable &add_input_parameter(MFDataType data_type, std::string name = "");
template<typename T> MFVariable &add_single_input_parameter(std::string name = "");
template<typename T> MFVariable &add_single_mutable_parameter(std::string name = "");
void add_output_parameter(MFVariable &variable);
private:
void link_to_cursors(MFInstruction *instruction);
};
struct MFProcedureBuilder::Branch {
MFProcedureBuilder branch_true;
MFProcedureBuilder branch_false;
};
struct MFProcedureBuilder::Loop {
MFInstruction *begin = nullptr;
MFDummyInstruction *end = nullptr;
};
/* --------------------------------------------------------------------
* MFInstructionCursor inline methods.
*/
inline MFInstructionCursor::MFInstructionCursor(MFCallInstruction &instruction)
: instruction_(&instruction)
{
}
inline MFInstructionCursor::MFInstructionCursor(MFDestructInstruction &instruction)
: instruction_(&instruction)
{
}
inline MFInstructionCursor::MFInstructionCursor(MFBranchInstruction &instruction,
bool branch_output)
: instruction_(&instruction), branch_output_(branch_output)
{
}
inline MFInstructionCursor::MFInstructionCursor(MFDummyInstruction &instruction)
: instruction_(&instruction)
{
}
inline MFInstructionCursor MFInstructionCursor::Entry()
{
MFInstructionCursor cursor;
cursor.is_entry_ = true;
return cursor;
}
/* --------------------------------------------------------------------
* MFProcedureBuilder inline methods.
*/
inline MFProcedureBuilder::MFProcedureBuilder(Branch &branch)
: MFProcedureBuilder(*branch.branch_true.procedure_)
{
this->set_cursor_after_branch(branch);
}
inline MFProcedureBuilder::MFProcedureBuilder(MFProcedure &procedure,
MFInstructionCursor initial_cursor)
: procedure_(&procedure), cursors_({initial_cursor})
{
}
inline MFProcedureBuilder::MFProcedureBuilder(Span<MFProcedureBuilder *> builders)
: MFProcedureBuilder(*builders[0]->procedure_)
{
this->set_cursor(builders);
}
inline void MFProcedureBuilder::set_cursor(const MFInstructionCursor &cursor)
{
cursors_ = {cursor};
}
inline void MFProcedureBuilder::set_cursor(Span<MFInstructionCursor> cursors)
{
cursors_ = cursors;
}
inline void MFProcedureBuilder::set_cursor_after_branch(Branch &branch)
{
this->set_cursor({&branch.branch_false, &branch.branch_true});
}
inline void MFProcedureBuilder::set_cursor_after_loop(Loop &loop)
{
this->set_cursor(MFInstructionCursor{*loop.end});
}
inline void MFProcedureBuilder::set_cursor(Span<MFProcedureBuilder *> builders)
{
cursors_.clear();
for (MFProcedureBuilder *builder : builders) {
cursors_.extend(builder->cursors_);
}
}
template<int OutputN>
inline std::array<MFVariable *, OutputN> MFProcedureBuilder::add_call(
const MultiFunction &fn, Span<MFVariable *> input_and_mutable_variables)
{
Vector<MFVariable *> output_variables = this->add_call(fn, input_and_mutable_variables);
BLI_assert(output_variables.size() == OutputN);
std::array<MFVariable *, OutputN> output_array;
initialized_copy_n(output_variables.data(), OutputN, output_array.data());
return output_array;
}
inline void MFProcedureBuilder::add_parameter(MFParamType::InterfaceType interface_type,
MFVariable &variable)
{
procedure_->add_parameter(interface_type, variable);
}
inline MFVariable &MFProcedureBuilder::add_parameter(MFParamType param_type, std::string name)
{
MFVariable &variable = procedure_->new_variable(param_type.data_type(), std::move(name));
this->add_parameter(param_type.interface_type(), variable);
return variable;
}
inline MFVariable &MFProcedureBuilder::add_input_parameter(MFDataType data_type, std::string name)
{
return this->add_parameter(MFParamType(MFParamType::Input, data_type), std::move(name));
}
template<typename T>
inline MFVariable &MFProcedureBuilder::add_single_input_parameter(std::string name)
{
return this->add_parameter(MFParamType::ForSingleInput(CPPType::get<T>()), std::move(name));
}
template<typename T>
inline MFVariable &MFProcedureBuilder::add_single_mutable_parameter(std::string name)
{
return this->add_parameter(MFParamType::ForMutableSingle(CPPType::get<T>()), std::move(name));
}
inline void MFProcedureBuilder::add_output_parameter(MFVariable &variable)
{
this->add_parameter(MFParamType::Output, variable);
}
inline void MFProcedureBuilder::link_to_cursors(MFInstruction *instruction)
{
for (MFInstructionCursor &cursor : cursors_) {
cursor.insert(*procedure_, instruction);
}
}
} // namespace blender::fn

View File

@@ -16,19 +16,23 @@
#pragma once
#include <optional>
/** \file
* \ingroup fn
*/
#include "BKE_node.h"
#include "FN_multi_function_procedure.hh"
#include "FN_multi_function_data_type.hh"
namespace blender::fn {
namespace blender::nodes {
class MFProcedureExecutor : public MultiFunction {
private:
MFSignature signature_;
const MFProcedure &procedure_;
using fn::CPPType;
using fn::MFDataType;
public:
MFProcedureExecutor(std::string name, const MFProcedure &procedure);
std::optional<MFDataType> socket_mf_type_get(const bNodeSocketType &stype);
bool socket_is_mf_data_socket(const bNodeSocketType &stype);
void socket_expand_in_mf_network(SocketMFNetworkBuilder &builder);
void call(IndexMask mask, MFParams params, MFContext context) const override;
};
} // namespace blender::nodes
} // namespace blender::fn

View File

@@ -160,6 +160,21 @@ class MFSignatureBuilder {
}
}
void add(StringRef name, const MFParamType &param_type)
{
switch (param_type.interface_type()) {
case MFParamType::Input:
this->input(name, param_type.data_type());
break;
case MFParamType::Mutable:
this->mutable_(name, param_type.data_type());
break;
case MFParamType::Output:
this->output(name, param_type.data_type());
break;
}
}
/* Context */
/** This indicates that the function accesses the context. This disables optimizations that

View File

@@ -1,330 +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.
*/
#include "BLI_dot_export.hh"
#include "BLI_stack.hh"
#include "FN_multi_function_network.hh"
namespace blender::fn {
MFNetwork::~MFNetwork()
{
for (MFFunctionNode *node : function_nodes_) {
node->destruct_sockets();
node->~MFFunctionNode();
}
for (MFDummyNode *node : dummy_nodes_) {
node->destruct_sockets();
node->~MFDummyNode();
}
}
void MFNode::destruct_sockets()
{
for (MFInputSocket *socket : inputs_) {
socket->~MFInputSocket();
}
for (MFOutputSocket *socket : outputs_) {
socket->~MFOutputSocket();
}
}
/**
* Add a new function node to the network. The caller keeps the ownership of the function. The
* function should not be freed before the network. A reference to the new node is returned. The
* node is owned by the network.
*/
MFFunctionNode &MFNetwork::add_function(const MultiFunction &function)
{
Vector<int, 16> input_param_indices, output_param_indices;
for (int param_index : function.param_indices()) {
switch (function.param_type(param_index).interface_type()) {
case MFParamType::Input: {
input_param_indices.append(param_index);
break;
}
case MFParamType::Output: {
output_param_indices.append(param_index);
break;
}
case MFParamType::Mutable: {
input_param_indices.append(param_index);
output_param_indices.append(param_index);
break;
}
}
}
MFFunctionNode &node = *allocator_.construct<MFFunctionNode>().release();
function_nodes_.add_new(&node);
node.network_ = this;
node.is_dummy_ = false;
node.id_ = node_or_null_by_id_.append_and_get_index(&node);
node.function_ = &function;
node.input_param_indices_ = allocator_.construct_array_copy<int>(input_param_indices);
node.output_param_indices_ = allocator_.construct_array_copy<int>(output_param_indices);
node.inputs_ = allocator_.construct_elements_and_pointer_array<MFInputSocket>(
input_param_indices.size());
node.outputs_ = allocator_.construct_elements_and_pointer_array<MFOutputSocket>(
output_param_indices.size());
for (int i : input_param_indices.index_range()) {
int param_index = input_param_indices[i];
MFParamType param = function.param_type(param_index);
BLI_assert(param.is_input_or_mutable());
MFInputSocket &socket = *node.inputs_[i];
socket.data_type_ = param.data_type();
socket.node_ = &node;
socket.index_ = i;
socket.is_output_ = false;
socket.name_ = function.param_name(param_index);
socket.origin_ = nullptr;
socket.id_ = socket_or_null_by_id_.append_and_get_index(&socket);
}
for (int i : output_param_indices.index_range()) {
int param_index = output_param_indices[i];
MFParamType param = function.param_type(param_index);
BLI_assert(param.is_output_or_mutable());
MFOutputSocket &socket = *node.outputs_[i];
socket.data_type_ = param.data_type();
socket.node_ = &node;
socket.index_ = i;
socket.is_output_ = true;
socket.name_ = function.param_name(param_index);
socket.id_ = socket_or_null_by_id_.append_and_get_index(&socket);
}
return node;
}
/**
* Add a dummy node with the given input and output sockets.
*/
MFDummyNode &MFNetwork::add_dummy(StringRef name,
Span<MFDataType> input_types,
Span<MFDataType> output_types,
Span<StringRef> input_names,
Span<StringRef> output_names)
{
assert_same_size(input_types, input_names);
assert_same_size(output_types, output_names);
MFDummyNode &node = *allocator_.construct<MFDummyNode>().release();
dummy_nodes_.add_new(&node);
node.network_ = this;
node.is_dummy_ = true;
node.name_ = allocator_.copy_string(name);
node.id_ = node_or_null_by_id_.append_and_get_index(&node);
node.inputs_ = allocator_.construct_elements_and_pointer_array<MFInputSocket>(
input_types.size());
node.outputs_ = allocator_.construct_elements_and_pointer_array<MFOutputSocket>(
output_types.size());
node.input_names_ = allocator_.allocate_array<StringRefNull>(input_types.size());
node.output_names_ = allocator_.allocate_array<StringRefNull>(output_types.size());
for (int i : input_types.index_range()) {
MFInputSocket &socket = *node.inputs_[i];
socket.data_type_ = input_types[i];
socket.node_ = &node;
socket.index_ = i;
socket.is_output_ = false;
socket.name_ = allocator_.copy_string(input_names[i]);
socket.id_ = socket_or_null_by_id_.append_and_get_index(&socket);
node.input_names_[i] = socket.name_;
}
for (int i : output_types.index_range()) {
MFOutputSocket &socket = *node.outputs_[i];
socket.data_type_ = output_types[i];
socket.node_ = &node;
socket.index_ = i;
socket.is_output_ = true;
socket.name_ = allocator_.copy_string(output_names[i]);
socket.id_ = socket_or_null_by_id_.append_and_get_index(&socket);
node.output_names_[i] = socket.name_;
}
return node;
}
/**
* Connect two sockets. This invokes undefined behavior if the sockets belong to different
* networks, the sockets have a different data type, or the `to` socket is connected to something
* else already.
*/
void MFNetwork::add_link(MFOutputSocket &from, MFInputSocket &to)
{
BLI_assert(to.origin_ == nullptr);
BLI_assert(from.node_->network_ == to.node_->network_);
BLI_assert(from.data_type_ == to.data_type_);
from.targets_.append(&to);
to.origin_ = &from;
}
MFOutputSocket &MFNetwork::add_input(StringRef name, MFDataType data_type)
{
return this->add_dummy(name, {}, {data_type}, {}, {"Value"}).output(0);
}
MFInputSocket &MFNetwork::add_output(StringRef name, MFDataType data_type)
{
return this->add_dummy(name, {data_type}, {}, {"Value"}, {}).input(0);
}
void MFNetwork::relink(MFOutputSocket &old_output, MFOutputSocket &new_output)
{
BLI_assert(&old_output != &new_output);
BLI_assert(old_output.data_type_ == new_output.data_type_);
for (MFInputSocket *input : old_output.targets()) {
input->origin_ = &new_output;
}
new_output.targets_.extend(old_output.targets_);
old_output.targets_.clear();
}
void MFNetwork::remove(MFNode &node)
{
for (MFInputSocket *socket : node.inputs_) {
if (socket->origin_ != nullptr) {
socket->origin_->targets_.remove_first_occurrence_and_reorder(socket);
}
socket_or_null_by_id_[socket->id_] = nullptr;
}
for (MFOutputSocket *socket : node.outputs_) {
for (MFInputSocket *other : socket->targets_) {
other->origin_ = nullptr;
}
socket_or_null_by_id_[socket->id_] = nullptr;
}
node.destruct_sockets();
if (node.is_dummy()) {
MFDummyNode &dummy_node = node.as_dummy();
dummy_node.~MFDummyNode();
dummy_nodes_.remove_contained(&dummy_node);
}
else {
MFFunctionNode &function_node = node.as_function();
function_node.~MFFunctionNode();
function_nodes_.remove_contained(&function_node);
}
node_or_null_by_id_[node.id_] = nullptr;
}
void MFNetwork::remove(Span<MFNode *> nodes)
{
for (MFNode *node : nodes) {
this->remove(*node);
}
}
void MFNetwork::find_dependencies(Span<const MFInputSocket *> sockets,
VectorSet<const MFOutputSocket *> &r_dummy_sockets,
VectorSet<const MFInputSocket *> &r_unlinked_inputs) const
{
Set<const MFNode *> visited_nodes;
Stack<const MFInputSocket *> sockets_to_check;
sockets_to_check.push_multiple(sockets);
while (!sockets_to_check.is_empty()) {
const MFInputSocket &socket = *sockets_to_check.pop();
const MFOutputSocket *origin_socket = socket.origin();
if (origin_socket == nullptr) {
r_unlinked_inputs.add(&socket);
continue;
}
const MFNode &origin_node = origin_socket->node();
if (origin_node.is_dummy()) {
r_dummy_sockets.add(origin_socket);
continue;
}
if (visited_nodes.add(&origin_node)) {
sockets_to_check.push_multiple(origin_node.inputs());
}
}
}
bool MFNetwork::have_dummy_or_unlinked_dependencies(Span<const MFInputSocket *> sockets) const
{
VectorSet<const MFOutputSocket *> dummy_sockets;
VectorSet<const MFInputSocket *> unlinked_inputs;
this->find_dependencies(sockets, dummy_sockets, unlinked_inputs);
return dummy_sockets.size() + unlinked_inputs.size() > 0;
}
std::string MFNetwork::to_dot(Span<const MFNode *> marked_nodes) const
{
dot::DirectedGraph digraph;
digraph.set_rankdir(dot::Attr_rankdir::LeftToRight);
Map<const MFNode *, dot::NodeWithSocketsRef> dot_nodes;
Vector<const MFNode *> all_nodes;
all_nodes.extend(function_nodes_.as_span().cast<const MFNode *>());
all_nodes.extend(dummy_nodes_.as_span().cast<const MFNode *>());
for (const MFNode *node : all_nodes) {
dot::Node &dot_node = digraph.new_node("");
Vector<std::string> input_names, output_names;
for (const MFInputSocket *socket : node->inputs_) {
input_names.append(socket->name() + "(" + socket->data_type().to_string() + ")");
}
for (const MFOutputSocket *socket : node->outputs_) {
output_names.append(socket->name() + " (" + socket->data_type().to_string() + ")");
}
dot::NodeWithSocketsRef dot_node_ref{dot_node, node->name(), input_names, output_names};
dot_nodes.add_new(node, dot_node_ref);
}
for (const MFDummyNode *node : dummy_nodes_) {
dot_nodes.lookup(node).node().set_background_color("#77EE77");
}
for (const MFNode *node : marked_nodes) {
dot_nodes.lookup(node).node().set_background_color("#7777EE");
}
for (const MFNode *to_node : all_nodes) {
dot::NodeWithSocketsRef to_dot_node = dot_nodes.lookup(to_node);
for (const MFInputSocket *to_socket : to_node->inputs_) {
const MFOutputSocket *from_socket = to_socket->origin_;
if (from_socket != nullptr) {
const MFNode *from_node = from_socket->node_;
dot::NodeWithSocketsRef from_dot_node = dot_nodes.lookup(from_node);
digraph.new_edge(from_dot_node.output(from_socket->index_),
to_dot_node.input(to_socket->index_));
}
}
}
return digraph.to_dot_string();
}
} // namespace blender::fn

View File

@@ -1,501 +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.
*/
/** \file
* \ingroup fn
*/
/* Used to check if two multi-functions have the exact same type. */
#include <typeinfo>
#include "FN_multi_function_builder.hh"
#include "FN_multi_function_network_evaluation.hh"
#include "FN_multi_function_network_optimization.hh"
#include "BLI_disjoint_set.hh"
#include "BLI_ghash.h"
#include "BLI_map.hh"
#include "BLI_multi_value_map.hh"
#include "BLI_rand.h"
#include "BLI_stack.hh"
namespace blender::fn::mf_network_optimization {
/* -------------------------------------------------------------------- */
/** \name Utility functions to find nodes in a network.
* \{ */
static bool set_tag_and_check_if_modified(bool &tag, bool new_value)
{
if (tag != new_value) {
tag = new_value;
return true;
}
return false;
}
static Array<bool> mask_nodes_to_the_left(MFNetwork &network, Span<MFNode *> nodes)
{
Array<bool> is_to_the_left(network.node_id_amount(), false);
Stack<MFNode *> nodes_to_check;
for (MFNode *node : nodes) {
is_to_the_left[node->id()] = true;
nodes_to_check.push(node);
}
while (!nodes_to_check.is_empty()) {
MFNode &node = *nodes_to_check.pop();
for (MFInputSocket *input_socket : node.inputs()) {
MFOutputSocket *origin = input_socket->origin();
if (origin != nullptr) {
MFNode &origin_node = origin->node();
if (set_tag_and_check_if_modified(is_to_the_left[origin_node.id()], true)) {
nodes_to_check.push(&origin_node);
}
}
}
}
return is_to_the_left;
}
static Array<bool> mask_nodes_to_the_right(MFNetwork &network, Span<MFNode *> nodes)
{
Array<bool> is_to_the_right(network.node_id_amount(), false);
Stack<MFNode *> nodes_to_check;
for (MFNode *node : nodes) {
is_to_the_right[node->id()] = true;
nodes_to_check.push(node);
}
while (!nodes_to_check.is_empty()) {
MFNode &node = *nodes_to_check.pop();
for (MFOutputSocket *output_socket : node.outputs()) {
for (MFInputSocket *target_socket : output_socket->targets()) {
MFNode &target_node = target_socket->node();
if (set_tag_and_check_if_modified(is_to_the_right[target_node.id()], true)) {
nodes_to_check.push(&target_node);
}
}
}
}
return is_to_the_right;
}
static Vector<MFNode *> find_nodes_based_on_mask(MFNetwork &network,
Span<bool> id_mask,
bool mask_value)
{
Vector<MFNode *> nodes;
for (int id : id_mask.index_range()) {
if (id_mask[id] == mask_value) {
MFNode *node = network.node_or_null_by_id(id);
if (node != nullptr) {
nodes.append(node);
}
}
}
return nodes;
}
/** \} */
/* -------------------------------------------------------------------- */
/** \name Dead Node Removal
* \{ */
/**
* Unused nodes are all those nodes that no dummy node depends upon.
*/
void dead_node_removal(MFNetwork &network)
{
Array<bool> node_is_used_mask = mask_nodes_to_the_left(network,
network.dummy_nodes().cast<MFNode *>());
Vector<MFNode *> nodes_to_remove = find_nodes_based_on_mask(network, node_is_used_mask, false);
network.remove(nodes_to_remove);
}
/** \} */
/* -------------------------------------------------------------------- */
/** \name Constant Folding
* \{ */
static bool function_node_can_be_constant(MFFunctionNode *node)
{
if (node->has_unlinked_inputs()) {
return false;
}
if (node->function().depends_on_context()) {
return false;
}
return true;
}
static Vector<MFNode *> find_non_constant_nodes(MFNetwork &network)
{
Vector<MFNode *> non_constant_nodes;
non_constant_nodes.extend(network.dummy_nodes().cast<MFNode *>());
for (MFFunctionNode *node : network.function_nodes()) {
if (!function_node_can_be_constant(node)) {
non_constant_nodes.append(node);
}
}
return non_constant_nodes;
}
static bool output_has_non_constant_target_node(MFOutputSocket *output_socket,
Span<bool> is_not_constant_mask)
{
for (MFInputSocket *target_socket : output_socket->targets()) {
MFNode &target_node = target_socket->node();
bool target_is_not_constant = is_not_constant_mask[target_node.id()];
if (target_is_not_constant) {
return true;
}
}
return false;
}
static MFInputSocket *try_find_dummy_target_socket(MFOutputSocket *output_socket)
{
for (MFInputSocket *target_socket : output_socket->targets()) {
if (target_socket->node().is_dummy()) {
return target_socket;
}
}
return nullptr;
}
static Vector<MFInputSocket *> find_constant_inputs_to_fold(
MFNetwork &network, Vector<MFDummyNode *> &r_temporary_nodes)
{
Vector<MFNode *> non_constant_nodes = find_non_constant_nodes(network);
Array<bool> is_not_constant_mask = mask_nodes_to_the_right(network, non_constant_nodes);
Vector<MFNode *> constant_nodes = find_nodes_based_on_mask(network, is_not_constant_mask, false);
Vector<MFInputSocket *> sockets_to_compute;
for (MFNode *node : constant_nodes) {
if (node->inputs().size() == 0) {
continue;
}
for (MFOutputSocket *output_socket : node->outputs()) {
MFDataType data_type = output_socket->data_type();
if (output_has_non_constant_target_node(output_socket, is_not_constant_mask)) {
MFInputSocket *dummy_target = try_find_dummy_target_socket(output_socket);
if (dummy_target == nullptr) {
dummy_target = &network.add_output("Dummy", data_type);
network.add_link(*output_socket, *dummy_target);
r_temporary_nodes.append(&dummy_target->node().as_dummy());
}
sockets_to_compute.append(dummy_target);
}
}
}
return sockets_to_compute;
}
static void prepare_params_for_constant_folding(const MultiFunction &network_fn,
MFParamsBuilder &params,
ResourceScope &scope)
{
for (int param_index : network_fn.param_indices()) {
MFParamType param_type = network_fn.param_type(param_index);
MFDataType data_type = param_type.data_type();
switch (data_type.category()) {
case MFDataType::Single: {
/* Allocates memory for a single constant folded value. */
const CPPType &cpp_type = data_type.single_type();
void *buffer = scope.linear_allocator().allocate(cpp_type.size(), cpp_type.alignment());
GMutableSpan array{cpp_type, buffer, 1};
params.add_uninitialized_single_output(array);
break;
}
case MFDataType::Vector: {
/* Allocates memory for a constant folded vector. */
const CPPType &cpp_type = data_type.vector_base_type();
GVectorArray &vector_array = scope.construct<GVectorArray>(AT, cpp_type, 1);
params.add_vector_output(vector_array);
break;
}
}
}
}
static Array<MFOutputSocket *> add_constant_folded_sockets(const MultiFunction &network_fn,
MFParamsBuilder &params,
ResourceScope &scope,
MFNetwork &network)
{
Array<MFOutputSocket *> folded_sockets{network_fn.param_indices().size(), nullptr};
for (int param_index : network_fn.param_indices()) {
MFParamType param_type = network_fn.param_type(param_index);
MFDataType data_type = param_type.data_type();
const MultiFunction *constant_fn = nullptr;
switch (data_type.category()) {
case MFDataType::Single: {
const CPPType &cpp_type = data_type.single_type();
GMutableSpan array = params.computed_array(param_index);
void *buffer = array.data();
scope.add(buffer, array.type().destruct_fn(), AT);
constant_fn = &scope.construct<CustomMF_GenericConstant>(AT, cpp_type, buffer);
break;
}
case MFDataType::Vector: {
GVectorArray &vector_array = params.computed_vector_array(param_index);
GSpan array = vector_array[0];
constant_fn = &scope.construct<CustomMF_GenericConstantArray>(AT, array);
break;
}
}
MFFunctionNode &folded_node = network.add_function(*constant_fn);
folded_sockets[param_index] = &folded_node.output(0);
}
return folded_sockets;
}
static Array<MFOutputSocket *> compute_constant_sockets_and_add_folded_nodes(
MFNetwork &network, Span<const MFInputSocket *> sockets_to_compute, ResourceScope &scope)
{
MFNetworkEvaluator network_fn{{}, sockets_to_compute};
MFContextBuilder context;
MFParamsBuilder params{network_fn, 1};
prepare_params_for_constant_folding(network_fn, params, scope);
network_fn.call({0}, params, context);
return add_constant_folded_sockets(network_fn, params, scope, network);
}
class MyClass {
MFDummyNode node;
};
/**
* Find function nodes that always output the same value and replace those with constant nodes.
*/
void constant_folding(MFNetwork &network, ResourceScope &scope)
{
Vector<MFDummyNode *> temporary_nodes;
Vector<MFInputSocket *> inputs_to_fold = find_constant_inputs_to_fold(network, temporary_nodes);
if (inputs_to_fold.size() == 0) {
return;
}
Array<MFOutputSocket *> folded_sockets = compute_constant_sockets_and_add_folded_nodes(
network, inputs_to_fold, scope);
for (int i : inputs_to_fold.index_range()) {
MFOutputSocket &original_socket = *inputs_to_fold[i]->origin();
network.relink(original_socket, *folded_sockets[i]);
}
network.remove(temporary_nodes.as_span().cast<MFNode *>());
}
/** \} */
/* -------------------------------------------------------------------- */
/** \name Common Sub-network Elimination
* \{ */
static uint64_t compute_node_hash(MFFunctionNode &node, RNG *rng, Span<uint64_t> node_hashes)
{
if (node.function().depends_on_context()) {
return BLI_rng_get_uint(rng);
}
if (node.has_unlinked_inputs()) {
return BLI_rng_get_uint(rng);
}
uint64_t combined_inputs_hash = 394659347u;
for (MFInputSocket *input_socket : node.inputs()) {
MFOutputSocket *origin_socket = input_socket->origin();
uint64_t input_hash = BLI_ghashutil_combine_hash(node_hashes[origin_socket->node().id()],
origin_socket->index());
combined_inputs_hash = BLI_ghashutil_combine_hash(combined_inputs_hash, input_hash);
}
uint64_t function_hash = node.function().hash();
uint64_t node_hash = BLI_ghashutil_combine_hash(combined_inputs_hash, function_hash);
return node_hash;
}
/**
* Produces a hash for every node. Two nodes with the same hash should have a high probability of
* outputting the same values.
*/
static Array<uint64_t> compute_node_hashes(MFNetwork &network)
{
RNG *rng = BLI_rng_new(0);
Array<uint64_t> node_hashes(network.node_id_amount());
Array<bool> node_is_hashed(network.node_id_amount(), false);
/* No dummy nodes are not assumed to output the same values. */
for (MFDummyNode *node : network.dummy_nodes()) {
uint64_t node_hash = BLI_rng_get_uint(rng);
node_hashes[node->id()] = node_hash;
node_is_hashed[node->id()] = true;
}
Stack<MFFunctionNode *> nodes_to_check;
nodes_to_check.push_multiple(network.function_nodes());
while (!nodes_to_check.is_empty()) {
MFFunctionNode &node = *nodes_to_check.peek();
if (node_is_hashed[node.id()]) {
nodes_to_check.pop();
continue;
}
/* Make sure that origin nodes are hashed first. */
bool all_dependencies_ready = true;
for (MFInputSocket *input_socket : node.inputs()) {
MFOutputSocket *origin_socket = input_socket->origin();
if (origin_socket != nullptr) {
MFNode &origin_node = origin_socket->node();
if (!node_is_hashed[origin_node.id()]) {
all_dependencies_ready = false;
nodes_to_check.push(&origin_node.as_function());
}
}
}
if (!all_dependencies_ready) {
continue;
}
uint64_t node_hash = compute_node_hash(node, rng, node_hashes);
node_hashes[node.id()] = node_hash;
node_is_hashed[node.id()] = true;
nodes_to_check.pop();
}
BLI_rng_free(rng);
return node_hashes;
}
static MultiValueMap<uint64_t, MFNode *> group_nodes_by_hash(MFNetwork &network,
Span<uint64_t> node_hashes)
{
MultiValueMap<uint64_t, MFNode *> nodes_by_hash;
for (int id : IndexRange(network.node_id_amount())) {
MFNode *node = network.node_or_null_by_id(id);
if (node != nullptr) {
uint64_t node_hash = node_hashes[id];
nodes_by_hash.add(node_hash, node);
}
}
return nodes_by_hash;
}
static bool functions_are_equal(const MultiFunction &a, const MultiFunction &b)
{
if (&a == &b) {
return true;
}
if (typeid(a) == typeid(b)) {
return a.equals(b);
}
return false;
}
static bool nodes_output_same_values(DisjointSet &cache, const MFNode &a, const MFNode &b)
{
if (cache.in_same_set(a.id(), b.id())) {
return true;
}
if (a.is_dummy() || b.is_dummy()) {
return false;
}
if (!functions_are_equal(a.as_function().function(), b.as_function().function())) {
return false;
}
for (int i : a.inputs().index_range()) {
const MFOutputSocket *origin_a = a.input(i).origin();
const MFOutputSocket *origin_b = b.input(i).origin();
if (origin_a == nullptr || origin_b == nullptr) {
return false;
}
if (!nodes_output_same_values(cache, origin_a->node(), origin_b->node())) {
return false;
}
}
cache.join(a.id(), b.id());
return true;
}
static void relink_duplicate_nodes(MFNetwork &network,
MultiValueMap<uint64_t, MFNode *> &nodes_by_hash)
{
DisjointSet same_node_cache{network.node_id_amount()};
for (Span<MFNode *> nodes_with_same_hash : nodes_by_hash.values()) {
if (nodes_with_same_hash.size() <= 1) {
continue;
}
Vector<MFNode *, 16> nodes_to_check = nodes_with_same_hash;
while (nodes_to_check.size() >= 2) {
Vector<MFNode *, 16> remaining_nodes;
MFNode &deduplicated_node = *nodes_to_check[0];
for (MFNode *node : nodes_to_check.as_span().drop_front(1)) {
/* This is true with fairly high probability, but hash collisions can happen. So we have to
* check if the node actually output the same values. */
if (nodes_output_same_values(same_node_cache, deduplicated_node, *node)) {
for (int i : deduplicated_node.outputs().index_range()) {
network.relink(node->output(i), deduplicated_node.output(i));
}
}
else {
remaining_nodes.append(node);
}
}
nodes_to_check = std::move(remaining_nodes);
}
}
}
/**
* Tries to detect duplicate sub-networks and eliminates them. This can help quite a lot when node
* groups were used to create the network.
*/
void common_subnetwork_elimination(MFNetwork &network)
{
Array<uint64_t> node_hashes = compute_node_hashes(network);
MultiValueMap<uint64_t, MFNode *> nodes_by_hash = group_nodes_by_hash(network, node_hashes);
relink_duplicate_nodes(network, nodes_by_hash);
}
/** \} */
} // namespace blender::fn::mf_network_optimization

View File

@@ -0,0 +1,743 @@
/*
* 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.
*/
#include "FN_multi_function_procedure.hh"
#include "BLI_dot_export.hh"
#include "BLI_stack.hh"
namespace blender::fn {
void MFVariable::set_name(std::string name)
{
name_ = std::move(name);
}
void MFCallInstruction::set_next(MFInstruction *instruction)
{
if (next_ != nullptr) {
next_->prev_.remove_first_occurrence_and_reorder(this);
}
if (instruction != nullptr) {
instruction->prev_.append(this);
}
next_ = instruction;
}
void MFCallInstruction::set_param_variable(int param_index, MFVariable *variable)
{
if (params_[param_index] != nullptr) {
params_[param_index]->users_.remove_first_occurrence_and_reorder(this);
}
if (variable != nullptr) {
BLI_assert(fn_->param_type(param_index).data_type() == variable->data_type());
variable->users_.append(this);
}
params_[param_index] = variable;
}
void MFCallInstruction::set_params(Span<MFVariable *> variables)
{
BLI_assert(variables.size() == params_.size());
for (const int i : variables.index_range()) {
this->set_param_variable(i, variables[i]);
}
}
void MFBranchInstruction::set_condition(MFVariable *variable)
{
if (condition_ != nullptr) {
condition_->users_.remove_first_occurrence_and_reorder(this);
}
if (variable != nullptr) {
variable->users_.append(this);
}
condition_ = variable;
}
void MFBranchInstruction::set_branch_true(MFInstruction *instruction)
{
if (branch_true_ != nullptr) {
branch_true_->prev_.remove_first_occurrence_and_reorder(this);
}
if (instruction != nullptr) {
instruction->prev_.append(this);
}
branch_true_ = instruction;
}
void MFBranchInstruction::set_branch_false(MFInstruction *instruction)
{
if (branch_false_ != nullptr) {
branch_false_->prev_.remove_first_occurrence_and_reorder(this);
}
if (instruction != nullptr) {
instruction->prev_.append(this);
}
branch_false_ = instruction;
}
void MFDestructInstruction::set_variable(MFVariable *variable)
{
if (variable_ != nullptr) {
variable_->users_.remove_first_occurrence_and_reorder(this);
}
if (variable != nullptr) {
variable->users_.append(this);
}
variable_ = variable;
}
void MFDestructInstruction::set_next(MFInstruction *instruction)
{
if (next_ != nullptr) {
next_->prev_.remove_first_occurrence_and_reorder(this);
}
if (instruction != nullptr) {
instruction->prev_.append(this);
}
next_ = instruction;
}
void MFDummyInstruction::set_next(MFInstruction *instruction)
{
if (next_ != nullptr) {
next_->prev_.remove_first_occurrence_and_reorder(this);
}
if (instruction != nullptr) {
instruction->prev_.append(this);
}
next_ = instruction;
}
MFVariable &MFProcedure::new_variable(MFDataType data_type, std::string name)
{
MFVariable &variable = *allocator_.construct<MFVariable>().release();
variable.name_ = std::move(name);
variable.data_type_ = data_type;
variable.id_ = variables_.size();
variables_.append(&variable);
return variable;
}
MFCallInstruction &MFProcedure::new_call_instruction(const MultiFunction &fn)
{
MFCallInstruction &instruction = *allocator_.construct<MFCallInstruction>().release();
instruction.type_ = MFInstructionType::Call;
instruction.fn_ = &fn;
instruction.params_ = allocator_.allocate_array<MFVariable *>(fn.param_amount());
instruction.params_.fill(nullptr);
call_instructions_.append(&instruction);
return instruction;
}
MFBranchInstruction &MFProcedure::new_branch_instruction()
{
MFBranchInstruction &instruction = *allocator_.construct<MFBranchInstruction>().release();
instruction.type_ = MFInstructionType::Branch;
branch_instructions_.append(&instruction);
return instruction;
}
MFDestructInstruction &MFProcedure::new_destruct_instruction()
{
MFDestructInstruction &instruction = *allocator_.construct<MFDestructInstruction>().release();
instruction.type_ = MFInstructionType::Destruct;
destruct_instructions_.append(&instruction);
return instruction;
}
MFDummyInstruction &MFProcedure::new_dummy_instruction()
{
MFDummyInstruction &instruction = *allocator_.construct<MFDummyInstruction>().release();
instruction.type_ = MFInstructionType::Dummy;
dummy_instructions_.append(&instruction);
return instruction;
}
MFReturnInstruction &MFProcedure::new_return_instruction()
{
MFReturnInstruction &instruction = *allocator_.construct<MFReturnInstruction>().release();
instruction.type_ = MFInstructionType::Return;
return_instructions_.append(&instruction);
return instruction;
}
void MFProcedure::add_parameter(MFParamType::InterfaceType interface_type, MFVariable &variable)
{
params_.append({interface_type, &variable});
}
void MFProcedure::set_entry(MFInstruction &entry)
{
entry_ = &entry;
}
void MFProcedure::assert_valid() const
{
/**
* - Non parameter variables are destructed.
* - At every instruction, every variable is either initialized or uninitialized.
* - Input and mutable parameters of call instructions are initialized.
* - Condition of branch instruction is initialized.
* - Output parameters of call instructions are not initialized.
* - Input parameters are never destructed.
* - Mutable and output parameteres are initialized on every exit.
* - No aliasing issues in call instructions (can happen when variable is used more than once).
*/
}
MFProcedure::~MFProcedure()
{
for (MFCallInstruction *instruction : call_instructions_) {
instruction->~MFCallInstruction();
}
for (MFBranchInstruction *instruction : branch_instructions_) {
instruction->~MFBranchInstruction();
}
for (MFDestructInstruction *instruction : destruct_instructions_) {
instruction->~MFDestructInstruction();
}
for (MFDummyInstruction *instruction : dummy_instructions_) {
instruction->~MFDummyInstruction();
}
for (MFReturnInstruction *instruction : return_instructions_) {
instruction->~MFReturnInstruction();
}
for (MFVariable *variable : variables_) {
variable->~MFVariable();
}
}
bool MFProcedure::validate() const
{
if (entry_ == nullptr) {
return false;
}
if (!this->validate_all_instruction_pointers_set()) {
return false;
}
if (!this->validate_all_params_provided()) {
return false;
}
if (!this->validate_same_variables_in_one_call()) {
return false;
}
if (!this->validate_parameters()) {
return false;
}
if (!this->validate_initialization()) {
return false;
}
return true;
}
bool MFProcedure::validate_all_instruction_pointers_set() const
{
for (const MFCallInstruction *instruction : call_instructions_) {
if (instruction->next_ == nullptr) {
return false;
}
}
for (const MFDestructInstruction *instruction : destruct_instructions_) {
if (instruction->next_ == nullptr) {
return false;
}
}
for (const MFBranchInstruction *instruction : branch_instructions_) {
if (instruction->branch_true_ == nullptr) {
return false;
}
if (instruction->branch_false_ == nullptr) {
return false;
}
}
for (const MFDummyInstruction *instruction : dummy_instructions_) {
if (instruction->next_ == nullptr) {
return false;
}
}
return true;
}
bool MFProcedure::validate_all_params_provided() const
{
for (const MFCallInstruction *instruction : call_instructions_) {
for (const MFVariable *variable : instruction->params_) {
if (variable == nullptr) {
return false;
}
}
}
for (const MFBranchInstruction *instruction : branch_instructions_) {
if (instruction->condition_ == nullptr) {
return false;
}
}
for (const MFDestructInstruction *instruction : destruct_instructions_) {
if (instruction->variable_ == nullptr) {
return false;
}
}
return true;
}
bool MFProcedure::validate_same_variables_in_one_call() const
{
for (const MFCallInstruction *instruction : call_instructions_) {
const MultiFunction &fn = *instruction->fn_;
for (const int param_index : fn.param_indices()) {
const MFParamType param_type = fn.param_type(param_index);
const MFVariable *variable = instruction->params_[param_index];
for (const int other_param_index : fn.param_indices()) {
if (other_param_index == param_index) {
continue;
}
const MFVariable *other_variable = instruction->params_[other_param_index];
if (other_variable != variable) {
continue;
}
if (ELEM(param_type.interface_type(), MFParamType::Mutable, MFParamType::Output)) {
/* When a variable is used as mutable or output parameter, it can only be used once. */
return false;
}
const MFParamType other_param_type = fn.param_type(other_param_index);
/* A variable is allowed to be used as input more than once. */
if (other_param_type.interface_type() != MFParamType::Input) {
return false;
}
}
}
}
return true;
}
bool MFProcedure::validate_parameters() const
{
Set<const MFVariable *> variables;
for (const MFParameter &param : params_) {
/* One variable cannot be used as multiple parameters. */
if (!variables.add(param.variable)) {
return false;
}
}
return true;
}
bool MFProcedure::validate_initialization() const
{
/* TODO: Issue warning when it maybe wrongly initialized. */
for (const MFDestructInstruction *instruction : destruct_instructions_) {
const MFVariable &variable = *instruction->variable_;
const InitState state = this->find_initialization_state_before_instruction(*instruction,
variable);
if (!state.can_be_initialized) {
return false;
}
}
for (const MFBranchInstruction *instruction : branch_instructions_) {
const MFVariable &variable = *instruction->condition_;
const InitState state = this->find_initialization_state_before_instruction(*instruction,
variable);
if (!state.can_be_initialized) {
return false;
}
}
for (const MFCallInstruction *instruction : call_instructions_) {
const MultiFunction &fn = *instruction->fn_;
for (const int param_index : fn.param_indices()) {
const MFParamType param_type = fn.param_type(param_index);
const MFVariable &variable = *instruction->params_[param_index];
const InitState state = this->find_initialization_state_before_instruction(*instruction,
variable);
switch (param_type.interface_type()) {
case MFParamType::Input:
case MFParamType::Mutable: {
if (!state.can_be_initialized) {
return false;
}
break;
}
case MFParamType::Output: {
if (!state.can_be_uninitialized) {
return false;
}
break;
}
}
}
}
Set<const MFVariable *> variables_that_should_be_initialized_on_return;
for (const MFParameter &param : params_) {
if (ELEM(param.type, MFParamType::Mutable, MFParamType::Output)) {
variables_that_should_be_initialized_on_return.add_new(param.variable);
}
}
for (const MFReturnInstruction *instruction : return_instructions_) {
for (const MFVariable *variable : variables_) {
const InitState init_state = this->find_initialization_state_before_instruction(*instruction,
*variable);
if (variables_that_should_be_initialized_on_return.contains(variable)) {
if (!init_state.can_be_initialized) {
return false;
}
}
else {
if (!init_state.can_be_uninitialized) {
return false;
}
}
}
}
return true;
}
MFProcedure::InitState MFProcedure::find_initialization_state_before_instruction(
const MFInstruction &target_instruction, const MFVariable &target_variable) const
{
InitState state;
auto check_entry_instruction = [&]() {
bool caller_initialized_variable = false;
for (const MFParameter &param : params_) {
if (param.variable == &target_variable) {
if (ELEM(param.type, MFParamType::Input, MFParamType::Mutable)) {
caller_initialized_variable = true;
break;
}
}
}
if (caller_initialized_variable) {
state.can_be_initialized = true;
}
else {
state.can_be_uninitialized = true;
}
};
if (&target_instruction == entry_) {
check_entry_instruction();
}
Set<const MFInstruction *> checked_instructions;
Stack<const MFInstruction *> instructions_to_check;
instructions_to_check.push_multiple(target_instruction.prev_);
while (!instructions_to_check.is_empty()) {
const MFInstruction &instruction = *instructions_to_check.pop();
if (!checked_instructions.add(&instruction)) {
/* Skip if the instruction has been checked already. */
continue;
}
bool state_modified = false;
switch (instruction.type_) {
case MFInstructionType::Call: {
const MFCallInstruction &call_instruction = static_cast<const MFCallInstruction &>(
instruction);
const MultiFunction &fn = *call_instruction.fn_;
for (const int param_index : fn.param_indices()) {
if (call_instruction.params_[param_index] == &target_variable) {
const MFParamType param_type = fn.param_type(param_index);
if (param_type.interface_type() == MFParamType::Output) {
state.can_be_initialized = true;
state_modified = true;
break;
}
}
}
break;
}
case MFInstructionType::Destruct: {
const MFDestructInstruction &destruct_instruction =
static_cast<const MFDestructInstruction &>(instruction);
if (destruct_instruction.variable_ == &target_variable) {
state.can_be_uninitialized = true;
state_modified = true;
}
break;
}
case MFInstructionType::Branch:
case MFInstructionType::Dummy:
case MFInstructionType::Return: {
/* These instruction types don't change the initialization state of variables. */
break;
}
}
if (!state_modified) {
if (&instruction == entry_) {
check_entry_instruction();
}
instructions_to_check.push_multiple(instruction.prev_);
}
}
return state;
}
static bool has_to_be_block_begin(const MFProcedure &procedure, const MFInstruction &instruction)
{
if (procedure.entry() == &instruction) {
return true;
}
if (instruction.prev().size() != 1) {
return true;
}
if (instruction.prev()[0]->type() == MFInstructionType::Branch) {
return true;
}
return false;
}
static const MFInstruction &get_first_instruction_in_block(const MFProcedure &procedure,
const MFInstruction &representative)
{
const MFInstruction *current = &representative;
while (!has_to_be_block_begin(procedure, *current)) {
current = current->prev()[0];
if (current == &representative) {
/* There is a loop without entry or exit, just break it up here. */
break;
}
}
return *current;
}
static const MFInstruction *get_next_instruction_in_block(const MFProcedure &procedure,
const MFInstruction &instruction,
const MFInstruction &block_begin)
{
const MFInstruction *next = nullptr;
switch (instruction.type()) {
case MFInstructionType::Call: {
next = static_cast<const MFCallInstruction &>(instruction).next();
break;
}
case MFInstructionType::Destruct: {
next = static_cast<const MFDestructInstruction &>(instruction).next();
break;
}
case MFInstructionType::Dummy: {
next = static_cast<const MFDummyInstruction &>(instruction).next();
break;
}
case MFInstructionType::Return:
case MFInstructionType::Branch: {
break;
}
}
if (next == nullptr) {
return nullptr;
}
if (next == &block_begin) {
return nullptr;
}
if (has_to_be_block_begin(procedure, *next)) {
return nullptr;
}
return next;
}
static Vector<const MFInstruction *> get_instructions_in_block(const MFProcedure &procedure,
const MFInstruction &representative)
{
Vector<const MFInstruction *> instructions;
const MFInstruction &begin = get_first_instruction_in_block(procedure, representative);
for (const MFInstruction *current = &begin; current != nullptr;
current = get_next_instruction_in_block(procedure, *current, begin)) {
instructions.append(current);
}
return instructions;
}
static void variable_to_string(const MFVariable *variable, std::stringstream &ss)
{
if (variable == nullptr) {
ss << "<none>";
}
else {
ss << "$" << variable->id();
if (!variable->name().is_empty()) {
ss << "(" << variable->name() << ")";
}
}
}
static void instruction_to_string(const MFCallInstruction &instruction, std::stringstream &ss)
{
const MultiFunction &fn = instruction.fn();
ss << fn.name() << " - ";
for (const int param_index : fn.param_indices()) {
const MFParamType param_type = fn.param_type(param_index);
const MFVariable *variable = instruction.params()[param_index];
switch (param_type.interface_type()) {
case MFParamType::Input: {
ss << "in";
break;
}
case MFParamType::Mutable: {
ss << "mut";
break;
}
case MFParamType::Output: {
ss << "out";
break;
}
}
ss << " ";
variable_to_string(variable, ss);
if (param_index < fn.param_amount() - 1) {
ss << ", ";
}
}
}
static void instruction_to_string(const MFDestructInstruction &instruction, std::stringstream &ss)
{
ss << "Destruct ";
variable_to_string(instruction.variable(), ss);
}
static void instruction_to_string(const MFDummyInstruction &UNUSED(instruction),
std::stringstream &ss)
{
ss << "Dummy";
}
static void instruction_to_string(const MFReturnInstruction &UNUSED(instruction),
std::stringstream &ss)
{
ss << "Return";
}
static void instruction_to_string(const MFBranchInstruction &instruction, std::stringstream &ss)
{
ss << "Branch on ";
variable_to_string(instruction.condition(), ss);
}
std::string MFProcedure::to_dot() const
{
Vector<const MFInstruction *> all_instructions;
all_instructions.extend(call_instructions_.begin(), call_instructions_.end());
all_instructions.extend(branch_instructions_.begin(), branch_instructions_.end());
all_instructions.extend(destruct_instructions_.begin(), destruct_instructions_.end());
all_instructions.extend(dummy_instructions_.begin(), dummy_instructions_.end());
all_instructions.extend(return_instructions_.begin(), return_instructions_.end());
Set<const MFInstruction *> handled_instructions;
dot::DirectedGraph digraph;
Map<const MFInstruction *, dot::Node *> dot_nodes_by_begin;
Map<const MFInstruction *, dot::Node *> dot_nodes_by_end;
for (const MFInstruction *representative : all_instructions) {
if (handled_instructions.contains(representative)) {
continue;
}
Vector<const MFInstruction *> block_instructions = get_instructions_in_block(*this,
*representative);
std::stringstream ss;
for (const MFInstruction *current : block_instructions) {
handled_instructions.add_new(current);
switch (current->type()) {
case MFInstructionType::Call: {
instruction_to_string(*static_cast<const MFCallInstruction *>(current), ss);
break;
}
case MFInstructionType::Destruct: {
instruction_to_string(*static_cast<const MFDestructInstruction *>(current), ss);
break;
}
case MFInstructionType::Dummy: {
instruction_to_string(*static_cast<const MFDummyInstruction *>(current), ss);
break;
}
case MFInstructionType::Return: {
instruction_to_string(*static_cast<const MFReturnInstruction *>(current), ss);
break;
}
case MFInstructionType::Branch: {
instruction_to_string(*static_cast<const MFBranchInstruction *>(current), ss);
break;
}
}
ss << "\\l";
}
dot::Node &dot_node = digraph.new_node(ss.str());
dot_node.set_shape(dot::Attr_shape::Rectangle);
dot_nodes_by_begin.add_new(block_instructions.first(), &dot_node);
dot_nodes_by_end.add_new(block_instructions.last(), &dot_node);
}
auto create_edge = [&](dot::Node &from_node,
const MFInstruction *to_instruction) -> dot::DirectedEdge & {
if (to_instruction == nullptr) {
dot::Node &to_node = digraph.new_node("missing");
to_node.set_shape(dot::Attr_shape::Diamond);
return digraph.new_edge(from_node, to_node);
}
dot::Node &to_node = *dot_nodes_by_begin.lookup(to_instruction);
return digraph.new_edge(from_node, to_node);
};
for (auto item : dot_nodes_by_end.items()) {
const MFInstruction &from_instruction = *item.key;
dot::Node &from_node = *item.value;
switch (from_instruction.type()) {
case MFInstructionType::Call: {
const MFInstruction *to_instruction =
static_cast<const MFCallInstruction &>(from_instruction).next();
create_edge(from_node, to_instruction);
break;
}
case MFInstructionType::Destruct: {
const MFInstruction *to_instruction =
static_cast<const MFDestructInstruction &>(from_instruction).next();
create_edge(from_node, to_instruction);
break;
}
case MFInstructionType::Dummy: {
const MFInstruction *to_instruction =
static_cast<const MFDummyInstruction &>(from_instruction).next();
create_edge(from_node, to_instruction);
break;
}
case MFInstructionType::Return: {
break;
}
case MFInstructionType::Branch: {
const MFBranchInstruction &branch_instruction = static_cast<const MFBranchInstruction &>(
from_instruction);
const MFInstruction *to_true_instruction = branch_instruction.branch_true();
const MFInstruction *to_false_instruction = branch_instruction.branch_false();
create_edge(from_node, to_true_instruction).attributes.set("color", "#118811");
create_edge(from_node, to_false_instruction).attributes.set("color", "#881111");
break;
}
}
}
dot::Node &entry_node = digraph.new_node("Entry");
entry_node.set_shape(dot::Attr_shape::Circle);
create_edge(entry_node, entry_);
return digraph.to_dot_string();
}
} // namespace blender::fn

View File

@@ -0,0 +1,174 @@
/*
* 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.
*/
#include "FN_multi_function_procedure_builder.hh"
namespace blender::fn {
void MFInstructionCursor::insert(MFProcedure &procedure, MFInstruction *new_instruction)
{
if (instruction_ == nullptr) {
if (is_entry_) {
procedure.set_entry(*new_instruction);
}
else {
/* The cursors points at nothing, nothing to do. */
}
}
else {
switch (instruction_->type()) {
case MFInstructionType::Call: {
static_cast<MFCallInstruction *>(instruction_)->set_next(new_instruction);
break;
}
case MFInstructionType::Branch: {
MFBranchInstruction &branch_instruction = *static_cast<MFBranchInstruction *>(
instruction_);
if (branch_output_) {
branch_instruction.set_branch_true(new_instruction);
}
else {
branch_instruction.set_branch_false(new_instruction);
}
break;
}
case MFInstructionType::Destruct: {
static_cast<MFDestructInstruction *>(instruction_)->set_next(new_instruction);
break;
}
case MFInstructionType::Dummy: {
static_cast<MFDummyInstruction *>(instruction_)->set_next(new_instruction);
break;
}
case MFInstructionType::Return: {
/* It shouldn't be possible to build a cursor that points to a return instruction. */
BLI_assert_unreachable();
break;
}
}
}
}
void MFProcedureBuilder::add_destruct(MFVariable &variable)
{
MFDestructInstruction &instruction = procedure_->new_destruct_instruction();
instruction.set_variable(&variable);
this->link_to_cursors(&instruction);
cursors_ = {MFInstructionCursor{instruction}};
}
void MFProcedureBuilder::add_destruct(Span<MFVariable *> variables)
{
for (MFVariable *variable : variables) {
this->add_destruct(*variable);
}
}
MFReturnInstruction &MFProcedureBuilder::add_return()
{
MFReturnInstruction &instruction = procedure_->new_return_instruction();
this->link_to_cursors(&instruction);
cursors_ = {};
return instruction;
}
MFCallInstruction &MFProcedureBuilder::add_call_with_no_variables(const MultiFunction &fn)
{
MFCallInstruction &instruction = procedure_->new_call_instruction(fn);
this->link_to_cursors(&instruction);
cursors_ = {MFInstructionCursor{instruction}};
return instruction;
}
MFCallInstruction &MFProcedureBuilder::add_call_with_all_variables(
const MultiFunction &fn, Span<MFVariable *> param_variables)
{
MFCallInstruction &instruction = this->add_call_with_no_variables(fn);
instruction.set_params(param_variables);
return instruction;
}
Vector<MFVariable *> MFProcedureBuilder::add_call(const MultiFunction &fn,
Span<MFVariable *> input_and_mutable_variables)
{
Vector<MFVariable *> output_variables;
MFCallInstruction &instruction = this->add_call_with_no_variables(fn);
for (const int param_index : fn.param_indices()) {
const MFParamType param_type = fn.param_type(param_index);
switch (param_type.interface_type()) {
case MFParamType::Input:
case MFParamType::Mutable: {
MFVariable *variable = input_and_mutable_variables.first();
instruction.set_param_variable(param_index, variable);
input_and_mutable_variables = input_and_mutable_variables.drop_front(1);
break;
}
case MFParamType::Output: {
MFVariable &variable = procedure_->new_variable(param_type.data_type());
instruction.set_param_variable(param_index, &variable);
output_variables.append(&variable);
break;
}
}
}
/* All passed in variables should have been dropped in the loop above. */
BLI_assert(input_and_mutable_variables.is_empty());
return output_variables;
}
MFProcedureBuilder::Branch MFProcedureBuilder::add_branch(MFVariable &condition)
{
MFBranchInstruction &instruction = procedure_->new_branch_instruction();
instruction.set_condition(&condition);
this->link_to_cursors(&instruction);
/* Clear cursors because this builder ends here. */
cursors_.clear();
Branch branch{*procedure_, *procedure_};
branch.branch_true.set_cursor(MFInstructionCursor{instruction, true});
branch.branch_false.set_cursor(MFInstructionCursor{instruction, false});
return branch;
}
MFProcedureBuilder::Loop MFProcedureBuilder::add_loop()
{
MFDummyInstruction &loop_begin = procedure_->new_dummy_instruction();
MFDummyInstruction &loop_end = procedure_->new_dummy_instruction();
this->link_to_cursors(&loop_begin);
cursors_ = {MFInstructionCursor{loop_begin}};
Loop loop;
loop.begin = &loop_begin;
loop.end = &loop_end;
return loop;
}
void MFProcedureBuilder::add_loop_continue(Loop &loop)
{
this->link_to_cursors(loop.begin);
/* Clear cursors because this builder ends here. */
cursors_.clear();
}
void MFProcedureBuilder::add_loop_break(Loop &loop)
{
this->link_to_cursors(loop.end);
/* Clear cursors because this builder ends here. */
cursors_.clear();
}
} // namespace blender::fn

File diff suppressed because it is too large Load Diff

View File

@@ -1,280 +0,0 @@
/* Apache License, Version 2.0 */
#include "testing/testing.h"
#include "FN_multi_function_builder.hh"
#include "FN_multi_function_network.hh"
#include "FN_multi_function_network_evaluation.hh"
namespace blender::fn::tests {
namespace {
TEST(multi_function_network, Test1)
{
CustomMF_SI_SO<int, int> add_10_fn("add 10", [](int value) { return value + 10; });
CustomMF_SI_SI_SO<int, int, int> multiply_fn("multiply", [](int a, int b) { return a * b; });
MFNetwork network;
MFNode &node1 = network.add_function(add_10_fn);
MFNode &node2 = network.add_function(multiply_fn);
MFOutputSocket &input_socket = network.add_input("Input", MFDataType::ForSingle<int>());
MFInputSocket &output_socket = network.add_output("Output", MFDataType::ForSingle<int>());
network.add_link(node1.output(0), node2.input(0));
network.add_link(node1.output(0), node2.input(1));
network.add_link(node2.output(0), output_socket);
network.add_link(input_socket, node1.input(0));
MFNetworkEvaluator network_fn{{&input_socket}, {&output_socket}};
{
Array<int> values = {4, 6, 1, 2, 0};
Array<int> results(values.size(), 0);
MFParamsBuilder params(network_fn, values.size());
params.add_readonly_single_input(values.as_span());
params.add_uninitialized_single_output(results.as_mutable_span());
MFContextBuilder context;
network_fn.call({0, 2, 3, 4}, params, context);
EXPECT_EQ(results[0], 14 * 14);
EXPECT_EQ(results[1], 0);
EXPECT_EQ(results[2], 11 * 11);
EXPECT_EQ(results[3], 12 * 12);
EXPECT_EQ(results[4], 10 * 10);
}
{
int value = 3;
Array<int> results(5, 0);
MFParamsBuilder params(network_fn, results.size());
params.add_readonly_single_input(&value);
params.add_uninitialized_single_output(results.as_mutable_span());
MFContextBuilder context;
network_fn.call({1, 2, 4}, params, context);
EXPECT_EQ(results[0], 0);
EXPECT_EQ(results[1], 13 * 13);
EXPECT_EQ(results[2], 13 * 13);
EXPECT_EQ(results[3], 0);
EXPECT_EQ(results[4], 13 * 13);
}
}
class ConcatVectorsFunction : public MultiFunction {
public:
ConcatVectorsFunction()
{
static MFSignature signature = create_signature();
this->set_signature(&signature);
}
static MFSignature create_signature()
{
MFSignatureBuilder signature{"Concat Vectors"};
signature.vector_mutable<int>("A");
signature.vector_input<int>("B");
return signature.build();
}
void call(IndexMask mask, MFParams params, MFContext UNUSED(context)) const override
{
GVectorArray &a = params.vector_mutable(0);
const GVVectorArray &b = params.readonly_vector_input(1);
a.extend(mask, b);
}
};
class AppendFunction : public MultiFunction {
public:
AppendFunction()
{
static MFSignature signature = create_signature();
this->set_signature(&signature);
}
static MFSignature create_signature()
{
MFSignatureBuilder signature{"Append"};
signature.vector_mutable<int>("Vector");
signature.single_input<int>("Value");
return signature.build();
}
void call(IndexMask mask, MFParams params, MFContext UNUSED(context)) const override
{
GVectorArray_TypedMutableRef<int> vectors = params.vector_mutable<int>(0);
const VArray<int> &values = params.readonly_single_input<int>(1);
for (int64_t i : mask) {
vectors.append(i, values[i]);
}
}
};
class SumVectorFunction : public MultiFunction {
public:
SumVectorFunction()
{
static MFSignature signature = create_signature();
this->set_signature(&signature);
}
static MFSignature create_signature()
{
MFSignatureBuilder signature{"Sum Vectors"};
signature.vector_input<int>("Vector");
signature.single_output<int>("Sum");
return signature.build();
}
void call(IndexMask mask, MFParams params, MFContext UNUSED(context)) const override
{
const VVectorArray<int> &vectors = params.readonly_vector_input<int>(0);
MutableSpan<int> sums = params.uninitialized_single_output<int>(1);
for (int64_t i : mask) {
int sum = 0;
for (int j : IndexRange(vectors.get_vector_size(i))) {
sum += vectors.get_vector_element(i, j);
}
sums[i] = sum;
}
}
};
class CreateRangeFunction : public MultiFunction {
public:
CreateRangeFunction()
{
static MFSignature signature = create_signature();
this->set_signature(&signature);
}
static MFSignature create_signature()
{
MFSignatureBuilder signature{"Create Range"};
signature.single_input<int>("Size");
signature.vector_output<int>("Range");
return signature.build();
}
void call(IndexMask mask, MFParams params, MFContext UNUSED(context)) const override
{
const VArray<int> &sizes = params.readonly_single_input<int>(0, "Size");
GVectorArray_TypedMutableRef<int> ranges = params.vector_output<int>(1, "Range");
for (int64_t i : mask) {
int size = sizes[i];
for (int j : IndexRange(size)) {
ranges.append(i, j);
}
}
}
};
TEST(multi_function_network, Test2)
{
CustomMF_SI_SO<int, int> add_3_fn("add 3", [](int value) { return value + 3; });
ConcatVectorsFunction concat_vectors_fn;
AppendFunction append_fn;
SumVectorFunction sum_fn;
CreateRangeFunction create_range_fn;
MFNetwork network;
MFOutputSocket &input1 = network.add_input("Input 1", MFDataType::ForVector<int>());
MFOutputSocket &input2 = network.add_input("Input 2", MFDataType::ForSingle<int>());
MFInputSocket &output1 = network.add_output("Output 1", MFDataType::ForVector<int>());
MFInputSocket &output2 = network.add_output("Output 2", MFDataType::ForSingle<int>());
MFNode &node1 = network.add_function(add_3_fn);
MFNode &node2 = network.add_function(create_range_fn);
MFNode &node3 = network.add_function(concat_vectors_fn);
MFNode &node4 = network.add_function(sum_fn);
MFNode &node5 = network.add_function(append_fn);
MFNode &node6 = network.add_function(sum_fn);
network.add_link(input2, node1.input(0));
network.add_link(node1.output(0), node2.input(0));
network.add_link(node2.output(0), node3.input(1));
network.add_link(input1, node3.input(0));
network.add_link(input1, node4.input(0));
network.add_link(node4.output(0), node5.input(1));
network.add_link(node3.output(0), node5.input(0));
network.add_link(node5.output(0), node6.input(0));
network.add_link(node3.output(0), output1);
network.add_link(node6.output(0), output2);
// std::cout << network.to_dot() << "\n\n";
MFNetworkEvaluator network_fn{{&input1, &input2}, {&output1, &output2}};
{
Array<int> input_value_1 = {3, 6};
int input_value_2 = 4;
GVectorArray output_value_1(CPPType::get<int32_t>(), 5);
Array<int> output_value_2(5, -1);
MFParamsBuilder params(network_fn, 5);
GVVectorArray_For_SingleGSpan inputs_1{input_value_1.as_span(), 5};
params.add_readonly_vector_input(inputs_1);
params.add_readonly_single_input(&input_value_2);
params.add_vector_output(output_value_1);
params.add_uninitialized_single_output(output_value_2.as_mutable_span());
MFContextBuilder context;
network_fn.call({1, 2, 4}, params, context);
EXPECT_EQ(output_value_1[0].size(), 0);
EXPECT_EQ(output_value_1[1].size(), 9);
EXPECT_EQ(output_value_1[2].size(), 9);
EXPECT_EQ(output_value_1[3].size(), 0);
EXPECT_EQ(output_value_1[4].size(), 9);
EXPECT_EQ(output_value_2[0], -1);
EXPECT_EQ(output_value_2[1], 39);
EXPECT_EQ(output_value_2[2], 39);
EXPECT_EQ(output_value_2[3], -1);
EXPECT_EQ(output_value_2[4], 39);
}
{
GVectorArray input_value_1(CPPType::get<int32_t>(), 3);
GVectorArray_TypedMutableRef<int> input_value_1_ref{input_value_1};
input_value_1_ref.extend(0, {3, 4, 5});
input_value_1_ref.extend(1, {1, 2});
Array<int> input_value_2 = {4, 2, 3};
GVectorArray output_value_1(CPPType::get<int32_t>(), 3);
Array<int> output_value_2(3, -1);
MFParamsBuilder params(network_fn, 3);
params.add_readonly_vector_input(input_value_1);
params.add_readonly_single_input(input_value_2.as_span());
params.add_vector_output(output_value_1);
params.add_uninitialized_single_output(output_value_2.as_mutable_span());
MFContextBuilder context;
network_fn.call({0, 1, 2}, params, context);
EXPECT_EQ(output_value_1[0].size(), 10);
EXPECT_EQ(output_value_1[1].size(), 7);
EXPECT_EQ(output_value_1[2].size(), 6);
EXPECT_EQ(output_value_2[0], 45);
EXPECT_EQ(output_value_2[1], 16);
EXPECT_EQ(output_value_2[2], 15);
}
}
} // namespace
} // namespace blender::fn::tests

View File

@@ -0,0 +1,344 @@
/* Apache License, Version 2.0 */
#include "testing/testing.h"
#include "FN_multi_function_builder.hh"
#include "FN_multi_function_procedure_builder.hh"
#include "FN_multi_function_procedure_executor.hh"
#include "FN_multi_function_test_common.hh"
namespace blender::fn::tests {
TEST(multi_function_procedure, SimpleTest)
{
/**
* procedure(int var1, int var2, int *var4) {
* int var3 = var1 + var2;
* var4 = var2 + var3;
* var4 += 10;
* }
*/
CustomMF_SI_SI_SO<int, int, int> add_fn{"add", [](int a, int b) { return a + b; }};
CustomMF_SM<int> add_10_fn{"add_10", [](int &a) { a += 10; }};
MFProcedure procedure;
MFProcedureBuilder builder{procedure};
MFVariable *var1 = &builder.add_single_input_parameter<int>();
MFVariable *var2 = &builder.add_single_input_parameter<int>();
auto [var3] = builder.add_call<1>(add_fn, {var1, var2});
auto [var4] = builder.add_call<1>(add_fn, {var2, var3});
builder.add_call(add_10_fn, {var4});
builder.add_destruct({var1, var2, var3});
builder.add_return();
builder.add_output_parameter(*var4);
EXPECT_TRUE(procedure.validate());
MFProcedureExecutor executor{"My Procedure", procedure};
MFParamsBuilder params{executor, 3};
MFContextBuilder context;
Array<int> input_array = {1, 2, 3};
params.add_readonly_single_input(input_array.as_span());
params.add_readonly_single_input_value(3);
Array<int> output_array(3);
params.add_uninitialized_single_output(output_array.as_mutable_span());
executor.call(IndexRange(3), params, context);
EXPECT_EQ(output_array[0], 17);
EXPECT_EQ(output_array[1], 18);
EXPECT_EQ(output_array[2], 19);
}
TEST(multi_function_procedure, BranchTest)
{
/**
* procedure(int &var1, bool var2) {
* if (var2) {
* var1 += 100;
* }
* else {
* var1 += 10;
* }
* var1 += 10;
* }
*/
CustomMF_SM<int> add_10_fn{"add_10", [](int &a) { a += 10; }};
CustomMF_SM<int> add_100_fn{"add_100", [](int &a) { a += 100; }};
MFProcedure procedure;
MFProcedureBuilder builder{procedure};
MFVariable *var1 = &builder.add_single_mutable_parameter<int>();
MFVariable *var2 = &builder.add_single_input_parameter<bool>();
MFProcedureBuilder::Branch branch = builder.add_branch(*var2);
branch.branch_false.add_call(add_10_fn, {var1});
branch.branch_true.add_call(add_100_fn, {var1});
builder.set_cursor_after_branch(branch);
builder.add_call(add_10_fn, {var1});
builder.add_destruct({var2});
builder.add_return();
EXPECT_TRUE(procedure.validate());
MFProcedureExecutor procedure_fn{"Condition Test", procedure};
MFParamsBuilder params(procedure_fn, 5);
Array<int> values_a = {1, 5, 3, 6, 2};
Array<bool> values_cond = {true, false, true, true, false};
params.add_single_mutable(values_a.as_mutable_span());
params.add_readonly_single_input(values_cond.as_span());
MFContextBuilder context;
procedure_fn.call({1, 2, 3, 4}, params, context);
EXPECT_EQ(values_a[0], 1);
EXPECT_EQ(values_a[1], 25);
EXPECT_EQ(values_a[2], 113);
EXPECT_EQ(values_a[3], 116);
EXPECT_EQ(values_a[4], 22);
}
TEST(multi_function_procedure, EvaluateOne)
{
/**
* procedure(int var1, int var2) {
* var2 = var1 + 10;
* }
*/
int tot_evaluations = 0;
CustomMF_SI_SO<int, int> add_10_fn{"add_10", [&](int a) {
tot_evaluations++;
return a + 10;
}};
MFProcedure procedure;
MFProcedureBuilder builder{procedure};
MFVariable *var1 = &builder.add_single_input_parameter<int>();
auto [var2] = builder.add_call<1>(add_10_fn, {var1});
builder.add_destruct(*var1);
builder.add_return();
builder.add_output_parameter(*var2);
MFProcedureExecutor procedure_fn{"Evaluate One", procedure};
MFParamsBuilder params{procedure_fn, 5};
Array<int> values_out = {1, 2, 3, 4, 5};
params.add_readonly_single_input_value(1);
params.add_uninitialized_single_output(values_out.as_mutable_span());
MFContextBuilder context;
procedure_fn.call({0, 1, 3, 4}, params, context);
EXPECT_EQ(values_out[0], 11);
EXPECT_EQ(values_out[1], 11);
EXPECT_EQ(values_out[2], 3);
EXPECT_EQ(values_out[3], 11);
EXPECT_EQ(values_out[4], 11);
/* We expect only one evaluation, because the input is constant. */
EXPECT_EQ(tot_evaluations, 1);
}
TEST(multi_function_procedure, SimpleLoop)
{
/**
* procedure(int count, int *out) {
* out = 1;
* int index = 0'
* loop {
* if (index >= count) {
* break;
* }
* out *= 2;
* index += 1;
* }
* out += 1000;
* }
*/
CustomMF_Constant<int> const_1_fn{1};
CustomMF_Constant<int> const_0_fn{0};
CustomMF_SI_SI_SO<int, int, bool> greater_or_equal_fn{"greater or equal",
[](int a, int b) { return a >= b; }};
CustomMF_SM<int> double_fn{"double", [](int &a) { a *= 2; }};
CustomMF_SM<int> add_1000_fn{"add 1000", [](int &a) { a += 1000; }};
CustomMF_SM<int> add_1_fn{"add 1", [](int &a) { a += 1; }};
MFProcedure procedure;
MFProcedureBuilder builder{procedure};
MFVariable *var_count = &builder.add_single_input_parameter<int>("count");
auto [var_out] = builder.add_call<1>(const_1_fn);
var_out->set_name("out");
auto [var_index] = builder.add_call<1>(const_0_fn);
var_index->set_name("index");
MFProcedureBuilder::Loop loop = builder.add_loop();
auto [var_condition] = builder.add_call<1>(greater_or_equal_fn, {var_index, var_count});
var_condition->set_name("condition");
MFProcedureBuilder::Branch branch = builder.add_branch(*var_condition);
branch.branch_true.add_destruct(*var_condition);
branch.branch_true.add_loop_break(loop);
branch.branch_false.add_destruct(*var_condition);
builder.set_cursor_after_branch(branch);
builder.add_call(double_fn, {var_out});
builder.add_call(add_1_fn, {var_index});
builder.add_loop_continue(loop);
builder.set_cursor_after_loop(loop);
builder.add_call(add_1000_fn, {var_out});
builder.add_destruct({var_count, var_index});
builder.add_return();
builder.add_output_parameter(*var_out);
EXPECT_TRUE(procedure.validate());
MFProcedureExecutor procedure_fn{"Simple Loop", procedure};
MFParamsBuilder params{procedure_fn, 5};
Array<int> counts = {4, 3, 7, 6, 4};
Array<int> results(5, -1);
params.add_readonly_single_input(counts.as_span());
params.add_uninitialized_single_output(results.as_mutable_span());
MFContextBuilder context;
procedure_fn.call({0, 1, 3, 4}, params, context);
EXPECT_EQ(results[0], 1016);
EXPECT_EQ(results[1], 1008);
EXPECT_EQ(results[2], -1);
EXPECT_EQ(results[3], 1064);
EXPECT_EQ(results[4], 1016);
}
TEST(multi_function_procedure, Vectors)
{
/**
* procedure(vector<int> v1, vector<int> &v2, vector<int> *v3) {
* v1.extend(v2);
* int constant = 5;
* v2.append(constant);
* v2.extend(v1);
* int len = sum(v2);
* v3 = range(len);
* }
*/
CreateRangeFunction create_range_fn;
ConcatVectorsFunction extend_fn;
GenericAppendFunction append_fn{CPPType::get<int>()};
SumVectorFunction sum_elements_fn;
CustomMF_Constant<int> constant_5_fn{5};
MFProcedure procedure;
MFProcedureBuilder builder{procedure};
MFVariable *var_v1 = &builder.add_input_parameter(MFDataType::ForVector<int>());
MFVariable *var_v2 = &builder.add_parameter(MFParamType::ForMutableVector(CPPType::get<int>()));
builder.add_call(extend_fn, {var_v1, var_v2});
auto [var_constant] = builder.add_call<1>(constant_5_fn);
builder.add_call(append_fn, {var_v2, var_constant});
builder.add_destruct(*var_constant);
builder.add_call(extend_fn, {var_v2, var_v1});
auto [var_len] = builder.add_call<1>(sum_elements_fn, {var_v2});
auto [var_v3] = builder.add_call<1>(create_range_fn, {var_len});
builder.add_destruct({var_v1, var_len});
builder.add_return();
builder.add_output_parameter(*var_v3);
EXPECT_TRUE(procedure.validate());
MFProcedureExecutor procedure_fn{"Vectors", procedure};
MFParamsBuilder params{procedure_fn, 5};
Array<int> v1 = {5, 2, 3};
GVectorArray v2{CPPType::get<int>(), 5};
GVectorArray v3{CPPType::get<int>(), 5};
int value_10 = 10;
v2.append(0, &value_10);
v2.append(4, &value_10);
params.add_readonly_vector_input(v1.as_span());
params.add_vector_mutable(v2);
params.add_vector_output(v3);
MFContextBuilder context;
procedure_fn.call({0, 1, 3, 4}, params, context);
EXPECT_EQ(v2[0].size(), 6);
EXPECT_EQ(v2[1].size(), 4);
EXPECT_EQ(v2[2].size(), 0);
EXPECT_EQ(v2[3].size(), 4);
EXPECT_EQ(v2[4].size(), 6);
EXPECT_EQ(v3[0].size(), 35);
EXPECT_EQ(v3[1].size(), 15);
EXPECT_EQ(v3[2].size(), 0);
EXPECT_EQ(v3[3].size(), 15);
EXPECT_EQ(v3[4].size(), 35);
}
TEST(multi_function_procedure, BufferReuse)
{
/**
* procedure(int a, int *out) {
* int b = a + 10;
* int c = c + 10;
* int d = d + 10;
* int e = d + 10;
* out = e + 10;
* }
*/
CustomMF_SI_SO<int, int> add_10_fn{"add 10", [](int a) { return a + 10; }};
MFProcedure procedure;
MFProcedureBuilder builder{procedure};
MFVariable *var_a = &builder.add_single_input_parameter<int>();
auto [var_b] = builder.add_call<1>(add_10_fn, {var_a});
builder.add_destruct(*var_a);
auto [var_c] = builder.add_call<1>(add_10_fn, {var_b});
builder.add_destruct(*var_b);
auto [var_d] = builder.add_call<1>(add_10_fn, {var_c});
builder.add_destruct(*var_c);
auto [var_e] = builder.add_call<1>(add_10_fn, {var_d});
builder.add_destruct(*var_d);
auto [var_out] = builder.add_call<1>(add_10_fn, {var_e});
builder.add_destruct(*var_e);
builder.add_return();
builder.add_output_parameter(*var_out);
EXPECT_TRUE(procedure.validate());
MFProcedureExecutor procedure_fn{"Buffer Reuse", procedure};
Array<int> inputs = {4, 1, 6, 2, 3};
Array<int> results(5, -1);
MFParamsBuilder params{procedure_fn, 5};
params.add_readonly_single_input(inputs.as_span());
params.add_uninitialized_single_output(results.as_mutable_span());
MFContextBuilder context;
procedure_fn.call({0, 2, 3, 4}, params, context);
EXPECT_EQ(results[0], 54);
EXPECT_EQ(results[1], -1);
EXPECT_EQ(results[2], 56);
EXPECT_EQ(results[3], 52);
EXPECT_EQ(results[4], 53);
}
} // namespace blender::fn::tests

View File

@@ -4,6 +4,7 @@
#include "FN_multi_function.hh"
#include "FN_multi_function_builder.hh"
#include "FN_multi_function_test_common.hh"
namespace blender::fn::tests {
namespace {
@@ -59,33 +60,6 @@ TEST(multi_function, AddFunction)
EXPECT_EQ(output[2], 36);
}
class AddPrefixFunction : public MultiFunction {
public:
AddPrefixFunction()
{
static MFSignature signature = create_signature();
this->set_signature(&signature);
}
static MFSignature create_signature()
{
MFSignatureBuilder signature{"Add Prefix"};
signature.single_input<std::string>("Prefix");
signature.single_mutable<std::string>("Strings");
return signature.build();
}
void call(IndexMask mask, MFParams params, MFContext UNUSED(context)) const override
{
const VArray<std::string> &prefixes = params.readonly_single_input<std::string>(0, "Prefix");
MutableSpan<std::string> strings = params.single_mutable<std::string>(1, "Strings");
for (int64_t i : mask) {
strings[i] = prefixes[i] + strings[i];
}
}
};
TEST(multi_function, AddPrefixFunction)
{
AddPrefixFunction fn;
@@ -113,43 +87,13 @@ TEST(multi_function, AddPrefixFunction)
EXPECT_EQ(strings[3], "ABAnother much longer string to trigger an allocation");
}
class CreateRangeFunction : public MultiFunction {
public:
CreateRangeFunction()
{
static MFSignature signature = create_signature();
this->set_signature(&signature);
}
static MFSignature create_signature()
{
MFSignatureBuilder signature{"Create Range"};
signature.single_input<uint>("Size");
signature.vector_output<uint>("Range");
return signature.build();
}
void call(IndexMask mask, MFParams params, MFContext UNUSED(context)) const override
{
const VArray<uint> &sizes = params.readonly_single_input<uint>(0, "Size");
GVectorArray &ranges = params.vector_output(1, "Range");
for (int64_t i : mask) {
uint size = sizes[i];
for (uint j : IndexRange(size)) {
ranges.append(i, &j);
}
}
}
};
TEST(multi_function, CreateRangeFunction)
{
CreateRangeFunction fn;
GVectorArray ranges(CPPType::get<uint>(), 5);
GVectorArray_TypedMutableRef<uint> ranges_ref{ranges};
Array<uint> sizes = {3, 0, 6, 1, 4};
GVectorArray ranges(CPPType::get<int>(), 5);
GVectorArray_TypedMutableRef<int> ranges_ref{ranges};
Array<int> sizes = {3, 0, 6, 1, 4};
MFParamsBuilder params(fn, ranges.size());
params.add_readonly_single_input(sizes.as_span());
@@ -172,34 +116,6 @@ TEST(multi_function, CreateRangeFunction)
EXPECT_EQ(ranges_ref[2][1], 1);
}
class GenericAppendFunction : public MultiFunction {
private:
MFSignature signature_;
public:
GenericAppendFunction(const CPPType &type)
{
MFSignatureBuilder signature{"Append"};
signature.vector_mutable("Vector", type);
signature.single_input("Value", type);
signature_ = signature.build();
this->set_signature(&signature_);
}
void call(IndexMask mask, MFParams params, MFContext UNUSED(context)) const override
{
GVectorArray &vectors = params.vector_mutable(0, "Vector");
const GVArray &values = params.readonly_single_input(1, "Value");
for (int64_t i : mask) {
BUFFER_FOR_CPP_TYPE_VALUE(values.type(), buffer);
values.get(i, buffer);
vectors.append(i, buffer);
values.type().destruct(buffer);
}
}
};
TEST(multi_function, GenericAppendFunction)
{
GenericAppendFunction fn(CPPType::get<int32_t>());

View File

@@ -0,0 +1,174 @@
/* Apache License, Version 2.0 */
#include "FN_multi_function.hh"
namespace blender::fn::tests {
class AddPrefixFunction : public MultiFunction {
public:
AddPrefixFunction()
{
static MFSignature signature = create_signature();
this->set_signature(&signature);
}
static MFSignature create_signature()
{
MFSignatureBuilder signature{"Add Prefix"};
signature.single_input<std::string>("Prefix");
signature.single_mutable<std::string>("Strings");
return signature.build();
}
void call(IndexMask mask, MFParams params, MFContext UNUSED(context)) const override
{
const VArray<std::string> &prefixes = params.readonly_single_input<std::string>(0, "Prefix");
MutableSpan<std::string> strings = params.single_mutable<std::string>(1, "Strings");
for (int64_t i : mask) {
strings[i] = prefixes[i] + strings[i];
}
}
};
class CreateRangeFunction : public MultiFunction {
public:
CreateRangeFunction()
{
static MFSignature signature = create_signature();
this->set_signature(&signature);
}
static MFSignature create_signature()
{
MFSignatureBuilder signature{"Create Range"};
signature.single_input<int>("Size");
signature.vector_output<int>("Range");
return signature.build();
}
void call(IndexMask mask, MFParams params, MFContext UNUSED(context)) const override
{
const VArray<int> &sizes = params.readonly_single_input<int>(0, "Size");
GVectorArray &ranges = params.vector_output(1, "Range");
for (int64_t i : mask) {
int size = sizes[i];
for (int j : IndexRange(size)) {
ranges.append(i, &j);
}
}
}
};
class GenericAppendFunction : public MultiFunction {
private:
MFSignature signature_;
public:
GenericAppendFunction(const CPPType &type)
{
MFSignatureBuilder signature{"Append"};
signature.vector_mutable("Vector", type);
signature.single_input("Value", type);
signature_ = signature.build();
this->set_signature(&signature_);
}
void call(IndexMask mask, MFParams params, MFContext UNUSED(context)) const override
{
GVectorArray &vectors = params.vector_mutable(0, "Vector");
const GVArray &values = params.readonly_single_input(1, "Value");
for (int64_t i : mask) {
BUFFER_FOR_CPP_TYPE_VALUE(values.type(), buffer);
values.get(i, buffer);
vectors.append(i, buffer);
values.type().destruct(buffer);
}
}
};
class ConcatVectorsFunction : public MultiFunction {
public:
ConcatVectorsFunction()
{
static MFSignature signature = create_signature();
this->set_signature(&signature);
}
static MFSignature create_signature()
{
MFSignatureBuilder signature{"Concat Vectors"};
signature.vector_mutable<int>("A");
signature.vector_input<int>("B");
return signature.build();
}
void call(IndexMask mask, MFParams params, MFContext UNUSED(context)) const override
{
GVectorArray &a = params.vector_mutable(0);
const GVVectorArray &b = params.readonly_vector_input(1);
a.extend(mask, b);
}
};
class AppendFunction : public MultiFunction {
public:
AppendFunction()
{
static MFSignature signature = create_signature();
this->set_signature(&signature);
}
static MFSignature create_signature()
{
MFSignatureBuilder signature{"Append"};
signature.vector_mutable<int>("Vector");
signature.single_input<int>("Value");
return signature.build();
}
void call(IndexMask mask, MFParams params, MFContext UNUSED(context)) const override
{
GVectorArray_TypedMutableRef<int> vectors = params.vector_mutable<int>(0);
const VArray<int> &values = params.readonly_single_input<int>(1);
for (int64_t i : mask) {
vectors.append(i, values[i]);
}
}
};
class SumVectorFunction : public MultiFunction {
public:
SumVectorFunction()
{
static MFSignature signature = create_signature();
this->set_signature(&signature);
}
static MFSignature create_signature()
{
MFSignatureBuilder signature{"Sum Vectors"};
signature.vector_input<int>("Vector");
signature.single_output<int>("Sum");
return signature.build();
}
void call(IndexMask mask, MFParams params, MFContext UNUSED(context)) const override
{
const VVectorArray<int> &vectors = params.readonly_vector_input<int>(0);
MutableSpan<int> sums = params.uninitialized_single_output<int>(1);
for (int64_t i : mask) {
int sum = 0;
for (int j : IndexRange(vectors.get_vector_size(i))) {
sum += vectors.get_vector_element(i, j);
}
sums[i] = sum;
}
}
};
} // namespace blender::fn::tests

View File

@@ -162,7 +162,7 @@ void UnitConverter::calculate_scale(Scene &sce)
* Translation map.
* Used to translate every COLLADA id to a valid id, no matter what "wrong" letters may be
* included. Look at the IDREF XSD declaration for more.
* Follows strictly the COLLADA XSD declaration which explicitly allows non-english chars,
* Follows strictly the COLLADA XSD declaration which explicitly allows non-English chars,
* like special chars (e.g. micro sign), umlauts and so on.
* The COLLADA spec also allows additional chars for member access ('.'), these
* must obviously be removed too, otherwise they would be heavily misinterpreted.

View File

@@ -40,6 +40,8 @@
.handle = NULL, \
.handle_filepath[0] = '\0', \
.handle_readers = NULL, \
.use_prefetch = 1, \
.prefetch_cache_size = 4096, \
}
/** \} */

View File

@@ -101,7 +101,15 @@ typedef struct CacheFile {
*/
char use_render_procedural;
char _pad1[7];
char _pad1[3];
/** Enable data prefetching when using the Cycles Procedural. */
char use_prefetch;
/** Size in megabytes for the prefetch cache used by the Cycles Procedural. */
int prefetch_cache_size;
char _pad2[7];
char velocity_unit;
/* Name of the velocity property in the archive. */

View File

@@ -150,6 +150,23 @@ static void rna_def_cachefile(BlenderRNA *brna)
"determine which file to use in a file sequence");
RNA_def_property_update(prop, 0, "rna_CacheFile_update");
/* ----------------- Cache controls ----------------- */
prop = RNA_def_property(srna, "use_prefetch", PROP_BOOLEAN, PROP_NONE);
RNA_def_property_ui_text(
prop,
"Use Prefetch",
"When enabled, the Cycles Procedural will preload animation data for faster updates");
RNA_def_property_update(prop, 0, "rna_CacheFile_update");
prop = RNA_def_property(srna, "prefetch_cache_size", PROP_INT, PROP_UNSIGNED);
RNA_def_property_ui_text(
prop,
"Prefetch Cache Size",
"Memory usage limit in megabytes for the Cycles Procedural cache, if the data does not "
"fit within the limit, rendering is aborted");
RNA_def_property_update(prop, 0, "rna_CacheFile_update");
/* ----------------- Axis Conversion ----------------- */
prop = RNA_def_property(srna, "forward_axis", PROP_ENUM, PROP_NONE);

View File

@@ -84,7 +84,8 @@
#include "NOD_derived_node_tree.hh"
#include "NOD_geometry.h"
#include "NOD_geometry_nodes_eval_log.hh"
#include "NOD_node_tree_multi_function.hh"
#include "FN_multi_function.hh"
using blender::destruct_ptr;
using blender::float3;
@@ -858,7 +859,7 @@ static GeometrySet compute_geometry(const DerivedNodeTree &tree,
{
blender::ResourceScope scope;
blender::LinearAllocator<> &allocator = scope.linear_allocator();
blender::nodes::MultiFunctionByNode mf_by_node = get_multi_function_per_node(tree, scope);
blender::nodes::NodeMultiFunctions mf_by_node{tree, scope};
Map<DOutputSocket, GMutablePointer> group_inputs;

View File

@@ -836,7 +836,7 @@ class GeometryNodesEvaluator {
}
/* Use the multi-function implementation if it exists. */
const MultiFunction *multi_function = params_.mf_by_node->lookup_default(node, nullptr);
const MultiFunction *multi_function = params_.mf_by_node->try_get(node);
if (multi_function != nullptr) {
this->execute_multi_function_node(node, *multi_function, node_state);
return;

View File

@@ -20,12 +20,14 @@
#include "NOD_derived_node_tree.hh"
#include "NOD_geometry_nodes_eval_log.hh"
#include "NOD_node_tree_multi_function.hh"
#include "NOD_multi_function.hh"
#include "FN_generic_pointer.hh"
#include "DNA_modifier_types.h"
#include "FN_multi_function.hh"
namespace geo_log = blender::nodes::geometry_nodes_eval_log;
namespace blender::modifiers::geometry_nodes {
@@ -45,7 +47,7 @@ struct GeometryNodesEvaluationParams {
* necessary in all cases. Sometimes `log_socket_value_fn` might just want to look at the value
* and then it can be freed. */
Vector<DSocket> force_compute_sockets;
nodes::MultiFunctionByNode *mf_by_node;
nodes::NodeMultiFunctions *mf_by_node;
const NodesModifierData *modifier_;
Depsgraph *depsgraph;
Object *self_object;

View File

@@ -358,13 +358,8 @@ static bool get_show_adaptive_options(const bContext *C, Panel *panel)
/* Don't show adaptive options if the cycles experimental feature set is disabled. */
Scene *scene = CTX_data_scene(C);
PointerRNA scene_ptr;
RNA_id_pointer_create(&scene->id, &scene_ptr);
if (BKE_scene_uses_cycles(scene)) {
PointerRNA cycles_ptr = RNA_pointer_get(&scene_ptr, "cycles");
if (RNA_enum_get(&cycles_ptr, "feature_set") != 1) { /* EXPERIMENTAL */
return false;
}
if (!BKE_scene_uses_cycles_experimental_features(scene)) {
return false;
}
return true;

View File

@@ -345,11 +345,10 @@ set(SRC
intern/node_common.c
intern/node_exec.cc
intern/node_geometry_exec.cc
intern/node_multi_function.cc
intern/node_socket.cc
intern/node_tree_multi_function.cc
intern/node_tree_ref.cc
intern/node_util.c
intern/type_callbacks.cc
intern/type_conversions.cc
composite/node_composite_util.h
@@ -366,13 +365,12 @@ set(SRC
NOD_geometry_exec.hh
NOD_geometry_nodes_eval_log.hh
NOD_math_functions.hh
NOD_node_tree_multi_function.hh
NOD_multi_function.hh
NOD_node_tree_ref.hh
NOD_shader.h
NOD_socket.h
NOD_static_types.h
NOD_texture.h
NOD_type_callbacks.hh
NOD_type_conversions.hh
intern/node_common.h
intern/node_exec.h

View File

@@ -0,0 +1,130 @@
/*
* 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.
*/
#pragma once
#include "FN_multi_function.hh"
#include "DNA_node_types.h"
#include "NOD_derived_node_tree.hh"
namespace blender::nodes {
using namespace fn::multi_function_types;
class NodeMultiFunctions;
/**
* Utility class to help nodes build a multi-function for themselves.
*/
class NodeMultiFunctionBuilder : NonCopyable, NonMovable {
private:
ResourceScope &resource_scope_;
bNode &node_;
bNodeTree &tree_;
const MultiFunction *built_fn_ = nullptr;
friend NodeMultiFunctions;
public:
NodeMultiFunctionBuilder(ResourceScope &resource_scope, bNode &node, bNodeTree &tree);
/**
* Assign a multi-function for the current node. The input and output parameters of the function
* have to match the available sockets in the node.
*/
void set_matching_fn(const MultiFunction *fn);
void set_matching_fn(const MultiFunction &fn);
/**
* Utility method for creating and assigning a multi-function when it can't have a static
* lifetime.
*/
template<typename T, typename... Args> void construct_and_set_matching_fn(Args &&...args);
bNode &node();
bNodeTree &tree();
ResourceScope &resource_scope();
};
/**
* Gives access to multi-functions for all nodes in a node tree that support them.
*/
class NodeMultiFunctions {
private:
Map<const bNode *, const MultiFunction *> map_;
public:
NodeMultiFunctions(const DerivedNodeTree &tree, ResourceScope &resource_scope);
const MultiFunction *try_get(const DNode &node) const;
};
/* --------------------------------------------------------------------
* NodeMultiFunctionBuilder inline methods.
*/
inline NodeMultiFunctionBuilder::NodeMultiFunctionBuilder(ResourceScope &resource_scope,
bNode &node,
bNodeTree &tree)
: resource_scope_(resource_scope), node_(node), tree_(tree)
{
}
inline bNode &NodeMultiFunctionBuilder::node()
{
return node_;
}
inline bNodeTree &NodeMultiFunctionBuilder::tree()
{
return tree_;
}
inline ResourceScope &NodeMultiFunctionBuilder::resource_scope()
{
return resource_scope_;
}
inline void NodeMultiFunctionBuilder::set_matching_fn(const MultiFunction *fn)
{
built_fn_ = fn;
}
inline void NodeMultiFunctionBuilder::set_matching_fn(const MultiFunction &fn)
{
this->set_matching_fn(&fn);
}
template<typename T, typename... Args>
inline void NodeMultiFunctionBuilder::construct_and_set_matching_fn(Args &&...args)
{
const T &fn = resource_scope_.construct<T>(__func__, std::forward<Args>(args)...);
this->set_matching_fn(&fn);
}
/* --------------------------------------------------------------------
* NodeMultiFunctions inline methods.
*/
inline const MultiFunction *NodeMultiFunctions::try_get(const DNode &node) const
{
return map_.lookup_default(node->bnode(), nullptr);
}
} // namespace blender::nodes

View File

@@ -1,390 +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.
*/
#pragma once
/** \file
* \ingroup nodes
*
* This file allows you to generate a multi-function network from a user-generated node tree.
*/
#include "FN_multi_function_builder.hh"
#include "FN_multi_function_network.hh"
#include "NOD_derived_node_tree.hh"
#include "NOD_type_callbacks.hh"
#include "BLI_multi_value_map.hh"
#include "BLI_resource_scope.hh"
namespace blender::nodes {
/**
* A MFNetworkTreeMap maps various components of a node tree to components of a fn::MFNetwork. This
* is necessary for further processing of a multi-function network that has been generated from a
* node tree.
*/
class MFNetworkTreeMap {
private:
/**
* Store by id instead of using a hash table to avoid unnecessary hash table lookups.
*
* Input sockets in a node tree can have multiple corresponding sockets in the generated
* MFNetwork. This is because nodes are allowed to expand into multiple multi-function nodes.
*/
const DerivedNodeTree &tree_;
fn::MFNetwork &network_;
MultiValueMap<DSocket, fn::MFSocket *> sockets_by_dsocket_;
public:
MFNetworkTreeMap(const DerivedNodeTree &tree, fn::MFNetwork &network)
: tree_(tree), network_(network)
{
}
const DerivedNodeTree &tree() const
{
return tree_;
}
const fn::MFNetwork &network() const
{
return network_;
}
fn::MFNetwork &network()
{
return network_;
}
void add(const DSocket &dsocket, fn::MFSocket &socket)
{
BLI_assert(dsocket->is_input() == socket.is_input());
BLI_assert(dsocket->is_input() || sockets_by_dsocket_.lookup(dsocket).is_empty());
sockets_by_dsocket_.add(dsocket, &socket);
}
void add(const DInputSocket &dsocket, fn::MFInputSocket &socket)
{
sockets_by_dsocket_.add(dsocket, &socket);
}
void add(const DOutputSocket &dsocket, fn::MFOutputSocket &socket)
{
/* There can be at most one matching output socket. */
BLI_assert(sockets_by_dsocket_.lookup(dsocket).is_empty());
sockets_by_dsocket_.add(dsocket, &socket);
}
void add(const DTreeContext &context,
Span<const InputSocketRef *> dsockets,
Span<fn::MFInputSocket *> sockets)
{
assert_same_size(dsockets, sockets);
for (int i : dsockets.index_range()) {
this->add(DInputSocket(&context, dsockets[i]), *sockets[i]);
}
}
void add(const DTreeContext &context,
Span<const OutputSocketRef *> dsockets,
Span<fn::MFOutputSocket *> sockets)
{
assert_same_size(dsockets, sockets);
for (int i : dsockets.index_range()) {
this->add(DOutputSocket(&context, dsockets[i]), *sockets[i]);
}
}
void add_try_match(const DNode &dnode, fn::MFNode &node)
{
this->add_try_match(*dnode.context(),
dnode->inputs().cast<const SocketRef *>(),
node.inputs().cast<fn::MFSocket *>());
this->add_try_match(*dnode.context(),
dnode->outputs().cast<const SocketRef *>(),
node.outputs().cast<fn::MFSocket *>());
}
void add_try_match(const DTreeContext &context,
Span<const InputSocketRef *> dsockets,
Span<fn::MFInputSocket *> sockets)
{
this->add_try_match(
context, dsockets.cast<const SocketRef *>(), sockets.cast<fn::MFSocket *>());
}
void add_try_match(const DTreeContext &context,
Span<const OutputSocketRef *> dsockets,
Span<fn::MFOutputSocket *> sockets)
{
this->add_try_match(
context, dsockets.cast<const SocketRef *>(), sockets.cast<fn::MFSocket *>());
}
void add_try_match(const DTreeContext &context,
Span<const SocketRef *> dsockets,
Span<fn::MFSocket *> sockets)
{
int used_sockets = 0;
for (const SocketRef *dsocket : dsockets) {
if (!dsocket->is_available()) {
continue;
}
if (!socket_is_mf_data_socket(*dsocket->typeinfo())) {
continue;
}
fn::MFSocket *socket = sockets[used_sockets];
this->add(DSocket(&context, dsocket), *socket);
used_sockets++;
}
}
fn::MFOutputSocket &lookup(const DOutputSocket &dsocket)
{
return sockets_by_dsocket_.lookup(dsocket)[0]->as_output();
}
Span<fn::MFInputSocket *> lookup(const DInputSocket &dsocket)
{
return sockets_by_dsocket_.lookup(dsocket).cast<fn::MFInputSocket *>();
}
fn::MFInputSocket &lookup_dummy(const DInputSocket &dsocket)
{
Span<fn::MFInputSocket *> sockets = this->lookup(dsocket);
BLI_assert(sockets.size() == 1);
fn::MFInputSocket &socket = *sockets[0];
BLI_assert(socket.node().is_dummy());
return socket;
}
fn::MFOutputSocket &lookup_dummy(const DOutputSocket &dsocket)
{
fn::MFOutputSocket &socket = this->lookup(dsocket);
BLI_assert(socket.node().is_dummy());
return socket;
}
bool is_mapped(const DSocket &dsocket) const
{
return !sockets_by_dsocket_.lookup(dsocket).is_empty();
}
};
/**
* This data is necessary throughout the generation of a MFNetwork from a node tree.
*/
struct CommonMFNetworkBuilderData {
ResourceScope &scope;
fn::MFNetwork &network;
MFNetworkTreeMap &network_map;
const DerivedNodeTree &tree;
};
class MFNetworkBuilderBase {
protected:
CommonMFNetworkBuilderData &common_;
public:
MFNetworkBuilderBase(CommonMFNetworkBuilderData &common) : common_(common)
{
}
/**
* Returns the network that is currently being built.
*/
fn::MFNetwork &network()
{
return common_.network;
}
/**
* Returns the map between the node tree and the multi-function network that is being built.
*/
MFNetworkTreeMap &network_map()
{
return common_.network_map;
}
/**
* Returns a resource collector that will only be destructed after the multi-function network is
* destructed.
*/
ResourceScope &resource_scope()
{
return common_.scope;
}
/**
* Constructs a new function that will live at least as long as the MFNetwork.
*/
template<typename T, typename... Args> T &construct_fn(Args &&...args)
{
BLI_STATIC_ASSERT((std::is_base_of_v<fn::MultiFunction, T>), "");
void *buffer = common_.scope.linear_allocator().allocate(sizeof(T), alignof(T));
T *fn = new (buffer) T(std::forward<Args>(args)...);
common_.scope.add(destruct_ptr<T>(fn), fn->name().c_str());
return *fn;
}
};
/**
* This class is used by socket implementations to define how an unlinked input socket is handled
* in a multi-function network.
*/
class SocketMFNetworkBuilder : public MFNetworkBuilderBase {
private:
bNodeSocket *bsocket_;
fn::MFOutputSocket *built_socket_ = nullptr;
public:
SocketMFNetworkBuilder(CommonMFNetworkBuilderData &common, const DSocket &dsocket)
: MFNetworkBuilderBase(common), bsocket_(dsocket->bsocket())
{
}
/**
* Returns the socket that is currently being built.
*/
bNodeSocket &bsocket()
{
return *bsocket_;
}
/**
* Utility method that returns bsocket->default_value for the current socket.
*/
template<typename T> T *socket_default_value()
{
return static_cast<T *>(bsocket_->default_value);
}
/**
* Builds a function node for that socket that outputs the given constant value.
*/
template<typename T> void set_constant_value(T value)
{
this->construct_generator_fn<fn::CustomMF_Constant<T>>(std::move(value));
}
void set_constant_value(const CPPType &type, const void *value)
{
/* The value has live as long as the generated mf network. */
this->construct_generator_fn<fn::CustomMF_GenericConstant>(type, value);
}
template<typename T, typename... Args> void construct_generator_fn(Args &&...args)
{
const fn::MultiFunction &fn = this->construct_fn<T>(std::forward<Args>(args)...);
this->set_generator_fn(fn);
}
/**
* Uses the first output of the given multi-function as value of the socket.
*/
void set_generator_fn(const fn::MultiFunction &fn)
{
fn::MFFunctionNode &node = common_.network.add_function(fn);
this->set_socket(node.output(0));
}
/**
* Define a multi-function socket that outputs the value of the bsocket.
*/
void set_socket(fn::MFOutputSocket &socket)
{
built_socket_ = &socket;
}
fn::MFOutputSocket *built_socket()
{
return built_socket_;
}
};
/**
* This class is used by node implementations to define how a user-level node expands into
* multi-function nodes internally.
*/
class NodeMFNetworkBuilder : public MFNetworkBuilderBase {
private:
DNode dnode_;
public:
NodeMFNetworkBuilder(CommonMFNetworkBuilderData &common, DNode dnode)
: MFNetworkBuilderBase(common), dnode_(dnode)
{
}
/**
* Tells the builder to build a function that corresponds to the node that is being built. It
* will try to match up sockets.
*/
template<typename T, typename... Args> T &construct_and_set_matching_fn(Args &&...args)
{
T &function = this->construct_fn<T>(std::forward<Args>(args)...);
this->set_matching_fn(function);
return function;
}
const fn::MultiFunction &get_not_implemented_fn()
{
return this->get_default_fn("Not Implemented (" + dnode_->name() + ")");
}
const fn::MultiFunction &get_default_fn(StringRef name);
const void set_not_implemented()
{
this->set_matching_fn(this->get_not_implemented_fn());
}
/**
* Tells the builder that the given function corresponds to the node that is being built. It will
* try to match up sockets. For that it skips unavailable and non-data sockets.
*/
void set_matching_fn(const fn::MultiFunction &function)
{
fn::MFFunctionNode &node = common_.network.add_function(function);
common_.network_map.add_try_match(dnode_, node);
}
/**
* Returns the node that is currently being built.
*/
bNode &bnode()
{
return *dnode_->bnode();
}
/**
* Returns the node that is currently being built.
*/
const DNode &dnode() const
{
return dnode_;
}
};
MFNetworkTreeMap insert_node_tree_into_mf_network(fn::MFNetwork &network,
const DerivedNodeTree &tree,
ResourceScope &scope);
using MultiFunctionByNode = Map<DNode, const fn::MultiFunction *>;
MultiFunctionByNode get_multi_function_per_node(const DerivedNodeTree &tree, ResourceScope &scope);
} // namespace blender::nodes

View File

@@ -30,7 +30,7 @@
#include "BLT_translation.h"
#include "NOD_function.h"
#include "NOD_node_tree_multi_function.hh"
#include "NOD_multi_function.hh"
#include "node_util.h"

View File

@@ -58,7 +58,7 @@ static void node_boolean_math_label(bNodeTree *UNUSED(ntree), bNode *node, char
BLI_strncpy(label, IFACE_(name), maxlen);
}
static const blender::fn::MultiFunction &get_multi_function(bNode &bnode)
static const blender::fn::MultiFunction *get_multi_function(bNode &bnode)
{
static blender::fn::CustomMF_SI_SI_SO<bool, bool, bool> and_fn{
"And", [](bool a, bool b) { return a && b; }};
@@ -68,20 +68,21 @@ static const blender::fn::MultiFunction &get_multi_function(bNode &bnode)
switch (bnode.custom1) {
case NODE_BOOLEAN_MATH_AND:
return and_fn;
return &and_fn;
case NODE_BOOLEAN_MATH_OR:
return or_fn;
return &or_fn;
case NODE_BOOLEAN_MATH_NOT:
return not_fn;
return &not_fn;
}
BLI_assert(false);
return blender::fn::dummy_multi_function;
BLI_assert_unreachable();
return nullptr;
}
static void node_boolean_expand_in_mf_network(blender::nodes::NodeMFNetworkBuilder &builder)
static void fn_node_boolean_math_build_multi_function(
blender::nodes::NodeMultiFunctionBuilder &builder)
{
const blender::fn::MultiFunction &fn = get_multi_function(builder.bnode());
const blender::fn::MultiFunction *fn = get_multi_function(builder.node());
builder.set_matching_fn(fn);
}
@@ -93,7 +94,7 @@ void register_node_type_fn_boolean_math()
node_type_socket_templates(&ntype, fn_node_boolean_math_in, fn_node_boolean_math_out);
node_type_label(&ntype, node_boolean_math_label);
node_type_update(&ntype, node_boolean_math_update);
ntype.expand_in_mf_network = node_boolean_expand_in_mf_network;
ntype.build_multi_function = fn_node_boolean_math_build_multi_function;
ntype.draw_buttons = fn_node_boolean_math_layout;
nodeRegisterType(&ntype);
}

View File

@@ -64,7 +64,7 @@ static void node_float_compare_label(bNodeTree *UNUSED(ntree),
BLI_strncpy(label, IFACE_(name), maxlen);
}
static const blender::fn::MultiFunction &get_multi_function(bNode &node)
static const blender::fn::MultiFunction *get_multi_function(bNode &node)
{
static blender::fn::CustomMF_SI_SI_SO<float, float, bool> less_than_fn{
"Less Than", [](float a, float b) { return a < b; }};
@@ -81,26 +81,27 @@ static const blender::fn::MultiFunction &get_multi_function(bNode &node)
switch (node.custom1) {
case NODE_FLOAT_COMPARE_LESS_THAN:
return less_than_fn;
return &less_than_fn;
case NODE_FLOAT_COMPARE_LESS_EQUAL:
return less_equal_fn;
return &less_equal_fn;
case NODE_FLOAT_COMPARE_GREATER_THAN:
return greater_than_fn;
return &greater_than_fn;
case NODE_FLOAT_COMPARE_GREATER_EQUAL:
return greater_equal_fn;
return &greater_equal_fn;
case NODE_FLOAT_COMPARE_EQUAL:
return equal_fn;
return &equal_fn;
case NODE_FLOAT_COMPARE_NOT_EQUAL:
return not_equal_fn;
return &not_equal_fn;
}
BLI_assert(false);
return blender::fn::dummy_multi_function;
BLI_assert_unreachable();
return nullptr;
}
static void node_float_compare_expand_in_mf_network(blender::nodes::NodeMFNetworkBuilder &builder)
static void fn_node_float_compare_build_multi_function(
blender::nodes::NodeMultiFunctionBuilder &builder)
{
const blender::fn::MultiFunction &fn = get_multi_function(builder.bnode());
const blender::fn::MultiFunction *fn = get_multi_function(builder.node());
builder.set_matching_fn(fn);
}
@@ -112,7 +113,7 @@ void register_node_type_fn_float_compare()
node_type_socket_templates(&ntype, fn_node_float_compare_in, fn_node_float_compare_out);
node_type_label(&ntype, node_float_compare_label);
node_type_update(&ntype, node_float_compare_update);
ntype.expand_in_mf_network = node_float_compare_expand_in_mf_network;
ntype.build_multi_function = fn_node_float_compare_build_multi_function;
ntype.draw_buttons = geo_node_float_compare_layout;
nodeRegisterType(&ntype);
}

View File

@@ -50,7 +50,7 @@ static void node_float_to_int_label(bNodeTree *UNUSED(ntree), bNode *node, char
BLI_strncpy(label, IFACE_(name), maxlen);
}
static const blender::fn::MultiFunction &get_multi_function(bNode &bnode)
static const blender::fn::MultiFunction *get_multi_function(bNode &bnode)
{
static blender::fn::CustomMF_SI_SO<float, int> round_fn{"Round",
[](float a) { return (int)round(a); }};
@@ -63,22 +63,23 @@ static const blender::fn::MultiFunction &get_multi_function(bNode &bnode)
switch (static_cast<FloatToIntRoundingMode>(bnode.custom1)) {
case FN_NODE_FLOAT_TO_INT_ROUND:
return round_fn;
return &round_fn;
case FN_NODE_FLOAT_TO_INT_FLOOR:
return floor_fn;
return &floor_fn;
case FN_NODE_FLOAT_TO_INT_CEIL:
return ceil_fn;
return &ceil_fn;
case FN_NODE_FLOAT_TO_INT_TRUNCATE:
return trunc_fn;
return &trunc_fn;
}
BLI_assert_unreachable();
return blender::fn::dummy_multi_function;
return nullptr;
}
static void node_float_to_int_expand_in_mf_network(blender::nodes::NodeMFNetworkBuilder &builder)
static void fn_node_float_to_int_build_multi_function(
blender::nodes::NodeMultiFunctionBuilder &builder)
{
const blender::fn::MultiFunction &fn = get_multi_function(builder.bnode());
const blender::fn::MultiFunction *fn = get_multi_function(builder.node());
builder.set_matching_fn(fn);
}
@@ -89,7 +90,7 @@ void register_node_type_fn_float_to_int()
fn_node_type_base(&ntype, FN_NODE_FLOAT_TO_INT, "Float to Integer", NODE_CLASS_CONVERTOR, 0);
node_type_socket_templates(&ntype, fn_node_float_to_int_in, fn_node_float_to_int_out);
node_type_label(&ntype, node_float_to_int_label);
ntype.expand_in_mf_network = node_float_to_int_expand_in_mf_network;
ntype.build_multi_function = fn_node_float_to_int_build_multi_function;
ntype.draw_buttons = fn_node_float_to_int_layout;
nodeRegisterType(&ntype);
}

View File

@@ -29,14 +29,14 @@ static void fn_node_input_string_layout(uiLayout *layout, bContext *UNUSED(C), P
uiItemR(layout, ptr, "string", 0, "", ICON_NONE);
}
static void fn_node_input_string_expand_in_mf_network(
blender::nodes::NodeMFNetworkBuilder &builder)
static void fn_node_input_string_build_multi_function(
blender::nodes::NodeMultiFunctionBuilder &builder)
{
bNode &bnode = builder.bnode();
bNode &bnode = builder.node();
NodeInputString *node_storage = static_cast<NodeInputString *>(bnode.storage);
std::string string = std::string((node_storage->string) ? node_storage->string : "");
builder.construct_and_set_matching_fn<blender::fn::CustomMF_Constant<std::string>>(string);
builder.construct_and_set_matching_fn<blender::fn::CustomMF_Constant<std::string>>(
std::move(string));
}
static void fn_node_input_string_init(bNodeTree *UNUSED(ntree), bNode *node)
@@ -78,7 +78,7 @@ void register_node_type_fn_input_string()
node_type_socket_templates(&ntype, nullptr, fn_node_input_string_out);
node_type_init(&ntype, fn_node_input_string_init);
node_type_storage(&ntype, "NodeInputString", fn_node_input_string_free, fn_node_string_copy);
ntype.expand_in_mf_network = fn_node_input_string_expand_in_mf_network;
ntype.build_multi_function = fn_node_input_string_build_multi_function;
ntype.draw_buttons = fn_node_input_string_layout;
nodeRegisterType(&ntype);
}

View File

@@ -32,16 +32,14 @@ static void fn_node_input_vector_layout(uiLayout *layout, bContext *UNUSED(C), P
uiItemR(col, ptr, "vector", UI_ITEM_R_EXPAND, "", ICON_NONE);
}
static void fn_node_vector_input_expand_in_mf_network(
blender::nodes::NodeMFNetworkBuilder &builder)
static void fn_node_vector_input_build_multi_function(
blender::nodes::NodeMultiFunctionBuilder &builder)
{
bNode &bnode = builder.bnode();
bNode &bnode = builder.node();
NodeInputVector *node_storage = static_cast<NodeInputVector *>(bnode.storage);
blender::float3 vector(node_storage->vector);
builder.construct_and_set_matching_fn<blender::fn::CustomMF_Constant<blender::float3>>(vector);
}
static void fn_node_input_vector_init(bNodeTree *UNUSED(ntree), bNode *node)
{
NodeInputVector *data = (NodeInputVector *)MEM_callocN(sizeof(NodeInputVector),
@@ -58,7 +56,7 @@ void register_node_type_fn_input_vector()
node_type_init(&ntype, fn_node_input_vector_init);
node_type_storage(
&ntype, "NodeInputVector", node_free_standard_storage, node_copy_standard_storage);
ntype.expand_in_mf_network = fn_node_vector_input_expand_in_mf_network;
ntype.build_multi_function = fn_node_vector_input_build_multi_function;
ntype.draw_buttons = fn_node_input_vector_layout;
nodeRegisterType(&ntype);
}

View File

@@ -67,10 +67,11 @@ class RandomFloatFunction : public blender::fn::MultiFunction {
}
};
static void fn_node_random_float_expand_in_mf_network(
blender::nodes::NodeMFNetworkBuilder &builder)
static void fn_node_random_float_build_multi_function(
blender::nodes::NodeMultiFunctionBuilder &builder)
{
builder.construct_and_set_matching_fn<RandomFloatFunction>();
static RandomFloatFunction fn;
builder.set_matching_fn(fn);
}
void register_node_type_fn_random_float()
@@ -79,6 +80,6 @@ void register_node_type_fn_random_float()
fn_node_type_base(&ntype, FN_NODE_RANDOM_FLOAT, "Random Float", 0, 0);
node_type_socket_templates(&ntype, fn_node_random_float_in, fn_node_random_float_out);
ntype.expand_in_mf_network = fn_node_random_float_expand_in_mf_network;
ntype.build_multi_function = fn_node_random_float_build_multi_function;
nodeRegisterType(&ntype);
}

View File

@@ -19,7 +19,6 @@
#include "DEG_depsgraph_query.h"
#include "NOD_geometry_exec.hh"
#include "NOD_type_callbacks.hh"
#include "NOD_type_conversions.hh"
#include "node_geometry_util.hh"

View File

@@ -0,0 +1,40 @@
/*
* 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.
*/
#include "NOD_multi_function.hh"
namespace blender::nodes {
NodeMultiFunctions::NodeMultiFunctions(const DerivedNodeTree &tree, ResourceScope &resource_scope)
{
for (const NodeTreeRef *tree_ref : tree.used_node_tree_refs()) {
bNodeTree *btree = tree_ref->btree();
for (const NodeRef *node : tree_ref->nodes()) {
bNode *bnode = node->bnode();
if (bnode->typeinfo->build_multi_function == nullptr) {
continue;
}
NodeMultiFunctionBuilder builder{resource_scope, *bnode, *btree};
bnode->typeinfo->build_multi_function(builder);
const MultiFunction *fn = builder.built_fn_;
if (fn != nullptr) {
map_.add_new(bnode, fn);
}
}
}
}
} // namespace blender::nodes

View File

@@ -44,7 +44,6 @@
#include "MEM_guardedalloc.h"
#include "NOD_node_tree_multi_function.hh"
#include "NOD_socket.h"
#include "FN_cpp_type_make.hh"

View File

@@ -1,409 +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.
*/
#include "NOD_node_tree_multi_function.hh"
#include "NOD_type_conversions.hh"
#include "FN_multi_function_network_evaluation.hh"
#include "BLI_color.hh"
#include "BLI_float2.hh"
#include "BLI_float3.hh"
namespace blender::nodes {
const fn::MultiFunction &NodeMFNetworkBuilder::get_default_fn(StringRef name)
{
Vector<fn::MFDataType, 10> input_types;
Vector<fn::MFDataType, 10> output_types;
for (const InputSocketRef *dsocket : dnode_->inputs()) {
if (dsocket->is_available()) {
std::optional<fn::MFDataType> data_type = socket_mf_type_get(*dsocket->typeinfo());
if (data_type.has_value()) {
input_types.append(*data_type);
}
}
}
for (const OutputSocketRef *dsocket : dnode_->outputs()) {
if (dsocket->is_available()) {
std::optional<fn::MFDataType> data_type = socket_mf_type_get(*dsocket->typeinfo());
if (data_type.has_value()) {
output_types.append(*data_type);
}
}
}
const fn::MultiFunction &fn = this->construct_fn<fn::CustomMF_DefaultOutput>(
name, input_types, output_types);
return fn;
}
static void insert_dummy_node(CommonMFNetworkBuilderData &common, const DNode &dnode)
{
constexpr int stack_capacity = 10;
Vector<fn::MFDataType, stack_capacity> input_types;
Vector<StringRef, stack_capacity> input_names;
Vector<const InputSocketRef *, stack_capacity> input_dsockets;
for (const InputSocketRef *dsocket : dnode->inputs()) {
if (dsocket->is_available()) {
std::optional<fn::MFDataType> data_type = socket_mf_type_get(*dsocket->bsocket()->typeinfo);
if (data_type.has_value()) {
input_types.append(*data_type);
input_names.append(dsocket->name());
input_dsockets.append(dsocket);
}
}
}
Vector<fn::MFDataType, stack_capacity> output_types;
Vector<StringRef, stack_capacity> output_names;
Vector<const OutputSocketRef *, stack_capacity> output_dsockets;
for (const OutputSocketRef *dsocket : dnode->outputs()) {
if (dsocket->is_available()) {
std::optional<fn::MFDataType> data_type = socket_mf_type_get(*dsocket->bsocket()->typeinfo);
if (data_type.has_value()) {
output_types.append(*data_type);
output_names.append(dsocket->name());
output_dsockets.append(dsocket);
}
}
}
fn::MFDummyNode &dummy_node = common.network.add_dummy(
dnode->name(), input_types, output_types, input_names, output_names);
common.network_map.add(*dnode.context(), input_dsockets, dummy_node.inputs());
common.network_map.add(*dnode.context(), output_dsockets, dummy_node.outputs());
}
static bool has_data_sockets(const DNode &dnode)
{
for (const InputSocketRef *socket : dnode->inputs()) {
if (socket_is_mf_data_socket(*socket->bsocket()->typeinfo)) {
return true;
}
}
for (const OutputSocketRef *socket : dnode->outputs()) {
if (socket_is_mf_data_socket(*socket->bsocket()->typeinfo)) {
return true;
}
}
return false;
}
static void foreach_node_to_insert(CommonMFNetworkBuilderData &common,
FunctionRef<void(DNode)> callback)
{
common.tree.foreach_node([&](const DNode dnode) {
if (dnode->is_group_node()) {
return;
}
/* Don't insert non-root group input/output nodes, because they will be inlined. */
if (!dnode.context()->is_root()) {
if (dnode->is_group_input_node() || dnode->is_group_output_node()) {
return;
}
}
callback(dnode);
});
}
/**
* Expands all function nodes in the multi-function network. Nodes that don't have an expand
* function, but do have data sockets, will get corresponding dummy nodes.
*/
static void insert_nodes(CommonMFNetworkBuilderData &common)
{
foreach_node_to_insert(common, [&](const DNode dnode) {
const bNodeType *node_type = dnode->typeinfo();
if (node_type->expand_in_mf_network != nullptr) {
NodeMFNetworkBuilder builder{common, dnode};
node_type->expand_in_mf_network(builder);
}
else if (has_data_sockets(dnode)) {
insert_dummy_node(common, dnode);
}
});
}
static fn::MFOutputSocket &insert_default_value_for_type(CommonMFNetworkBuilderData &common,
fn::MFDataType type)
{
const fn::MultiFunction *default_fn;
if (type.is_single()) {
default_fn = &common.scope.construct<fn::CustomMF_GenericConstant>(
AT, type.single_type(), type.single_type().default_value());
}
else {
default_fn = &common.scope.construct<fn::CustomMF_GenericConstantArray>(
AT, fn::GSpan(type.vector_base_type()));
}
fn::MFNode &node = common.network.add_function(*default_fn);
return node.output(0);
}
static fn::MFOutputSocket *insert_unlinked_input(CommonMFNetworkBuilderData &common,
const DInputSocket &dsocket)
{
BLI_assert(socket_is_mf_data_socket(*dsocket->typeinfo()));
SocketMFNetworkBuilder builder{common, dsocket};
socket_expand_in_mf_network(builder);
fn::MFOutputSocket *built_socket = builder.built_socket();
BLI_assert(built_socket != nullptr);
return built_socket;
}
static void insert_links_and_unlinked_inputs(CommonMFNetworkBuilderData &common)
{
foreach_node_to_insert(common, [&](const DNode dnode) {
for (const InputSocketRef *socket_ref : dnode->inputs()) {
const DInputSocket to_dsocket{dnode.context(), socket_ref};
if (!to_dsocket->is_available()) {
continue;
}
if (!socket_is_mf_data_socket(*to_dsocket->typeinfo())) {
continue;
}
Span<fn::MFInputSocket *> to_sockets = common.network_map.lookup(to_dsocket);
BLI_assert(to_sockets.size() >= 1);
const fn::MFDataType to_type = to_sockets[0]->data_type();
Vector<DSocket> from_dsockets;
to_dsocket.foreach_origin_socket([&](DSocket socket) { from_dsockets.append(socket); });
if (from_dsockets.size() > 1) {
fn::MFOutputSocket &from_socket = insert_default_value_for_type(common, to_type);
for (fn::MFInputSocket *to_socket : to_sockets) {
common.network.add_link(from_socket, *to_socket);
}
continue;
}
if (from_dsockets.is_empty()) {
/* The socket is not linked. Need to use the value of the socket itself. */
fn::MFOutputSocket *built_socket = insert_unlinked_input(common, to_dsocket);
for (fn::MFInputSocket *to_socket : to_sockets) {
common.network.add_link(*built_socket, *to_socket);
}
continue;
}
if (from_dsockets[0]->is_input()) {
DInputSocket from_dsocket{from_dsockets[0]};
fn::MFOutputSocket *built_socket = insert_unlinked_input(common, from_dsocket);
for (fn::MFInputSocket *to_socket : to_sockets) {
common.network.add_link(*built_socket, *to_socket);
}
continue;
}
DOutputSocket from_dsocket{from_dsockets[0]};
fn::MFOutputSocket *from_socket = &common.network_map.lookup(from_dsocket);
const fn::MFDataType from_type = from_socket->data_type();
if (from_type != to_type) {
const fn::MultiFunction *conversion_fn =
get_implicit_type_conversions().get_conversion_multi_function(from_type, to_type);
if (conversion_fn != nullptr) {
fn::MFNode &node = common.network.add_function(*conversion_fn);
common.network.add_link(*from_socket, node.input(0));
from_socket = &node.output(0);
}
else {
from_socket = &insert_default_value_for_type(common, to_type);
}
}
for (fn::MFInputSocket *to_socket : to_sockets) {
common.network.add_link(*from_socket, *to_socket);
}
}
});
}
/**
* Expands all function nodes contained in the given node tree within the given multi-function
* network.
*
* Returns a mapping between the original node tree and the generated nodes/sockets for further
* processing.
*/
MFNetworkTreeMap insert_node_tree_into_mf_network(fn::MFNetwork &network,
const DerivedNodeTree &tree,
ResourceScope &scope)
{
MFNetworkTreeMap network_map{tree, network};
CommonMFNetworkBuilderData common{scope, network, network_map, tree};
insert_nodes(common);
insert_links_and_unlinked_inputs(common);
return network_map;
}
/**
* A single node is allowed to expand into multiple nodes before evaluation. Depending on what
* nodes it expands to, it belongs a different type of the ones below.
*/
enum class NodeExpandType {
SingleFunctionNode,
MultipleFunctionNodes,
HasDummyNodes,
};
/**
* Checks how the given node expanded in the multi-function network. If it is only a single
* function node, the corresponding function is returned as well.
*/
static NodeExpandType get_node_expand_type(MFNetworkTreeMap &network_map,
const DNode &dnode,
const fn::MultiFunction **r_single_function)
{
const fn::MFFunctionNode *single_function_node = nullptr;
bool has_multiple_nodes = false;
bool has_dummy_nodes = false;
auto check_mf_node = [&](fn::MFNode &mf_node) {
if (mf_node.is_function()) {
if (single_function_node == nullptr) {
single_function_node = &mf_node.as_function();
}
if (&mf_node != single_function_node) {
has_multiple_nodes = true;
}
}
else {
BLI_assert(mf_node.is_dummy());
has_dummy_nodes = true;
}
};
for (const InputSocketRef *dsocket : dnode->inputs()) {
if (dsocket->is_available()) {
for (fn::MFInputSocket *mf_input :
network_map.lookup(DInputSocket(dnode.context(), dsocket))) {
check_mf_node(mf_input->node());
}
}
}
for (const OutputSocketRef *dsocket : dnode->outputs()) {
if (dsocket->is_available()) {
fn::MFOutputSocket &mf_output = network_map.lookup(DOutputSocket(dnode.context(), dsocket));
check_mf_node(mf_output.node());
}
}
if (has_dummy_nodes) {
return NodeExpandType::HasDummyNodes;
}
if (has_multiple_nodes) {
return NodeExpandType::MultipleFunctionNodes;
}
*r_single_function = &single_function_node->function();
return NodeExpandType::SingleFunctionNode;
}
static const fn::MultiFunction &create_function_for_node_that_expands_into_multiple(
const DNode &dnode,
fn::MFNetwork &network,
MFNetworkTreeMap &network_map,
ResourceScope &scope)
{
Vector<const fn::MFOutputSocket *> dummy_fn_inputs;
for (const InputSocketRef *dsocket : dnode->inputs()) {
if (dsocket->is_available()) {
MFDataType data_type = *socket_mf_type_get(*dsocket->typeinfo());
fn::MFOutputSocket &fn_input = network.add_input(data_type.to_string(), data_type);
for (fn::MFInputSocket *mf_input :
network_map.lookup(DInputSocket(dnode.context(), dsocket))) {
network.add_link(fn_input, *mf_input);
dummy_fn_inputs.append(&fn_input);
}
}
}
Vector<const fn::MFInputSocket *> dummy_fn_outputs;
for (const OutputSocketRef *dsocket : dnode->outputs()) {
if (dsocket->is_available()) {
fn::MFOutputSocket &mf_output = network_map.lookup(DOutputSocket(dnode.context(), dsocket));
MFDataType data_type = mf_output.data_type();
fn::MFInputSocket &fn_output = network.add_output(data_type.to_string(), data_type);
network.add_link(mf_output, fn_output);
dummy_fn_outputs.append(&fn_output);
}
}
fn::MFNetworkEvaluator &fn_evaluator = scope.construct<fn::MFNetworkEvaluator>(
__func__, std::move(dummy_fn_inputs), std::move(dummy_fn_outputs));
return fn_evaluator;
}
/**
* Returns a single multi-function for every node that supports it. This makes it easier to reuse
* the multi-function implementation of nodes in different contexts.
*/
MultiFunctionByNode get_multi_function_per_node(const DerivedNodeTree &tree, ResourceScope &scope)
{
/* Build a network that nodes can insert themselves into. However, the individual nodes are not
* connected. */
fn::MFNetwork &network = scope.construct<fn::MFNetwork>(__func__);
MFNetworkTreeMap network_map{tree, network};
MultiFunctionByNode functions_by_node;
CommonMFNetworkBuilderData common{scope, network, network_map, tree};
tree.foreach_node([&](DNode dnode) {
const bNodeType *node_type = dnode->typeinfo();
if (node_type->expand_in_mf_network == nullptr) {
/* This node does not have a multi-function implementation. */
return;
}
NodeMFNetworkBuilder builder{common, dnode};
node_type->expand_in_mf_network(builder);
const fn::MultiFunction *single_function = nullptr;
const NodeExpandType expand_type = get_node_expand_type(network_map, dnode, &single_function);
switch (expand_type) {
case NodeExpandType::HasDummyNodes: {
/* Dummy nodes cannot be executed, so skip them. */
break;
}
case NodeExpandType::SingleFunctionNode: {
/* This is the common case. Most nodes just expand to a single function. */
functions_by_node.add_new(dnode, single_function);
break;
}
case NodeExpandType::MultipleFunctionNodes: {
/* If a node expanded into multiple functions, a new function has to be created that
* combines those. */
const fn::MultiFunction &fn = create_function_for_node_that_expands_into_multiple(
dnode, network, network_map, scope);
functions_by_node.add_new(dnode, &fn);
break;
}
}
});
return functions_by_node;
}
} // namespace blender::nodes

View File

@@ -1,60 +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.
*/
#include "NOD_node_tree_multi_function.hh"
#include "NOD_type_callbacks.hh"
namespace blender::nodes {
std::optional<MFDataType> socket_mf_type_get(const bNodeSocketType &stype)
{
const CPPType *cpp_type = stype.get_base_cpp_type ? stype.get_base_cpp_type() : nullptr;
if (cpp_type != nullptr) {
return MFDataType::ForSingle(*cpp_type);
}
return {};
}
bool socket_is_mf_data_socket(const bNodeSocketType &stype)
{
if (!socket_mf_type_get(stype).has_value()) {
return false;
}
if (stype.expand_in_mf_network == nullptr && stype.get_base_cpp_value == nullptr) {
return false;
}
return true;
}
void socket_expand_in_mf_network(SocketMFNetworkBuilder &builder)
{
bNodeSocket &socket = builder.bsocket();
if (socket.typeinfo->expand_in_mf_network != nullptr) {
socket.typeinfo->expand_in_mf_network(builder);
}
else if (socket.typeinfo->get_base_cpp_value != nullptr) {
const CPPType &type = *socket.typeinfo->get_base_cpp_type();
void *buffer = builder.resource_scope().linear_allocator().allocate(type.size(),
type.alignment());
socket.typeinfo->get_base_cpp_value(socket, buffer);
builder.set_constant_value(type, buffer);
}
else {
BLI_assert_unreachable();
}
}
} // namespace blender::nodes

View File

@@ -72,7 +72,7 @@
#ifdef __cplusplus
# include "FN_multi_function_builder.hh"
# include "NOD_node_tree_multi_function.hh"
# include "NOD_multi_function.hh"
# include "BLI_color.hh"
# include "BLI_float3.hh"

View File

@@ -51,7 +51,7 @@ static int gpu_shader_clamp(GPUMaterial *mat,
GPU_stack_link(mat, node, "clamp_range", in, out);
}
static void sh_node_clamp_expand_in_mf_network(blender::nodes::NodeMFNetworkBuilder &builder)
static void sh_node_clamp_build_multi_function(blender::nodes::NodeMultiFunctionBuilder &builder)
{
static blender::fn::CustomMF_SI_SI_SI_SO<float, float, float, float> minmax_fn{
"Clamp (Min Max)",
@@ -65,7 +65,7 @@ static void sh_node_clamp_expand_in_mf_network(blender::nodes::NodeMFNetworkBuil
return clamp_f(value, b, a);
}};
int clamp_type = builder.bnode().custom1;
int clamp_type = builder.node().custom1;
if (clamp_type == NODE_CLAMP_MINMAX) {
builder.set_matching_fn(minmax_fn);
}
@@ -82,7 +82,7 @@ void register_node_type_sh_clamp(void)
node_type_socket_templates(&ntype, sh_node_clamp_in, sh_node_clamp_out);
node_type_init(&ntype, node_shader_init_clamp);
node_type_gpu(&ntype, gpu_shader_clamp);
ntype.expand_in_mf_network = sh_node_clamp_expand_in_mf_network;
ntype.build_multi_function = sh_node_clamp_build_multi_function;
nodeRegisterType(&ntype);
}

View File

@@ -143,9 +143,10 @@ class CurveVecFunction : public blender::fn::MultiFunction {
}
};
static void sh_node_curve_vec_expand_in_mf_network(blender::nodes::NodeMFNetworkBuilder &builder)
static void sh_node_curve_vec_build_multi_function(
blender::nodes::NodeMultiFunctionBuilder &builder)
{
bNode &bnode = builder.bnode();
bNode &bnode = builder.node();
CurveMapping *cumap = (CurveMapping *)bnode.storage;
BKE_curvemapping_init(cumap);
builder.construct_and_set_matching_fn<CurveVecFunction>(*cumap);
@@ -162,7 +163,7 @@ void register_node_type_sh_curve_vec(void)
node_type_storage(&ntype, "CurveMapping", node_free_curves, node_copy_curves);
node_type_exec(&ntype, node_initexec_curves, nullptr, node_shader_exec_curve_vec);
node_type_gpu(&ntype, gpu_shader_curve_vec);
ntype.expand_in_mf_network = sh_node_curve_vec_expand_in_mf_network;
ntype.build_multi_function = sh_node_curve_vec_build_multi_function;
nodeRegisterType(&ntype);
}
@@ -317,9 +318,10 @@ class CurveRGBFunction : public blender::fn::MultiFunction {
}
};
static void sh_node_curve_rgb_expand_in_mf_network(blender::nodes::NodeMFNetworkBuilder &builder)
static void sh_node_curve_rgb_build_multi_function(
blender::nodes::NodeMultiFunctionBuilder &builder)
{
bNode &bnode = builder.bnode();
bNode &bnode = builder.node();
CurveMapping *cumap = (CurveMapping *)bnode.storage;
BKE_curvemapping_init(cumap);
builder.construct_and_set_matching_fn<CurveRGBFunction>(*cumap);
@@ -336,7 +338,7 @@ void register_node_type_sh_curve_rgb(void)
node_type_storage(&ntype, "CurveMapping", node_free_curves, node_copy_curves);
node_type_exec(&ntype, node_initexec_curves, nullptr, node_shader_exec_curve_rgb);
node_type_gpu(&ntype, gpu_shader_curve_rgb);
ntype.expand_in_mf_network = sh_node_curve_rgb_expand_in_mf_network;
ntype.build_multi_function = sh_node_curve_rgb_build_multi_function;
nodeRegisterType(&ntype);
}

View File

@@ -261,9 +261,10 @@ class MapRangeSmootherstepFunction : public blender::fn::MultiFunction {
}
};
static void sh_node_map_range_expand_in_mf_network(blender::nodes::NodeMFNetworkBuilder &builder)
static void sh_node_map_range_build_multi_function(
blender::nodes::NodeMultiFunctionBuilder &builder)
{
bNode &bnode = builder.bnode();
bNode &bnode = builder.node();
bool clamp = bnode.custom1 != 0;
int interpolation_type = bnode.custom2;
@@ -301,7 +302,6 @@ static void sh_node_map_range_expand_in_mf_network(blender::nodes::NodeMFNetwork
break;
}
default:
builder.set_not_implemented();
break;
}
}
@@ -315,7 +315,7 @@ void register_node_type_sh_map_range(void)
node_type_init(&ntype, node_shader_init_map_range);
node_type_update(&ntype, node_shader_update_map_range);
node_type_gpu(&ntype, gpu_shader_map_range);
ntype.expand_in_mf_network = sh_node_map_range_expand_in_mf_network;
ntype.build_multi_function = sh_node_map_range_build_multi_function;
nodeRegisterType(&ntype);
}

View File

@@ -69,11 +69,9 @@ static int gpu_shader_math(GPUMaterial *mat,
return 0;
}
static const blender::fn::MultiFunction &get_base_multi_function(
blender::nodes::NodeMFNetworkBuilder &builder)
static const blender::fn::MultiFunction *get_base_multi_function(bNode &node)
{
const int mode = builder.bnode().custom1;
const int mode = node.custom1;
const blender::fn::MultiFunction *base_fn = nullptr;
blender::nodes::try_dispatch_float_math_fl_to_fl(
@@ -82,7 +80,7 @@ static const blender::fn::MultiFunction &get_base_multi_function(
base_fn = &fn;
});
if (base_fn != nullptr) {
return *base_fn;
return base_fn;
}
blender::nodes::try_dispatch_float_math_fl_fl_to_fl(
@@ -92,7 +90,7 @@ static const blender::fn::MultiFunction &get_base_multi_function(
base_fn = &fn;
});
if (base_fn != nullptr) {
return *base_fn;
return base_fn;
}
blender::nodes::try_dispatch_float_math_fl_fl_fl_to_fl(
@@ -102,36 +100,51 @@ static const blender::fn::MultiFunction &get_base_multi_function(
base_fn = &fn;
});
if (base_fn != nullptr) {
return *base_fn;
return base_fn;
}
return builder.get_not_implemented_fn();
return nullptr;
}
static void sh_node_math_expand_in_mf_network(blender::nodes::NodeMFNetworkBuilder &builder)
class ClampWrapperFunction : public blender::fn::MultiFunction {
private:
const blender::fn::MultiFunction &fn_;
public:
ClampWrapperFunction(const blender::fn::MultiFunction &fn) : fn_(fn)
{
this->set_signature(&fn.signature());
}
void call(blender::IndexMask mask,
blender::fn::MFParams params,
blender::fn::MFContext context) const override
{
fn_.call(mask, params, context);
/* Assumes the output parameter is the last one. */
const int output_param_index = this->param_amount() - 1;
/* This has actually been initialized in the call above. */
blender::MutableSpan<float> results = params.uninitialized_single_output<float>(
output_param_index);
for (const int i : mask) {
float &value = results[i];
CLAMP(value, 0.0f, 1.0f);
}
}
};
static void sh_node_math_build_multi_function(blender::nodes::NodeMultiFunctionBuilder &builder)
{
const blender::fn::MultiFunction &base_function = get_base_multi_function(builder);
const blender::fn::MultiFunction *base_function = get_base_multi_function(builder.node());
const blender::nodes::DNode &dnode = builder.dnode();
blender::fn::MFNetwork &network = builder.network();
blender::fn::MFFunctionNode &base_node = network.add_function(base_function);
builder.network_map().add_try_match(*dnode.context(), dnode->inputs(), base_node.inputs());
const bool clamp_output = builder.bnode().custom2 != 0;
const bool clamp_output = builder.node().custom2 != 0;
if (clamp_output) {
static blender::fn::CustomMF_SI_SO<float, float> clamp_fn{"Clamp", [](float value) {
CLAMP(value, 0.0f, 1.0f);
return value;
}};
blender::fn::MFFunctionNode &clamp_node = network.add_function(clamp_fn);
network.add_link(base_node.output(0), clamp_node.input(0));
builder.network_map().add(blender::nodes::DOutputSocket(dnode.context(), &dnode->output(0)),
clamp_node.output(0));
builder.construct_and_set_matching_fn<ClampWrapperFunction>(*base_function);
}
else {
builder.network_map().add(blender::nodes::DOutputSocket(dnode.context(), &dnode->output(0)),
base_node.output(0));
builder.set_matching_fn(base_function);
}
}
@@ -144,7 +157,7 @@ void register_node_type_sh_math(void)
node_type_label(&ntype, node_math_label);
node_type_gpu(&ntype, gpu_shader_math);
node_type_update(&ntype, node_math_update);
ntype.expand_in_mf_network = sh_node_math_expand_in_mf_network;
ntype.build_multi_function = sh_node_math_build_multi_function;
nodeRegisterType(&ntype);
}

View File

@@ -174,9 +174,9 @@ class MixRGBFunction : public blender::fn::MultiFunction {
}
};
static void sh_node_mix_rgb_expand_in_mf_network(blender::nodes::NodeMFNetworkBuilder &builder)
static void sh_node_mix_rgb_build_multi_function(blender::nodes::NodeMultiFunctionBuilder &builder)
{
bNode &node = builder.bnode();
bNode &node = builder.node();
bool clamp = node.custom2 & SHD_MIXRGB_CLAMP;
int mix_type = node.custom1;
builder.construct_and_set_matching_fn<MixRGBFunction>(clamp, mix_type);
@@ -191,7 +191,7 @@ void register_node_type_sh_mix_rgb(void)
node_type_label(&ntype, node_blend_label);
node_type_exec(&ntype, nullptr, nullptr, node_shader_exec_mix_rgb);
node_type_gpu(&ntype, gpu_shader_mix_rgb);
ntype.expand_in_mf_network = sh_node_mix_rgb_expand_in_mf_network;
ntype.build_multi_function = sh_node_mix_rgb_build_multi_function;
nodeRegisterType(&ntype);
}

View File

@@ -96,7 +96,7 @@ class SeparateRGBFunction : public blender::fn::MultiFunction {
}
};
static void sh_node_seprgb_expand_in_mf_network(blender::nodes::NodeMFNetworkBuilder &builder)
static void sh_node_seprgb_build_multi_function(blender::nodes::NodeMultiFunctionBuilder &builder)
{
static SeparateRGBFunction fn;
builder.set_matching_fn(fn);
@@ -110,7 +110,7 @@ void register_node_type_sh_seprgb(void)
node_type_socket_templates(&ntype, sh_node_seprgb_in, sh_node_seprgb_out);
node_type_exec(&ntype, nullptr, nullptr, node_shader_exec_seprgb);
node_type_gpu(&ntype, gpu_shader_seprgb);
ntype.expand_in_mf_network = sh_node_seprgb_expand_in_mf_network;
ntype.build_multi_function = sh_node_seprgb_build_multi_function;
nodeRegisterType(&ntype);
}
@@ -153,7 +153,7 @@ static int gpu_shader_combrgb(GPUMaterial *mat,
return GPU_stack_link(mat, node, "combine_rgb", in, out);
}
static void sh_node_combrgb_expand_in_mf_network(blender::nodes::NodeMFNetworkBuilder &builder)
static void sh_node_combrgb_build_multi_function(blender::nodes::NodeMultiFunctionBuilder &builder)
{
static blender::fn::CustomMF_SI_SI_SI_SO<float, float, float, blender::ColorGeometry4f> fn{
"Combine RGB",
@@ -169,7 +169,7 @@ void register_node_type_sh_combrgb(void)
node_type_socket_templates(&ntype, sh_node_combrgb_in, sh_node_combrgb_out);
node_type_exec(&ntype, nullptr, nullptr, node_shader_exec_combrgb);
node_type_gpu(&ntype, gpu_shader_combrgb);
ntype.expand_in_mf_network = sh_node_combrgb_expand_in_mf_network;
ntype.build_multi_function = sh_node_combrgb_build_multi_function;
nodeRegisterType(&ntype);
}

Some files were not shown because too many files have changed in this diff Show More