Simulation nodes: UI for simulation state items in sidebar #106919
18
GNUmakefile
18
GNUmakefile
|
@ -58,9 +58,6 @@ Static Source Code Checking
|
|||
* check_cppcheck: Run blender source through cppcheck (C & C++).
|
||||
* check_clang_array: Run blender source through clang array checking script (C & C++).
|
||||
* check_deprecated: Check if there is any deprecated code to remove.
|
||||
* check_splint: Run blenders source through splint (C only).
|
||||
* check_sparse: Run blenders source through sparse (C only).
|
||||
* check_smatch: Run blenders source through smatch (C only).
|
||||
* check_descriptions: Check for duplicate/invalid descriptions.
|
||||
* check_licenses: Check license headers follow the SPDX license specification,
|
||||
using one of the accepted licenses in 'doc/license/SPDX-license-identifiers.txt'
|
||||
|
@ -474,21 +471,6 @@ check_clang_array: .FORCE
|
|||
@cd "$(BUILD_DIR)" ; \
|
||||
$(PYTHON) "$(BLENDER_DIR)/build_files/cmake/cmake_static_check_clang_array.py"
|
||||
|
||||
check_splint: .FORCE
|
||||
@$(CMAKE_CONFIG)
|
||||
@cd "$(BUILD_DIR)" ; \
|
||||
$(PYTHON) "$(BLENDER_DIR)/build_files/cmake/cmake_static_check_splint.py"
|
||||
|
||||
check_sparse: .FORCE
|
||||
@$(CMAKE_CONFIG)
|
||||
@cd "$(BUILD_DIR)" ; \
|
||||
$(PYTHON) "$(BLENDER_DIR)/build_files/cmake/cmake_static_check_sparse.py"
|
||||
|
||||
check_smatch: .FORCE
|
||||
@$(CMAKE_CONFIG)
|
||||
@cd "$(BUILD_DIR)" ; \
|
||||
$(PYTHON) "$(BLENDER_DIR)/build_files/cmake/cmake_static_check_smatch.py"
|
||||
|
||||
check_mypy: .FORCE
|
||||
@$(PYTHON) "$(BLENDER_DIR)/tools/check_source/check_mypy.py"
|
||||
|
||||
|
|
|
@ -1,58 +0,0 @@
|
|||
#!/usr/bin/env python3
|
||||
# SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
CHECKER_IGNORE_PREFIX = [
|
||||
"extern",
|
||||
"intern/moto",
|
||||
]
|
||||
|
||||
CHECKER_BIN = "smatch"
|
||||
CHECKER_ARGS = [
|
||||
"--full-path",
|
||||
"--two-passes",
|
||||
]
|
||||
|
||||
import project_source_info
|
||||
import subprocess
|
||||
import sys
|
||||
import os
|
||||
|
||||
USE_QUIET = (os.environ.get("QUIET", None) is not None)
|
||||
|
||||
|
||||
def main():
|
||||
source_info = project_source_info.build_info(use_cxx=False, ignore_prefix_list=CHECKER_IGNORE_PREFIX)
|
||||
source_defines = project_source_info.build_defines_as_args()
|
||||
|
||||
check_commands = []
|
||||
for c, inc_dirs, defs in source_info:
|
||||
|
||||
cmd = ([CHECKER_BIN] +
|
||||
CHECKER_ARGS +
|
||||
[c] +
|
||||
[("-I%s" % i) for i in inc_dirs] +
|
||||
[("-D%s" % d) for d in defs] +
|
||||
source_defines
|
||||
)
|
||||
|
||||
check_commands.append((c, cmd))
|
||||
|
||||
def my_process(i, c, cmd):
|
||||
if not USE_QUIET:
|
||||
percent = 100.0 * (i / len(check_commands))
|
||||
percent_str = "[" + ("%.2f]" % percent).rjust(7) + " %:"
|
||||
|
||||
sys.stdout.flush()
|
||||
sys.stdout.write("%s %s\n" % (percent_str, c))
|
||||
|
||||
return subprocess.Popen(cmd)
|
||||
|
||||
process_functions = []
|
||||
for i, (c, cmd) in enumerate(check_commands):
|
||||
process_functions.append((my_process, (i, c, cmd)))
|
||||
|
||||
project_source_info.queue_processes(process_functions)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
|
@ -1,56 +0,0 @@
|
|||
#!/usr/bin/env python3
|
||||
# SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
CHECKER_IGNORE_PREFIX = [
|
||||
"extern",
|
||||
"intern/moto",
|
||||
]
|
||||
|
||||
CHECKER_BIN = "sparse"
|
||||
CHECKER_ARGS = [
|
||||
]
|
||||
|
||||
import project_source_info
|
||||
import subprocess
|
||||
import sys
|
||||
import os
|
||||
|
||||
USE_QUIET = (os.environ.get("QUIET", None) is not None)
|
||||
|
||||
|
||||
def main():
|
||||
source_info = project_source_info.build_info(use_cxx=False, ignore_prefix_list=CHECKER_IGNORE_PREFIX)
|
||||
source_defines = project_source_info.build_defines_as_args()
|
||||
|
||||
check_commands = []
|
||||
for c, inc_dirs, defs in source_info:
|
||||
|
||||
cmd = ([CHECKER_BIN] +
|
||||
CHECKER_ARGS +
|
||||
[c] +
|
||||
[("-I%s" % i) for i in inc_dirs] +
|
||||
[("-D%s" % d) for d in defs] +
|
||||
source_defines
|
||||
)
|
||||
|
||||
check_commands.append((c, cmd))
|
||||
|
||||
def my_process(i, c, cmd):
|
||||
if not USE_QUIET:
|
||||
percent = 100.0 * (i / len(check_commands))
|
||||
percent_str = "[" + ("%.2f]" % percent).rjust(7) + " %:"
|
||||
|
||||
sys.stdout.flush()
|
||||
sys.stdout.write("%s %s\n" % (percent_str, c))
|
||||
|
||||
return subprocess.Popen(cmd)
|
||||
|
||||
process_functions = []
|
||||
for i, (c, cmd) in enumerate(check_commands):
|
||||
process_functions.append((my_process, (i, c, cmd)))
|
||||
|
||||
project_source_info.queue_processes(process_functions)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
|
@ -1,86 +0,0 @@
|
|||
#!/usr/bin/env python3
|
||||
# SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
CHECKER_IGNORE_PREFIX = [
|
||||
"extern",
|
||||
"intern/moto",
|
||||
]
|
||||
|
||||
CHECKER_BIN = "splint"
|
||||
|
||||
CHECKER_ARGS = [
|
||||
"-weak",
|
||||
"-posix-lib",
|
||||
"-linelen", "10000",
|
||||
"+ignorequals",
|
||||
"+relaxtypes",
|
||||
"-retvalother",
|
||||
"+matchanyintegral",
|
||||
"+longintegral",
|
||||
"+ignoresigns",
|
||||
"-nestcomment",
|
||||
"-predboolothers",
|
||||
"-ifempty",
|
||||
"-unrecogcomments",
|
||||
|
||||
# we may want to remove these later
|
||||
"-type",
|
||||
"-fixedformalarray",
|
||||
"-fullinitblock",
|
||||
"-fcnuse",
|
||||
"-initallelements",
|
||||
"-castfcnptr",
|
||||
# -forcehints,
|
||||
"-bufferoverflowhigh", # warns a lot about sprintf()
|
||||
|
||||
# re-definitions, rna causes most of these
|
||||
"-redef",
|
||||
"-syntax",
|
||||
|
||||
# dummy, witjout this splint complains with:
|
||||
# /usr/include/bits/confname.h:31:27: *** Internal Bug at cscannerHelp.c:2428: Unexpanded macro not function or constant: int _PC_MAX_CANON
|
||||
"-D_PC_MAX_CANON=0",
|
||||
]
|
||||
|
||||
|
||||
import project_source_info
|
||||
import subprocess
|
||||
import sys
|
||||
import os
|
||||
|
||||
USE_QUIET = (os.environ.get("QUIET", None) is not None)
|
||||
|
||||
|
||||
def main():
|
||||
source_info = project_source_info.build_info(use_cxx=False, ignore_prefix_list=CHECKER_IGNORE_PREFIX)
|
||||
|
||||
check_commands = []
|
||||
for c, inc_dirs, defs in source_info:
|
||||
cmd = ([CHECKER_BIN] +
|
||||
CHECKER_ARGS +
|
||||
[c] +
|
||||
[("-I%s" % i) for i in inc_dirs] +
|
||||
[("-D%s" % d) for d in defs]
|
||||
)
|
||||
|
||||
check_commands.append((c, cmd))
|
||||
|
||||
def my_process(i, c, cmd):
|
||||
if not USE_QUIET:
|
||||
percent = 100.0 * (i / len(check_commands))
|
||||
percent_str = "[" + ("%.2f]" % percent).rjust(7) + " %:"
|
||||
|
||||
sys.stdout.write("%s %s\n" % (percent_str, c))
|
||||
sys.stdout.flush()
|
||||
|
||||
return subprocess.Popen(cmd)
|
||||
|
||||
process_functions = []
|
||||
for i, (c, cmd) in enumerate(check_commands):
|
||||
process_functions.append((my_process, (i, c, cmd)))
|
||||
|
||||
project_source_info.queue_processes(process_functions)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
|
@ -174,7 +174,7 @@ if(SYSTEMSTUBS_LIBRARY)
|
|||
list(APPEND PLATFORM_LINKLIBS SystemStubs)
|
||||
endif()
|
||||
|
||||
string(APPEND PLATFORM_CFLAGS " -pipe -funsigned-char -fno-strict-aliasing")
|
||||
string(APPEND PLATFORM_CFLAGS " -pipe -funsigned-char -fno-strict-aliasing -ffp-contract=off")
|
||||
set(PLATFORM_LINKFLAGS
|
||||
"-fexceptions -framework CoreServices -framework Foundation -framework IOKit -framework AppKit -framework Cocoa -framework Carbon -framework AudioUnit -framework AudioToolbox -framework CoreAudio -framework Metal -framework QuartzCore"
|
||||
)
|
||||
|
|
|
@ -803,8 +803,7 @@ if(CMAKE_COMPILER_IS_GNUCC)
|
|||
# Automatically turned on when building with "-march=native". This is
|
||||
# explicitly turned off here as it will make floating point math give a bit
|
||||
# different results. This will lead to automated test failures. So disable
|
||||
# this until we support it. Seems to default to off in clang and the intel
|
||||
# compiler.
|
||||
# this until we support it.
|
||||
set(PLATFORM_CFLAGS "-pipe -fPIC -funsigned-char -fno-strict-aliasing -ffp-contract=off")
|
||||
|
||||
# `maybe-uninitialized` is unreliable in release builds, but fine in debug builds.
|
||||
|
@ -892,7 +891,7 @@ if(CMAKE_COMPILER_IS_GNUCC)
|
|||
|
||||
# CLang is the same as GCC for now.
|
||||
elseif(CMAKE_C_COMPILER_ID MATCHES "Clang")
|
||||
set(PLATFORM_CFLAGS "-pipe -fPIC -funsigned-char -fno-strict-aliasing")
|
||||
set(PLATFORM_CFLAGS "-pipe -fPIC -funsigned-char -fno-strict-aliasing -ffp-contract=off")
|
||||
|
||||
if(WITH_LINKER_MOLD AND _IS_LINKER_DEFAULT)
|
||||
find_program(MOLD_BIN "mold")
|
||||
|
|
|
@ -865,29 +865,40 @@ Unfortunate Corner Cases
|
|||
Besides all expected cases listed above, there are a few others that should not be
|
||||
an issue but, due to internal implementation details, currently are:
|
||||
|
||||
- ``Object.hide_viewport``, ``Object.hide_select`` and ``Object.hide_render``:
|
||||
Setting any of those Booleans will trigger a rebuild of Collection caches,
|
||||
thus breaking any current iteration over ``Collection.all_objects``.
|
||||
|
||||
Collection Objects
|
||||
^^^^^^^^^^^^^^^^^^
|
||||
|
||||
Changing: ``Object.hide_viewport``, ``Object.hide_select`` or ``Object.hide_render``
|
||||
will trigger a rebuild of Collection caches, thus breaking any current iteration over ``Collection.all_objects``.
|
||||
|
||||
.. rubric:: Do not:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
# `all_objects` is an iterator. Using it directly while performing operations on its members that will update
|
||||
# the memory accessed by the `all_objects` iterator will lead to invalid memory accesses and crashes.
|
||||
for object in bpy.data.collections["Collection"].all_objects:
|
||||
object.hide_viewport = True
|
||||
|
||||
|
||||
.. rubric:: Do not:
|
||||
.. rubric:: Do:
|
||||
|
||||
.. code-block:: python
|
||||
.. code-block:: python
|
||||
|
||||
# `all_objects` is an iterator. Using it directly while performing operations on its members that will update
|
||||
# the memory accessed by the `all_objects` iterator will lead to invalid memory accesses and crashes.
|
||||
for object in bpy.data.collections["Collection"].all_objects:
|
||||
object.hide_viewport = True
|
||||
# `all_objects[:]` is an independent list generated from the iterator. As long as no objects are deleted,
|
||||
# its content will remain valid even if the data accessed by the `all_objects` iterator is modified.
|
||||
for object in bpy.data.collections["Collection"].all_objects[:]:
|
||||
object.hide_viewport = True
|
||||
|
||||
|
||||
.. rubric:: Do:
|
||||
Data-Blocks Renaming During Iteration
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
# `all_objects[:]` is an independent list generated from the iterator. As long as no objects are deleted,
|
||||
# its content will remain valid even if the data accessed by the `all_objects` iterator is modified.
|
||||
for object in bpy.data.collections["Collection"].all_objects[:]:
|
||||
object.hide_viewport = True
|
||||
Data-blocks accessed from ``bpy.data`` are sorted when their name is set.
|
||||
Any loop that iterates of a data such as ``bpy.data.objects`` for example,
|
||||
and sets the objects ``name`` must get all items from the iterator first (typically by converting to a list or tuple)
|
||||
to avoid missing some objects and iterating over others multiple times.
|
||||
|
||||
|
||||
sys.exit
|
||||
|
|
|
@ -111,9 +111,13 @@ BVHEmbree::~BVHEmbree()
|
|||
}
|
||||
}
|
||||
|
||||
void BVHEmbree::build(Progress &progress, Stats *stats, RTCDevice rtc_device_)
|
||||
void BVHEmbree::build(Progress &progress,
|
||||
Stats *stats,
|
||||
RTCDevice rtc_device_,
|
||||
const bool rtc_device_is_sycl_)
|
||||
{
|
||||
rtc_device = rtc_device_;
|
||||
rtc_device_is_sycl = rtc_device_is_sycl_;
|
||||
assert(rtc_device);
|
||||
|
||||
rtcSetDeviceErrorFunction(rtc_device, rtc_error_func, NULL);
|
||||
|
@ -266,15 +270,29 @@ void BVHEmbree::add_triangles(const Object *ob, const Mesh *mesh, int i)
|
|||
rtcSetGeometryTimeStepCount(geom_id, num_motion_steps);
|
||||
|
||||
const int *triangles = mesh->get_triangles().data();
|
||||
rtcSetSharedGeometryBuffer(geom_id,
|
||||
RTC_BUFFER_TYPE_INDEX,
|
||||
0,
|
||||
RTC_FORMAT_UINT3,
|
||||
triangles,
|
||||
0,
|
||||
sizeof(int) * 3,
|
||||
num_triangles);
|
||||
|
||||
if (!rtc_device_is_sycl) {
|
||||
rtcSetSharedGeometryBuffer(geom_id,
|
||||
RTC_BUFFER_TYPE_INDEX,
|
||||
0,
|
||||
RTC_FORMAT_UINT3,
|
||||
triangles,
|
||||
0,
|
||||
sizeof(int) * 3,
|
||||
num_triangles);
|
||||
}
|
||||
else {
|
||||
/* NOTE(sirgienko): If the Embree device is a SYCL device, then Embree execution will
|
||||
* happen on GPU, and we cannot use standard host pointers at this point. So instead
|
||||
* of making a shared geometry buffer - a new Embree buffer will be created and data
|
||||
* will be copied. */
|
||||
int *triangles_buffer = (int *)rtcSetNewGeometryBuffer(
|
||||
geom_id, RTC_BUFFER_TYPE_INDEX, 0, RTC_FORMAT_UINT3, sizeof(int) * 3, num_triangles);
|
||||
assert(triangles_buffer);
|
||||
if (triangles_buffer) {
|
||||
static_assert(sizeof(int) == sizeof(uint));
|
||||
std::memcpy(triangles_buffer, triangles, sizeof(int) * 3 * (num_triangles));
|
||||
}
|
||||
}
|
||||
set_tri_vertex_buffer(geom_id, mesh, false);
|
||||
|
||||
rtcSetGeometryUserData(geom_id, (void *)prim_offset);
|
||||
|
@ -323,14 +341,38 @@ void BVHEmbree::set_tri_vertex_buffer(RTCGeometry geom_id, const Mesh *mesh, con
|
|||
rtcUpdateGeometryBuffer(geom_id, RTC_BUFFER_TYPE_VERTEX, t);
|
||||
}
|
||||
else {
|
||||
rtcSetSharedGeometryBuffer(geom_id,
|
||||
RTC_BUFFER_TYPE_VERTEX,
|
||||
t,
|
||||
RTC_FORMAT_FLOAT3,
|
||||
verts,
|
||||
0,
|
||||
sizeof(float3),
|
||||
num_verts + 1);
|
||||
if (!rtc_device_is_sycl) {
|
||||
rtcSetSharedGeometryBuffer(geom_id,
|
||||
RTC_BUFFER_TYPE_VERTEX,
|
||||
t,
|
||||
RTC_FORMAT_FLOAT3,
|
||||
verts,
|
||||
0,
|
||||
sizeof(float3),
|
||||
num_verts + 1);
|
||||
}
|
||||
else {
|
||||
/* NOTE(sirgienko): If the Embree device is a SYCL device, then Embree execution will
|
||||
* happen on GPU, and we cannot use standard host pointers at this point. So instead
|
||||
* of making a shared geometry buffer - a new Embree buffer will be created and data
|
||||
* will be copied. */
|
||||
/* As float3 is packed on GPU side, we map it to packed_float3. */
|
||||
packed_float3 *verts_buffer = (packed_float3 *)rtcSetNewGeometryBuffer(
|
||||
geom_id,
|
||||
RTC_BUFFER_TYPE_VERTEX,
|
||||
t,
|
||||
RTC_FORMAT_FLOAT3,
|
||||
sizeof(packed_float3),
|
||||
num_verts + 1);
|
||||
assert(verts_buffer);
|
||||
if (verts_buffer) {
|
||||
for (size_t i = (size_t)0; i < num_verts + 1; ++i) {
|
||||
verts_buffer[i].x = verts[i].x;
|
||||
verts_buffer[i].y = verts[i].y;
|
||||
verts_buffer[i].z = verts[i].z;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -29,7 +29,10 @@ class PointCloud;
|
|||
|
||||
class BVHEmbree : public BVH {
|
||||
public:
|
||||
void build(Progress &progress, Stats *stats, RTCDevice rtc_device);
|
||||
void build(Progress &progress,
|
||||
Stats *stats,
|
||||
RTCDevice rtc_device,
|
||||
const bool isSyclEmbreeDevice = false);
|
||||
void refit(Progress &progress);
|
||||
|
||||
RTCScene scene;
|
||||
|
@ -55,6 +58,7 @@ class BVHEmbree : public BVH {
|
|||
const bool update);
|
||||
|
||||
RTCDevice rtc_device;
|
||||
bool rtc_device_is_sycl;
|
||||
enum RTCBuildQuality build_quality;
|
||||
};
|
||||
|
||||
|
|
|
@ -143,7 +143,7 @@ void OneapiDevice::build_bvh(BVH *bvh, Progress &progress, bool refit)
|
|||
bvh_embree->refit(progress);
|
||||
}
|
||||
else {
|
||||
bvh_embree->build(progress, &stats, embree_device);
|
||||
bvh_embree->build(progress, &stats, embree_device, true);
|
||||
}
|
||||
if (bvh->params.top_level) {
|
||||
embree_scene = bvh_embree->scene;
|
||||
|
@ -866,9 +866,9 @@ char *OneapiDevice::device_capabilities()
|
|||
|
||||
sycl::id<3> max_work_item_sizes =
|
||||
device.get_info<sycl::info::device::max_work_item_sizes<3>>();
|
||||
WRITE_ATTR("max_work_item_sizes_dim0", ((size_t)max_work_item_sizes.get(0)))
|
||||
WRITE_ATTR("max_work_item_sizes_dim1", ((size_t)max_work_item_sizes.get(1)))
|
||||
WRITE_ATTR("max_work_item_sizes_dim2", ((size_t)max_work_item_sizes.get(2)))
|
||||
WRITE_ATTR(max_work_item_sizes_dim0, ((size_t)max_work_item_sizes.get(0)))
|
||||
WRITE_ATTR(max_work_item_sizes_dim1, ((size_t)max_work_item_sizes.get(1)))
|
||||
WRITE_ATTR(max_work_item_sizes_dim2, ((size_t)max_work_item_sizes.get(2)))
|
||||
|
||||
GET_NUM_ATTR(max_work_group_size)
|
||||
GET_NUM_ATTR(max_num_sub_groups)
|
||||
|
@ -891,7 +891,7 @@ char *OneapiDevice::device_capabilities()
|
|||
GET_NUM_ATTR(native_vector_width_half)
|
||||
|
||||
size_t max_clock_frequency = device.get_info<sycl::info::device::max_clock_frequency>();
|
||||
WRITE_ATTR("max_clock_frequency", max_clock_frequency)
|
||||
WRITE_ATTR(max_clock_frequency, max_clock_frequency)
|
||||
|
||||
GET_NUM_ATTR(address_bits)
|
||||
GET_NUM_ATTR(max_mem_alloc_size)
|
||||
|
@ -900,7 +900,7 @@ char *OneapiDevice::device_capabilities()
|
|||
* supported so we always return false, even if device supports HW texture usage acceleration.
|
||||
*/
|
||||
bool image_support = false;
|
||||
WRITE_ATTR("image_support", (size_t)image_support)
|
||||
WRITE_ATTR(image_support, (size_t)image_support)
|
||||
|
||||
GET_NUM_ATTR(max_parameter_size)
|
||||
GET_NUM_ATTR(mem_base_addr_align)
|
||||
|
|
|
@ -778,8 +778,6 @@ if(WITH_CYCLES_DEVICE_ONEAPI)
|
|||
|
||||
# Host execution won't use GPU binaries, no need to compile them.
|
||||
if(WITH_CYCLES_ONEAPI_BINARIES AND NOT WITH_CYCLES_ONEAPI_HOST_TASK_EXECUTION)
|
||||
# AoT binaries aren't currently reused when calling sycl::build.
|
||||
list(APPEND sycl_compiler_flags -DWITH_CYCLES_ONEAPI_BINARIES)
|
||||
# Iterate over all targest and their options
|
||||
list(JOIN CYCLES_ONEAPI_SYCL_TARGETS "," targets_string)
|
||||
list(APPEND sycl_compiler_flags -fsycl-targets=${targets_string})
|
||||
|
|
|
@ -289,6 +289,15 @@ ccl_device_intersect bool scene_intersect_volume(KernelGlobals kg,
|
|||
return false;
|
||||
}
|
||||
|
||||
# ifdef __EMBREE__
|
||||
IF_USING_EMBREE
|
||||
{
|
||||
if (kernel_data.device_bvh) {
|
||||
return kernel_embree_intersect_volume(kg, ray, isect, visibility);
|
||||
}
|
||||
}
|
||||
# endif
|
||||
|
||||
IF_NOT_USING_EMBREE
|
||||
{
|
||||
# ifdef __OBJECT_MOTION__
|
||||
|
|
|
@ -109,7 +109,9 @@ struct CCLVolumeContext
|
|||
#if EMBREE_MAJOR_VERSION >= 4
|
||||
KernelGlobals kg;
|
||||
const Ray *ray;
|
||||
# ifdef __VOLUME_RECORD_ALL__
|
||||
numhit_t max_hits;
|
||||
# endif
|
||||
numhit_t num_hits;
|
||||
#endif
|
||||
Intersection *vol_isect;
|
||||
|
@ -505,8 +507,10 @@ ccl_device_forceinline void kernel_embree_filter_occluded_volume_all_func_impl(
|
|||
#endif
|
||||
const Ray *cray = ctx->ray;
|
||||
|
||||
#ifdef __VOLUME_RECORD_ALL__
|
||||
/* Append the intersection to the end of the array. */
|
||||
if (ctx->num_hits < ctx->max_hits) {
|
||||
#endif
|
||||
Intersection current_isect;
|
||||
kernel_embree_convert_hit(
|
||||
kg, ray, hit, ¤t_isect, reinterpret_cast<intptr_t>(args->geometryUserPtr));
|
||||
|
@ -523,10 +527,17 @@ ccl_device_forceinline void kernel_embree_filter_occluded_volume_all_func_impl(
|
|||
int object_flag = kernel_data_fetch(object_flag, tri_object);
|
||||
if ((object_flag & SD_OBJECT_HAS_VOLUME) == 0) {
|
||||
--ctx->num_hits;
|
||||
#ifndef __VOLUME_RECORD_ALL__
|
||||
/* Without __VOLUME_RECORD_ALL__ we need only a first counted hit, so we will
|
||||
* continue tracing only if a current hit is not counted. */
|
||||
*args->valid = 0;
|
||||
#endif
|
||||
}
|
||||
#ifdef __VOLUME_RECORD_ALL__
|
||||
/* This tells Embree to continue tracing. */
|
||||
*args->valid = 0;
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
#if EMBREE_MAJOR_VERSION < 4
|
||||
|
@ -844,7 +855,9 @@ ccl_device_intersect bool kernel_embree_intersect_shadow_all(KernelGlobals kg,
|
|||
ccl_device_intersect uint kernel_embree_intersect_volume(KernelGlobals kg,
|
||||
ccl_private const Ray *ray,
|
||||
ccl_private Intersection *isect,
|
||||
# ifdef __VOLUME_RECORD_ALL__
|
||||
const uint max_hits,
|
||||
# endif
|
||||
const uint visibility)
|
||||
{
|
||||
# if EMBREE_MAJOR_VERSION >= 4
|
||||
|
@ -864,7 +877,9 @@ ccl_device_intersect uint kernel_embree_intersect_volume(KernelGlobals kg,
|
|||
rtcInitIntersectContext(&ctx);
|
||||
# endif
|
||||
ctx.vol_isect = isect;
|
||||
# ifdef __VOLUME_RECORD_ALL__
|
||||
ctx.max_hits = numhit_t(max_hits);
|
||||
# endif
|
||||
ctx.num_hits = numhit_t(0);
|
||||
ctx.ray = ray;
|
||||
RTCRay rtc_ray;
|
||||
|
|
|
@ -174,6 +174,14 @@ bool oneapi_kernel_is_required_for_features(const std::string &kernel_name,
|
|||
return true;
|
||||
}
|
||||
|
||||
bool oneapi_kernel_is_raytrace_or_mnee(const std::string &kernel_name)
|
||||
{
|
||||
return (kernel_name.find(device_kernel_as_string(DEVICE_KERNEL_INTEGRATOR_SHADE_SURFACE_MNEE)) !=
|
||||
std::string::npos) ||
|
||||
(kernel_name.find(device_kernel_as_string(
|
||||
DEVICE_KERNEL_INTEGRATOR_SHADE_SURFACE_RAYTRACE)) != std::string::npos);
|
||||
}
|
||||
|
||||
bool oneapi_kernel_is_using_embree(const std::string &kernel_name)
|
||||
{
|
||||
# ifdef WITH_EMBREE_GPU
|
||||
|
@ -182,8 +190,7 @@ bool oneapi_kernel_is_using_embree(const std::string &kernel_name)
|
|||
DeviceKernel kernel = (DeviceKernel)i;
|
||||
if (device_kernel_has_intersection(kernel)) {
|
||||
if (kernel_name.find(device_kernel_as_string(kernel)) != std::string::npos) {
|
||||
return !(kernel == DEVICE_KERNEL_INTEGRATOR_SHADE_SURFACE_RAYTRACE ||
|
||||
kernel == DEVICE_KERNEL_INTEGRATOR_SHADE_SURFACE_MNEE);
|
||||
return !oneapi_kernel_is_raytrace_or_mnee(kernel_name);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -241,10 +248,6 @@ bool oneapi_load_kernels(SyclQueue *queue_,
|
|||
}
|
||||
# endif
|
||||
|
||||
# ifdef WITH_CYCLES_ONEAPI_BINARIES
|
||||
(void)queue_;
|
||||
(void)kernel_features;
|
||||
# else
|
||||
try {
|
||||
sycl::kernel_bundle<sycl::bundle_state::input> all_kernels_bundle =
|
||||
sycl::get_kernel_bundle<sycl::bundle_state::input>(queue->get_context(),
|
||||
|
@ -260,18 +263,22 @@ bool oneapi_load_kernels(SyclQueue *queue_,
|
|||
continue;
|
||||
}
|
||||
|
||||
sycl::kernel_bundle<sycl::bundle_state::input> one_kernel_bundle_input =
|
||||
sycl::get_kernel_bundle<sycl::bundle_state::input>(queue->get_context(), {kernel_id});
|
||||
# ifdef WITH_EMBREE_GPU
|
||||
/* This is expected to be the default, we set it again to be sure. */
|
||||
if (one_kernel_bundle_input
|
||||
.has_specialization_constant<ONEAPIKernelContext::oneapi_embree_features>()) {
|
||||
# ifdef WITH_EMBREE_GPU
|
||||
if (oneapi_kernel_is_using_embree(kernel_name) ||
|
||||
oneapi_kernel_is_raytrace_or_mnee(kernel_name)) {
|
||||
sycl::kernel_bundle<sycl::bundle_state::input> one_kernel_bundle_input =
|
||||
sycl::get_kernel_bundle<sycl::bundle_state::input>(queue->get_context(), {kernel_id});
|
||||
one_kernel_bundle_input
|
||||
.set_specialization_constant<ONEAPIKernelContext::oneapi_embree_features>(
|
||||
RTC_FEATURE_FLAG_NONE);
|
||||
sycl::build(one_kernel_bundle_input);
|
||||
continue;
|
||||
}
|
||||
# endif
|
||||
sycl::build(one_kernel_bundle_input);
|
||||
# endif
|
||||
/* This call will ensure that AoT or cached JIT binaries are available
|
||||
* for execution. It will trigger compilation if it is not already the case. */
|
||||
(void)sycl::get_kernel_bundle<sycl::bundle_state::executable>(queue->get_context(),
|
||||
{kernel_id});
|
||||
}
|
||||
}
|
||||
catch (sycl::exception const &e) {
|
||||
|
@ -280,7 +287,6 @@ bool oneapi_load_kernels(SyclQueue *queue_,
|
|||
}
|
||||
return false;
|
||||
}
|
||||
# endif
|
||||
return true;
|
||||
}
|
||||
|
||||
|
|
|
@ -15,8 +15,12 @@ set(SRC
|
|||
camera.cpp
|
||||
colorspace.cpp
|
||||
constant_fold.cpp
|
||||
devicescene.cpp
|
||||
film.cpp
|
||||
geometry.cpp
|
||||
geometry_attributes.cpp
|
||||
geometry_bvh.cpp
|
||||
geometry_mesh.cpp
|
||||
hair.cpp
|
||||
image.cpp
|
||||
image_oiio.cpp
|
||||
|
@ -55,6 +59,7 @@ set(SRC_HEADERS
|
|||
camera.h
|
||||
colorspace.h
|
||||
constant_fold.h
|
||||
devicescene.h
|
||||
film.h
|
||||
geometry.h
|
||||
hair.h
|
||||
|
|
|
@ -0,0 +1,64 @@
|
|||
/* SPDX-License-Identifier: Apache-2.0
|
||||
* Copyright 2011-2022 Blender Foundation */
|
||||
|
||||
#include "scene/devicescene.h"
|
||||
#include "device/device.h"
|
||||
#include "device/memory.h"
|
||||
|
||||
CCL_NAMESPACE_BEGIN
|
||||
|
||||
DeviceScene::DeviceScene(Device *device)
|
||||
: bvh_nodes(device, "bvh_nodes", MEM_GLOBAL),
|
||||
bvh_leaf_nodes(device, "bvh_leaf_nodes", MEM_GLOBAL),
|
||||
object_node(device, "object_node", MEM_GLOBAL),
|
||||
prim_type(device, "prim_type", MEM_GLOBAL),
|
||||
prim_visibility(device, "prim_visibility", MEM_GLOBAL),
|
||||
prim_index(device, "prim_index", MEM_GLOBAL),
|
||||
prim_object(device, "prim_object", MEM_GLOBAL),
|
||||
prim_time(device, "prim_time", MEM_GLOBAL),
|
||||
tri_verts(device, "tri_verts", MEM_GLOBAL),
|
||||
tri_shader(device, "tri_shader", MEM_GLOBAL),
|
||||
tri_vnormal(device, "tri_vnormal", MEM_GLOBAL),
|
||||
tri_vindex(device, "tri_vindex", MEM_GLOBAL),
|
||||
tri_patch(device, "tri_patch", MEM_GLOBAL),
|
||||
tri_patch_uv(device, "tri_patch_uv", MEM_GLOBAL),
|
||||
curves(device, "curves", MEM_GLOBAL),
|
||||
curve_keys(device, "curve_keys", MEM_GLOBAL),
|
||||
curve_segments(device, "curve_segments", MEM_GLOBAL),
|
||||
patches(device, "patches", MEM_GLOBAL),
|
||||
points(device, "points", MEM_GLOBAL),
|
||||
points_shader(device, "points_shader", MEM_GLOBAL),
|
||||
objects(device, "objects", MEM_GLOBAL),
|
||||
object_motion_pass(device, "object_motion_pass", MEM_GLOBAL),
|
||||
object_motion(device, "object_motion", MEM_GLOBAL),
|
||||
object_flag(device, "object_flag", MEM_GLOBAL),
|
||||
object_volume_step(device, "object_volume_step", MEM_GLOBAL),
|
||||
object_prim_offset(device, "object_prim_offset", MEM_GLOBAL),
|
||||
camera_motion(device, "camera_motion", MEM_GLOBAL),
|
||||
attributes_map(device, "attributes_map", MEM_GLOBAL),
|
||||
attributes_float(device, "attributes_float", MEM_GLOBAL),
|
||||
attributes_float2(device, "attributes_float2", MEM_GLOBAL),
|
||||
attributes_float3(device, "attributes_float3", MEM_GLOBAL),
|
||||
attributes_float4(device, "attributes_float4", MEM_GLOBAL),
|
||||
attributes_uchar4(device, "attributes_uchar4", MEM_GLOBAL),
|
||||
light_distribution(device, "light_distribution", MEM_GLOBAL),
|
||||
lights(device, "lights", MEM_GLOBAL),
|
||||
light_background_marginal_cdf(device, "light_background_marginal_cdf", MEM_GLOBAL),
|
||||
light_background_conditional_cdf(device, "light_background_conditional_cdf", MEM_GLOBAL),
|
||||
light_tree_nodes(device, "light_tree_nodes", MEM_GLOBAL),
|
||||
light_tree_emitters(device, "light_tree_emitters", MEM_GLOBAL),
|
||||
light_to_tree(device, "light_to_tree", MEM_GLOBAL),
|
||||
object_to_tree(device, "object_to_tree", MEM_GLOBAL),
|
||||
object_lookup_offset(device, "object_lookup_offset", MEM_GLOBAL),
|
||||
triangle_to_tree(device, "triangle_to_tree", MEM_GLOBAL),
|
||||
particles(device, "particles", MEM_GLOBAL),
|
||||
svm_nodes(device, "svm_nodes", MEM_GLOBAL),
|
||||
shaders(device, "shaders", MEM_GLOBAL),
|
||||
lookup_table(device, "lookup_table", MEM_GLOBAL),
|
||||
sample_pattern_lut(device, "sample_pattern_lut", MEM_GLOBAL),
|
||||
ies_lights(device, "ies", MEM_GLOBAL)
|
||||
{
|
||||
memset((void *)&data, 0, sizeof(data));
|
||||
}
|
||||
|
||||
CCL_NAMESPACE_END
|
|
@ -0,0 +1,101 @@
|
|||
/* SPDX-License-Identifier: Apache-2.0
|
||||
* Copyright 2011-2022 Blender Foundation */
|
||||
|
||||
#ifndef __DEVICESCENE_H__
|
||||
#define __DEVICESCENE_H__
|
||||
|
||||
#include "device/device.h"
|
||||
#include "device/memory.h"
|
||||
|
||||
#include "util/types.h"
|
||||
#include "util/vector.h"
|
||||
|
||||
CCL_NAMESPACE_BEGIN
|
||||
|
||||
class DeviceScene {
|
||||
public:
|
||||
/* BVH */
|
||||
device_vector<int4> bvh_nodes;
|
||||
device_vector<int4> bvh_leaf_nodes;
|
||||
device_vector<int> object_node;
|
||||
device_vector<int> prim_type;
|
||||
device_vector<uint> prim_visibility;
|
||||
device_vector<int> prim_index;
|
||||
device_vector<int> prim_object;
|
||||
device_vector<float2> prim_time;
|
||||
|
||||
/* mesh */
|
||||
device_vector<packed_float3> tri_verts;
|
||||
device_vector<uint> tri_shader;
|
||||
device_vector<packed_float3> tri_vnormal;
|
||||
device_vector<packed_uint3> tri_vindex;
|
||||
device_vector<uint> tri_patch;
|
||||
device_vector<float2> tri_patch_uv;
|
||||
|
||||
device_vector<KernelCurve> curves;
|
||||
device_vector<float4> curve_keys;
|
||||
device_vector<KernelCurveSegment> curve_segments;
|
||||
|
||||
device_vector<uint> patches;
|
||||
|
||||
/* point-cloud */
|
||||
device_vector<float4> points;
|
||||
device_vector<uint> points_shader;
|
||||
|
||||
/* objects */
|
||||
device_vector<KernelObject> objects;
|
||||
device_vector<Transform> object_motion_pass;
|
||||
device_vector<DecomposedTransform> object_motion;
|
||||
device_vector<uint> object_flag;
|
||||
device_vector<float> object_volume_step;
|
||||
device_vector<uint> object_prim_offset;
|
||||
|
||||
/* cameras */
|
||||
device_vector<DecomposedTransform> camera_motion;
|
||||
|
||||
/* attributes */
|
||||
device_vector<AttributeMap> attributes_map;
|
||||
device_vector<float> attributes_float;
|
||||
device_vector<float2> attributes_float2;
|
||||
device_vector<packed_float3> attributes_float3;
|
||||
device_vector<float4> attributes_float4;
|
||||
device_vector<uchar4> attributes_uchar4;
|
||||
|
||||
/* lights */
|
||||
device_vector<KernelLightDistribution> light_distribution;
|
||||
device_vector<KernelLight> lights;
|
||||
device_vector<float2> light_background_marginal_cdf;
|
||||
device_vector<float2> light_background_conditional_cdf;
|
||||
|
||||
/* light tree */
|
||||
device_vector<KernelLightTreeNode> light_tree_nodes;
|
||||
device_vector<KernelLightTreeEmitter> light_tree_emitters;
|
||||
device_vector<uint> light_to_tree;
|
||||
device_vector<uint> object_to_tree;
|
||||
device_vector<uint> object_lookup_offset;
|
||||
device_vector<uint> triangle_to_tree;
|
||||
|
||||
/* particles */
|
||||
device_vector<KernelParticle> particles;
|
||||
|
||||
/* shaders */
|
||||
device_vector<int4> svm_nodes;
|
||||
device_vector<KernelShader> shaders;
|
||||
|
||||
/* lookup tables */
|
||||
device_vector<float> lookup_table;
|
||||
|
||||
/* integrator */
|
||||
device_vector<float> sample_pattern_lut;
|
||||
|
||||
/* IES lights */
|
||||
device_vector<float> ies_lights;
|
||||
|
||||
KernelData data;
|
||||
|
||||
DeviceScene(Device *device);
|
||||
};
|
||||
|
||||
CCL_NAMESPACE_END
|
||||
|
||||
#endif /* __DEVICESCENE_H__ */
|
File diff suppressed because it is too large
Load Diff
|
@ -30,6 +30,38 @@ class Shader;
|
|||
class Volume;
|
||||
struct PackedBVH;
|
||||
|
||||
/* Set of flags used to help determining what data has been modified or needs reallocation, so we
|
||||
* can decide which device data to free or update. */
|
||||
enum {
|
||||
DEVICE_CURVE_DATA_MODIFIED = (1 << 0),
|
||||
DEVICE_MESH_DATA_MODIFIED = (1 << 1),
|
||||
DEVICE_POINT_DATA_MODIFIED = (1 << 2),
|
||||
|
||||
ATTR_FLOAT_MODIFIED = (1 << 3),
|
||||
ATTR_FLOAT2_MODIFIED = (1 << 4),
|
||||
ATTR_FLOAT3_MODIFIED = (1 << 5),
|
||||
ATTR_FLOAT4_MODIFIED = (1 << 6),
|
||||
ATTR_UCHAR4_MODIFIED = (1 << 7),
|
||||
|
||||
CURVE_DATA_NEED_REALLOC = (1 << 8),
|
||||
MESH_DATA_NEED_REALLOC = (1 << 9),
|
||||
POINT_DATA_NEED_REALLOC = (1 << 10),
|
||||
|
||||
ATTR_FLOAT_NEEDS_REALLOC = (1 << 11),
|
||||
ATTR_FLOAT2_NEEDS_REALLOC = (1 << 12),
|
||||
ATTR_FLOAT3_NEEDS_REALLOC = (1 << 13),
|
||||
ATTR_FLOAT4_NEEDS_REALLOC = (1 << 14),
|
||||
|
||||
ATTR_UCHAR4_NEEDS_REALLOC = (1 << 15),
|
||||
|
||||
ATTRS_NEED_REALLOC = (ATTR_FLOAT_NEEDS_REALLOC | ATTR_FLOAT2_NEEDS_REALLOC |
|
||||
ATTR_FLOAT3_NEEDS_REALLOC | ATTR_FLOAT4_NEEDS_REALLOC |
|
||||
ATTR_UCHAR4_NEEDS_REALLOC),
|
||||
DEVICE_MESH_DATA_NEEDS_REALLOC = (MESH_DATA_NEED_REALLOC | ATTRS_NEED_REALLOC),
|
||||
DEVICE_POINT_DATA_NEEDS_REALLOC = (POINT_DATA_NEED_REALLOC | ATTRS_NEED_REALLOC),
|
||||
DEVICE_CURVE_DATA_NEEDS_REALLOC = (CURVE_DATA_NEED_REALLOC | ATTRS_NEED_REALLOC),
|
||||
};
|
||||
|
||||
/* Geometry
|
||||
*
|
||||
* Base class for geometric types like Mesh and Hair. */
|
||||
|
|
|
@ -0,0 +1,722 @@
|
|||
/* SPDX-License-Identifier: Apache-2.0
|
||||
* Copyright 2011-2022 Blender Foundation */
|
||||
|
||||
#include "bvh/bvh.h"
|
||||
#include "bvh/bvh2.h"
|
||||
|
||||
#include "device/device.h"
|
||||
|
||||
#include "scene/attribute.h"
|
||||
#include "scene/camera.h"
|
||||
#include "scene/geometry.h"
|
||||
#include "scene/hair.h"
|
||||
#include "scene/light.h"
|
||||
#include "scene/mesh.h"
|
||||
#include "scene/object.h"
|
||||
#include "scene/pointcloud.h"
|
||||
#include "scene/scene.h"
|
||||
#include "scene/shader.h"
|
||||
#include "scene/shader_nodes.h"
|
||||
#include "scene/stats.h"
|
||||
#include "scene/volume.h"
|
||||
|
||||
#include "subd/patch_table.h"
|
||||
#include "subd/split.h"
|
||||
|
||||
#include "kernel/osl/globals.h"
|
||||
|
||||
#include "util/foreach.h"
|
||||
#include "util/log.h"
|
||||
#include "util/progress.h"
|
||||
#include "util/task.h"
|
||||
|
||||
CCL_NAMESPACE_BEGIN
|
||||
|
||||
bool Geometry::need_attribute(Scene *scene, AttributeStandard std)
|
||||
{
|
||||
if (std == ATTR_STD_NONE)
|
||||
return false;
|
||||
|
||||
if (scene->need_global_attribute(std))
|
||||
return true;
|
||||
|
||||
foreach (Node *node, used_shaders) {
|
||||
Shader *shader = static_cast<Shader *>(node);
|
||||
if (shader->attributes.find(std))
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool Geometry::need_attribute(Scene * /*scene*/, ustring name)
|
||||
{
|
||||
if (name == ustring())
|
||||
return false;
|
||||
|
||||
foreach (Node *node, used_shaders) {
|
||||
Shader *shader = static_cast<Shader *>(node);
|
||||
if (shader->attributes.find(name))
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
AttributeRequestSet Geometry::needed_attributes()
|
||||
{
|
||||
AttributeRequestSet result;
|
||||
|
||||
foreach (Node *node, used_shaders) {
|
||||
Shader *shader = static_cast<Shader *>(node);
|
||||
result.add(shader->attributes);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
bool Geometry::has_voxel_attributes() const
|
||||
{
|
||||
foreach (const Attribute &attr, attributes.attributes) {
|
||||
if (attr.element == ATTR_ELEMENT_VOXEL) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/* Generate a normal attribute map entry from an attribute descriptor. */
|
||||
static void emit_attribute_map_entry(AttributeMap *attr_map,
|
||||
size_t index,
|
||||
uint64_t id,
|
||||
TypeDesc type,
|
||||
const AttributeDescriptor &desc)
|
||||
{
|
||||
attr_map[index].id = id;
|
||||
attr_map[index].element = desc.element;
|
||||
attr_map[index].offset = as_uint(desc.offset);
|
||||
|
||||
if (type == TypeDesc::TypeFloat)
|
||||
attr_map[index].type = NODE_ATTR_FLOAT;
|
||||
else if (type == TypeDesc::TypeMatrix)
|
||||
attr_map[index].type = NODE_ATTR_MATRIX;
|
||||
else if (type == TypeFloat2)
|
||||
attr_map[index].type = NODE_ATTR_FLOAT2;
|
||||
else if (type == TypeFloat4)
|
||||
attr_map[index].type = NODE_ATTR_FLOAT4;
|
||||
else if (type == TypeRGBA)
|
||||
attr_map[index].type = NODE_ATTR_RGBA;
|
||||
else
|
||||
attr_map[index].type = NODE_ATTR_FLOAT3;
|
||||
|
||||
attr_map[index].flags = desc.flags;
|
||||
}
|
||||
|
||||
/* Generate an attribute map end marker, optionally including a link to another map.
|
||||
* Links are used to connect object attribute maps to mesh attribute maps. */
|
||||
static void emit_attribute_map_terminator(AttributeMap *attr_map,
|
||||
size_t index,
|
||||
bool chain,
|
||||
uint chain_link)
|
||||
{
|
||||
for (int j = 0; j < ATTR_PRIM_TYPES; j++) {
|
||||
attr_map[index + j].id = ATTR_STD_NONE;
|
||||
attr_map[index + j].element = chain; /* link is valid flag */
|
||||
attr_map[index + j].offset = chain ? chain_link + j : 0; /* link to the correct sub-entry */
|
||||
attr_map[index + j].type = 0;
|
||||
attr_map[index + j].flags = 0;
|
||||
}
|
||||
}
|
||||
|
||||
/* Generate all necessary attribute map entries from the attribute request. */
|
||||
static void emit_attribute_mapping(
|
||||
AttributeMap *attr_map, size_t index, uint64_t id, AttributeRequest &req, Geometry *geom)
|
||||
{
|
||||
emit_attribute_map_entry(attr_map, index, id, req.type, req.desc);
|
||||
|
||||
if (geom->is_mesh()) {
|
||||
Mesh *mesh = static_cast<Mesh *>(geom);
|
||||
if (mesh->get_num_subd_faces()) {
|
||||
emit_attribute_map_entry(attr_map, index + 1, id, req.subd_type, req.subd_desc);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void GeometryManager::update_svm_attributes(Device *,
|
||||
DeviceScene *dscene,
|
||||
Scene *scene,
|
||||
vector<AttributeRequestSet> &geom_attributes,
|
||||
vector<AttributeRequestSet> &object_attributes)
|
||||
{
|
||||
/* for SVM, the attributes_map table is used to lookup the offset of an
|
||||
* attribute, based on a unique shader attribute id. */
|
||||
|
||||
/* compute array stride */
|
||||
size_t attr_map_size = 0;
|
||||
|
||||
for (size_t i = 0; i < scene->geometry.size(); i++) {
|
||||
Geometry *geom = scene->geometry[i];
|
||||
geom->attr_map_offset = attr_map_size;
|
||||
|
||||
#ifdef WITH_OSL
|
||||
size_t attr_count = 0;
|
||||
foreach (AttributeRequest &req, geom_attributes[i].requests) {
|
||||
if (req.std != ATTR_STD_NONE &&
|
||||
scene->shader_manager->get_attribute_id(req.std) != (uint64_t)req.std)
|
||||
attr_count += 2;
|
||||
else
|
||||
attr_count += 1;
|
||||
}
|
||||
#else
|
||||
const size_t attr_count = geom_attributes[i].size();
|
||||
#endif
|
||||
|
||||
attr_map_size += (attr_count + 1) * ATTR_PRIM_TYPES;
|
||||
}
|
||||
|
||||
for (size_t i = 0; i < scene->objects.size(); i++) {
|
||||
Object *object = scene->objects[i];
|
||||
|
||||
/* only allocate a table for the object if it actually has attributes */
|
||||
if (object_attributes[i].size() == 0) {
|
||||
object->attr_map_offset = 0;
|
||||
}
|
||||
else {
|
||||
object->attr_map_offset = attr_map_size;
|
||||
attr_map_size += (object_attributes[i].size() + 1) * ATTR_PRIM_TYPES;
|
||||
}
|
||||
}
|
||||
|
||||
if (attr_map_size == 0)
|
||||
return;
|
||||
|
||||
if (!dscene->attributes_map.need_realloc()) {
|
||||
return;
|
||||
}
|
||||
|
||||
/* create attribute map */
|
||||
AttributeMap *attr_map = dscene->attributes_map.alloc(attr_map_size);
|
||||
memset(attr_map, 0, dscene->attributes_map.size() * sizeof(*attr_map));
|
||||
|
||||
for (size_t i = 0; i < scene->geometry.size(); i++) {
|
||||
Geometry *geom = scene->geometry[i];
|
||||
AttributeRequestSet &attributes = geom_attributes[i];
|
||||
|
||||
/* set geometry attributes */
|
||||
size_t index = geom->attr_map_offset;
|
||||
|
||||
foreach (AttributeRequest &req, attributes.requests) {
|
||||
uint64_t id;
|
||||
if (req.std == ATTR_STD_NONE)
|
||||
id = scene->shader_manager->get_attribute_id(req.name);
|
||||
else
|
||||
id = scene->shader_manager->get_attribute_id(req.std);
|
||||
|
||||
emit_attribute_mapping(attr_map, index, id, req, geom);
|
||||
index += ATTR_PRIM_TYPES;
|
||||
|
||||
#ifdef WITH_OSL
|
||||
/* Some standard attributes are explicitly referenced via their standard ID, so add those
|
||||
* again in case they were added under a different attribute ID. */
|
||||
if (req.std != ATTR_STD_NONE && id != (uint64_t)req.std) {
|
||||
emit_attribute_mapping(attr_map, index, (uint64_t)req.std, req, geom);
|
||||
index += ATTR_PRIM_TYPES;
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
emit_attribute_map_terminator(attr_map, index, false, 0);
|
||||
}
|
||||
|
||||
for (size_t i = 0; i < scene->objects.size(); i++) {
|
||||
Object *object = scene->objects[i];
|
||||
AttributeRequestSet &attributes = object_attributes[i];
|
||||
|
||||
/* set object attributes */
|
||||
if (attributes.size() > 0) {
|
||||
size_t index = object->attr_map_offset;
|
||||
|
||||
foreach (AttributeRequest &req, attributes.requests) {
|
||||
uint64_t id;
|
||||
if (req.std == ATTR_STD_NONE)
|
||||
id = scene->shader_manager->get_attribute_id(req.name);
|
||||
else
|
||||
id = scene->shader_manager->get_attribute_id(req.std);
|
||||
|
||||
emit_attribute_mapping(attr_map, index, id, req, object->geometry);
|
||||
index += ATTR_PRIM_TYPES;
|
||||
}
|
||||
|
||||
emit_attribute_map_terminator(attr_map, index, true, object->geometry->attr_map_offset);
|
||||
}
|
||||
}
|
||||
|
||||
/* copy to device */
|
||||
dscene->attributes_map.copy_to_device();
|
||||
}
|
||||
|
||||
void GeometryManager::update_attribute_element_offset(Geometry *geom,
|
||||
device_vector<float> &attr_float,
|
||||
size_t &attr_float_offset,
|
||||
device_vector<float2> &attr_float2,
|
||||
size_t &attr_float2_offset,
|
||||
device_vector<packed_float3> &attr_float3,
|
||||
size_t &attr_float3_offset,
|
||||
device_vector<float4> &attr_float4,
|
||||
size_t &attr_float4_offset,
|
||||
device_vector<uchar4> &attr_uchar4,
|
||||
size_t &attr_uchar4_offset,
|
||||
Attribute *mattr,
|
||||
AttributePrimitive prim,
|
||||
TypeDesc &type,
|
||||
AttributeDescriptor &desc)
|
||||
{
|
||||
if (mattr) {
|
||||
/* store element and type */
|
||||
desc.element = mattr->element;
|
||||
desc.flags = mattr->flags;
|
||||
type = mattr->type;
|
||||
|
||||
/* store attribute data in arrays */
|
||||
size_t size = mattr->element_size(geom, prim);
|
||||
|
||||
AttributeElement &element = desc.element;
|
||||
int &offset = desc.offset;
|
||||
|
||||
if (mattr->element == ATTR_ELEMENT_VOXEL) {
|
||||
/* store slot in offset value */
|
||||
ImageHandle &handle = mattr->data_voxel();
|
||||
offset = handle.svm_slot();
|
||||
}
|
||||
else if (mattr->element == ATTR_ELEMENT_CORNER_BYTE) {
|
||||
uchar4 *data = mattr->data_uchar4();
|
||||
offset = attr_uchar4_offset;
|
||||
|
||||
assert(attr_uchar4.size() >= offset + size);
|
||||
if (mattr->modified) {
|
||||
for (size_t k = 0; k < size; k++) {
|
||||
attr_uchar4[offset + k] = data[k];
|
||||
}
|
||||
attr_uchar4.tag_modified();
|
||||
}
|
||||
attr_uchar4_offset += size;
|
||||
}
|
||||
else if (mattr->type == TypeDesc::TypeFloat) {
|
||||
float *data = mattr->data_float();
|
||||
offset = attr_float_offset;
|
||||
|
||||
assert(attr_float.size() >= offset + size);
|
||||
if (mattr->modified) {
|
||||
for (size_t k = 0; k < size; k++) {
|
||||
attr_float[offset + k] = data[k];
|
||||
}
|
||||
attr_float.tag_modified();
|
||||
}
|
||||
attr_float_offset += size;
|
||||
}
|
||||
else if (mattr->type == TypeFloat2) {
|
||||
float2 *data = mattr->data_float2();
|
||||
offset = attr_float2_offset;
|
||||
|
||||
assert(attr_float2.size() >= offset + size);
|
||||
if (mattr->modified) {
|
||||
for (size_t k = 0; k < size; k++) {
|
||||
attr_float2[offset + k] = data[k];
|
||||
}
|
||||
attr_float2.tag_modified();
|
||||
}
|
||||
attr_float2_offset += size;
|
||||
}
|
||||
else if (mattr->type == TypeDesc::TypeMatrix) {
|
||||
Transform *tfm = mattr->data_transform();
|
||||
offset = attr_float4_offset;
|
||||
|
||||
assert(attr_float4.size() >= offset + size * 3);
|
||||
if (mattr->modified) {
|
||||
for (size_t k = 0; k < size * 3; k++) {
|
||||
attr_float4[offset + k] = (&tfm->x)[k];
|
||||
}
|
||||
attr_float4.tag_modified();
|
||||
}
|
||||
attr_float4_offset += size * 3;
|
||||
}
|
||||
else if (mattr->type == TypeFloat4 || mattr->type == TypeRGBA) {
|
||||
float4 *data = mattr->data_float4();
|
||||
offset = attr_float4_offset;
|
||||
|
||||
assert(attr_float4.size() >= offset + size);
|
||||
if (mattr->modified) {
|
||||
for (size_t k = 0; k < size; k++) {
|
||||
attr_float4[offset + k] = data[k];
|
||||
}
|
||||
attr_float4.tag_modified();
|
||||
}
|
||||
attr_float4_offset += size;
|
||||
}
|
||||
else {
|
||||
float3 *data = mattr->data_float3();
|
||||
offset = attr_float3_offset;
|
||||
|
||||
assert(attr_float3.size() >= offset + size);
|
||||
if (mattr->modified) {
|
||||
for (size_t k = 0; k < size; k++) {
|
||||
attr_float3[offset + k] = data[k];
|
||||
}
|
||||
attr_float3.tag_modified();
|
||||
}
|
||||
attr_float3_offset += size;
|
||||
}
|
||||
|
||||
/* mesh vertex/curve index is global, not per object, so we sneak
|
||||
* a correction for that in here */
|
||||
if (geom->is_mesh()) {
|
||||
Mesh *mesh = static_cast<Mesh *>(geom);
|
||||
if (mesh->subdivision_type == Mesh::SUBDIVISION_CATMULL_CLARK &&
|
||||
desc.flags & ATTR_SUBDIVIDED) {
|
||||
/* Indices for subdivided attributes are retrieved
|
||||
* from patch table so no need for correction here. */
|
||||
}
|
||||
else if (element == ATTR_ELEMENT_VERTEX)
|
||||
offset -= mesh->vert_offset;
|
||||
else if (element == ATTR_ELEMENT_VERTEX_MOTION)
|
||||
offset -= mesh->vert_offset;
|
||||
else if (element == ATTR_ELEMENT_FACE) {
|
||||
if (prim == ATTR_PRIM_GEOMETRY)
|
||||
offset -= mesh->prim_offset;
|
||||
else
|
||||
offset -= mesh->face_offset;
|
||||
}
|
||||
else if (element == ATTR_ELEMENT_CORNER || element == ATTR_ELEMENT_CORNER_BYTE) {
|
||||
if (prim == ATTR_PRIM_GEOMETRY)
|
||||
offset -= 3 * mesh->prim_offset;
|
||||
else
|
||||
offset -= mesh->corner_offset;
|
||||
}
|
||||
}
|
||||
else if (geom->is_hair()) {
|
||||
Hair *hair = static_cast<Hair *>(geom);
|
||||
if (element == ATTR_ELEMENT_CURVE)
|
||||
offset -= hair->prim_offset;
|
||||
else if (element == ATTR_ELEMENT_CURVE_KEY)
|
||||
offset -= hair->curve_key_offset;
|
||||
else if (element == ATTR_ELEMENT_CURVE_KEY_MOTION)
|
||||
offset -= hair->curve_key_offset;
|
||||
}
|
||||
else if (geom->is_pointcloud()) {
|
||||
if (element == ATTR_ELEMENT_VERTEX)
|
||||
offset -= geom->prim_offset;
|
||||
else if (element == ATTR_ELEMENT_VERTEX_MOTION)
|
||||
offset -= geom->prim_offset;
|
||||
}
|
||||
}
|
||||
else {
|
||||
/* attribute not found */
|
||||
desc.element = ATTR_ELEMENT_NONE;
|
||||
desc.offset = 0;
|
||||
}
|
||||
}
|
||||
|
||||
static void update_attribute_element_size(Geometry *geom,
|
||||
Attribute *mattr,
|
||||
AttributePrimitive prim,
|
||||
size_t *attr_float_size,
|
||||
size_t *attr_float2_size,
|
||||
size_t *attr_float3_size,
|
||||
size_t *attr_float4_size,
|
||||
size_t *attr_uchar4_size)
|
||||
{
|
||||
if (mattr) {
|
||||
size_t size = mattr->element_size(geom, prim);
|
||||
|
||||
if (mattr->element == ATTR_ELEMENT_VOXEL) {
|
||||
/* pass */
|
||||
}
|
||||
else if (mattr->element == ATTR_ELEMENT_CORNER_BYTE) {
|
||||
*attr_uchar4_size += size;
|
||||
}
|
||||
else if (mattr->type == TypeDesc::TypeFloat) {
|
||||
*attr_float_size += size;
|
||||
}
|
||||
else if (mattr->type == TypeFloat2) {
|
||||
*attr_float2_size += size;
|
||||
}
|
||||
else if (mattr->type == TypeDesc::TypeMatrix) {
|
||||
*attr_float4_size += size * 4;
|
||||
}
|
||||
else if (mattr->type == TypeFloat4 || mattr->type == TypeRGBA) {
|
||||
*attr_float4_size += size;
|
||||
}
|
||||
else {
|
||||
*attr_float3_size += size;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void GeometryManager::device_update_attributes(Device *device,
|
||||
DeviceScene *dscene,
|
||||
Scene *scene,
|
||||
Progress &progress)
|
||||
{
|
||||
progress.set_status("Updating Mesh", "Computing attributes");
|
||||
|
||||
/* gather per mesh requested attributes. as meshes may have multiple
|
||||
* shaders assigned, this merges the requested attributes that have
|
||||
* been set per shader by the shader manager */
|
||||
vector<AttributeRequestSet> geom_attributes(scene->geometry.size());
|
||||
|
||||
for (size_t i = 0; i < scene->geometry.size(); i++) {
|
||||
Geometry *geom = scene->geometry[i];
|
||||
|
||||
geom->index = i;
|
||||
scene->need_global_attributes(geom_attributes[i]);
|
||||
|
||||
foreach (Node *node, geom->get_used_shaders()) {
|
||||
Shader *shader = static_cast<Shader *>(node);
|
||||
geom_attributes[i].add(shader->attributes);
|
||||
}
|
||||
|
||||
if (geom->is_hair() && static_cast<Hair *>(geom)->need_shadow_transparency()) {
|
||||
geom_attributes[i].add(ATTR_STD_SHADOW_TRANSPARENCY);
|
||||
}
|
||||
}
|
||||
|
||||
/* convert object attributes to use the same data structures as geometry ones */
|
||||
vector<AttributeRequestSet> object_attributes(scene->objects.size());
|
||||
vector<AttributeSet> object_attribute_values;
|
||||
|
||||
object_attribute_values.reserve(scene->objects.size());
|
||||
|
||||
for (size_t i = 0; i < scene->objects.size(); i++) {
|
||||
Object *object = scene->objects[i];
|
||||
Geometry *geom = object->geometry;
|
||||
size_t geom_idx = geom->index;
|
||||
|
||||
assert(geom_idx < scene->geometry.size() && scene->geometry[geom_idx] == geom);
|
||||
|
||||
object_attribute_values.push_back(AttributeSet(geom, ATTR_PRIM_GEOMETRY));
|
||||
|
||||
AttributeRequestSet &geom_requests = geom_attributes[geom_idx];
|
||||
AttributeRequestSet &attributes = object_attributes[i];
|
||||
AttributeSet &values = object_attribute_values[i];
|
||||
|
||||
for (size_t j = 0; j < object->attributes.size(); j++) {
|
||||
ParamValue ¶m = object->attributes[j];
|
||||
|
||||
/* add attributes that are requested and not already handled by the mesh */
|
||||
if (geom_requests.find(param.name()) && !geom->attributes.find(param.name())) {
|
||||
attributes.add(param.name());
|
||||
|
||||
Attribute *attr = values.add(param.name(), param.type(), ATTR_ELEMENT_OBJECT);
|
||||
assert(param.datasize() == attr->buffer.size());
|
||||
memcpy(attr->buffer.data(), param.data(), param.datasize());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* mesh attribute are stored in a single array per data type. here we fill
|
||||
* those arrays, and set the offset and element type to create attribute
|
||||
* maps next */
|
||||
|
||||
/* Pre-allocate attributes to avoid arrays re-allocation which would
|
||||
* take 2x of overall attribute memory usage.
|
||||
*/
|
||||
size_t attr_float_size = 0;
|
||||
size_t attr_float2_size = 0;
|
||||
size_t attr_float3_size = 0;
|
||||
size_t attr_float4_size = 0;
|
||||
size_t attr_uchar4_size = 0;
|
||||
|
||||
for (size_t i = 0; i < scene->geometry.size(); i++) {
|
||||
Geometry *geom = scene->geometry[i];
|
||||
AttributeRequestSet &attributes = geom_attributes[i];
|
||||
foreach (AttributeRequest &req, attributes.requests) {
|
||||
Attribute *attr = geom->attributes.find(req);
|
||||
|
||||
update_attribute_element_size(geom,
|
||||
attr,
|
||||
ATTR_PRIM_GEOMETRY,
|
||||
&attr_float_size,
|
||||
&attr_float2_size,
|
||||
&attr_float3_size,
|
||||
&attr_float4_size,
|
||||
&attr_uchar4_size);
|
||||
|
||||
if (geom->is_mesh()) {
|
||||
Mesh *mesh = static_cast<Mesh *>(geom);
|
||||
Attribute *subd_attr = mesh->subd_attributes.find(req);
|
||||
|
||||
update_attribute_element_size(mesh,
|
||||
subd_attr,
|
||||
ATTR_PRIM_SUBD,
|
||||
&attr_float_size,
|
||||
&attr_float2_size,
|
||||
&attr_float3_size,
|
||||
&attr_float4_size,
|
||||
&attr_uchar4_size);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (size_t i = 0; i < scene->objects.size(); i++) {
|
||||
Object *object = scene->objects[i];
|
||||
|
||||
foreach (Attribute &attr, object_attribute_values[i].attributes) {
|
||||
update_attribute_element_size(object->geometry,
|
||||
&attr,
|
||||
ATTR_PRIM_GEOMETRY,
|
||||
&attr_float_size,
|
||||
&attr_float2_size,
|
||||
&attr_float3_size,
|
||||
&attr_float4_size,
|
||||
&attr_uchar4_size);
|
||||
}
|
||||
}
|
||||
|
||||
dscene->attributes_float.alloc(attr_float_size);
|
||||
dscene->attributes_float2.alloc(attr_float2_size);
|
||||
dscene->attributes_float3.alloc(attr_float3_size);
|
||||
dscene->attributes_float4.alloc(attr_float4_size);
|
||||
dscene->attributes_uchar4.alloc(attr_uchar4_size);
|
||||
|
||||
/* The order of those flags needs to match that of AttrKernelDataType. */
|
||||
const bool attributes_need_realloc[AttrKernelDataType::NUM] = {
|
||||
dscene->attributes_float.need_realloc(),
|
||||
dscene->attributes_float2.need_realloc(),
|
||||
dscene->attributes_float3.need_realloc(),
|
||||
dscene->attributes_float4.need_realloc(),
|
||||
dscene->attributes_uchar4.need_realloc(),
|
||||
};
|
||||
|
||||
size_t attr_float_offset = 0;
|
||||
size_t attr_float2_offset = 0;
|
||||
size_t attr_float3_offset = 0;
|
||||
size_t attr_float4_offset = 0;
|
||||
size_t attr_uchar4_offset = 0;
|
||||
|
||||
/* Fill in attributes. */
|
||||
for (size_t i = 0; i < scene->geometry.size(); i++) {
|
||||
Geometry *geom = scene->geometry[i];
|
||||
AttributeRequestSet &attributes = geom_attributes[i];
|
||||
|
||||
/* todo: we now store std and name attributes from requests even if
|
||||
* they actually refer to the same mesh attributes, optimize */
|
||||
foreach (AttributeRequest &req, attributes.requests) {
|
||||
Attribute *attr = geom->attributes.find(req);
|
||||
|
||||
if (attr) {
|
||||
/* force a copy if we need to reallocate all the data */
|
||||
attr->modified |= attributes_need_realloc[Attribute::kernel_type(*attr)];
|
||||
}
|
||||
|
||||
update_attribute_element_offset(geom,
|
||||
dscene->attributes_float,
|
||||
attr_float_offset,
|
||||
dscene->attributes_float2,
|
||||
attr_float2_offset,
|
||||
dscene->attributes_float3,
|
||||
attr_float3_offset,
|
||||
dscene->attributes_float4,
|
||||
attr_float4_offset,
|
||||
dscene->attributes_uchar4,
|
||||
attr_uchar4_offset,
|
||||
attr,
|
||||
ATTR_PRIM_GEOMETRY,
|
||||
req.type,
|
||||
req.desc);
|
||||
|
||||
if (geom->is_mesh()) {
|
||||
Mesh *mesh = static_cast<Mesh *>(geom);
|
||||
Attribute *subd_attr = mesh->subd_attributes.find(req);
|
||||
|
||||
if (subd_attr) {
|
||||
/* force a copy if we need to reallocate all the data */
|
||||
subd_attr->modified |= attributes_need_realloc[Attribute::kernel_type(*subd_attr)];
|
||||
}
|
||||
|
||||
update_attribute_element_offset(mesh,
|
||||
dscene->attributes_float,
|
||||
attr_float_offset,
|
||||
dscene->attributes_float2,
|
||||
attr_float2_offset,
|
||||
dscene->attributes_float3,
|
||||
attr_float3_offset,
|
||||
dscene->attributes_float4,
|
||||
attr_float4_offset,
|
||||
dscene->attributes_uchar4,
|
||||
attr_uchar4_offset,
|
||||
subd_attr,
|
||||
ATTR_PRIM_SUBD,
|
||||
req.subd_type,
|
||||
req.subd_desc);
|
||||
}
|
||||
|
||||
if (progress.get_cancel())
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
for (size_t i = 0; i < scene->objects.size(); i++) {
|
||||
Object *object = scene->objects[i];
|
||||
AttributeRequestSet &attributes = object_attributes[i];
|
||||
AttributeSet &values = object_attribute_values[i];
|
||||
|
||||
foreach (AttributeRequest &req, attributes.requests) {
|
||||
Attribute *attr = values.find(req);
|
||||
|
||||
if (attr) {
|
||||
attr->modified |= attributes_need_realloc[Attribute::kernel_type(*attr)];
|
||||
}
|
||||
|
||||
update_attribute_element_offset(object->geometry,
|
||||
dscene->attributes_float,
|
||||
attr_float_offset,
|
||||
dscene->attributes_float2,
|
||||
attr_float2_offset,
|
||||
dscene->attributes_float3,
|
||||
attr_float3_offset,
|
||||
dscene->attributes_float4,
|
||||
attr_float4_offset,
|
||||
dscene->attributes_uchar4,
|
||||
attr_uchar4_offset,
|
||||
attr,
|
||||
ATTR_PRIM_GEOMETRY,
|
||||
req.type,
|
||||
req.desc);
|
||||
|
||||
/* object attributes don't care about subdivision */
|
||||
req.subd_type = req.type;
|
||||
req.subd_desc = req.desc;
|
||||
|
||||
if (progress.get_cancel())
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
/* create attribute lookup maps */
|
||||
if (scene->shader_manager->use_osl())
|
||||
update_osl_globals(device, scene);
|
||||
|
||||
update_svm_attributes(device, dscene, scene, geom_attributes, object_attributes);
|
||||
|
||||
if (progress.get_cancel())
|
||||
return;
|
||||
|
||||
/* copy to device */
|
||||
progress.set_status("Updating Mesh", "Copying Attributes to device");
|
||||
|
||||
dscene->attributes_float.copy_to_device_if_modified();
|
||||
dscene->attributes_float2.copy_to_device_if_modified();
|
||||
dscene->attributes_float3.copy_to_device_if_modified();
|
||||
dscene->attributes_float4.copy_to_device_if_modified();
|
||||
dscene->attributes_uchar4.copy_to_device_if_modified();
|
||||
|
||||
if (progress.get_cancel())
|
||||
return;
|
||||
|
||||
/* After mesh attributes and patch tables have been copied to device memory,
|
||||
* we need to update offsets in the objects. */
|
||||
scene->object_manager->device_update_geom_offsets(device, dscene, scene);
|
||||
}
|
||||
|
||||
CCL_NAMESPACE_END
|
|
@ -0,0 +1,196 @@
|
|||
/* SPDX-License-Identifier: Apache-2.0
|
||||
* Copyright 2011-2022 Blender Foundation */
|
||||
|
||||
#include "bvh/bvh.h"
|
||||
#include "bvh/bvh2.h"
|
||||
|
||||
#include "device/device.h"
|
||||
|
||||
#include "scene/attribute.h"
|
||||
#include "scene/camera.h"
|
||||
#include "scene/geometry.h"
|
||||
#include "scene/hair.h"
|
||||
#include "scene/light.h"
|
||||
#include "scene/mesh.h"
|
||||
#include "scene/object.h"
|
||||
#include "scene/pointcloud.h"
|
||||
#include "scene/scene.h"
|
||||
#include "scene/shader.h"
|
||||
#include "scene/shader_nodes.h"
|
||||
#include "scene/stats.h"
|
||||
#include "scene/volume.h"
|
||||
|
||||
#include "subd/patch_table.h"
|
||||
#include "subd/split.h"
|
||||
|
||||
#include "kernel/osl/globals.h"
|
||||
|
||||
#include "util/foreach.h"
|
||||
#include "util/log.h"
|
||||
#include "util/progress.h"
|
||||
#include "util/task.h"
|
||||
|
||||
CCL_NAMESPACE_BEGIN
|
||||
|
||||
void Geometry::compute_bvh(Device *device,
|
||||
DeviceScene *dscene,
|
||||
SceneParams *params,
|
||||
Progress *progress,
|
||||
size_t n,
|
||||
size_t total)
|
||||
{
|
||||
if (progress->get_cancel())
|
||||
return;
|
||||
|
||||
compute_bounds();
|
||||
|
||||
const BVHLayout bvh_layout = BVHParams::best_bvh_layout(
|
||||
params->bvh_layout, device->get_bvh_layout_mask(dscene->data.kernel_features));
|
||||
if (need_build_bvh(bvh_layout)) {
|
||||
string msg = "Updating Geometry BVH ";
|
||||
if (name.empty())
|
||||
msg += string_printf("%u/%u", (uint)(n + 1), (uint)total);
|
||||
else
|
||||
msg += string_printf("%s %u/%u", name.c_str(), (uint)(n + 1), (uint)total);
|
||||
|
||||
Object object;
|
||||
|
||||
/* Ensure all visibility bits are set at the geometry level BVH. In
|
||||
* the object level BVH is where actual visibility is tested. */
|
||||
object.set_is_shadow_catcher(true);
|
||||
object.set_visibility(~0);
|
||||
|
||||
object.set_geometry(this);
|
||||
|
||||
vector<Geometry *> geometry;
|
||||
geometry.push_back(this);
|
||||
vector<Object *> objects;
|
||||
objects.push_back(&object);
|
||||
|
||||
if (bvh && !need_update_rebuild) {
|
||||
progress->set_status(msg, "Refitting BVH");
|
||||
|
||||
bvh->replace_geometry(geometry, objects);
|
||||
|
||||
device->build_bvh(bvh, *progress, true);
|
||||
}
|
||||
else {
|
||||
progress->set_status(msg, "Building BVH");
|
||||
|
||||
BVHParams bparams;
|
||||
bparams.use_spatial_split = params->use_bvh_spatial_split;
|
||||
bparams.use_compact_structure = params->use_bvh_compact_structure;
|
||||
bparams.bvh_layout = bvh_layout;
|
||||
bparams.use_unaligned_nodes = dscene->data.bvh.have_curves &&
|
||||
params->use_bvh_unaligned_nodes;
|
||||
bparams.num_motion_triangle_steps = params->num_bvh_time_steps;
|
||||
bparams.num_motion_curve_steps = params->num_bvh_time_steps;
|
||||
bparams.num_motion_point_steps = params->num_bvh_time_steps;
|
||||
bparams.bvh_type = params->bvh_type;
|
||||
bparams.curve_subdivisions = params->curve_subdivisions();
|
||||
|
||||
delete bvh;
|
||||
bvh = BVH::create(bparams, geometry, objects, device);
|
||||
MEM_GUARDED_CALL(progress, device->build_bvh, bvh, *progress, false);
|
||||
}
|
||||
}
|
||||
|
||||
need_update_rebuild = false;
|
||||
need_update_bvh_for_offset = false;
|
||||
}
|
||||
|
||||
void GeometryManager::device_update_bvh(Device *device,
|
||||
DeviceScene *dscene,
|
||||
Scene *scene,
|
||||
Progress &progress)
|
||||
{
|
||||
/* bvh build */
|
||||
progress.set_status("Updating Scene BVH", "Building");
|
||||
|
||||
BVHParams bparams;
|
||||
bparams.top_level = true;
|
||||
bparams.bvh_layout = BVHParams::best_bvh_layout(
|
||||
scene->params.bvh_layout, device->get_bvh_layout_mask(dscene->data.kernel_features));
|
||||
bparams.use_spatial_split = scene->params.use_bvh_spatial_split;
|
||||
bparams.use_unaligned_nodes = dscene->data.bvh.have_curves &&
|
||||
scene->params.use_bvh_unaligned_nodes;
|
||||
bparams.num_motion_triangle_steps = scene->params.num_bvh_time_steps;
|
||||
bparams.num_motion_curve_steps = scene->params.num_bvh_time_steps;
|
||||
bparams.num_motion_point_steps = scene->params.num_bvh_time_steps;
|
||||
bparams.bvh_type = scene->params.bvh_type;
|
||||
bparams.curve_subdivisions = scene->params.curve_subdivisions();
|
||||
|
||||
VLOG_INFO << "Using " << bvh_layout_name(bparams.bvh_layout) << " layout.";
|
||||
|
||||
const bool can_refit = scene->bvh != nullptr &&
|
||||
(bparams.bvh_layout == BVHLayout::BVH_LAYOUT_OPTIX ||
|
||||
bparams.bvh_layout == BVHLayout::BVH_LAYOUT_METAL);
|
||||
|
||||
BVH *bvh = scene->bvh;
|
||||
if (!scene->bvh) {
|
||||
bvh = scene->bvh = BVH::create(bparams, scene->geometry, scene->objects, device);
|
||||
}
|
||||
|
||||
device->build_bvh(bvh, progress, can_refit);
|
||||
|
||||
if (progress.get_cancel()) {
|
||||
return;
|
||||
}
|
||||
|
||||
const bool has_bvh2_layout = (bparams.bvh_layout == BVH_LAYOUT_BVH2);
|
||||
|
||||
PackedBVH pack;
|
||||
if (has_bvh2_layout) {
|
||||
pack = std::move(static_cast<BVH2 *>(bvh)->pack);
|
||||
}
|
||||
else {
|
||||
pack.root_index = -1;
|
||||
}
|
||||
|
||||
/* copy to device */
|
||||
progress.set_status("Updating Scene BVH", "Copying BVH to device");
|
||||
|
||||
/* When using BVH2, we always have to copy/update the data as its layout is dependent on the
|
||||
* BVH's leaf nodes which may be different when the objects or vertices move. */
|
||||
|
||||
if (pack.nodes.size()) {
|
||||
dscene->bvh_nodes.steal_data(pack.nodes);
|
||||
dscene->bvh_nodes.copy_to_device();
|
||||
}
|
||||
if (pack.leaf_nodes.size()) {
|
||||
dscene->bvh_leaf_nodes.steal_data(pack.leaf_nodes);
|
||||
dscene->bvh_leaf_nodes.copy_to_device();
|
||||
}
|
||||
if (pack.object_node.size()) {
|
||||
dscene->object_node.steal_data(pack.object_node);
|
||||
dscene->object_node.copy_to_device();
|
||||
}
|
||||
if (pack.prim_type.size()) {
|
||||
dscene->prim_type.steal_data(pack.prim_type);
|
||||
dscene->prim_type.copy_to_device();
|
||||
}
|
||||
if (pack.prim_visibility.size()) {
|
||||
dscene->prim_visibility.steal_data(pack.prim_visibility);
|
||||
dscene->prim_visibility.copy_to_device();
|
||||
}
|
||||
if (pack.prim_index.size()) {
|
||||
dscene->prim_index.steal_data(pack.prim_index);
|
||||
dscene->prim_index.copy_to_device();
|
||||
}
|
||||
if (pack.prim_object.size()) {
|
||||
dscene->prim_object.steal_data(pack.prim_object);
|
||||
dscene->prim_object.copy_to_device();
|
||||
}
|
||||
if (pack.prim_time.size()) {
|
||||
dscene->prim_time.steal_data(pack.prim_time);
|
||||
dscene->prim_time.copy_to_device();
|
||||
}
|
||||
|
||||
dscene->data.bvh.root = pack.root_index;
|
||||
dscene->data.bvh.use_bvh_steps = (scene->params.num_bvh_time_steps != 0);
|
||||
dscene->data.bvh.curve_subdivisions = scene->params.curve_subdivisions();
|
||||
/* The scene handle is set in 'CPUDevice::const_copy_to' and 'OptiXDevice::const_copy_to' */
|
||||
dscene->data.device_bvh = 0;
|
||||
}
|
||||
|
||||
CCL_NAMESPACE_END
|
|
@ -0,0 +1,223 @@
|
|||
/* SPDX-License-Identifier: Apache-2.0
|
||||
* Copyright 2011-2022 Blender Foundation */
|
||||
|
||||
#include "bvh/bvh.h"
|
||||
#include "bvh/bvh2.h"
|
||||
|
||||
#include "device/device.h"
|
||||
|
||||
#include "scene/attribute.h"
|
||||
#include "scene/camera.h"
|
||||
#include "scene/geometry.h"
|
||||
#include "scene/hair.h"
|
||||
#include "scene/light.h"
|
||||
#include "scene/mesh.h"
|
||||
#include "scene/object.h"
|
||||
#include "scene/osl.h"
|
||||
#include "scene/pointcloud.h"
|
||||
#include "scene/scene.h"
|
||||
#include "scene/shader.h"
|
||||
#include "scene/shader_nodes.h"
|
||||
#include "scene/stats.h"
|
||||
#include "scene/volume.h"
|
||||
|
||||
#include "subd/patch_table.h"
|
||||
#include "subd/split.h"
|
||||
|
||||
#ifdef WITH_OSL
|
||||
# include "kernel/osl/globals.h"
|
||||
#endif
|
||||
|
||||
#include "util/foreach.h"
|
||||
#include "util/log.h"
|
||||
#include "util/progress.h"
|
||||
#include "util/task.h"
|
||||
|
||||
CCL_NAMESPACE_BEGIN
|
||||
|
||||
void GeometryManager::device_update_mesh(Device *,
|
||||
DeviceScene *dscene,
|
||||
Scene *scene,
|
||||
Progress &progress)
|
||||
{
|
||||
/* Count. */
|
||||
size_t vert_size = 0;
|
||||
size_t tri_size = 0;
|
||||
|
||||
size_t curve_key_size = 0;
|
||||
size_t curve_size = 0;
|
||||
size_t curve_segment_size = 0;
|
||||
|
||||
size_t point_size = 0;
|
||||
|
||||
size_t patch_size = 0;
|
||||
|
||||
foreach (Geometry *geom, scene->geometry) {
|
||||
if (geom->geometry_type == Geometry::MESH || geom->geometry_type == Geometry::VOLUME) {
|
||||
Mesh *mesh = static_cast<Mesh *>(geom);
|
||||
|
||||
vert_size += mesh->verts.size();
|
||||
tri_size += mesh->num_triangles();
|
||||
|
||||
if (mesh->get_num_subd_faces()) {
|
||||
Mesh::SubdFace last = mesh->get_subd_face(mesh->get_num_subd_faces() - 1);
|
||||
patch_size += (last.ptex_offset + last.num_ptex_faces()) * 8;
|
||||
|
||||
/* patch tables are stored in same array so include them in patch_size */
|
||||
if (mesh->patch_table) {
|
||||
mesh->patch_table_offset = patch_size;
|
||||
patch_size += mesh->patch_table->total_size();
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (geom->is_hair()) {
|
||||
Hair *hair = static_cast<Hair *>(geom);
|
||||
|
||||
curve_key_size += hair->get_curve_keys().size();
|
||||
curve_size += hair->num_curves();
|
||||
curve_segment_size += hair->num_segments();
|
||||
}
|
||||
else if (geom->is_pointcloud()) {
|
||||
PointCloud *pointcloud = static_cast<PointCloud *>(geom);
|
||||
point_size += pointcloud->num_points();
|
||||
}
|
||||
}
|
||||
|
||||
/* Fill in all the arrays. */
|
||||
if (tri_size != 0) {
|
||||
/* normals */
|
||||
progress.set_status("Updating Mesh", "Computing normals");
|
||||
|
||||
packed_float3 *tri_verts = dscene->tri_verts.alloc(vert_size);
|
||||
uint *tri_shader = dscene->tri_shader.alloc(tri_size);
|
||||
packed_float3 *vnormal = dscene->tri_vnormal.alloc(vert_size);
|
||||
packed_uint3 *tri_vindex = dscene->tri_vindex.alloc(tri_size);
|
||||
uint *tri_patch = dscene->tri_patch.alloc(tri_size);
|
||||
float2 *tri_patch_uv = dscene->tri_patch_uv.alloc(vert_size);
|
||||
|
||||
const bool copy_all_data = dscene->tri_shader.need_realloc() ||
|
||||
dscene->tri_vindex.need_realloc() ||
|
||||
dscene->tri_vnormal.need_realloc() ||
|
||||
dscene->tri_patch.need_realloc() ||
|
||||
dscene->tri_patch_uv.need_realloc();
|
||||
|
||||
foreach (Geometry *geom, scene->geometry) {
|
||||
if (geom->geometry_type == Geometry::MESH || geom->geometry_type == Geometry::VOLUME) {
|
||||
Mesh *mesh = static_cast<Mesh *>(geom);
|
||||
|
||||
if (mesh->shader_is_modified() || mesh->smooth_is_modified() ||
|
||||
mesh->triangles_is_modified() || copy_all_data) {
|
||||
mesh->pack_shaders(scene, &tri_shader[mesh->prim_offset]);
|
||||
}
|
||||
|
||||
if (mesh->verts_is_modified() || copy_all_data) {
|
||||
mesh->pack_normals(&vnormal[mesh->vert_offset]);
|
||||
}
|
||||
|
||||
if (mesh->verts_is_modified() || mesh->triangles_is_modified() ||
|
||||
mesh->vert_patch_uv_is_modified() || copy_all_data) {
|
||||
mesh->pack_verts(&tri_verts[mesh->vert_offset],
|
||||
&tri_vindex[mesh->prim_offset],
|
||||
&tri_patch[mesh->prim_offset],
|
||||
&tri_patch_uv[mesh->vert_offset]);
|
||||
}
|
||||
|
||||
if (progress.get_cancel())
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
/* vertex coordinates */
|
||||
progress.set_status("Updating Mesh", "Copying Mesh to device");
|
||||
|
||||
dscene->tri_verts.copy_to_device_if_modified();
|
||||
dscene->tri_shader.copy_to_device_if_modified();
|
||||
dscene->tri_vnormal.copy_to_device_if_modified();
|
||||
dscene->tri_vindex.copy_to_device_if_modified();
|
||||
dscene->tri_patch.copy_to_device_if_modified();
|
||||
dscene->tri_patch_uv.copy_to_device_if_modified();
|
||||
}
|
||||
|
||||
if (curve_segment_size != 0) {
|
||||
progress.set_status("Updating Mesh", "Copying Curves to device");
|
||||
|
||||
float4 *curve_keys = dscene->curve_keys.alloc(curve_key_size);
|
||||
KernelCurve *curves = dscene->curves.alloc(curve_size);
|
||||
KernelCurveSegment *curve_segments = dscene->curve_segments.alloc(curve_segment_size);
|
||||
|
||||
const bool copy_all_data = dscene->curve_keys.need_realloc() ||
|
||||
dscene->curves.need_realloc() ||
|
||||
dscene->curve_segments.need_realloc();
|
||||
|
||||
foreach (Geometry *geom, scene->geometry) {
|
||||
if (geom->is_hair()) {
|
||||
Hair *hair = static_cast<Hair *>(geom);
|
||||
|
||||
bool curve_keys_co_modified = hair->curve_radius_is_modified() ||
|
||||
hair->curve_keys_is_modified();
|
||||
bool curve_data_modified = hair->curve_shader_is_modified() ||
|
||||
hair->curve_first_key_is_modified();
|
||||
|
||||
if (!curve_keys_co_modified && !curve_data_modified && !copy_all_data) {
|
||||
continue;
|
||||
}
|
||||
|
||||
hair->pack_curves(scene,
|
||||
&curve_keys[hair->curve_key_offset],
|
||||
&curves[hair->prim_offset],
|
||||
&curve_segments[hair->curve_segment_offset]);
|
||||
if (progress.get_cancel())
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
dscene->curve_keys.copy_to_device_if_modified();
|
||||
dscene->curves.copy_to_device_if_modified();
|
||||
dscene->curve_segments.copy_to_device_if_modified();
|
||||
}
|
||||
|
||||
if (point_size != 0) {
|
||||
progress.set_status("Updating Mesh", "Copying Point clouds to device");
|
||||
|
||||
float4 *points = dscene->points.alloc(point_size);
|
||||
uint *points_shader = dscene->points_shader.alloc(point_size);
|
||||
|
||||
foreach (Geometry *geom, scene->geometry) {
|
||||
if (geom->is_pointcloud()) {
|
||||
PointCloud *pointcloud = static_cast<PointCloud *>(geom);
|
||||
pointcloud->pack(
|
||||
scene, &points[pointcloud->prim_offset], &points_shader[pointcloud->prim_offset]);
|
||||
if (progress.get_cancel())
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
dscene->points.copy_to_device();
|
||||
dscene->points_shader.copy_to_device();
|
||||
}
|
||||
|
||||
if (patch_size != 0 && dscene->patches.need_realloc()) {
|
||||
progress.set_status("Updating Mesh", "Copying Patches to device");
|
||||
|
||||
uint *patch_data = dscene->patches.alloc(patch_size);
|
||||
|
||||
foreach (Geometry *geom, scene->geometry) {
|
||||
if (geom->is_mesh()) {
|
||||
Mesh *mesh = static_cast<Mesh *>(geom);
|
||||
mesh->pack_patches(&patch_data[mesh->patch_offset]);
|
||||
|
||||
if (mesh->patch_table) {
|
||||
mesh->patch_table->copy_adjusting_offsets(&patch_data[mesh->patch_table_offset],
|
||||
mesh->patch_table_offset);
|
||||
}
|
||||
|
||||
if (progress.get_cancel())
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
dscene->patches.copy_to_device();
|
||||
}
|
||||
}
|
||||
|
||||
CCL_NAMESPACE_END
|
|
@ -24,6 +24,7 @@
|
|||
#include "scene/svm.h"
|
||||
#include "scene/tables.h"
|
||||
#include "scene/volume.h"
|
||||
#include "scene/devicescene.h"
|
||||
#include "session/session.h"
|
||||
|
||||
#include "util/foreach.h"
|
||||
|
@ -33,59 +34,7 @@
|
|||
|
||||
CCL_NAMESPACE_BEGIN
|
||||
|
||||
DeviceScene::DeviceScene(Device *device)
|
||||
: bvh_nodes(device, "bvh_nodes", MEM_GLOBAL),
|
||||
bvh_leaf_nodes(device, "bvh_leaf_nodes", MEM_GLOBAL),
|
||||
object_node(device, "object_node", MEM_GLOBAL),
|
||||
prim_type(device, "prim_type", MEM_GLOBAL),
|
||||
prim_visibility(device, "prim_visibility", MEM_GLOBAL),
|
||||
prim_index(device, "prim_index", MEM_GLOBAL),
|
||||
prim_object(device, "prim_object", MEM_GLOBAL),
|
||||
prim_time(device, "prim_time", MEM_GLOBAL),
|
||||
tri_verts(device, "tri_verts", MEM_GLOBAL),
|
||||
tri_shader(device, "tri_shader", MEM_GLOBAL),
|
||||
tri_vnormal(device, "tri_vnormal", MEM_GLOBAL),
|
||||
tri_vindex(device, "tri_vindex", MEM_GLOBAL),
|
||||
tri_patch(device, "tri_patch", MEM_GLOBAL),
|
||||
tri_patch_uv(device, "tri_patch_uv", MEM_GLOBAL),
|
||||
curves(device, "curves", MEM_GLOBAL),
|
||||
curve_keys(device, "curve_keys", MEM_GLOBAL),
|
||||
curve_segments(device, "curve_segments", MEM_GLOBAL),
|
||||
patches(device, "patches", MEM_GLOBAL),
|
||||
points(device, "points", MEM_GLOBAL),
|
||||
points_shader(device, "points_shader", MEM_GLOBAL),
|
||||
objects(device, "objects", MEM_GLOBAL),
|
||||
object_motion_pass(device, "object_motion_pass", MEM_GLOBAL),
|
||||
object_motion(device, "object_motion", MEM_GLOBAL),
|
||||
object_flag(device, "object_flag", MEM_GLOBAL),
|
||||
object_volume_step(device, "object_volume_step", MEM_GLOBAL),
|
||||
object_prim_offset(device, "object_prim_offset", MEM_GLOBAL),
|
||||
camera_motion(device, "camera_motion", MEM_GLOBAL),
|
||||
attributes_map(device, "attributes_map", MEM_GLOBAL),
|
||||
attributes_float(device, "attributes_float", MEM_GLOBAL),
|
||||
attributes_float2(device, "attributes_float2", MEM_GLOBAL),
|
||||
attributes_float3(device, "attributes_float3", MEM_GLOBAL),
|
||||
attributes_float4(device, "attributes_float4", MEM_GLOBAL),
|
||||
attributes_uchar4(device, "attributes_uchar4", MEM_GLOBAL),
|
||||
light_distribution(device, "light_distribution", MEM_GLOBAL),
|
||||
lights(device, "lights", MEM_GLOBAL),
|
||||
light_background_marginal_cdf(device, "light_background_marginal_cdf", MEM_GLOBAL),
|
||||
light_background_conditional_cdf(device, "light_background_conditional_cdf", MEM_GLOBAL),
|
||||
light_tree_nodes(device, "light_tree_nodes", MEM_GLOBAL),
|
||||
light_tree_emitters(device, "light_tree_emitters", MEM_GLOBAL),
|
||||
light_to_tree(device, "light_to_tree", MEM_GLOBAL),
|
||||
object_to_tree(device, "object_to_tree", MEM_GLOBAL),
|
||||
object_lookup_offset(device, "object_lookup_offset", MEM_GLOBAL),
|
||||
triangle_to_tree(device, "triangle_to_tree", MEM_GLOBAL),
|
||||
particles(device, "particles", MEM_GLOBAL),
|
||||
svm_nodes(device, "svm_nodes", MEM_GLOBAL),
|
||||
shaders(device, "shaders", MEM_GLOBAL),
|
||||
lookup_table(device, "lookup_table", MEM_GLOBAL),
|
||||
sample_pattern_lut(device, "sample_pattern_lut", MEM_GLOBAL),
|
||||
ies_lights(device, "ies", MEM_GLOBAL)
|
||||
{
|
||||
memset((void *)&data, 0, sizeof(data));
|
||||
}
|
||||
|
||||
|
||||
Scene::Scene(const SceneParams ¶ms_, Device *device)
|
||||
: name("Scene"),
|
||||
|
|
|
@ -6,20 +6,16 @@
|
|||
|
||||
#include "bvh/params.h"
|
||||
|
||||
#include "scene/devicescene.h"
|
||||
#include "scene/film.h"
|
||||
#include "scene/image.h"
|
||||
#include "scene/shader.h"
|
||||
|
||||
#include "device/device.h"
|
||||
#include "device/memory.h"
|
||||
|
||||
#include "util/param.h"
|
||||
#include "util/string.h"
|
||||
#include "util/system.h"
|
||||
#include "util/texture.h"
|
||||
#include "util/thread.h"
|
||||
#include "util/types.h"
|
||||
#include "util/vector.h"
|
||||
|
||||
CCL_NAMESPACE_BEGIN
|
||||
|
||||
|
@ -54,92 +50,6 @@ class RenderStats;
|
|||
class SceneUpdateStats;
|
||||
class Volume;
|
||||
|
||||
/* Scene Device Data */
|
||||
|
||||
class DeviceScene {
|
||||
public:
|
||||
/* BVH */
|
||||
device_vector<int4> bvh_nodes;
|
||||
device_vector<int4> bvh_leaf_nodes;
|
||||
device_vector<int> object_node;
|
||||
device_vector<int> prim_type;
|
||||
device_vector<uint> prim_visibility;
|
||||
device_vector<int> prim_index;
|
||||
device_vector<int> prim_object;
|
||||
device_vector<float2> prim_time;
|
||||
|
||||
/* mesh */
|
||||
device_vector<packed_float3> tri_verts;
|
||||
device_vector<uint> tri_shader;
|
||||
device_vector<packed_float3> tri_vnormal;
|
||||
device_vector<packed_uint3> tri_vindex;
|
||||
device_vector<uint> tri_patch;
|
||||
device_vector<float2> tri_patch_uv;
|
||||
|
||||
device_vector<KernelCurve> curves;
|
||||
device_vector<float4> curve_keys;
|
||||
device_vector<KernelCurveSegment> curve_segments;
|
||||
|
||||
device_vector<uint> patches;
|
||||
|
||||
/* point-cloud */
|
||||
device_vector<float4> points;
|
||||
device_vector<uint> points_shader;
|
||||
|
||||
/* objects */
|
||||
device_vector<KernelObject> objects;
|
||||
device_vector<Transform> object_motion_pass;
|
||||
device_vector<DecomposedTransform> object_motion;
|
||||
device_vector<uint> object_flag;
|
||||
device_vector<float> object_volume_step;
|
||||
device_vector<uint> object_prim_offset;
|
||||
|
||||
/* cameras */
|
||||
device_vector<DecomposedTransform> camera_motion;
|
||||
|
||||
/* attributes */
|
||||
device_vector<AttributeMap> attributes_map;
|
||||
device_vector<float> attributes_float;
|
||||
device_vector<float2> attributes_float2;
|
||||
device_vector<packed_float3> attributes_float3;
|
||||
device_vector<float4> attributes_float4;
|
||||
device_vector<uchar4> attributes_uchar4;
|
||||
|
||||
/* lights */
|
||||
device_vector<KernelLightDistribution> light_distribution;
|
||||
device_vector<KernelLight> lights;
|
||||
device_vector<float2> light_background_marginal_cdf;
|
||||
device_vector<float2> light_background_conditional_cdf;
|
||||
|
||||
/* light tree */
|
||||
device_vector<KernelLightTreeNode> light_tree_nodes;
|
||||
device_vector<KernelLightTreeEmitter> light_tree_emitters;
|
||||
device_vector<uint> light_to_tree;
|
||||
device_vector<uint> object_to_tree;
|
||||
device_vector<uint> object_lookup_offset;
|
||||
device_vector<uint> triangle_to_tree;
|
||||
|
||||
/* particles */
|
||||
device_vector<KernelParticle> particles;
|
||||
|
||||
/* shaders */
|
||||
device_vector<int4> svm_nodes;
|
||||
device_vector<KernelShader> shaders;
|
||||
|
||||
/* lookup tables */
|
||||
device_vector<float> lookup_table;
|
||||
|
||||
/* integrator */
|
||||
device_vector<float> sample_pattern_lut;
|
||||
|
||||
/* IES lights */
|
||||
device_vector<float> ies_lights;
|
||||
|
||||
KernelData data;
|
||||
|
||||
DeviceScene(Device *device);
|
||||
};
|
||||
|
||||
/* Scene Parameters */
|
||||
|
||||
class SceneParams {
|
||||
|
|
|
@ -72,7 +72,7 @@ class GHOST_ISystemPaths {
|
|||
/**
|
||||
* Add the file to the operating system most recently used files
|
||||
*/
|
||||
virtual void addToSystemRecentFiles(const char *filename) const = 0;
|
||||
virtual void addToSystemRecentFiles(const char *filepath) const = 0;
|
||||
|
||||
private:
|
||||
/** The one and only system paths. */
|
||||
|
|
|
@ -61,7 +61,7 @@ extern const char *GHOST_getBinaryDir(void);
|
|||
/**
|
||||
* Add the file to the operating system most recently used files
|
||||
*/
|
||||
extern void GHOST_addToSystemRecentFiles(const char *filename);
|
||||
extern void GHOST_addToSystemRecentFiles(const char *filepath);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
|
|
|
@ -10,6 +10,9 @@
|
|||
#include "GHOST_Context.hh"
|
||||
|
||||
#ifdef _WIN32
|
||||
# define WIN32_LEAN_AND_MEAN
|
||||
# include <windows.h>
|
||||
|
||||
# include <epoxy/wgl.h>
|
||||
# include <tchar.h>
|
||||
#
|
||||
|
@ -18,6 +21,8 @@
|
|||
# endif
|
||||
#endif
|
||||
|
||||
#include <epoxy/gl.h>
|
||||
|
||||
#include <cstdio>
|
||||
#include <cstring>
|
||||
|
||||
|
|
|
@ -11,8 +11,6 @@
|
|||
#include "GHOST_IContext.hh"
|
||||
#include "GHOST_Types.h"
|
||||
|
||||
#include <epoxy/gl.h>
|
||||
|
||||
#include <cstdlib> // for NULL
|
||||
|
||||
class GHOST_Context : public GHOST_IContext {
|
||||
|
|
|
@ -19,6 +19,8 @@
|
|||
#include <Metal/Metal.h>
|
||||
#include <QuartzCore/QuartzCore.h>
|
||||
|
||||
#include <epoxy/gl.h>
|
||||
|
||||
#include <cassert>
|
||||
#include <vector>
|
||||
|
||||
|
|
|
@ -903,13 +903,14 @@ GHOST_TSuccess GHOST_ContextVK::initializeDrawingContext()
|
|||
auto extensions_available = getExtensionsAvailable();
|
||||
|
||||
vector<const char *> layers_enabled;
|
||||
if (m_debug) {
|
||||
enableLayer(layers_available, layers_enabled, VkLayer::KHRONOS_validation, m_debug);
|
||||
}
|
||||
|
||||
vector<const char *> extensions_device;
|
||||
vector<const char *> extensions_enabled;
|
||||
|
||||
if (m_debug) {
|
||||
enableLayer(layers_available, layers_enabled, VkLayer::KHRONOS_validation, m_debug);
|
||||
requireExtension(extensions_available, extensions_enabled, VK_EXT_DEBUG_UTILS_EXTENSION_NAME);
|
||||
}
|
||||
|
||||
if (use_window_surface) {
|
||||
const char *native_surface_extension_name = getPlatformSpecificSurfaceExtension();
|
||||
|
||||
|
|
|
@ -57,4 +57,4 @@ class GHOST_IXrGraphicsBinding {
|
|||
};
|
||||
|
||||
std::unique_ptr<GHOST_IXrGraphicsBinding> GHOST_XrGraphicsBindingCreateFromType(
|
||||
GHOST_TXrGraphicsBinding type, GHOST_Context &ghost_ctx);
|
||||
GHOST_TXrGraphicsBinding type, GHOST_Context &context);
|
||||
|
|
|
@ -47,5 +47,5 @@ class GHOST_SystemPaths : public GHOST_ISystemPaths {
|
|||
/**
|
||||
* Add the file to the operating system most recently used files
|
||||
*/
|
||||
virtual void addToSystemRecentFiles(const char *filename) const = 0;
|
||||
virtual void addToSystemRecentFiles(const char *filepath) const = 0;
|
||||
};
|
||||
|
|
|
@ -54,5 +54,5 @@ class GHOST_SystemPathsCocoa : public GHOST_SystemPaths {
|
|||
/**
|
||||
* Add the file to the operating system most recently used files
|
||||
*/
|
||||
void addToSystemRecentFiles(const char *filename) const;
|
||||
void addToSystemRecentFiles(const char *filepath) const;
|
||||
};
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
/* SPDX-License-Identifier: GPL-2.0-or-later
|
||||
* Copyright 2010 Blender Foundation */
|
||||
|
||||
#import <AppKit/NSDocumentController.h>
|
||||
#import <Foundation/Foundation.h>
|
||||
|
||||
#include "GHOST_Debug.hh"
|
||||
|
@ -112,7 +113,10 @@ const char *GHOST_SystemPathsCocoa::getBinaryDir() const
|
|||
return tempPath;
|
||||
}
|
||||
|
||||
void GHOST_SystemPathsCocoa::addToSystemRecentFiles(const char *filename) const
|
||||
void GHOST_SystemPathsCocoa::addToSystemRecentFiles(const char *filepath) const
|
||||
{
|
||||
/* TODO: implement for macOS */
|
||||
@autoreleasepool {
|
||||
NSURL *const file_url = [NSURL fileURLWithPath:[NSString stringWithUTF8String:filepath]];
|
||||
[[NSDocumentController sharedDocumentController] noteNewRecentDocumentURL:file_url];
|
||||
}
|
||||
}
|
||||
|
|
|
@ -52,5 +52,5 @@ class GHOST_SystemPathsUnix : public GHOST_SystemPaths {
|
|||
/**
|
||||
* Add the file to the operating system most recently used files
|
||||
*/
|
||||
void addToSystemRecentFiles(const char *filename) const;
|
||||
void addToSystemRecentFiles(const char *filepath) const;
|
||||
};
|
||||
|
|
|
@ -117,10 +117,10 @@ const char *GHOST_SystemPathsWin32::getBinaryDir() const
|
|||
return NULL;
|
||||
}
|
||||
|
||||
void GHOST_SystemPathsWin32::addToSystemRecentFiles(const char *filename) const
|
||||
void GHOST_SystemPathsWin32::addToSystemRecentFiles(const char *filepath) const
|
||||
{
|
||||
/* SHARD_PATH resolves to SHARD_PATHA for non-UNICODE build */
|
||||
UTF16_ENCODE(filename);
|
||||
SHAddToRecentDocs(SHARD_PATHW, filename_16);
|
||||
UTF16_UN_ENCODE(filename);
|
||||
UTF16_ENCODE(filepath);
|
||||
SHAddToRecentDocs(SHARD_PATHW, filepath_16);
|
||||
UTF16_UN_ENCODE(filepath);
|
||||
}
|
||||
|
|
|
@ -61,5 +61,5 @@ class GHOST_SystemPathsWin32 : public GHOST_SystemPaths {
|
|||
/**
|
||||
* Add the file to the operating system most recently used files
|
||||
*/
|
||||
void addToSystemRecentFiles(const char *filename) const;
|
||||
void addToSystemRecentFiles(const char *filepath) const;
|
||||
};
|
||||
|
|
|
@ -5586,8 +5586,6 @@ GHOST_SystemWayland::GHOST_SystemWayland(bool background)
|
|||
# endif
|
||||
display_destroy_and_free_all();
|
||||
throw std::runtime_error("Wayland: unable to find libdecor!");
|
||||
|
||||
use_libdecor = true;
|
||||
}
|
||||
}
|
||||
else {
|
||||
|
@ -5610,7 +5608,7 @@ GHOST_SystemWayland::GHOST_SystemWayland(bool background)
|
|||
(void)background;
|
||||
#endif
|
||||
{
|
||||
GWL_XDG_Decor_System &decor = *display_->xdg_decor;
|
||||
const GWL_XDG_Decor_System &decor = *display_->xdg_decor;
|
||||
if (!decor.shell) {
|
||||
display_destroy_and_free_all();
|
||||
throw std::runtime_error("Wayland: unable to access xdg_shell!");
|
||||
|
@ -6071,10 +6069,8 @@ static GHOST_TSuccess getCursorPositionClientRelative_impl(
|
|||
/* As the cursor is restored at the warped location,
|
||||
* apply warping when requesting the cursor location. */
|
||||
GHOST_Rect wrap_bounds{};
|
||||
if (win->getCursorGrabModeIsWarp()) {
|
||||
if (win->getCursorGrabBounds(wrap_bounds) == GHOST_kFailure) {
|
||||
win->getClientBounds(wrap_bounds);
|
||||
}
|
||||
if (win->getCursorGrabBounds(wrap_bounds) == GHOST_kFailure) {
|
||||
win->getClientBounds(wrap_bounds);
|
||||
}
|
||||
int xy_wrap[2] = {
|
||||
seat_state_pointer->xy[0],
|
||||
|
@ -6680,10 +6676,9 @@ GHOST_TSuccess GHOST_SystemWayland::cursor_shape_custom_set(uint8_t *bitmap,
|
|||
static constexpr uint32_t transparent = 0x00000000;
|
||||
|
||||
uint8_t datab = 0, maskb = 0;
|
||||
uint32_t *pixel;
|
||||
|
||||
for (int y = 0; y < sizey; ++y) {
|
||||
pixel = &static_cast<uint32_t *>(cursor->custom_data)[y * sizex];
|
||||
uint32_t *pixel = &static_cast<uint32_t *>(cursor->custom_data)[y * sizex];
|
||||
for (int x = 0; x < sizex; ++x) {
|
||||
if ((x % 8) == 0) {
|
||||
datab = *bitmap++;
|
||||
|
|
|
@ -37,15 +37,15 @@ bool ghost_wl_output_own(const struct wl_output *wl_output);
|
|||
void ghost_wl_output_tag(struct wl_output *wl_output);
|
||||
struct GWL_Output *ghost_wl_output_user_data(struct wl_output *wl_output);
|
||||
|
||||
bool ghost_wl_surface_own(const struct wl_surface *surface);
|
||||
void ghost_wl_surface_tag(struct wl_surface *surface);
|
||||
GHOST_WindowWayland *ghost_wl_surface_user_data(struct wl_surface *surface);
|
||||
bool ghost_wl_surface_own(const struct wl_surface *wl_surface);
|
||||
void ghost_wl_surface_tag(struct wl_surface *wl_surface);
|
||||
GHOST_WindowWayland *ghost_wl_surface_user_data(struct wl_surface *wl_surface);
|
||||
|
||||
bool ghost_wl_surface_own_cursor_pointer(const struct wl_surface *surface);
|
||||
void ghost_wl_surface_tag_cursor_pointer(struct wl_surface *surface);
|
||||
bool ghost_wl_surface_own_cursor_pointer(const struct wl_surface *wl_surface);
|
||||
void ghost_wl_surface_tag_cursor_pointer(struct wl_surface *wl_surface);
|
||||
|
||||
bool ghost_wl_surface_own_cursor_tablet(const struct wl_surface *surface);
|
||||
void ghost_wl_surface_tag_cursor_tablet(struct wl_surface *surface);
|
||||
bool ghost_wl_surface_own_cursor_tablet(const struct wl_surface *wl_surface);
|
||||
void ghost_wl_surface_tag_cursor_tablet(struct wl_surface *wl_surface);
|
||||
|
||||
/* Scaling to: translates from WAYLAND into GHOST (viewport local) coordinates.
|
||||
* Scaling from: performs the reverse translation.
|
||||
|
|
|
@ -85,7 +85,7 @@ static uchar bit_is_on(const uchar *ptr, int bit)
|
|||
|
||||
static GHOST_TKey ghost_key_from_keysym(const KeySym key);
|
||||
static GHOST_TKey ghost_key_from_keycode(const XkbDescPtr xkb_descr, const KeyCode keycode);
|
||||
static GHOST_TKey ghost_key_from_keysym_or_keycode(const KeySym key,
|
||||
static GHOST_TKey ghost_key_from_keysym_or_keycode(const KeySym key_sym,
|
||||
const XkbDescPtr xkb_descr,
|
||||
const KeyCode keycode);
|
||||
|
||||
|
@ -99,7 +99,12 @@ static bool use_xwayland_hack = false;
|
|||
|
||||
using namespace std;
|
||||
|
||||
GHOST_SystemX11::GHOST_SystemX11() : GHOST_System(), m_xkb_descr(nullptr), m_start_time(0)
|
||||
GHOST_SystemX11::GHOST_SystemX11()
|
||||
: GHOST_System(),
|
||||
m_xkb_descr(nullptr),
|
||||
m_start_time(0),
|
||||
m_keyboard_vector{0},
|
||||
m_keycode_last_repeat_key(uint(-1))
|
||||
{
|
||||
XInitThreads();
|
||||
m_display = XOpenDisplay(nullptr);
|
||||
|
@ -897,7 +902,7 @@ void GHOST_SystemX11::processEvent(XEvent *xe)
|
|||
#endif /* WITH_X11_XINPUT */
|
||||
switch (xe->type) {
|
||||
case Expose: {
|
||||
XExposeEvent &xee = xe->xexpose;
|
||||
const XExposeEvent &xee = xe->xexpose;
|
||||
|
||||
if (xee.count == 0) {
|
||||
/* Only generate a single expose event
|
||||
|
@ -909,7 +914,7 @@ void GHOST_SystemX11::processEvent(XEvent *xe)
|
|||
}
|
||||
|
||||
case MotionNotify: {
|
||||
XMotionEvent &xme = xe->xmotion;
|
||||
const XMotionEvent &xme = xe->xmotion;
|
||||
|
||||
bool is_tablet = window->GetTabletData().Active != GHOST_kTabletModeNone;
|
||||
|
||||
|
@ -1235,7 +1240,7 @@ void GHOST_SystemX11::processEvent(XEvent *xe)
|
|||
|
||||
case ButtonPress:
|
||||
case ButtonRelease: {
|
||||
XButtonEvent &xbe = xe->xbutton;
|
||||
const XButtonEvent &xbe = xe->xbutton;
|
||||
GHOST_TButton gbmask = GHOST_kButtonMaskLeft;
|
||||
GHOST_TEventType type = (xbe.type == ButtonPress) ? GHOST_kEventButtonDown :
|
||||
GHOST_kEventButtonUp;
|
||||
|
@ -1290,14 +1295,14 @@ void GHOST_SystemX11::processEvent(XEvent *xe)
|
|||
|
||||
/* change of size, border, layer etc. */
|
||||
case ConfigureNotify: {
|
||||
// XConfigureEvent & xce = xe->xconfigure;
|
||||
// const XConfigureEvent & xce = xe->xconfigure;
|
||||
g_event = new GHOST_Event(getMilliSeconds(), GHOST_kEventWindowSize, window);
|
||||
break;
|
||||
}
|
||||
|
||||
case FocusIn:
|
||||
case FocusOut: {
|
||||
XFocusChangeEvent &xfe = xe->xfocus;
|
||||
const XFocusChangeEvent &xfe = xe->xfocus;
|
||||
|
||||
/* TODO: make sure this is the correct place for activate/deactivate */
|
||||
// printf("X: focus %s for window %d\n",
|
||||
|
@ -1385,7 +1390,7 @@ void GHOST_SystemX11::processEvent(XEvent *xe)
|
|||
* (really crossing between windows) since some window-managers
|
||||
* also send grab/un-grab crossings for mouse-wheel events.
|
||||
*/
|
||||
XCrossingEvent &xce = xe->xcrossing;
|
||||
const XCrossingEvent &xce = xe->xcrossing;
|
||||
if (xce.mode == NotifyNormal) {
|
||||
g_event = new GHOST_EventCursor(getMilliSeconds(),
|
||||
GHOST_kEventCursorMove,
|
||||
|
@ -1776,11 +1781,11 @@ bool GHOST_SystemX11::generateWindowExposeEvents()
|
|||
return anyProcessed;
|
||||
}
|
||||
|
||||
static GHOST_TKey ghost_key_from_keysym_or_keycode(const KeySym keysym,
|
||||
static GHOST_TKey ghost_key_from_keysym_or_keycode(const KeySym key_sym,
|
||||
XkbDescPtr xkb_descr,
|
||||
const KeyCode keycode)
|
||||
{
|
||||
GHOST_TKey type = ghost_key_from_keysym(keysym);
|
||||
GHOST_TKey type = ghost_key_from_keysym(key_sym);
|
||||
if (type == GHOST_kKeyUnknown) {
|
||||
if (xkb_descr) {
|
||||
type = ghost_key_from_keycode(xkb_descr, keycode);
|
||||
|
@ -2376,7 +2381,7 @@ class DialogData {
|
|||
}
|
||||
|
||||
/* Is the mouse inside the given button */
|
||||
bool isInsideButton(XEvent &e, uint button_num)
|
||||
bool isInsideButton(const XEvent &e, uint button_num)
|
||||
{
|
||||
return (
|
||||
(e.xmotion.y > int(height - padding_y - button_height)) &&
|
||||
|
|
|
@ -31,7 +31,7 @@
|
|||
#endif
|
||||
|
||||
/* generic error handlers */
|
||||
int GHOST_X11_ApplicationErrorHandler(Display *display, XErrorEvent *theEvent);
|
||||
int GHOST_X11_ApplicationErrorHandler(Display *display, XErrorEvent *event);
|
||||
int GHOST_X11_ApplicationIOErrorHandler(Display *display);
|
||||
|
||||
#define GHOST_X11_ERROR_HANDLERS_OVERRIDE(var) \
|
||||
|
|
|
@ -21,22 +21,21 @@ GHOST_Window::GHOST_Window(uint32_t width,
|
|||
const bool wantStereoVisual,
|
||||
const bool /*exclusive*/)
|
||||
: m_drawingContextType(GHOST_kDrawingContextTypeNone),
|
||||
m_userData(nullptr),
|
||||
m_cursorVisible(true),
|
||||
m_cursorGrab(GHOST_kGrabDisable),
|
||||
m_cursorGrabAxis(GHOST_kAxisNone),
|
||||
m_cursorGrabInitPos{0, 0},
|
||||
m_cursorGrabAccumPos{0, 0},
|
||||
m_cursorShape(GHOST_kStandardCursorDefault),
|
||||
m_progressBarVisible(false),
|
||||
m_canAcceptDragOperation(false),
|
||||
m_isUnsavedChanges(false),
|
||||
m_wantStereoVisual(wantStereoVisual),
|
||||
m_nativePixelSize(1.0f),
|
||||
m_context(new GHOST_ContextNone(false))
|
||||
|
||||
{
|
||||
m_isUnsavedChanges = false;
|
||||
m_canAcceptDragOperation = false;
|
||||
|
||||
m_progressBarVisible = false;
|
||||
|
||||
m_cursorGrabAccumPos[0] = 0;
|
||||
m_cursorGrabAccumPos[1] = 0;
|
||||
|
||||
m_nativePixelSize = 1.0f;
|
||||
|
||||
m_fullScreen = state == GHOST_kWindowStateFullScreen;
|
||||
if (m_fullScreen) {
|
||||
|
|
|
@ -155,7 +155,7 @@ class GHOST_Window : public GHOST_IWindow {
|
|||
GHOST_TSuccess getCursorGrabBounds(GHOST_Rect &bounds) const override;
|
||||
|
||||
void getCursorGrabState(GHOST_TGrabCursorMode &mode,
|
||||
GHOST_TAxisFlag &axis_flag,
|
||||
GHOST_TAxisFlag &wrap_axis,
|
||||
GHOST_Rect &bounds,
|
||||
bool &use_software_cursor) override;
|
||||
/**
|
||||
|
|
|
@ -89,11 +89,13 @@ class GHOST_XrAction {
|
|||
const XrTime &predicted_display_time);
|
||||
void applyHapticFeedback(XrSession session,
|
||||
const char *action_name,
|
||||
const char *subaction_path,
|
||||
const char *subaction_path_str,
|
||||
const int64_t &duration,
|
||||
const float &frequency,
|
||||
const float &litude);
|
||||
void stopHapticFeedback(XrSession session, const char *action_name, const char *subaction_path);
|
||||
void stopHapticFeedback(XrSession session,
|
||||
const char *action_name,
|
||||
const char *subaction_path_str);
|
||||
|
||||
void *getCustomdata();
|
||||
void getBindings(std::map<XrPath, std::vector<XrActionSuggestedBinding>> &r_bindings) const;
|
||||
|
|
Binary file not shown.
Binary file not shown.
Binary file not shown.
|
@ -204,6 +204,9 @@ _km_hierarchy = [
|
|||
('Grease Pencil Stroke Sculpt (Clone)', 'EMPTY', 'WINDOW', []),
|
||||
('Grease Pencil Stroke Weight Mode', 'EMPTY', 'WINDOW', []),
|
||||
('Grease Pencil Stroke Weight (Draw)', 'EMPTY', 'WINDOW', []),
|
||||
('Grease Pencil Stroke Weight (Blur)', 'EMPTY', 'WINDOW', []),
|
||||
('Grease Pencil Stroke Weight (Average)', 'EMPTY', 'WINDOW', []),
|
||||
('Grease Pencil Stroke Weight (Smear)', 'EMPTY', 'WINDOW', []),
|
||||
('Grease Pencil Stroke Vertex Mode', 'EMPTY', 'WINDOW', []),
|
||||
('Grease Pencil Stroke Vertex (Draw)', 'EMPTY', 'WINDOW', []),
|
||||
('Grease Pencil Stroke Vertex (Blur)', 'EMPTY', 'WINDOW', []),
|
||||
|
|
|
@ -4178,6 +4178,9 @@ def km_grease_pencil_stroke_weight_mode(params):
|
|||
# Brush size
|
||||
("wm.radial_control", {"type": 'F', "value": 'PRESS'},
|
||||
{"properties": [("data_path_primary", 'tool_settings.gpencil_weight_paint.brush.size')]}),
|
||||
# Brush weight
|
||||
("wm.radial_control", {"type": 'F', "value": 'PRESS', "ctrl": True},
|
||||
{"properties": [("data_path_primary", 'tool_settings.gpencil_weight_paint.brush.weight')]}),
|
||||
# Increase/Decrease brush size
|
||||
("brush.scale_size", {"type": 'LEFT_BRACKET', "value": 'PRESS', "repeat": True},
|
||||
{"properties": [("scalar", 0.9)]}),
|
||||
|
@ -4197,6 +4200,10 @@ def km_grease_pencil_stroke_weight_mode(params):
|
|||
op_menu("VIEW3D_MT_gpencil_animation", {"type": 'I', "value": 'PRESS'}),
|
||||
# Context menu
|
||||
*_template_items_context_panel("VIEW3D_PT_gpencil_weight_context_menu", params.context_menu_event),
|
||||
# Toggle Add/Subtract for weight draw tool
|
||||
("gpencil.weight_toggle_direction", {"type": 'D', "value": 'PRESS'}, None),
|
||||
# Weight sample
|
||||
("gpencil.weight_sample", {"type": params.action_mouse, "value": 'PRESS', "ctrl": True}, None),
|
||||
])
|
||||
|
||||
if params.select_mouse == 'LEFTMOUSE':
|
||||
|
@ -4220,6 +4227,59 @@ def km_grease_pencil_stroke_weight_draw(_params):
|
|||
# Draw
|
||||
("gpencil.weight_paint", {"type": 'LEFTMOUSE', "value": 'PRESS'},
|
||||
{"properties": [("wait_for_input", False)]}),
|
||||
("gpencil.weight_paint", {"type": 'LEFTMOUSE', "value": 'PRESS', "ctrl": True},
|
||||
{"properties": [("wait_for_input", False)]}),
|
||||
])
|
||||
|
||||
return keymap
|
||||
|
||||
|
||||
def km_grease_pencil_stroke_weight_blur(_params):
|
||||
items = []
|
||||
keymap = (
|
||||
"Grease Pencil Stroke Weight (Blur)",
|
||||
{"space_type": 'EMPTY', "region_type": 'WINDOW'},
|
||||
{"items": items},
|
||||
)
|
||||
|
||||
items.extend([
|
||||
# Blur
|
||||
("gpencil.weight_paint", {"type": 'LEFTMOUSE', "value": 'PRESS'},
|
||||
{"properties": [("wait_for_input", False)]}),
|
||||
])
|
||||
|
||||
return keymap
|
||||
|
||||
|
||||
def km_grease_pencil_stroke_weight_average(_params):
|
||||
items = []
|
||||
keymap = (
|
||||
"Grease Pencil Stroke Weight (Average)",
|
||||
{"space_type": 'EMPTY', "region_type": 'WINDOW'},
|
||||
{"items": items},
|
||||
)
|
||||
|
||||
items.extend([
|
||||
# Average
|
||||
("gpencil.weight_paint", {"type": 'LEFTMOUSE', "value": 'PRESS'},
|
||||
{"properties": [("wait_for_input", False)]}),
|
||||
])
|
||||
|
||||
return keymap
|
||||
|
||||
|
||||
def km_grease_pencil_stroke_weight_smear(_params):
|
||||
items = []
|
||||
keymap = (
|
||||
"Grease Pencil Stroke Weight (Smear)",
|
||||
{"space_type": 'EMPTY', "region_type": 'WINDOW'},
|
||||
{"items": items},
|
||||
)
|
||||
|
||||
items.extend([
|
||||
# Smear
|
||||
("gpencil.weight_paint", {"type": 'LEFTMOUSE', "value": 'PRESS'},
|
||||
{"properties": [("wait_for_input", False)]}),
|
||||
])
|
||||
|
||||
return keymap
|
||||
|
@ -6788,6 +6848,17 @@ def km_3d_view_tool_cursor(params):
|
|||
)
|
||||
|
||||
|
||||
def km_3d_view_tool_text_select(params):
|
||||
return (
|
||||
"3D View Tool: Edit Text, Select Text",
|
||||
{"space_type": 'VIEW_3D', "region_type": 'WINDOW'},
|
||||
{"items": [
|
||||
("font.selection_set", {"type": 'LEFTMOUSE', "value": 'PRESS'}, None),
|
||||
("font.select_word", {"type": 'LEFTMOUSE', "value": 'DOUBLE_CLICK'}, None),
|
||||
]},
|
||||
)
|
||||
|
||||
|
||||
def km_3d_view_tool_select(params, *, fallback):
|
||||
if params.use_tweak_select_passthrough:
|
||||
operator_props = (("vert_without_handles", True),)
|
||||
|
@ -8115,6 +8186,9 @@ def generate_keymaps(params=None):
|
|||
km_grease_pencil_stroke_sculpt_clone(params),
|
||||
km_grease_pencil_stroke_weight_mode(params),
|
||||
km_grease_pencil_stroke_weight_draw(params),
|
||||
km_grease_pencil_stroke_weight_blur(params),
|
||||
km_grease_pencil_stroke_weight_average(params),
|
||||
km_grease_pencil_stroke_weight_smear(params),
|
||||
km_grease_pencil_stroke_vertex_mode(params),
|
||||
km_grease_pencil_stroke_vertex_draw(params),
|
||||
km_grease_pencil_stroke_vertex_blur(params),
|
||||
|
@ -8201,6 +8275,7 @@ def generate_keymaps(params=None):
|
|||
*(km_node_editor_tool_select_circle(params, fallback=fallback) for fallback in (False, True)),
|
||||
km_node_editor_tool_links_cut(params),
|
||||
km_3d_view_tool_cursor(params),
|
||||
km_3d_view_tool_text_select(params),
|
||||
*(km_3d_view_tool_select(params, fallback=fallback) for fallback in (False, True)),
|
||||
*(km_3d_view_tool_select_box(params, fallback=fallback) for fallback in (False, True)),
|
||||
*(km_3d_view_tool_select_circle(params, fallback=fallback) for fallback in (False, True)),
|
||||
|
|
|
@ -2777,6 +2777,56 @@ def km_grease_pencil_stroke_weight_draw(_params):
|
|||
{"items": items},
|
||||
)
|
||||
|
||||
items.extend([
|
||||
("gpencil.weight_paint", {"type": 'LEFTMOUSE', "value": 'PRESS'},
|
||||
{"properties": [("wait_for_input", False)]}),
|
||||
("gpencil.weight_paint", {"type": 'LEFTMOUSE', "value": 'PRESS', "ctrl": True},
|
||||
{"properties": [("wait_for_input", False)]}),
|
||||
])
|
||||
|
||||
return keymap
|
||||
|
||||
|
||||
def km_grease_pencil_stroke_weight_blur(_params):
|
||||
items = []
|
||||
keymap = (
|
||||
"Grease Pencil Stroke Weight (Blur)",
|
||||
{"space_type": 'EMPTY', "region_type": 'WINDOW'},
|
||||
{"items": items},
|
||||
)
|
||||
|
||||
items.extend([
|
||||
("gpencil.weight_paint", {"type": 'LEFTMOUSE', "value": 'PRESS'},
|
||||
{"properties": [("wait_for_input", False)]}),
|
||||
])
|
||||
|
||||
return keymap
|
||||
|
||||
|
||||
def km_grease_pencil_stroke_weight_average(_params):
|
||||
items = []
|
||||
keymap = (
|
||||
"Grease Pencil Stroke Weight (Average)",
|
||||
{"space_type": 'EMPTY', "region_type": 'WINDOW'},
|
||||
{"items": items},
|
||||
)
|
||||
|
||||
items.extend([
|
||||
("gpencil.weight_paint", {"type": 'LEFTMOUSE', "value": 'PRESS'},
|
||||
{"properties": [("wait_for_input", False)]}),
|
||||
])
|
||||
|
||||
return keymap
|
||||
|
||||
|
||||
def km_grease_pencil_stroke_weight_smear(_params):
|
||||
items = []
|
||||
keymap = (
|
||||
"Grease Pencil Stroke Weight (Smear)",
|
||||
{"space_type": 'EMPTY', "region_type": 'WINDOW'},
|
||||
{"items": items},
|
||||
)
|
||||
|
||||
items.extend([
|
||||
("gpencil.weight_paint", {"type": 'LEFTMOUSE', "value": 'PRESS'},
|
||||
{"properties": [("wait_for_input", False)]}),
|
||||
|
@ -4195,6 +4245,9 @@ def generate_keymaps_impl(params=None):
|
|||
km_grease_pencil_stroke_sculpt_clone(params),
|
||||
km_grease_pencil_stroke_weight_mode(params),
|
||||
km_grease_pencil_stroke_weight_draw(params),
|
||||
km_grease_pencil_stroke_weight_blur(params),
|
||||
km_grease_pencil_stroke_weight_average(params),
|
||||
km_grease_pencil_stroke_weight_smear(params),
|
||||
km_grease_pencil_stroke_vertex_mode(params),
|
||||
km_grease_pencil_stroke_vertex_draw(params),
|
||||
km_grease_pencil_stroke_vertex_blur(params),
|
||||
|
|
|
@ -220,6 +220,7 @@ class NODE_MT_geometry_node_GEO_GEOMETRY_SAMPLE(Menu):
|
|||
def draw(self, _context):
|
||||
layout = self.layout
|
||||
node_add_menu.add_node_type(layout, "GeometryNodeProximity")
|
||||
node_add_menu.add_node_type(layout, "GeometryNodeIndexOfNearest")
|
||||
node_add_menu.add_node_type(layout, "GeometryNodeRaycast")
|
||||
node_add_menu.add_node_type(layout, "GeometryNodeSampleIndex")
|
||||
node_add_menu.add_node_type(layout, "GeometryNodeSampleNearest")
|
||||
|
|
|
@ -371,10 +371,18 @@ class DATA_PT_font(CurveButtonsPanelText, Panel):
|
|||
row.prop(char, "use_small_caps", toggle=True)
|
||||
else:
|
||||
row = layout.row(align=True)
|
||||
row.operator("font.style_toggle", text="Bold", icon='BOLD' , depress = text.is_select_bold).style = 'BOLD'
|
||||
row.operator("font.style_toggle", text="Italic", icon='ITALIC' , depress = text.is_select_italic).style = 'ITALIC'
|
||||
row.operator("font.style_toggle", text="Underline", icon='UNDERLINE' , depress = text.is_select_underline).style = 'UNDERLINE'
|
||||
row.operator("font.style_toggle", text="Small Caps", icon='SMALL_CAPS' , depress = text.is_select_smallcaps).style = 'SMALL_CAPS'
|
||||
row.operator(
|
||||
"font.style_toggle", text="Bold", icon='BOLD', depress=text.is_select_bold,
|
||||
).style = 'BOLD'
|
||||
row.operator(
|
||||
"font.style_toggle", text="Italic", icon='ITALIC', depress=text.is_select_italic,
|
||||
).style = 'ITALIC'
|
||||
row.operator(
|
||||
"font.style_toggle", text="Underline", icon='UNDERLINE', depress=text.is_select_underline,
|
||||
).style = 'UNDERLINE'
|
||||
row.operator(
|
||||
"font.style_toggle", text="Small Caps", icon='SMALL_CAPS', depress=text.is_select_smallcaps,
|
||||
).style = 'SMALL_CAPS'
|
||||
|
||||
|
||||
class DATA_PT_font_transform(CurveButtonsPanelText, Panel):
|
||||
|
|
|
@ -236,6 +236,17 @@ class OBJECT_PT_display(ObjectButtonsPanel, Panel):
|
|||
row.prop_decorator(obj, "display_bounds_type")
|
||||
|
||||
|
||||
class OBJECT_PT_baking(ObjectButtonsPanel, Panel):
|
||||
bl_label = "Baking"
|
||||
bl_options = {'DEFAULT_CLOSED'}
|
||||
|
||||
def draw(self, context):
|
||||
layout = self.layout
|
||||
col = layout.column()
|
||||
col.label(text="Simulation Nodes Cache:")
|
||||
col.operator("object.simulation_nodes_cache_bake", text="Bake").selected = True
|
||||
col.operator("object.simulation_nodes_cache_delete", text="Delete").selected = True
|
||||
|
||||
class OBJECT_PT_instancing(ObjectButtonsPanel, Panel):
|
||||
bl_label = "Instancing"
|
||||
bl_options = {'DEFAULT_CLOSED'}
|
||||
|
@ -412,6 +423,7 @@ classes = (
|
|||
OBJECT_PT_motion_paths,
|
||||
OBJECT_PT_motion_paths_display,
|
||||
OBJECT_PT_display,
|
||||
OBJECT_PT_baking,
|
||||
OBJECT_PT_visibility,
|
||||
OBJECT_PT_lineart,
|
||||
OBJECT_PT_custom_props,
|
||||
|
|
|
@ -1415,7 +1415,11 @@ def brush_basic_gpencil_weight_settings(layout, _context, brush, *, compact=Fals
|
|||
row.prop(brush, "strength", slider=True)
|
||||
row.prop(brush, "use_pressure_strength", text="")
|
||||
|
||||
layout.prop(brush, "weight", slider=True)
|
||||
if brush.gpencil_weight_tool in {'WEIGHT'}:
|
||||
layout.prop(brush, "weight", slider=True)
|
||||
|
||||
gp_settings = brush.gpencil_settings
|
||||
layout.prop(gp_settings, "direction", expand=True, text="" if compact else "Direction")
|
||||
|
||||
|
||||
def brush_basic_gpencil_vertex_settings(layout, _context, brush, *, compact=False):
|
||||
|
|
|
@ -903,7 +903,10 @@ class NodeTreeInterfacePanel(Panel):
|
|||
props = property_row.operator_menu_enum(
|
||||
"node.tree_socket_change_subtype",
|
||||
"socket_subtype",
|
||||
text=active_socket.bl_subtype_label if active_socket.bl_subtype_label else active_socket.bl_idname
|
||||
text=(
|
||||
active_socket.bl_subtype_label if active_socket.bl_subtype_label else
|
||||
active_socket.bl_idname
|
||||
),
|
||||
)
|
||||
|
||||
layout.use_property_split = True
|
||||
|
|
|
@ -68,6 +68,7 @@ def generate_from_enum_ex(
|
|||
dict(
|
||||
idname=idname_prefix + name,
|
||||
label=name,
|
||||
description=enum.description,
|
||||
icon=icon,
|
||||
cursor=cursor,
|
||||
data_block=idname,
|
||||
|
@ -1283,6 +1284,20 @@ class _defs_edit_curve:
|
|||
)
|
||||
|
||||
|
||||
class _defs_edit_text:
|
||||
|
||||
@ToolDef.from_fn
|
||||
def select_text():
|
||||
return dict(
|
||||
idname="builtin.select_text",
|
||||
label="Select Text",
|
||||
cursor='TEXT',
|
||||
icon="ops.generic.select_box",
|
||||
widget=None,
|
||||
keymap=(),
|
||||
)
|
||||
|
||||
|
||||
class _defs_pose:
|
||||
|
||||
@ToolDef.from_fn
|
||||
|
@ -2979,6 +2994,7 @@ class VIEW3D_PT_tools_active(ToolSelectPanelHelper, Panel):
|
|||
_defs_transform.shear,
|
||||
],
|
||||
'EDIT_TEXT': [
|
||||
_defs_edit_text.select_text,
|
||||
_defs_view3d_generic.cursor,
|
||||
None,
|
||||
*_tools_annotate,
|
||||
|
|
|
@ -8,6 +8,7 @@ from bpy.types import (
|
|||
from bl_ui.properties_paint_common import (
|
||||
UnifiedPaintPanel,
|
||||
brush_basic_texpaint_settings,
|
||||
brush_basic_gpencil_weight_settings,
|
||||
)
|
||||
from bl_ui.properties_grease_pencil_common import (
|
||||
AnnotationDataPanel,
|
||||
|
@ -414,11 +415,13 @@ class _draw_tool_settings_context_mode:
|
|||
paint = context.tool_settings.gpencil_weight_paint
|
||||
brush = paint.brush
|
||||
|
||||
from bl_ui.properties_paint_common import (
|
||||
brush_basic_gpencil_weight_settings,
|
||||
)
|
||||
layout.template_ID_preview(paint, "brush", rows=3, cols=8, hide_buttons=True)
|
||||
|
||||
brush_basic_gpencil_weight_settings(layout, context, brush, compact=True)
|
||||
|
||||
layout.popover("VIEW3D_PT_tools_grease_pencil_weight_options", text="Options")
|
||||
layout.popover("VIEW3D_PT_tools_grease_pencil_brush_weight_falloff", text="Falloff")
|
||||
|
||||
return True
|
||||
|
||||
@staticmethod
|
||||
|
@ -7662,12 +7665,10 @@ class VIEW3D_PT_gpencil_weight_context_menu(Panel):
|
|||
tool_settings = context.tool_settings
|
||||
settings = tool_settings.gpencil_weight_paint
|
||||
brush = settings.brush
|
||||
|
||||
layout = self.layout
|
||||
|
||||
layout.prop(brush, "size", slider=True)
|
||||
layout.prop(brush, "strength")
|
||||
layout.prop(brush, "weight")
|
||||
# Weight settings
|
||||
brush_basic_gpencil_weight_settings(layout, context, brush)
|
||||
|
||||
# Layers
|
||||
draw_gpencil_layer_active(context, layout)
|
||||
|
|
|
@ -2021,6 +2021,9 @@ class VIEW3D_PT_tools_grease_pencil_weight_paint_settings(Panel, View3DPanel, Gr
|
|||
bl_label = "Brush Settings"
|
||||
|
||||
def draw(self, context):
|
||||
if self.is_popover:
|
||||
return
|
||||
|
||||
layout = self.layout
|
||||
layout.use_property_split = True
|
||||
layout.use_property_decorate = False
|
||||
|
@ -2029,15 +2032,15 @@ class VIEW3D_PT_tools_grease_pencil_weight_paint_settings(Panel, View3DPanel, Gr
|
|||
settings = tool_settings.gpencil_weight_paint
|
||||
brush = settings.brush
|
||||
|
||||
if not self.is_popover:
|
||||
from bl_ui.properties_paint_common import (
|
||||
brush_basic_gpencil_weight_settings,
|
||||
)
|
||||
brush_basic_gpencil_weight_settings(layout, context, brush)
|
||||
from bl_ui.properties_paint_common import (
|
||||
brush_basic_gpencil_weight_settings,
|
||||
)
|
||||
brush_basic_gpencil_weight_settings(layout, context, brush)
|
||||
|
||||
|
||||
class VIEW3D_PT_tools_grease_pencil_brush_weight_falloff(GreasePencilBrushFalloff, Panel, View3DPaintPanel):
|
||||
bl_context = ".greasepencil_weight"
|
||||
bl_parent_id = 'VIEW3D_PT_tools_grease_pencil_weight_paint_settings'
|
||||
bl_label = "Falloff"
|
||||
bl_options = {'DEFAULT_CLOSED'}
|
||||
|
||||
|
@ -2049,6 +2052,20 @@ class VIEW3D_PT_tools_grease_pencil_brush_weight_falloff(GreasePencilBrushFallof
|
|||
return (brush and brush.curve)
|
||||
|
||||
|
||||
class VIEW3D_PT_tools_grease_pencil_weight_options(Panel, View3DPanel, GreasePencilWeightPanel):
|
||||
bl_label = "Options"
|
||||
bl_options = {'DEFAULT_CLOSED'}
|
||||
|
||||
def draw(self, context):
|
||||
layout = self.layout
|
||||
layout.use_property_split = True
|
||||
layout.use_property_decorate = False
|
||||
tool_settings = context.scene.tool_settings
|
||||
|
||||
col = layout.column()
|
||||
col.prop(tool_settings, "use_auto_normalize", text="Auto Normalize")
|
||||
|
||||
|
||||
# Grease Pencil vertex painting tools
|
||||
class GreasePencilVertexPanel:
|
||||
bl_context = ".greasepencil_vertex"
|
||||
|
@ -2425,6 +2442,7 @@ classes = (
|
|||
VIEW3D_PT_tools_grease_pencil_sculpt_appearance,
|
||||
VIEW3D_PT_tools_grease_pencil_weight_paint_select,
|
||||
VIEW3D_PT_tools_grease_pencil_weight_paint_settings,
|
||||
VIEW3D_PT_tools_grease_pencil_weight_options,
|
||||
VIEW3D_PT_tools_grease_pencil_weight_appearance,
|
||||
VIEW3D_PT_tools_grease_pencil_vertex_paint_select,
|
||||
VIEW3D_PT_tools_grease_pencil_vertex_paint_settings,
|
||||
|
|
|
@ -260,6 +260,7 @@ void AssetCatalogService::update_catalog_path(const CatalogID catalog_id,
|
|||
}
|
||||
cat->path = new_path;
|
||||
cat->simple_name_refresh();
|
||||
this->tag_has_unsaved_changes(cat);
|
||||
|
||||
/* TODO(Sybren): go over all assets that are assigned to this catalog, defined in the current
|
||||
* blend file, and update the catalog simple name stored there. */
|
||||
|
|
|
@ -29,7 +29,7 @@ namespace blender::bke {
|
|||
*
|
||||
* Once created, #AnonymousAttributeID is immutable. Also it is intrinsically reference counted so
|
||||
* that it can have shared ownership. `std::shared_ptr` can't be used for that purpose here,
|
||||
* because that is not available in C code. If possible, the #AutoAnonymousAttributeID wrapper
|
||||
* because that is not available in C code. If possible, the #AnonymousAttributeIDPtr wrapper
|
||||
* should be used to avoid manual reference counting in C++ code.
|
||||
*/
|
||||
class AnonymousAttributeID : public ImplicitSharingMixin {
|
||||
|
@ -54,7 +54,7 @@ class AnonymousAttributeID : public ImplicitSharingMixin {
|
|||
};
|
||||
|
||||
/** Wrapper for #AnonymousAttributeID that avoids manual reference counting. */
|
||||
using AutoAnonymousAttributeID = ImplicitSharingPtr<const AnonymousAttributeID>;
|
||||
using AnonymousAttributeIDPtr = ImplicitSharingPtr<const AnonymousAttributeID>;
|
||||
|
||||
/**
|
||||
* A set of anonymous attribute names that is passed around in geometry nodes.
|
||||
|
|
|
@ -55,7 +55,7 @@ struct BasisCache {
|
|||
class CurvesGeometryRuntime {
|
||||
public:
|
||||
/** Implicit sharing user count for #CurvesGeometry::curve_offsets. */
|
||||
ImplicitSharingInfo *curve_offsets_sharing_info = nullptr;
|
||||
const ImplicitSharingInfo *curve_offsets_sharing_info = nullptr;
|
||||
|
||||
/**
|
||||
* The cached number of curves with each type. Unlike other caches here, this is not computed
|
||||
|
|
|
@ -259,11 +259,11 @@ class NormalFieldInput : public GeometryFieldInput {
|
|||
|
||||
class AnonymousAttributeFieldInput : public GeometryFieldInput {
|
||||
private:
|
||||
AutoAnonymousAttributeID anonymous_id_;
|
||||
AnonymousAttributeIDPtr anonymous_id_;
|
||||
std::string producer_name_;
|
||||
|
||||
public:
|
||||
AnonymousAttributeFieldInput(AutoAnonymousAttributeID anonymous_id,
|
||||
AnonymousAttributeFieldInput(AnonymousAttributeIDPtr anonymous_id,
|
||||
const CPPType &type,
|
||||
std::string producer_name)
|
||||
: GeometryFieldInput(type, anonymous_id->user_name()),
|
||||
|
@ -274,7 +274,7 @@ class AnonymousAttributeFieldInput : public GeometryFieldInput {
|
|||
}
|
||||
|
||||
template<typename T>
|
||||
static fn::Field<T> Create(AutoAnonymousAttributeID anonymous_id, std::string producer_name)
|
||||
static fn::Field<T> Create(AnonymousAttributeIDPtr anonymous_id, std::string producer_name)
|
||||
{
|
||||
const CPPType &type = CPPType::get<T>();
|
||||
auto field_input = std::make_shared<AnonymousAttributeFieldInput>(
|
||||
|
@ -282,7 +282,7 @@ class AnonymousAttributeFieldInput : public GeometryFieldInput {
|
|||
return fn::Field<T>{field_input};
|
||||
}
|
||||
|
||||
const AutoAnonymousAttributeID &anonymous_id() const
|
||||
const AnonymousAttributeIDPtr &anonymous_id() const
|
||||
{
|
||||
return anonymous_id_;
|
||||
}
|
||||
|
|
|
@ -69,20 +69,25 @@ namespace blender::bke {
|
|||
/**
|
||||
* Cache of a mesh's loose edges, accessed with #Mesh::loose_edges(). *
|
||||
*/
|
||||
struct LooseEdgeCache {
|
||||
struct LooseGeomCache {
|
||||
/**
|
||||
* A bitmap set to true for each loose edge, false if the edge is used by any face.
|
||||
* Allocated only if there is at least one loose edge.
|
||||
* A bitmap set to true for each loose element, false if the element is used by any face.
|
||||
* Allocated only if there is at least one loose element.
|
||||
*/
|
||||
blender::BitVector<> is_loose_bits;
|
||||
/**
|
||||
* The number of loose edges. If zero, the #is_loose_bits shouldn't be accessed.
|
||||
* The number of loose elements. If zero, the #is_loose_bits shouldn't be accessed.
|
||||
* If less than zero, the cache has been accessed in an invalid way
|
||||
* (i.e.directly instead of through #Mesh::loose_edges()).
|
||||
*/
|
||||
int count = -1;
|
||||
};
|
||||
|
||||
struct LooseEdgeCache : public LooseGeomCache {
|
||||
};
|
||||
struct LooseVertCache : public LooseGeomCache {
|
||||
};
|
||||
|
||||
struct MeshRuntime {
|
||||
/* Evaluated mesh for objects which do not have effective modifiers.
|
||||
* This mesh is used as a result of modifier stack evaluation.
|
||||
|
@ -98,7 +103,7 @@ struct MeshRuntime {
|
|||
std::mutex render_mutex;
|
||||
|
||||
/** Implicit sharing user count for #Mesh::poly_offset_indices. */
|
||||
ImplicitSharingInfoHandle *poly_offsets_sharing_info;
|
||||
const ImplicitSharingInfoHandle *poly_offsets_sharing_info;
|
||||
|
||||
/**
|
||||
* A cache of bounds shared between data-blocks with unchanged positions. When changing positions
|
||||
|
@ -166,11 +171,12 @@ struct MeshRuntime {
|
|||
mutable Vector<float3> vert_normals;
|
||||
mutable Vector<float3> poly_normals;
|
||||
|
||||
/**
|
||||
* A cache of data about the loose edges. Can be shared with other data-blocks with unchanged
|
||||
* topology. Accessed with #Mesh::loose_edges().
|
||||
*/
|
||||
/** Cache of data about edges not used by faces. See #Mesh::loose_edges(). */
|
||||
SharedCache<LooseEdgeCache> loose_edges_cache;
|
||||
/** Cache of data about vertices not used by edges. See #Mesh::loose_verts(). */
|
||||
SharedCache<LooseVertCache> loose_verts_cache;
|
||||
/** Cache of data about vertices not used by faces. See #Mesh::loose_verts(). */
|
||||
SharedCache<LooseVertCache> verts_no_face_cache;
|
||||
|
||||
/**
|
||||
* A bit vector the size of the number of vertices, set to true for the center vertices of
|
||||
|
|
|
@ -1584,6 +1584,7 @@ void BKE_nodetree_remove_layer_n(struct bNodeTree *ntree, struct Scene *scene, i
|
|||
#define GEO_NODE_SDF_VOLUME_SPHERE 1196
|
||||
#define GEO_NODE_MEAN_FILTER_SDF_VOLUME 1197
|
||||
#define GEO_NODE_OFFSET_SDF_VOLUME 1198
|
||||
#define GEO_NODE_INDEX_OF_NEAREST 1199
|
||||
|
||||
/** \} */
|
||||
|
||||
|
|
|
@ -5,9 +5,13 @@
|
|||
#include "BKE_geometry_set.hh"
|
||||
|
||||
#include "BLI_map.hh"
|
||||
#include "BLI_sub_frame.hh"
|
||||
|
||||
namespace blender::bke::sim {
|
||||
|
||||
class BDataSharing;
|
||||
class ModifierSimulationCache;
|
||||
|
||||
class SimulationStateItem {
|
||||
public:
|
||||
virtual ~SimulationStateItem() = default;
|
||||
|
@ -47,10 +51,15 @@ struct SimulationZoneID {
|
|||
|
||||
class ModifierSimulationState {
|
||||
private:
|
||||
mutable std::mutex mutex_;
|
||||
Map<SimulationZoneID, std::unique_ptr<SimulationZoneState>> zone_states_;
|
||||
mutable bool bake_loaded_;
|
||||
|
||||
public:
|
||||
ModifierSimulationCache *owner_;
|
||||
mutable std::mutex mutex_;
|
||||
Map<SimulationZoneID, std::unique_ptr<SimulationZoneState>> zone_states_;
|
||||
std::optional<std::string> meta_path_;
|
||||
std::optional<std::string> bdata_dir_;
|
||||
|
||||
const SimulationZoneState *get_zone_state(const SimulationZoneID &zone_id) const
|
||||
{
|
||||
std::lock_guard lock{mutex_};
|
||||
|
@ -66,62 +75,112 @@ class ModifierSimulationState {
|
|||
return *zone_states_.lookup_or_add_cb(
|
||||
zone_id, []() { return std::make_unique<SimulationZoneState>(); });
|
||||
}
|
||||
|
||||
void ensure_bake_loaded() const;
|
||||
};
|
||||
|
||||
struct ModifierSimulationStateAtFrame {
|
||||
SubFrame frame;
|
||||
ModifierSimulationState state;
|
||||
};
|
||||
|
||||
enum class CacheState {
|
||||
Valid,
|
||||
Invalid,
|
||||
Baked,
|
||||
};
|
||||
|
||||
struct StatesAroundFrame {
|
||||
const ModifierSimulationStateAtFrame *prev = nullptr;
|
||||
const ModifierSimulationStateAtFrame *current = nullptr;
|
||||
const ModifierSimulationStateAtFrame *next = nullptr;
|
||||
};
|
||||
|
||||
class ModifierSimulationCache {
|
||||
private:
|
||||
Map<float, std::unique_ptr<ModifierSimulationState>> states_by_time_;
|
||||
bool invalid_ = false;
|
||||
Vector<std::unique_ptr<ModifierSimulationStateAtFrame>> states_at_frames_;
|
||||
std::unique_ptr<BDataSharing> bdata_sharing_;
|
||||
|
||||
friend ModifierSimulationState;
|
||||
|
||||
public:
|
||||
bool has_state_at_time(const float time) const
|
||||
CacheState cache_state_ = CacheState::Valid;
|
||||
bool failed_finding_bake_ = false;
|
||||
|
||||
void try_discover_bake(StringRefNull meta_dir, StringRefNull bdata_dir);
|
||||
|
||||
bool has_state_at_frame(const SubFrame &frame) const
|
||||
{
|
||||
return states_by_time_.contains(time);
|
||||
for (const auto &item : states_at_frames_) {
|
||||
if (item->frame == frame) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
const ModifierSimulationState *get_state_at_time(const float time) const
|
||||
bool has_states() const
|
||||
{
|
||||
if (auto *ptr = states_by_time_.lookup_ptr(time)) {
|
||||
return ptr->get();
|
||||
return !states_at_frames_.is_empty();
|
||||
}
|
||||
|
||||
const ModifierSimulationState *get_state_at_exact_frame(const SubFrame &frame) const
|
||||
{
|
||||
for (const auto &item : states_at_frames_) {
|
||||
if (item->frame == frame) {
|
||||
return &item->state;
|
||||
}
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
ModifierSimulationState &get_state_for_write(const float time)
|
||||
ModifierSimulationState &get_state_at_frame_for_write(const SubFrame &frame)
|
||||
{
|
||||
return *states_by_time_.lookup_or_add_cb(
|
||||
time, []() { return std::make_unique<ModifierSimulationState>(); });
|
||||
}
|
||||
|
||||
std::pair<float, const ModifierSimulationState *> try_get_last_state_before(
|
||||
const float time) const
|
||||
{
|
||||
float last_time = -FLT_MAX;
|
||||
const ModifierSimulationState *last_state = nullptr;
|
||||
for (const auto &item : states_by_time_.items()) {
|
||||
if (item.key < time && item.key > last_time) {
|
||||
last_time = item.key;
|
||||
last_state = item.value.get();
|
||||
for (const auto &item : states_at_frames_) {
|
||||
if (item->frame == frame) {
|
||||
return item->state;
|
||||
}
|
||||
}
|
||||
return {last_time, last_state};
|
||||
states_at_frames_.append(std::make_unique<ModifierSimulationStateAtFrame>());
|
||||
states_at_frames_.last()->frame = frame;
|
||||
states_at_frames_.last()->state.owner_ = this;
|
||||
return states_at_frames_.last()->state;
|
||||
}
|
||||
|
||||
StatesAroundFrame get_states_around_frame(const SubFrame &frame) const
|
||||
{
|
||||
StatesAroundFrame states_around_frame;
|
||||
for (const auto &item : states_at_frames_) {
|
||||
if (item->frame < frame) {
|
||||
if (states_around_frame.prev == nullptr || item->frame > states_around_frame.prev->frame) {
|
||||
states_around_frame.prev = item.get();
|
||||
}
|
||||
}
|
||||
if (item->frame == frame) {
|
||||
if (states_around_frame.current == nullptr) {
|
||||
states_around_frame.current = item.get();
|
||||
}
|
||||
}
|
||||
if (item->frame > frame) {
|
||||
if (states_around_frame.next == nullptr || item->frame < states_around_frame.next->frame) {
|
||||
states_around_frame.next = item.get();
|
||||
}
|
||||
}
|
||||
}
|
||||
return states_around_frame;
|
||||
}
|
||||
|
||||
void invalidate()
|
||||
{
|
||||
invalid_ = true;
|
||||
cache_state_ = CacheState::Invalid;
|
||||
}
|
||||
|
||||
bool is_invalid() const
|
||||
CacheState cache_state() const
|
||||
{
|
||||
return invalid_;
|
||||
return cache_state_;
|
||||
}
|
||||
|
||||
void reset()
|
||||
{
|
||||
states_by_time_.clear();
|
||||
invalid_ = false;
|
||||
}
|
||||
void reset();
|
||||
};
|
||||
|
||||
} // namespace blender::bke::sim
|
||||
|
|
|
@ -0,0 +1,170 @@
|
|||
/* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "BKE_simulation_state.hh"
|
||||
|
||||
#include "BLI_serialize.hh"
|
||||
|
||||
struct Main;
|
||||
struct ModifierData;
|
||||
|
||||
namespace blender {
|
||||
class fstream;
|
||||
}
|
||||
|
||||
namespace blender::bke::sim {
|
||||
|
||||
using DictionaryValue = io::serialize::DictionaryValue;
|
||||
using DictionaryValuePtr = std::shared_ptr<DictionaryValue>;
|
||||
|
||||
/**
|
||||
* Reference to a slice of memory typically stored on disk.
|
||||
*/
|
||||
struct BDataSlice {
|
||||
std::string name;
|
||||
IndexRange range;
|
||||
|
||||
DictionaryValuePtr serialize() const;
|
||||
static std::optional<BDataSlice> deserialize(const io::serialize::DictionaryValue &io_slice);
|
||||
};
|
||||
|
||||
/**
|
||||
* Abstract base class for loading binary data.
|
||||
*/
|
||||
class BDataReader {
|
||||
public:
|
||||
/**
|
||||
* Read the data from the given slice into the provided memory buffer.
|
||||
* \return True on success, otherwise false.
|
||||
*/
|
||||
[[nodiscard]] virtual bool read(const BDataSlice &slice, void *r_data) const = 0;
|
||||
};
|
||||
|
||||
/**
|
||||
* Abstract base class for writing binary data.
|
||||
*/
|
||||
class BDataWriter {
|
||||
public:
|
||||
/**
|
||||
* Write the provided binary data.
|
||||
* \return Slice where the data has been written to.
|
||||
*/
|
||||
virtual BDataSlice write(const void *data, int64_t size) = 0;
|
||||
};
|
||||
|
||||
/**
|
||||
* Allows for simple data deduplication when writing or reading data by making use of implicit
|
||||
* sharing.
|
||||
*/
|
||||
class BDataSharing {
|
||||
private:
|
||||
struct StoredByRuntimeValue {
|
||||
/**
|
||||
* Version of the shared data that was written before. This is needed because the data might
|
||||
* be changed later without changing the #ImplicitSharingInfo pointer.
|
||||
*/
|
||||
int64_t sharing_info_version;
|
||||
/**
|
||||
* Identifier of the stored data. This includes information for where the data is stored (a
|
||||
* #BDataSlice) and optionally information for how it is loaded (e.g. endian information).
|
||||
*/
|
||||
DictionaryValuePtr io_data;
|
||||
};
|
||||
|
||||
/**
|
||||
* Map used to detect when some data has already been written. It keeps a weak reference to
|
||||
* #ImplicitSharingInfo, allowing it to check for equality of two arrays just by comparing the
|
||||
* sharing info's pointer and version.
|
||||
*/
|
||||
Map<const ImplicitSharingInfo *, StoredByRuntimeValue> stored_by_runtime_;
|
||||
|
||||
/**
|
||||
* Use a mutex so that #read_shared can be implemented in a thread-safe way.
|
||||
*/
|
||||
mutable std::mutex mutex_;
|
||||
/**
|
||||
* Map used to detect when some data has been previously loaded. This keeps strong
|
||||
* references to #ImplicitSharingInfo.
|
||||
*/
|
||||
mutable Map<std::string, ImplicitSharingInfoAndData> runtime_by_stored_;
|
||||
|
||||
public:
|
||||
~BDataSharing();
|
||||
|
||||
/**
|
||||
* Check if the data referenced by `sharing_info` has been written before. If yes, return the
|
||||
* identifier for the previously written data. Otherwise, write the data now and store the
|
||||
* identifier for later use.
|
||||
* \return Identifier that indicates from where the data has been written.
|
||||
*/
|
||||
[[nodiscard]] DictionaryValuePtr write_shared(const ImplicitSharingInfo *sharing_info,
|
||||
FunctionRef<DictionaryValuePtr()> write_fn);
|
||||
|
||||
/**
|
||||
* Check if the data identified by `io_data` has been read before or load it now.
|
||||
* \return Shared ownership to the read data, or none if there was an error.
|
||||
*/
|
||||
[[nodiscard]] std::optional<ImplicitSharingInfoAndData> read_shared(
|
||||
const DictionaryValue &io_data,
|
||||
FunctionRef<std::optional<ImplicitSharingInfoAndData>()> read_fn) const;
|
||||
};
|
||||
|
||||
/**
|
||||
* A specific #BDataReader that reads from disk.
|
||||
*/
|
||||
class DiskBDataReader : public BDataReader {
|
||||
private:
|
||||
const std::string bdata_dir_;
|
||||
mutable std::mutex mutex_;
|
||||
mutable Map<std::string, std::unique_ptr<fstream>> open_input_streams_;
|
||||
|
||||
public:
|
||||
DiskBDataReader(std::string bdata_dir);
|
||||
[[nodiscard]] bool read(const BDataSlice &slice, void *r_data) const override;
|
||||
};
|
||||
|
||||
/**
|
||||
* A specific #BDataWriter that writes to a file on disk.
|
||||
*/
|
||||
class DiskBDataWriter : public BDataWriter {
|
||||
private:
|
||||
/** Name of the file that data is written to. */
|
||||
std::string bdata_name_;
|
||||
/** File handle. */
|
||||
std::ostream &bdata_file_;
|
||||
/** Current position in the file. */
|
||||
int64_t current_offset_;
|
||||
|
||||
public:
|
||||
DiskBDataWriter(std::string bdata_name, std::ostream &bdata_file, int64_t current_offset);
|
||||
|
||||
BDataSlice write(const void *data, int64_t size) override;
|
||||
};
|
||||
|
||||
/**
|
||||
* Get the directory that contains all baked simulation data for the given modifier. This is a
|
||||
* parent directory of the two directories below.
|
||||
*/
|
||||
std::string get_bake_directory(const Main &bmain, const Object &object, const ModifierData &md);
|
||||
std::string get_bdata_directory(const Main &bmain, const Object &object, const ModifierData &md);
|
||||
std::string get_meta_directory(const Main &bmain, const Object &object, const ModifierData &md);
|
||||
|
||||
/**
|
||||
* Encode the simulation state in a #DictionaryValue which also contains references to external
|
||||
* binary data that has been written using #bdata_writer.
|
||||
*/
|
||||
void serialize_modifier_simulation_state(const ModifierSimulationState &state,
|
||||
BDataWriter &bdata_writer,
|
||||
BDataSharing &bdata_sharing,
|
||||
DictionaryValue &r_io_root);
|
||||
/**
|
||||
* Fill the simulation state by parsing the provided #DictionaryValue which also contains
|
||||
* references to external binary data that is read using #bdata_reader.
|
||||
*/
|
||||
void deserialize_modifier_simulation_state(const DictionaryValue &io_root,
|
||||
const BDataReader &bdata_reader,
|
||||
const BDataSharing &bdata_sharing,
|
||||
ModifierSimulationState &r_state);
|
||||
|
||||
} // namespace blender::bke::sim
|
|
@ -154,6 +154,11 @@ void BKE_sound_set_scene_sound_volume(void *handle, float volume, char animated)
|
|||
|
||||
void BKE_sound_set_scene_sound_pitch(void *handle, float pitch, char animated);
|
||||
|
||||
void BKE_sound_set_scene_sound_pitch_constant_range(void *handle,
|
||||
int frame_start,
|
||||
int frame_end,
|
||||
float pitch);
|
||||
|
||||
void BKE_sound_set_scene_sound_pan(void *handle, float pan, char animated);
|
||||
|
||||
void BKE_sound_update_sequencer(struct Main *main, struct bSound *sound);
|
||||
|
|
|
@ -89,6 +89,9 @@ bool BKE_vfont_to_curve_ex(struct Object *ob,
|
|||
bool *r_text_free,
|
||||
struct CharTrans **r_chartransdata);
|
||||
bool BKE_vfont_to_curve_nubase(struct Object *ob, int mode, struct ListBase *r_nubase);
|
||||
|
||||
int BKE_vfont_cursor_to_text_index(struct Object *ob, float cursor_location[2]);
|
||||
|
||||
/**
|
||||
* \warning Expects to have access to evaluated data (i.e. passed object should be evaluated one).
|
||||
*/
|
||||
|
|
|
@ -266,6 +266,8 @@ set(SRC
|
|||
intern/shader_fx.c
|
||||
intern/shrinkwrap.cc
|
||||
intern/simulation.cc
|
||||
intern/simulation_state.cc
|
||||
intern/simulation_state_serialize.cc
|
||||
intern/softbody.c
|
||||
intern/sound.c
|
||||
intern/speaker.c
|
||||
|
@ -467,6 +469,7 @@ set(SRC
|
|||
BKE_shrinkwrap.h
|
||||
BKE_simulation.h
|
||||
BKE_simulation_state.hh
|
||||
BKE_simulation_state_serialize.hh
|
||||
BKE_softbody.h
|
||||
BKE_sound.h
|
||||
BKE_speaker.h
|
||||
|
|
|
@ -569,6 +569,27 @@ static Mesh *modifier_modify_mesh_and_geometry_set(ModifierData *md,
|
|||
return mesh_output;
|
||||
}
|
||||
|
||||
static void set_rest_position(Mesh &mesh)
|
||||
{
|
||||
using namespace blender;
|
||||
using namespace blender::bke;
|
||||
MutableAttributeAccessor attributes = mesh.attributes_for_write();
|
||||
const AttributeReader positions = attributes.lookup<float3>("position");
|
||||
attributes.remove("rest_position");
|
||||
if (positions) {
|
||||
if (positions.sharing_info && positions.varray.is_span()) {
|
||||
attributes.add<float3>("rest_position",
|
||||
ATTR_DOMAIN_POINT,
|
||||
AttributeInitShared(positions.varray.get_internal_span().data(),
|
||||
*positions.sharing_info));
|
||||
}
|
||||
else {
|
||||
attributes.add<float3>(
|
||||
"rest_position", ATTR_DOMAIN_POINT, AttributeInitVArray(positions.varray));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void mesh_calc_modifiers(struct Depsgraph *depsgraph,
|
||||
const Scene *scene,
|
||||
Object *ob,
|
||||
|
@ -657,20 +678,7 @@ static void mesh_calc_modifiers(struct Depsgraph *depsgraph,
|
|||
mesh_final = BKE_mesh_copy_for_eval(mesh_input);
|
||||
ASSERT_IS_VALID_MESH(mesh_final);
|
||||
}
|
||||
MutableAttributeAccessor attributes = mesh_final->attributes_for_write();
|
||||
const AttributeReader positions = attributes.lookup<float3>("position");
|
||||
if (positions) {
|
||||
if (positions.sharing_info && positions.varray.is_span()) {
|
||||
attributes.add<float3>("rest_position",
|
||||
ATTR_DOMAIN_POINT,
|
||||
AttributeInitShared(positions.varray.get_internal_span().data(),
|
||||
*positions.sharing_info));
|
||||
}
|
||||
else {
|
||||
attributes.add<float3>(
|
||||
"rest_position", ATTR_DOMAIN_POINT, AttributeInitVArray(positions.varray));
|
||||
}
|
||||
}
|
||||
set_rest_position(*mesh_final);
|
||||
}
|
||||
|
||||
/* Apply all leading deform modifiers. */
|
||||
|
@ -1194,6 +1202,14 @@ static void editbmesh_calc_modifiers(struct Depsgraph *depsgraph,
|
|||
/* Clear errors before evaluation. */
|
||||
BKE_modifiers_clear_errors(ob);
|
||||
|
||||
if (ob->modifier_flag & OB_MODIFIER_FLAG_ADD_REST_POSITION) {
|
||||
if (mesh_final == nullptr) {
|
||||
mesh_final = BKE_mesh_from_bmesh_for_eval_nomain(em_input->bm, nullptr, mesh_input);
|
||||
ASSERT_IS_VALID_MESH(mesh_final);
|
||||
}
|
||||
set_rest_position(*mesh_final);
|
||||
}
|
||||
|
||||
for (int i = 0; md; i++, md = md->next, md_datamask = md_datamask->next) {
|
||||
const ModifierTypeInfo *mti = BKE_modifier_get_info((ModifierType)md->type);
|
||||
|
||||
|
|
|
@ -1276,14 +1276,57 @@ void BKE_gpencil_brush_preset_set(Main *bmain, Brush *brush, const short type)
|
|||
|
||||
break;
|
||||
}
|
||||
case GP_BRUSH_PRESET_DRAW_WEIGHT: {
|
||||
case GP_BRUSH_PRESET_WEIGHT_DRAW: {
|
||||
brush->gpencil_settings->icon_id = GP_BRUSH_ICON_GPBRUSH_WEIGHT;
|
||||
brush->gpencil_weight_tool = GPWEIGHT_TOOL_DRAW;
|
||||
|
||||
brush->size = 25.0f;
|
||||
brush->gpencil_settings->flag |= GP_BRUSH_USE_PRESSURE;
|
||||
|
||||
brush->gpencil_settings->draw_strength = 0.8f;
|
||||
brush->alpha = 0.3f;
|
||||
brush->gpencil_settings->draw_strength = 0.3f;
|
||||
brush->gpencil_settings->flag |= GP_BRUSH_USE_STRENGTH_PRESSURE;
|
||||
brush->gpencil_settings->sculpt_mode_flag |= GP_SCULPT_FLAGMODE_APPLY_POSITION;
|
||||
|
||||
break;
|
||||
}
|
||||
case GP_BRUSH_PRESET_WEIGHT_BLUR: {
|
||||
brush->gpencil_settings->icon_id = GP_BRUSH_ICON_VERTEX_BLUR;
|
||||
brush->gpencil_weight_tool = GPWEIGHT_TOOL_BLUR;
|
||||
|
||||
brush->size = 50.0f;
|
||||
brush->gpencil_settings->flag |= GP_BRUSH_USE_PRESSURE;
|
||||
|
||||
brush->alpha = 0.3f;
|
||||
brush->gpencil_settings->draw_strength = 0.3f;
|
||||
brush->gpencil_settings->flag |= GP_BRUSH_USE_STRENGTH_PRESSURE;
|
||||
brush->gpencil_settings->sculpt_mode_flag |= GP_SCULPT_FLAGMODE_APPLY_POSITION;
|
||||
|
||||
break;
|
||||
}
|
||||
case GP_BRUSH_PRESET_WEIGHT_AVERAGE: {
|
||||
brush->gpencil_settings->icon_id = GP_BRUSH_ICON_VERTEX_BLUR;
|
||||
brush->gpencil_weight_tool = GPWEIGHT_TOOL_AVERAGE;
|
||||
|
||||
brush->size = 50.0f;
|
||||
brush->gpencil_settings->flag |= GP_BRUSH_USE_PRESSURE;
|
||||
|
||||
brush->alpha = 0.3f;
|
||||
brush->gpencil_settings->draw_strength = 0.3f;
|
||||
brush->gpencil_settings->flag |= GP_BRUSH_USE_STRENGTH_PRESSURE;
|
||||
brush->gpencil_settings->sculpt_mode_flag |= GP_SCULPT_FLAGMODE_APPLY_POSITION;
|
||||
|
||||
break;
|
||||
}
|
||||
case GP_BRUSH_PRESET_WEIGHT_SMEAR: {
|
||||
brush->gpencil_settings->icon_id = GP_BRUSH_ICON_VERTEX_BLUR;
|
||||
brush->gpencil_weight_tool = GPWEIGHT_TOOL_SMEAR;
|
||||
|
||||
brush->size = 50.0f;
|
||||
brush->gpencil_settings->flag |= GP_BRUSH_USE_PRESSURE;
|
||||
|
||||
brush->alpha = 0.3f;
|
||||
brush->gpencil_settings->draw_strength = 0.3f;
|
||||
brush->gpencil_settings->flag |= GP_BRUSH_USE_STRENGTH_PRESSURE;
|
||||
brush->gpencil_settings->sculpt_mode_flag |= GP_SCULPT_FLAGMODE_APPLY_POSITION;
|
||||
|
||||
|
@ -1569,13 +1612,32 @@ void BKE_brush_gpencil_weight_presets(Main *bmain, ToolSettings *ts, const bool
|
|||
Paint *weightpaint = &ts->gp_weightpaint->paint;
|
||||
Brush *brush_prev = weightpaint->brush;
|
||||
Brush *brush, *deft_weight;
|
||||
/* Vertex Draw brush. */
|
||||
brush = gpencil_brush_ensure(bmain, ts, "Draw Weight", OB_MODE_WEIGHT_GPENCIL, &r_new);
|
||||
|
||||
/* Weight Draw brush. */
|
||||
brush = gpencil_brush_ensure(bmain, ts, "Weight Draw", OB_MODE_WEIGHT_GPENCIL, &r_new);
|
||||
if ((reset) || (r_new)) {
|
||||
BKE_gpencil_brush_preset_set(bmain, brush, GP_BRUSH_PRESET_DRAW_WEIGHT);
|
||||
BKE_gpencil_brush_preset_set(bmain, brush, GP_BRUSH_PRESET_WEIGHT_DRAW);
|
||||
}
|
||||
deft_weight = brush; /* save default brush. */
|
||||
|
||||
/* Weight Blur brush. */
|
||||
brush = gpencil_brush_ensure(bmain, ts, "Weight Blur", OB_MODE_WEIGHT_GPENCIL, &r_new);
|
||||
if ((reset) || (r_new)) {
|
||||
BKE_gpencil_brush_preset_set(bmain, brush, GP_BRUSH_PRESET_WEIGHT_BLUR);
|
||||
}
|
||||
|
||||
/* Weight Average brush. */
|
||||
brush = gpencil_brush_ensure(bmain, ts, "Weight Average", OB_MODE_WEIGHT_GPENCIL, &r_new);
|
||||
if ((reset) || (r_new)) {
|
||||
BKE_gpencil_brush_preset_set(bmain, brush, GP_BRUSH_PRESET_WEIGHT_AVERAGE);
|
||||
}
|
||||
|
||||
/* Weight Smear brush. */
|
||||
brush = gpencil_brush_ensure(bmain, ts, "Weight Smear", OB_MODE_WEIGHT_GPENCIL, &r_new);
|
||||
if ((reset) || (r_new)) {
|
||||
BKE_gpencil_brush_preset_set(bmain, brush, GP_BRUSH_PRESET_WEIGHT_SMEAR);
|
||||
}
|
||||
|
||||
/* Set default brush. */
|
||||
if (reset || brush_prev == nullptr) {
|
||||
BKE_paint_brush_set(weightpaint, deft_weight);
|
||||
|
|
|
@ -833,8 +833,7 @@ static BVHTree *bvhtree_from_editmesh_edges_create_tree(float epsilon,
|
|||
}
|
||||
|
||||
static BVHTree *bvhtree_from_mesh_edges_create_tree(const float (*positions)[3],
|
||||
const blender::int2 *edge,
|
||||
const int edge_num,
|
||||
blender::Span<blender::int2> edges,
|
||||
const BitSpan edges_mask,
|
||||
int edges_num_active,
|
||||
float epsilon,
|
||||
|
@ -842,10 +841,10 @@ static BVHTree *bvhtree_from_mesh_edges_create_tree(const float (*positions)[3],
|
|||
int axis)
|
||||
{
|
||||
if (!edges_mask.is_empty()) {
|
||||
BLI_assert(IN_RANGE_INCL(edges_num_active, 0, edge_num));
|
||||
BLI_assert(IN_RANGE_INCL(edges_num_active, 0, edges.size()));
|
||||
}
|
||||
else {
|
||||
edges_num_active = edge_num;
|
||||
edges_num_active = edges.size();
|
||||
}
|
||||
if (edges_num_active == 0) {
|
||||
return nullptr;
|
||||
|
@ -857,13 +856,13 @@ static BVHTree *bvhtree_from_mesh_edges_create_tree(const float (*positions)[3],
|
|||
return nullptr;
|
||||
}
|
||||
|
||||
for (int i = 0; i < edge_num; i++) {
|
||||
for (const int i : edges.index_range()) {
|
||||
if (!edges_mask.is_empty() && !edges_mask[i]) {
|
||||
continue;
|
||||
}
|
||||
float co[2][3];
|
||||
copy_v3_v3(co[0], positions[edge[i][0]]);
|
||||
copy_v3_v3(co[1], positions[edge[i][1]]);
|
||||
copy_v3_v3(co[0], positions[edges[i][0]]);
|
||||
copy_v3_v3(co[1], positions[edges[i][1]]);
|
||||
|
||||
BLI_bvhtree_insert(tree, i, co[0], 2);
|
||||
}
|
||||
|
@ -908,7 +907,7 @@ BVHTree *bvhtree_from_mesh_edges_ex(BVHTreeFromMesh *data,
|
|||
int axis)
|
||||
{
|
||||
BVHTree *tree = bvhtree_from_mesh_edges_create_tree(
|
||||
vert_positions, edge, edges_num, edges_mask, edges_num_active, epsilon, tree_type, axis);
|
||||
vert_positions, {edge, edges_num}, edges_mask, edges_num_active, epsilon, tree_type, axis);
|
||||
|
||||
bvhtree_balance(tree, false);
|
||||
|
||||
|
@ -1142,38 +1141,6 @@ BVHTree *bvhtree_from_mesh_looptri_ex(BVHTreeFromMesh *data,
|
|||
return tree;
|
||||
}
|
||||
|
||||
static BitVector<> loose_verts_map_get(const Span<blender::int2> edges,
|
||||
int verts_num,
|
||||
int *r_loose_vert_num)
|
||||
{
|
||||
BitVector<> loose_verts_mask(verts_num, true);
|
||||
|
||||
int num_linked_verts = 0;
|
||||
for (const int64_t i : edges.index_range()) {
|
||||
const blender::int2 &edge = edges[i];
|
||||
if (loose_verts_mask[edge[0]]) {
|
||||
loose_verts_mask[edge[0]].reset();
|
||||
num_linked_verts++;
|
||||
}
|
||||
if (loose_verts_mask[edge[1]]) {
|
||||
loose_verts_mask[edge[1]].reset();
|
||||
num_linked_verts++;
|
||||
}
|
||||
}
|
||||
|
||||
*r_loose_vert_num = verts_num - num_linked_verts;
|
||||
|
||||
return loose_verts_mask;
|
||||
}
|
||||
|
||||
static BitVector<> loose_edges_map_get(const Mesh &mesh, int *r_loose_edge_len)
|
||||
{
|
||||
using namespace blender::bke;
|
||||
const LooseEdgeCache &loose_edges = mesh.loose_edges();
|
||||
*r_loose_edge_len = loose_edges.count;
|
||||
return loose_edges.is_loose_bits;
|
||||
}
|
||||
|
||||
static BitVector<> looptri_no_hidden_map_get(const blender::OffsetIndices<int> polys,
|
||||
const VArray<bool> &hide_poly,
|
||||
const int looptri_len,
|
||||
|
@ -1243,27 +1210,36 @@ BVHTree *BKE_bvhtree_from_mesh_get(struct BVHTreeFromMesh *data,
|
|||
}
|
||||
|
||||
/* Create BVHTree. */
|
||||
BitVector<> mask;
|
||||
int mask_bits_act_len = -1;
|
||||
|
||||
switch (bvh_cache_type) {
|
||||
case BVHTREE_FROM_LOOSEVERTS:
|
||||
mask = loose_verts_map_get(edges, mesh->totvert, &mask_bits_act_len);
|
||||
ATTR_FALLTHROUGH;
|
||||
case BVHTREE_FROM_VERTS:
|
||||
case BVHTREE_FROM_LOOSEVERTS: {
|
||||
const blender::bke::LooseVertCache &loose_verts = mesh->loose_verts();
|
||||
data->tree = bvhtree_from_mesh_verts_create_tree(0.0f,
|
||||
tree_type,
|
||||
6,
|
||||
positions,
|
||||
mesh->totvert,
|
||||
loose_verts.is_loose_bits,
|
||||
loose_verts.count);
|
||||
break;
|
||||
}
|
||||
case BVHTREE_FROM_VERTS: {
|
||||
data->tree = bvhtree_from_mesh_verts_create_tree(
|
||||
0.0f, tree_type, 6, positions, mesh->totvert, mask, mask_bits_act_len);
|
||||
0.0f, tree_type, 6, positions, mesh->totvert, {}, -1);
|
||||
break;
|
||||
|
||||
case BVHTREE_FROM_LOOSEEDGES:
|
||||
mask = loose_edges_map_get(*mesh, &mask_bits_act_len);
|
||||
ATTR_FALLTHROUGH;
|
||||
case BVHTREE_FROM_EDGES:
|
||||
}
|
||||
case BVHTREE_FROM_LOOSEEDGES: {
|
||||
const blender::bke::LooseEdgeCache &loose_edges = mesh->loose_edges();
|
||||
data->tree = bvhtree_from_mesh_edges_create_tree(
|
||||
positions, edges.data(), mesh->totedge, mask, mask_bits_act_len, 0.0f, tree_type, 6);
|
||||
positions, edges, loose_edges.is_loose_bits, loose_edges.count, 0.0f, tree_type, 6);
|
||||
break;
|
||||
|
||||
case BVHTREE_FROM_FACES:
|
||||
}
|
||||
case BVHTREE_FROM_EDGES: {
|
||||
data->tree = bvhtree_from_mesh_edges_create_tree(
|
||||
positions, edges, {}, -1, 0.0f, tree_type, 6);
|
||||
break;
|
||||
}
|
||||
case BVHTREE_FROM_FACES: {
|
||||
BLI_assert(!(mesh->totface == 0 && mesh->totpoly != 0));
|
||||
data->tree = bvhtree_from_mesh_faces_create_tree(
|
||||
0.0f,
|
||||
|
@ -1275,25 +1251,29 @@ BVHTree *BKE_bvhtree_from_mesh_get(struct BVHTreeFromMesh *data,
|
|||
{},
|
||||
-1);
|
||||
break;
|
||||
|
||||
}
|
||||
case BVHTREE_FROM_LOOPTRI_NO_HIDDEN: {
|
||||
blender::bke::AttributeAccessor attributes = mesh->attributes();
|
||||
mask = looptri_no_hidden_map_get(
|
||||
int mask_bits_act_len = -1;
|
||||
const BitVector<> mask = looptri_no_hidden_map_get(
|
||||
mesh->polys(),
|
||||
*attributes.lookup_or_default(".hide_poly", ATTR_DOMAIN_FACE, false),
|
||||
looptris.size(),
|
||||
&mask_bits_act_len);
|
||||
ATTR_FALLTHROUGH;
|
||||
}
|
||||
case BVHTREE_FROM_LOOPTRI:
|
||||
data->tree = bvhtree_from_mesh_looptri_create_tree(
|
||||
0.0f, tree_type, 6, positions, corner_verts.data(), looptris, mask, mask_bits_act_len);
|
||||
break;
|
||||
}
|
||||
case BVHTREE_FROM_LOOPTRI: {
|
||||
data->tree = bvhtree_from_mesh_looptri_create_tree(
|
||||
0.0f, tree_type, 6, positions, corner_verts.data(), looptris, {}, -1);
|
||||
break;
|
||||
}
|
||||
case BVHTREE_FROM_EM_VERTS:
|
||||
case BVHTREE_FROM_EM_EDGES:
|
||||
case BVHTREE_FROM_EM_LOOPTRI:
|
||||
case BVHTREE_MAX_ITEM:
|
||||
BLI_assert(false);
|
||||
BLI_assert_unreachable();
|
||||
break;
|
||||
}
|
||||
|
||||
|
|
|
@ -198,7 +198,7 @@ static void collection_foreach_id(ID *id, LibraryForeachIDData *data)
|
|||
* anyway... */
|
||||
const int cb_flag = ((parent->collection != NULL &&
|
||||
(parent->collection->id.flag & LIB_EMBEDDED_DATA) != 0) ?
|
||||
IDWALK_CB_EMBEDDED :
|
||||
IDWALK_CB_EMBEDDED_NOT_OWNING :
|
||||
IDWALK_CB_NOP);
|
||||
BKE_LIB_FOREACHID_PROCESS_IDSUPER(
|
||||
data, parent->collection, IDWALK_CB_NEVER_SELF | IDWALK_CB_LOOPBACK | cb_flag);
|
||||
|
|
|
@ -237,7 +237,8 @@ struct ResultOffsets {
|
|||
Array<int> profile_indices;
|
||||
|
||||
/** Whether any curve in the profile or curve input has only a single evaluated point. */
|
||||
bool any_single_point_curve;
|
||||
bool any_single_point_main;
|
||||
bool any_single_point_profile;
|
||||
};
|
||||
static ResultOffsets calculate_result_offsets(const CurvesInfo &info, const bool fill_caps)
|
||||
{
|
||||
|
@ -315,10 +316,8 @@ static ResultOffsets calculate_result_offsets(const CurvesInfo &info, const bool
|
|||
}
|
||||
}
|
||||
},
|
||||
[&]() {
|
||||
result.any_single_point_curve = offsets_contain_single_point(main_offsets) ||
|
||||
offsets_contain_single_point(profile_offsets);
|
||||
});
|
||||
[&]() { result.any_single_point_main = offsets_contain_single_point(main_offsets); },
|
||||
[&]() { result.any_single_point_profile = offsets_contain_single_point(profile_offsets); });
|
||||
|
||||
return result;
|
||||
}
|
||||
|
@ -765,9 +764,13 @@ Mesh *curve_to_mesh_sweep(const CurvesGeometry &main,
|
|||
positions.slice(info.vert_range));
|
||||
});
|
||||
|
||||
if (!offsets.any_single_point_curve) {
|
||||
/* If there are no single point curves, every curve combination will always have faces. */
|
||||
mesh->loose_edges_tag_none();
|
||||
if (!offsets.any_single_point_main) {
|
||||
/* If there are no single point curves, every combination will have at least loose edges. */
|
||||
mesh->tag_loose_verts_none();
|
||||
if (!offsets.any_single_point_profile) {
|
||||
/* If there are no single point profiles, every combination will have faces. */
|
||||
mesh->loose_edges_tag_none();
|
||||
}
|
||||
}
|
||||
|
||||
SpanAttributeWriter<bool> sharp_edges;
|
||||
|
|
|
@ -2358,12 +2358,12 @@ CustomData CustomData_shallow_copy_remove_non_bmesh_attributes(const CustomData
|
|||
class CustomDataLayerImplicitSharing : public ImplicitSharingInfo {
|
||||
private:
|
||||
const void *data_;
|
||||
const int totelem_;
|
||||
int totelem_;
|
||||
const eCustomDataType type_;
|
||||
|
||||
public:
|
||||
CustomDataLayerImplicitSharing(const void *data, const int totelem, const eCustomDataType type)
|
||||
: ImplicitSharingInfo(1), data_(data), totelem_(totelem), type_(type)
|
||||
: ImplicitSharingInfo(), data_(data), totelem_(totelem), type_(type)
|
||||
{
|
||||
}
|
||||
|
||||
|
@ -2373,12 +2373,19 @@ class CustomDataLayerImplicitSharing : public ImplicitSharingInfo {
|
|||
free_layer_data(type_, data_, totelem_);
|
||||
MEM_delete(this);
|
||||
}
|
||||
|
||||
void delete_data_only() override
|
||||
{
|
||||
free_layer_data(type_, data_, totelem_);
|
||||
data_ = nullptr;
|
||||
totelem_ = 0;
|
||||
}
|
||||
};
|
||||
|
||||
/** Create a #ImplicitSharingInfo that takes ownership of the data. */
|
||||
static ImplicitSharingInfo *make_implicit_sharing_info_for_layer(const eCustomDataType type,
|
||||
const void *data,
|
||||
const int totelem)
|
||||
static const ImplicitSharingInfo *make_implicit_sharing_info_for_layer(const eCustomDataType type,
|
||||
const void *data,
|
||||
const int totelem)
|
||||
{
|
||||
return MEM_new<CustomDataLayerImplicitSharing>(__func__, data, totelem, type);
|
||||
}
|
||||
|
@ -2404,6 +2411,9 @@ static void ensure_layer_data_is_mutable(CustomDataLayer &layer, const int totel
|
|||
layer.sharing_info->remove_user_and_delete_if_last();
|
||||
layer.sharing_info = make_implicit_sharing_info_for_layer(type, layer.data, totelem);
|
||||
}
|
||||
else {
|
||||
layer.sharing_info->tag_ensured_mutable();
|
||||
}
|
||||
}
|
||||
|
||||
void CustomData_realloc(CustomData *data, const int old_size, const int new_size)
|
||||
|
|
|
@ -431,7 +431,9 @@ CDataFileLayer *cdf_layer_add(CDataFile *cdf, int type, const char *name, size_t
|
|||
|
||||
/* expand array */
|
||||
newlayer = MEM_calloc_arrayN((cdf->totlayer + 1), sizeof(CDataFileLayer), "CDataFileLayer");
|
||||
memcpy(newlayer, cdf->layer, sizeof(CDataFileLayer) * cdf->totlayer);
|
||||
if (cdf->totlayer > 0) {
|
||||
memcpy(newlayer, cdf->layer, sizeof(CDataFileLayer) * cdf->totlayer);
|
||||
}
|
||||
cdf->layer = newlayer;
|
||||
|
||||
cdf->totlayer++;
|
||||
|
|
|
@ -372,7 +372,7 @@ static ComponentAttributeProviders create_attribute_providers_for_curve()
|
|||
ATTR_DOMAIN_POINT,
|
||||
CD_PROP_FLOAT3,
|
||||
CD_PROP_FLOAT3,
|
||||
BuiltinAttributeProvider::NonCreatable,
|
||||
BuiltinAttributeProvider::Creatable,
|
||||
BuiltinAttributeProvider::NonDeletable,
|
||||
point_access,
|
||||
tag_component_positions_changed);
|
||||
|
|
|
@ -190,29 +190,27 @@ void adapt_mesh_domain_corner_to_point_impl(const Mesh &mesh,
|
|||
BLI_assert(r_values.size() == mesh.totvert);
|
||||
const Span<int> corner_verts = mesh.corner_verts();
|
||||
|
||||
Array<bool> loose_verts(mesh.totvert, true);
|
||||
|
||||
r_values.fill(true);
|
||||
for (const int corner : IndexRange(mesh.totloop)) {
|
||||
const int point_index = corner_verts[corner];
|
||||
|
||||
loose_verts[point_index] = false;
|
||||
if (!old_values[corner]) {
|
||||
r_values[point_index] = false;
|
||||
}
|
||||
}
|
||||
|
||||
/* Deselect loose vertices without corners that are still selected from the 'true' default. */
|
||||
/* The record fact says that the value is true.
|
||||
* Writing to the array from different threads is okay because each thread sets the same value.
|
||||
*/
|
||||
threading::parallel_for(loose_verts.index_range(), 2048, [&](const IndexRange range) {
|
||||
for (const int vert_index : range) {
|
||||
if (loose_verts[vert_index]) {
|
||||
r_values[vert_index] = false;
|
||||
const bke::LooseVertCache &loose_verts = mesh.verts_no_face();
|
||||
if (loose_verts.count > 0) {
|
||||
const BitSpan bits = loose_verts.is_loose_bits;
|
||||
threading::parallel_for(bits.index_range(), 2048, [&](const IndexRange range) {
|
||||
for (const int vert_index : range) {
|
||||
if (bits[vert_index]) {
|
||||
r_values[vert_index] = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
static GVArray adapt_mesh_domain_corner_to_point(const Mesh &mesh, const GVArray &varray)
|
||||
|
@ -754,20 +752,26 @@ static bool can_simple_adapt_for_single(const Mesh &mesh,
|
|||
/* All other domains are always connected to points. */
|
||||
return true;
|
||||
case ATTR_DOMAIN_EDGE:
|
||||
/* There may be loose vertices not connected to edges. */
|
||||
return ELEM(to_domain, ATTR_DOMAIN_FACE, ATTR_DOMAIN_CORNER);
|
||||
if (to_domain == ATTR_DOMAIN_POINT) {
|
||||
return mesh.loose_verts().count == 0;
|
||||
}
|
||||
return true;
|
||||
case ATTR_DOMAIN_FACE:
|
||||
/* There may be loose vertices or edges not connected to faces. */
|
||||
if (to_domain == ATTR_DOMAIN_POINT) {
|
||||
return mesh.verts_no_face().count == 0;
|
||||
}
|
||||
if (to_domain == ATTR_DOMAIN_EDGE) {
|
||||
return mesh.loose_edges().count == 0;
|
||||
}
|
||||
return to_domain == ATTR_DOMAIN_CORNER;
|
||||
return true;
|
||||
case ATTR_DOMAIN_CORNER:
|
||||
/* Only faces are always connected to corners. */
|
||||
if (to_domain == ATTR_DOMAIN_POINT) {
|
||||
return mesh.verts_no_face().count == 0;
|
||||
}
|
||||
if (to_domain == ATTR_DOMAIN_EDGE) {
|
||||
return mesh.loose_edges().count == 0;
|
||||
}
|
||||
return to_domain == ATTR_DOMAIN_FACE;
|
||||
return true;
|
||||
default:
|
||||
BLI_assert_unreachable();
|
||||
return false;
|
||||
|
@ -1153,7 +1157,7 @@ static ComponentAttributeProviders create_attribute_providers_for_mesh()
|
|||
ATTR_DOMAIN_EDGE,
|
||||
CD_PROP_INT32_2D,
|
||||
CD_PROP_INT32_2D,
|
||||
BuiltinAttributeProvider::NonCreatable,
|
||||
BuiltinAttributeProvider::Creatable,
|
||||
BuiltinAttributeProvider::NonDeletable,
|
||||
edge_access,
|
||||
nullptr,
|
||||
|
@ -1169,7 +1173,7 @@ static ComponentAttributeProviders create_attribute_providers_for_mesh()
|
|||
ATTR_DOMAIN_CORNER,
|
||||
CD_PROP_INT32,
|
||||
CD_PROP_INT32,
|
||||
BuiltinAttributeProvider::NonCreatable,
|
||||
BuiltinAttributeProvider::Creatable,
|
||||
BuiltinAttributeProvider::NonDeletable,
|
||||
corner_access,
|
||||
nullptr,
|
||||
|
@ -1178,7 +1182,7 @@ static ComponentAttributeProviders create_attribute_providers_for_mesh()
|
|||
ATTR_DOMAIN_CORNER,
|
||||
CD_PROP_INT32,
|
||||
CD_PROP_INT32,
|
||||
BuiltinAttributeProvider::NonCreatable,
|
||||
BuiltinAttributeProvider::Creatable,
|
||||
BuiltinAttributeProvider::NonDeletable,
|
||||
corner_access,
|
||||
nullptr,
|
||||
|
|
|
@ -120,6 +120,7 @@ GeometryComponent &GeometrySet::get_component_for_write(GeometryComponentType co
|
|||
}
|
||||
if (component_ptr->is_mutable()) {
|
||||
/* If the referenced component is already mutable, return it directly. */
|
||||
component_ptr->tag_ensured_mutable();
|
||||
return *component_ptr;
|
||||
}
|
||||
/* If the referenced component is shared, make a copy. The copy is not shared and is
|
||||
|
|
|
@ -130,6 +130,8 @@ static void mesh_copy_data(Main *bmain, ID *id_dst, const ID *id_src, const int
|
|||
* when the source is persistent and edits to the destination mesh don't affect the caches.
|
||||
* Caches will be "un-shared" as necessary later on. */
|
||||
mesh_dst->runtime->bounds_cache = mesh_src->runtime->bounds_cache;
|
||||
mesh_dst->runtime->loose_verts_cache = mesh_src->runtime->loose_verts_cache;
|
||||
mesh_dst->runtime->verts_no_face_cache = mesh_src->runtime->verts_no_face_cache;
|
||||
mesh_dst->runtime->loose_edges_cache = mesh_src->runtime->loose_edges_cache;
|
||||
mesh_dst->runtime->looptris_cache = mesh_src->runtime->looptris_cache;
|
||||
|
||||
|
|
|
@ -106,32 +106,90 @@ MeshRuntime::~MeshRuntime()
|
|||
}
|
||||
}
|
||||
|
||||
static int reset_bits_and_count(MutableBitSpan bits, const Span<int> indices_to_reset)
|
||||
{
|
||||
int count = bits.size();
|
||||
for (const int vert : indices_to_reset) {
|
||||
if (bits[vert]) {
|
||||
bits[vert].reset();
|
||||
count--;
|
||||
}
|
||||
}
|
||||
return count;
|
||||
}
|
||||
|
||||
static void bit_vector_with_reset_bits_or_empty(const Span<int> indices_to_reset,
|
||||
const int indexed_elems_num,
|
||||
BitVector<> &r_bits,
|
||||
int &r_count)
|
||||
{
|
||||
r_bits.resize(0);
|
||||
r_bits.resize(indexed_elems_num, true);
|
||||
r_count = reset_bits_and_count(r_bits, indices_to_reset);
|
||||
if (r_count == 0) {
|
||||
r_bits.clear_and_shrink();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* If there are no loose edges and no loose vertices, all vertices are used by faces.
|
||||
*/
|
||||
static void try_tag_verts_no_face_none(const Mesh &mesh)
|
||||
{
|
||||
if (mesh.runtime->loose_edges_cache.is_cached() || mesh.loose_edges().count > 0) {
|
||||
return;
|
||||
}
|
||||
if (mesh.runtime->loose_verts_cache.is_cached() || mesh.loose_verts().count > 0) {
|
||||
return;
|
||||
}
|
||||
mesh.runtime->verts_no_face_cache.ensure([&](LooseVertCache &r_data) {
|
||||
r_data.is_loose_bits.clear_and_shrink();
|
||||
r_data.count = 0;
|
||||
});
|
||||
}
|
||||
|
||||
} // namespace blender::bke
|
||||
|
||||
const blender::bke::LooseVertCache &Mesh::loose_verts() const
|
||||
{
|
||||
using namespace blender::bke;
|
||||
this->runtime->loose_verts_cache.ensure([&](LooseVertCache &r_data) {
|
||||
const Span<int> verts = this->edges().cast<int>();
|
||||
bit_vector_with_reset_bits_or_empty(verts, this->totvert, r_data.is_loose_bits, r_data.count);
|
||||
});
|
||||
return this->runtime->loose_verts_cache.data();
|
||||
}
|
||||
|
||||
const blender::bke::LooseVertCache &Mesh::verts_no_face() const
|
||||
{
|
||||
using namespace blender::bke;
|
||||
this->runtime->verts_no_face_cache.ensure([&](LooseVertCache &r_data) {
|
||||
const Span<int> verts = this->corner_verts();
|
||||
bit_vector_with_reset_bits_or_empty(verts, this->totvert, r_data.is_loose_bits, r_data.count);
|
||||
});
|
||||
return this->runtime->verts_no_face_cache.data();
|
||||
}
|
||||
|
||||
const blender::bke::LooseEdgeCache &Mesh::loose_edges() const
|
||||
{
|
||||
using namespace blender::bke;
|
||||
this->runtime->loose_edges_cache.ensure([&](LooseEdgeCache &r_data) {
|
||||
blender::BitVector<> &loose_edges = r_data.is_loose_bits;
|
||||
loose_edges.resize(0);
|
||||
loose_edges.resize(this->totedge, true);
|
||||
|
||||
int count = this->totedge;
|
||||
for (const int edge : this->corner_edges()) {
|
||||
if (loose_edges[edge]) {
|
||||
loose_edges[edge].reset();
|
||||
count--;
|
||||
}
|
||||
}
|
||||
if (count == 0) {
|
||||
loose_edges.clear_and_shrink();
|
||||
}
|
||||
r_data.count = count;
|
||||
const Span<int> edges = this->corner_edges();
|
||||
bit_vector_with_reset_bits_or_empty(edges, this->totedge, r_data.is_loose_bits, r_data.count);
|
||||
});
|
||||
|
||||
return this->runtime->loose_edges_cache.data();
|
||||
}
|
||||
|
||||
void Mesh::tag_loose_verts_none() const
|
||||
{
|
||||
using namespace blender::bke;
|
||||
this->runtime->loose_verts_cache.ensure([&](LooseVertCache &r_data) {
|
||||
r_data.is_loose_bits.clear_and_shrink();
|
||||
r_data.count = 0;
|
||||
});
|
||||
try_tag_verts_no_face_none(*this);
|
||||
}
|
||||
|
||||
void Mesh::loose_edges_tag_none() const
|
||||
{
|
||||
using namespace blender::bke;
|
||||
|
@ -139,6 +197,7 @@ void Mesh::loose_edges_tag_none() const
|
|||
r_data.is_loose_bits.clear_and_shrink();
|
||||
r_data.count = 0;
|
||||
});
|
||||
try_tag_verts_no_face_none(*this);
|
||||
}
|
||||
|
||||
blender::Span<MLoopTri> Mesh::looptris() const
|
||||
|
@ -219,6 +278,8 @@ void BKE_mesh_runtime_clear_geometry(Mesh *mesh)
|
|||
free_subdiv_ccg(*mesh->runtime);
|
||||
mesh->runtime->bounds_cache.tag_dirty();
|
||||
mesh->runtime->loose_edges_cache.tag_dirty();
|
||||
mesh->runtime->loose_verts_cache.tag_dirty();
|
||||
mesh->runtime->verts_no_face_cache.tag_dirty();
|
||||
mesh->runtime->looptris_cache.tag_dirty();
|
||||
mesh->runtime->subsurf_face_dot_tags.clear_and_shrink();
|
||||
mesh->runtime->subsurf_optimal_display_edges.clear_and_shrink();
|
||||
|
@ -237,6 +298,8 @@ void BKE_mesh_tag_edges_split(struct Mesh *mesh)
|
|||
reset_normals(*mesh->runtime);
|
||||
free_subdiv_ccg(*mesh->runtime);
|
||||
mesh->runtime->loose_edges_cache.tag_dirty();
|
||||
mesh->runtime->loose_verts_cache.tag_dirty();
|
||||
mesh->runtime->verts_no_face_cache.tag_dirty();
|
||||
mesh->runtime->subsurf_face_dot_tags.clear_and_shrink();
|
||||
mesh->runtime->subsurf_optimal_display_edges.clear_and_shrink();
|
||||
if (mesh->runtime->shrinkwrap_data) {
|
||||
|
|
|
@ -63,20 +63,10 @@ BLI_INLINE void mesh_calc_tessellation_for_face_impl(const Span<int> corner_vert
|
|||
MLoopTri *mlt_a = mlt++;
|
||||
create_tri(0, 2, 3);
|
||||
MLoopTri *mlt_b = mlt;
|
||||
|
||||
if (UNLIKELY(face_normal ? is_quad_flip_v3_first_third_fast_with_normal(
|
||||
/* Simpler calculation (using the normal). */
|
||||
positions[corner_verts[mlt_a->tri[0]]],
|
||||
positions[corner_verts[mlt_a->tri[1]]],
|
||||
positions[corner_verts[mlt_a->tri[2]]],
|
||||
positions[corner_verts[mlt_b->tri[2]]],
|
||||
normal_precalc) :
|
||||
is_quad_flip_v3_first_third_fast(
|
||||
/* Expensive calculation (no normal). */
|
||||
positions[corner_verts[mlt_a->tri[0]]],
|
||||
positions[corner_verts[mlt_a->tri[1]]],
|
||||
positions[corner_verts[mlt_a->tri[2]]],
|
||||
positions[corner_verts[mlt_b->tri[2]]]))) {
|
||||
if (UNLIKELY(is_quad_flip_v3_first_third_fast(positions[corner_verts[mlt_a->tri[0]]],
|
||||
positions[corner_verts[mlt_a->tri[1]]],
|
||||
positions[corner_verts[mlt_a->tri[2]]],
|
||||
positions[corner_verts[mlt_b->tri[2]]]))) {
|
||||
/* Flip out of degenerate 0-2 state. */
|
||||
mlt_a->tri[2] = mlt_b->tri[2];
|
||||
mlt_b->tri[0] = mlt_a->tri[1];
|
||||
|
|
|
@ -1020,8 +1020,6 @@ static void multires_unsubdivide_extract_grids(MultiresUnsubdivideContext *conte
|
|||
* so they can be used from #BMesh. */
|
||||
multires_unsubdivide_add_original_index_datalayers(base_mesh);
|
||||
|
||||
const int base_l_layer_index = CustomData_get_named_layer_index(
|
||||
&base_mesh->ldata, CD_PROP_INT32, lname);
|
||||
BMesh *bm_base_mesh = get_bmesh_from_mesh(base_mesh);
|
||||
BMIter iter, iter_a, iter_b;
|
||||
BMVert *v;
|
||||
|
@ -1031,8 +1029,8 @@ static void multires_unsubdivide_extract_grids(MultiresUnsubdivideContext *conte
|
|||
BM_mesh_elem_table_ensure(bm_base_mesh, BM_FACE);
|
||||
|
||||
/* Get the data-layer that contains the loops indices. */
|
||||
const int base_l_offset = CustomData_get_n_offset(
|
||||
&bm_base_mesh->ldata, CD_PROP_INT32, base_l_layer_index);
|
||||
const int base_l_offset = CustomData_get_offset_named(
|
||||
&bm_base_mesh->ldata, CD_PROP_INT32, lname);
|
||||
|
||||
const blender::OffsetIndices polys = base_mesh->polys();
|
||||
const blender::Span<int> corner_verts = base_mesh->corner_verts();
|
||||
|
|
|
@ -0,0 +1,93 @@
|
|||
/* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
#include "BKE_simulation_state.hh"
|
||||
#include "BKE_simulation_state_serialize.hh"
|
||||
|
||||
#include "BLI_fileops.hh"
|
||||
#include "BLI_path_util.h"
|
||||
|
||||
namespace blender::bke::sim {
|
||||
|
||||
void ModifierSimulationCache::try_discover_bake(const StringRefNull meta_dir,
|
||||
const StringRefNull bdata_dir)
|
||||
{
|
||||
if (failed_finding_bake_) {
|
||||
return;
|
||||
}
|
||||
if (!BLI_is_dir(meta_dir.c_str()) || !BLI_is_dir(bdata_dir.c_str())) {
|
||||
failed_finding_bake_ = true;
|
||||
return;
|
||||
}
|
||||
|
||||
direntry *dir_entries = nullptr;
|
||||
const int dir_entries_num = BLI_filelist_dir_contents(meta_dir.c_str(), &dir_entries);
|
||||
BLI_SCOPED_DEFER([&]() { BLI_filelist_free(dir_entries, dir_entries_num); });
|
||||
|
||||
if (dir_entries_num == 0) {
|
||||
failed_finding_bake_ = true;
|
||||
return;
|
||||
}
|
||||
|
||||
this->reset();
|
||||
|
||||
for (const int i : IndexRange(dir_entries_num)) {
|
||||
const direntry &dir_entry = dir_entries[i];
|
||||
const StringRefNull dir_entry_path = dir_entry.path;
|
||||
if (!dir_entry_path.endswith(".json")) {
|
||||
continue;
|
||||
}
|
||||
char modified_file_name[FILENAME_MAX];
|
||||
BLI_strncpy(modified_file_name, dir_entry.relname, sizeof(modified_file_name));
|
||||
BLI_str_replace_char(modified_file_name, '_', '.');
|
||||
|
||||
const SubFrame frame = std::stof(modified_file_name);
|
||||
|
||||
auto new_state_at_frame = std::make_unique<ModifierSimulationStateAtFrame>();
|
||||
new_state_at_frame->frame = frame;
|
||||
new_state_at_frame->state.bdata_dir_ = bdata_dir;
|
||||
new_state_at_frame->state.meta_path_ = dir_entry.path;
|
||||
new_state_at_frame->state.owner_ = this;
|
||||
states_at_frames_.append(std::move(new_state_at_frame));
|
||||
}
|
||||
|
||||
bdata_sharing_ = std::make_unique<BDataSharing>();
|
||||
|
||||
cache_state_ = CacheState::Baked;
|
||||
}
|
||||
|
||||
void ModifierSimulationState::ensure_bake_loaded() const
|
||||
{
|
||||
std::scoped_lock lock{mutex_};
|
||||
if (bake_loaded_) {
|
||||
return;
|
||||
}
|
||||
if (!meta_path_ || !bdata_dir_) {
|
||||
return;
|
||||
}
|
||||
|
||||
const std::shared_ptr<io::serialize::Value> io_root_value = io::serialize::read_json_file(
|
||||
*meta_path_);
|
||||
if (!io_root_value) {
|
||||
return;
|
||||
}
|
||||
const DictionaryValue *io_root = io_root_value->as_dictionary_value();
|
||||
if (!io_root) {
|
||||
return;
|
||||
}
|
||||
|
||||
const DiskBDataReader bdata_reader{*bdata_dir_};
|
||||
deserialize_modifier_simulation_state(*io_root,
|
||||
bdata_reader,
|
||||
*owner_->bdata_sharing_,
|
||||
const_cast<ModifierSimulationState &>(*this));
|
||||
bake_loaded_ = true;
|
||||
}
|
||||
|
||||
void ModifierSimulationCache::reset()
|
||||
{
|
||||
states_at_frames_.clear();
|
||||
bdata_sharing_.reset();
|
||||
cache_state_ = CacheState::Valid;
|
||||
}
|
||||
|
||||
} // namespace blender::bke::sim
|
|
@ -0,0 +1,945 @@
|
|||
/* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
#include "BKE_curves.hh"
|
||||
#include "BKE_instances.hh"
|
||||
#include "BKE_lib_id.h"
|
||||
#include "BKE_main.h"
|
||||
#include "BKE_mesh.hh"
|
||||
#include "BKE_pointcloud.h"
|
||||
#include "BKE_simulation_state_serialize.hh"
|
||||
|
||||
#include "DNA_material_types.h"
|
||||
#include "DNA_modifier_types.h"
|
||||
#include "DNA_object_types.h"
|
||||
|
||||
#include "BLI_endian_defines.h"
|
||||
#include "BLI_endian_switch.h"
|
||||
#include "BLI_fileops.hh"
|
||||
#include "BLI_math_matrix_types.hh"
|
||||
#include "BLI_path_util.h"
|
||||
|
||||
#include "RNA_access.h"
|
||||
#include "RNA_enum_types.h"
|
||||
|
||||
namespace blender::bke::sim {
|
||||
|
||||
/**
|
||||
* Turn the name into something that can be used as file name. It does not necessarily have to be
|
||||
* human readible, but it can help if it is at least partially readible.
|
||||
*/
|
||||
static std::string escape_name(const StringRef name)
|
||||
{
|
||||
std::stringstream ss;
|
||||
for (const char c : name) {
|
||||
/* Only some letters allowed. Digits are not because they could lead to name collisions. */
|
||||
if (('a' <= c && c <= 'z') || ('A' <= c && c <= 'Z')) {
|
||||
ss << c;
|
||||
}
|
||||
else {
|
||||
ss << int(c);
|
||||
}
|
||||
}
|
||||
return ss.str();
|
||||
}
|
||||
|
||||
static std::string get_blendcache_directory(const Main &bmain)
|
||||
{
|
||||
StringRefNull blend_file_path = BKE_main_blendfile_path(&bmain);
|
||||
char blend_directory[FILE_MAX];
|
||||
char blend_name[FILE_MAX];
|
||||
BLI_split_dirfile(blend_file_path.c_str(),
|
||||
blend_directory,
|
||||
blend_name,
|
||||
sizeof(blend_directory),
|
||||
sizeof(blend_name));
|
||||
blend_name[StringRef(blend_name).rfind(".")] = '\0';
|
||||
const std::string blendcache_name = "blendcache_" + StringRef(blend_name);
|
||||
|
||||
char blendcache_dir[FILE_MAX];
|
||||
BLI_path_join(blendcache_dir, sizeof(blendcache_dir), blend_directory, blendcache_name.c_str());
|
||||
return blendcache_dir;
|
||||
}
|
||||
|
||||
static std::string get_modifier_sim_name(const Object &object, const ModifierData &md)
|
||||
{
|
||||
const std::string object_name_escaped = escape_name(object.id.name + 2);
|
||||
const std::string modifier_name_escaped = escape_name(md.name);
|
||||
return "sim_" + object_name_escaped + "_" + modifier_name_escaped;
|
||||
}
|
||||
|
||||
std::string get_bake_directory(const Main &bmain, const Object &object, const ModifierData &md)
|
||||
{
|
||||
char bdata_dir[FILE_MAX];
|
||||
BLI_path_join(bdata_dir,
|
||||
sizeof(bdata_dir),
|
||||
get_blendcache_directory(bmain).c_str(),
|
||||
get_modifier_sim_name(object, md).c_str());
|
||||
return bdata_dir;
|
||||
}
|
||||
|
||||
std::string get_bdata_directory(const Main &bmain, const Object &object, const ModifierData &md)
|
||||
{
|
||||
char bdata_dir[FILE_MAX];
|
||||
BLI_path_join(
|
||||
bdata_dir, sizeof(bdata_dir), get_bake_directory(bmain, object, md).c_str(), "bdata");
|
||||
return bdata_dir;
|
||||
}
|
||||
|
||||
std::string get_meta_directory(const Main &bmain, const Object &object, const ModifierData &md)
|
||||
{
|
||||
char meta_dir[FILE_MAX];
|
||||
BLI_path_join(meta_dir, sizeof(meta_dir), get_bake_directory(bmain, object, md).c_str(), "meta");
|
||||
return meta_dir;
|
||||
}
|
||||
|
||||
std::shared_ptr<DictionaryValue> BDataSlice::serialize() const
|
||||
{
|
||||
auto io_slice = std::make_shared<DictionaryValue>();
|
||||
io_slice->append_str("name", this->name);
|
||||
io_slice->append_int("start", range.start());
|
||||
io_slice->append_int("size", range.size());
|
||||
return io_slice;
|
||||
}
|
||||
|
||||
std::optional<BDataSlice> BDataSlice::deserialize(const DictionaryValue &io_slice)
|
||||
{
|
||||
const std::optional<StringRefNull> name = io_slice.lookup_str("name");
|
||||
const std::optional<int64_t> start = io_slice.lookup_int("start");
|
||||
const std::optional<int64_t> size = io_slice.lookup_int("size");
|
||||
if (!name || !start || !size) {
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
return BDataSlice{*name, {*start, *size}};
|
||||
}
|
||||
|
||||
static StringRefNull get_endian_io_name(const int endian)
|
||||
{
|
||||
if (endian == L_ENDIAN) {
|
||||
return "little";
|
||||
}
|
||||
BLI_assert(endian == B_ENDIAN);
|
||||
return "big";
|
||||
}
|
||||
|
||||
static StringRefNull get_domain_io_name(const eAttrDomain domain)
|
||||
{
|
||||
const char *io_name = "unknown";
|
||||
RNA_enum_id_from_value(rna_enum_attribute_domain_items, domain, &io_name);
|
||||
return io_name;
|
||||
}
|
||||
|
||||
static StringRefNull get_data_type_io_name(const eCustomDataType data_type)
|
||||
{
|
||||
const char *io_name = "unknown";
|
||||
RNA_enum_id_from_value(rna_enum_attribute_type_items, data_type, &io_name);
|
||||
return io_name;
|
||||
}
|
||||
|
||||
static std::optional<eAttrDomain> get_domain_from_io_name(const StringRefNull io_name)
|
||||
{
|
||||
int domain;
|
||||
if (!RNA_enum_value_from_identifier(rna_enum_attribute_domain_items, io_name.c_str(), &domain)) {
|
||||
return std::nullopt;
|
||||
}
|
||||
return eAttrDomain(domain);
|
||||
}
|
||||
|
||||
static std::optional<eCustomDataType> get_data_type_from_io_name(const StringRefNull io_name)
|
||||
{
|
||||
int domain;
|
||||
if (!RNA_enum_value_from_identifier(rna_enum_attribute_type_items, io_name.c_str(), &domain)) {
|
||||
return std::nullopt;
|
||||
}
|
||||
return eCustomDataType(domain);
|
||||
}
|
||||
|
||||
/**
|
||||
* Write the data and remember which endianness the data had.
|
||||
*/
|
||||
static std::shared_ptr<DictionaryValue> write_bdata_raw_data_with_endian(
|
||||
BDataWriter &bdata_writer, const void *data, const int64_t size_in_bytes)
|
||||
{
|
||||
auto io_data = bdata_writer.write(data, size_in_bytes).serialize();
|
||||
if (ENDIAN_ORDER == B_ENDIAN) {
|
||||
io_data->append_str("endian", get_endian_io_name(ENDIAN_ORDER));
|
||||
}
|
||||
return io_data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Read data of an into an array and optionally perform an endian switch if necessary.
|
||||
*/
|
||||
[[nodiscard]] static bool read_bdata_raw_data_with_endian(const BDataReader &bdata_reader,
|
||||
const DictionaryValue &io_data,
|
||||
const int64_t element_size,
|
||||
const int64_t elements_num,
|
||||
void *r_data)
|
||||
{
|
||||
const std::optional<BDataSlice> slice = BDataSlice::deserialize(io_data);
|
||||
if (!slice) {
|
||||
return false;
|
||||
}
|
||||
if (slice->range.size() != element_size * elements_num) {
|
||||
return false;
|
||||
}
|
||||
if (!bdata_reader.read(*slice, r_data)) {
|
||||
return false;
|
||||
}
|
||||
const StringRefNull stored_endian = io_data.lookup_str("endian").value_or("little");
|
||||
const StringRefNull current_endian = get_endian_io_name(ENDIAN_ORDER);
|
||||
const bool need_endian_switch = stored_endian != current_endian;
|
||||
if (need_endian_switch) {
|
||||
switch (element_size) {
|
||||
case 1:
|
||||
break;
|
||||
case 2:
|
||||
BLI_endian_switch_uint16_array(static_cast<uint16_t *>(r_data), elements_num);
|
||||
break;
|
||||
case 4:
|
||||
BLI_endian_switch_uint32_array(static_cast<uint32_t *>(r_data), elements_num);
|
||||
break;
|
||||
case 8:
|
||||
BLI_endian_switch_uint64_array(static_cast<uint64_t *>(r_data), elements_num);
|
||||
break;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/** Write bytes ignoring endianness. */
|
||||
static std::shared_ptr<DictionaryValue> write_bdata_raw_bytes(BDataWriter &bdata_writer,
|
||||
const void *data,
|
||||
const int64_t size_in_bytes)
|
||||
{
|
||||
return bdata_writer.write(data, size_in_bytes).serialize();
|
||||
}
|
||||
|
||||
/** Read bytes ignoring endianness. */
|
||||
[[nodiscard]] static bool read_bdata_raw_bytes(const BDataReader &bdata_reader,
|
||||
const DictionaryValue &io_data,
|
||||
const int64_t bytes_num,
|
||||
void *r_data)
|
||||
{
|
||||
const std::optional<BDataSlice> slice = BDataSlice::deserialize(io_data);
|
||||
if (!slice) {
|
||||
return false;
|
||||
}
|
||||
if (slice->range.size() != bytes_num) {
|
||||
return false;
|
||||
}
|
||||
return bdata_reader.read(*slice, r_data);
|
||||
}
|
||||
|
||||
static std::shared_ptr<DictionaryValue> write_bdata_simple_gspan(BDataWriter &bdata_writer,
|
||||
const GSpan data)
|
||||
{
|
||||
const CPPType &type = data.type();
|
||||
BLI_assert(type.is_trivial());
|
||||
if (type.size() == 1 || type.is<ColorGeometry4b>()) {
|
||||
return write_bdata_raw_bytes(bdata_writer, data.data(), data.size_in_bytes());
|
||||
}
|
||||
return write_bdata_raw_data_with_endian(bdata_writer, data.data(), data.size_in_bytes());
|
||||
}
|
||||
|
||||
[[nodiscard]] static bool read_bdata_simple_gspan(const BDataReader &bdata_reader,
|
||||
const DictionaryValue &io_data,
|
||||
GMutableSpan r_data)
|
||||
{
|
||||
const CPPType &type = r_data.type();
|
||||
BLI_assert(type.is_trivial());
|
||||
if (type.size() == 1 || type.is<ColorGeometry4b>()) {
|
||||
return read_bdata_raw_bytes(bdata_reader, io_data, r_data.size_in_bytes(), r_data.data());
|
||||
}
|
||||
if (type.is_any<int16_t, uint16_t, int32_t, uint32_t, int64_t, uint64_t, float>()) {
|
||||
return read_bdata_raw_data_with_endian(
|
||||
bdata_reader, io_data, type.size(), r_data.size(), r_data.data());
|
||||
}
|
||||
if (type.is_any<float2, int2>()) {
|
||||
return read_bdata_raw_data_with_endian(
|
||||
bdata_reader, io_data, sizeof(int32_t), r_data.size() * 2, r_data.data());
|
||||
}
|
||||
if (type.is<float3>()) {
|
||||
return read_bdata_raw_data_with_endian(
|
||||
bdata_reader, io_data, sizeof(float), r_data.size() * 3, r_data.data());
|
||||
}
|
||||
if (type.is<float4x4>()) {
|
||||
return read_bdata_raw_data_with_endian(
|
||||
bdata_reader, io_data, sizeof(float), r_data.size() * 16, r_data.data());
|
||||
}
|
||||
if (type.is<ColorGeometry4f>()) {
|
||||
return read_bdata_raw_data_with_endian(
|
||||
bdata_reader, io_data, sizeof(float), r_data.size() * 4, r_data.data());
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
static std::shared_ptr<DictionaryValue> write_bdata_shared_simple_gspan(
|
||||
BDataWriter &bdata_writer,
|
||||
BDataSharing &bdata_sharing,
|
||||
const GSpan data,
|
||||
const ImplicitSharingInfo *sharing_info)
|
||||
{
|
||||
return bdata_sharing.write_shared(
|
||||
sharing_info, [&]() { return write_bdata_simple_gspan(bdata_writer, data); });
|
||||
}
|
||||
|
||||
[[nodiscard]] static const void *read_bdata_shared_simple_gspan(
|
||||
const DictionaryValue &io_data,
|
||||
const BDataReader &bdata_reader,
|
||||
const BDataSharing &bdata_sharing,
|
||||
const CPPType &cpp_type,
|
||||
const int size,
|
||||
const ImplicitSharingInfo **r_sharing_info)
|
||||
{
|
||||
const std::optional<ImplicitSharingInfoAndData> sharing_info_and_data =
|
||||
bdata_sharing.read_shared(io_data, [&]() -> std::optional<ImplicitSharingInfoAndData> {
|
||||
void *data_mem = MEM_mallocN_aligned(
|
||||
size * cpp_type.size(), cpp_type.alignment(), __func__);
|
||||
if (!read_bdata_simple_gspan(bdata_reader, io_data, {cpp_type, data_mem, size})) {
|
||||
MEM_freeN(data_mem);
|
||||
return std::nullopt;
|
||||
}
|
||||
return ImplicitSharingInfoAndData{implicit_sharing::info_for_mem_free(data_mem), data_mem};
|
||||
});
|
||||
if (!sharing_info_and_data) {
|
||||
*r_sharing_info = nullptr;
|
||||
return nullptr;
|
||||
}
|
||||
*r_sharing_info = sharing_info_and_data->sharing_info;
|
||||
return sharing_info_and_data->data;
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
[[nodiscard]] static bool read_bdata_shared_simple_span(const DictionaryValue &io_data,
|
||||
const BDataReader &bdata_reader,
|
||||
const BDataSharing &bdata_sharing,
|
||||
const int size,
|
||||
T **r_data,
|
||||
const ImplicitSharingInfo **r_sharing_info)
|
||||
{
|
||||
*r_data = const_cast<T *>(static_cast<const T *>(read_bdata_shared_simple_gspan(
|
||||
io_data, bdata_reader, bdata_sharing, CPPType::get<T>(), size, r_sharing_info)));
|
||||
return *r_data != nullptr;
|
||||
}
|
||||
|
||||
[[nodiscard]] static bool load_attributes(const io::serialize::ArrayValue &io_attributes,
|
||||
bke::MutableAttributeAccessor &attributes,
|
||||
const BDataReader &bdata_reader,
|
||||
const BDataSharing &bdata_sharing)
|
||||
{
|
||||
for (const auto &io_attribute_value : io_attributes.elements()) {
|
||||
const auto *io_attribute = io_attribute_value->as_dictionary_value();
|
||||
if (!io_attribute) {
|
||||
return false;
|
||||
}
|
||||
const std::optional<StringRefNull> name = io_attribute->lookup_str("name");
|
||||
const std::optional<StringRefNull> domain_str = io_attribute->lookup_str("domain");
|
||||
const std::optional<StringRefNull> type_str = io_attribute->lookup_str("type");
|
||||
auto io_data = io_attribute->lookup_dict("data");
|
||||
if (!name || !domain_str || !type_str || !io_data) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const std::optional<eAttrDomain> domain = get_domain_from_io_name(*domain_str);
|
||||
const std::optional<eCustomDataType> data_type = get_data_type_from_io_name(*type_str);
|
||||
if (!domain || !data_type) {
|
||||
return false;
|
||||
}
|
||||
const CPPType *cpp_type = custom_data_type_to_cpp_type(*data_type);
|
||||
if (!cpp_type) {
|
||||
return false;
|
||||
}
|
||||
const int domain_size = attributes.domain_size(*domain);
|
||||
const ImplicitSharingInfo *attribute_sharing_info;
|
||||
const void *attribute_data = read_bdata_shared_simple_gspan(
|
||||
*io_data, bdata_reader, bdata_sharing, *cpp_type, domain_size, &attribute_sharing_info);
|
||||
if (!attribute_data) {
|
||||
return false;
|
||||
}
|
||||
BLI_SCOPED_DEFER([&]() { attribute_sharing_info->remove_user_and_delete_if_last(); });
|
||||
|
||||
if (attributes.contains(*name)) {
|
||||
/* If the attribute exists already, copy the values over to the existing array. */
|
||||
bke::GSpanAttributeWriter attribute = attributes.lookup_or_add_for_write_only_span(
|
||||
*name, *domain, *data_type);
|
||||
if (!attribute) {
|
||||
return false;
|
||||
}
|
||||
cpp_type->copy_assign_n(attribute_data, attribute.span.data(), domain_size);
|
||||
attribute.finish();
|
||||
}
|
||||
else {
|
||||
/* Add a new attribute that shares the data. */
|
||||
if (!attributes.add(*name,
|
||||
*domain,
|
||||
*data_type,
|
||||
AttributeInitShared(attribute_data, *attribute_sharing_info))) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
static PointCloud *try_load_pointcloud(const DictionaryValue &io_geometry,
|
||||
const BDataReader &bdata_reader,
|
||||
const BDataSharing &bdata_sharing)
|
||||
{
|
||||
const DictionaryValue *io_pointcloud = io_geometry.lookup_dict("pointcloud");
|
||||
if (!io_pointcloud) {
|
||||
return nullptr;
|
||||
}
|
||||
const io::serialize::ArrayValue *io_attributes = io_pointcloud->lookup_array("attributes");
|
||||
if (!io_attributes) {
|
||||
return nullptr;
|
||||
}
|
||||
PointCloud *pointcloud = BKE_pointcloud_new_nomain(0);
|
||||
CustomData_free_layer_named(&pointcloud->pdata, "position", 0);
|
||||
pointcloud->totpoint = io_pointcloud->lookup_int("num_points").value_or(0);
|
||||
|
||||
auto cancel = [&]() {
|
||||
BKE_id_free(nullptr, pointcloud);
|
||||
return nullptr;
|
||||
};
|
||||
|
||||
bke::MutableAttributeAccessor attributes = pointcloud->attributes_for_write();
|
||||
if (!load_attributes(*io_attributes, attributes, bdata_reader, bdata_sharing)) {
|
||||
return cancel();
|
||||
}
|
||||
return pointcloud;
|
||||
}
|
||||
|
||||
static Curves *try_load_curves(const DictionaryValue &io_geometry,
|
||||
const BDataReader &bdata_reader,
|
||||
const BDataSharing &bdata_sharing)
|
||||
{
|
||||
const DictionaryValue *io_curves = io_geometry.lookup_dict("curves");
|
||||
if (!io_curves) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
const io::serialize::ArrayValue *io_attributes = io_curves->lookup_array("attributes");
|
||||
if (!io_attributes) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
Curves *curves_id = bke::curves_new_nomain(0, 0);
|
||||
bke::CurvesGeometry &curves = curves_id->geometry.wrap();
|
||||
CustomData_free_layer_named(&curves.point_data, "position", 0);
|
||||
curves.point_num = io_curves->lookup_int("num_points").value_or(0);
|
||||
curves.curve_num = io_curves->lookup_int("num_curves").value_or(0);
|
||||
|
||||
auto cancel = [&]() {
|
||||
BKE_id_free(nullptr, curves_id);
|
||||
return nullptr;
|
||||
};
|
||||
|
||||
if (curves.curves_num() > 0) {
|
||||
const auto io_curve_offsets = io_curves->lookup_dict("curve_offsets");
|
||||
if (!io_curve_offsets) {
|
||||
return cancel();
|
||||
}
|
||||
if (!read_bdata_shared_simple_span(*io_curve_offsets,
|
||||
bdata_reader,
|
||||
bdata_sharing,
|
||||
curves.curves_num() + 1,
|
||||
&curves.curve_offsets,
|
||||
&curves.runtime->curve_offsets_sharing_info)) {
|
||||
return cancel();
|
||||
}
|
||||
}
|
||||
|
||||
bke::MutableAttributeAccessor attributes = curves.attributes_for_write();
|
||||
if (!load_attributes(*io_attributes, attributes, bdata_reader, bdata_sharing)) {
|
||||
return cancel();
|
||||
}
|
||||
|
||||
return curves_id;
|
||||
}
|
||||
|
||||
static Mesh *try_load_mesh(const DictionaryValue &io_geometry,
|
||||
const BDataReader &bdata_reader,
|
||||
const BDataSharing &bdata_sharing)
|
||||
{
|
||||
const DictionaryValue *io_mesh = io_geometry.lookup_dict("mesh");
|
||||
if (!io_mesh) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
const io::serialize::ArrayValue *io_attributes = io_mesh->lookup_array("attributes");
|
||||
if (!io_attributes) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
Mesh *mesh = BKE_mesh_new_nomain(0, 0, 0, 0);
|
||||
CustomData_free_layer_named(&mesh->vdata, "position", 0);
|
||||
CustomData_free_layer_named(&mesh->edata, ".edge_verts", 0);
|
||||
CustomData_free_layer_named(&mesh->ldata, ".corner_vert", 0);
|
||||
CustomData_free_layer_named(&mesh->ldata, ".corner_edge", 0);
|
||||
mesh->totvert = io_mesh->lookup_int("num_vertices").value_or(0);
|
||||
mesh->totedge = io_mesh->lookup_int("num_edges").value_or(0);
|
||||
mesh->totpoly = io_mesh->lookup_int("num_polygons").value_or(0);
|
||||
mesh->totloop = io_mesh->lookup_int("num_corners").value_or(0);
|
||||
|
||||
auto cancel = [&]() {
|
||||
BKE_id_free(nullptr, mesh);
|
||||
return nullptr;
|
||||
};
|
||||
|
||||
if (mesh->totpoly > 0) {
|
||||
const auto io_poly_offsets = io_mesh->lookup_dict("poly_offsets");
|
||||
if (!io_poly_offsets) {
|
||||
return cancel();
|
||||
}
|
||||
if (!read_bdata_shared_simple_span(*io_poly_offsets,
|
||||
bdata_reader,
|
||||
bdata_sharing,
|
||||
mesh->totpoly + 1,
|
||||
&mesh->poly_offset_indices,
|
||||
&mesh->runtime->poly_offsets_sharing_info)) {
|
||||
return cancel();
|
||||
}
|
||||
}
|
||||
|
||||
bke::MutableAttributeAccessor attributes = mesh->attributes_for_write();
|
||||
if (!load_attributes(*io_attributes, attributes, bdata_reader, bdata_sharing)) {
|
||||
return cancel();
|
||||
}
|
||||
|
||||
return mesh;
|
||||
}
|
||||
|
||||
static GeometrySet load_geometry(const DictionaryValue &io_geometry,
|
||||
const BDataReader &bdata_reader,
|
||||
const BDataSharing &bdata_sharing);
|
||||
|
||||
static std::unique_ptr<bke::Instances> try_load_instances(const DictionaryValue &io_geometry,
|
||||
const BDataReader &bdata_reader,
|
||||
const BDataSharing &bdata_sharing)
|
||||
{
|
||||
const DictionaryValue *io_instances = io_geometry.lookup_dict("instances");
|
||||
if (!io_instances) {
|
||||
return nullptr;
|
||||
}
|
||||
const int num_instances = io_instances->lookup_int("num_instances").value_or(0);
|
||||
if (num_instances == 0) {
|
||||
return nullptr;
|
||||
}
|
||||
const io::serialize::ArrayValue *io_attributes = io_instances->lookup_array("attributes");
|
||||
if (!io_attributes) {
|
||||
return nullptr;
|
||||
}
|
||||
const io::serialize::ArrayValue *io_references = io_instances->lookup_array("references");
|
||||
if (!io_references) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
std::unique_ptr<bke::Instances> instances = std::make_unique<bke::Instances>();
|
||||
instances->resize(num_instances);
|
||||
|
||||
for (const auto &io_reference_value : io_references->elements()) {
|
||||
const DictionaryValue *io_reference = io_reference_value->as_dictionary_value();
|
||||
GeometrySet reference_geometry;
|
||||
if (io_reference) {
|
||||
reference_geometry = load_geometry(*io_reference, bdata_reader, bdata_sharing);
|
||||
}
|
||||
instances->add_reference(std::move(reference_geometry));
|
||||
}
|
||||
|
||||
const auto io_transforms = io_instances->lookup_dict("transforms");
|
||||
if (!io_transforms) {
|
||||
return {};
|
||||
}
|
||||
if (!read_bdata_simple_gspan(bdata_reader, *io_transforms, instances->transforms())) {
|
||||
return {};
|
||||
}
|
||||
|
||||
const auto io_handles = io_instances->lookup_dict("handles");
|
||||
if (!io_handles) {
|
||||
return {};
|
||||
}
|
||||
if (!read_bdata_simple_gspan(bdata_reader, *io_handles, instances->reference_handles())) {
|
||||
return {};
|
||||
}
|
||||
|
||||
bke::MutableAttributeAccessor attributes = instances->attributes_for_write();
|
||||
if (!load_attributes(*io_attributes, attributes, bdata_reader, bdata_sharing)) {
|
||||
return {};
|
||||
}
|
||||
|
||||
return instances;
|
||||
}
|
||||
|
||||
static GeometrySet load_geometry(const DictionaryValue &io_geometry,
|
||||
const BDataReader &bdata_reader,
|
||||
const BDataSharing &bdata_sharing)
|
||||
{
|
||||
GeometrySet geometry;
|
||||
geometry.replace_mesh(try_load_mesh(io_geometry, bdata_reader, bdata_sharing));
|
||||
geometry.replace_pointcloud(try_load_pointcloud(io_geometry, bdata_reader, bdata_sharing));
|
||||
geometry.replace_curves(try_load_curves(io_geometry, bdata_reader, bdata_sharing));
|
||||
geometry.replace_instances(
|
||||
try_load_instances(io_geometry, bdata_reader, bdata_sharing).release());
|
||||
return geometry;
|
||||
}
|
||||
|
||||
static std::shared_ptr<io::serialize::ArrayValue> serialize_material_slots(
|
||||
const Span<const Material *> material_slots)
|
||||
{
|
||||
auto io_materials = std::make_shared<io::serialize::ArrayValue>();
|
||||
for (const Material *material : material_slots) {
|
||||
if (material == nullptr) {
|
||||
io_materials->append_null();
|
||||
}
|
||||
else {
|
||||
auto io_material = io_materials->append_dict();
|
||||
io_material->append_str("name", material->id.name + 2);
|
||||
if (material->id.lib != nullptr) {
|
||||
io_material->append_str("lib_name", material->id.lib->id.name + 2);
|
||||
}
|
||||
}
|
||||
}
|
||||
return io_materials;
|
||||
}
|
||||
|
||||
static std::shared_ptr<io::serialize::ArrayValue> serialize_attributes(
|
||||
const bke::AttributeAccessor &attributes,
|
||||
BDataWriter &bdata_writer,
|
||||
BDataSharing &bdata_sharing,
|
||||
const Set<std::string> &attributes_to_ignore)
|
||||
{
|
||||
auto io_attributes = std::make_shared<io::serialize::ArrayValue>();
|
||||
attributes.for_all(
|
||||
[&](const bke::AttributeIDRef &attribute_id, const bke::AttributeMetaData &meta_data) {
|
||||
if (attributes_to_ignore.contains_as(attribute_id.name())) {
|
||||
return true;
|
||||
}
|
||||
|
||||
auto io_attribute = io_attributes->append_dict();
|
||||
|
||||
io_attribute->append_str("name", attribute_id.name());
|
||||
|
||||
const StringRefNull domain_name = get_domain_io_name(meta_data.domain);
|
||||
io_attribute->append_str("domain", domain_name);
|
||||
|
||||
const StringRefNull type_name = get_data_type_io_name(meta_data.data_type);
|
||||
io_attribute->append_str("type", type_name);
|
||||
|
||||
const bke::GAttributeReader attribute = attributes.lookup(attribute_id);
|
||||
const GVArraySpan attribute_span(attribute.varray);
|
||||
io_attribute->append("data",
|
||||
write_bdata_shared_simple_gspan(
|
||||
bdata_writer,
|
||||
bdata_sharing,
|
||||
attribute_span,
|
||||
attribute.varray.is_span() ? attribute.sharing_info : nullptr));
|
||||
return true;
|
||||
});
|
||||
return io_attributes;
|
||||
}
|
||||
|
||||
static std::shared_ptr<DictionaryValue> serialize_geometry_set(const GeometrySet &geometry,
|
||||
BDataWriter &bdata_writer,
|
||||
BDataSharing &bdata_sharing)
|
||||
{
|
||||
auto io_geometry = std::make_shared<DictionaryValue>();
|
||||
if (geometry.has_mesh()) {
|
||||
const Mesh &mesh = *geometry.get_mesh_for_read();
|
||||
auto io_mesh = io_geometry->append_dict("mesh");
|
||||
|
||||
io_mesh->append_int("num_vertices", mesh.totvert);
|
||||
io_mesh->append_int("num_edges", mesh.totedge);
|
||||
io_mesh->append_int("num_polygons", mesh.totpoly);
|
||||
io_mesh->append_int("num_corners", mesh.totloop);
|
||||
|
||||
if (mesh.totpoly > 0) {
|
||||
io_mesh->append("poly_offsets",
|
||||
write_bdata_shared_simple_gspan(bdata_writer,
|
||||
bdata_sharing,
|
||||
mesh.poly_offsets(),
|
||||
mesh.runtime->poly_offsets_sharing_info));
|
||||
}
|
||||
|
||||
auto io_materials = serialize_material_slots({mesh.mat, mesh.totcol});
|
||||
io_mesh->append("materials", io_materials);
|
||||
|
||||
auto io_attributes = serialize_attributes(mesh.attributes(), bdata_writer, bdata_sharing, {});
|
||||
io_mesh->append("attributes", io_attributes);
|
||||
}
|
||||
if (geometry.has_pointcloud()) {
|
||||
const PointCloud &pointcloud = *geometry.get_pointcloud_for_read();
|
||||
auto io_pointcloud = io_geometry->append_dict("pointcloud");
|
||||
|
||||
io_pointcloud->append_int("num_points", pointcloud.totpoint);
|
||||
|
||||
auto io_materials = serialize_material_slots({pointcloud.mat, pointcloud.totcol});
|
||||
io_pointcloud->append("materials", io_materials);
|
||||
|
||||
auto io_attributes = serialize_attributes(
|
||||
pointcloud.attributes(), bdata_writer, bdata_sharing, {});
|
||||
io_pointcloud->append("attributes", io_attributes);
|
||||
}
|
||||
if (geometry.has_curves()) {
|
||||
const Curves &curves_id = *geometry.get_curves_for_read();
|
||||
const bke::CurvesGeometry &curves = curves_id.geometry.wrap();
|
||||
|
||||
auto io_curves = io_geometry->append_dict("curves");
|
||||
|
||||
io_curves->append_int("num_points", curves.point_num);
|
||||
io_curves->append_int("num_curves", curves.curve_num);
|
||||
|
||||
if (curves.curve_num > 0) {
|
||||
io_curves->append(
|
||||
"curve_offsets",
|
||||
write_bdata_shared_simple_gspan(bdata_writer,
|
||||
bdata_sharing,
|
||||
curves.offsets(),
|
||||
curves.runtime->curve_offsets_sharing_info));
|
||||
}
|
||||
|
||||
auto io_materials = serialize_material_slots({curves_id.mat, curves_id.totcol});
|
||||
io_curves->append("materials", io_materials);
|
||||
|
||||
auto io_attributes = serialize_attributes(
|
||||
curves.attributes(), bdata_writer, bdata_sharing, {});
|
||||
io_curves->append("attributes", io_attributes);
|
||||
}
|
||||
if (geometry.has_instances()) {
|
||||
const bke::Instances &instances = *geometry.get_instances_for_read();
|
||||
auto io_instances = io_geometry->append_dict("instances");
|
||||
|
||||
io_instances->append_int("num_instances", instances.instances_num());
|
||||
|
||||
auto io_references = io_instances->append_array("references");
|
||||
for (const bke::InstanceReference &reference : instances.references()) {
|
||||
BLI_assert(reference.type() == bke::InstanceReference::Type::GeometrySet);
|
||||
io_references->append(
|
||||
serialize_geometry_set(reference.geometry_set(), bdata_writer, bdata_sharing));
|
||||
}
|
||||
|
||||
io_instances->append("transforms",
|
||||
write_bdata_simple_gspan(bdata_writer, instances.transforms()));
|
||||
io_instances->append("handles",
|
||||
write_bdata_simple_gspan(bdata_writer, instances.reference_handles()));
|
||||
|
||||
auto io_attributes = serialize_attributes(
|
||||
instances.attributes(), bdata_writer, bdata_sharing, {"position"});
|
||||
io_instances->append("attributes", io_attributes);
|
||||
}
|
||||
return io_geometry;
|
||||
}
|
||||
|
||||
void serialize_modifier_simulation_state(const ModifierSimulationState &state,
|
||||
BDataWriter &bdata_writer,
|
||||
BDataSharing &bdata_sharing,
|
||||
DictionaryValue &r_io_root)
|
||||
{
|
||||
r_io_root.append_int("version", 1);
|
||||
auto io_zones = r_io_root.append_array("zones");
|
||||
|
||||
for (const auto item : state.zone_states_.items()) {
|
||||
const SimulationZoneID &zone_id = item.key;
|
||||
const SimulationZoneState &zone_state = *item.value;
|
||||
|
||||
auto io_zone = io_zones->append_dict();
|
||||
|
||||
auto io_zone_id = io_zone->append_array("zone_id");
|
||||
|
||||
for (const int node_id : zone_id.node_ids) {
|
||||
io_zone_id->append_int(node_id);
|
||||
}
|
||||
|
||||
auto io_state_items = io_zone->append_array("state_items");
|
||||
for (const std::unique_ptr<SimulationStateItem> &state_item : zone_state.items) {
|
||||
/* TODO: Use better id. */
|
||||
const std::string state_item_id = std::to_string(&state_item - zone_state.items.begin());
|
||||
|
||||
auto io_state_item = io_state_items->append_dict();
|
||||
|
||||
io_state_item->append_str("id", state_item_id);
|
||||
|
||||
if (const GeometrySimulationStateItem *geometry_state_item =
|
||||
dynamic_cast<const GeometrySimulationStateItem *>(state_item.get())) {
|
||||
io_state_item->append_str("type", "geometry");
|
||||
|
||||
const GeometrySet &geometry = geometry_state_item->geometry();
|
||||
|
||||
auto io_geometry = serialize_geometry_set(geometry, bdata_writer, bdata_sharing);
|
||||
io_state_item->append("data", io_geometry);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void deserialize_modifier_simulation_state(const DictionaryValue &io_root,
|
||||
const BDataReader &bdata_reader,
|
||||
const BDataSharing &bdata_sharing,
|
||||
ModifierSimulationState &r_state)
|
||||
{
|
||||
io::serialize::JsonFormatter formatter;
|
||||
const std::optional<int> version = io_root.lookup_int("version");
|
||||
if (!version) {
|
||||
return;
|
||||
}
|
||||
if (*version != 1) {
|
||||
return;
|
||||
}
|
||||
const io::serialize::ArrayValue *io_zones = io_root.lookup_array("zones");
|
||||
if (!io_zones) {
|
||||
return;
|
||||
}
|
||||
for (const auto &io_zone_value : io_zones->elements()) {
|
||||
const DictionaryValue *io_zone = io_zone_value->as_dictionary_value();
|
||||
if (!io_zone) {
|
||||
continue;
|
||||
}
|
||||
const io::serialize::ArrayValue *io_zone_id = io_zone->lookup_array("zone_id");
|
||||
bke::sim::SimulationZoneID zone_id;
|
||||
for (const auto &io_zone_id_element : io_zone_id->elements()) {
|
||||
const io::serialize::IntValue *io_node_id = io_zone_id_element->as_int_value();
|
||||
if (!io_node_id) {
|
||||
continue;
|
||||
}
|
||||
zone_id.node_ids.append(io_node_id->value());
|
||||
}
|
||||
|
||||
const io::serialize::ArrayValue *io_state_items = io_zone->lookup_array("state_items");
|
||||
if (!io_state_items) {
|
||||
continue;
|
||||
}
|
||||
|
||||
auto zone_state = std::make_unique<bke::sim::SimulationZoneState>();
|
||||
|
||||
for (const auto &io_state_item_value : io_state_items->elements()) {
|
||||
const DictionaryValue *io_state_item = io_state_item_value->as_dictionary_value();
|
||||
if (!io_state_item) {
|
||||
continue;
|
||||
}
|
||||
const std::optional<StringRefNull> state_item_type = io_state_item->lookup_str("type");
|
||||
if (!state_item_type) {
|
||||
continue;
|
||||
}
|
||||
if (*state_item_type == StringRef("geometry")) {
|
||||
const DictionaryValue *io_geometry = io_state_item->lookup_dict("data");
|
||||
if (!io_geometry) {
|
||||
continue;
|
||||
}
|
||||
GeometrySet geometry = load_geometry(*io_geometry, bdata_reader, bdata_sharing);
|
||||
auto state_item = std::make_unique<bke::sim::GeometrySimulationStateItem>(
|
||||
std::move(geometry));
|
||||
zone_state->items.append(std::move(state_item));
|
||||
}
|
||||
}
|
||||
|
||||
r_state.zone_states_.add_overwrite(zone_id, std::move(zone_state));
|
||||
}
|
||||
}
|
||||
|
||||
DiskBDataReader::DiskBDataReader(std::string bdata_dir) : bdata_dir_(std::move(bdata_dir)) {}
|
||||
|
||||
[[nodiscard]] bool DiskBDataReader::read(const BDataSlice &slice, void *r_data) const
|
||||
{
|
||||
if (slice.range.is_empty()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
char bdata_path[FILE_MAX];
|
||||
BLI_path_join(bdata_path, sizeof(bdata_path), bdata_dir_.c_str(), slice.name.c_str());
|
||||
|
||||
std::lock_guard lock{mutex_};
|
||||
std::unique_ptr<fstream> &bdata_file = open_input_streams_.lookup_or_add_cb_as(
|
||||
bdata_path,
|
||||
[&]() { return std::make_unique<fstream>(bdata_path, std::ios::in | std::ios::binary); });
|
||||
bdata_file->seekg(slice.range.start());
|
||||
bdata_file->read(static_cast<char *>(r_data), slice.range.size());
|
||||
if (bdata_file->gcount() != slice.range.size()) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
DiskBDataWriter::DiskBDataWriter(std::string bdata_name,
|
||||
std::ostream &bdata_file,
|
||||
const int64_t current_offset)
|
||||
: bdata_name_(std::move(bdata_name)), bdata_file_(bdata_file), current_offset_(current_offset)
|
||||
{
|
||||
}
|
||||
|
||||
BDataSlice DiskBDataWriter::write(const void *data, const int64_t size)
|
||||
{
|
||||
const int64_t old_offset = current_offset_;
|
||||
bdata_file_.write(static_cast<const char *>(data), size);
|
||||
current_offset_ += size;
|
||||
return {bdata_name_, {old_offset, size}};
|
||||
}
|
||||
|
||||
BDataSharing::~BDataSharing()
|
||||
{
|
||||
for (const ImplicitSharingInfo *sharing_info : stored_by_runtime_.keys()) {
|
||||
sharing_info->remove_weak_user_and_delete_if_last();
|
||||
}
|
||||
for (const ImplicitSharingInfoAndData &value : runtime_by_stored_.values()) {
|
||||
if (value.sharing_info) {
|
||||
value.sharing_info->remove_user_and_delete_if_last();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
DictionaryValuePtr BDataSharing::write_shared(const ImplicitSharingInfo *sharing_info,
|
||||
FunctionRef<DictionaryValuePtr()> write_fn)
|
||||
{
|
||||
if (sharing_info == nullptr) {
|
||||
return write_fn();
|
||||
}
|
||||
return stored_by_runtime_.add_or_modify(
|
||||
sharing_info,
|
||||
/* Create new value. */
|
||||
[&](StoredByRuntimeValue *value) {
|
||||
new (value) StoredByRuntimeValue();
|
||||
value->io_data = write_fn();
|
||||
value->sharing_info_version = sharing_info->version();
|
||||
sharing_info->add_weak_user();
|
||||
return value->io_data;
|
||||
},
|
||||
/* Potentially modify existing value. */
|
||||
[&](StoredByRuntimeValue *value) {
|
||||
const int64_t new_version = sharing_info->version();
|
||||
BLI_assert(value->sharing_info_version <= new_version);
|
||||
if (value->sharing_info_version < new_version) {
|
||||
value->io_data = write_fn();
|
||||
value->sharing_info_version = new_version;
|
||||
}
|
||||
return value->io_data;
|
||||
});
|
||||
}
|
||||
|
||||
std::optional<ImplicitSharingInfoAndData> BDataSharing::read_shared(
|
||||
const DictionaryValue &io_data,
|
||||
FunctionRef<std::optional<ImplicitSharingInfoAndData>()> read_fn) const
|
||||
{
|
||||
std::lock_guard lock{mutex_};
|
||||
|
||||
io::serialize::JsonFormatter formatter;
|
||||
std::stringstream ss;
|
||||
formatter.serialize(ss, io_data);
|
||||
const std::string key = ss.str();
|
||||
|
||||
if (const ImplicitSharingInfoAndData *shared_data = runtime_by_stored_.lookup_ptr(key)) {
|
||||
shared_data->sharing_info->add_user();
|
||||
return *shared_data;
|
||||
}
|
||||
std::optional<ImplicitSharingInfoAndData> data = read_fn();
|
||||
if (!data) {
|
||||
return std::nullopt;
|
||||
}
|
||||
if (data->sharing_info != nullptr) {
|
||||
data->sharing_info->add_user();
|
||||
runtime_by_stored_.add_new(key, *data);
|
||||
}
|
||||
return data;
|
||||
}
|
||||
|
||||
} // namespace blender::bke::sim
|
|
@ -819,6 +819,15 @@ void BKE_sound_set_scene_sound_pitch(void *handle, float pitch, char animated)
|
|||
AUD_SequenceEntry_setAnimationData(handle, AUD_AP_PITCH, sound_cfra, &pitch, animated);
|
||||
}
|
||||
|
||||
void BKE_sound_set_scene_sound_pitch_constant_range(void *handle,
|
||||
int frame_start,
|
||||
int frame_end,
|
||||
float pitch)
|
||||
{
|
||||
AUD_SequenceEntry_setConstantRangeAnimationData(
|
||||
handle, AUD_AP_PITCH, frame_start, frame_end, &pitch);
|
||||
}
|
||||
|
||||
void BKE_sound_set_scene_sound_pan(void *handle, float pan, char animated)
|
||||
{
|
||||
AUD_SequenceEntry_setAnimationData(handle, AUD_AP_PANNING, sound_cfra, &pan, animated);
|
||||
|
@ -1369,6 +1378,12 @@ void BKE_sound_set_scene_sound_pitch(void *UNUSED(handle),
|
|||
char UNUSED(animated))
|
||||
{
|
||||
}
|
||||
void BKE_sound_set_scene_sound_pitch_constant_range(void *UNUSED(handle),
|
||||
int UNUSED(frame_start),
|
||||
int UNUSED(frame_end),
|
||||
float UNUSED(pitch))
|
||||
{
|
||||
}
|
||||
float BKE_sound_get_length(struct Main *UNUSED(bmain), bSound *UNUSED(sound))
|
||||
{
|
||||
return 0;
|
||||
|
|
|
@ -177,6 +177,9 @@ static bool is_infinite_sharp_vertex(const OpenSubdiv_Converter *converter,
|
|||
return true;
|
||||
}
|
||||
#endif
|
||||
if (storage->infinite_sharp_vertices_map == nullptr) {
|
||||
return false;
|
||||
}
|
||||
const int vertex_index = storage->manifold_vertex_index_reverse[manifold_vertex_index];
|
||||
return BLI_BITMAP_TEST_BOOL(storage->infinite_sharp_vertices_map, vertex_index);
|
||||
}
|
||||
|
@ -264,7 +267,7 @@ static void free_user_data(const OpenSubdiv_Converter *converter)
|
|||
ConverterStorage *user_data = static_cast<ConverterStorage *>(converter->user_data);
|
||||
MEM_SAFE_FREE(user_data->loop_uv_indices);
|
||||
MEM_freeN(user_data->manifold_vertex_index);
|
||||
MEM_freeN(user_data->infinite_sharp_vertices_map);
|
||||
MEM_SAFE_FREE(user_data->infinite_sharp_vertices_map);
|
||||
MEM_freeN(user_data->manifold_vertex_index_reverse);
|
||||
MEM_freeN(user_data->manifold_edge_index_reverse);
|
||||
MEM_freeN(user_data);
|
||||
|
@ -306,7 +309,7 @@ static void init_functions(OpenSubdiv_Converter *converter)
|
|||
converter->freeUserData = free_user_data;
|
||||
}
|
||||
|
||||
static void initialize_manifold_index_array(const BLI_bitmap *used_map,
|
||||
static void initialize_manifold_index_array(const blender::BitSpan not_used_map,
|
||||
const int num_elements,
|
||||
int **r_indices,
|
||||
int **r_indices_reverse,
|
||||
|
@ -323,7 +326,7 @@ static void initialize_manifold_index_array(const BLI_bitmap *used_map,
|
|||
}
|
||||
int offset = 0;
|
||||
for (int i = 0; i < num_elements; i++) {
|
||||
if (BLI_BITMAP_TEST_BOOL(used_map, i)) {
|
||||
if (not_used_map.is_empty() || !not_used_map[i]) {
|
||||
if (indices != nullptr) {
|
||||
indices[i] = i - offset;
|
||||
}
|
||||
|
@ -349,42 +352,35 @@ static void initialize_manifold_index_array(const BLI_bitmap *used_map,
|
|||
|
||||
static void initialize_manifold_indices(ConverterStorage *storage)
|
||||
{
|
||||
using namespace blender;
|
||||
const Mesh *mesh = storage->mesh;
|
||||
const blender::Span<blender::int2> edges = storage->edges;
|
||||
const blender::OffsetIndices<int> polys = storage->polys;
|
||||
const blender::Span<int> corner_verts = storage->corner_verts;
|
||||
const blender::Span<int> corner_edges = storage->corner_edges;
|
||||
/* Set bits of elements which are not loose. */
|
||||
BLI_bitmap *vert_used_map = BLI_BITMAP_NEW(mesh->totvert, "vert used map");
|
||||
BLI_bitmap *edge_used_map = BLI_BITMAP_NEW(mesh->totedge, "edge used map");
|
||||
for (int poly_index = 0; poly_index < mesh->totpoly; poly_index++) {
|
||||
for (const int corner : polys[poly_index]) {
|
||||
BLI_BITMAP_ENABLE(vert_used_map, corner_verts[corner]);
|
||||
BLI_BITMAP_ENABLE(edge_used_map, corner_edges[corner]);
|
||||
}
|
||||
}
|
||||
initialize_manifold_index_array(vert_used_map,
|
||||
const bke::LooseVertCache &loose_verts = mesh->verts_no_face();
|
||||
const bke::LooseEdgeCache &loose_edges = mesh->loose_edges();
|
||||
initialize_manifold_index_array(loose_verts.is_loose_bits,
|
||||
mesh->totvert,
|
||||
&storage->manifold_vertex_index,
|
||||
&storage->manifold_vertex_index_reverse,
|
||||
&storage->num_manifold_vertices);
|
||||
initialize_manifold_index_array(edge_used_map,
|
||||
initialize_manifold_index_array(loose_edges.is_loose_bits,
|
||||
mesh->totedge,
|
||||
nullptr,
|
||||
&storage->manifold_edge_index_reverse,
|
||||
&storage->num_manifold_edges);
|
||||
/* Initialize infinite sharp mapping. */
|
||||
storage->infinite_sharp_vertices_map = BLI_BITMAP_NEW(mesh->totvert, "vert used map");
|
||||
for (int edge_index = 0; edge_index < mesh->totedge; edge_index++) {
|
||||
if (!BLI_BITMAP_TEST_BOOL(edge_used_map, edge_index)) {
|
||||
const blender::int2 &edge = edges[edge_index];
|
||||
BLI_BITMAP_ENABLE(storage->infinite_sharp_vertices_map, edge[0]);
|
||||
BLI_BITMAP_ENABLE(storage->infinite_sharp_vertices_map, edge[1]);
|
||||
if (loose_edges.count > 0) {
|
||||
const Span<int2> edges = storage->edges;
|
||||
storage->infinite_sharp_vertices_map = BLI_BITMAP_NEW(mesh->totvert, "vert used map");
|
||||
for (int edge_index = 0; edge_index < mesh->totedge; edge_index++) {
|
||||
if (loose_edges.is_loose_bits[edge_index]) {
|
||||
const int2 edge = edges[edge_index];
|
||||
BLI_BITMAP_ENABLE(storage->infinite_sharp_vertices_map, edge[0]);
|
||||
BLI_BITMAP_ENABLE(storage->infinite_sharp_vertices_map, edge[1]);
|
||||
}
|
||||
}
|
||||
}
|
||||
/* Free working variables. */
|
||||
MEM_freeN(vert_used_map);
|
||||
MEM_freeN(edge_used_map);
|
||||
else {
|
||||
storage->infinite_sharp_vertices_map = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
static void init_user_data(OpenSubdiv_Converter *converter,
|
||||
|
|
|
@ -734,6 +734,25 @@ typedef struct VFontToCurveIter {
|
|||
int status;
|
||||
} VFontToCurveIter;
|
||||
|
||||
/** \} */
|
||||
|
||||
/* -------------------------------------------------------------------- */
|
||||
/** \name VFont Mouse Cursor to Text Offset
|
||||
*
|
||||
* This is an optional argument to `vfont_to_curve` for getting the text
|
||||
* offset into the string at a mouse cursor location. Used for getting
|
||||
* text cursor (caret) position or selection range.
|
||||
* \{ */
|
||||
/* Used when translating a mouse cursor location to a position within the string. */
|
||||
typedef struct VFontCursor_Params {
|
||||
/* Mouse cursor location in Object coordinate space as input. */
|
||||
float cursor_location[2];
|
||||
/* Character position within EditFont::textbuf as output. */
|
||||
int r_string_offset;
|
||||
} VFontCursor_Params;
|
||||
|
||||
/** \} */
|
||||
|
||||
enum {
|
||||
VFONT_TO_CURVE_INIT = 0,
|
||||
VFONT_TO_CURVE_BISECT,
|
||||
|
@ -774,6 +793,7 @@ static bool vfont_to_curve(Object *ob,
|
|||
Curve *cu,
|
||||
int mode,
|
||||
VFontToCurveIter *iter_data,
|
||||
struct VFontCursor_Params *cursor_params,
|
||||
ListBase *r_nubase,
|
||||
const char32_t **r_text,
|
||||
int *r_text_len,
|
||||
|
@ -1441,6 +1461,35 @@ static bool vfont_to_curve(Object *ob,
|
|||
}
|
||||
}
|
||||
|
||||
if (cursor_params) {
|
||||
cursor_params->r_string_offset = -1;
|
||||
for (i = 0; i <= slen; i++, ct++) {
|
||||
info = &custrinfo[i];
|
||||
ascii = mem[i];
|
||||
if (info->flag & CU_CHINFO_SMALLCAPS_CHECK) {
|
||||
ascii = towupper(ascii);
|
||||
}
|
||||
ct = &chartransdata[i];
|
||||
che = find_vfont_char(vfd, ascii);
|
||||
float charwidth = char_width(cu, che, info);
|
||||
float charhalf = (charwidth / 2.0f);
|
||||
if (cursor_params->cursor_location[1] >= ct->yof - (0.25f * linedist) &&
|
||||
cursor_params->cursor_location[1] <= (ct->yof + (0.75f * linedist))) {
|
||||
/* On this row. */
|
||||
if (cursor_params->cursor_location[0] >= (ct->xof) &&
|
||||
cursor_params->cursor_location[0] <= (ct->xof + charhalf)) {
|
||||
/* Left half of character. */
|
||||
cursor_params->r_string_offset = i;
|
||||
}
|
||||
else if (cursor_params->cursor_location[0] >= (ct->xof + charhalf) &&
|
||||
cursor_params->cursor_location[0] <= (ct->xof + charwidth)) {
|
||||
/* Right half of character. */
|
||||
cursor_params->r_string_offset = i + 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (ELEM(mode, FO_CURSUP, FO_CURSDOWN, FO_PAGEUP, FO_PAGEDOWN) &&
|
||||
iter_data->status == VFONT_TO_CURVE_INIT) {
|
||||
ct = &chartransdata[ef->pos];
|
||||
|
@ -1738,13 +1787,49 @@ bool BKE_vfont_to_curve_ex(Object *ob,
|
|||
};
|
||||
|
||||
do {
|
||||
data.ok &= vfont_to_curve(
|
||||
ob, cu, mode, &data, r_nubase, r_text, r_text_len, r_text_free, r_chartransdata);
|
||||
data.ok &= vfont_to_curve(ob,
|
||||
cu,
|
||||
mode,
|
||||
&data,
|
||||
NULL,
|
||||
r_nubase,
|
||||
r_text,
|
||||
r_text_len,
|
||||
r_text_free,
|
||||
r_chartransdata);
|
||||
} while (data.ok && ELEM(data.status, VFONT_TO_CURVE_SCALE_ONCE, VFONT_TO_CURVE_BISECT));
|
||||
|
||||
return data.ok;
|
||||
}
|
||||
|
||||
int BKE_vfont_cursor_to_text_index(Object *ob, float cursor_location[2])
|
||||
{
|
||||
Curve *cu = (Curve *)ob->data;
|
||||
ListBase *r_nubase = &cu->nurb;
|
||||
|
||||
/* TODO: iterating to calculate the scale can be avoided. */
|
||||
|
||||
VFontToCurveIter data = {
|
||||
.iteraction = cu->totbox * FONT_TO_CURVE_SCALE_ITERATIONS,
|
||||
.scale_to_fit = 1.0f,
|
||||
.word_wrap = true,
|
||||
.ok = true,
|
||||
.status = VFONT_TO_CURVE_INIT,
|
||||
};
|
||||
|
||||
VFontCursor_Params cursor_params = {
|
||||
.cursor_location = {cursor_location[0], cursor_location[1]},
|
||||
.r_string_offset = -1,
|
||||
};
|
||||
|
||||
do {
|
||||
data.ok &= vfont_to_curve(
|
||||
ob, cu, FO_CURS, &data, &cursor_params, r_nubase, NULL, NULL, NULL, NULL);
|
||||
} while (data.ok && ELEM(data.status, VFONT_TO_CURVE_SCALE_ONCE, VFONT_TO_CURVE_BISECT));
|
||||
|
||||
return cursor_params.r_string_offset;
|
||||
}
|
||||
|
||||
#undef FONT_TO_CURVE_SCALE_ITERATIONS
|
||||
#undef FONT_TO_CURVE_SCALE_THRESHOLD
|
||||
|
||||
|
|
|
@ -705,6 +705,11 @@ class CPPType : NonCopyable, NonMovable {
|
|||
return this == &CPPType::get<std::decay_t<T>>();
|
||||
}
|
||||
|
||||
template<typename... T> bool is_any() const
|
||||
{
|
||||
return (this->is<T>() || ...);
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert a #CPPType that is only known at run-time, to a static type that is known at
|
||||
* compile-time. This allows the compiler to optimize a function for specific types, while all
|
||||
|
|
|
@ -65,6 +65,11 @@ class GSpan {
|
|||
return size_;
|
||||
}
|
||||
|
||||
int64_t size_in_bytes() const
|
||||
{
|
||||
return type_->size() * size_;
|
||||
}
|
||||
|
||||
const void *data() const
|
||||
{
|
||||
return data_;
|
||||
|
@ -186,6 +191,11 @@ class GMutableSpan {
|
|||
return size_;
|
||||
}
|
||||
|
||||
int64_t size_in_bytes() const
|
||||
{
|
||||
return type_->size() * size_;
|
||||
}
|
||||
|
||||
void *data() const
|
||||
{
|
||||
return data_;
|
||||
|
|
|
@ -217,7 +217,7 @@ template<typename T> struct DefaultHash<T *> {
|
|||
|
||||
template<typename T> uint64_t get_default_hash(const T &v)
|
||||
{
|
||||
return DefaultHash<T>{}(v);
|
||||
return DefaultHash<std::decay_t<T>>{}(v);
|
||||
}
|
||||
|
||||
template<typename T1, typename T2> uint64_t get_default_hash_2(const T1 &v1, const T2 &v2)
|
||||
|
|
|
@ -37,11 +37,25 @@ namespace blender {
|
|||
*/
|
||||
class ImplicitSharingInfo : NonCopyable, NonMovable {
|
||||
private:
|
||||
mutable std::atomic<int> users_;
|
||||
/**
|
||||
* Number of users that want to own the shared data.
|
||||
*/
|
||||
mutable std::atomic<int> strong_users_ = 1;
|
||||
/**
|
||||
* Number of users that only keep a reference to the `ImplicitSharingInfo` but don't need to own
|
||||
* the shared data. One additional weak user is added as long as there is at least one strong
|
||||
* user. Together with the version below this adds an efficient way to detect if data has been
|
||||
* changed.
|
||||
*/
|
||||
mutable std::atomic<int> weak_users_ = 1;
|
||||
/**
|
||||
* The data referenced by an #ImplicitSharingInfo can change over time. This version is
|
||||
* incremented whenever the referenced data is about to be changed. This allows weak users to
|
||||
* detect if the data has changed since the weak user was created.
|
||||
*/
|
||||
mutable std::atomic<int64_t> version_ = 0;
|
||||
|
||||
public:
|
||||
ImplicitSharingInfo(const int initial_users) : users_(initial_users) {}
|
||||
|
||||
virtual ~ImplicitSharingInfo()
|
||||
{
|
||||
BLI_assert(this->is_mutable());
|
||||
|
@ -50,7 +64,7 @@ class ImplicitSharingInfo : NonCopyable, NonMovable {
|
|||
/** True if there are other const references to the resource, meaning it cannot be modified. */
|
||||
bool is_shared() const
|
||||
{
|
||||
return users_.load(std::memory_order_relaxed) >= 2;
|
||||
return strong_users_.load(std::memory_order_relaxed) >= 2;
|
||||
}
|
||||
|
||||
/** Whether the resource can be modified without a copy because there is only one owner. */
|
||||
|
@ -59,10 +73,56 @@ class ImplicitSharingInfo : NonCopyable, NonMovable {
|
|||
return !this->is_shared();
|
||||
}
|
||||
|
||||
/**
|
||||
* Weak users don't protect the referenced data from being freed. If the data is freed while
|
||||
* there is still a weak referenced, this returns true.
|
||||
*/
|
||||
bool expired() const
|
||||
{
|
||||
return strong_users_.load(std::memory_order_acquire) == 0;
|
||||
}
|
||||
|
||||
/** Call when a the data has a new additional owner. */
|
||||
void add_user() const
|
||||
{
|
||||
users_.fetch_add(1, std::memory_order_relaxed);
|
||||
strong_users_.fetch_add(1, std::memory_order_relaxed);
|
||||
}
|
||||
|
||||
/**
|
||||
* Adding a weak owner prevents the #ImplicitSharingInfo from being freed but not the referenced
|
||||
* data.
|
||||
*
|
||||
* \note Unlike std::shared_ptr a weak user cannot be turned into a strong user. This is
|
||||
* because some code might change the referenced data assuming that there is only one strong user
|
||||
* while a new strong user is added by another thread.
|
||||
*/
|
||||
void add_weak_user() const
|
||||
{
|
||||
weak_users_.fetch_add(1, std::memory_order_relaxed);
|
||||
}
|
||||
|
||||
/**
|
||||
* Call this when making sure that the referenced data is mutable, which also implies that it is
|
||||
* about to be modified. This allows other code to detect whether data has not been changed very
|
||||
* efficiently.
|
||||
*/
|
||||
void tag_ensured_mutable() const
|
||||
{
|
||||
BLI_assert(this->is_mutable());
|
||||
/* This might not need an atomic increment when the #version method below is only called when
|
||||
* the code calling it is a strong user of this sharing info. Better be safe and use an atomic
|
||||
* for now. */
|
||||
version_.fetch_add(1, std::memory_order_acq_rel);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a version number that is increased when the data is modified. If the version is the same
|
||||
* at two points in time on the same #ImplicitSharingInfo, one can be sure that the referenced
|
||||
* data has not been modified.
|
||||
*/
|
||||
int64_t version() const
|
||||
{
|
||||
return version_.load(std::memory_order_acquire);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -71,10 +131,31 @@ class ImplicitSharingInfo : NonCopyable, NonMovable {
|
|||
*/
|
||||
void remove_user_and_delete_if_last() const
|
||||
{
|
||||
const int old_user_count = users_.fetch_sub(1, std::memory_order_acq_rel);
|
||||
const int old_user_count = strong_users_.fetch_sub(1, std::memory_order_acq_rel);
|
||||
BLI_assert(old_user_count >= 1);
|
||||
const bool was_last_user = old_user_count == 1;
|
||||
if (was_last_user) {
|
||||
const int old_weak_user_count = weak_users_.load(std::memory_order_acquire);
|
||||
if (old_weak_user_count == 1) {
|
||||
const_cast<ImplicitSharingInfo *>(this)->delete_self_with_data();
|
||||
}
|
||||
else {
|
||||
const_cast<ImplicitSharingInfo *>(this)->delete_data_only();
|
||||
this->remove_weak_user_and_delete_if_last();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This might just decrement the weak user count or might delete the data. Should be used in
|
||||
* conjunction with #add_weak_user.
|
||||
*/
|
||||
void remove_weak_user_and_delete_if_last() const
|
||||
{
|
||||
const int old_weak_user_count = weak_users_.fetch_sub(1, std::memory_order_acq_rel);
|
||||
BLI_assert(old_weak_user_count >= 1);
|
||||
const bool was_last_weak_user = old_weak_user_count == 1;
|
||||
if (was_last_weak_user) {
|
||||
const_cast<ImplicitSharingInfo *>(this)->delete_self_with_data();
|
||||
}
|
||||
}
|
||||
|
@ -82,6 +163,8 @@ class ImplicitSharingInfo : NonCopyable, NonMovable {
|
|||
private:
|
||||
/** Has to free the #ImplicitSharingInfo and the referenced data. */
|
||||
virtual void delete_self_with_data() = 0;
|
||||
/** Can free the referenced data but the #ImplicitSharingInfo still has to be kept alive. */
|
||||
virtual void delete_data_only() {}
|
||||
};
|
||||
|
||||
/**
|
||||
|
@ -89,8 +172,6 @@ class ImplicitSharingInfo : NonCopyable, NonMovable {
|
|||
* class can be used with #ImplicitSharingPtr.
|
||||
*/
|
||||
class ImplicitSharingMixin : public ImplicitSharingInfo {
|
||||
public:
|
||||
ImplicitSharingMixin() : ImplicitSharingInfo(1) {}
|
||||
|
||||
private:
|
||||
void delete_self_with_data() override
|
||||
|
@ -102,6 +183,14 @@ class ImplicitSharingMixin : public ImplicitSharingInfo {
|
|||
virtual void delete_self() = 0;
|
||||
};
|
||||
|
||||
/**
|
||||
* Utility that contains sharing information and the data that is shared.
|
||||
*/
|
||||
struct ImplicitSharingInfoAndData {
|
||||
const ImplicitSharingInfo *sharing_info = nullptr;
|
||||
const void *data = nullptr;
|
||||
};
|
||||
|
||||
namespace implicit_sharing {
|
||||
|
||||
namespace detail {
|
||||
|
@ -110,11 +199,11 @@ void *resize_trivial_array_impl(void *old_data,
|
|||
int64_t old_size,
|
||||
int64_t new_size,
|
||||
int64_t alignment,
|
||||
ImplicitSharingInfo **sharing_info);
|
||||
const ImplicitSharingInfo **sharing_info);
|
||||
void *make_trivial_data_mutable_impl(void *old_data,
|
||||
int64_t size,
|
||||
int64_t alignment,
|
||||
ImplicitSharingInfo **sharing_info);
|
||||
const ImplicitSharingInfo **sharing_info);
|
||||
|
||||
} // namespace detail
|
||||
|
||||
|
@ -124,9 +213,9 @@ void *make_trivial_data_mutable_impl(void *old_data,
|
|||
*/
|
||||
template<typename T>
|
||||
void copy_shared_pointer(T *src_ptr,
|
||||
ImplicitSharingInfo *src_sharing_info,
|
||||
const ImplicitSharingInfo *src_sharing_info,
|
||||
T **r_dst_ptr,
|
||||
ImplicitSharingInfo **r_dst_sharing_info)
|
||||
const ImplicitSharingInfo **r_dst_sharing_info)
|
||||
{
|
||||
*r_dst_ptr = src_ptr;
|
||||
*r_dst_sharing_info = src_sharing_info;
|
||||
|
@ -139,7 +228,7 @@ void copy_shared_pointer(T *src_ptr,
|
|||
/**
|
||||
* Remove this reference to the shared data and remove dangling pointers.
|
||||
*/
|
||||
template<typename T> void free_shared_data(T **data, ImplicitSharingInfo **sharing_info)
|
||||
template<typename T> void free_shared_data(T **data, const ImplicitSharingInfo **sharing_info)
|
||||
{
|
||||
if (*sharing_info) {
|
||||
BLI_assert(*data != nullptr);
|
||||
|
@ -153,13 +242,15 @@ template<typename T> void free_shared_data(T **data, ImplicitSharingInfo **shari
|
|||
* Create an implicit sharing object that takes ownership of the data, allowing it to be shared.
|
||||
* When it is no longer used, the data is freed with #MEM_freeN, so it must be a trivial type.
|
||||
*/
|
||||
ImplicitSharingInfo *info_for_mem_free(void *data);
|
||||
const ImplicitSharingInfo *info_for_mem_free(void *data);
|
||||
|
||||
/**
|
||||
* Make data mutable (single-user) if it is shared. For trivially-copyable data only.
|
||||
*/
|
||||
template<typename T>
|
||||
void make_trivial_data_mutable(T **data, ImplicitSharingInfo **sharing_info, const int64_t size)
|
||||
void make_trivial_data_mutable(T **data,
|
||||
const ImplicitSharingInfo **sharing_info,
|
||||
const int64_t size)
|
||||
{
|
||||
*data = static_cast<T *>(
|
||||
detail::make_trivial_data_mutable_impl(*data, sizeof(T) * size, alignof(T), sharing_info));
|
||||
|
@ -171,7 +262,7 @@ void make_trivial_data_mutable(T **data, ImplicitSharingInfo **sharing_info, con
|
|||
*/
|
||||
template<typename T>
|
||||
void resize_trivial_array(T **data,
|
||||
ImplicitSharingInfo **sharing_info,
|
||||
const ImplicitSharingInfo **sharing_info,
|
||||
int64_t old_size,
|
||||
int64_t new_size)
|
||||
{
|
||||
|
|
|
@ -105,6 +105,23 @@ inline void BLI_kdtree_nd_(range_search_cb_cpp)(const KDTree *tree,
|
|||
},
|
||||
const_cast<Fn *>(&fn));
|
||||
}
|
||||
|
||||
template<typename Fn>
|
||||
inline int BLI_kdtree_nd_(find_nearest_cb_cpp)(const KDTree *tree,
|
||||
const float co[KD_DIMS],
|
||||
KDTreeNearest *r_nearest,
|
||||
Fn &&fn)
|
||||
{
|
||||
return BLI_kdtree_nd_(find_nearest_cb)(
|
||||
tree,
|
||||
co,
|
||||
[](void *user_data, const int index, const float *co, const float dist_sq) {
|
||||
Fn &fn = *static_cast<Fn *>(user_data);
|
||||
return fn(index, co, dist_sq);
|
||||
},
|
||||
&fn,
|
||||
r_nearest);
|
||||
}
|
||||
#endif
|
||||
|
||||
#undef _BLI_CONCAT_AUX
|
||||
|
|
|
@ -145,11 +145,6 @@ bool is_quad_flip_v3_first_third_fast(const float v1[3],
|
|||
const float v2[3],
|
||||
const float v3[3],
|
||||
const float v4[3]);
|
||||
bool is_quad_flip_v3_first_third_fast_with_normal(const float v1[3],
|
||||
const float v2[3],
|
||||
const float v3[3],
|
||||
const float v4[3],
|
||||
const float normal[3]);
|
||||
|
||||
/** \} */
|
||||
|
||||
|
|
|
@ -87,12 +87,7 @@ template<typename T, eValueType V> class PrimitiveValue;
|
|||
using IntValue = PrimitiveValue<int64_t, eValueType::Int>;
|
||||
using DoubleValue = PrimitiveValue<double, eValueType::Double>;
|
||||
using BooleanValue = PrimitiveValue<bool, eValueType::Boolean>;
|
||||
|
||||
template<typename Container, eValueType V, typename ContainerItem = typename Container::value_type>
|
||||
class ContainerValue;
|
||||
/* ArrayValue stores its items as shared pointer as it shares data with a lookup table that can
|
||||
* be created by calling `create_lookup`. */
|
||||
using ArrayValue = ContainerValue<Vector<std::shared_ptr<Value>>, eValueType::Array>;
|
||||
class ArrayValue;
|
||||
|
||||
/**
|
||||
* Class containing a (de)serializable value.
|
||||
|
@ -214,7 +209,7 @@ template<
|
|||
eValueType V,
|
||||
|
||||
/** Type of the data inside the container. */
|
||||
typename ContainerItem>
|
||||
typename ContainerItem = typename Container::value_type>
|
||||
class ContainerValue : public Value {
|
||||
public:
|
||||
using Items = Container;
|
||||
|
@ -237,6 +232,20 @@ class ContainerValue : public Value {
|
|||
}
|
||||
};
|
||||
|
||||
class ArrayValue : public ContainerValue<Vector<std::shared_ptr<Value>>, eValueType::Array> {
|
||||
public:
|
||||
void append(std::shared_ptr<Value> value)
|
||||
{
|
||||
this->elements().append(std::move(value));
|
||||
}
|
||||
|
||||
void append_int(int value);
|
||||
void append_str(std::string value);
|
||||
void append_null();
|
||||
std::shared_ptr<DictionaryValue> append_dict();
|
||||
std::shared_ptr<ArrayValue> append_array();
|
||||
};
|
||||
|
||||
/**
|
||||
* Internal storage type for DictionaryValue.
|
||||
*
|
||||
|
@ -268,6 +277,62 @@ class DictionaryValue
|
|||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
const std::shared_ptr<Value> *lookup_value(const StringRef key) const
|
||||
{
|
||||
for (const auto &item : this->elements()) {
|
||||
if (item.first == key) {
|
||||
return &item.second;
|
||||
}
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
std::optional<StringRefNull> lookup_str(const StringRef key) const
|
||||
{
|
||||
if (const std::shared_ptr<Value> *value = this->lookup_value(key)) {
|
||||
if (const StringValue *str_value = (*value)->as_string_value()) {
|
||||
return StringRefNull(str_value->value());
|
||||
}
|
||||
}
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
std::optional<int64_t> lookup_int(const StringRef key) const
|
||||
{
|
||||
if (const std::shared_ptr<Value> *value = this->lookup_value(key)) {
|
||||
if (const IntValue *int_value = (*value)->as_int_value()) {
|
||||
return int_value->value();
|
||||
}
|
||||
}
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
const DictionaryValue *lookup_dict(const StringRef key) const
|
||||
{
|
||||
if (const std::shared_ptr<Value> *value = this->lookup_value(key)) {
|
||||
return (*value)->as_dictionary_value();
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
const ArrayValue *lookup_array(const StringRef key) const
|
||||
{
|
||||
if (const std::shared_ptr<Value> *value = this->lookup_value(key)) {
|
||||
return (*value)->as_array_value();
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
void append(std::string key, std::shared_ptr<Value> value)
|
||||
{
|
||||
this->elements().append({std::move(key), std::move(value)});
|
||||
}
|
||||
|
||||
void append_int(std::string key, int64_t value);
|
||||
void append_str(std::string key, std::string value);
|
||||
std::shared_ptr<DictionaryValue> append_dict(std::string key);
|
||||
std::shared_ptr<ArrayValue> append_array(std::string key);
|
||||
};
|
||||
|
||||
/**
|
||||
|
@ -300,4 +365,7 @@ class JsonFormatter : public Formatter {
|
|||
std::unique_ptr<Value> deserialize(std::istream &is) override;
|
||||
};
|
||||
|
||||
void write_json_file(StringRef path, const Value &value);
|
||||
[[nodiscard]] std::shared_ptr<Value> read_json_file(StringRef path);
|
||||
|
||||
} // namespace blender::io::serialize
|
||||
|
|
|
@ -0,0 +1,93 @@
|
|||
/* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "BLI_assert.h"
|
||||
#include "BLI_math_base.h"
|
||||
|
||||
namespace blender {
|
||||
|
||||
/**
|
||||
* Contains an integer frame number and a subframe float in the range [0, 1).
|
||||
*/
|
||||
struct SubFrame {
|
||||
private:
|
||||
int frame_;
|
||||
float subframe_;
|
||||
|
||||
public:
|
||||
SubFrame(const int frame = 0, float subframe = 0.0f) : frame_(frame), subframe_(subframe)
|
||||
{
|
||||
BLI_assert(subframe >= 0.0f);
|
||||
BLI_assert(subframe < 1.0f);
|
||||
}
|
||||
|
||||
SubFrame(const float frame) : SubFrame(int(floorf(frame)), fractf(frame)) {}
|
||||
|
||||
int frame() const
|
||||
{
|
||||
return frame_;
|
||||
}
|
||||
|
||||
float subframe() const
|
||||
{
|
||||
return subframe_;
|
||||
}
|
||||
|
||||
explicit operator float() const
|
||||
{
|
||||
return float(frame_) + float(subframe_);
|
||||
}
|
||||
|
||||
explicit operator double() const
|
||||
{
|
||||
return double(frame_) + double(subframe_);
|
||||
}
|
||||
|
||||
static SubFrame min()
|
||||
{
|
||||
return {INT32_MIN, 0.0f};
|
||||
}
|
||||
|
||||
static SubFrame max()
|
||||
{
|
||||
return {INT32_MAX, std::nexttowardf(1.0f, 0.0)};
|
||||
}
|
||||
|
||||
friend bool operator==(const SubFrame &a, const SubFrame &b)
|
||||
{
|
||||
return a.frame_ == b.frame_ && a.subframe_ == b.subframe_;
|
||||
}
|
||||
|
||||
friend bool operator!=(const SubFrame &a, const SubFrame &b)
|
||||
{
|
||||
return !(a == b);
|
||||
}
|
||||
|
||||
friend bool operator<(const SubFrame &a, const SubFrame &b)
|
||||
{
|
||||
return a.frame_ < b.frame_ || (a.frame_ == b.frame_ && a.subframe_ < b.subframe_);
|
||||
}
|
||||
|
||||
friend bool operator<=(const SubFrame &a, const SubFrame &b)
|
||||
{
|
||||
return a.frame_ <= b.frame_ || (a.frame_ == b.frame_ && a.subframe_ <= b.subframe_);
|
||||
}
|
||||
|
||||
friend bool operator>(const SubFrame &a, const SubFrame &b)
|
||||
{
|
||||
return a.frame_ > b.frame_ || (a.frame_ == b.frame_ && a.subframe_ > b.subframe_);
|
||||
}
|
||||
|
||||
friend bool operator>=(const SubFrame &a, const SubFrame &b)
|
||||
{
|
||||
return a.frame_ >= b.frame_ || (a.frame_ == b.frame_ && a.subframe_ >= b.subframe_);
|
||||
}
|
||||
|
||||
friend std::ostream &operator<<(std::ostream &stream, const SubFrame &a)
|
||||
{
|
||||
return stream << float(a);
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace blender
|
|
@ -352,6 +352,7 @@ set(SRC
|
|||
BLI_string_search.h
|
||||
BLI_string_utf8.h
|
||||
BLI_string_utils.h
|
||||
BLI_sub_frame.hh
|
||||
BLI_sys_types.h
|
||||
BLI_system.h
|
||||
BLI_task.h
|
||||
|
|
|
@ -13,7 +13,7 @@ class MEMFreeImplicitSharing : public ImplicitSharingInfo {
|
|||
public:
|
||||
void *data;
|
||||
|
||||
MEMFreeImplicitSharing(void *data) : ImplicitSharingInfo(1), data(data)
|
||||
MEMFreeImplicitSharing(void *data) : data(data)
|
||||
{
|
||||
BLI_assert(data != nullptr);
|
||||
}
|
||||
|
@ -26,7 +26,7 @@ class MEMFreeImplicitSharing : public ImplicitSharingInfo {
|
|||
}
|
||||
};
|
||||
|
||||
ImplicitSharingInfo *info_for_mem_free(void *data)
|
||||
const ImplicitSharingInfo *info_for_mem_free(void *data)
|
||||
{
|
||||
return MEM_new<MEMFreeImplicitSharing>(__func__, data);
|
||||
}
|
||||
|
@ -36,7 +36,7 @@ namespace detail {
|
|||
void *make_trivial_data_mutable_impl(void *old_data,
|
||||
const int64_t size,
|
||||
const int64_t alignment,
|
||||
ImplicitSharingInfo **sharing_info)
|
||||
const ImplicitSharingInfo **sharing_info)
|
||||
{
|
||||
if (!old_data) {
|
||||
BLI_assert(size == 0);
|
||||
|
@ -51,6 +51,9 @@ void *make_trivial_data_mutable_impl(void *old_data,
|
|||
*sharing_info = info_for_mem_free(new_data);
|
||||
return new_data;
|
||||
}
|
||||
else {
|
||||
(*sharing_info)->tag_ensured_mutable();
|
||||
}
|
||||
|
||||
return old_data;
|
||||
}
|
||||
|
@ -59,7 +62,7 @@ void *resize_trivial_array_impl(void *old_data,
|
|||
const int64_t old_size,
|
||||
const int64_t new_size,
|
||||
const int64_t alignment,
|
||||
ImplicitSharingInfo **sharing_info)
|
||||
const ImplicitSharingInfo **sharing_info)
|
||||
{
|
||||
if (new_size == 0) {
|
||||
if (*sharing_info) {
|
||||
|
@ -79,11 +82,13 @@ void *resize_trivial_array_impl(void *old_data,
|
|||
|
||||
BLI_assert(old_size != 0);
|
||||
if ((*sharing_info)->is_mutable()) {
|
||||
if (auto *info = dynamic_cast<MEMFreeImplicitSharing *>(*sharing_info)) {
|
||||
if (auto *info = const_cast<MEMFreeImplicitSharing *>(
|
||||
dynamic_cast<const MEMFreeImplicitSharing *>(*sharing_info))) {
|
||||
/* If the array was allocated with the MEM allocator, we can use realloc directly, which
|
||||
* could theoretically give better performance if the data can be reused in place. */
|
||||
void *new_data = static_cast<int *>(MEM_reallocN(old_data, new_size));
|
||||
info->data = new_data;
|
||||
(*sharing_info)->tag_ensured_mutable();
|
||||
return new_data;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -5889,6 +5889,9 @@ bool is_quad_flip_v3_first_third_fast(const float v1[3],
|
|||
const float v3[3],
|
||||
const float v4[3])
|
||||
{
|
||||
/* NOTE: if the faces normal has been calculated it's possible to simplify the following checks,
|
||||
* however this means the solution may be different depending on the existence of normals
|
||||
* causing tessellation to be "unstable" depending on the existence of normals, see #106469. */
|
||||
float d_12[3], d_13[3], d_14[3];
|
||||
float cross_a[3], cross_b[3];
|
||||
sub_v3_v3v3(d_12, v2, v1);
|
||||
|
@ -5899,19 +5902,6 @@ bool is_quad_flip_v3_first_third_fast(const float v1[3],
|
|||
return dot_v3v3(cross_a, cross_b) > 0.0f;
|
||||
}
|
||||
|
||||
bool is_quad_flip_v3_first_third_fast_with_normal(const float v1[3],
|
||||
const float v2[3],
|
||||
const float v3[3],
|
||||
const float v4[3],
|
||||
const float normal[3])
|
||||
{
|
||||
float dir_v3v1[3], tangent[3];
|
||||
sub_v3_v3v3(dir_v3v1, v3, v1);
|
||||
cross_v3_v3v3(tangent, dir_v3v1, normal);
|
||||
const float dot = dot_v3v3(v1, tangent);
|
||||
return (dot_v3v3(v4, tangent) >= dot) || (dot_v3v3(v2, tangent) <= dot);
|
||||
}
|
||||
|
||||
float cubic_tangent_factor_circle_v3(const float tan_l[3], const float tan_r[3])
|
||||
{
|
||||
BLI_ASSERT_UNIT_V3(tan_l);
|
||||
|
|
|
@ -2728,7 +2728,7 @@ void svd_m4(float U[4][4], float s[4], float V[4][4], float A_[4][4])
|
|||
e[k + 1] += 1.0f;
|
||||
}
|
||||
e[k] = -e[k];
|
||||
if ((k + 1 < m) & (e[k] != 0.0f)) {
|
||||
if ((k + 1 < m) && (e[k] != 0.0f)) {
|
||||
float invek1;
|
||||
|
||||
/* Apply the transformation. */
|
||||
|
@ -2812,7 +2812,7 @@ void svd_m4(float U[4][4], float s[4], float V[4][4], float A_[4][4])
|
|||
/* If required, generate V. */
|
||||
|
||||
for (k = n - 1; k >= 0; k--) {
|
||||
if ((k < nrt) & (e[k] != 0.0f)) {
|
||||
if ((k < nrt) && (e[k] != 0.0f)) {
|
||||
for (j = k + 1; j < nu; j++) {
|
||||
float t = 0;
|
||||
for (i = k + 1; i < n; i++) {
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue