WIP: Vulkan: Workbench #107886

Closed
Jeroen Bakker wants to merge 88 commits from Jeroen-Bakker:vulkan-draw-manager-workbench into main

When changing the target branch, be careful to rebase the branch in your fork to match. See documentation.
200 changed files with 2908 additions and 1469 deletions
Showing only changes of commit 34d23517f2 - Show all commits

View File

@ -503,7 +503,7 @@ if(NOT APPLE)
mark_as_advanced(WITH_CYCLES_DEVICE_CUDA)
option(WITH_CYCLES_CUDA_BINARIES "Build Cycles NVIDIA CUDA binaries" OFF)
set(CYCLES_CUDA_BINARIES_ARCH sm_30 sm_35 sm_37 sm_50 sm_52 sm_60 sm_61 sm_70 sm_75 sm_86 compute_75 CACHE STRING "CUDA architectures to build binaries for")
set(CYCLES_CUDA_BINARIES_ARCH sm_30 sm_35 sm_37 sm_50 sm_52 sm_60 sm_61 sm_70 sm_75 sm_86 sm_89 compute_75 CACHE STRING "CUDA architectures to build binaries for")
option(WITH_CYCLES_CUDA_BUILD_SERIAL "Build cubins one after another (useful on machines with limited RAM)" OFF)
option(WITH_CUDA_DYNLOAD "Dynamically load CUDA libraries at runtime (for developers, makes cuda-gdb work)" ON)

View File

@ -14,6 +14,7 @@ set(EMBREE_EXTRA_ARGS
-DEMBREE_BACKFACE_CULLING=OFF
-DEMBREE_BACKFACE_CULLING_CURVES=ON
-DEMBREE_BACKFACE_CULLING_SPHERES=ON
-DEMBREE_NO_SPLASH=ON
-DEMBREE_TASKING_SYSTEM=TBB
-DEMBREE_TBB_ROOT=${LIBDIR}/tbb
-DTBB_ROOT=${LIBDIR}/tbb

View File

@ -477,9 +477,9 @@ set(SQLITE_HASH_TYPE SHA1)
set(SQLITE_FILE sqlite-autoconf-${SQLLITE_LONG_VERSION}.tar.gz)
set(SQLITE_CPE "cpe:2.3:a:sqlite:sqlite:${SQLITE_VERSION}:*:*:*:*:*:*:*")
set(EMBREE_VERSION 4.0.1)
set(EMBREE_VERSION 4.1.0)
set(EMBREE_URI https://github.com/embree/embree/archive/v${EMBREE_VERSION}.zip)
set(EMBREE_HASH dd26617719a587e126b341d1b32f7fd0)
set(EMBREE_HASH 4b525955b08e1249a700dea5b5ffc8b2)
set(EMBREE_HASH_TYPE MD5)
set(EMBREE_FILE embree-v${EMBREE_VERSION}.zip)

View File

@ -24,144 +24,3 @@ index 7c2f43d..106b1d5 100644
DISABLE_STACK_PROTECTOR_FOR_INTERSECTORS(${EMBREE_LIBRARY_FILES_AVX2})
ADD_LIBRARY(embree_avx2 STATIC ${EMBREE_LIBRARY_FILES_AVX2})
TARGET_LINK_LIBRARIES(embree_avx2 PRIVATE tasking)
diff --git a/include/embree4/rtcore_device.h b/include/embree4/rtcore_device.h
index 45bf95583..62ee7787d 100644
--- a/include/embree4/rtcore_device.h
+++ b/include/embree4/rtcore_device.h
@@ -55,6 +55,7 @@ enum RTCDeviceProperty
RTC_DEVICE_PROPERTY_FILTER_FUNCTION_SUPPORTED = 66,
RTC_DEVICE_PROPERTY_IGNORE_INVALID_RAYS_ENABLED = 67,
RTC_DEVICE_PROPERTY_COMPACT_POLYS_ENABLED = 68,
+ RTC_DEVICE_PROPERTY_BACKFACE_CULLING_SPHERES_ENABLED = 69,
RTC_DEVICE_PROPERTY_TRIANGLE_GEOMETRY_SUPPORTED = 96,
RTC_DEVICE_PROPERTY_QUAD_GEOMETRY_SUPPORTED = 97,
diff --git a/kernels/common/device.cpp b/kernels/common/device.cpp
index 3ffac7e37..215ccc961 100644
--- a/kernels/common/device.cpp
+++ b/kernels/common/device.cpp
@@ -170,6 +170,9 @@ namespace embree
#if defined (EMBREE_BACKFACE_CULLING_CURVES)
v += "backfacecullingcurves ";
#endif
+#if defined (EMBREE_BACKFACE_CULLING_SPHERES)
+ v += "backfacecullingspheres ";
+#endif
#if defined(EMBREE_FILTER_FUNCTION)
v += "intersection_filter ";
#endif
@@ -477,6 +480,12 @@ namespace embree
case RTC_DEVICE_PROPERTY_BACKFACE_CULLING_CURVES_ENABLED: return 0;
#endif
+#if defined(EMBREE_BACKFACE_CULLING_SPHERES)
+ case RTC_DEVICE_PROPERTY_BACKFACE_CULLING_SPHERES_ENABLED: return 1;
+#else
+ case RTC_DEVICE_PROPERTY_BACKFACE_CULLING_SPHERES_ENABLED: return 0;
+#endif
+
#if defined(EMBREE_COMPACT_POLYS)
case RTC_DEVICE_PROPERTY_COMPACT_POLYS_ENABLED: return 1;
#else
diff --git a/kernels/config.h.in b/kernels/config.h.in
index f02c90360..ba9acde56 100644
--- a/kernels/config.h.in
+++ b/kernels/config.h.in
@@ -5,6 +5,7 @@
#cmakedefine EMBREE_STAT_COUNTERS
#cmakedefine EMBREE_BACKFACE_CULLING
#cmakedefine EMBREE_BACKFACE_CULLING_CURVES
+#cmakedefine EMBREE_BACKFACE_CULLING_SPHERES
#cmakedefine EMBREE_FILTER_FUNCTION
#cmakedefine EMBREE_IGNORE_INVALID_RAYS
#cmakedefine EMBREE_GEOMETRY_TRIANGLE
diff --git a/kernels/geometry/sphere_intersector.h b/kernels/geometry/sphere_intersector.h
index 074f910a2..30f490818 100644
--- a/kernels/geometry/sphere_intersector.h
+++ b/kernels/geometry/sphere_intersector.h
@@ -106,8 +106,13 @@ namespace embree
const vbool<M> valid_front = valid & (ray.tnear() <= t_front) & (t_front <= ray.tfar);
const vbool<M> valid_back = valid & (ray.tnear() <= t_back ) & (t_back <= ray.tfar);
+#if defined (EMBREE_BACKFACE_CULLING_SPHERES)
+ /* check if there is a first hit */
+ const vbool<M> valid_first = valid_front;
+#else
/* check if there is a first hit */
const vbool<M> valid_first = valid_front | valid_back;
+#endif
if (unlikely(none(valid_first)))
return false;
@@ -120,7 +125,8 @@ namespace embree
/* invoke intersection filter for first hit */
const bool is_hit_first = epilog(valid_first, hit);
-
+
+#if !defined (EMBREE_BACKFACE_CULLING_SPHERES)
/* check for possible second hits before potentially accepted hit */
const vfloat<M> t_second = t_back;
const vbool<M> valid_second = valid_front & valid_back & (t_second <= ray.tfar);
@@ -131,7 +137,9 @@ namespace embree
const Vec3vf<M> Ng_second = td_back * ray_dir - perp;
hit = SphereIntersectorHitM<M> (t_second, Ng_second);
const bool is_hit_second = epilog(valid_second, hit);
-
+#else
+ constexpr bool is_hit_second = false;
+#endif
return is_hit_first | is_hit_second;
}
@@ -186,8 +194,13 @@ namespace embree
const vbool<M> valid_front = valid & (ray.tnear()[k] <= t_front) & (t_front <= ray.tfar[k]);
const vbool<M> valid_back = valid & (ray.tnear()[k] <= t_back ) & (t_back <= ray.tfar[k]);
+#if defined (EMBREE_BACKFACE_CULLING_SPHERES)
+ /* check if there is a first hit */
+ const vbool<M> valid_first = valid_front;
+#else
/* check if there is a first hit */
const vbool<M> valid_first = valid_front | valid_back;
+#endif
if (unlikely(none(valid_first)))
return false;
@@ -200,7 +213,8 @@ namespace embree
/* invoke intersection filter for first hit */
const bool is_hit_first = epilog(valid_first, hit);
-
+
+#if !defined (EMBREE_BACKFACE_CULLING_SPHERES)
/* check for possible second hits before potentially accepted hit */
const vfloat<M> t_second = t_back;
const vbool<M> valid_second = valid_front & valid_back & (t_second <= ray.tfar[k]);
@@ -211,7 +225,9 @@ namespace embree
const Vec3vf<M> Ng_second = td_back * ray_dir - perp;
hit = SphereIntersectorHitM<M> (t_second, Ng_second);
const bool is_hit_second = epilog(valid_second, hit);
-
+#else
+ constexpr bool is_hit_second = false;
+#endif
return is_hit_first | is_hit_second;
}
};
diff -ruN a/kernels/sycl/rthwif_embree_builder.cpp b/kernels/sycl/rthwif_embree_builder.cpp
--- a/kernels/sycl/rthwif_embree_builder.cpp 2023-03-28 17:23:06.429190200 +0200
+++ b/kernels/sycl/rthwif_embree_builder.cpp 2023-03-28 17:35:01.291938600 +0200
@@ -540,7 +540,12 @@
assert(offset <= geomDescrData.size());
}
+ /* Force running BVH building sequentially from the calling thread if using TBB < 2021, as it otherwise leads to runtime issues. */
+#if TBB_VERSION_MAJOR<2021
+ RTHWIF_PARALLEL_OPERATION parallelOperation = nullptr;
+#else
RTHWIF_PARALLEL_OPERATION parallelOperation = rthwifNewParallelOperation();
+#endif
/* estimate static accel size */
BBox1f time_range(0,1);

View File

@ -8,6 +8,8 @@ buildbot:
version: '10.1.243'
cuda11:
version: '11.4.1'
cuda12:
version: '12.1.1'
hip:
version: '5.5.30571'
hiprt:

View File

@ -302,10 +302,6 @@
* \ingroup imbuf
*/
/** \defgroup imbdds DDS
* \ingroup imbuf
*/
/** \defgroup openexr OpenEXR
* \ingroup imbuf
*/

View File

@ -389,7 +389,7 @@ static float4 LerpCurveSegmentMotionCV(ParticleCurveData *CData, int sys, int cu
}
const float4 mP = CurveSegmentMotionCV(CData, sys, curve, first_curve_key + curvekey);
const float4 mP2 = CurveSegmentMotionCV(CData, sys, curve, first_curve_key + curvekey2);
return lerp(mP, mP2, remainder);
return mix(mP, mP2, remainder);
}
static void export_hair_motion_validate_attribute(Hair *hair,
@ -899,9 +899,9 @@ static float4 interpolate_curve_points(const float (*b_attr_position)[3],
const int point_a = clamp((int)curve_t, 0, num_points - 1);
const int point_b = min(point_a + 1, num_points - 1);
const float t = curve_t - (float)point_a;
return lerp(curve_point_as_float4(b_attr_position, b_attr_radius, first_point_index + point_a),
curve_point_as_float4(b_attr_position, b_attr_radius, first_point_index + point_b),
t);
return mix(curve_point_as_float4(b_attr_position, b_attr_radius, first_point_index + point_a),
curve_point_as_float4(b_attr_position, b_attr_radius, first_point_index + point_b),
t);
}
static void export_hair_curves(Scene *scene,

View File

@ -515,6 +515,7 @@ void BlenderSession::render_frame_finish()
static bool bake_setup_pass(Scene *scene, const string &bake_type_str, const int bake_filter)
{
Integrator *integrator = scene->integrator;
Film *film = scene->film;
const char *bake_type = bake_type_str.c_str();
PassType type = PASS_NONE;
@ -542,13 +543,29 @@ static bool bake_setup_pass(Scene *scene, const string &bake_type_str, const int
else if (strcmp(bake_type, "ENVIRONMENT") == 0) {
type = PASS_BACKGROUND;
}
/* AO passes. */
/* AO pass. */
else if (strcmp(bake_type, "AO") == 0) {
type = PASS_AO;
}
/* Shadow pass. */
else if (strcmp(bake_type, "SHADOW") == 0) {
/* Bake as combined pass, together with marking the object as a shadow catcher. */
type = PASS_SHADOW_CATCHER;
film->set_use_approximate_shadow_catcher(true);
use_direct_light = true;
use_indirect_light = true;
include_albedo = true;
integrator->set_use_diffuse(true);
integrator->set_use_glossy(true);
integrator->set_use_transmission(true);
integrator->set_use_emission(true);
}
/* Combined pass. */
else if (strcmp(bake_type, "COMBINED") == 0) {
type = PASS_COMBINED;
film->set_use_approximate_shadow_catcher(true);
use_direct_light = (bake_filter & BL::BakeSettings::pass_filter_DIRECT) != 0;
use_indirect_light = (bake_filter & BL::BakeSettings::pass_filter_INDIRECT) != 0;
@ -683,17 +700,23 @@ void BlenderSession::bake(BL::Depsgraph &b_depsgraph_,
/* Object might have been disabled for rendering or excluded in some
* other way, in that case Blender will report a warning afterwards. */
bool object_found = false;
Object *bake_object = nullptr;
if (!session->progress.get_cancel()) {
foreach (Object *ob, scene->objects) {
if (ob->name == b_object.name()) {
object_found = true;
bake_object = ob;
break;
}
}
}
if (object_found && !session->progress.get_cancel()) {
/* For the shadow pass, temporarily mark the object as a shadow catcher. */
const bool was_shadow_catcher = (bake_object) ? bake_object->get_is_shadow_catcher() : false;
if (bake_object && bake_type == "SHADOW") {
bake_object->set_is_shadow_catcher(true);
}
if (bake_object && !session->progress.get_cancel()) {
/* Get session and buffer parameters. */
const SessionParams session_params = BlenderSync::get_session_params(
b_engine, b_userpref, b_scene, background);
@ -714,10 +737,15 @@ void BlenderSession::bake(BL::Depsgraph &b_depsgraph_,
}
/* Perform bake. Check cancel to avoid crash with incomplete scene data. */
if (object_found && !session->progress.get_cancel()) {
if (bake_object && !session->progress.get_cancel()) {
session->start();
session->wait();
}
/* Restore object state. */
if (bake_object) {
bake_object->set_is_shadow_catcher(was_shadow_catcher);
}
}
void BlenderSession::synchronize(BL::Depsgraph &b_depsgraph_)

View File

@ -360,7 +360,7 @@ void BVHSpatialSplit::split_triangle_primitive(const Mesh *mesh,
/* edge intersects the plane => insert intersection to both boxes. */
if ((v0p < pos && v1p > pos) || (v0p > pos && v1p < pos)) {
float3 t = lerp(v0, v1, clamp((pos - v0p) / (v1p - v0p), 0.0f, 1.0f));
float3 t = mix(v0, v1, clamp((pos - v0p) / (v1p - v0p), 0.0f, 1.0f));
left_bounds.grow(t);
right_bounds.grow(t);
}
@ -408,7 +408,7 @@ void BVHSpatialSplit::split_curve_primitive(const Hair *hair,
/* edge intersects the plane => insert intersection to both boxes. */
if ((v0p < pos && v1p > pos) || (v0p > pos && v1p < pos)) {
float3 t = lerp(v0, v1, clamp((pos - v0p) / (v1p - v0p), 0.0f, 1.0f));
float3 t = mix(v0, v1, clamp((pos - v0p) / (v1p - v0p), 0.0f, 1.0f));
left_bounds.grow(t);
right_bounds.grow(t);
}

View File

@ -122,8 +122,13 @@ bool OneapiDevice::check_peer_access(Device * /*peer_device*/)
bool OneapiDevice::can_use_hardware_raytracing_for_features(uint requested_features) const
{
/* MNEE and Ray-trace kernels currently don't work correctly with HWRT. */
/* MNEE and Raytrace kernels work correctly with Hardware Raytracing starting with Embree 4.1. */
# if defined(RTC_VERSION) && RTC_VERSION < 40100
return !(requested_features & (KERNEL_FEATURE_MNEE | KERNEL_FEATURE_NODE_RAYTRACE));
# else
(void)requested_features;
return true;
# endif
}
BVHLayoutMask OneapiDevice::get_bvh_layout_mask(uint requested_features) const

View File

@ -62,7 +62,7 @@ bool work_balance_do_rebalance(vector<WorkBalanceInfo> &work_balance_infos)
bool has_big_difference = false;
for (const WorkBalanceInfo &info : work_balance_infos) {
const double time_target = lerp(info.time_spent, time_average, lerp_weight);
const double time_target = mix(info.time_spent, time_average, lerp_weight);
const double new_weight = info.weight * time_target / info.time_spent;
new_weights.push_back(new_weight);
total_weight += new_weight;

View File

@ -540,12 +540,12 @@ if(WITH_CYCLES_CUDA_BINARIES)
elseif(${arch} MATCHES ".*_7." AND "${CUDA_VERSION}" LESS 100)
message(STATUS "CUDA binaries for ${arch} require CUDA 10.0+, skipped.")
elseif(${arch} MATCHES ".*_8.")
if(DEFINED CUDA11_NVCC_EXECUTABLE)
set(cuda_nvcc_executable ${CUDA11_NVCC_EXECUTABLE})
set(cuda_toolkit_root_dir ${CUDA11_TOOLKIT_ROOT_DIR})
elseif("${CUDA_VERSION}" GREATER_EQUAL 111) # Support for sm_86 was introduced in CUDA 11
if("${CUDA_VERSION}" GREATER_EQUAL 111) # Support for sm_86 was introduced in CUDA 11
set(cuda_nvcc_executable ${CUDA_NVCC_EXECUTABLE})
set(cuda_toolkit_root_dir ${CUDA_TOOLKIT_ROOT_DIR})
elseif(DEFINED CUDA11_NVCC_EXECUTABLE)
set(cuda_nvcc_executable ${CUDA11_NVCC_EXECUTABLE})
set(cuda_toolkit_root_dir ${CUDA11_TOOLKIT_ROOT_DIR})
else()
message(STATUS "CUDA binaries for ${arch} require CUDA 11.1+, skipped.")
endif()

View File

@ -96,7 +96,7 @@ ccl_device_forceinline Spectrum interpolate_fresnel_color(float3 L,
{
/* Compute the real Fresnel term and remap it from real_F0..1 to F0..1.
* The reason why we use this remapping instead of directly doing the
* Schlick approximation lerp(F0, 1.0, (1.0-cosLH)^5) is that for cases
* Schlick approximation mix(F0, 1.0, (1.0-cosLH)^5) is that for cases
* with similar IORs (e.g. ice in water), the relative IOR can be close
* enough to 1.0 that the Schlick approximation becomes inaccurate. */
float real_F = fresnel_dielectric_cos(dot(L, H), ior);

View File

@ -174,27 +174,30 @@ 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)
bool oneapi_kernel_is_compatible_with_hardware_raytracing(const std::string &kernel_name)
{
return (kernel_name.find(device_kernel_as_string(DEVICE_KERNEL_INTEGRATOR_SHADE_SURFACE_MNEE)) !=
std::string::npos) ||
/* MNEE and Raytrace kernels work correctly with Hardware Raytracing starting with Embree 4.1.
*/
# if defined(RTC_VERSION) && RTC_VERSION < 40100
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);
DEVICE_KERNEL_INTEGRATOR_SHADE_SURFACE_RAYTRACE)) == std::string::npos);
# else
return true;
# endif
}
bool oneapi_kernel_is_using_embree(const std::string &kernel_name)
bool oneapi_kernel_has_intersections(const std::string &kernel_name)
{
# ifdef WITH_EMBREE_GPU
/* MNEE and Ray-trace kernels aren't yet enabled to use Embree. */
for (int i = 0; i < (int)DEVICE_KERNEL_NUM; i++) {
DeviceKernel kernel = (DeviceKernel)i;
if (device_kernel_has_intersection(kernel)) {
if (kernel_name.find(device_kernel_as_string(kernel)) != std::string::npos) {
return !oneapi_kernel_is_raytrace_or_mnee(kernel_name);
return true;
}
}
}
# endif
return false;
}
@ -217,7 +220,8 @@ bool oneapi_load_kernels(SyclQueue *queue_,
const std::string &kernel_name = kernel_id.get_name();
if (!oneapi_kernel_is_required_for_features(kernel_name, kernel_features) ||
!oneapi_kernel_is_using_embree(kernel_name))
!(oneapi_kernel_has_intersections(kernel_name) &&
oneapi_kernel_is_compatible_with_hardware_raytracing(kernel_name)))
{
continue;
}
@ -260,14 +264,14 @@ bool oneapi_load_kernels(SyclQueue *queue_,
/* In case HWRT is on, compilation of kernels using Embree is already handled in previous
* block. */
if (!oneapi_kernel_is_required_for_features(kernel_name, kernel_features) ||
(use_hardware_raytracing && oneapi_kernel_is_using_embree(kernel_name)))
(use_hardware_raytracing && oneapi_kernel_has_intersections(kernel_name) &&
oneapi_kernel_is_compatible_with_hardware_raytracing(kernel_name)))
{
continue;
}
# ifdef WITH_EMBREE_GPU
if (oneapi_kernel_is_using_embree(kernel_name) ||
oneapi_kernel_is_raytrace_or_mnee(kernel_name)) {
if (oneapi_kernel_has_intersections(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

View File

@ -8,6 +8,7 @@
#include "kernel/film/adaptive_sampling.h"
#include "kernel/film/light_passes.h"
#include "kernel/integrator/intersect_closest.h"
#include "kernel/integrator/path_state.h"
#include "kernel/sample/pattern.h"
@ -327,6 +328,8 @@ ccl_device bool integrator_init_from_bake(KernelGlobals kg,
else {
integrator_path_init_sorted(kg, state, DEVICE_KERNEL_INTEGRATOR_SHADE_SURFACE, shader_index);
}
integrator_split_shadow_catcher(kg, state, &isect, render_buffer);
}
return true;

View File

@ -398,6 +398,7 @@ ccl_device_forceinline bool mnee_newton_solver(KernelGlobals kg,
ccl_private const ShaderData *sd,
ccl_private ShaderData *sd_vtx,
ccl_private const LightSample *ls,
const bool light_fixed_direction,
int vertex_count,
ccl_private ManifoldVertex *vertices)
{
@ -413,7 +414,6 @@ ccl_device_forceinline bool mnee_newton_solver(KernelGlobals kg,
projection_ray.time = sd->time;
Intersection projection_isect;
const bool light_fixed_direction = (ls->t == FLT_MAX);
const float3 light_sample = light_fixed_direction ? ls->D : ls->P;
/* We start gently, potentially ramping up to beta = 1, since target configurations
@ -639,6 +639,7 @@ ccl_device_forceinline Spectrum mnee_eval_bsdf_contribution(ccl_private ShaderCl
/* Compute transfer matrix determinant |T1| = |dx1/dxn| (and |dh/dx| in the process) */
ccl_device_forceinline bool mnee_compute_transfer_matrix(ccl_private const ShaderData *sd,
ccl_private const LightSample *ls,
const bool light_fixed_direction,
int vertex_count,
ccl_private ManifoldVertex *vertices,
ccl_private float *dx1_dxlight,
@ -692,7 +693,7 @@ ccl_device_forceinline bool mnee_compute_transfer_matrix(ccl_private const Shade
float dxn_dwn;
float4 dc_dlight;
if (ls->t == FLT_MAX) {
if (light_fixed_direction) {
/* Constant direction toward light sample. */
float3 wo = ls->D;
@ -764,6 +765,7 @@ ccl_device_forceinline bool mnee_path_contribution(KernelGlobals kg,
ccl_private ShaderData *sd,
ccl_private ShaderData *sd_mnee,
ccl_private LightSample *ls,
const bool light_fixed_direction,
int vertex_count,
ccl_private ManifoldVertex *vertices,
ccl_private BsdfEval *throughput)
@ -807,7 +809,8 @@ ccl_device_forceinline bool mnee_path_contribution(KernelGlobals kg,
float dh_dx;
float dx1_dxlight;
if (!mnee_compute_transfer_matrix(sd, ls, vertex_count, vertices, &dx1_dxlight, &dh_dx))
if (!mnee_compute_transfer_matrix(
sd, ls, light_fixed_direction, vertex_count, vertices, &dx1_dxlight, &dh_dx))
return false;
/* Receiver bsdf eval above already contains |n.wo|. */
@ -882,7 +885,7 @@ ccl_device_forceinline bool mnee_path_contribution(KernelGlobals kg,
kg, state, sd_mnee, NULL, PATH_RAY_DIFFUSE, true);
/* Set light looking dir. */
wo = (vi == vertex_count - 1) ? (ls->t == FLT_MAX ? ls->D : ls->P - v.p) :
wo = (vi == vertex_count - 1) ? (light_fixed_direction ? ls->D : ls->P - v.p) :
vertices[vi + 1].p - v.p;
wo = normalize_len(wo, &wo_len);
@ -1039,12 +1042,22 @@ ccl_device_forceinline int kernel_path_mnee_sample(KernelGlobals kg,
* discontinuity is visible between direct and indirect contributions */
INTEGRATOR_STATE_WRITE(state, path, mnee) |= PATH_MNEE_VALID;
/* 2. Walk on the specular manifold to find vertices on the
* casters that satisfy snell's law for each interface
*/
if (mnee_newton_solver(kg, sd, sd_mnee, ls, vertex_count, vertices)) {
/* Distant or environment light. */
bool light_fixed_direction = (ls->t == FLT_MAX);
if (ls->type == LIGHT_AREA) {
const ccl_global KernelLight *klight = &kernel_data_fetch(lights, ls->lamp);
if (klight->area.tan_half_spread == 0.0f) {
/* Area light with zero spread also has fixed direction. */
light_fixed_direction = true;
}
}
/* 2. Walk on the specular manifold to find vertices on the casters that satisfy snell's law for
* each interface. */
if (mnee_newton_solver(kg, sd, sd_mnee, ls, light_fixed_direction, vertex_count, vertices)) {
/* 3. If a solution exists, calculate contribution of the corresponding path */
if (!mnee_path_contribution(kg, state, sd, sd_mnee, ls, vertex_count, vertices, throughput))
if (!mnee_path_contribution(
kg, state, sd, sd_mnee, ls, light_fixed_direction, vertex_count, vertices, throughput))
return 0;
return vertex_count;

View File

@ -227,55 +227,43 @@ ccl_device bool area_light_spread_clamp_light(const float3 P,
}
/* Common API. */
/* Compute `eval_fac` and `pdf`. Also sample a new position on the light if `sample_coord`. */
template<bool in_volume_segment>
ccl_device_inline bool area_light_sample(const ccl_global KernelLight *klight,
const float randu,
const float randv,
const float3 P,
ccl_private LightSample *ls)
ccl_device_inline bool area_light_eval(const ccl_global KernelLight *klight,
const float3 ray_P,
ccl_private float3 *light_P,
ccl_private LightSample *ccl_restrict ls,
float randu,
float randv,
bool sample_coord)
{
ls->P = klight->co;
float3 axis_u = klight->area.axis_u;
float3 axis_v = klight->area.axis_v;
float len_u = klight->area.len_u;
float len_v = klight->area.len_v;
float3 Ng = klight->area.dir;
const float3 Ng = klight->area.dir;
const float invarea = fabsf(klight->area.invarea);
bool sample_rectangle = (klight->area.invarea > 0.0f);
if (!in_volume_segment) {
if (dot(ls->P - P, Ng) > 0.0f) {
return false;
}
}
const float3 axis_u = klight->area.axis_u;
const float3 axis_v = klight->area.axis_v;
const float len_u = klight->area.len_u;
const float len_v = klight->area.len_v;
float invarea = fabsf(klight->area.invarea);
bool is_ellipse = (klight->area.invarea < 0.0f);
bool sample_rectangle = !is_ellipse;
float3 inplane;
float3 light_P_new = *light_P;
if (in_volume_segment) {
inplane = sample_rectangle ?
rectangle_sample(axis_u * len_u * 0.5f, axis_v * len_v * 0.5f, randu, randv) :
ellipse_sample(axis_u * len_u * 0.5f, axis_v * len_v * 0.5f, randu, randv);
ls->P += inplane;
light_P_new += sample_rectangle ?
rectangle_sample(
axis_u * len_u * 0.5f, axis_v * len_v * 0.5f, randu, randv) :
ellipse_sample(axis_u * len_u * 0.5f, axis_v * len_v * 0.5f, randu, randv);
ls->pdf = invarea;
}
else {
float3 old_P = ls->P;
float3 sample_axis_u = axis_u;
float3 sample_axis_v = axis_v;
float sample_len_u = len_u;
float sample_len_v = len_v;
if (klight->area.normalize_spread > 0) {
if (!area_light_spread_clamp_light(P,
if (!area_light_spread_clamp_light(ray_P,
Ng,
&ls->P,
&sample_axis_u,
&sample_len_u,
&sample_axis_v,
&sample_len_v,
&light_P_new,
&axis_u,
&len_u,
&axis_v,
&len_v,
klight->area.tan_half_spread,
&sample_rectangle))
{
@ -285,53 +273,82 @@ ccl_device_inline bool area_light_sample(const ccl_global KernelLight *klight,
if (sample_rectangle) {
ls->pdf = area_light_rect_sample(
P, &ls->P, sample_axis_u, sample_len_u, sample_axis_v, sample_len_v, randu, randv, true);
ray_P, &light_P_new, axis_u, len_u, axis_v, len_v, randu, randv, sample_coord);
}
else {
if (klight->area.tan_half_spread == 0.0f) {
ls->pdf = 1.0f;
}
else {
ls->P += ellipse_sample(sample_axis_u * sample_len_u * 0.5f,
sample_axis_v * sample_len_v * 0.5f,
randu,
randv);
ls->pdf = 4.0f * M_1_PI_F / (sample_len_u * sample_len_v);
if (sample_coord) {
light_P_new += ellipse_sample(
axis_u * len_u * 0.5f, axis_v * len_v * 0.5f, randu, randv);
}
ls->pdf = 4.0f * M_1_PI_F / (len_u * len_v);
}
}
inplane = ls->P - old_P;
}
const float light_u = dot(inplane, axis_u) / len_u;
const float light_v = dot(inplane, axis_v) / len_v;
/* Sampled point lies outside of the area light. */
if (is_ellipse && (sqr(light_u) + sqr(light_v) > 0.25f)) {
return false;
if (sample_coord) {
*light_P = light_P_new;
ls->D = normalize_len(*light_P - ray_P, &ls->t);
}
if (!is_ellipse && (fabsf(light_u) > 0.5f || fabsf(light_v) > 0.5f)) {
return false;
}
/* NOTE: Return barycentric coordinates in the same notation as Embree and OptiX. */
ls->u = light_v + 0.5f;
ls->v = -light_u - light_v;
ls->Ng = Ng;
ls->D = normalize_len(ls->P - P, &ls->t);
ls->eval_fac = 0.25f * invarea;
if (klight->area.normalize_spread > 0) {
/* Area Light spread angle attenuation */
ls->eval_fac *= area_light_spread_attenuation(
ls->D, ls->Ng, klight->area.tan_half_spread, klight->area.normalize_spread);
ls->D, Ng, klight->area.tan_half_spread, klight->area.normalize_spread);
}
if (!sample_rectangle && klight->area.tan_half_spread > 0) {
if (in_volume_segment || (!sample_rectangle && klight->area.tan_half_spread > 0)) {
ls->pdf *= lamp_light_pdf(Ng, -ls->D, ls->t);
}
return ls->eval_fac > 0;
}
template<bool in_volume_segment>
ccl_device_inline bool area_light_sample(const ccl_global KernelLight *klight,
const float randu,
const float randv,
const float3 P,
ccl_private LightSample *ls)
{
ls->P = klight->co;
ls->Ng = klight->area.dir;
if (!in_volume_segment) {
if (dot(ls->P - P, ls->Ng) > 0.0f) {
return false;
}
}
if (!area_light_eval<in_volume_segment>(klight, P, &ls->P, ls, randu, randv, true)) {
return false;
}
const float3 inplane = ls->P - klight->co;
const float light_u = dot(inplane, klight->area.axis_u) / klight->area.len_u;
const float light_v = dot(inplane, klight->area.axis_v) / klight->area.len_v;
if (!in_volume_segment) {
const bool is_ellipse = (klight->area.invarea < 0.0f);
/* Sampled point lies outside of the area light. */
if (is_ellipse && (sqr(light_u) + sqr(light_v) > 0.25f)) {
return false;
}
if (!is_ellipse && (fabsf(light_u) > 0.5f || fabsf(light_v) > 0.5f)) {
return false;
}
}
/* NOTE: Return barycentric coordinates in the same notation as Embree and OptiX. */
ls->u = light_v + 0.5f;
ls->v = -light_u - light_v;
return true;
}
@ -339,14 +356,15 @@ ccl_device_forceinline void area_light_update_position(const ccl_global KernelLi
ccl_private LightSample *ls,
const float3 P)
{
const float invarea = fabsf(klight->area.invarea);
ls->D = normalize_len(ls->P - P, &ls->t);
ls->pdf = invarea;
if (klight->area.normalize_spread > 0) {
ls->eval_fac = 0.25f * invarea;
ls->eval_fac *= area_light_spread_attenuation(
ls->D, ls->Ng, klight->area.tan_half_spread, klight->area.normalize_spread);
if (klight->area.tan_half_spread == 0) {
/* Update position on the light to keep the direction fixed. */
area_light_eval<false>(klight, P, &ls->P, ls, 0, 0, true);
}
else {
ls->D = normalize_len(ls->P - P, &ls->t);
area_light_eval<false>(klight, P, &ls->P, ls, 0, 0, false);
/* Convert pdf to be in area measure. */
ls->pdf /= lamp_light_pdf(ls->Ng, -ls->D, ls->t);
}
}
@ -397,60 +415,13 @@ ccl_device_inline bool area_light_sample_from_intersection(
const float3 ray_D,
ccl_private LightSample *ccl_restrict ls)
{
/* area light */
float invarea = fabsf(klight->area.invarea);
float3 Ng = klight->area.dir;
float3 light_P = klight->co;
ls->u = isect->u;
ls->v = isect->v;
ls->D = ray_D;
ls->Ng = Ng;
ls->Ng = klight->area.dir;
float3 sample_axis_u = klight->area.axis_u;
float3 sample_axis_v = klight->area.axis_v;
float sample_len_u = klight->area.len_u;
float sample_len_v = klight->area.len_v;
bool is_ellipse = (klight->area.invarea < 0.0f);
bool sample_rectangle = !is_ellipse;
if (klight->area.normalize_spread > 0) {
if (!area_light_spread_clamp_light(ray_P,
Ng,
&light_P,
&sample_axis_u,
&sample_len_u,
&sample_axis_v,
&sample_len_v,
klight->area.tan_half_spread,
&sample_rectangle))
{
return false;
}
}
if (sample_rectangle) {
ls->pdf = area_light_rect_sample(
ray_P, &light_P, sample_axis_u, sample_len_u, sample_axis_v, sample_len_v, 0, 0, false);
}
else {
ls->pdf = klight->area.tan_half_spread == 0.0f ?
1.0f :
4.0f * M_1_PI_F / (sample_len_u * sample_len_v) *
lamp_light_pdf(Ng, -ray_D, ls->t);
}
ls->eval_fac = 0.25f * invarea;
if (klight->area.normalize_spread > 0) {
/* Area Light spread angle attenuation */
ls->eval_fac *= area_light_spread_attenuation(
ls->D, ls->Ng, klight->area.tan_half_spread, klight->area.normalize_spread);
}
return ls->eval_fac > 0;
float3 light_P = klight->co;
return area_light_eval<false>(klight, ray_P, &light_P, ls, 0, 0, false);
}
template<bool in_volume_segment>

View File

@ -36,7 +36,7 @@ static float shutter_curve_eval(float x, array<float> &shutter_curve)
int index = (int)x;
float frac = x - index;
if (index < shutter_curve.size() - 1) {
return lerp(shutter_curve[index], shutter_curve[index + 1], frac);
return mix(shutter_curve[index], shutter_curve[index + 1], frac);
}
else {
return shutter_curve[shutter_curve.size() - 1];

View File

@ -1291,7 +1291,7 @@ template<class T> void init_test_curve(array<T> &buffer, T start, T end, int ste
buffer.resize(steps);
for (int i = 0; i < steps; i++) {
buffer[i] = lerp(start, end, float(i) / (steps - 1));
buffer[i] = mix(start, end, float(i) / (steps - 1));
}
}

View File

@ -555,16 +555,6 @@ CCL_NAMESPACE_END
CCL_NAMESPACE_BEGIN
#if !defined(__KERNEL_METAL__)
/* Interpolation */
template<class A, class B> A lerp(const A &a, const A &b, const B &t)
{
return (A)(a * ((B)1 - t) + b * t);
}
#endif /* __KERNEL_METAL__ */
/* Triangle */
ccl_device_inline float triangle_area(ccl_private const float3 &v1,

View File

@ -379,7 +379,7 @@ ccl_device_inline Transform transform_empty()
ccl_device_inline float4 quat_interpolate(float4 q1, float4 q2, float t)
{
/* Optix and MetalRT are using lerp to interpolate motion transformations. */
/* Optix and MetalRT are using linear interpolation to interpolate motion transformations. */
#if defined(__KERNEL_GPU_RAYTRACING__)
return normalize((1.0f - t) * q1 + t * q2);
#else /* defined(__KERNEL_GPU_RAYTRACING__) */

View File

@ -20,7 +20,7 @@ from bpy.app.translations import pgettext_tip as tip_
class ANIM_OT_keying_set_export(Operator):
"""Export Keying Set to a python script"""
"""Export Keying Set to a Python script"""
bl_idname = "anim.keying_set_export"
bl_label = "Export Keying Set..."
@ -38,7 +38,7 @@ class ANIM_OT_keying_set_export(Operator):
options={'HIDDEN'},
)
filter_python: BoolProperty(
name="Filter python",
name="Filter Python",
default=True,
options={'HIDDEN'},
)

View File

@ -16,7 +16,7 @@ def _lang_module_get(sc):
class ConsoleExec(Operator):
"""Execute the current console line as a python expression"""
"""Execute the current console line as a Python expression"""
bl_idname = "console.execute"
bl_label = "Console Execute"
bl_options = {'UNDO_GROUPED'}

View File

@ -78,7 +78,7 @@ class WM_OT_previews_batch_generate(Operator):
use_trusted: BoolProperty(
default=False,
name="Trusted Blend Files",
description="Enable python evaluation for selected files",
description="Enable Python evaluation for selected files",
)
use_backups: BoolProperty(
default=True,
@ -188,7 +188,7 @@ class WM_OT_previews_batch_clear(Operator):
use_trusted: BoolProperty(
default=False,
name="Trusted Blend Files",
description="Enable python evaluation for selected files",
description="Enable Python evaluation for selected files",
)
use_backups: BoolProperty(
default=True,

View File

@ -173,7 +173,7 @@ class PREFERENCES_OT_keyconfig_test(Operator):
class PREFERENCES_OT_keyconfig_import(Operator):
"""Import key configuration from a python script"""
"""Import key configuration from a Python script"""
bl_idname = "preferences.keyconfig_import"
bl_label = "Import Key Configuration..."
@ -192,7 +192,7 @@ class PREFERENCES_OT_keyconfig_import(Operator):
options={'HIDDEN'},
)
filter_python: BoolProperty(
name="Filter python",
name="Filter Python",
default=True,
options={'HIDDEN'},
)
@ -244,7 +244,7 @@ class PREFERENCES_OT_keyconfig_import(Operator):
class PREFERENCES_OT_keyconfig_export(Operator):
"""Export key configuration to a python script"""
"""Export key configuration to a Python script"""
bl_idname = "preferences.keyconfig_export"
bl_label = "Export Key Configuration..."
@ -268,7 +268,7 @@ class PREFERENCES_OT_keyconfig_export(Operator):
options={'HIDDEN'},
)
filter_python: BoolProperty(
name="Filter python",
name="Filter Python",
default=True,
options={'HIDDEN'},
)
@ -610,7 +610,7 @@ class PREFERENCES_OT_addon_install(Operator):
options={'HIDDEN'},
)
filter_python: BoolProperty(
name="Filter python",
name="Filter Python",
default=True,
options={'HIDDEN'},
)

View File

@ -746,7 +746,7 @@ class WM_OT_operator_pie_enum(Operator):
data_path: StringProperty(
name="Operator",
description="Operator name (in python as string)",
description="Operator name (in Python as string)",
maxlen=1024,
)
prop_string: StringProperty(
@ -1018,9 +1018,44 @@ class WM_OT_url_open(Operator):
description="URL to open",
)
@staticmethod
def _add_utm_param_to_url(url, utm_source):
import urllib
# Make sure we have a scheme otherwise we can't parse the url.
if not url.startswith(("http://", "https://")):
url = "https://" + url
# Parse the URL to get its domain and query parameters.
parsed_url = urllib.parse.urlparse(url)
domain = parsed_url.netloc
# Only add a utm source if it points to a blender.org domain.
if not (domain.endswith(".blender.org") or domain == "blender.org"):
return url
# Parse the query parameters and add or update the utm_source parameter.
query_params = urllib.parse.parse_qs(parsed_url.query)
query_params["utm_source"] = utm_source
new_query = urllib.parse.urlencode(query_params, doseq=True)
# Create a new URL with the updated query parameters.
new_url_parts = list(parsed_url)
new_url_parts[4] = new_query
new_url = urllib.parse.urlunparse(new_url_parts)
return new_url
@staticmethod
def _get_utm_source():
version = bpy.app.version_string
formatted_version = version.replace(' ', '-').lower()
return f"blender-{formatted_version}"
def execute(self, _context):
import webbrowser
webbrowser.open(self.url)
complete_url = self._add_utm_param_to_url(self.url, self._get_utm_source())
webbrowser.open(complete_url)
return {'FINISHED'}
@ -1102,10 +1137,7 @@ class WM_OT_url_open_preset(Operator):
url = url(self, context)
break
import webbrowser
webbrowser.open(url)
return {'FINISHED'}
return bpy.ops.wm.url_open(url=url)
class WM_OT_path_open(Operator):
@ -1307,9 +1339,7 @@ class WM_OT_doc_view_manual(Operator):
)
return {'CANCELLED'}
else:
import webbrowser
webbrowser.open(url)
return {'FINISHED'}
return bpy.ops.wm.url_open(url=url)
class WM_OT_doc_view(Operator):
@ -1325,10 +1355,7 @@ class WM_OT_doc_view(Operator):
if url is None:
return {'CANCELLED'}
import webbrowser
webbrowser.open(url)
return {'FINISHED'}
return bpy.ops.wm.url_open(url=url)
rna_path = StringProperty(
@ -1354,7 +1381,7 @@ rna_custom_property_type_items = (
('BOOL', "Boolean", "A true or false value"),
('BOOL_ARRAY', "Boolean Array", "An array of true or false values"),
('STRING', "String", "A string value"),
('PYTHON', "Python", "Edit a python value directly, for unsupported property types"),
('PYTHON', "Python", "Edit a Python value directly, for unsupported property types"),
)
rna_custom_property_subtype_none_item = ('NONE', "Plain Data", "Data values without special behavior")

View File

@ -831,7 +831,7 @@ class ConstraintButtonsPanel:
def draw_python_constraint(self, _context):
layout = self.layout
layout.label(text="Blender 2.6 doesn't support python constraints yet")
layout.label(text="Blender 2.6 doesn't support Python constraints yet")
def draw_armature(self, context):
layout = self.layout

View File

@ -168,6 +168,7 @@ class TIME_MT_cache(Menu):
col.prop(st, "cache_softbody")
col.prop(st, "cache_particles")
col.prop(st, "cache_cloth")
col.prop(st, "cache_simulation_nodes")
col.prop(st, "cache_smoke")
col.prop(st, "cache_dynamicpaint")
col.prop(st, "cache_rigidbody")

View File

@ -5,6 +5,8 @@
#include "BLI_array.hh"
#include "BLI_color.hh"
#include "BLI_cpp_type.hh"
#include "BLI_generic_span.hh"
#include "BLI_generic_virtual_array.hh"
#include "BLI_math_color.hh"
#include "BLI_math_vector.h"
#include "BLI_math_vector.hh"
@ -587,4 +589,16 @@ template<typename T> using DefaultMixer = typename DefaultMixerStruct<T>::type;
/** \} */
/* -------------------------------------------------------------------- */
/** \name Generic Array Utils Implementations
*
* Extra implementations of functions from #BLI_array_utils.hh for all attribute types,
* used to avoid templating the same logic for each type in many places.
* \{ */
void gather(GSpan src, Span<int> map, GMutableSpan dst);
void gather(const GVArray &src, Span<int> map, GMutableSpan dst);
/** \} */
} // namespace blender::bke::attribute_math

View File

@ -31,7 +31,7 @@ extern "C" {
* version. Older Blender versions will test this and show a warning if the file
* was written with too new a version. */
#define BLENDER_FILE_MIN_VERSION 305
#define BLENDER_FILE_MIN_SUBVERSION 9
#define BLENDER_FILE_MIN_SUBVERSION 10
/** User readable version string. */
const char *BKE_blender_version_string(void);

View File

@ -303,6 +303,12 @@ void BKE_collection_parent_relations_rebuild(struct Collection *collection);
*/
void BKE_main_collections_parent_relations_rebuild(struct Main *bmain);
/**
* Perform some validation on integrity of the data of this collection.
*
* \return `true` if everything is OK, false if some errors are detected. */
bool BKE_collection_validate(struct Collection *collection);
/* .blend file I/O */
void BKE_collection_blend_write_nolib(struct BlendWriter *writer, struct Collection *collection);

View File

@ -47,7 +47,7 @@ float BKE_cryptomatte_hash_to_float(uint32_t cryptomatte_hash);
bool BKE_cryptomatte_find_name(const struct CryptomatteSession *session,
float encoded_hash,
char *r_name,
int name_len);
int name_maxncpy);
char *BKE_cryptomatte_entries_to_matte_id(struct NodeCryptomatte *node_storage);
void BKE_cryptomatte_matte_id_to_entries(struct NodeCryptomatte *node_storage,

View File

@ -118,9 +118,15 @@ struct CryptomatteStampDataCallbackData {
static blender::StringRef extract_layer_hash(blender::StringRefNull key);
/* C type callback function (StampCallback). */
static void extract_layer_names(void *_data, const char *propname, char *propvalue, int len);
static void extract_layer_names(void *_data,
const char *propname,
char *propvalue,
int propvalue_maxncpy);
/* C type callback function (StampCallback). */
static void extract_layer_manifest(void *_data, const char *propname, char *propvalue, int len);
static void extract_layer_manifest(void *_data,
const char *propname,
char *propvalue,
int propvalue_maxncpy);
};
const blender::Vector<std::string> &BKE_cryptomatte_layer_names_get(

View File

@ -50,7 +50,10 @@ void BKE_image_free_gputextures(struct Image *ima);
*/
void BKE_image_free_data(struct Image *image);
typedef void(StampCallback)(void *data, const char *propname, char *propvalue, int len);
typedef void(StampCallback)(void *data,
const char *propname,
char *propvalue,
int propvalue_maxncpy);
void BKE_render_result_stamp_info(struct Scene *scene,
struct Object *camera,

View File

@ -422,7 +422,7 @@ bActionGroup *action_groups_add_new(bAction *act, const char name[])
/* make it selected, with default name */
agrp->flag = AGRP_SELECTED;
STRNCPY(agrp->name, name[0] ? name : DATA_("Group"));
STRNCPY_UTF8(agrp->name, name[0] ? name : DATA_("Group"));
/* add to action, and validate */
BLI_addtail(&act->groups, agrp);

View File

@ -169,7 +169,7 @@ static void action_flip_pchan(Object *ob_arm,
BLI_str_escape(pchan_name_esc, pchan->name, sizeof(pchan_name_esc));
const int path_xform_prefix_len = SNPRINTF(path_xform, "pose.bones[\"%s\"]", pchan_name_esc);
char *path_xform_suffix = path_xform + path_xform_prefix_len;
const int path_xform_suffix_len = sizeof(path_xform) - path_xform_prefix_len;
const int path_xform_suffix_maxncpy = sizeof(path_xform) - path_xform_prefix_len;
/* Lookup and assign all available #FCurve channels,
* unavailable channels are left NULL. */
@ -191,11 +191,11 @@ static void action_flip_pchan(Object *ob_arm,
} fkc_pchan = {{{NULL}}};
#define FCURVE_ASSIGN_VALUE(id, path_test_suffix, index) \
BLI_strncpy(path_xform_suffix, path_test_suffix, path_xform_suffix_len); \
BLI_strncpy(path_xform_suffix, path_test_suffix, path_xform_suffix_maxncpy); \
action_flip_pchan_cache_fcurve_assign_value(&fkc_pchan.id, index, path_xform, fcache)
#define FCURVE_ASSIGN_ARRAY(id, path_test_suffix) \
BLI_strncpy(path_xform_suffix, path_test_suffix, path_xform_suffix_len); \
BLI_strncpy(path_xform_suffix, path_test_suffix, path_xform_suffix_maxncpy); \
action_flip_pchan_cache_fcurve_assign_array( \
fkc_pchan.id, ARRAY_SIZE(fkc_pchan.id), path_xform, fcache)

View File

@ -131,8 +131,8 @@ KeyingSet *BKE_keyingset_add(
/* allocate new KeyingSet */
ks = MEM_callocN(sizeof(KeyingSet), "KeyingSet");
STRNCPY(ks->idname, (idname) ? idname : (name) ? name : DATA_("KeyingSet"));
STRNCPY(ks->name, (name) ? name : (idname) ? idname : DATA_("Keying Set"));
STRNCPY_UTF8(ks->idname, (idname) ? idname : (name) ? name : DATA_("KeyingSet"));
STRNCPY_UTF8(ks->name, (name) ? name : (idname) ? idname : DATA_("Keying Set"));
ks->flag = flag;
ks->keyingflag = keyingflag;

View File

@ -173,10 +173,10 @@ bool BKE_id_attribute_rename(ID *id,
* is clamped to it's maximum length, otherwise assigning an over-long name multiple times
* will add `.001` suffix unnecessarily. */
{
const int maxlength = CustomData_name_max_length_calc(new_name);
const int new_name_maxncpy = CustomData_name_max_length_calc(new_name);
/* NOTE: A function that performs a clamped comparison without copying would be handy here. */
char new_name_clamped[MAX_CUSTOMDATA_LAYER_NAME];
BLI_strncpy_utf8(new_name_clamped, new_name, maxlength);
BLI_strncpy_utf8(new_name_clamped, new_name, new_name_maxncpy);
if (STREQ(old_name, new_name_clamped)) {
return false;
}
@ -254,19 +254,14 @@ static bool unique_name_cb(void *arg, const char *name)
bool BKE_id_attribute_calc_unique_name(ID *id, const char *name, char *outname)
{
AttrUniqueData data{id};
const int maxlength = CustomData_name_max_length_calc(name);
const int name_maxncpy = CustomData_name_max_length_calc(name);
/* Set default name if none specified.
* NOTE: We only call IFACE_() if needed to avoid locale lookup overhead. */
if (!name || name[0] == '\0') {
BLI_strncpy(outname, IFACE_("Attribute"), maxlength);
}
else {
BLI_strncpy_utf8(outname, name, maxlength);
}
BLI_strncpy_utf8(outname, (name && name[0]) ? name : IFACE_("Attribute"), name_maxncpy);
const char *defname = ""; /* Dummy argument, never used as `name` is never zero length. */
return BLI_uniquename_cb(unique_name_cb, &data, defname, '.', outname, maxlength);
return BLI_uniquename_cb(unique_name_cb, &data, defname, '.', outname, name_maxncpy);
}
CustomDataLayer *BKE_id_attribute_new(ID *id,

View File

@ -1,5 +1,7 @@
/* SPDX-License-Identifier: GPL-2.0-or-later */
#include "BLI_array_utils.hh"
#include "BKE_attribute_math.hh"
namespace blender::bke::attribute_math {
@ -128,4 +130,20 @@ void ColorGeometry4bMixer::finalize(const IndexMask mask)
});
}
void gather(const GSpan src, const Span<int> map, GMutableSpan dst)
{
attribute_math::convert_to_static_type(src.type(), [&](auto dummy) {
using T = decltype(dummy);
array_utils::gather(src.typed<T>(), map, dst.typed<T>());
});
}
void gather(const GVArray &src, const Span<int> map, GMutableSpan dst)
{
attribute_math::convert_to_static_type(src.type(), [&](auto dummy) {
using T = decltype(dummy);
array_utils::gather(src.typed<T>(), map, dst.typed<T>());
});
}
} // namespace blender::bke::attribute_math

View File

@ -1617,7 +1617,7 @@ BoidRule *boid_new_rule(int type)
rule->type = type;
rule->flag |= BOIDRULE_IN_AIR | BOIDRULE_ON_LAND;
STRNCPY(rule->name, DATA_(rna_enum_boidrule_type_items[type - 1].name));
STRNCPY_UTF8(rule->name, DATA_(rna_enum_boidrule_type_items[type - 1].name));
return rule;
}

View File

@ -1944,6 +1944,49 @@ void BKE_main_collections_parent_relations_rebuild(Main *bmain)
}
}
bool BKE_collection_validate(struct Collection *collection)
{
if (!BLI_listbase_validate(&collection->children)) {
return false;
}
if (!BLI_listbase_validate(&collection->runtime.parents)) {
return false;
}
if (BKE_collection_cycle_find(collection, NULL)) {
return false;
}
bool is_ok = true;
/* Check that children have each collection used/referenced only once. */
GSet *processed_collections = BLI_gset_ptr_new(__func__);
for (CollectionChild *child = collection->children.first; child; child = child->next) {
void **r_key;
if (BLI_gset_ensure_p_ex(processed_collections, child->collection, &r_key)) {
is_ok = false;
}
else {
*r_key = child->collection;
}
}
/* Check that parents have each collection used/referenced only once. */
BLI_gset_clear(processed_collections, NULL);
for (CollectionParent *parent = collection->runtime.parents.first; parent; parent = parent->next)
{
void **r_key;
if (BLI_gset_ensure_p_ex(processed_collections, parent->collection, &r_key)) {
is_ok = false;
}
else {
*r_key = parent->collection;
}
}
BLI_gset_free(processed_collections, NULL);
return is_ok;
}
/** \} */
/* -------------------------------------------------------------------- */

View File

@ -219,14 +219,14 @@ float BKE_cryptomatte_hash_to_float(uint32_t cryptomatte_hash)
bool BKE_cryptomatte_find_name(const CryptomatteSession *session,
const float encoded_hash,
char *r_name,
int name_len)
int name_maxncpy)
{
std::optional<std::string> name = (*session)[encoded_hash];
if (!name) {
return false;
}
BLI_strncpy(r_name, name->c_str(), name_len);
BLI_strncpy(r_name, name->c_str(), name_maxncpy);
return true;
}
@ -580,7 +580,7 @@ blender::StringRef CryptomatteStampDataCallbackData::extract_layer_hash(blender:
void CryptomatteStampDataCallbackData::extract_layer_names(void *_data,
const char *propname,
char *propvalue,
int /*len*/)
int /*propvalue_maxncpy*/)
{
CryptomatteStampDataCallbackData *data = static_cast<CryptomatteStampDataCallbackData *>(_data);
@ -598,7 +598,7 @@ void CryptomatteStampDataCallbackData::extract_layer_names(void *_data,
void CryptomatteStampDataCallbackData::extract_layer_manifest(void *_data,
const char *propname,
char *propvalue,
int /*len*/)
int /*propvalue_maxncpy*/)
{
CryptomatteStampDataCallbackData *data = static_cast<CryptomatteStampDataCallbackData *>(_data);

View File

@ -91,7 +91,7 @@ TEST(cryptomatte, extract_layer_hash_from_metadata_key)
static void validate_cryptomatte_session_from_stamp_data(void * /*data*/,
const char *propname,
char *propvalue,
int /*len*/)
int /*propvalue_maxncpy*/)
{
blender::StringRefNull prop_name(propname);
if (!prop_name.startswith("cryptomatte/")) {

View File

@ -1124,14 +1124,6 @@ static void copy_construct_data(const GSpan src, GMutableSpan dst)
src.type().copy_construct_n(src.data(), dst.data(), src.size());
}
static void copy_with_map(const GSpan src, const Span<int> map, GMutableSpan dst)
{
attribute_math::convert_to_static_type(src.type(), [&](auto dummy) {
using T = decltype(dummy);
array_utils::gather(src.typed<T>(), map, dst.typed<T>());
});
}
static CurvesGeometry copy_with_removed_points(
const CurvesGeometry &curves,
const IndexMask points_to_delete,
@ -1216,7 +1208,7 @@ static CurvesGeometry copy_with_removed_points(
attribute.dst.span.copy_from(attribute.src);
}
else {
copy_with_map(attribute.src, new_curve_orig_indices, attribute.dst.span);
bke::attribute_math::gather(attribute.src, new_curve_orig_indices, attribute.dst.span);
}
}
});

View File

@ -30,6 +30,7 @@
#include "BLI_span.hh"
#include "BLI_string.h"
#include "BLI_string_ref.hh"
#include "BLI_string_utf8.h"
#include "BLI_string_utils.h"
#include "BLI_utildefines.h"
@ -4413,7 +4414,7 @@ void CustomData_set_layer_unique_name(CustomData *data, const int index)
/* Set default name if none specified. Note we only call DATA_() when
* needed to avoid overhead of locale lookups in the depsgraph. */
if (nlayer->name[0] == '\0') {
STRNCPY(nlayer->name, DATA_(typeInfo->defaultname));
STRNCPY_UTF8(nlayer->name, DATA_(typeInfo->defaultname));
}
const char *defname = ""; /* Dummy argument, never used as `name` is never zero length. */
@ -4437,10 +4438,10 @@ void CustomData_validate_layer_name(const CustomData *data,
* deleted, so assign the active layer to name
*/
index = CustomData_get_active_layer_index(data, type);
BLI_strncpy(outname, data->layers[index].name, MAX_CUSTOMDATA_LAYER_NAME);
BLI_strncpy_utf8(outname, data->layers[index].name, MAX_CUSTOMDATA_LAYER_NAME);
}
else {
BLI_strncpy(outname, name, MAX_CUSTOMDATA_LAYER_NAME);
BLI_strncpy_utf8(outname, name, MAX_CUSTOMDATA_LAYER_NAME);
}
}

View File

@ -405,7 +405,7 @@ static bool surface_duplicateNameExists(void *arg, const char *name)
void dynamicPaintSurface_setUniqueName(DynamicPaintSurface *surface, const char *basename)
{
char name[64];
STRNCPY(name, basename); /* in case basename is surface->name use a copy */
STRNCPY_UTF8(name, basename); /* in case basename is surface->name use a copy */
BLI_uniquename_cb(
surface_duplicateNameExists, surface, name, '.', surface->name, sizeof(surface->name));
}

View File

@ -16,6 +16,7 @@
#include "BLI_expr_pylike_eval.h"
#include "BLI_listbase.h"
#include "BLI_math.h"
#include "BLI_string_utf8.h"
#include "BLI_string_utils.h"
#include "BLI_threads.h"
#include "BLI_utildefines.h"
@ -955,7 +956,7 @@ DriverVar *driver_add_new_variable(ChannelDriver *driver)
BLI_addtail(&driver->variables, dvar);
/* Give the variable a 'unique' name. */
strcpy(dvar->name, CTX_DATA_(BLT_I18NCONTEXT_ID_ACTION, "var"));
STRNCPY_UTF8(dvar->name, CTX_DATA_(BLT_I18NCONTEXT_ID_ACTION, "var"));
BLI_uniquename(&driver->variables,
dvar,
CTX_DATA_(BLT_I18NCONTEXT_ID_ACTION, "var"),

View File

@ -675,7 +675,7 @@ bGPDlayer *BKE_gpencil_layer_addnew(bGPdata *gpd,
}
/* auto-name */
STRNCPY(gpl->info, DATA_(name));
STRNCPY_UTF8(gpl->info, DATA_(name));
BLI_uniquename(&gpd->layers,
gpl,
(gpd->flag & GP_DATA_ANNOTATIONS) ? DATA_("Note") : DATA_("GP_Layer"),

View File

@ -356,7 +356,7 @@ GpencilModifierData *BKE_gpencil_modifier_new(int type)
GpencilModifierData *md = MEM_callocN(mti->struct_size, mti->struct_name);
/* NOTE: this name must be made unique later. */
STRNCPY(md->name, DATA_(mti->name));
STRNCPY_UTF8(md->name, DATA_(mti->name));
md->type = type;
md->mode = eGpencilModifierMode_Realtime | eGpencilModifierMode_Render;

View File

@ -2486,18 +2486,24 @@ void BKE_stamp_data_free(StampData *stamp_data)
}
/* wrap for callback only */
static void metadata_set_field(void *data, const char *propname, char *propvalue, int /*len*/)
static void metadata_set_field(void *data,
const char *propname,
char *propvalue,
int /*propvalue_maxncpy*/)
{
/* We know it is an ImBuf* because that's what we pass to BKE_stamp_info_callback. */
ImBuf *imbuf = static_cast<ImBuf *>(data);
IMB_metadata_set_field(imbuf->metadata, propname, propvalue);
}
static void metadata_get_field(void *data, const char *propname, char *propvalue, int len)
static void metadata_get_field(void *data,
const char *propname,
char *propvalue,
int propvalue_maxncpy)
{
/* We know it is an ImBuf* because that's what we pass to BKE_stamp_info_callback. */
ImBuf *imbuf = static_cast<ImBuf *>(data);
IMB_metadata_get_field(imbuf->metadata, propname, propvalue, len);
IMB_metadata_get_field(imbuf->metadata, propname, propvalue, propvalue_maxncpy);
}
void BKE_imbuf_stamp_info(const RenderResult *rr, ImBuf *ibuf)

View File

@ -1845,7 +1845,7 @@ KeyBlock *BKE_keyblock_add(Key *key, const char *name)
}
else {
if (tot == 1) {
STRNCPY(kb->name, DATA_("Basis"));
STRNCPY_UTF8(kb->name, DATA_("Basis"));
}
else {
SNPRINTF(kb->name, DATA_("Key %d"), tot - 1);

View File

@ -787,7 +787,7 @@ void BKE_layer_collection_resync_allow(void)
}
struct LayerCollectionResync {
LayerCollectionResync *prev, *next;
LayerCollectionResync *next, *prev;
/* Temp data used to generate a queue during valid layer search. See
* #layer_collection_resync_find. */
@ -1006,7 +1006,7 @@ void BKE_main_view_layers_synced_ensure(const Main *bmain)
BKE_scene_view_layers_synced_ensure(scene);
}
/* NOTE: This is not (yet?) covered by the dirty tag and differed re-sync system */
/* NOTE: This is not (yet?) covered by the dirty tag and deffered re-sync system. */
BKE_layer_collection_local_sync_all(bmain);
}
@ -2522,7 +2522,7 @@ ViewLayerAOV *BKE_view_layer_add_aov(ViewLayer *view_layer)
ViewLayerAOV *aov;
aov = MEM_cnew<ViewLayerAOV>(__func__);
aov->type = AOV_TYPE_COLOR;
STRNCPY(aov->name, DATA_("AOV"));
STRNCPY_UTF8(aov->name, DATA_("AOV"));
BLI_addtail(&view_layer->aovs, aov);
viewlayer_aov_active_set(view_layer, aov);
viewlayer_aov_make_name_unique(view_layer);
@ -2642,12 +2642,7 @@ ViewLayerLightgroup *BKE_view_layer_add_lightgroup(ViewLayer *view_layer, const
{
ViewLayerLightgroup *lightgroup;
lightgroup = MEM_cnew<ViewLayerLightgroup>(__func__);
if (name && name[0]) {
STRNCPY(lightgroup->name, name);
}
else {
STRNCPY(lightgroup->name, DATA_("Lightgroup"));
}
STRNCPY_UTF8(lightgroup->name, (name && name[0]) ? name : DATA_("Lightgroup"));
BLI_addtail(&view_layer->lightgroups, lightgroup);
viewlayer_lightgroup_active_set(view_layer, lightgroup);
viewlayer_lightgroup_make_name_unique(view_layer, lightgroup);

View File

@ -1603,7 +1603,7 @@ bool BKE_id_new_name_validate(
if (name[0] == '\0') {
/* Disallow empty names. */
STRNCPY(name, DATA_(BKE_idtype_idcode_to_name(GS(id->name))));
STRNCPY_UTF8(name, DATA_(BKE_idtype_idcode_to_name(GS(id->name))));
}
else {
/* disallow non utf8 chars,
@ -1666,6 +1666,10 @@ void BKE_main_id_refcount_recompute(struct Main *bmain, const bool do_linked_onl
id->tag &= ~(LIB_TAG_EXTRAUSER | LIB_TAG_EXTRAUSER_SET);
id_us_ensure_real(id);
}
if (ELEM(GS(id->name), ID_SCE, ID_WM, ID_WS)) {
/* These IDs should always have a 'virtual' user. */
id_us_ensure_real(id);
}
}
FOREACH_MAIN_ID_END;

View File

@ -404,6 +404,11 @@ uint64_t BKE_library_id_can_use_filter_id(const ID *id_owner, const bool include
return FILTER_ID_ALL;
}
if (ID_IS_OVERRIDE_LIBRARY_REAL(id_owner)) {
/* LibOverride data 'hierarchy root' can virtually point back to any type of ID. */
return FILTER_ID_ALL;
}
switch ((ID_Type)id_type_owner) {
case ID_LI:
return FILTER_ID_LI;

View File

@ -37,6 +37,7 @@
#include "BLI_path_util.h"
#include "BLI_session_uuid.h"
#include "BLI_string.h"
#include "BLI_string_utf8.h"
#include "BLI_string_utils.h"
#include "BLI_utildefines.h"
@ -137,7 +138,7 @@ static ModifierData *modifier_allocate_and_init(ModifierType type)
ModifierData *md = static_cast<ModifierData *>(MEM_callocN(mti->structSize, mti->structName));
/* NOTE: this name must be made unique later. */
STRNCPY(md->name, DATA_(mti->name));
STRNCPY_UTF8(md->name, DATA_(mti->name));
md->type = type;
md->mode = eModifierMode_Realtime | eModifierMode_Render;

View File

@ -1521,8 +1521,10 @@ void multires_ensure_external_read(struct Mesh *mesh, int top_level)
return;
}
MDisps *mdisps = static_cast<MDisps *>(
CustomData_get_layer_for_write(&mesh->ldata, CD_MDISPS, mesh->totloop));
/* Modify the data array from the original mesh, not the evaluated mesh.
* When multiple objects share the same mesh, this can lead to memory leaks. */
MDisps *mdisps = const_cast<MDisps *>(
static_cast<const MDisps *>(CustomData_get_layer(&mesh->ldata, CD_MDISPS)));
if (mdisps == nullptr) {
mdisps = static_cast<MDisps *>(
CustomData_add_layer(&mesh->ldata, CD_MDISPS, CD_SET_DEFAULT, mesh->totloop));

View File

@ -40,6 +40,7 @@
#include "BLI_set.hh"
#include "BLI_stack.hh"
#include "BLI_string.h"
#include "BLI_string_utf8.h"
#include "BLI_string_utils.h"
#include "BLI_threads.h"
#include "BLI_utildefines.h"
@ -1159,7 +1160,7 @@ static void node_init(const bContext *C, bNodeTree *ntree, bNode *node)
* Data have their own translation option!
* This solution may be a bit rougher than nodeLabel()'s returned string, but it's simpler
* than adding "do_translate" flags to this func (and labelfunc() as well). */
STRNCPY(node->name, DATA_(ntype->ui_name));
STRNCPY_UTF8(node->name, DATA_(ntype->ui_name));
nodeUniqueName(ntree, node);
/* Generally sockets should be added after the initialization, because the set of sockets might

View File

@ -949,12 +949,12 @@ static void object_blend_read_lib(BlendLibReader *reader, ID *id)
if (ob->id.lib) {
BLO_reportf_wrap(reports,
RPT_INFO,
TIP_("Can't find object data of %s lib %s\n"),
TIP_("Can't find object data of %s lib %s"),
ob->id.name + 2,
ob->id.lib->filepath);
}
else {
BLO_reportf_wrap(reports, RPT_INFO, TIP_("Object %s lost data\n"), ob->id.name + 2);
BLO_reportf_wrap(reports, RPT_INFO, TIP_("Object %s lost data"), ob->id.name + 2);
}
reports->count.missing_obdata++;
}

View File

@ -481,9 +481,9 @@ char *BKE_packedfile_unpack_to_file(ReportList *reports,
static void unpack_generate_paths(const char *filepath,
ID *id,
char *r_abspath,
size_t abspath_maxncpy,
char *r_relpath,
size_t abspathlen,
size_t relpathlen)
size_t relpath_maxncpy)
{
const short id_type = GS(id->name);
char temp_filename[FILE_MAX];
@ -545,13 +545,13 @@ static void unpack_generate_paths(const char *filepath,
break;
}
if (dir_name) {
BLI_path_join(r_relpath, relpathlen, "//", dir_name, temp_filename);
BLI_path_join(r_relpath, relpath_maxncpy, "//", dir_name, temp_filename);
}
}
{
size_t len = BLI_strncpy_rlen(r_abspath, temp_dirname, abspathlen);
BLI_strncpy(r_abspath + len, temp_filename, abspathlen - len);
size_t len = BLI_strncpy_rlen(r_abspath, temp_dirname, abspath_maxncpy);
BLI_strncpy(r_abspath + len, temp_filename, abspath_maxncpy - len);
}
}
@ -567,7 +567,7 @@ char *BKE_packedfile_unpack(Main *bmain,
if (id != NULL) {
unpack_generate_paths(
orig_file_path, id, absname, localname, sizeof(absname), sizeof(localname));
orig_file_path, id, absname, sizeof(absname), localname, sizeof(localname));
new_name = BKE_packedfile_unpack_to_file(
reports, BKE_main_blendfile_path(bmain), absname, localname, pf, how);
}

View File

@ -910,7 +910,7 @@ static bool seq_foreach_path_callback(Sequence *seq, void *user_data)
BPathForeachPathData *bpath_data = (BPathForeachPathData *)user_data;
if (ELEM(seq->type, SEQ_TYPE_MOVIE, SEQ_TYPE_SOUND_RAM) && se) {
BKE_bpath_foreach_path_dirfile_fixed_process(bpath_data, seq->strip->dir, se->name);
BKE_bpath_foreach_path_dirfile_fixed_process(bpath_data, seq->strip->dirpath, se->filename);
}
else if ((seq->type == SEQ_TYPE_IMAGE) && se) {
/* NOTE: An option not to loop over all strips could be useful? */
@ -923,12 +923,13 @@ static bool seq_foreach_path_callback(Sequence *seq, void *user_data)
}
for (i = 0; i < len; i++, se++) {
BKE_bpath_foreach_path_dirfile_fixed_process(bpath_data, seq->strip->dir, se->name);
BKE_bpath_foreach_path_dirfile_fixed_process(
bpath_data, seq->strip->dirpath, se->filename);
}
}
else {
/* simple case */
BKE_bpath_foreach_path_fixed_process(bpath_data, seq->strip->dir);
BKE_bpath_foreach_path_fixed_process(bpath_data, seq->strip->dirpath);
}
}
return true;

View File

@ -65,7 +65,7 @@ ShaderFxData *BKE_shaderfx_new(int type)
ShaderFxData *fx = MEM_callocN(fxi->struct_size, fxi->struct_name);
/* NOTE: this name must be made unique later. */
STRNCPY(fx->name, DATA_(fxi->name));
STRNCPY_UTF8(fx->name, DATA_(fxi->name));
fx->type = type;
fx->mode = eShaderFxMode_Realtime | eShaderFxMode_Render;

View File

@ -68,7 +68,7 @@ void ModifierSimulationCache::try_discover_bake(const StringRefNull meta_dir,
if (!dir_entry_path.endswith(".json")) {
continue;
}
char modified_file_name[FILENAME_MAX];
char modified_file_name[FILE_MAX];
STRNCPY(modified_file_name, dir_entry.relname);
BLI_str_replace_char(modified_file_name, '_', '.');

View File

@ -164,13 +164,13 @@ static void filepath_avi(char *filepath, const RenderData *rd, bool preview, con
if (rd->scemode & R_EXTENSION) {
if (!BLI_path_extension_check(filepath, ".avi")) {
BLI_path_frame_range(filepath, sfra, efra, 4);
BLI_path_frame_range(filepath, FILE_MAX, sfra, efra, 4);
BLI_strncat(filepath, ".avi", FILE_MAX);
}
}
else {
if (BLI_path_frame_check_chars(filepath)) {
BLI_path_frame_range(filepath, sfra, efra, 4);
BLI_path_frame_range(filepath, FILE_MAX, sfra, efra, 4);
}
}

View File

@ -1081,7 +1081,7 @@ static void ffmpeg_dict_set_int(AVDictionary **dict, const char *key, int value)
static void ffmpeg_add_metadata_callback(void *data,
const char *propname,
char *propvalue,
int UNUSED(len))
int UNUSED(propvalue_maxncpy))
{
AVDictionary **metadata = (AVDictionary **)data;
av_dict_set(metadata, propname, propvalue, 0);
@ -1406,7 +1406,7 @@ static void ffmpeg_filepath_get(FFMpegContext *context,
if (*fe == NULL) {
BLI_strncat(string, autosplit, FILE_MAX);
BLI_path_frame_range(string, sfra, efra, 4);
BLI_path_frame_range(string, FILE_MAX, sfra, efra, 4);
BLI_strncat(string, *exts, FILE_MAX);
}
else {
@ -1417,7 +1417,7 @@ static void ffmpeg_filepath_get(FFMpegContext *context,
}
else {
if (BLI_path_frame_check_chars(string)) {
BLI_path_frame_range(string, sfra, efra, 4);
BLI_path_frame_range(string, FILE_MAX, sfra, efra, 4);
}
BLI_strncat(string, autosplit, FILE_MAX);

View File

@ -285,6 +285,10 @@ BLI_INLINE void BLI_listbase_clear(struct ListBase *lb)
lb->first = lb->last = (void *)0;
}
/** Validate the integrity of a given ListBase, returns `true` if evrything is OK, false otherwise.
*/
bool BLI_listbase_validate(struct ListBase *lb);
/**
* Equality check for ListBase.
*

View File

@ -15,27 +15,219 @@
extern "C" {
#endif
/* -------------------------------------------------------------------- */
/** \name Path Queries
* \{ */
/**
* Sets the specified environment variable to the specified value,
* and clears it if `val == NULL`.
*/
void BLI_setenv(const char *env, const char *val) ATTR_NONNULL(1);
/**
* Only set an environment variable if already not there.
* Like Unix `setenv(env, val, 0);`
* Get an element of the path at an index, eg:
* `/some/path/file.txt` where an index of:
* - 0 or -3: `some`
* - 1 or -2: `path`
* - 2 or -1: `file.txt`
*
* (not used anywhere).
* Ignored elements in the path:
* - Multiple slashes at any point in the path (including start/end).
* - Single '.' in the path: `/./` except for the beginning of the path
* where it's used to signify a $PWD relative path.
*/
void BLI_setenv_if_new(const char *env, const char *val) ATTR_NONNULL(1);
bool BLI_path_name_at_index(const char *__restrict path,
int index,
int *__restrict r_offset,
int *__restrict r_len) ATTR_NONNULL(1, 3, 4) ATTR_WARN_UNUSED_RESULT;
/**
* Get an environment variable, result has to be used immediately.
*
* On windows #getenv gets its variables from a static copy of the environment variables taken at
* process start-up, causing it to not pick up on environment variables created during runtime.
* This function uses an alternative method to get environment variables that does pick up on
* runtime environment variables. The result will be UTF-8 encoded.
* Return true if the path is a UNC share.
*/
const char *BLI_getenv(const char *env) ATTR_NONNULL(1) ATTR_WARN_UNUSED_RESULT;
bool BLI_path_is_unc(const char *path) ATTR_NONNULL(1) ATTR_WARN_UNUSED_RESULT;
/** \} */
/* -------------------------------------------------------------------- */
/** \name Path Parent Operations
* \{ */
/**
* Go back one directory.
*
* Replaces path with the path of its parent directory, returning true if
* it was able to find a parent directory within the path.
*
* On success, the resulting path will always have a trailing slash.
*/
bool BLI_path_parent_dir(char *path) ATTR_NONNULL(1);
/**
* Go back until the directory is found.
*
* Strips off nonexistent (or non-accessible) sub-directories from the end of `dir`,
* leaving the path of the lowest-level directory that does exist and we can read.
*/
bool BLI_path_parent_dir_until_exists(char *path) ATTR_NONNULL(1);
/**
* In the simple case this is similar to `BLI_path_slash_rfind(dirname)`
* however it behaves differently when there are redundant characters:
*
* `/test///dir/./file`
* ^
* `/test/dir/subdir//file`
* ^
* \return The position after the parent paths last character or NULL on failure.
* Neither `path` or `&path[path_len - 1]` are ever returned.
*/
const char *BLI_path_parent_dir_end(const char *path, size_t path_len)
ATTR_NONNULL(1) ATTR_WARN_UNUSED_RESULT;
/** \} */
/* -------------------------------------------------------------------- */
/** \name Path Make Safe / Sanitize
* \{ */
/**
* Make given name safe to be used in paths.
*
* \param allow_tokens: Permit the usage of '<' and '>' characters. This can be
* leveraged by higher layers to support "virtual filenames" which contain
* substitution markers delineated between the two characters.
*
* \return true if \a fname was changed, false otherwise.
*
* For now, simply replaces reserved chars (as listed in
* https://en.wikipedia.org/wiki/Filename#Reserved_characters_and_words )
* by underscores ('_').
*
* \note Space case ' ' is a bit of an edge case here - in theory it is allowed,
* but again can be an issue in some cases, so we simply replace it by an underscore too
* (good practice anyway).
* REMOVED based on popular demand (see #45900).
* Percent '%' char is a bit same case - not recommended to use it,
* but supported by all decent file-systems/operating-systems around.
*
* \note On Windows, it also ensures there is no '.' (dot char) at the end of the file,
* this can lead to issues.
*
* \note On Windows, it also checks for forbidden names
* (see https://msdn.microsoft.com/en-us/library/windows/desktop/aa365247%28v=vs.85%29.aspx ).
*/
bool BLI_path_make_safe_filename_ex(char *fname, bool allow_tokens) ATTR_NONNULL(1);
bool BLI_path_make_safe_filename(char *fname) ATTR_NONNULL(1);
/**
* Make given path OS-safe.
*
* \return true if \a path was changed, false otherwise.
*/
bool BLI_path_make_safe(char *path) ATTR_NONNULL(1);
/**
* Creates a display string from path to be used menus and the user interface.
* Like `bpy.path.display_name()`.
*/
void BLI_path_to_display_name(char *display_name, int display_name_maxncpy, const char *name)
ATTR_NONNULL(1, 3);
/** \} */
/* -------------------------------------------------------------------- */
/** \name Path Normalize
* \{ */
/**
* Remove redundant characters from \a path.
*
* The following operations are performed:
* - Redundant path components such as `//`, `/./` & `./` (prefix) are stripped.
* (with the exception of `//` prefix used for blend-file relative paths).
* - `..` are resolved so `<parent>/../<child>/` resolves to `<child>/`.
* Note that the resulting path may begin with `..` if it's relative.
*
* Details:
* - The slash direction is expected to be native (see #SEP).
* When calculating a canonical paths you may need to run #BLI_path_slash_native first.
* #BLI_path_cmp_normalized can be used for canonical path comparison.
* - Trailing slashes are left intact (unlike Python which strips them).
* - Handling paths beginning with `..` depends on them being absolute or relative.
* For absolute paths they are removed (e.g. `/../path` becomes `/path`).
* For relative paths they are kept as it's valid to reference paths above a relative location
* such as `//../parent` or `../parent`.
*
* \param path: The path to a file or directory which can be absolute or relative.
*/
void BLI_path_normalize(char *path) ATTR_NONNULL(1);
/**
* Cleanup file-path ensuring a trailing slash.
*
* \note Same as #BLI_path_normalize but adds a trailing slash.
*/
void BLI_path_normalize_dir(char *dir, size_t dir_maxncpy) ATTR_NONNULL(1);
#if defined(WIN32)
void BLI_path_normalize_unc_16(wchar_t *path_16);
void BLI_path_normalize_unc(char *path, int path_maxncpy);
#endif
/** \} */
/* -------------------------------------------------------------------- */
/** \name Path FileName Manipulation
* \{ */
/**
* Ensure `filepath` has a file component, adding `filename` when it's empty or ends with a slash.
* \return true if the `filename` was appended to `filepath`.
*/
bool BLI_path_filename_ensure(char *filepath, size_t filepath_maxncpy, const char *filename)
ATTR_NONNULL(1, 3);
/**
* Appends a suffix to the `path`, fitting it before the extension
*
* path = `Foo.png`, suffix = `123`, separator = `_`.
* `Foo.png` -> `Foo_123.png`.
*
* \param path: original (and final) string.
* \param path_maxncpy: Maximum length of path.
* \param suffix: String to append to the original path.
* \param sep: Optional separator character.
* \return true if succeeded.
*/
bool BLI_path_suffix(char *path, size_t path_maxncpy, const char *suffix, const char *sep)
ATTR_NONNULL(1, 3, 4);
/** \} */
/* -------------------------------------------------------------------- */
/** \name Path Slash Utilities
* \{ */
/**
* \return pointer to the leftmost path separator in path (or NULL when not found).
*/
const char *BLI_path_slash_find(const char *path) ATTR_NONNULL(1) ATTR_WARN_UNUSED_RESULT;
/**
* \return pointer to the rightmost path separator in path (or NULL when not found).
*/
const char *BLI_path_slash_rfind(const char *path) ATTR_NONNULL(1) ATTR_WARN_UNUSED_RESULT;
/**
* Appends a slash to path if there isn't one there already.
* Returns the new length of the path.
*/
int BLI_path_slash_ensure(char *path, size_t path_maxncpy) ATTR_NONNULL(1);
/**
* Removes the last slash and everything after it to the end of path, if there is one.
*/
void BLI_path_slash_rstrip(char *path) ATTR_NONNULL(1);
/**
* Changes to the path separators to the native ones for this OS.
*/
void BLI_path_slash_native(char *path) ATTR_NONNULL(1);
/** \} */
/* -------------------------------------------------------------------- */
/** \name Path Directory/FileName Split
* \{ */
/**
* Copies directory and file components from `filepath` into `dir` and `file`, e.g.
@ -58,17 +250,20 @@ void BLI_path_split_dir_part(const char *filepath, char *dir, size_t dir_maxncpy
*/
void BLI_path_split_file_part(const char *filepath, char *file, size_t file_maxncpy)
ATTR_NONNULL(1, 2);
/**
* Returns a pointer to the last extension (e.g. the position of the last period).
* Returns a pointer to the nil byte when no extension is found.
* Like Python's `os.path.basename()`
*
* \return The pointer into \a path string immediately after last slash,
* or start of \a path if none found.
*/
const char *BLI_path_extension_or_end(const char *filepath)
ATTR_NONNULL(1) ATTR_WARN_UNUSED_RESULT ATTR_RETURNS_NONNULL;
/**
* Returns a pointer to the last extension (e.g. the position of the last period).
* Returns NULL if there is no extension.
*/
const char *BLI_path_extension(const char *filepath) ATTR_NONNULL(1) ATTR_WARN_UNUSED_RESULT;
const char *BLI_path_basename(const char *path) ATTR_NONNULL(1) ATTR_WARN_UNUSED_RESULT;
/** \} */
/* -------------------------------------------------------------------- */
/** \name Path Append
* \{ */
/**
* Append a filename to a dir, ensuring slash separates.
@ -83,6 +278,12 @@ size_t BLI_path_append(char *__restrict dst, size_t dst_maxncpy, const char *__r
size_t BLI_path_append_dir(char *__restrict dst, size_t dst_maxncpy, const char *__restrict dir)
ATTR_NONNULL(1, 3);
/** \} */
/* -------------------------------------------------------------------- */
/** \name Path Join
* \{ */
/**
* See #BLI_path_join doc-string.
*/
@ -192,65 +393,23 @@ BLI_INLINE size_t _BLI_path_join_12(_BLI_PATH_JOIN_ARGS_10)
#undef _BLI_PATH_JOIN_ARGS_9
#undef _BLI_PATH_JOIN_ARGS_10
/**
* Like Python's `os.path.basename()`
*
* \return The pointer into \a path string immediately after last slash,
* or start of \a path if none found.
*/
const char *BLI_path_basename(const char *path) ATTR_NONNULL(1) ATTR_WARN_UNUSED_RESULT;
/**
* Get an element of the path at an index, eg:
* `/some/path/file.txt` where an index of:
* - 0 or -3: `some`
* - 1 or -2: `path`
* - 2 or -1: `file.txt`
*
* Ignored elements in the path:
* - Multiple slashes at any point in the path (including start/end).
* - Single '.' in the path: `/./` except for the beginning of the path
* where it's used to signify a $PWD relative path.
*/
bool BLI_path_name_at_index(const char *__restrict path,
int index,
int *__restrict r_offset,
int *__restrict r_len) ATTR_NONNULL(1, 3, 4) ATTR_WARN_UNUSED_RESULT;
/** \} */
/** Return true only if #containee_path is contained in #container_path. */
bool BLI_path_contains(const char *container_path, const char *containee_path)
ATTR_NONNULL(1, 2) ATTR_WARN_UNUSED_RESULT;
/* -------------------------------------------------------------------- */
/** \name Path File Extensions
* \{ */
/**
* \return pointer to the leftmost path separator in path (or NULL when not found).
* Returns a pointer to the last extension (e.g. the position of the last period).
* Returns a pointer to the nil byte when no extension is found.
*/
const char *BLI_path_slash_find(const char *path) ATTR_NONNULL(1) ATTR_WARN_UNUSED_RESULT;
const char *BLI_path_extension_or_end(const char *filepath)
ATTR_NONNULL(1) ATTR_WARN_UNUSED_RESULT ATTR_RETURNS_NONNULL;
/**
* \return pointer to the rightmost path separator in path (or NULL when not found).
* Returns a pointer to the last extension (e.g. the position of the last period).
* Returns NULL if there is no extension.
*/
const char *BLI_path_slash_rfind(const char *path) ATTR_NONNULL(1) ATTR_WARN_UNUSED_RESULT;
/**
* Appends a slash to path if there isn't one there already.
* Returns the new length of the path.
*/
int BLI_path_slash_ensure(char *path, size_t path_maxncpy) ATTR_NONNULL(1);
/**
* Removes the last slash and everything after it to the end of path, if there is one.
*/
void BLI_path_slash_rstrip(char *path) ATTR_NONNULL(1);
/**
* Changes to the path separators to the native ones for this OS.
*/
void BLI_path_slash_native(char *path) ATTR_NONNULL(1);
#ifdef _WIN32
bool BLI_path_program_extensions_add_win32(char *program_name, size_t program_name_maxncpy);
#endif
/**
* Search for a binary (executable)
*/
bool BLI_path_program_search(char *program_filepath,
size_t program_filepath_maxncpy,
const char *program_name) ATTR_NONNULL(1, 3);
const char *BLI_path_extension(const char *filepath) ATTR_NONNULL(1) ATTR_WARN_UNUSED_RESULT;
/**
* \return true when `path` end with `ext` (case insensitive).
@ -295,230 +454,12 @@ bool BLI_path_extension_strip(char *path) ATTR_NONNULL(1);
*/
bool BLI_path_extension_ensure(char *path, size_t path_maxncpy, const char *ext)
ATTR_NONNULL(1, 3);
/**
* Ensure `filepath` has a file component, adding `filename` when it's empty or ends with a slash.
* \return true if the `filename` was appended to `filepath`.
*/
bool BLI_path_filename_ensure(char *filepath, size_t filepath_maxncpy, const char *filename)
ATTR_NONNULL(1, 3);
/**
* Looks for a sequence of decimal digits in `path`, preceding any filename extension,
* returning the integer value if found, or 0 if not.
*
* \param path: String to scan.
* \param head: Optional area to return copy of part of `path` prior to digits,
* or before dot if no digits.
* \param tail: Optional area to return copy of part of `path` following digits,
* or from dot if no digits.
* \param r_digits_len: Optional to return number of digits found.
*/
int BLI_path_sequence_decode(const char *path,
char *head,
size_t head_maxncpy,
char *tail,
size_t tail_maxncpy,
unsigned short *r_digits_len);
/**
* Returns in area pointed to by `path` a string of the form `<head><pic><tail>`,
* where pic is formatted as `numlen` digits with leading zeroes.
*/
void BLI_path_sequence_encode(char *path,
size_t path_maxncpy,
const char *head,
const char *tail,
unsigned short numlen,
int pic);
/**
* Remove redundant characters from \a path.
*
* The following operations are performed:
* - Redundant path components such as `//`, `/./` & `./` (prefix) are stripped.
* (with the exception of `//` prefix used for blend-file relative paths).
* - `..` are resolved so `<parent>/../<child>/` resolves to `<child>/`.
* Note that the resulting path may begin with `..` if it's relative.
*
* Details:
* - The slash direction is expected to be native (see #SEP).
* When calculating a canonical paths you may need to run #BLI_path_slash_native first.
* #BLI_path_cmp_normalized can be used for canonical path comparison.
* - Trailing slashes are left intact (unlike Python which strips them).
* - Handling paths beginning with `..` depends on them being absolute or relative.
* For absolute paths they are removed (e.g. `/../path` becomes `/path`).
* For relative paths they are kept as it's valid to reference paths above a relative location
* such as `//../parent` or `../parent`.
*
* \param path: The path to a file or directory which can be absolute or relative.
*/
void BLI_path_normalize(char *path) ATTR_NONNULL(1);
/**
* Cleanup file-path ensuring a trailing slash.
*
* \note Same as #BLI_path_normalize but adds a trailing slash.
*/
void BLI_path_normalize_dir(char *dir, size_t dir_maxncpy) ATTR_NONNULL(1);
/** \} */
/**
* Make given name safe to be used in paths.
*
* \param allow_tokens: Permit the usage of '<' and '>' characters. This can be
* leveraged by higher layers to support "virtual filenames" which contain
* substitution markers delineated between the two characters.
*
* \return true if \a fname was changed, false otherwise.
*
* For now, simply replaces reserved chars (as listed in
* https://en.wikipedia.org/wiki/Filename#Reserved_characters_and_words )
* by underscores ('_').
*
* \note Space case ' ' is a bit of an edge case here - in theory it is allowed,
* but again can be an issue in some cases, so we simply replace it by an underscore too
* (good practice anyway).
* REMOVED based on popular demand (see #45900).
* Percent '%' char is a bit same case - not recommended to use it,
* but supported by all decent file-systems/operating-systems around.
*
* \note On Windows, it also ensures there is no '.' (dot char) at the end of the file,
* this can lead to issues.
*
* \note On Windows, it also checks for forbidden names
* (see https://msdn.microsoft.com/en-us/library/windows/desktop/aa365247%28v=vs.85%29.aspx ).
*/
bool BLI_path_make_safe_filename_ex(char *fname, bool allow_tokens) ATTR_NONNULL(1);
bool BLI_path_make_safe_filename(char *fname) ATTR_NONNULL(1);
/**
* Make given path OS-safe.
*
* \return true if \a path was changed, false otherwise.
*/
bool BLI_path_make_safe(char *path) ATTR_NONNULL(1);
/**
* Go back one directory.
*
* Replaces path with the path of its parent directory, returning true if
* it was able to find a parent directory within the path.
*
* On success, the resulting path will always have a trailing slash.
*/
bool BLI_path_parent_dir(char *path) ATTR_NONNULL(1);
/**
* Go back until the directory is found.
*
* Strips off nonexistent (or non-accessible) sub-directories from the end of `dir`,
* leaving the path of the lowest-level directory that does exist and we can read.
*/
bool BLI_path_parent_dir_until_exists(char *path) ATTR_NONNULL(1);
/**
* In the simple case this is similar to `BLI_path_slash_rfind(dirname)`
* however it behaves differently when there are redundant characters:
*
* `/test///dir/./file`
* ^
* `/test/dir/subdir//file`
* ^
* \return The position after the parent paths last character or NULL on failure.
* Neither `path` or `&path[path_len - 1]` are ever returned.
*/
const char *BLI_path_parent_dir_end(const char *path, size_t path_len)
ATTR_NONNULL(1) ATTR_WARN_UNUSED_RESULT;
/**
* If path begins with "//", strips that and replaces it with `basepath` directory.
*
* \note Also converts drive-letter prefix to something more sensible
* if this is a non-drive-letter-based system.
*
* \param path: The path to convert.
* \param basepath: The directory to base relative paths with.
* \return true if the path was relative (started with "//").
*/
bool BLI_path_abs(char *path, const char *basepath) ATTR_NONNULL(1, 2);
/**
* Replaces "#" character sequence in last slash-separated component of `path`
* with frame as decimal integer, with leading zeroes as necessary, to make digits.
*/
bool BLI_path_frame(char *path, size_t path_maxncpy, int frame, int digits) ATTR_NONNULL(1);
/**
* Replaces "#" character sequence in last slash-separated component of `path`
* with sta and end as decimal integers, with leading zeroes as necessary, to make digits
* digits each, with a hyphen in-between.
*/
bool BLI_path_frame_range(char *path, int sta, int end, int digits) ATTR_NONNULL(1);
/**
* Get the frame from a filename formatted by blender's frame scheme
*/
bool BLI_path_frame_get(const char *path, int *r_frame, int *r_digits_len) ATTR_NONNULL(1, 2, 3);
/**
* Given a `path` with digits representing frame numbers, replace the digits with the '#'
* character and extract the extension.
* So: `/some/path_123.jpeg`
* Becomes: `/some/path_###` with `r_ext` set to `.jpeg`.
*/
void BLI_path_frame_strip(char *path, char *r_ext, size_t ext_maxncpy) ATTR_NONNULL(1, 2);
/**
* Check if we have '#' chars, usable for #BLI_path_frame, #BLI_path_frame_range
*/
bool BLI_path_frame_check_chars(const char *path) ATTR_NONNULL(1) ATTR_WARN_UNUSED_RESULT;
/**
* Checks for a relative path (ignoring Blender's "//") prefix
* (unlike `!BLI_path_is_rel(path)`).
* When false, #BLI_path_abs_from_cwd would expand the absolute path.
*/
bool BLI_path_is_abs_from_cwd(const char *path) ATTR_NONNULL(1) ATTR_WARN_UNUSED_RESULT;
/**
* Checks for relative path, expanding them relative to the current working directory.
* \returns true if the expansion was performed.
*
* \note Should only be called with command line paths.
* This is _not_ something Blender's internal paths support, instead they use the "//" prefix.
* In most cases #BLI_path_abs should be used instead.
*/
bool BLI_path_abs_from_cwd(char *path, size_t path_maxncpy) ATTR_NONNULL(1);
/**
* Replaces `file` with a relative version (prefixed by "//") such that #BLI_path_abs, given
* the same `basename`, will convert it back to its original value.
*/
void BLI_path_rel(char *path, const char *basename) ATTR_NONNULL(1);
/**
* Does path begin with the special "//" prefix that Blender uses to indicate
* a path relative to the .blend file.
*/
bool BLI_path_is_rel(const char *path) ATTR_NONNULL(1) ATTR_WARN_UNUSED_RESULT;
/**
* Return true if the path is a UNC share.
*/
bool BLI_path_is_unc(const char *path) ATTR_NONNULL(1) ATTR_WARN_UNUSED_RESULT;
/**
* Creates a display string from path to be used menus and the user interface.
* Like `bpy.path.display_name()`.
*/
void BLI_path_to_display_name(char *display_name, int display_name_maxncpy, const char *name)
ATTR_NONNULL(1, 3);
#if defined(WIN32)
void BLI_path_normalize_unc_16(wchar_t *path_16);
void BLI_path_normalize_unc(char *path, int path_maxncpy);
#endif
/**
* Appends a suffix to the `path`, fitting it before the extension
*
* path = `Foo.png`, suffix = `123`, separator = `_`.
* `Foo.png` -> `Foo_123.png`.
*
* \param path: original (and final) string.
* \param path_maxncpy: Maximum length of path.
* \param suffix: String to append to the original path.
* \param sep: Optional separator character.
* \return true if succeeded.
*/
bool BLI_path_suffix(char *path, size_t path_maxncpy, const char *suffix, const char *sep)
ATTR_NONNULL(1, 3, 4);
/* -------------------------------------------------------------------- */
/** \name Path Comparison / Contains
* \{ */
/* Path string comparisons: case-insensitive for Windows, case-sensitive otherwise. */
#if defined(WIN32)
@ -543,14 +484,172 @@ bool BLI_path_suffix(char *path, size_t path_maxncpy, const char *suffix, const
int BLI_path_cmp_normalized(const char *p1, const char *p2)
ATTR_NONNULL(1, 2) ATTR_WARN_UNUSED_RESULT;
/* These values need to be hard-coded in structs, dna does not recognize defines */
/* also defined in `DNA_space_types.h`. */
/** Return true only if #containee_path is contained in #container_path. */
bool BLI_path_contains(const char *container_path, const char *containee_path)
ATTR_NONNULL(1, 2) ATTR_WARN_UNUSED_RESULT;
/** \} */
/* -------------------------------------------------------------------- */
/** \name Program Specific Path Functions
* \{ */
#ifdef _WIN32
bool BLI_path_program_extensions_add_win32(char *program_name, size_t program_name_maxncpy);
#endif
/**
* Search for a binary (executable)
*/
bool BLI_path_program_search(char *program_filepath,
size_t program_filepath_maxncpy,
const char *program_name) ATTR_NONNULL(1, 3);
/** \} */
/* -------------------------------------------------------------------- */
/** \name Blender Specific Frame Sequence Encode/Decode
* \{ */
/**
* Returns in area pointed to by `path` a string of the form `<head><pic><tail>`,
* where pic is formatted as `numlen` digits with leading zeroes.
*/
void BLI_path_sequence_encode(char *path,
size_t path_maxncpy,
const char *head,
const char *tail,
unsigned short numlen,
int pic);
/**
* Looks for a sequence of decimal digits in `path`, preceding any filename extension,
* returning the integer value if found, or 0 if not.
*
* \param path: String to scan.
* \param head: Optional area to return copy of part of `path` prior to digits,
* or before dot if no digits.
* \param tail: Optional area to return copy of part of `path` following digits,
* or from dot if no digits.
* \param r_digits_len: Optional to return number of digits found.
*/
int BLI_path_sequence_decode(const char *path,
char *head,
size_t head_maxncpy,
char *tail,
size_t tail_maxncpy,
unsigned short *r_digits_len);
/** \} */
/* -------------------------------------------------------------------- */
/** \name Blender Specific Frame Number Apply/Strip
* \{ */
/**
* Replaces "#" character sequence in last slash-separated component of `path`
* with frame as decimal integer, with leading zeroes as necessary, to make digits.
*/
bool BLI_path_frame(char *path, size_t path_maxncpy, int frame, int digits) ATTR_NONNULL(1);
/**
* Replaces "#" character sequence in last slash-separated component of `path`
* with sta and end as decimal integers, with leading zeroes as necessary, to make digits
* digits each, with a hyphen in-between.
*/
bool BLI_path_frame_range(char *path, size_t path_maxncpy, int sta, int end, int digits)
ATTR_NONNULL(1);
/**
* Get the frame from a filename formatted by blender's frame scheme
*/
bool BLI_path_frame_get(const char *path, int *r_frame, int *r_digits_len) ATTR_NONNULL(1, 2, 3);
/**
* Given a `path` with digits representing frame numbers, replace the digits with the '#'
* character and extract the extension.
* So: `/some/path_123.jpeg`
* Becomes: `/some/path_###` with `r_ext` set to `.jpeg`.
*/
void BLI_path_frame_strip(char *path, char *r_ext, size_t ext_maxncpy) ATTR_NONNULL(1, 2);
/**
* Check if we have '#' chars, usable for #BLI_path_frame, #BLI_path_frame_range
*/
bool BLI_path_frame_check_chars(const char *path) ATTR_NONNULL(1) ATTR_WARN_UNUSED_RESULT;
/** \} */
/* -------------------------------------------------------------------- */
/** \name Blender Spesific File Relative Paths
* \{ */
/**
* These values need to be hard-coded in structs, dna does not recognize defines
* (also defined in `DNA_space_types.h`).
*
* \note In general path functions should *not* depend on these hard coded limits,
* there is an exception for:
* - #BLI_path_abs
* - #BLI_path_rel
* These functions deal specifically with `.blend` file paths,
* where #FILE_MAX assumed to be the limit of all paths passes into these functions.
*
* Some parts of the API which use #FILE_MAX which aren't specifically handling blend file paths,
* in most cases these can be updated to use #PATH_MAX or a platform specific limit.
*/
#ifndef FILE_MAXDIR
# define FILE_MAXDIR 768
# define FILE_MAXFILE 256
# define FILE_MAX 1024
#endif
/**
* If path begins with "//", strips that and replaces it with `basepath` directory.
*
* \note Also converts drive-letter prefix to something more sensible
* if this is a non-drive-letter-based system.
*
* \param path: The path to convert.
* \param basepath: The directory to base relative paths with.
* \return true if the path was relative (started with "//").
*/
bool BLI_path_abs(char path[FILE_MAX], const char *basepath) ATTR_NONNULL(1, 2);
/**
* Replaces `path` with a relative version (prefixed by "//") such that #BLI_path_abs, given
* the same `basepath`, will convert it back to its original value.
*/
void BLI_path_rel(char path[FILE_MAX], const char *basepath) ATTR_NONNULL(1);
/**
* Does path begin with the special "//" prefix that Blender uses to indicate
* a path relative to the .blend file.
*/
bool BLI_path_is_rel(const char *path) ATTR_NONNULL(1) ATTR_WARN_UNUSED_RESULT;
/** \} */
/* -------------------------------------------------------------------- */
/** \name Current Working Directory Specific Paths
* \{ */
/**
* Checks for a relative path (ignoring Blender's "//") prefix
* (unlike `!BLI_path_is_rel(path)`).
* When false, #BLI_path_abs_from_cwd would expand the absolute path.
*/
bool BLI_path_is_abs_from_cwd(const char *path) ATTR_NONNULL(1) ATTR_WARN_UNUSED_RESULT;
/**
* Checks for relative path, expanding them relative to the current working directory.
* \returns true if the expansion was performed.
*
* \note Should only be called with command line paths.
* This is _not_ something Blender's internal paths support, instead they use the "//" prefix.
* In most cases #BLI_path_abs should be used instead.
*/
bool BLI_path_abs_from_cwd(char *path, size_t path_maxncpy) ATTR_NONNULL(1);
/** \} */
/* -------------------------------------------------------------------- */
/** \name Native Slash Defines & Checks
* \{ */
#ifdef WIN32
# define SEP '\\'
# define ALTSEP '/'
@ -581,6 +680,42 @@ BLI_INLINE bool BLI_path_slash_is_native_compat(const char ch)
return false;
}
/** \} */
/* -------------------------------------------------------------------- */
/** \name OS Level Wrappers
*
* TODO: move these to a different module, they are not path functions.
* \{ */
/**
* Sets the specified environment variable to the specified value,
* and clears it if `val == NULL`.
*/
void BLI_setenv(const char *env, const char *val) ATTR_NONNULL(1);
/**
* Only set an environment variable if already not there.
* Like Unix `setenv(env, val, 0);`
*
* (not used anywhere).
*/
void BLI_setenv_if_new(const char *env, const char *val) ATTR_NONNULL(1);
/**
* Get an environment variable, result has to be used immediately.
*
* On windows #getenv gets its variables from a static copy of the environment variables taken at
* process start-up, causing it to not pick up on environment variables created during runtime.
* This function uses an alternative method to get environment variables that does pick up on
* runtime environment variables. The result will be UTF-8 encoded.
*/
const char *BLI_getenv(const char *env) ATTR_NONNULL(1) ATTR_WARN_UNUSED_RESULT;
/** \} */
/* -------------------------------------------------------------------- */
/** \name Current & Parent Directory Defines/Macros
* \{ */
/* Parent and current dir helpers. */
#define FILENAME_PARENT ".."
#define FILENAME_CURRENT "."
@ -591,6 +726,8 @@ BLI_INLINE bool BLI_path_slash_is_native_compat(const char ch)
#define FILENAME_IS_CURRPAR(_n) \
(((_n)[0] == '.') && (((_n)[1] == '\0') || (((_n)[1] == '.') && ((_n)[2] == '\0'))))
/** \} */
#ifdef __cplusplus
}
#endif

View File

@ -191,6 +191,15 @@ bool BLI_str_replace_table_exact(char *string,
const char *replace_table[][2],
int replace_table_len);
/**
* Write `dst` into the range between `src_beg` & `src_end`,
* resize within `string_maxncpy` limits, ensure null terminated.
*
* \return the length of `string`.
*/
size_t BLI_str_replace_range(
char *string, size_t string_maxncpy, int src_beg, int src_end, const char *dst);
/**
* Portable replacement for #snprintf
*/

View File

@ -42,6 +42,26 @@ void BLI_str_cursor_step_utf32(const char32_t *str,
eStrCursorJumpType jump,
bool use_init_step);
/**
* Given a position within a string,
* return the start and end of the closest sequence of delimited characters.
* Typically a word, but can be a sequence of characters (including spaces).
*
* \note When used for word-selection the caller should set the cursor to `r_end` (by convention).
*
* \param str: The string with a cursor position
* \param str_maxlen: The maximum characters to consider
* \param pos: The starting cursor position.
* \param r_start: returned start of word/sequence boundary (0-based)
* \param r_end: returned end of word/sequence boundary (0-based)
*/
void BLI_str_cursor_step_bounds_utf8(
const char *str, const size_t str_maxlen, int pos, int *r_start, int *r_end);
/** A UTF32 version of #BLI_str_cursor_step_bounds_utf8 */
void BLI_str_cursor_step_bounds_utf32(
const char32_t *str, const size_t str_maxlen, int pos, int *r_start, int *r_end);
#ifdef __cplusplus
}
#endif

View File

@ -560,7 +560,7 @@ static bool delete_recursive(const char *dir)
char path[FILE_MAXDIR];
/* dir listing produces dir path without trailing slash... */
BLI_strncpy(path, fl->path, sizeof(path));
STRNCPY(path, fl->path);
BLI_path_slash_ensure(path, sizeof(path));
if (delete_recursive(path)) {
@ -627,7 +627,7 @@ int BLI_path_move(const char *file, const char *to)
* it has to be 'mv filepath filepath' and not
* 'mv filepath destination_directory' */
BLI_strncpy(str, to, sizeof(str));
STRNCPY(str, to);
/* points 'to' to a directory ? */
if (BLI_path_slash_rfind(str) == (str + strlen(str) - 1)) {
if (BLI_path_slash_rfind(file) != NULL) {
@ -658,7 +658,7 @@ int BLI_copy(const char *file, const char *to)
* it has to be 'cp filepath filepath' and not
* 'cp filepath destdir' */
BLI_strncpy(str, to, sizeof(str));
STRNCPY(str, to);
/* points 'to' to a directory ? */
if (BLI_path_slash_rfind(str) == (str + strlen(str) - 1)) {
if (BLI_path_slash_rfind(file) != NULL) {
@ -741,9 +741,9 @@ static void join_dirfile_alloc(char **dst, size_t *alloc_len, const char *dir, c
BLI_path_join(*dst, len + 1, dir, file);
}
static char *strip_last_slash(const char *dir)
static char *strip_last_slash(const char *dirpath)
{
char *result = BLI_strdup(dir);
char *result = BLI_strdup(dirpath);
BLI_path_slash_rstrip(result);
return result;

View File

@ -871,6 +871,47 @@ void BLI_listbase_rotate_last(ListBase *lb, void *vlink)
((Link *)lb->last)->next = nullptr;
}
bool BLI_listbase_validate(ListBase *lb)
{
if (lb->first == nullptr && lb->last == nullptr) {
/* Empty list. */
return true;
}
if (ELEM(nullptr, lb->first, lb->last)) {
/* If one of the pointer is null, but not this other, this is a corrupted listbase. */
return false;
}
/* Walk the list in bot directions to ensure all next & prev pointers are valid and consistent.
*/
for (Link *lb_link = static_cast<Link *>(lb->first); lb_link; lb_link = lb_link->next) {
if (lb_link == lb->first) {
if (lb_link->prev != nullptr) {
return false;
}
}
if (lb_link == lb->last) {
if (lb_link->next != nullptr) {
return false;
}
}
}
for (Link *lb_link = static_cast<Link *>(lb->last); lb_link; lb_link = lb_link->prev) {
if (lb_link == lb->last) {
if (lb_link->next != nullptr) {
return false;
}
}
if (lb_link == lb->first) {
if (lb_link->prev != nullptr) {
return false;
}
}
}
return true;
}
LinkData *BLI_genericNodeN(void *data)
{
LinkData *ld;

View File

@ -45,6 +45,14 @@ static int BLI_path_unc_prefix_len(const char *path);
static bool BLI_path_is_abs_win32(const char *path);
#endif /* WIN32 */
/**
* The maximum number of `#` characters expanded for #BLI_path_frame & #BLI_path_frame_range
* Typically 12 is enough and even 16 is very large.
* Use a much larger value so hitting the upper limit is not an issue.
* Exceeding this limit won't fail either, it will just not insert so many leading zeros.
*/
#define FILENAME_FRAME_CHARS_MAX FILE_MAX
int BLI_path_sequence_decode(const char *path,
char *head,
const size_t head_maxncpy,
@ -89,7 +97,7 @@ int BLI_path_sequence_decode(const char *path,
const long long int ret = strtoll(&(path[nums]), NULL, 10);
if (ret >= INT_MIN && ret <= INT_MAX) {
if (tail) {
strcpy(tail, &path[nume + 1]);
BLI_strncpy(tail, &path[nume + 1], tail_maxncpy);
}
if (head) {
BLI_strncpy(head, path, MIN2(head_maxncpy, nums + 1));
@ -584,7 +592,7 @@ void BLI_path_normalize_unc_16(wchar_t *path_16)
}
#endif
void BLI_path_rel(char *path, const char *basename)
void BLI_path_rel(char path[FILE_MAX], const char *basepath)
{
BLI_string_debug_size_after_nil(path, FILE_MAX);
@ -598,24 +606,24 @@ void BLI_path_rel(char *path, const char *basename)
}
/* Also bail out if relative path is not set. */
if (basename[0] == '\0') {
if (basepath[0] == '\0') {
return;
}
#ifdef WIN32
if (BLI_strnlen(basename, 3) > 2 && !BLI_path_is_abs_win32(basename)) {
if (BLI_strnlen(basepath, 3) > 2 && !BLI_path_is_abs_win32(basepath)) {
char *ptemp;
/* Fix missing volume name in relative base,
* can happen with old `recent-files.txt` files. */
BLI_windows_get_default_root_dir(temp);
ptemp = &temp[2];
if (!ELEM(basename[0], '\\', '/')) {
if (!ELEM(basepath[0], '\\', '/')) {
ptemp++;
}
BLI_strncpy(ptemp, basename, FILE_MAX - 3);
BLI_strncpy(ptemp, basepath, FILE_MAX - 3);
}
else {
BLI_strncpy(temp, basename, FILE_MAX);
BLI_strncpy(temp, basepath, FILE_MAX);
}
if (BLI_strnlen(path, 3) > 2) {
@ -645,7 +653,7 @@ void BLI_path_rel(char *path, const char *basename)
}
}
#else
STRNCPY(temp, basename);
STRNCPY(temp, basepath);
#endif
BLI_str_replace_char(temp + BLI_path_unc_prefix_len(temp), '\\', '/');
@ -819,9 +827,14 @@ bool BLI_path_parent_dir_until_exists(char *dir)
/**
* Looks for a sequence of "#" characters in the last slash-separated component of `path`,
* returning the indexes of the first and one past the last character in the sequence in
* `char_start` and `char_end` respectively. Returns true if such a sequence was found.
* `char_start` and `char_end` respectively.
*
* \param char_start: The first `#` character.
* \param char_end: The last `#` character +1.
*
* \return true if a frame sequence range was found.
*/
static bool stringframe_chars(const char *path, int *char_start, int *char_end)
static bool path_frame_chars_find_range(const char *path, int *char_start, int *char_end)
{
uint ch_sta, ch_end, i;
/* Insert current frame: `file###` -> `file001`. */
@ -885,18 +898,19 @@ bool BLI_path_frame(char *path, size_t path_maxncpy, int frame, int digits)
ensure_digits(path, digits);
}
if (stringframe_chars(path, &ch_sta, &ch_end)) { /* Warning: `ch_end` is the last # +1. */
char tmp[FILE_MAX];
SNPRINTF(tmp, "%.*s%.*d%s", ch_sta, path, ch_end - ch_sta, frame, path + ch_end);
BLI_strncpy(path, tmp, path_maxncpy);
if (path_frame_chars_find_range(path, &ch_sta, &ch_end)) {
char frame_str[FILENAME_FRAME_CHARS_MAX + 1]; /* One for null. */
const int ch_span = MIN2(ch_end - ch_sta, FILENAME_FRAME_CHARS_MAX);
BLI_snprintf(frame_str, sizeof(frame_str), "%.*d", ch_span, frame);
BLI_str_replace_range(path, path_maxncpy, ch_sta, ch_end, frame_str);
return true;
}
return false;
}
bool BLI_path_frame_range(char *path, int sta, int end, int digits)
bool BLI_path_frame_range(char *path, size_t path_maxncpy, int sta, int end, int digits)
{
BLI_string_debug_size_after_nil(path, FILE_MAX);
BLI_string_debug_size_after_nil(path, path_maxncpy);
int ch_sta, ch_end;
@ -904,18 +918,11 @@ bool BLI_path_frame_range(char *path, int sta, int end, int digits)
ensure_digits(path, digits);
}
if (stringframe_chars(path, &ch_sta, &ch_end)) { /* Warning: `ch_end` is the last # +1. */
char tmp[FILE_MAX];
SNPRINTF(tmp,
"%.*s%.*d-%.*d%s",
ch_sta,
path,
ch_end - ch_sta,
sta,
ch_end - ch_sta,
end,
path + ch_end);
BLI_strncpy(path, tmp, FILE_MAX);
if (path_frame_chars_find_range(path, &ch_sta, &ch_end)) {
char frame_str[(FILENAME_FRAME_CHARS_MAX * 2) + 1 + 1]; /* One for null, one for the '-' */
const int ch_span = MIN2(ch_end - ch_sta, FILENAME_FRAME_CHARS_MAX);
BLI_snprintf(frame_str, sizeof(frame_str), "%.*d-%.*d", ch_span, sta, ch_span, end);
BLI_str_replace_range(path, path_maxncpy, ch_sta, ch_end, frame_str);
return true;
}
return false;
@ -981,7 +988,7 @@ void BLI_path_frame_strip(char *path, char *r_ext, const size_t ext_maxncpy)
bool BLI_path_frame_check_chars(const char *path)
{
int ch_sta_dummy, ch_end_dummy;
return stringframe_chars(path, &ch_sta_dummy, &ch_end_dummy);
return path_frame_chars_find_range(path, &ch_sta_dummy, &ch_end_dummy);
}
void BLI_path_to_display_name(char *display_name, int display_name_maxncpy, const char *name)
@ -1023,7 +1030,7 @@ void BLI_path_to_display_name(char *display_name, int display_name_maxncpy, cons
}
}
bool BLI_path_abs(char *path, const char *basepath)
bool BLI_path_abs(char path[FILE_MAX], const char *basepath)
{
BLI_string_debug_size_after_nil(path, FILE_MAX);
@ -1146,10 +1153,10 @@ bool BLI_path_abs_from_cwd(char *path, const size_t path_maxncpy)
BLI_string_debug_size_after_nil(path, path_maxncpy);
if (!BLI_path_is_abs_from_cwd(path)) {
char cwd[FILE_MAX];
char cwd[PATH_MAX];
/* In case the full path to the blend isn't used. */
if (BLI_current_working_dir(cwd, sizeof(cwd))) {
char origpath[FILE_MAX];
char origpath[PATH_MAX];
STRNCPY(origpath, path);
BLI_path_join(path, path_maxncpy, cwd, origpath);
}
@ -1232,7 +1239,7 @@ bool BLI_path_program_search(char *program_filepath,
path = BLI_getenv("PATH");
if (path) {
char filepath_test[FILE_MAX];
char filepath_test[PATH_MAX];
const char *temp;
do {
@ -1484,13 +1491,13 @@ bool BLI_path_extension_ensure(char *path, size_t path_maxncpy, const char *ext)
return true;
}
bool BLI_path_filename_ensure(char *filepath, size_t filepath_maxncpy, const char *file)
bool BLI_path_filename_ensure(char *filepath, size_t filepath_maxncpy, const char *filename)
{
BLI_string_debug_size_after_nil(filepath, filepath_maxncpy);
char *c = (char *)BLI_path_slash_rfind(filepath);
if (!c || ((c - filepath) < filepath_maxncpy - (strlen(file) + 1))) {
strcpy(c ? &c[1] : filepath, file);
char *c = (char *)BLI_path_basename(filepath);
const size_t filename_size = strlen(filename) + 1;
if (filename_size <= filepath_maxncpy - (c - filepath)) {
memcpy(c, filename, filename_size);
return true;
}
return false;
@ -1911,11 +1918,17 @@ int BLI_path_cmp_normalized(const char *p1, const char *p2)
BLI_assert_msg(!BLI_path_is_rel(p1) && !BLI_path_is_rel(p2), "Paths arguments must be absolute");
/* Normalize the paths so we can compare them. */
char norm_p1[FILE_MAX];
char norm_p2[FILE_MAX];
char norm_p1_buf[256];
char norm_p2_buf[256];
STRNCPY(norm_p1, p1);
STRNCPY(norm_p2, p2);
const size_t p1_size = strlen(p1) + 1;
const size_t p2_size = strlen(p2) + 1;
char *norm_p1 = (p1_size <= sizeof(norm_p1_buf)) ? norm_p1_buf : MEM_mallocN(p1_size, __func__);
char *norm_p2 = (p2_size <= sizeof(norm_p2_buf)) ? norm_p2_buf : MEM_mallocN(p2_size, __func__);
memcpy(norm_p1, p1, p1_size);
memcpy(norm_p2, p2, p2_size);
BLI_path_slash_native(norm_p1);
BLI_path_slash_native(norm_p2);
@ -1923,5 +1936,13 @@ int BLI_path_cmp_normalized(const char *p1, const char *p2)
BLI_path_normalize(norm_p1);
BLI_path_normalize(norm_p2);
return BLI_path_cmp(norm_p1, norm_p2);
const int result = BLI_path_cmp(norm_p1, norm_p2);
if (norm_p1 != norm_p1_buf) {
MEM_freeN(norm_p1);
}
if (norm_p2 != norm_p2_buf) {
MEM_freeN(norm_p2);
}
return result;
}

View File

@ -552,6 +552,55 @@ bool BLI_str_replace_table_exact(char *string,
return false;
}
size_t BLI_str_replace_range(
char *string, size_t string_maxncpy, int src_beg, int src_end, const char *dst)
{
int string_len = strlen(string);
BLI_assert(src_beg <= src_end);
BLI_assert(src_end <= string_len);
const int src_len = src_end - src_beg;
int dst_len = strlen(dst);
if (src_len < dst_len) {
/* Grow, first handle special cases. */
/* Special case, the src_end is entirely clipped. */
if (UNLIKELY(string_maxncpy <= src_beg + dst_len)) {
/* There is only room for the destination. */
dst_len = ((int)string_maxncpy - src_beg) - 1;
string_len = src_end;
string[string_len] = '\0';
}
const int ofs = dst_len - src_len;
/* Clip the string when inserting the destination string exceeds `string_maxncpy`. */
if (string_len + ofs >= string_maxncpy) {
string_len = ((int)string_maxncpy - ofs) - 1;
string[string_len] = '\0';
BLI_assert(src_end <= string_len);
}
/* Grow. */
memmove(string + (src_end + ofs), string + src_end, (size_t)(string_len - src_end) + 1);
string_len += ofs;
}
else if (src_len > dst_len) {
/* Shrink. */
const int ofs = src_len - dst_len;
memmove(string + (src_end - ofs), string + src_end, (size_t)(string_len - src_end) + 1);
string_len -= ofs;
}
else { /* Simple case, no resizing. */
BLI_assert(src_len == dst_len);
}
if (dst_len > 0) {
memcpy(string + src_beg, dst, (size_t)dst_len);
}
BLI_assert(string[string_len] == '\0');
return (size_t)string_len;
}
/** \} */
/* -------------------------------------------------------------------- */

View File

@ -17,6 +17,18 @@
# pragma GCC diagnostic error "-Wsign-conversion"
#endif
/**
* The category of character as returned by #cursor_delim_type_unicode.
*
* \note Don't compare with any values besides #STRCUR_DELIM_NONE as cursor motion
* should only delimit on changes, not treat some groups differently.
*
* For range calculation the order prioritizes expansion direction,
* when the cursor is between two different categories, "hug" the smaller values.
* Where white-space gets lowest priority. See #BLI_str_cursor_step_bounds_utf8.
* This is done so expanding the range at a word boundary always chooses the word instead
* of the white-space before or after it.
*/
typedef enum eStrCursorDelimType {
STRCUR_DELIM_NONE,
STRCUR_DELIM_ALPHANUMERIC,
@ -24,8 +36,8 @@ typedef enum eStrCursorDelimType {
STRCUR_DELIM_BRACE,
STRCUR_DELIM_OPERATOR,
STRCUR_DELIM_QUOTE,
STRCUR_DELIM_WHITESPACE,
STRCUR_DELIM_OTHER,
STRCUR_DELIM_WHITESPACE,
} eStrCursorDelimType;
static eStrCursorDelimType cursor_delim_type_unicode(const uint uch)
@ -314,3 +326,46 @@ void BLI_str_cursor_step_utf32(const char32_t *str,
BLI_assert_unreachable();
}
}
void BLI_str_cursor_step_bounds_utf8(
const char *str, const size_t str_maxlen, const int pos, int *r_start, int *r_end)
{
/* Identify the type of characters are on either side of the current cursor position. */
const eStrCursorDelimType prev = (pos > 0) ? cursor_delim_type_utf8(str, str_maxlen, pos - 1) :
STRCUR_DELIM_NONE;
const eStrCursorDelimType next = (pos < str_maxlen) ?
cursor_delim_type_utf8(str, str_maxlen, pos) :
STRCUR_DELIM_NONE;
*r_start = pos;
*r_end = pos;
if ((prev <= next) && (prev != STRCUR_DELIM_NONE)) {
/* Expand backward if we are between similar content. */
BLI_str_cursor_step_utf8(str, str_maxlen, r_start, STRCUR_DIR_PREV, STRCUR_JUMP_DELIM, false);
}
if ((prev >= next) && (next != STRCUR_DELIM_NONE)) {
/* Expand forward if we are between similar content, after whitespace, or at beginning. */
BLI_str_cursor_step_utf8(str, str_maxlen, r_end, STRCUR_DIR_NEXT, STRCUR_JUMP_DELIM, false);
}
}
void BLI_str_cursor_step_bounds_utf32(
const char32_t *str, const size_t str_maxlen, const int pos, int *r_start, int *r_end)
{
/* Identify the type of characters are on either side of the current cursor position. */
const eStrCursorDelimType prev = (pos > 0) ? cursor_delim_type_unicode(str[pos - 1]) :
STRCUR_DELIM_NONE;
const eStrCursorDelimType next = (pos < str_maxlen) ? cursor_delim_type_unicode(str[pos]) :
STRCUR_DELIM_NONE;
*r_start = pos;
*r_end = pos;
if ((prev <= next) && (prev != STRCUR_DELIM_NONE)) {
/* Expand backward if we are between similar content, before whitespace, or at end. */
BLI_str_cursor_step_utf32(str, str_maxlen, r_start, STRCUR_DIR_PREV, STRCUR_JUMP_DELIM, false);
}
if ((prev >= next) && (next != STRCUR_DELIM_NONE)) {
/* Expand forward if we are between similar content, after whitespace, or at beginning. */
BLI_str_cursor_step_utf32(str, str_maxlen, r_end, STRCUR_DIR_NEXT, STRCUR_JUMP_DELIM, false);
}
}

View File

@ -43,6 +43,9 @@
* - 254..255: invalid.
*
* Invalid values fall back to 1 byte or -1 (for an error value).
*
* \note From testing string copying via #BLI_strncpy_utf8 with large (multi-megabyte) strings,
* using a function instead of a lookup-table is between 2 & 3 times faster.
* \{ */
BLI_INLINE int utf8_char_compute_skip(const char c)
@ -310,7 +313,7 @@ int BLI_str_utf8_invalid_strip(char *str, size_t length)
BLI_INLINE char *str_utf8_copy_max_bytes_impl(char *dst, const char *src, size_t dst_maxncpy)
{
/* Cast to `uint8_t` is a no-op, quiets array subscript of type `char` warning.
* No need to check `src` points to a nil byte, this will break out of the switch statement. */
* No need to check `src` points to a nil byte as this will return from the switch statement. */
size_t utf8_size;
while ((utf8_size = (size_t)utf8_char_compute_skip(*src)) < dst_maxncpy) {
dst_maxncpy -= utf8_size;
@ -318,12 +321,12 @@ BLI_INLINE char *str_utf8_copy_max_bytes_impl(char *dst, const char *src, size_t
/* NOLINTBEGIN: bugprone-assignment-in-if-condition */
/* clang-format off */
switch (utf8_size) {
case 6: if (UNLIKELY(!(*dst = *src++))) { return dst; } dst++; ATTR_FALLTHROUGH;
case 5: if (UNLIKELY(!(*dst = *src++))) { return dst; } dst++; ATTR_FALLTHROUGH;
case 4: if (UNLIKELY(!(*dst = *src++))) { return dst; } dst++; ATTR_FALLTHROUGH;
case 3: if (UNLIKELY(!(*dst = *src++))) { return dst; } dst++; ATTR_FALLTHROUGH;
case 2: if (UNLIKELY(!(*dst = *src++))) { return dst; } dst++; ATTR_FALLTHROUGH;
case 1: if (UNLIKELY(!(*dst = *src++))) { return dst; } dst++;
case 6: if (UNLIKELY(!(*dst = *src++))) { return dst; } dst++; ATTR_FALLTHROUGH;
case 5: if (UNLIKELY(!(*dst = *src++))) { return dst; } dst++; ATTR_FALLTHROUGH;
case 4: if (UNLIKELY(!(*dst = *src++))) { return dst; } dst++; ATTR_FALLTHROUGH;
case 3: if (UNLIKELY(!(*dst = *src++))) { return dst; } dst++; ATTR_FALLTHROUGH;
case 2: if (UNLIKELY(!(*dst = *src++))) { return dst; } dst++; ATTR_FALLTHROUGH;
case 1: if (UNLIKELY(!(*dst = *src++))) { return dst; } dst++;
}
/* clang-format on */
/* NOLINTEND: bugprone-assignment-in-if-condition */

View File

@ -112,7 +112,7 @@ bool BLI_windows_register_blend_extension(const bool background)
&hkey,
&dwd);
if (lresult == ERROR_SUCCESS) {
BLI_snprintf(buffer, sizeof(buffer), "\"%s\" \"%%1\"", BlPath);
SNPRINTF(buffer, "\"%s\" \"%%1\"", BlPath);
lresult = RegSetValueEx(hkey, NULL, 0, REG_SZ, (BYTE *)buffer, strlen(buffer) + 1);
RegCloseKey(hkey);
}
@ -131,7 +131,7 @@ bool BLI_windows_register_blend_extension(const bool background)
&hkey,
&dwd);
if (lresult == ERROR_SUCCESS) {
BLI_snprintf(buffer, sizeof(buffer), "\"%s\", 1", BlPath);
SNPRINTF(buffer, "\"%s\", 1", BlPath);
lresult = RegSetValueEx(hkey, NULL, 0, REG_SZ, (BYTE *)buffer, strlen(buffer) + 1);
RegCloseKey(hkey);
}
@ -169,12 +169,10 @@ bool BLI_windows_register_blend_extension(const bool background)
RegCloseKey(root);
printf("success (%s)\n", usr_mode ? "user" : "system");
if (!background) {
BLI_snprintf(MBox,
sizeof(MBox),
"File extension registered for %s.",
usr_mode ?
"the current user. To register for all users, run as an administrator" :
"all users");
SNPRINTF(MBox,
"File extension registered for %s.",
usr_mode ? "the current user. To register for all users, run as an administrator" :
"all users");
MessageBox(0, MBox, "Blender", MB_OK | MB_ICONINFORMATION);
}
return true;

View File

@ -107,7 +107,7 @@ TEST(listbase, FindLinkOrIndex)
TEST(listbase, FindLinkFromStringOrPointer)
{
struct TestLink {
struct TestLink *prev, *next;
struct TestLink *next, *prev;
char name[64];
const void *ptr;
};

View File

@ -638,6 +638,15 @@ TEST(path_util, Frame)
EXPECT_TRUE(ret);
EXPECT_STREQ(path, "test_-0100");
}
/* Ensure very large ranges work. */
{
char path[FILE_MAX * 2];
memset(path, '#', sizeof(path));
path[sizeof(path) - 1] = '\0';
ret = BLI_path_frame(path, sizeof(path), 123456789, 0);
EXPECT_TRUE(BLI_str_endswith(path, "0123456789"));
}
}
/** \} */
@ -987,7 +996,7 @@ TEST(path_util, FrameCheckChars)
char path[FILE_MAX]; \
bool ret; \
STRNCPY(path, input_path); \
ret = BLI_path_frame_range(path, sta, end, digits); \
ret = BLI_path_frame_range(path, sizeof(path), sta, end, digits); \
if (expect_outpath == nullptr) { \
EXPECT_FALSE(ret); \
} \

View File

@ -335,7 +335,7 @@ static void polyfill_to_obj(const char *id,
FILE *f;
uint i;
BLI_snprintf(path, sizeof(path), "%s.obj", id);
SNPRINTF(path, "%s.obj", id);
f = fopen(path, "w");
if (!f) {

View File

@ -122,6 +122,47 @@ TEST(string, StrCopyUTF8_TerminateEncodingEarly)
/** \} */
/* -------------------------------------------------------------------- */
/** \name String Replace
* \{ */
TEST(string, StrReplaceRange)
{
#define STR_REPLACE_RANGE(src, size, beg, end, dst, result_expect) \
{ \
char string[size] = src; \
BLI_str_replace_range(string, sizeof(string), beg, end, dst); \
EXPECT_STREQ(string, result_expect); \
}
STR_REPLACE_RANGE("a ", 5, 2, 2, "b!", "a b!");
STR_REPLACE_RANGE("a ", 4, 2, 2, "b!", "a b");
STR_REPLACE_RANGE("a ", 5, 1, 2, "b!", "ab!");
STR_REPLACE_RANGE("XYZ", 5, 1, 1, "A", "XAYZ");
STR_REPLACE_RANGE("XYZ", 5, 1, 1, "AB", "XABY");
STR_REPLACE_RANGE("XYZ", 5, 1, 1, "ABC", "XABC");
/* Add at the end when there is no room (no-op). */
STR_REPLACE_RANGE("XYZA", 5, 4, 4, "?", "XYZA");
/* Add at the start, replace all contents. */
STR_REPLACE_RANGE("XYZ", 4, 0, 0, "ABC", "ABC");
STR_REPLACE_RANGE("XYZ", 7, 0, 0, "ABC", "ABCXYZ");
/* Only remove. */
STR_REPLACE_RANGE("XYZ", 4, 1, 3, "", "X");
STR_REPLACE_RANGE("XYZ", 4, 0, 2, "", "Z");
STR_REPLACE_RANGE("XYZ", 4, 0, 3, "", "");
/* Only Add. */
STR_REPLACE_RANGE("", 4, 0, 0, "XYZ", "XYZ");
STR_REPLACE_RANGE("", 4, 0, 0, "XYZ?", "XYZ");
/* Do nothing. */
STR_REPLACE_RANGE("", 1, 0, 0, "?", "");
STR_REPLACE_RANGE("", 1, 0, 0, "", "");
#undef STR_REPLACE_RANGE
}
/** \} */
/* -------------------------------------------------------------------- */
/** \name String Partition
* \{ */

View File

@ -239,7 +239,8 @@ static const char *library_parent_filepath(Library *lib)
struct NewAddress {
void *newp;
/* `nr` is "user count" for data, and ID code for libdata. */
/** `nr` is "user count" for data, and ID code for libdata. */
int nr;
};
@ -261,9 +262,9 @@ static void oldnewmap_insert(OldNewMap *onm, const void *oldaddr, void *newaddr,
onm->map.add_overwrite(oldaddr, NewAddress{newaddr, nr});
}
static void oldnewmap_lib_insert(FileData *fd, const void *oldaddr, ID *newaddr, int nr)
static void oldnewmap_lib_insert(FileData *fd, const void *oldaddr, ID *newaddr, int id_code)
{
oldnewmap_insert(fd->libmap, oldaddr, newaddr, nr);
oldnewmap_insert(fd->libmap, oldaddr, newaddr, id_code);
}
void blo_do_versions_oldnewmap_insert(OldNewMap *onm, const void *oldaddr, void *newaddr, int nr)
@ -1297,8 +1298,8 @@ void blo_filedata_free(FileData *fd)
if (fd->libmap && !(fd->flags & FD_FLAGS_NOT_MY_LIBMAP)) {
oldnewmap_free(fd->libmap);
}
if (fd->old_idmap != nullptr) {
BKE_main_idmap_destroy(fd->old_idmap);
if (fd->old_idmap_uuid != nullptr) {
BKE_main_idmap_destroy(fd->old_idmap_uuid);
}
blo_cache_storage_end(fd);
if (fd->bheadmap) {
@ -1533,10 +1534,10 @@ void blo_add_library_pointer_map(ListBase *old_mainlist, FileData *fd)
void blo_make_old_idmap_from_main(FileData *fd, Main *bmain)
{
if (fd->old_idmap != nullptr) {
BKE_main_idmap_destroy(fd->old_idmap);
if (fd->old_idmap_uuid != nullptr) {
BKE_main_idmap_destroy(fd->old_idmap_uuid);
}
fd->old_idmap = BKE_main_idmap_create(bmain, false, nullptr, MAIN_IDMAP_TYPE_UUID);
fd->old_idmap_uuid = BKE_main_idmap_create(bmain, false, nullptr, MAIN_IDMAP_TYPE_UUID);
}
struct BLOCacheStorage {
@ -1898,7 +1899,7 @@ static void direct_link_id_override_property_cb(BlendDataReader *reader, void *d
}
static void direct_link_id_common(
BlendDataReader *reader, Library *current_library, ID *id, ID *id_old, const int tag);
BlendDataReader *reader, Library *current_library, ID *id, ID *id_old, const int id_tag);
static void direct_link_id_embedded_id(BlendDataReader *reader,
Library *current_library,
@ -1990,7 +1991,7 @@ static int direct_link_id_restore_recalc(const FileData *fd,
}
static void direct_link_id_common(
BlendDataReader *reader, Library *current_library, ID *id, ID *id_old, const int tag)
BlendDataReader *reader, Library *current_library, ID *id, ID *id_old, const int id_tag)
{
if (!BLO_read_data_is_undo(reader)) {
/* When actually reading a file, we do want to reset/re-generate session UUIDS.
@ -1998,7 +1999,7 @@ static void direct_link_id_common(
id->session_uuid = MAIN_ID_SESSION_UUID_UNSET;
}
if ((tag & LIB_TAG_TEMP_MAIN) == 0) {
if ((id_tag & LIB_TAG_TEMP_MAIN) == 0) {
BKE_lib_libblock_session_uuid_ensure(id);
}
@ -2011,10 +2012,10 @@ static void direct_link_id_common(
/* Initialize with provided tag. */
if (BLO_read_data_is_undo(reader)) {
id->tag = tag | (id->tag & LIB_TAG_KEEP_ON_UNDO);
id->tag = (id_tag & ~LIB_TAG_KEEP_ON_UNDO) | (id->tag & LIB_TAG_KEEP_ON_UNDO);
}
else {
id->tag = tag;
id->tag = id_tag;
}
if (ID_IS_LINKED(id)) {
@ -2024,7 +2025,7 @@ static void direct_link_id_common(
BLO_read_data_address(reader, &id->library_weak_reference);
}
if (tag & LIB_TAG_ID_LINK_PLACEHOLDER) {
if (id_tag & LIB_TAG_ID_LINK_PLACEHOLDER) {
/* For placeholder we only need to set the tag and properly initialize generic ID fields above,
* no further data to read. */
return;
@ -3071,13 +3072,13 @@ static bool read_libblock_undo_restore_linked(FileData *fd, Main *main, const ID
/* For undo, restore unchanged datablock from old main. */
static void read_libblock_undo_restore_identical(
FileData *fd, Main *main, const ID * /*id*/, ID *id_old, const int tag)
FileData *fd, Main *main, const ID * /*id*/, ID *id_old, const int id_tag)
{
BLI_assert((fd->skip_flags & BLO_READ_SKIP_UNDO_OLD_MAIN) == 0);
BLI_assert(id_old != nullptr);
/* Some tags need to be preserved here. */
id_old->tag = tag | (id_old->tag & LIB_TAG_KEEP_ON_UNDO);
id_old->tag = (id_tag & ~LIB_TAG_KEEP_ON_UNDO) | (id_old->tag & LIB_TAG_KEEP_ON_UNDO);
id_old->lib = main->curlib;
id_old->us = ID_FAKE_USERS(id_old);
/* Do not reset id->icon_id here, memory allocated for it remains valid. */
@ -3146,7 +3147,7 @@ static void read_libblock_undo_restore_at_old_address(FileData *fd, Main *main,
}
static bool read_libblock_undo_restore(
FileData *fd, Main *main, BHead *bhead, const int tag, ID **r_id_old)
FileData *fd, Main *main, BHead *bhead, int id_tag, ID **r_id_old)
{
/* Get pointer to memory of new ID that we will be reading. */
const ID *id = static_cast<const ID *>(peek_struct_undo(fd, bhead));
@ -3181,8 +3182,8 @@ static bool read_libblock_undo_restore(
/* Find the 'current' existing ID we want to reuse instead of the one we
* would read from the undo memfile. */
BLI_assert(fd->old_idmap != nullptr);
id_old = BKE_main_idmap_lookup_uuid(fd->old_idmap, id->session_uuid);
BLI_assert(fd->old_idmap_uuid != nullptr);
id_old = BKE_main_idmap_lookup_uuid(fd->old_idmap_uuid, id->session_uuid);
}
if (id_old != nullptr && read_libblock_is_identical(fd, bhead)) {
@ -3198,7 +3199,7 @@ static bool read_libblock_undo_restore(
* it will tell us which ID is re-used from old Main, and which one is actually new. */
/* Also do not add LIB_TAG_NEED_LINK, those IDs will never be re-liblinked, hence that tag will
* never be cleared, leading to critical issue in link/append code. */
const int id_tag = tag | LIB_TAG_UNDO_OLD_ID_REUSED;
id_tag |= LIB_TAG_UNDO_OLD_ID_REUSED_UNCHANGED;
read_libblock_undo_restore_identical(fd, main, id, id_old, id_tag);
/* Insert into library map for lookup by newly read datablocks (with pointer value bhead->old).
@ -3236,7 +3237,7 @@ static bool read_libblock_undo_restore(
static BHead *read_libblock(FileData *fd,
Main *main,
BHead *bhead,
const int tag,
int id_tag,
const bool placeholder_set_indirect_extern,
ID **r_id)
{
@ -3257,7 +3258,7 @@ static BHead *read_libblock(FileData *fd,
* So undo case does not seem to be affected by this. A future cleanup should try to remove
* most of this related code in the future, and instead assert that both `r_id` and
* `main->id_map` are `nullptr`. */
if (read_libblock_undo_restore(fd, main, bhead, tag, &id_old)) {
if (read_libblock_undo_restore(fd, main, bhead, id_tag, &id_old)) {
if (r_id) {
*r_id = id_old;
}
@ -3307,7 +3308,7 @@ static BHead *read_libblock(FileData *fd,
/* Set tag for new datablock to indicate lib linking and versioning needs
* to be done still. */
int id_tag = tag | LIB_TAG_NEED_LINK | LIB_TAG_NEW;
id_tag |= (LIB_TAG_NEED_LINK | LIB_TAG_NEW);
if (bhead->code == ID_LINK_PLACEHOLDER) {
/* Read placeholder for linked datablock. */
@ -3598,7 +3599,7 @@ static void lib_link_all(FileData *fd, Main *bmain)
}
if ((fd->flags & FD_FLAGS_IS_MEMFILE) && do_partial_undo &&
(id->tag & LIB_TAG_UNDO_OLD_ID_REUSED) != 0)
(id->tag & LIB_TAG_UNDO_OLD_ID_REUSED_UNCHANGED) != 0)
{
/* This ID has been re-used from 'old' bmain. Since it was therefore unchanged across
* current undo step, and old IDs re-use their old memory address, we do not need to liblink
@ -4567,7 +4568,7 @@ static void library_link_end(Main *mainl, FileData **fd, const int flag)
/* Some versioning code does expect some proper userrefcounting, e.g. in conversion from
* groups to collections... We could optimize out that first call when we are reading a
* current version file, but again this is really not a bottle neck currently. so not worth
* current version file, but again this is really not a bottle neck currently. So not worth
* it. */
BKE_main_id_refcount_recompute(mainvar, false);

View File

@ -94,7 +94,14 @@ typedef struct FileData {
struct OldNewMap *datamap;
struct OldNewMap *globmap;
/**
* Store mapping from old ID pointers (the values they have in the .blend file) to new ones,
* typically from value in `bhead->old` to address in memory where the ID was read.
* Used during liblinking process (see #lib_link_all).
*/
struct OldNewMap *libmap;
struct OldNewMap *packedmap;
struct BLOCacheStorage *cache_storage;
@ -107,7 +114,10 @@ typedef struct FileData {
ListBase *mainlist;
/** Used for undo. */
ListBase *old_mainlist;
struct IDNameLib_Map *old_idmap;
/**
* IDMap using uuids as keys of all the old IDs in the old bmain. Used during undo to find a
* matching old data when reading a new ID. */
struct IDNameLib_Map *old_idmap_uuid;
struct BlendFileReadReport *reports;
} FileData;
@ -119,7 +129,7 @@ struct Main;
void blo_join_main(ListBase *mainlist);
void blo_split_main(ListBase *mainlist, struct Main *main);
BlendFileData *blo_read_file_internal(FileData *fd, const char *filepath);
struct BlendFileData *blo_read_file_internal(FileData *fd, const char *filepath);
/**
* On each new library added, it now checks for the current #FileData and expands relativeness

View File

@ -632,7 +632,7 @@ static bool seq_sound_proxy_update_cb(Sequence *seq, void *user_data)
Main *bmain = (Main *)user_data;
if (seq->type == SEQ_TYPE_SOUND_HD) {
char str[FILE_MAX];
BLI_path_join(str, sizeof(str), seq->strip->dir, seq->strip->stripdata->name);
BLI_path_join(str, sizeof(str), seq->strip->dirpath, seq->strip->stripdata->filename);
BLI_path_abs(str, BKE_main_blendfile_path(bmain));
seq->sound = BKE_sound_new_file(bmain, str);
}
@ -640,7 +640,7 @@ static bool seq_sound_proxy_update_cb(Sequence *seq, void *user_data)
#define SEQ_USE_PROXY_CUSTOM_FILE (1 << 21)
/* don't know, if anybody used that this way, but just in case, upgrade to new way... */
if ((seq->flag & SEQ_USE_PROXY_CUSTOM_FILE) && !(seq->flag & SEQ_USE_PROXY_CUSTOM_DIR)) {
SNPRINTF(seq->strip->proxy->dir, "%s" SEP_STR "BL_proxy", seq->strip->dir);
SNPRINTF(seq->strip->proxy->dirpath, "%s" SEP_STR "BL_proxy", seq->strip->dirpath);
}
#undef SEQ_USE_PROXY_CUSTOM_DIR
#undef SEQ_USE_PROXY_CUSTOM_FILE

View File

@ -4355,6 +4355,27 @@ void blo_do_versions_300(FileData *fd, Library * /*lib*/, Main *bmain)
}
}
if (!MAIN_VERSION_ATLEAST(bmain, 306, 9)) {
/* Fix sound strips with speed factor set to 0. See #107289. */
LISTBASE_FOREACH (Scene *, scene, &bmain->scenes) {
Editing *ed = SEQ_editing_get(scene);
if (ed != nullptr) {
SEQ_for_each_callback(&ed->seqbase, version_seq_fix_broken_sound_strips, nullptr);
}
}
LISTBASE_FOREACH (bScreen *, screen, &bmain->screens) {
LISTBASE_FOREACH (ScrArea *, area, &screen->areabase) {
LISTBASE_FOREACH (SpaceLink *, sl, &area->spacedata) {
if (sl->spacetype == SPACE_ACTION) {
SpaceAction *saction = reinterpret_cast<SpaceAction *>(sl);
saction->cache_display |= TIME_CACHE_SIMULATION_NODES;
}
}
}
}
}
/**
* Versioning code until next subversion bump goes here.
*
@ -4366,13 +4387,5 @@ void blo_do_versions_300(FileData *fd, Library * /*lib*/, Main *bmain)
*/
{
/* Keep this block, even when empty. */
/* Fix sound strips with speed factor set to 0. See #107289. */
LISTBASE_FOREACH (Scene *, scene, &bmain->scenes) {
Editing *ed = SEQ_editing_get(scene);
if (ed != nullptr) {
SEQ_for_each_callback(&ed->seqbase, version_seq_fix_broken_sound_strips, nullptr);
}
}
}
}

View File

@ -11,6 +11,7 @@
#include "BLI_listbase.h"
#include "BLI_math.h"
#include "BLI_string.h"
#include "BLI_string_utf8.h"
#include "BLI_utildefines.h"
#include "DNA_anim_types.h"
@ -812,7 +813,7 @@ void blo_do_versions_userdef(UserDef *userdef)
"Versioning user script path");
STRNCPY(script_dir->dir_path, userdef->pythondir_legacy);
STRNCPY(script_dir->name, DATA_("Untitled"));
STRNCPY_UTF8(script_dir->name, DATA_("Untitled"));
BLI_addhead(&userdef->script_directories, script_dir);
}
}

View File

@ -1063,7 +1063,7 @@ static void write_global(WriteData *wd, int fileflags, Main *mainvar)
#ifdef WITH_BUILDINFO
/* TODO(sergey): Add branch name to file as well? */
fg.build_commit_timestamp = build_commit_timestamp;
BLI_strncpy(fg.build_hash, build_hash, sizeof(fg.build_hash));
STRNCPY(fg.build_hash, build_hash);
#else
fg.build_commit_timestamp = 0;
STRNCPY(fg.build_hash, "unknown");

View File

@ -2,13 +2,15 @@
* Copyright 2019 Blender Foundation. */
#include "blendfile_loading_base_test.h"
#include "BLI_path_util.h"
class BlendfileLoadingTest : public BlendfileLoadingBaseTest {
};
TEST_F(BlendfileLoadingTest, CanaryTest)
{
/* Load the smallest blend file we have in the SVN lib/tests directory. */
if (!blendfile_load("modifier_stack/array_test.blend")) {
if (!blendfile_load("modifier_stack" SEP_STR "array_test.blend")) {
return;
}
depsgraph_create(DAG_EVAL_RENDER);

View File

@ -116,7 +116,7 @@ bool BlendfileLoadingBaseTest::blendfile_load(const char *filepath)
return false;
}
char abspath[FILENAME_MAX];
char abspath[FILE_MAX];
BLI_path_join(abspath, sizeof(abspath), test_assets_dir.c_str(), filepath);
BlendFileReadReport bf_reports = {nullptr};

View File

@ -73,6 +73,7 @@ set(SRC
cached_resources/intern/cached_mask.cc
cached_resources/intern/cached_texture.cc
cached_resources/intern/morphological_distance_feather_weights.cc
cached_resources/intern/ocio_color_space_conversion_shader.cc
cached_resources/intern/smaa_precomputed_textures.cc
cached_resources/intern/symmetric_blur_weights.cc
cached_resources/intern/symmetric_separable_blur_weights.cc
@ -81,6 +82,7 @@ set(SRC
cached_resources/COM_cached_resource.hh
cached_resources/COM_cached_texture.hh
cached_resources/COM_morphological_distance_feather_weights.hh
cached_resources/COM_ocio_color_space_conversion_shader.hh
cached_resources/COM_smaa_precomputed_textures.hh
cached_resources/COM_symmetric_blur_weights.hh
cached_resources/COM_symmetric_separable_blur_weights.hh
@ -173,6 +175,7 @@ set(GLSL_SRC
shaders/library/gpu_shader_compositor_main.glsl
shaders/library/gpu_shader_compositor_map_value.glsl
shaders/library/gpu_shader_compositor_normal.glsl
shaders/library/gpu_shader_compositor_ocio_processor.glsl
shaders/library/gpu_shader_compositor_posterize.glsl
shaders/library/gpu_shader_compositor_separate_combine.glsl
shaders/library/gpu_shader_compositor_set_alpha.glsl
@ -268,4 +271,18 @@ if(WITH_TBB)
endif()
endif()
if(WITH_OPENCOLORIO)
add_definitions(
-DWITH_OCIO
)
list(APPEND INC_SYS
${OPENCOLORIO_INCLUDE_DIRS}
)
list(APPEND LIB
${OPENCOLORIO_LIBRARIES}
)
endif()
blender_add_lib(bf_realtime_compositor "${SRC}" "${INC}" "${INC_SYS}" "${LIB}")

View File

@ -5,6 +5,7 @@
#include "COM_cached_mask.hh"
#include "COM_cached_texture.hh"
#include "COM_morphological_distance_feather_weights.hh"
#include "COM_ocio_color_space_conversion_shader.hh"
#include "COM_smaa_precomputed_textures.hh"
#include "COM_symmetric_blur_weights.hh"
#include "COM_symmetric_separable_blur_weights.hh"
@ -41,6 +42,7 @@ class StaticCacheManager {
CachedTextureContainer cached_textures;
CachedMaskContainer cached_masks;
SMAAPrecomputedTexturesContainer smaa_precomputed_textures;
OCIOColorSpaceConversionShaderContainer ocio_color_space_conversion_shaders;
/* Reset the cache manager by deleting the cached resources that are no longer needed because
* they weren't used in the last evaluation and prepare the remaining cached resources to track

View File

@ -0,0 +1,71 @@
/* SPDX-License-Identifier: GPL-2.0-or-later */
#pragma once
#include <cstdint>
#include <string>
#include "COM_cached_resource.hh"
namespace blender::realtime_compositor {
/* ------------------------------------------------------------------------------------------------
* OCIO Color Space Conversion Shader Key.
*/
class OCIOColorSpaceConversionShaderKey {
public:
std::string source;
std::string target;
std::string config_cache_id;
OCIOColorSpaceConversionShaderKey(std::string source,
std::string target,
std::string config_cache_id);
uint64_t hash() const;
};
bool operator==(const OCIOColorSpaceConversionShaderKey &a,
const OCIOColorSpaceConversionShaderKey &b);
class GPUShaderCreator;
/* -------------------------------------------------------------------------------------------------
* OCIO Color Space Conversion Shader.
*
* A cached resource that creates and caches a GPU shader that converts the source OCIO color space
* of an image into a different target OCIO color space. */
class OCIOColorSpaceConversionShader : public CachedResource {
private:
std::shared_ptr<GPUShaderCreator> shader_creator_;
public:
OCIOColorSpaceConversionShader(std::string source, std::string target);
GPUShader *bind_shader_and_resources();
void unbind_shader_and_resources();
const char *input_sampler_name();
const char *output_image_name();
};
/* ------------------------------------------------------------------------------------------------
* OCIO Color Space Conversion Shader Container.
*/
class OCIOColorSpaceConversionShaderContainer : CachedResourceContainer {
private:
Map<OCIOColorSpaceConversionShaderKey, std::unique_ptr<OCIOColorSpaceConversionShader>> map_;
public:
void reset() override;
/* Check if there is an available OCIOColorSpaceConversionShader cached resource with the given
* parameters in the container, if one exists, return it, otherwise, return a newly created one
* and add it to the container. In both cases, tag the cached resource as needed to keep it
* cached for the next evaluation. */
OCIOColorSpaceConversionShader &get(std::string source, std::string target);
};
} // namespace blender::realtime_compositor

View File

@ -0,0 +1,502 @@
/* SPDX-License-Identifier: GPL-2.0-or-later */
#include <cstdint>
#include <memory>
#include <string>
#include "BLI_assert.h"
#include "BLI_hash.hh"
#include "BLI_map.hh"
#include "BLI_string_ref.hh"
#include "BLI_vector.hh"
#include "BLI_vector_set.hh"
#include "GPU_capabilities.h"
#include "GPU_shader.h"
#include "GPU_texture.h"
#include "GPU_uniform_buffer.h"
#include "gpu_shader_create_info.hh"
#include "COM_ocio_color_space_conversion_shader.hh"
#if defined(WITH_OCIO)
# include <OpenColorIO/OpenColorIO.h>
#endif
namespace blender::realtime_compositor {
/* ------------------------------------------------------------------------------------------------
* OCIO Color Space Conversion Shader Key.
*/
OCIOColorSpaceConversionShaderKey::OCIOColorSpaceConversionShaderKey(std::string source,
std::string target,
std::string config_cache_id)
: source(source), target(target), config_cache_id(config_cache_id)
{
}
uint64_t OCIOColorSpaceConversionShaderKey::hash() const
{
return get_default_hash_3(source, target, config_cache_id);
}
bool operator==(const OCIOColorSpaceConversionShaderKey &a,
const OCIOColorSpaceConversionShaderKey &b)
{
return a.source == b.source && a.target == b.target && a.config_cache_id == b.config_cache_id;
}
/* --------------------------------------------------------------------
* GPU Shader Creator.
*/
#if defined(WITH_OCIO)
namespace OCIO = OCIO_NAMESPACE;
using namespace blender::gpu::shader;
/* A subclass of OCIO::GpuShaderCreator that constructs the shader using a ShaderCreateInfo. The
* Create method should be used to construct the creator, then the extractGpuShaderInfo() method of
* the appropriate OCIO::GPUProcessor should be called passing in the creator. After construction,
* the constructed compute shader can be used by calling the bind_shader_and_resources() method,
* followed by binding the input texture and output image using their names input_sampler_name()
* and output_image_name(), following by dispatching the shader on the domain of the input, and
* finally calling the unbind_shader_and_resources() method.
*
* Upon calling the extractGpuShaderInfo(), all the transforms in the GPU processor will add their
* needed resources by calling the respective addUniform() and add[3D]Texture() methods. Then, the
* shader code of all transforms will be generated and passed to the createShaderText() method,
* generating the full code of the processor. Finally, the finalize() method will be called to
* finally create the shader. */
class GPUShaderCreator : public OCIO::GpuShaderCreator {
public:
static std::shared_ptr<GPUShaderCreator> Create()
{
std::shared_ptr<GPUShaderCreator> instance = std::make_shared<GPUShaderCreator>();
instance->setLanguage(OCIO::GPU_LANGUAGE_GLSL_4_0);
return instance;
}
/* Not used, but needs to be overridden, so return a nullptr. */
OCIO::GpuShaderCreatorRcPtr clone() const override
{
return OCIO::GpuShaderCreatorRcPtr();
}
/* This is ignored since we query using our own GPU capabilities system. */
void setTextureMaxWidth(unsigned max_width) override {}
unsigned getTextureMaxWidth() const noexcept override
{
return GPU_max_texture_size();
}
bool addUniform(const char *name, const DoubleGetter &get_double) override
{
/* Check if a resource exists with the same name and assert if it is the case, returning false
* indicates failure to add the uniform for the shader creator. */
if (!resource_names_.add(std::make_unique<std::string>(name))) {
BLI_assert_unreachable();
return false;
}
/* Don't use the name argument directly since ShaderCreateInfo only stores references to
* resource names, instead, use the name that is stored in resource_names_. */
std::string &resource_name = *resource_names_[resource_names_.size() - 1];
shader_create_info_.push_constant(Type::FLOAT, resource_name);
float_uniforms_.add(resource_name, get_double);
return true;
}
bool addUniform(const char *name, const BoolGetter &get_bool) override
{
/* Check if a resource exists with the same name and assert if it is the case, returning false
* indicates failure to add the uniform for the shader creator. */
if (!resource_names_.add(std::make_unique<std::string>(name))) {
BLI_assert_unreachable();
return false;
}
/* Don't use the name argument directly since ShaderCreateInfo only stores references to
* resource names, instead, use the name that is stored in resource_names_. */
std::string &resource_name = *resource_names_[resource_names_.size() - 1];
shader_create_info_.push_constant(Type::BOOL, resource_name);
boolean_uniforms_.add(name, get_bool);
return true;
}
bool addUniform(const char *name, const Float3Getter &get_float3) override
{
/* Check if a resource exists with the same name and assert if it is the case, returning false
* indicates failure to add the uniform for the shader creator. */
if (!resource_names_.add(std::make_unique<std::string>(name))) {
BLI_assert_unreachable();
return false;
}
/* Don't use the name argument directly since ShaderCreateInfo only stores references to
* resource names, instead, use the name that is stored in resource_names_. */
std::string &resource_name = *resource_names_[resource_names_.size() - 1];
shader_create_info_.push_constant(Type::VEC3, resource_name);
vector_uniforms_.add(resource_name, get_float3);
return true;
}
bool addUniform(const char *name,
const SizeGetter &get_size,
const VectorFloatGetter &get_vector_float) override
{
/* Check if a resource exists with the same name and assert if it is the case, returning false
* indicates failure to add the uniform for the shader creator. */
if (!resource_names_.add(std::make_unique<std::string>(name))) {
BLI_assert_unreachable();
return false;
}
/* Don't use the name argument directly since ShaderCreateInfo only stores references to
* resource names, instead, use the name that is stored in resource_names_. */
std::string &resource_name = *resource_names_[resource_names_.size() - 1];
shader_create_info_.uniform_buf(buffers_sizes_.size(), "float", resource_name);
float_buffers_.add(resource_name, get_vector_float);
buffers_sizes_.add(resource_name, get_size);
return true;
}
bool addUniform(const char *name,
const SizeGetter &get_size,
const VectorIntGetter &get_vector_int) override
{
/* Check if a resource exists with the same name and assert if it is the case, returning false
* indicates failure to add the uniform for the shader creator. */
if (!resource_names_.add(std::make_unique<std::string>(name))) {
BLI_assert_unreachable();
return false;
}
/* Don't use the name argument directly since ShaderCreateInfo only stores references to
* resource names, instead, use the name that is stored in resource_names_. */
std::string &resource_name = *resource_names_[resource_names_.size() - 1];
shader_create_info_.uniform_buf(buffers_sizes_.size(), "int", resource_name);
int_buffers_.add(name, get_vector_int);
buffers_sizes_.add(name, get_size);
return true;
}
void addTexture(const char *texture_name,
const char *sampler_name,
unsigned width,
unsigned height,
TextureType channel,
OCIO::Interpolation interpolation,
const float *values) override
{
/* Check if a resource exists with the same name and assert if it is the case. */
if (!resource_names_.add(std::make_unique<std::string>(sampler_name))) {
BLI_assert_unreachable();
}
/* Don't use the name argument directly since ShaderCreateInfo only stores references to
* resource names, instead, use the name that is stored in resource_names_. */
std::string &resource_name = *resource_names_[resource_names_.size() - 1];
GPUTexture *texture;
eGPUTextureFormat texture_format = (channel == TEXTURE_RGB_CHANNEL) ? GPU_RGB16F : GPU_R16F;
/* A height of 1 indicates a 1D texture according to the OCIO API. */
if (height == 1) {
texture = GPU_texture_create_1d(
texture_name, width, 1, texture_format, GPU_TEXTURE_USAGE_SHADER_READ, values);
shader_create_info_.sampler(textures_.size() + 1, ImageType::FLOAT_1D, resource_name);
}
else {
texture = GPU_texture_create_2d(
texture_name, width, height, 1, texture_format, GPU_TEXTURE_USAGE_SHADER_READ, values);
shader_create_info_.sampler(textures_.size() + 1, ImageType::FLOAT_2D, resource_name);
}
GPU_texture_filter_mode(texture, interpolation != OCIO::INTERP_NEAREST);
textures_.add(sampler_name, texture);
}
void add3DTexture(const char *texture_name,
const char *sampler_name,
unsigned size,
OCIO::Interpolation interpolation,
const float *values) override
{
/* Check if a resource exists with the same name and assert if it is the case. */
if (!resource_names_.add(std::make_unique<std::string>(sampler_name))) {
BLI_assert_unreachable();
}
/* Don't use the name argument directly since ShaderCreateInfo only stores references to
* resource names, instead, use the name that is stored in resource_names_. */
std::string &resource_name = *resource_names_[resource_names_.size() - 1];
shader_create_info_.sampler(textures_.size() + 1, ImageType::FLOAT_3D, resource_name);
GPUTexture *texture = GPU_texture_create_3d(
texture_name, size, size, size, 1, GPU_RGB16F, GPU_TEXTURE_USAGE_SHADER_READ, values);
GPU_texture_filter_mode(texture, interpolation != OCIO::INTERP_NEAREST);
textures_.add(sampler_name, texture);
}
/* This gets called before the finalize() method to construct the shader code. We just
* concatenate the code except for the declarations section. That's because the ShaderCreateInfo
* will add the declaration itself. */
void createShaderText(const char *declarations,
const char *helper_methods,
const char *function_header,
const char *function_body,
const char *function_footer) override
{
shader_code_ += helper_methods;
shader_code_ += function_header;
shader_code_ += function_body;
shader_code_ += function_footer;
}
/* This gets called when all resources were added using the respective addUniform() or
* add[3D]Texture() methods and the shader code was generated using the createShaderText()
* method. That is, we are ready to complete the ShaderCreateInfo and create a shader from it. */
void finalize() override
{
GpuShaderCreator::finalize();
shader_create_info_.local_group_size(16, 16);
shader_create_info_.sampler(0, ImageType::FLOAT_2D, input_sampler_name());
shader_create_info_.image(
0, GPU_RGBA16F, Qualifier::WRITE, ImageType::FLOAT_2D, output_image_name());
shader_create_info_.compute_source("gpu_shader_compositor_ocio_processor.glsl");
shader_create_info_.compute_source_generated += shader_code_;
GPUShaderCreateInfo *info = reinterpret_cast<GPUShaderCreateInfo *>(&shader_create_info_);
shader_ = GPU_shader_create_from_info(info);
}
GPUShader *bind_shader_and_resources()
{
GPU_shader_bind(shader_);
for (auto item : float_uniforms_.items()) {
GPU_shader_uniform_1f(shader_, item.key.c_str(), item.value());
}
for (auto item : boolean_uniforms_.items()) {
GPU_shader_uniform_1b(shader_, item.key.c_str(), item.value());
}
for (auto item : vector_uniforms_.items()) {
GPU_shader_uniform_3fv(shader_, item.key.c_str(), item.value().data());
}
for (auto item : float_buffers_.items()) {
GPUUniformBuf *buffer = GPU_uniformbuf_create_ex(
buffers_sizes_.lookup(item.key)(), item.value(), item.key.c_str());
const int ubo_location = GPU_shader_get_ubo_binding(shader_, item.key.c_str());
GPU_uniformbuf_bind(buffer, ubo_location);
uniform_buffers_.append(buffer);
}
for (auto item : int_buffers_.items()) {
GPUUniformBuf *buffer = GPU_uniformbuf_create_ex(
buffers_sizes_.lookup(item.key)(), item.value(), item.key.c_str());
const int ubo_location = GPU_shader_get_ubo_binding(shader_, item.key.c_str());
GPU_uniformbuf_bind(buffer, ubo_location);
uniform_buffers_.append(buffer);
}
for (auto item : textures_.items()) {
const int texture_image_unit = GPU_shader_get_sampler_binding(shader_, item.key.c_str());
GPU_texture_bind(item.value, texture_image_unit);
}
return shader_;
}
void unbind_shader_and_resources()
{
for (GPUUniformBuf *buffer : uniform_buffers_) {
GPU_uniformbuf_unbind(buffer);
GPU_uniformbuf_free(buffer);
}
for (GPUTexture *texture : textures_.values()) {
GPU_texture_unbind(texture);
}
GPU_shader_unbind();
}
const char *input_sampler_name()
{
return "input_tx";
}
const char *output_image_name()
{
return "output_img";
}
~GPUShaderCreator() override
{
for (GPUTexture *texture : textures_.values()) {
GPU_texture_free(texture);
}
GPU_shader_free(shader_);
}
private:
/* The processor shader and the ShaderCreateInfo used to construct it. Constructed and
* initialized in the finalize() method. */
GPUShader *shader_ = nullptr;
ShaderCreateInfo shader_create_info_ = ShaderCreateInfo("OCIO Processor");
/* Stores the generated OCIOMain function as well as a number of helper functions. Initialized in
* the createShaderText() method. */
std::string shader_code_;
/* Maps that associates the name of a uniform with a getter function that returns its value.
* Initialized in the respective addUniform() methods. */
Map<std::string, DoubleGetter> float_uniforms_;
Map<std::string, BoolGetter> boolean_uniforms_;
Map<std::string, Float3Getter> vector_uniforms_;
/* Maps that associates the name of uniform buffer objects with a getter function that returns
* its values. Initialized in the respective addUniform() methods. */
Map<std::string, VectorFloatGetter> float_buffers_;
Map<std::string, VectorIntGetter> int_buffers_;
/* A map that associates the name of uniform buffer objects with a getter functions that returns
* its number of elements. Initialized in the respective addUniform() methods. */
Map<std::string, SizeGetter> buffers_sizes_;
/* A map that associates the name of a sampler with its corresponding texture. Initialized in the
* addTexture() and add3DTexture() methods. */
Map<std::string, GPUTexture *> textures_;
/* A vector set that stores the names of all the resources used by the shader. This is used to:
* 1. Check for name collisions when adding new resources.
* 2. Store the resource names throughout the construction of the shader since the
* ShaderCreateInfo class only stores references to resources names. */
VectorSet<std::unique_ptr<std::string>> resource_names_;
/* A vectors that stores the created uniform buffers when bind_shader_and_resources() is called,
* so that they can be properly unbound and freed in the unbind_shader_and_resources() method. */
Vector<GPUUniformBuf *> uniform_buffers_;
};
#else
/* A stub implementation in case OCIO is disabled at build time. */
class GPUShaderCreator {
public:
GPUShader *bind_shader_and_resources()
{
return nullptr;
}
void unbind_shader_and_resources() {}
const char *input_sampler_name()
{
return nullptr;
}
const char *output_image_name()
{
return nullptr;
}
};
#endif
/* --------------------------------------------------------------------
* OCIO Color Space Conversion Shader.
*/
OCIOColorSpaceConversionShader::OCIOColorSpaceConversionShader(std::string source,
std::string target)
{
#if defined(WITH_OCIO)
/* Get a GPU processor that transforms the source color space to the target color space. */
OCIO::ConstConfigRcPtr config = OCIO::GetCurrentConfig();
OCIO::ConstProcessorRcPtr processor = config->getProcessor(source.c_str(), target.c_str());
OCIO::ConstGPUProcessorRcPtr gpu_processor = processor->getDefaultGPUProcessor();
/* Create a GPU shader creator and construct it based on the transforms in the default GPU
* processor. */
shader_creator_ = GPUShaderCreator::Create();
auto ocio_shader_creator = std::static_pointer_cast<OCIO::GpuShaderCreator>(shader_creator_);
gpu_processor->extractGpuShaderInfo(ocio_shader_creator);
#endif
}
GPUShader *OCIOColorSpaceConversionShader::bind_shader_and_resources()
{
return shader_creator_->bind_shader_and_resources();
}
void OCIOColorSpaceConversionShader::unbind_shader_and_resources()
{
shader_creator_->unbind_shader_and_resources();
}
const char *OCIOColorSpaceConversionShader::input_sampler_name()
{
return shader_creator_->input_sampler_name();
}
const char *OCIOColorSpaceConversionShader::output_image_name()
{
return shader_creator_->output_image_name();
}
/* --------------------------------------------------------------------
* OCIO Color Space Conversion Shader Container.
*/
void OCIOColorSpaceConversionShaderContainer::reset()
{
/* First, delete all resources that are no longer needed. */
map_.remove_if([](auto item) { return !item.value->needed; });
/* Second, reset the needed status of the remaining resources to false to ready them to track
* their needed status for the next evaluation. */
for (auto &value : map_.values()) {
value->needed = false;
}
}
OCIOColorSpaceConversionShader &OCIOColorSpaceConversionShaderContainer::get(std::string source,
std::string target)
{
#if defined(WITH_OCIO)
/* Use the config cache ID in the cache key in case the configuration changed at runtime. */
std::string config_cache_id = OCIO::GetCurrentConfig()->getCacheID();
#else
std::string config_cache_id;
#endif
const OCIOColorSpaceConversionShaderKey key(source, target, config_cache_id);
OCIOColorSpaceConversionShader &shader = *map_.lookup_or_add_cb(
key, [&]() { return std::make_unique<OCIOColorSpaceConversionShader>(source, target); });
shader.needed = true;
return shader;
}
} // namespace blender::realtime_compositor

View File

@ -12,6 +12,7 @@ void StaticCacheManager::reset()
cached_textures.reset();
cached_masks.reset();
smaa_precomputed_textures.reset();
ocio_color_space_conversion_shaders.reset();
}
} // namespace blender::realtime_compositor

View File

@ -0,0 +1,14 @@
#pragma BLENDER_REQUIRE(gpu_shader_compositor_texture_utilities.glsl)
/* OCIOMain will be dynamically generated in the OCIOColorSpaceConversionShader class and appended
* at the end of this file, so forward declare it. Such forward declarations are not supported nor
* needed on Metal. */
#if !defined(GPU_METAL)
vec4 OCIOMain(vec4 inPixel);
#endif
void main()
{
ivec2 texel = ivec2(gl_GlobalInvocationID.xy);
imageStore(output_img, texel, OCIOMain(texture_load(input_tx, texel)));
}

View File

@ -139,7 +139,7 @@ static void extract_tan_init_common(const MeshRenderData *mr,
char attr_name[32], attr_safe_name[GPU_MAX_SAFE_ATTR_NAME];
const char *layer_name = CustomData_get_layer_name(r_loop_data, CD_TANGENT, 0);
GPU_vertformat_safe_attr_name(layer_name, attr_safe_name, GPU_MAX_SAFE_ATTR_NAME);
BLI_snprintf(attr_name, sizeof(*attr_name), "t%s", attr_safe_name);
SNPRINTF(attr_name, "t%s", attr_safe_name);
GPU_vertformat_attr_add(format, attr_name, comp_type, 4, fetch_mode);
GPU_vertformat_alias_add(format, "t");
GPU_vertformat_alias_add(format, "at");

View File

@ -368,7 +368,7 @@ static void acf_generic_idblock_name(bAnimListElem *ale, char *name)
/* just copy the name... */
if (id && name) {
BLI_strncpy(name, id->name + 2, ANIM_CHAN_NAME_SIZE);
BLI_strncpy_utf8(name, id->name + 2, ANIM_CHAN_NAME_SIZE);
}
}
@ -476,7 +476,7 @@ static void acf_summary_backdrop(bAnimContext *ac, bAnimListElem *ale, float ymi
static void acf_summary_name(bAnimListElem *UNUSED(ale), char *name)
{
if (name) {
BLI_strncpy(name, IFACE_("Summary"), ANIM_CHAN_NAME_SIZE);
BLI_strncpy_utf8(name, IFACE_("Summary"), ANIM_CHAN_NAME_SIZE);
}
}
@ -1178,7 +1178,7 @@ static void acf_nla_controls_backdrop(bAnimContext *ac,
/* name for nla controls expander entries */
static void acf_nla_controls_name(bAnimListElem *UNUSED(ale), char *name)
{
BLI_strncpy(name, IFACE_("NLA Strip Controls"), ANIM_CHAN_NAME_SIZE);
BLI_strncpy_utf8(name, IFACE_("NLA Strip Controls"), ANIM_CHAN_NAME_SIZE);
}
/* check if some setting exists for this channel */
@ -1393,7 +1393,7 @@ static int acf_filldrivers_icon(bAnimListElem *UNUSED(ale))
static void acf_filldrivers_name(bAnimListElem *UNUSED(ale), char *name)
{
BLI_strncpy(name, IFACE_("Drivers"), ANIM_CHAN_NAME_SIZE);
BLI_strncpy_utf8(name, IFACE_("Drivers"), ANIM_CHAN_NAME_SIZE);
}
/* check if some setting exists for this channel */
@ -4468,6 +4468,10 @@ void ANIM_channel_draw(
/* just skip - drawn as widget now */
offset += ICON_WIDTH;
}
else {
/* A bit of padding when there is no expand widget. */
offset += (short)(0.2f * U.widget_unit);
}
/* step 3) draw icon ............................................... */
if (acf->icon) {
@ -4522,10 +4526,6 @@ void ANIM_channel_draw(
offset += ICON_WIDTH;
}
}
else if ((ac->spacetype == SPACE_NLA) && acf->has_setting(ac, ale, ACHANNEL_SETTING_SOLO)) {
/* just skip - drawn as widget now */
offset += ICON_WIDTH;
}
}
/* step 5) draw name ............................................... */
@ -5252,11 +5252,6 @@ void ANIM_channel_draw_widgets(const bContext *C,
offset += ICON_WIDTH;
}
}
else if ((ac->spacetype == SPACE_NLA) && acf->has_setting(ac, ale, ACHANNEL_SETTING_SOLO)) {
/* 'solo' setting for NLA Tracks */
draw_setting_widget(ac, ale, acf, block, offset, ymid, ACHANNEL_SETTING_SOLO);
offset += ICON_WIDTH;
}
}
/* step 4) draw text - check if renaming widget is in use... */
@ -5338,6 +5333,13 @@ void ANIM_channel_draw_widgets(const bContext *C,
/* check if there's enough space for the toggles if the sliders are drawn too */
if (!(draw_sliders) || (BLI_rcti_size_x(&v2d->mask) > ANIM_UI_get_channel_button_width() / 2))
{
/* solo... */
if ((ac->spacetype == SPACE_NLA) && acf->has_setting(ac, ale, ACHANNEL_SETTING_SOLO)) {
offset -= ICON_WIDTH;
draw_setting_widget(ac, ale, acf, block, offset, ymid, ACHANNEL_SETTING_SOLO);
/* A touch of padding because the star icon is so wide. */
offset -= (short)(0.2f * ICON_WIDTH);
}
/* protect... */
if (acf->has_setting(ac, ale, ACHANNEL_SETTING_PROTECT)) {
offset -= ICON_WIDTH;

View File

@ -1920,9 +1920,19 @@ void FONT_OT_selection_set(struct wmOperatorType *ot)
static int font_select_word_exec(bContext *C, wmOperator *UNUSED(op))
{
move_cursor(C, NEXT_CHAR, false);
move_cursor(C, PREV_WORD, false);
move_cursor(C, NEXT_WORD, true);
Object *obedit = CTX_data_edit_object(C);
Curve *cu = obedit->data;
EditFont *ef = cu->editfont;
BLI_str_cursor_step_bounds_utf32(ef->textbuf, ef->len, ef->pos, &ef->selstart, &ef->selend);
ef->pos = ef->selend;
/* XXX: Text object selection start is 1-based, unlike text processing elsewhere in Blender. */
ef->selstart += 1;
font_select_update_primary_clipboard(obedit);
text_update_edited(C, obedit, FO_CURS);
return OPERATOR_FINISHED;
}

View File

@ -3388,7 +3388,7 @@ void ED_gpencil_layer_merge(bGPdata *gpd,
}
}
static void gpencil_layer_new_name_get(bGPdata *gpd, char *rname)
static void gpencil_layer_new_name_get(bGPdata *gpd, char *r_name, size_t name_maxncpy)
{
int index = 0;
LISTBASE_FOREACH (bGPDlayer *, gpl, &gpd->layers) {
@ -3398,12 +3398,10 @@ static void gpencil_layer_new_name_get(bGPdata *gpd, char *rname)
}
if (index == 0) {
BLI_strncpy(rname, "GP_Layer", 128);
BLI_strncpy(r_name, "GP_Layer", name_maxncpy);
return;
}
char *name = BLI_sprintfN("%.*s.%03d", 128, "GP_Layer", index);
BLI_strncpy(rname, name, 128);
MEM_freeN(name);
BLI_snprintf(r_name, name_maxncpy, "GP_Layer.%03d", index);
}
int ED_gpencil_new_layer_dialog(bContext *C, wmOperator *op)
@ -3415,7 +3413,7 @@ int ED_gpencil_new_layer_dialog(bContext *C, wmOperator *op)
if (!RNA_property_is_set(op->ptr, prop)) {
char name[MAX_NAME];
bGPdata *gpd = ob->data;
gpencil_layer_new_name_get(gpd, name);
gpencil_layer_new_name_get(gpd, name, sizeof(name));
RNA_property_string_set(op->ptr, prop, name);
return WM_operator_props_dialog_popup(C, op, 200);
}

View File

@ -3937,11 +3937,11 @@ static void ui_but_update_ex(uiBut *but, const bool validate)
(void)str; /* UNUSED */
}
else {
STRNCPY(but->drawstr, IFACE_("Press a key"));
STRNCPY_UTF8(but->drawstr, IFACE_("Press a key"));
}
}
else {
STRNCPY(but->drawstr, but->str);
STRNCPY_UTF8(but->drawstr, but->str);
}
break;

View File

@ -3730,8 +3730,11 @@ static void ui_do_but_textedit(
/* only select a word in button if there was no selection before */
if (event->val == KM_DBL_CLICK && had_selection == false) {
ui_textedit_move(but, data, STRCUR_DIR_PREV, false, STRCUR_JUMP_DELIM);
ui_textedit_move(but, data, STRCUR_DIR_NEXT, true, STRCUR_JUMP_DELIM);
int selsta, selend;
BLI_str_cursor_step_bounds_utf8(data->str, strlen(data->str), but->pos, &selsta, &selend);
but->pos = (short)selend;
but->selsta = (short)selsta;
but->selend = (short)selend;
retval = WM_UI_HANDLER_BREAK;
changed = true;
}

View File

@ -36,6 +36,7 @@
#include "BLI_listbase.h"
#include "BLI_math.h"
#include "BLI_string.h"
#include "BLI_string_utf8.h"
#include "BLI_utildefines.h"
#include "BLI_vector.hh"
@ -2021,7 +2022,7 @@ static int object_speaker_add_exec(bContext *C, wmOperator *op)
BKE_nlatrack_add_strip(nlt, strip, is_liboverride);
/* Auto-name the strip, and give the track an interesting name. */
STRNCPY(nlt->name, DATA_("SoundTrack"));
STRNCPY_UTF8(nlt->name, DATA_("SoundTrack"));
BKE_nlastrip_validate_name(adt, strip);
WM_event_add_notifier(C, NC_ANIMATION | ND_NLA | NA_ADDED, nullptr);

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