WIP: Brush assets project #106303

Draft
Julian Eisel wants to merge 358 commits from brush-assets-project into main

When changing the target branch, be careful to rebase the branch in your fork to match. See documentation.
28 changed files with 356 additions and 267 deletions
Showing only changes of commit 30bd6b908f - Show all commits

View File

@ -1399,13 +1399,12 @@ endif()
# Test SIMD support, before platform includes to determine if sse2neon is needed.
if(WITH_CPU_SIMD)
set(COMPILER_SSE_FLAG)
set(COMPILER_SSE2_FLAG)
set(COMPILER_SSE42_FLAG)
# Test Neon first since macOS Arm can compile and run x86-64 SSE binaries.
test_neon_support()
if(NOT SUPPORT_NEON_BUILD)
test_sse_support(COMPILER_SSE_FLAG COMPILER_SSE2_FLAG)
test_sse_support(COMPILER_SSE42_FLAG)
endif()
endif()
@ -1445,8 +1444,6 @@ endif()
# Enable SIMD support if detected by `test_sse_support()` or `test_neon_support()`.
#
# This is done globally, so that all modules can use it if available, and
# because these are used in headers used by many modules.
if(WITH_CPU_SIMD)
if(SUPPORT_NEON_BUILD)
# Neon
@ -1456,15 +1453,20 @@ if(WITH_CPU_SIMD)
endif()
else()
# SSE
if(SUPPORT_SSE_BUILD)
string(PREPEND PLATFORM_CFLAGS "${COMPILER_SSE_FLAG} ")
add_definitions(-D__SSE__ -D__MMX__)
endif()
if(SUPPORT_SSE2_BUILD)
string(APPEND PLATFORM_CFLAGS " ${COMPILER_SSE2_FLAG}")
add_definitions(-D__SSE2__)
if(NOT SUPPORT_SSE_BUILD) # don't double up
add_definitions(-D__MMX__)
if(SUPPORT_SSE42_BUILD)
string(APPEND CMAKE_CXX_FLAGS " ${COMPILER_SSE42_FLAG}")
string(APPEND CMAKE_C_FLAGS " ${COMPILER_SSE42_FLAG}")
# MSVC doesn't define any of these and only does the AVX and higher flags.
# For consistency we define these flags for MSVC.
if(WIN32)
add_compile_definitions(
__MMX__
__SSE__
__SSE2__
__SSE3__
__SSE4_1__
__SSE4_2__
)
endif()
endif()
endif()
@ -1479,10 +1481,8 @@ if(FIRST_RUN)
else()
message(STATUS "Neon SIMD instructions detected but unused, requires sse2neon")
endif()
elseif(SUPPORT_SSE2_BUILD)
message(STATUS "SSE2 SIMD instructions enabled")
elseif(SUPPORT_SSE_BUILD)
message(STATUS "SSE SIMD instructions enabled")
elseif(SUPPORT_SSE42_BUILD)
message(STATUS "SSE42 SIMD instructions enabled")
else()
message(STATUS "No SIMD instructions detected")
endif()

View File

@ -540,49 +540,37 @@ function(setup_platform_linker_libs
endfunction()
macro(TEST_SSE_SUPPORT
_sse_flags
_sse2_flags)
_sse42_flags)
include(CheckCSourceRuns)
# message(STATUS "Detecting SSE support")
if(CMAKE_COMPILER_IS_GNUCC OR (CMAKE_C_COMPILER_ID MATCHES "Clang"))
set(${_sse_flags} "-msse")
set(${_sse2_flags} "-msse2")
set(${_sse42_flags} "-march=x86-64-v2")
elseif(MSVC)
# x86_64 has this auto enabled
if("${CMAKE_SIZEOF_VOID_P}" EQUAL "8")
set(${_sse_flags} "")
set(${_sse2_flags} "")
else()
set(${_sse_flags} "/arch:SSE")
set(${_sse2_flags} "/arch:SSE2")
endif()
# msvc has no specific build flags for SSE42, but when using intrinsics it will
# generate the right instructions.
set(${_sse42_flags} "")
elseif(CMAKE_C_COMPILER_ID STREQUAL "Intel")
set(${_sse_flags} "") # icc defaults to -msse
set(${_sse2_flags} "") # icc defaults to -msse2
if(WIN32)
set(${_sse42_flags} "/QxSSE4.2")
else()
set(${_sse42_flags} "-xsse4.2")
endif()
else()
message(WARNING "SSE flags for this compiler: '${CMAKE_C_COMPILER_ID}' not known")
set(${_sse_flags})
set(${_sse2_flags})
set(${_sse42_flags})
endif()
set(CMAKE_REQUIRED_FLAGS "${${_sse_flags}} ${${_sse2_flags}}")
set(CMAKE_REQUIRED_FLAGS "${${_sse42_flags}}")
if(NOT DEFINED SUPPORT_SSE_BUILD)
# result cached
check_c_source_runs("
#include <xmmintrin.h>
int main(void) { __m128 v = _mm_setzero_ps(); return 0; }"
SUPPORT_SSE_BUILD)
endif()
if(NOT DEFINED SUPPORT_SSE2_BUILD)
if(NOT DEFINED SUPPORT_SSE42_BUILD)
# result cached
check_c_source_runs("
#include <nmmintrin.h>
#include <emmintrin.h>
int main(void) { __m128d v = _mm_setzero_pd(); return 0; }"
SUPPORT_SSE2_BUILD)
int main(void) { __m128i v = _mm_setzero_si128(); v = _mm_cmpgt_epi64(v,v); return 0; }"
SUPPORT_SSE42_BUILD)
endif()
unset(CMAKE_REQUIRED_FLAGS)

View File

@ -181,3 +181,7 @@ endif()
if(WITH_FREESTYLE)
add_subdirectory(freestyle)
endif()
if(WITH_CPU_CHECK)
add_subdirectory(cpucheck)
endif()

View File

@ -582,14 +582,6 @@ struct SculptSession {
blender::float3 cursor_sampled_normal;
blender::float3 cursor_view_normal;
/* For Sculpt trimming gesture tools, initial ray-cast data from the position of the mouse
* when
* the gesture starts (intersection with the surface and if they ray hit the surface or not).
*/
blender::float3 gesture_initial_location;
blender::float3 gesture_initial_normal;
bool gesture_initial_hit;
/* TODO(jbakker): Replace rv3d and v3d with ViewContext */
RegionView3D *rv3d;
View3D *v3d;

View File

@ -98,18 +98,19 @@ void BKE_gpencil_cache_data_init(Depsgraph *depsgraph, Object *ob)
}
if (mmd->cache_data) {
BKE_shrinkwrap_free_tree(mmd->cache_data);
MEM_SAFE_FREE(mmd->cache_data);
MEM_delete(mmd->cache_data);
mmd->cache_data = nullptr;
}
Object *ob_target = DEG_get_evaluated_object(depsgraph, ob);
Mesh *target = BKE_modifier_get_evaluated_mesh_from_evaluated_object(ob_target);
mmd->cache_data = static_cast<ShrinkwrapTreeData *>(
MEM_callocN(sizeof(ShrinkwrapTreeData), __func__));
mmd->cache_data = MEM_new<ShrinkwrapTreeData>(__func__);
if (BKE_shrinkwrap_init_tree(
mmd->cache_data, target, mmd->shrink_type, mmd->shrink_mode, false))
{
}
else {
MEM_SAFE_FREE(mmd->cache_data);
MEM_delete(mmd->cache_data);
mmd->cache_data = nullptr;
}
break;
}
@ -136,7 +137,8 @@ void BKE_gpencil_cache_data_clear(Object *ob)
ShrinkwrapGpencilModifierData *mmd = (ShrinkwrapGpencilModifierData *)md;
if ((mmd) && (mmd->cache_data)) {
BKE_shrinkwrap_free_tree(mmd->cache_data);
MEM_SAFE_FREE(mmd->cache_data);
MEM_delete(mmd->cache_data);
mmd->cache_data = nullptr;
}
break;
}

View File

@ -321,11 +321,17 @@ void legacy_gpencil_frame_to_grease_pencil_drawing(const bGPDframe &gpf,
bool has_bezier_stroke = false;
LISTBASE_FOREACH (bGPDstroke *, gps, &gpf.strokes) {
if (gps->editcurve != nullptr) {
if (gps->editcurve->tot_curve_points == 0) {
continue;
}
has_bezier_stroke = true;
num_points += gps->editcurve->tot_curve_points;
curve_types.append(CURVE_TYPE_BEZIER);
}
else {
if (gps->totpoints == 0) {
continue;
}
num_points += gps->totpoints;
curve_types.append(CURVE_TYPE_POLY);
}
@ -333,13 +339,17 @@ void legacy_gpencil_frame_to_grease_pencil_drawing(const bGPDframe &gpf,
offsets.append(num_points);
}
/* Return if the legacy frame contains no strokes (or zero points). */
if (num_strokes == 0) {
return;
}
/* Resize the CurvesGeometry. */
Drawing &drawing = r_drawing.wrap();
CurvesGeometry &curves = drawing.strokes_for_write();
curves.resize(num_points, num_strokes);
if (num_strokes > 0) {
curves.offsets_for_write().copy_from(offsets);
}
curves.offsets_for_write().copy_from(offsets);
OffsetIndices<int> points_by_curve = curves.points_by_curve();
MutableAttributeAccessor attributes = curves.attributes_for_write();
@ -416,7 +426,16 @@ void legacy_gpencil_frame_to_grease_pencil_drawing(const bGPDframe &gpf,
Array<float4x2> legacy_texture_matrices(num_strokes);
int stroke_i = 0;
LISTBASE_FOREACH_INDEX (bGPDstroke *, gps, &gpf.strokes, stroke_i) {
LISTBASE_FOREACH (bGPDstroke *, gps, &gpf.strokes) {
/* In GPv2 strokes with 0 points could technically be represented. In `CurvesGeometry` this is
* not the case and would be a bug. So we explicitly make sure to skip over strokes with no
* points. */
if (gps->totpoints == 0 ||
(gps->editcurve != nullptr && gps->editcurve->tot_curve_points == 0))
{
continue;
}
stroke_cyclic.span[stroke_i] = (gps->flag & GP_STROKE_CYCLIC) != 0;
/* TODO: This should be a `double` attribute. */
stroke_init_times.span[stroke_i] = float(gps->inittime);
@ -428,10 +447,8 @@ void legacy_gpencil_frame_to_grease_pencil_drawing(const bGPDframe &gpf,
stroke_fill_colors[stroke_i] = ColorGeometry4f(gps->vert_color_fill);
stroke_materials.span[stroke_i] = gps->mat_nr;
IndexRange points = points_by_curve[stroke_i];
if (points.is_empty()) {
continue;
}
const IndexRange points = points_by_curve[stroke_i];
BLI_assert(points.size() == gps->totpoints);
const Span<bGPDspoint> src_points{gps->points, gps->totpoints};
/* Previously, Grease Pencil used a radius convention where 1 `px` = 0.001 units. This `px`
@ -512,6 +529,8 @@ void legacy_gpencil_frame_to_grease_pencil_drawing(const bGPDframe &gpf,
const float4x2 legacy_texture_matrix = get_legacy_texture_matrix(gps);
legacy_texture_matrices[stroke_i] = legacy_texture_matrix;
stroke_i++;
}
/* Ensure that the normals are up to date. */

View File

@ -0,0 +1,26 @@
# SPDX-FileCopyrightText: 2024 Blender Authors
#
# SPDX-License-Identifier: GPL-2.0-or-later
# The cpu check module cannot build with sse42 since it will be executed
# by cpus that may not support these instructions
if(COMPILER_SSE42_FLAG)
remove_cc_flag("${COMPILER_SSE42_FLAG}")
endif()
add_library(blender_cpu_check SHARED cpu_check.cc)
target_link_libraries(blender_cpu_check PRIVATE ${PLATFORM_LINKLIBS})
target_compile_definitions(blender_cpu_check PUBLIC WITH_CPU_CHECK)
if(NOT WIN32)
set(_LIB_SUB_FOLDER "lib/")
endif()
set_target_properties(blender_cpu_check
PROPERTIES
RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/bin/${_LIB_SUB_FOLDER}"
LIBRARY_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/bin/${_LIB_SUB_FOLDER}"
)
unset(_LIB_SUB_FOLDER)

View File

@ -276,12 +276,6 @@ static void compositor_engine_draw(void *data)
* the GPU for extended periods of time and sub-optimally schedule work for execution. */
GPU_flush();
}
else {
/* Realtime Compositor is not supported on macOS with the OpenGL backend. */
blender::StringRef("Viewport compositor is only supported on MacOS with the Metal Backend.")
.copy(compositor_data->info, GPU_INFO_SIZE);
return;
}
#endif
/* Execute Compositor render commands. */

View File

@ -3259,14 +3259,6 @@ void DRW_gpu_context_enable_ex(bool /*restore*/)
void DRW_gpu_context_disable_ex(bool restore)
{
if (DST.system_gpu_context != nullptr) {
#ifdef __APPLE__
/* Need to flush before disabling draw context, otherwise it does not
* always finish drawing and viewport can be empty or partially drawn */
if (GPU_type_matches_ex(GPU_DEVICE_ANY, GPU_OS_MAC, GPU_DRIVER_ANY, GPU_BACKEND_OPENGL)) {
GPU_flush();
}
#endif
if (BLI_thread_is_main() && restore) {
wm_window_reset_drawable();
}

View File

@ -315,7 +315,7 @@ void AssetClearHelper::reportResults(const bContext *C, ReportList &reports) con
&reports, RPT_INFO, "Data-block '%s' is not an asset anymore", stats.last_id->name + 2);
}
else {
BKE_reportf(&reports, RPT_INFO, "%i data-blocks are no assets anymore", stats.tot_cleared);
BKE_reportf(&reports, RPT_INFO, "%i data-blocks are not assets anymore", stats.tot_cleared);
}
}

View File

@ -1105,20 +1105,6 @@ void UI_widgetbase_draw_cache_end()
GPU_blend(GPU_BLEND_NONE);
}
/* Disable cached/instanced drawing and enforce single widget drawing pipeline.
* Works around interface artifacts happening on certain driver and hardware
* configurations. */
static bool draw_widgetbase_batch_skip_draw_cache()
{
/* MacOS is known to have issues on Mac Mini and MacBook Pro with Intel Iris GPU.
* For example, #78307. */
if (GPU_type_matches_ex(GPU_DEVICE_INTEL, GPU_OS_MAC, GPU_DRIVER_ANY, GPU_BACKEND_OPENGL)) {
return true;
}
return false;
}
static void draw_widgetbase_batch(uiWidgetBase *wtb)
{
wtb->uniform_params.tria_type = wtb->tria1.type;
@ -1127,7 +1113,7 @@ static void draw_widgetbase_batch(uiWidgetBase *wtb)
copy_v2_v2(wtb->uniform_params.tria1_center, wtb->tria1.center);
copy_v2_v2(wtb->uniform_params.tria2_center, wtb->tria2.center);
if (g_widget_base_batch.enabled && !draw_widgetbase_batch_skip_draw_cache()) {
if (g_widget_base_batch.enabled) {
g_widget_base_batch.params[g_widget_base_batch.count] = wtb->uniform_params;
g_widget_base_batch.count++;

View File

@ -284,16 +284,6 @@ void immDrawPixelsTexTiled_scaling_clipping(IMMDrawPixelsTexState *state,
immAttr2f(texco, left / float(tex_w), top / float(tex_h));
immVertex2f(pos, rast_x + offset_left * xzoom, rast_y + top * yzoom * scaleY);
immEnd();
/* NOTE: Weirdly enough this is only required on macOS. Without this there is some sort of
* bleeding of data is happening from tiles which are drawn later on.
* This doesn't seem to be too slow,
* but still would be nice to have fast and nice solution. */
#ifdef __APPLE__
if (GPU_type_matches_ex(GPU_DEVICE_ANY, GPU_OS_MAC, GPU_DRIVER_ANY, GPU_BACKEND_OPENGL)) {
GPU_flush();
}
#endif
}
}

View File

@ -38,6 +38,7 @@
#include "sculpt_intern.hh"
namespace blender::ed::sculpt_paint::trim {
enum class OperationType {
Intersect = 0,
Difference = 1,
@ -105,20 +106,24 @@ static EnumPropertyItem solver_modes[] = {
struct TrimOperation {
gesture::Operation op;
/* Operation-generated geometry. */
Mesh *mesh;
float (*true_mesh_co)[3];
float depth_front;
float depth_back;
/* Operator properties. */
bool use_cursor_depth;
bool initial_hit;
blender::float3 initial_location;
blender::float3 initial_normal;
OperationType mode;
SolverMode solver_mode;
OrientationType orientation;
ExtrudeMode extrude_mode;
};
/* Recalculate the mesh normals for the generated trim mesh. */
static void update_normals(gesture::GestureData &gesture_data)
{
TrimOperation *trim_operation = (TrimOperation *)gesture_data.operation;
@ -151,8 +156,8 @@ static void update_normals(gesture::GestureData &gesture_data)
trim_operation->mesh = result;
}
/* Get the origin and normal that are going to be used for calculating the depth and position the
* trimming geometry. */
/* Get the origin and normal that are going to be used for calculating the depth and position of
* the trimming geometry. */
static void get_origin_and_normal(gesture::GestureData &gesture_data,
float *r_origin,
float *r_normal)
@ -166,23 +171,26 @@ static void get_origin_and_normal(gesture::GestureData &gesture_data,
case OrientationType::View:
mul_v3_m4v3(r_origin,
gesture_data.vc.obact->object_to_world().ptr(),
gesture_data.ss->gesture_initial_location);
trim_operation->initial_location);
copy_v3_v3(r_normal, gesture_data.world_space_view_normal);
negate_v3(r_normal);
break;
case OrientationType::Surface:
mul_v3_m4v3(r_origin,
gesture_data.vc.obact->object_to_world().ptr(),
gesture_data.ss->gesture_initial_location);
trim_operation->initial_location);
/* Transforming the normal does not take non uniform scaling into account. Sculpt mode is not
* expected to work on object with non uniform scaling. */
copy_v3_v3(r_normal, gesture_data.ss->gesture_initial_normal);
copy_v3_v3(r_normal, trim_operation->initial_normal);
mul_mat3_m4_v3(gesture_data.vc.obact->object_to_world().ptr(), r_normal);
break;
}
}
static void calculate_depth(gesture::GestureData &gesture_data)
/* Calculates the depth of the drawn shape inside the scene.*/
static void calculate_depth(gesture::GestureData &gesture_data,
float &r_depth_front,
float &r_depth_back)
{
TrimOperation *trim_operation = (TrimOperation *)gesture_data.operation;
@ -197,8 +205,8 @@ static void calculate_depth(gesture::GestureData &gesture_data)
get_origin_and_normal(gesture_data, shape_origin, shape_normal);
plane_from_point_normal_v3(shape_plane, shape_origin, shape_normal);
trim_operation->depth_front = FLT_MAX;
trim_operation->depth_back = -FLT_MAX;
float depth_front = FLT_MAX;
float depth_back = -FLT_MAX;
for (int i = 0; i < totvert; i++) {
PBVHVertRef vertex = BKE_pbvh_index_to_vertex(ss->pbvh, i);
@ -210,35 +218,33 @@ static void calculate_depth(gesture::GestureData &gesture_data)
float world_space_vco[3];
mul_v3_m4v3(world_space_vco, vc->obact->object_to_world().ptr(), vco);
const float dist = dist_signed_to_plane_v3(world_space_vco, shape_plane);
trim_operation->depth_front = min_ff(dist, trim_operation->depth_front);
trim_operation->depth_back = max_ff(dist, trim_operation->depth_back);
depth_front = min_ff(dist, depth_front);
depth_back = max_ff(dist, depth_back);
}
if (trim_operation->use_cursor_depth) {
float world_space_gesture_initial_location[3];
mul_v3_m4v3(world_space_gesture_initial_location,
vc->obact->object_to_world().ptr(),
ss->gesture_initial_location);
trim_operation->initial_location);
float mid_point_depth;
if (trim_operation->orientation == OrientationType::View) {
mid_point_depth = ss->gesture_initial_hit ?
mid_point_depth = trim_operation->initial_hit ?
dist_signed_to_plane_v3(world_space_gesture_initial_location,
shape_plane) :
(trim_operation->depth_back + trim_operation->depth_front) * 0.5f;
(depth_back + depth_front) * 0.5f;
}
else {
/* When using normal orientation, if the stroke started over the mesh, position the mid point
* at 0 distance from the shape plane. This positions the trimming shape half inside of the
* surface. */
mid_point_depth = ss->gesture_initial_hit ?
0.0f :
(trim_operation->depth_back + trim_operation->depth_front) * 0.5f;
mid_point_depth = trim_operation->initial_hit ? 0.0f : (depth_back + depth_front) * 0.5f;
}
float depth_radius;
if (ss->gesture_initial_hit) {
if (trim_operation->initial_hit) {
depth_radius = ss->cursor_radius;
}
else {
@ -253,16 +259,19 @@ static void calculate_depth(gesture::GestureData &gesture_data)
if (!BKE_brush_use_locked_size(scene, brush)) {
depth_radius = paint_calc_object_space_radius(
vc, ss->gesture_initial_location, BKE_brush_size_get(scene, brush));
vc, trim_operation->initial_location, BKE_brush_size_get(scene, brush));
}
else {
depth_radius = BKE_brush_unprojected_radius_get(scene, brush);
}
}
trim_operation->depth_front = mid_point_depth - depth_radius;
trim_operation->depth_back = mid_point_depth + depth_radius;
depth_front = mid_point_depth - depth_radius;
depth_back = mid_point_depth + depth_radius;
}
r_depth_front = depth_front;
r_depth_back = depth_back;
}
static void generate_geometry(gesture::GestureData &gesture_data)
@ -281,20 +290,6 @@ static void generate_geometry(gesture::GestureData &gesture_data)
trim_operation->true_mesh_co = static_cast<float(*)[3]>(
MEM_malloc_arrayN(trim_totverts, sizeof(float[3]), "mesh orco"));
float depth_front = trim_operation->depth_front;
float depth_back = trim_operation->depth_back;
float pad_factor = 0.0f;
if (!trim_operation->use_cursor_depth) {
pad_factor = (depth_back - depth_front) * 0.01f + 0.001f;
/* When using cursor depth, don't modify the depth set by the cursor radius. If full depth is
* used, adding a little padding to the trimming shape can help avoiding booleans with coplanar
* faces. */
depth_front -= pad_factor;
depth_back += pad_factor;
}
float shape_origin[3];
float shape_normal[3];
float shape_plane[4];
@ -306,6 +301,20 @@ static void generate_geometry(gesture::GestureData &gesture_data)
/* Write vertices coordinates OperationType::Difference for the front face. */
MutableSpan<float3> positions = trim_operation->mesh->vert_positions_for_write();
float depth_front;
float depth_back;
calculate_depth(gesture_data, depth_front, depth_back);
if (!trim_operation->use_cursor_depth) {
float pad_factor = (depth_back - depth_front) * 0.01f + 0.001f;
/* When using cursor depth, don't modify the depth set by the cursor radius. If full depth is
* used, adding a little padding to the trimming shape can help avoiding booleans with coplanar
* faces. */
depth_front -= pad_factor;
depth_back += pad_factor;
}
float depth_point[3];
/* Get origin point for OrientationType::View.
@ -436,11 +445,16 @@ static void generate_geometry(gesture::GestureData &gesture_data)
update_normals(gesture_data);
}
static void free_geometry(gesture::GestureData &gesture_data)
static void gesture_begin(bContext &C, gesture::GestureData &gesture_data)
{
TrimOperation *trim_operation = (TrimOperation *)gesture_data.operation;
BKE_id_free(nullptr, trim_operation->mesh);
MEM_freeN(trim_operation->true_mesh_co);
Object *object = gesture_data.vc.obact;
SculptSession *ss = object->sculpt;
Depsgraph *depsgraph = CTX_data_ensure_evaluated_depsgraph(&C);
generate_geometry(gesture_data);
SCULPT_topology_islands_invalidate(ss);
BKE_sculpt_update_object_for_edit(depsgraph, gesture_data.vc.obact, false);
undo::push_node(gesture_data.vc.obact, nullptr, undo::Type::Geometry);
}
static int bm_face_isect_pair(BMFace *f, void * /*user_data*/)
@ -545,19 +559,6 @@ static void apply_trim(gesture::GestureData &gesture_data)
result, static_cast<Mesh *>(gesture_data.vc.obact->data), gesture_data.vc.obact);
}
static void gesture_begin(bContext &C, gesture::GestureData &gesture_data)
{
Object *object = gesture_data.vc.obact;
SculptSession *ss = object->sculpt;
Depsgraph *depsgraph = CTX_data_ensure_evaluated_depsgraph(&C);
calculate_depth(gesture_data);
generate_geometry(gesture_data);
SCULPT_topology_islands_invalidate(ss);
BKE_sculpt_update_object_for_edit(depsgraph, gesture_data.vc.obact, false);
undo::push_node(gesture_data.vc.obact, nullptr, undo::Type::Geometry);
}
static void gesture_apply_for_symmetry_pass(bContext & /*C*/, gesture::GestureData &gesture_data)
{
TrimOperation *trim_operation = (TrimOperation *)gesture_data.operation;
@ -570,6 +571,13 @@ static void gesture_apply_for_symmetry_pass(bContext & /*C*/, gesture::GestureDa
apply_trim(gesture_data);
}
static void free_geometry(gesture::GestureData &gesture_data)
{
TrimOperation *trim_operation = (TrimOperation *)gesture_data.operation;
BKE_id_free(nullptr, trim_operation->mesh);
MEM_freeN(trim_operation->true_mesh_co);
}
static void gesture_end(bContext & /*C*/, gesture::GestureData &gesture_data)
{
Object *object = gesture_data.vc.obact;
@ -606,13 +614,27 @@ static void init_operation(gesture::GestureData &gesture_data, wmOperator &op)
trim_operation->solver_mode = SolverMode(RNA_enum_get(op.ptr, "trim_solver"));
/* If the cursor was not over the mesh, force the orientation to view. */
if (!gesture_data.ss->gesture_initial_hit) {
if (!trim_operation->initial_hit) {
trim_operation->orientation = OrientationType::View;
}
}
static void operator_properties(wmOperatorType *ot)
{
PropertyRNA *prop;
prop = RNA_def_int_vector(ot->srna,
"location",
2,
nullptr,
INT_MIN,
INT_MAX,
"Location",
"Mouse location",
INT_MIN,
INT_MAX);
RNA_def_property_flag(prop, PROP_HIDDEN | PROP_SKIP_SAVE);
RNA_def_enum(ot->srna,
"trim_mode",
operation_types,
@ -669,21 +691,26 @@ static bool can_exec(const bContext &C)
return true;
}
static void initialize_cursor_info(bContext &C, const wmEvent *event)
static void initialize_cursor_info(bContext &C,
const wmOperator &op,
gesture::GestureData &gesture_data)
{
const Object &ob = *CTX_data_active_object(&C);
SculptSession &ss = *ob.sculpt;
SCULPT_vertex_random_access_ensure(&ss);
SculptCursorGeometryInfo sgi;
const float mval_fl[2] = {float(event->mval[0]), float(event->mval[1])};
int mval[2];
RNA_int_get_array(op.ptr, "location", mval);
/* TODO: Remove gesture_* properties from SculptSession */
ss.gesture_initial_hit = SCULPT_cursor_geometry_info_update(&C, &sgi, mval_fl, false);
if (ss.gesture_initial_hit) {
copy_v3_v3(ss.gesture_initial_location, sgi.location);
copy_v3_v3(ss.gesture_initial_normal, sgi.normal);
SculptCursorGeometryInfo sgi;
const float mval_fl[2] = {float(mval[0]), float(mval[1])};
TrimOperation *trim_operation = (TrimOperation *)gesture_data.operation;
trim_operation->initial_hit = SCULPT_cursor_geometry_info_update(&C, &sgi, mval_fl, false);
if (trim_operation->initial_hit) {
copy_v3_v3(trim_operation->initial_location, sgi.location);
copy_v3_v3(trim_operation->initial_normal, sgi.normal);
}
}
@ -698,7 +725,9 @@ static int gesture_box_exec(bContext *C, wmOperator *op)
return OPERATOR_CANCELLED;
}
initialize_cursor_info(*C, *op, *gesture_data);
init_operation(*gesture_data, *op);
gesture::apply(*C, *gesture_data, *op);
return OPERATOR_FINISHED;
}
@ -709,7 +738,7 @@ static int gesture_box_invoke(bContext *C, wmOperator *op, const wmEvent *event)
return OPERATOR_CANCELLED;
}
initialize_cursor_info(*C, event);
RNA_int_set_array(op->ptr, "location", event->mval);
return WM_gesture_box_invoke(C, op, event);
}
@ -724,7 +753,10 @@ static int gesture_lasso_exec(bContext *C, wmOperator *op)
if (!gesture_data) {
return OPERATOR_CANCELLED;
}
initialize_cursor_info(*C, *op, *gesture_data);
init_operation(*gesture_data, *op);
gesture::apply(*C, *gesture_data, *op);
return OPERATOR_FINISHED;
}
@ -735,7 +767,7 @@ static int gesture_lasso_invoke(bContext *C, wmOperator *op, const wmEvent *even
return OPERATOR_CANCELLED;
}
initialize_cursor_info(*C, event);
RNA_int_set_array(op->ptr, "location", event->mval);
return WM_gesture_lasso_invoke(C, op, event);
}

View File

@ -1332,7 +1332,8 @@ static void create_inspection_string_for_generic_value(const bNodeSocket &socket
((*static_cast<bool *>(socket_value)) ? TIP_("True") : TIP_("False")));
}
else if (socket_type.is<float4x4>()) {
const float4x4 &value = *static_cast<const float4x4 *>(socket_value);
/* Transpose to be able to print row by row. */
const float4x4 value = math::transpose(*static_cast<const float4x4 *>(socket_value));
ss << value[0] << ",\n";
ss << value[1] << ",\n";
ss << value[2] << ",\n";
@ -1480,8 +1481,7 @@ static void create_inspection_string_for_geometry_info(const geo_log::GeometryIn
}
static void create_inspection_string_for_geometry_socket(std::stringstream &ss,
const nodes::decl::Geometry *socket_decl,
const bool after_log)
const nodes::decl::Geometry *socket_decl)
{
/* If the geometry declaration is null, as is the case for input to group output,
* or it is an output socket don't show supported types. */
@ -1489,10 +1489,6 @@ static void create_inspection_string_for_geometry_socket(std::stringstream &ss,
return;
}
if (after_log) {
ss << ".\n\n";
}
Span<bke::GeometryComponent::Type> supported_types = socket_decl->supported_types();
if (supported_types.is_empty()) {
ss << TIP_("Supported: All Types");
@ -1536,17 +1532,34 @@ static void create_inspection_string_for_geometry_socket(std::stringstream &ss,
}
}
static std::optional<std::string> create_socket_inspection_string(
geo_log::GeoTreeLog &geo_tree_log, const bNodeSocket &socket)
static std::optional<std::string> create_description_inspection_string(const bNodeSocket &socket)
{
if (socket.runtime->declaration == nullptr) {
return std::nullopt;
}
const blender::nodes::SocketDeclaration &socket_decl = *socket.runtime->declaration;
blender::StringRefNull description = socket_decl.description;
if (description.is_empty()) {
return std::nullopt;
}
return TIP_(description.c_str());
}
static std::optional<std::string> create_log_inspection_string(geo_log::GeoTreeLog *geo_tree_log,
const bNodeSocket &socket)
{
using namespace blender::nodes::geo_eval_log;
if (geo_tree_log == nullptr) {
return std::nullopt;
}
if (socket.typeinfo->base_cpp_type == nullptr) {
return std::nullopt;
}
geo_tree_log.ensure_socket_values();
ValueLog *value_log = geo_tree_log.find_socket_value_log(socket);
geo_tree_log->ensure_socket_values();
ValueLog *value_log = geo_tree_log->find_socket_value_log(socket);
std::stringstream ss;
if (const geo_log::GenericValueLog *generic_value_log =
dynamic_cast<const geo_log::GenericValueLog *>(value_log))
@ -1564,11 +1577,20 @@ static std::optional<std::string> create_socket_inspection_string(
create_inspection_string_for_geometry_info(*geo_value_log, ss);
}
std::string str = ss.str();
if (str.empty()) {
return std::nullopt;
}
return str;
}
static std::optional<std::string> create_declaration_inspection_string(const bNodeSocket &socket)
{
std::stringstream ss;
if (const nodes::decl::Geometry *socket_decl = dynamic_cast<const nodes::decl::Geometry *>(
socket.runtime->declaration))
{
const bool after_log = value_log != nullptr;
create_inspection_string_for_geometry_socket(ss, socket_decl, after_log);
create_inspection_string_for_geometry_socket(ss, socket_decl);
}
std::string str = ss.str();
@ -1590,15 +1612,6 @@ static std::string node_socket_get_tooltip(const SpaceNode *snode,
}
}
std::stringstream output;
if (socket.runtime->declaration != nullptr) {
const blender::nodes::SocketDeclaration &socket_decl = *socket.runtime->declaration;
blender::StringRefNull description = socket_decl.description;
if (!description.is_empty()) {
output << TIP_(description.c_str());
}
}
geo_log::GeoTreeLog *geo_tree_log = [&]() -> geo_log::GeoTreeLog * {
const bNodeTreeZones *zones = ntree.zones();
if (!zones) {
@ -1608,27 +1621,45 @@ static std::string node_socket_get_tooltip(const SpaceNode *snode,
return tree_draw_ctx.geo_log_by_zone.lookup_default(zone, nullptr);
}();
if (ntree.type == NTREE_GEOMETRY && geo_tree_log != nullptr) {
if (!output.str().empty()) {
Vector<std::string> inspection_strings;
if (std::optional<std::string> info = create_description_inspection_string(socket)) {
inspection_strings.append(std::move(*info));
}
if (std::optional<std::string> info = create_log_inspection_string(geo_tree_log, socket)) {
inspection_strings.append(std::move(*info));
}
if (std::optional<std::string> info = create_declaration_inspection_string(socket)) {
inspection_strings.append(std::move(*info));
}
std::stringstream output;
for (const std::string &info : inspection_strings) {
output << info;
if (&info != &inspection_strings.last()) {
output << ".\n\n";
}
}
std::optional<std::string> socket_inspection_str = create_socket_inspection_string(
*geo_tree_log, socket);
if (socket_inspection_str.has_value()) {
output << *socket_inspection_str;
if (inspection_strings.is_empty()) {
const bNode &node = socket.owner_node();
if (node.is_reroute()) {
char reroute_name[MAX_NAME];
bke::nodeLabel(&ntree, &node, reroute_name, sizeof(reroute_name));
output << reroute_name;
}
else {
output << bke::nodeSocketLabel(&socket);
}
if (ntree.type == NTREE_GEOMETRY) {
output << ".\n\n";
output << TIP_(
"Unknown socket value. Either the socket was not used or its value was not logged "
"during the last evaluation");
}
}
if (output.str().empty()) {
output << bke::nodeSocketLabel(&socket);
}
return output.str();
}

View File

@ -7,6 +7,7 @@
#include <fmt/format.h>
#include "BLI_math_matrix.hh"
#include "BLI_math_quaternion_types.hh"
#include "BLI_math_vector_types.hh"
@ -395,7 +396,8 @@ class SpreadsheetLayoutDrawer : public SpreadsheetDrawer {
UI_but_func_tooltip_set(
but,
[](bContext * /*C*/, void *argN, const char * /*tip*/) {
const float4x4 &value = *static_cast<const float4x4 *>(argN);
/* Transpose to be able to print row by row. */
const float4x4 value = math::transpose(*static_cast<const float4x4 *>(argN));
std::stringstream ss;
ss << value[0] << ",\n";
ss << value[1] << ",\n";

View File

@ -238,6 +238,10 @@ ReverseUVSampler::Result ReverseUVSampler::sample(const float2 &query_uv) const
* the lookup can fail for floating point accuracy reasons when the uv is almost exact on an
* edge. */
const float edge_epsilon = 0.00001f;
/* If uv triangles are very small, it may look like the query hits multiple triangles due to
* floating point precision issues. Better just pick one of the triangles instead of failing the
* entire operation in this case. */
const float area_epsilon = 0.00001f;
for (const int tri_i : tri_indices) {
const int3 &tri = corner_tris_[tri_i];
@ -260,8 +264,14 @@ ReverseUVSampler::Result ReverseUVSampler::sample(const float2 &query_uv) const
const float worse_dist = std::max(dist, best_dist);
/* Allow ignoring multiple triangle intersections if the uv is almost exactly on an edge. */
if (worse_dist < -edge_epsilon) {
/* The uv sample is in multiple triangles. */
return Result{ResultType::Multiple};
const int3 &best_tri = corner_tris_[tri_i];
const float best_tri_area = area_tri_v2(
uv_map_[best_tri[0]], uv_map_[best_tri[1]], uv_map_[best_tri[2]]);
const float current_tri_area = area_tri_v2(uv_0, uv_1, uv_2);
if (best_tri_area > area_epsilon && current_tri_area > area_epsilon) {
/* The uv sample is in multiple triangles. */
return Result{ResultType::Multiple};
}
}
}

View File

@ -139,12 +139,12 @@ static void bake_modifier(Main * /*bmain*/,
/* Recalculate shrinkwrap data. */
if (mmd->cache_data) {
BKE_shrinkwrap_free_tree(mmd->cache_data);
MEM_SAFE_FREE(mmd->cache_data);
MEM_delete(mmd->cache_data);
mmd->cache_data = nullptr;
}
Object *ob_target = DEG_get_evaluated_object(depsgraph, mmd->target);
Mesh *target = BKE_modifier_get_evaluated_mesh_from_evaluated_object(ob_target);
mmd->cache_data = static_cast<ShrinkwrapTreeData *>(
MEM_callocN(sizeof(ShrinkwrapTreeData), __func__));
mmd->cache_data = MEM_new<ShrinkwrapTreeData>(__func__);
if (BKE_shrinkwrap_init_tree(
mmd->cache_data, target, mmd->shrink_type, mmd->shrink_mode, false))
{
@ -157,7 +157,8 @@ static void bake_modifier(Main * /*bmain*/,
/* Free data. */
if (mmd->cache_data) {
BKE_shrinkwrap_free_tree(mmd->cache_data);
MEM_SAFE_FREE(mmd->cache_data);
MEM_delete(mmd->cache_data);
mmd->cache_data = nullptr;
}
}
}
@ -172,7 +173,7 @@ static void free_data(GpencilModifierData *md)
ShrinkwrapGpencilModifierData *mmd = (ShrinkwrapGpencilModifierData *)md;
if (mmd->cache_data) {
BKE_shrinkwrap_free_tree(mmd->cache_data);
MEM_SAFE_FREE(mmd->cache_data);
MEM_delete(mmd->cache_data);
}
}

View File

@ -467,7 +467,6 @@ void gpu_shader_create_info_init()
GPU_OS_ANY,
GPU_DRIVER_ANY,
GPU_BACKEND_OPENGL) ||
GPU_type_matches_ex(GPU_DEVICE_ANY, GPU_OS_MAC, GPU_DRIVER_ANY, GPU_BACKEND_OPENGL) ||
GPU_crappy_amd_driver())
{
draw_modelmat = draw_modelmat_legacy;

View File

@ -1902,7 +1902,7 @@ void MSLGeneratorInterface::prepare_from_createinfo(const shader::ShaderCreateIn
case shader::ShaderCreateInfo::Resource::BindType::IMAGE: {
/* Flatten qualifier flags into final access state. */
MSLTextureSamplerAccess access;
if (bool(res.image.qualifiers & Qualifier::READ_WRITE)) {
if ((res.image.qualifiers & Qualifier::READ_WRITE) == Qualifier::READ_WRITE) {
access = MSLTextureSamplerAccess::TEXTURE_ACCESS_READWRITE;
}
else if (bool(res.image.qualifiers & Qualifier::WRITE)) {

View File

@ -437,6 +437,12 @@ static const char *load_face_element(PlyReadBuffer &file,
if (count < 1 || count > 255) {
return "Invalid face size, must be between 1 and 255";
}
/* Previous python based importer was accepting faces with fewer
* than 3 vertices, and silently dropping them. */
if (count < 3) {
fprintf(stderr, "PLY Importer: ignoring face %i (%i vertices)\n", i, count);
continue;
}
for (int j = 0; j < count; j++) {
int index;
@ -467,15 +473,22 @@ static const char *load_face_element(PlyReadBuffer &file,
scratch.resize(count * data_type_size[prop.type]);
file.read_bytes(scratch.data(), scratch.size());
ptr = scratch.data();
if (header.type == PlyFormatType::BINARY_BE) {
endian_switch_array((uint8_t *)ptr, data_type_size[prop.type], count);
/* Previous python based importer was accepting faces with fewer
* than 3 vertices, and silently dropping them. */
if (count < 3) {
fprintf(stderr, "PLY Importer: ignoring face %i (%i vertices)\n", i, count);
}
for (int j = 0; j < count; ++j) {
uint32_t index = get_binary_value<uint32_t>(prop.type, ptr);
data->face_vertices.append(index);
else {
ptr = scratch.data();
if (header.type == PlyFormatType::BINARY_BE) {
endian_switch_array((uint8_t *)ptr, data_type_size[prop.type], count);
}
for (int j = 0; j < count; ++j) {
uint32_t index = get_binary_value<uint32_t>(prop.type, ptr);
data->face_vertices.append(index);
}
data->face_sizes.append(count);
}
data->face_sizes.append(count);
/* Skip any properties after vertex indices. */
for (int j = prop_index + 1; j < element.properties.size(); j++) {

View File

@ -909,8 +909,7 @@ void USDMeshReader::read_custom_data(const ImportSettings *settings,
/* To avoid unnecessarily reloading static primvars during animation,
* early out if not first load and this primvar isn't animated. */
const bool is_time_varying = primvar_varying_map_.lookup_default(name, false);
if (!new_mesh && !is_time_varying) {
if (!new_mesh && primvar_varying_map_.contains(name) && !primvar_varying_map_.lookup(name)) {
continue;
}

View File

@ -69,11 +69,6 @@ set(SRC
dna_utils.cc
makesdna.cc
${SRC_BLENLIB}
../../../../intern/guardedalloc/intern/leak_detector.cc
../../../../intern/guardedalloc/intern/mallocn.c
../../../../intern/guardedalloc/intern/mallocn_guarded_impl.c
../../../../intern/guardedalloc/intern/mallocn_lockfree_impl.c
../../../../intern/guardedalloc/intern/memory_usage.cc
${dna_header_include_file}
${dna_header_string_file}
)

View File

@ -222,12 +222,6 @@ set(SRC
${DEFSRC}
${APISRC}
../../../../intern/clog/clog.c
../../../../intern/guardedalloc/intern/leak_detector.cc
../../../../intern/guardedalloc/intern/mallocn.c
../../../../intern/guardedalloc/intern/mallocn_guarded_impl.c
../../../../intern/guardedalloc/intern/mallocn_lockfree_impl.c
../../../../intern/guardedalloc/intern/memory_usage.cc
# Needed for defaults.
../../../../release/datafiles/userdef/userdef_default.c
../../../../release/datafiles/userdef/userdef_default_theme.c

View File

@ -74,7 +74,7 @@ static void free_data(ModifierData *md)
if (smd->cache_data) {
BKE_shrinkwrap_free_tree(smd->cache_data);
MEM_SAFE_FREE(smd->cache_data);
MEM_delete(smd->cache_data);
}
}
@ -196,17 +196,18 @@ static void ensure_shrinkwrap_cache_data(GreasePencilShrinkwrapModifierData &smd
{
if (smd.cache_data) {
BKE_shrinkwrap_free_tree(smd.cache_data);
MEM_SAFE_FREE(smd.cache_data);
MEM_delete(smd.cache_data);
smd.cache_data = nullptr;
}
Object *target_ob = DEG_get_evaluated_object(ctx.depsgraph, smd.target);
Mesh *target_mesh = BKE_modifier_get_evaluated_mesh_from_evaluated_object(target_ob);
smd.cache_data = static_cast<ShrinkwrapTreeData *>(
MEM_callocN(sizeof(ShrinkwrapTreeData), __func__));
smd.cache_data = MEM_new<ShrinkwrapTreeData>(__func__);
const bool tree_ok = BKE_shrinkwrap_init_tree(
smd.cache_data, target_mesh, smd.shrink_type, smd.shrink_mode, false);
if (!tree_ok) {
MEM_SAFE_FREE(smd.cache_data);
MEM_delete(smd.cache_data);
smd.cache_data = nullptr;
}
}

View File

@ -14,7 +14,9 @@ static void node_declare(NodeDeclarationBuilder &b)
{
b.is_function_node();
b.add_input<decl::Matrix>("Matrix");
b.add_output<decl::Matrix>("Matrix");
b.add_output<decl::Matrix>("Matrix").description(
"The inverted matrix or the identity matrix if the input is not invertable");
b.add_output<decl::Bool>("Invertable").description("True if the input matrix is invertable");
}
static void search_link_ops(GatherLinkSearchOpParams &params)
@ -24,10 +26,45 @@ static void search_link_ops(GatherLinkSearchOpParams &params)
}
}
class InvertMatrixFunction : public mf::MultiFunction {
public:
InvertMatrixFunction()
{
static mf::Signature signature = []() {
mf::Signature signature;
mf::SignatureBuilder builder{"Invert Matrix", signature};
builder.single_input<float4x4>("Matrix");
builder.single_output<float4x4>("Matrix", mf::ParamFlag::SupportsUnusedOutput);
builder.single_output<bool>("Invertable", mf::ParamFlag::SupportsUnusedOutput);
return signature;
}();
this->set_signature(&signature);
}
void call(const IndexMask &mask, mf::Params params, mf::Context /*context*/) const override
{
const VArraySpan<float4x4> in_matrices = params.readonly_single_input<float4x4>(0, "Matrix");
MutableSpan<float4x4> out_matrices = params.uninitialized_single_output_if_required<float4x4>(
1, "Matrix");
MutableSpan<bool> out_invertable = params.uninitialized_single_output_if_required<bool>(
2, "Invertable");
mask.foreach_index([&](const int64_t i) {
const float4x4 &matrix = in_matrices[i];
bool success;
float4x4 inverted_matrix = math::invert(matrix, success);
if (!out_matrices.is_empty()) {
out_matrices[i] = success ? inverted_matrix : float4x4::identity();
}
if (!out_invertable.is_empty()) {
out_invertable[i] = success;
}
});
}
};
static void node_build_multi_function(NodeMultiFunctionBuilder &builder)
{
static auto fn = mf::build::SI1_SO<float4x4, float4x4>(
"Invert Matrix", [](float4x4 matrix) { return math::invert(matrix); });
static InvertMatrixFunction fn;
builder.set_matching_fn(fn);
}

View File

@ -46,7 +46,7 @@ static void node_declare(NodeDeclarationBuilder &b)
b.add_output<decl::Geometry>("Points").propagate_all();
b.add_output<decl::Vector>("Tangent").field_on_all();
b.add_output<decl::Vector>("Normal").field_on_all();
b.add_output<decl::Vector>("Rotation").field_on_all();
b.add_output<decl::Rotation>("Rotation").field_on_all();
}
static void node_layout(uiLayout *layout, bContext * /*C*/, PointerRNA *ptr)
@ -76,12 +76,12 @@ static void node_update(bNodeTree *ntree, bNode *node)
static void fill_rotation_attribute(const Span<float3> tangents,
const Span<float3> normals,
MutableSpan<float3> rotations)
MutableSpan<math::Quaternion> rotations)
{
threading::parallel_for(IndexRange(rotations.size()), 512, [&](IndexRange range) {
for (const int i : range) {
rotations[i] = float3(
math::to_euler(math::from_orthonormal_axes<float4x4>(normals[i], tangents[i])));
rotations[i] = math::to_quaternion(
math::from_orthonormal_axes<float4x4>(normals[i], tangents[i]));
}
});
}
@ -121,8 +121,9 @@ static PointCloud *pointcloud_from_curves(bke::CurvesGeometry curves,
MutableAttributeAccessor attributes = curves.attributes_for_write();
const VArraySpan tangents = *attributes.lookup<float3>(tangent_id, AttrDomain::Point);
const VArraySpan normals = *attributes.lookup<float3>(normal_id, AttrDomain::Point);
SpanAttributeWriter<float3> rotations = attributes.lookup_or_add_for_write_only_span<float3>(
rotation_id, AttrDomain::Point);
SpanAttributeWriter<math::Quaternion> rotations =
attributes.lookup_or_add_for_write_only_span<math::Quaternion>(rotation_id,
AttrDomain::Point);
fill_rotation_attribute(tangents, normals, rotations.span);
rotations.finish();
}

View File

@ -333,29 +333,10 @@ if(WITH_PYTHON_MODULE)
else()
add_executable(blender ${EXETYPE} ${SRC})
if(WITH_CPU_CHECK)
target_compile_definitions(blender PRIVATE WITH_CPU_CHECK)
# we cannot directly link against any blender libraries for the cpu_check module
# as they may have been build for an ISA that is unsupported by the CPU
# running this code.
add_library(blender_cpu_check SHARED
creator_cpu_check.cc
)
target_link_libraries(blender_cpu_check
PRIVATE ${PLATFORM_LINKLIBS}
)
# blender_cpu_check *NEEDS* to be linked first, there can be no exceptions
# to this, this is to ensure this will be the first code to run once the
# blender binary has been loaded by the OS.
target_link_libraries(blender PRIVATE blender_cpu_check)
if(NOT WIN32)
set(_LIB_SUB_FOLDER "lib/")
endif()
set_target_properties(blender_cpu_check
PROPERTIES
RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/bin/${_LIB_SUB_FOLDER}"
LIBRARY_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/bin/${_LIB_SUB_FOLDER}"
)
unset(_LIB_SUB_FOLDER)
endif()
if(WIN32)
add_executable(blender-launcher WIN32