Compare commits
99 Commits
temp-geome
...
temp-multi
Author | SHA1 | Date | |
---|---|---|---|
eed93aaa07 | |||
fc0bb6cdee | |||
10f2ad1556 | |||
6d77b87b13 | |||
2101b46802 | |||
34f6765630 | |||
721fad37a1 | |||
67c29bc5a2 | |||
2ea66af742 | |||
2b170f16d6 | |||
34a05f39be | |||
0b7947e855 | |||
47e68537f8 | |||
c671bfe14e | |||
aed5a27755 | |||
0d7aab2375 | |||
b6a1bf757d | |||
86622467c5 | |||
![]() |
cd8d9383e7 | ||
1b5f17b867 | |||
6a404bc633 | |||
9bfc47c933 | |||
c58d1acba8 | |||
accf3045be | |||
ef502127dd | |||
0ee79f304e | |||
e642de3d6f | |||
0081200812 | |||
eca5a8b695 | |||
79c79f3c70 | |||
a9970d3cb9 | |||
b812f289f5 | |||
215ce0fb57 | |||
5154598845 | |||
1ee80d792c | |||
95284d2f1e | |||
7281f3eb56 | |||
607ef8f6c5 | |||
1ce640cc0b | |||
7bed18fdb1 | |||
c827b50d40 | |||
3c7e3c8e44 | |||
98e38ce4f3 | |||
132cf268c0 | |||
fd7edc9b05 | |||
ecf7c90840 | |||
d78a530af1 | |||
3ebe61db9f | |||
86c2f139c6 | |||
3596c348eb | |||
41a81474e4 | |||
aa2822d137 | |||
55b333d3e3 | |||
00cfad8578 | |||
1891c956e5 | |||
249c050757 | |||
a0081046b6 | |||
635f73b7f1 | |||
74fcd50e2f | |||
b04a2a7be7 | |||
083671e8ac | |||
78ea401e19 | |||
f3ca987bce | |||
2245add9f8 | |||
31004d7fac | |||
3d3f66ed41 | |||
c9c0195da5 | |||
70c0403858 | |||
8d4de82c7f | |||
22c51c2d51 | |||
158bd7c6a0 | |||
4a28d0b583 | |||
0501e6e693 | |||
8450ac09c1 | |||
1af00015e8 | |||
b44c3a3125 | |||
d729f1ca37 | |||
0fc9f00c14 | |||
6c9b339af7 | |||
b7a976af01 | |||
a689037917 | |||
313403c1f1 | |||
60409b8823 | |||
773dc2ec94 | |||
2a98c5d06b | |||
6d1b4ce3c6 | |||
0b2d961b70 | |||
d553b70470 | |||
6954f2cdd7 | |||
8cc832110a | |||
7b8c54b5a1 | |||
e850d175b5 | |||
326f79d59b | |||
ec4954ece2 | |||
b30e782c82 | |||
e34fe5d28e | |||
8581a062f1 | |||
b43971e5e9 | |||
855382170e |
@@ -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)
|
||||
|
66
build_files/cmake/Modules/FindZstd.cmake
Normal file
66
build_files/cmake/Modules/FindZstd.cmake
Normal 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
|
||||
)
|
@@ -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()
|
||||
|
@@ -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)
|
||||
|
@@ -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)
|
||||
|
@@ -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
|
||||
|
@@ -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,
|
||||
|
@@ -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());
|
||||
|
||||
|
@@ -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
|
||||
|
@@ -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
|
||||
|
@@ -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;
|
||||
|
@@ -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
|
||||
*/
|
||||
|
@@ -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) {
|
||||
|
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -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);
|
||||
}
|
||||
|
||||
/** \} */
|
||||
|
@@ -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;
|
||||
|
@@ -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);
|
||||
|
@@ -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. */
|
||||
|
@@ -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);
|
||||
|
@@ -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;
|
||||
|
@@ -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)
|
||||
|
@@ -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"
|
||||
|
||||
|
@@ -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();
|
||||
|
||||
|
81
source/blender/blenlib/BLI_filereader.h
Normal file
81
source/blender/blenlib/BLI_filereader.h
Normal 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
|
@@ -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)
|
||||
|
@@ -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.
|
||||
|
80
source/blender/blenlib/intern/filereader_file.c
Normal file
80
source/blender/blenlib/intern/filereader_file.c
Normal 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;
|
||||
}
|
108
source/blender/blenlib/intern/filereader_gzip.c
Normal file
108
source/blender/blenlib/intern/filereader_gzip.c
Normal 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;
|
||||
}
|
145
source/blender/blenlib/intern/filereader_memory.c
Normal file
145
source/blender/blenlib/intern/filereader_memory.c
Normal 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;
|
||||
}
|
335
source/blender/blenlib/intern/filereader_zstd.c
Normal file
335
source/blender/blenlib/intern/filereader_zstd.c
Normal 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;
|
||||
}
|
@@ -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);
|
||||
|
@@ -42,7 +42,7 @@ set(INC
|
||||
)
|
||||
|
||||
set(INC_SYS
|
||||
${ZLIB_INCLUDE_DIRS}
|
||||
${ZSTD_INCLUDE_DIRS}
|
||||
)
|
||||
|
||||
set(SRC
|
||||
|
@@ -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)
|
||||
|
@@ -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];
|
||||
|
||||
|
@@ -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;
|
||||
}
|
||||
|
@@ -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 */
|
||||
|
@@ -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 */
|
||||
|
@@ -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(
|
||||
|
@@ -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 {
|
||||
|
@@ -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 */
|
||||
|
@@ -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) {
|
||||
|
@@ -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));
|
||||
}
|
||||
|
@@ -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);
|
||||
|
@@ -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);
|
||||
|
@@ -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;
|
||||
|
||||
|
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
/** \} */
|
||||
|
@@ -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,
|
||||
|
@@ -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++;
|
||||
|
@@ -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);
|
||||
}
|
||||
|
@@ -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)) {
|
||||
|
@@ -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)
|
||||
{
|
||||
|
@@ -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
|
||||
|
@@ -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);
|
||||
|
@@ -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
|
@@ -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
|
@@ -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
|
408
source/blender/functions/FN_multi_function_procedure.hh
Normal file
408
source/blender/functions/FN_multi_function_procedure.hh
Normal 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
|
251
source/blender/functions/FN_multi_function_procedure_builder.hh
Normal file
251
source/blender/functions/FN_multi_function_procedure_builder.hh
Normal 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
|
@@ -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
|
@@ -160,6 +160,21 @@ class MFSignatureBuilder {
|
||||
}
|
||||
}
|
||||
|
||||
void add(StringRef name, const MFParamType ¶m_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
|
||||
|
@@ -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
|
File diff suppressed because it is too large
Load Diff
@@ -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 ¶ms,
|
||||
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 ¶ms,
|
||||
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
|
743
source/blender/functions/intern/multi_function_procedure.cc
Normal file
743
source/blender/functions/intern/multi_function_procedure.cc
Normal 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 ¶m : 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 ¶m : 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 ¶m : 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
|
@@ -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
|
1188
source/blender/functions/intern/multi_function_procedure_executor.cc
Normal file
1188
source/blender/functions/intern/multi_function_procedure_executor.cc
Normal file
File diff suppressed because it is too large
Load Diff
@@ -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
|
@@ -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
|
@@ -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>());
|
||||
|
174
source/blender/functions/tests/FN_multi_function_test_common.hh
Normal file
174
source/blender/functions/tests/FN_multi_function_test_common.hh
Normal 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
|
@@ -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.
|
||||
|
@@ -40,6 +40,8 @@
|
||||
.handle = NULL, \
|
||||
.handle_filepath[0] = '\0', \
|
||||
.handle_readers = NULL, \
|
||||
.use_prefetch = 1, \
|
||||
.prefetch_cache_size = 4096, \
|
||||
}
|
||||
|
||||
/** \} */
|
||||
|
@@ -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. */
|
||||
|
@@ -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);
|
||||
|
@@ -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;
|
||||
|
||||
|
@@ -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;
|
||||
|
@@ -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;
|
||||
|
@@ -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;
|
||||
|
@@ -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
|
||||
|
130
source/blender/nodes/NOD_multi_function.hh
Normal file
130
source/blender/nodes/NOD_multi_function.hh
Normal 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
|
@@ -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
|
@@ -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"
|
||||
|
||||
|
@@ -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 ¬_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);
|
||||
}
|
||||
|
@@ -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 ¬_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);
|
||||
}
|
||||
|
@@ -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);
|
||||
}
|
||||
|
@@ -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);
|
||||
}
|
||||
|
@@ -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);
|
||||
}
|
||||
|
@@ -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);
|
||||
}
|
||||
|
@@ -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"
|
||||
|
40
source/blender/nodes/intern/node_multi_function.cc
Normal file
40
source/blender/nodes/intern/node_multi_function.cc
Normal 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
|
@@ -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"
|
||||
|
@@ -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
|
@@ -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
|
@@ -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"
|
||||
|
@@ -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);
|
||||
}
|
||||
|
@@ -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);
|
||||
}
|
||||
|
@@ -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);
|
||||
}
|
||||
|
@@ -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);
|
||||
}
|
||||
|
@@ -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);
|
||||
}
|
||||
|
@@ -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
Reference in New Issue
Block a user