Geometry Nodes: add simulation support #104924

Closed
Hans Goudey wants to merge 211 commits from geometry-nodes-simulation into main

When changing the target branch, be careful to rebase the branch in your fork to match. See documentation.
54 changed files with 860 additions and 164 deletions
Showing only changes of commit ba7a634da5 - Show all commits

View File

@ -692,10 +692,12 @@ if(WITH_GHOST_WAYLAND)
if(WITH_GHOST_WAYLAND_LIBDECOR)
if(_use_system_wayland)
pkg_check_modules(libdecor REQUIRED libdecor-0>=0.1)
pkg_check_modules(libdecor libdecor-0>=0.1)
else()
set(libdecor_INCLUDE_DIRS "${LIBDIR}/wayland_libdecor/include/libdecor-0")
set(libdecor_FOUND ON)
endif()
set_and_warn_library_found("libdecor" libdecor_FOUND WITH_GHOST_WAYLAND_LIBDECOR)
endif()
if(WITH_GHOST_WAYLAND_DBUS)

View File

@ -224,10 +224,13 @@ ccl_device_intersect bool scene_intersect_local(KernelGlobals kg,
}
int blas_index = metal_ancillaries->blas_userID_to_index_lookUp[local_object];
// transform the ray into object's local space
Transform itfm = kernel_data_fetch(objects, local_object).itfm;
r.origin = transform_point(&itfm, r.origin);
r.direction = transform_direction(&itfm, r.direction);
if (!(kernel_data_fetch(object_flag, local_object) & SD_OBJECT_TRANSFORM_APPLIED)) {
// transform the ray into object's local space
Transform itfm = kernel_data_fetch(objects, local_object).itfm;
r.origin = transform_point(&itfm, r.origin);
r.direction = transform_direction(&itfm, r.direction);
}
intersection = metalrt_intersect.intersect(
r,

View File

@ -761,7 +761,7 @@ GHOST_IContext *GHOST_SystemCocoa::createOffscreenContext(GHOST_GLSettings glSet
#ifdef WITH_VULKAN_BACKEND
if (glSettings.context_type == GHOST_kDrawingContextTypeVulkan) {
const bool debug_context = (glSettings.flags & GHOST_glDebugContext) != 0;
GHOST_Context *context = new GHOST_ContextVK(false, NULL, 1, 0, debug_context);
GHOST_Context *context = new GHOST_ContextVK(false, NULL, 1, 2, debug_context);
if (!context->initializeDrawingContext()) {
delete context;
return NULL;

View File

@ -6298,7 +6298,7 @@ GHOST_IContext *GHOST_SystemWayland::createOffscreenContext(GHOST_GLSettings glS
wl_surface,
display_->wl_display,
1,
0,
2,
debug_context);
if (!context->initializeDrawingContext()) {

View File

@ -267,7 +267,7 @@ GHOST_IContext *GHOST_SystemWin32::createOffscreenContext(GHOST_GLSettings glSet
#ifdef WITH_VULKAN_BACKEND
/* Vulkan does not need a window. */
if (glSettings.context_type == GHOST_kDrawingContextTypeVulkan) {
context = new GHOST_ContextVK(false, (HWND)0, 1, 0, debug_context);
context = new GHOST_ContextVK(false, (HWND)0, 1, 2, debug_context);
if (!context->initializeDrawingContext()) {
delete context;

View File

@ -417,7 +417,7 @@ GHOST_IContext *GHOST_SystemX11::createOffscreenContext(GHOST_GLSettings glSetti
#ifdef WITH_VULKAN_BACKEND
if (glSettings.context_type == GHOST_kDrawingContextTypeVulkan) {
context = new GHOST_ContextVK(
false, GHOST_kVulkanPlatformX11, 0, m_display, NULL, NULL, 1, 0, debug_context);
false, GHOST_kVulkanPlatformX11, 0, m_display, NULL, NULL, 1, 2, debug_context);
if (!context->initializeDrawingContext()) {
delete context;

View File

@ -815,7 +815,7 @@ GHOST_Context *GHOST_WindowCocoa::newDrawingContext(GHOST_TDrawingContextType ty
{
#ifdef WITH_VULKAN_BACKEND
if (type == GHOST_kDrawingContextTypeVulkan) {
GHOST_Context *context = new GHOST_ContextVK(m_wantStereoVisual, m_metalLayer, 1, 0, true);
GHOST_Context *context = new GHOST_ContextVK(m_wantStereoVisual, m_metalLayer, 1, 2, true);
if (!context->initializeDrawingContext()) {
delete context;

View File

@ -1744,7 +1744,7 @@ GHOST_Context *GHOST_WindowWayland::newDrawingContext(GHOST_TDrawingContextType
window_->wl_surface,
system_->wl_display(),
1,
0,
2,
true);
break;
#endif

View File

@ -637,7 +637,7 @@ GHOST_Context *GHOST_WindowWin32::newDrawingContext(GHOST_TDrawingContextType ty
#ifdef WITH_VULKAN_BACKEND
else if (type == GHOST_kDrawingContextTypeVulkan) {
GHOST_Context *context = new GHOST_ContextVK(false, m_hWnd, 1, 0, m_debug_context);
GHOST_Context *context = new GHOST_ContextVK(false, m_hWnd, 1, 2, m_debug_context);
if (context->initializeDrawingContext()) {
return context;

View File

@ -1235,7 +1235,7 @@ GHOST_Context *GHOST_WindowX11::newDrawingContext(GHOST_TDrawingContextType type
NULL,
NULL,
1,
0,
2,
m_is_debug_context);
if (!context->initializeDrawingContext()) {

View File

@ -571,4 +571,8 @@ class KeyframesCo:
keyframe_points.foreach_set("co", co_buffer)
keyframe_points.foreach_set("interpolation", ipo_buffer)
fcurve.update()
# TODO: in Blender 4.0 the next lines can be replaced with one call to `fcurve.update()`.
# See https://projects.blender.org/blender/blender/issues/107126 for more info.
keyframe_points.sort()
keyframe_points.deduplicate()
keyframe_points.handles_recalc()

View File

@ -47,6 +47,8 @@ def get_context_modifier(context):
if context.area.type == 'PROPERTIES':
modifier = context.modifier
else:
if context.object is None:
return False
modifier = context.object.modifiers.active
if modifier is None or modifier.type != 'NODES':
return None

View File

@ -416,6 +416,7 @@ int BKE_fcurve_active_keyframe_index(const struct FCurve *fcu);
* Move the indexed keyframe to the given value,
* and move the handles with it to ensure the slope remains the same.
*/
void BKE_fcurve_keyframe_move_time_with_handles(BezTriple *keyframe, const float new_time);
void BKE_fcurve_keyframe_move_value_with_handles(struct BezTriple *keyframe, float new_value);
/* .............. */
@ -472,6 +473,14 @@ bool BKE_fcurve_bezt_subdivide_handles(struct BezTriple *bezt,
struct BezTriple *next,
float *r_pdelta);
/**
* Resize the FCurve 'bezt' array to fit the given length.
*
* \param new_totvert new number of elements in the FCurve's `bezt` array.
* Constraint: `0 <= new_totvert <= fcu->totvert`
*/
void BKE_fcurve_bezt_shrink(struct FCurve *fcu, int new_totvert);
/**
* Delete a keyframe from an F-curve at a specific index.
*/
@ -499,6 +508,21 @@ void BKE_fcurve_merge_duplicate_keys(struct FCurve *fcu,
const int sel_flag,
const bool use_handle);
/**
* Ensure the FCurve is a proper function, such that every X-coordinate of the
* timeline has only one value of the FCurve. In other words, removes duplicate
* keyframes.
*
* Contrary to #BKE_fcurve_merge_duplicate_keys, which is intended for
* interactive use, and where selection matters, this is a simpler deduplication
* where the last duplicate "wins".
*
* Assumes the keys are sorted (see #sort_time_fcurve).
*
* After deduplication, call `BKE_fcurve_handles_recalc(fcu);`
*/
void BKE_fcurve_deduplicate_keys(struct FCurve *fcu);
/* -------- Curve Sanity -------- */
/**

View File

@ -32,6 +32,8 @@
#include "BLI_compiler_attrs.h"
#include "BLI_utildefines.h"
#include "DNA_userdef_enums.h"
#ifdef __cplusplus
extern "C" {
#endif
@ -448,7 +450,7 @@ struct ID *BKE_id_copy(struct Main *bmain, const struct ID *id);
*/
struct ID *BKE_id_copy_for_duplicate(struct Main *bmain,
struct ID *id,
uint duplicate_flags,
eDupli_ID_Flags duplicate_flags,
int copy_flags);
/**

View File

@ -148,6 +148,28 @@ enum {
IDWALK_INCLUDE_UI = (1 << 2),
/** Do not process ID pointers inside embedded IDs. Needed by depsgraph processing e.g. */
IDWALK_IGNORE_EMBEDDED_ID = (1 << 3),
/**
* Do not access original processed pointer's data, only process its address value.
*
* This is required in cases where to current address may not be valid anymore (e.g. during
* readfile process). A few ID pointers (like e.g. the `LayerCollection.collection` one) are by
* default accessed to check things (e.g. whether they are pointing to an embedded ID or a
* regular one).
*
* \note Access to owning embedded ID pointers (e.g. `Scene.master_collection`) is not affected
* here, these are presumed always valid.
*
* \note This flag is mutually exclusive with `IDWALK_READONLY` and `IDWALK_RECURSE`, since by
* definition the only thing doable in readonly case is accessing current ID pointer, and this is
* also required for recursion.
*
* \note After remapping, code may access the newly set ID pointer, which is always presumed
* valid.
*
* \warning Use only with great caution, this flag will modify the handling of some ID pointers
* (especially when it comes to detecting `IDWALK_CB_EMBEDDED_NOT_OWNING` usages).
*/
IDWALK_NO_ORIG_POINTERS_ACCESS = (1 << 5),
/**
* Also process internal ID pointers like `ID.newid` or `ID.orig_id`.

View File

@ -88,6 +88,13 @@ enum {
/** Do NOT tag IDs which had some of their ID pointers updated for update in the depsgraph, or ID
* type specific updates, like e.g. with node trees. */
ID_REMAP_SKIP_UPDATE_TAGGING = 1 << 19,
/**
* Do not attempt to access original ID pointers (triggers usages of
* `IDWALK_NO_ORIG_POINTERS_ACCESS` too).
*
* Use when original ID pointers values are (probably) not valid, e.g. dureing readfile process.
*/
ID_REMAP_NO_ORIG_POINTERS_ACCESS = 1 << 20,
};
typedef enum eIDRemapType {

View File

@ -11,6 +11,7 @@
#include "BLI_sys_types.h"
#include "DNA_object_enums.h"
#include "DNA_userdef_enums.h"
#ifdef __cplusplus
extern "C" {
@ -234,7 +235,7 @@ bool BKE_object_obdata_is_libdata(const struct Object *ob);
*/
struct Object *BKE_object_duplicate(struct Main *bmain,
struct Object *ob,
uint dupflag,
eDupli_ID_Flags dupflag,
uint duplicate_options);
/**

View File

@ -168,6 +168,7 @@ static void collection_free_data(ID *id)
static void collection_foreach_id(ID *id, LibraryForeachIDData *data)
{
Collection *collection = (Collection *)id;
const int data_flags = BKE_lib_query_foreachid_process_flags_get(data);
BKE_LIB_FOREACHID_PROCESS_ID(
data, collection->runtime.owner_id, IDWALK_CB_LOOPBACK | IDWALK_CB_NEVER_SELF);
@ -197,6 +198,7 @@ static void collection_foreach_id(ID *id, LibraryForeachIDData *data)
/* XXX This is very weak. The whole idea of keeping pointers to private IDs is very bad
* anyway... */
const int cb_flag = ((parent->collection != NULL &&
(data_flags & IDWALK_NO_ORIG_POINTERS_ACCESS) == 0 &&
(parent->collection->id.flag & LIB_EMBEDDED_DATA) != 0) ?
IDWALK_CB_EMBEDDED_NOT_OWNING :
IDWALK_CB_NOP);
@ -1467,11 +1469,16 @@ bool BKE_collection_object_replace(Main *bmain,
return false;
}
id_us_min(&cob->ob->id);
cob->ob = ob_new;
id_us_plus(&cob->ob->id);
if (!BLI_ghash_haskey(collection->runtime.gobject_hash, ob_new)) {
id_us_min(&cob->ob->id);
cob->ob = ob_new;
id_us_plus(&cob->ob->id);
BLI_ghash_insert(collection->runtime.gobject_hash, cob->ob, cob);
BLI_ghash_insert(collection->runtime.gobject_hash, cob->ob, cob);
}
else {
collection_object_remove_no_gobject_hash(bmain, collection, cob, false);
}
if (BKE_collection_is_in_scene(collection)) {
BKE_main_collection_sync(bmain);

View File

@ -883,6 +883,14 @@ int BKE_fcurve_active_keyframe_index(const FCurve *fcu)
/** \} */
void BKE_fcurve_keyframe_move_time_with_handles(BezTriple *keyframe, const float new_time)
{
const float time_delta = new_time - keyframe->vec[1][0];
keyframe->vec[0][0] += time_delta;
keyframe->vec[1][0] = new_time;
keyframe->vec[2][0] += time_delta;
}
void BKE_fcurve_keyframe_move_value_with_handles(struct BezTriple *keyframe, const float new_value)
{
const float value_delta = new_value - keyframe->vec[1][1];
@ -1659,6 +1667,24 @@ bool BKE_fcurve_bezt_subdivide_handles(struct BezTriple *bezt,
return true;
}
void BKE_fcurve_bezt_shrink(struct FCurve *fcu, const int new_totvert)
{
BLI_assert(new_totvert >= 0);
BLI_assert(new_totvert <= fcu->totvert);
/* No early return when new_totvert == fcu->totvert. There is no way to know the intention of the
* caller, nor the history of the FCurve so far, so `fcu->bezt` may actually have allocated space
* for more than `fcu->totvert` keys. */
if (new_totvert == 0) {
fcurve_bezt_free(fcu);
return;
}
fcu->bezt = MEM_reallocN(fcu->bezt, new_totvert * sizeof(*(fcu->bezt)));
fcu->totvert = new_totvert;
}
void BKE_fcurve_delete_key(FCurve *fcu, int index)
{
/* sanity check */
@ -1849,6 +1875,52 @@ void BKE_fcurve_merge_duplicate_keys(FCurve *fcu, const int sel_flag, const bool
BLI_freelistN(&retained_keys);
}
void BKE_fcurve_deduplicate_keys(FCurve *fcu)
{
BLI_assert_msg(fcu->bezt, "this function only works with regular (non-sampled) FCurves");
if (fcu->totvert < 2 || fcu->bezt == NULL) {
return;
}
int prev_bezt_index = 0;
for (int i = 1; i < fcu->totvert; i++) {
BezTriple *bezt = &fcu->bezt[i];
BezTriple *prev_bezt = &fcu->bezt[prev_bezt_index];
const float bezt_x = bezt->vec[1][0];
const float prev_x = prev_bezt->vec[1][0];
if (bezt_x - prev_x <= BEZT_BINARYSEARCH_THRESH) {
/* Replace 'prev_bezt', as it has the same X-coord as 'bezt' and the last one wins. */
*prev_bezt = *bezt;
if (floor(bezt_x) == bezt_x) {
/* Keep the 'bezt_x' coordinate, as being on a frame is more desirable
* than being ever so slightly off. */
}
else {
/* Move the retained key to the old X-coordinate, to 'anchor' the X-coordinate used for
* subsequente comparisons. Without this, the reference X-coordinate would keep moving
* forward in time, potentially merging in more keys than desired. */
BKE_fcurve_keyframe_move_time_with_handles(prev_bezt, prev_x);
}
continue;
}
/* Next iteration should look at the current element. However, because of the deletions, that
* may not be at index 'i'; after this increment, `prev_bezt_index` points at where the current
* element should go. */
prev_bezt_index++;
if (prev_bezt_index != i) {
/* This bezt should be kept, so copy it to its new location in the array. */
fcu->bezt[prev_bezt_index] = *bezt;
}
}
BKE_fcurve_bezt_shrink(fcu, prev_bezt_index + 1);
}
/** \} */
/* -------------------------------------------------------------------- */

View File

@ -10,6 +10,8 @@
#include "DNA_anim_types.h"
#include "BLI_math_vector_types.hh"
namespace blender::bke::tests {
/* Epsilon for floating point comparisons. */
@ -346,6 +348,37 @@ TEST(BKE_fcurve, BKE_fcurve_keyframe_move_value_with_handles)
BKE_fcurve_free(fcu);
}
TEST(BKE_fcurve, BKE_fcurve_keyframe_move_time_with_handles)
{
FCurve *fcu = BKE_fcurve_create();
insert_vert_fcurve(fcu, 1.0f, 7.5f, BEZT_KEYTYPE_KEYFRAME, INSERTKEY_NO_USERPREF);
insert_vert_fcurve(fcu, 8.0f, 15.0f, BEZT_KEYTYPE_KEYFRAME, INSERTKEY_NO_USERPREF);
insert_vert_fcurve(fcu, 14.0f, 8.2f, BEZT_KEYTYPE_KEYFRAME, INSERTKEY_NO_USERPREF);
EXPECT_FLOAT_EQ(fcu->bezt[1].vec[0][0], 5.2671194f);
EXPECT_FLOAT_EQ(fcu->bezt[1].vec[0][1], 15.0f);
EXPECT_FLOAT_EQ(fcu->bezt[1].vec[1][0], 8.0f);
EXPECT_FLOAT_EQ(fcu->bezt[1].vec[1][1], 15.0f);
EXPECT_FLOAT_EQ(fcu->bezt[1].vec[2][0], 10.342469f);
EXPECT_FLOAT_EQ(fcu->bezt[1].vec[2][1], 15.0f);
BKE_fcurve_keyframe_move_time_with_handles(&fcu->bezt[1], 47.0f);
EXPECT_FLOAT_EQ(fcu->bezt[1].vec[0][0], 44.2671194f) << "Left handle time should be updated";
EXPECT_FLOAT_EQ(fcu->bezt[1].vec[0][1], 15.0f) << "Left handle should not move in value";
EXPECT_FLOAT_EQ(fcu->bezt[1].vec[1][0], 47.0f) << "Frame time should have been updated";
EXPECT_FLOAT_EQ(fcu->bezt[1].vec[1][1], 15.0f) << "Frame should not move in value";
EXPECT_FLOAT_EQ(fcu->bezt[1].vec[2][0], 49.342469f) << "Right handle time should be updated";
EXPECT_FLOAT_EQ(fcu->bezt[1].vec[2][1], 15.0f) << "Right handle should not move in value";
BKE_fcurve_free(fcu);
}
TEST(BKE_fcurve, BKE_fcurve_calc_range)
{
FCurve *fcu = BKE_fcurve_create();
@ -546,4 +579,127 @@ TEST(BKE_fcurve, BKE_fcurve_calc_bounds)
BKE_fcurve_free(fcu);
}
static void set_key(FCurve *fcu, const int index, const float x, const float y)
{
fcu->bezt[index].vec[0][0] = x - 0.5f;
fcu->bezt[index].vec[1][0] = x;
fcu->bezt[index].vec[2][0] = x + 0.5f;
fcu->bezt[index].vec[0][1] = y;
fcu->bezt[index].vec[1][1] = y;
fcu->bezt[index].vec[2][1] = y;
}
static FCurve *testcurve_with_duplicates()
{
/* Create a curve with some duplicate keys. The first ones are all with Y=1, the later repeats
* increase Y-coordinates on every repeat. */
FCurve *fcu = BKE_fcurve_create();
ED_keyframes_add(fcu, 10); /* Avoid `insert_vert_fcurve`, that deduplicates the keys. */
set_key(fcu, 0, 1.0f, 1.0f);
set_key(fcu, 1, 327.16f, 1.0f);
set_key(fcu, 2, 7.0f, 1.0f);
set_key(fcu, 3, 47.0f, 1.0f);
set_key(fcu, 4, 7.0f, 2.0f);
set_key(fcu, 5, 47.0f, 2.0f);
set_key(fcu, 6, 47.0f + BEZT_BINARYSEARCH_THRESH, 3.0f);
set_key(fcu, 7, 7.0f, 3.0f);
set_key(fcu, 8, 3.0f, 1.0f);
set_key(fcu, 9, 2.0f, 1.0f);
return fcu;
}
TEST(BKE_fcurve, sort_time_fcurve_stability)
{
FCurve *fcu = testcurve_with_duplicates();
ASSERT_EQ(fcu->totvert, 10);
sort_time_fcurve(fcu);
/* The sorting should be stable, i.e. retain the original order when the
* X-coordinates are identical. */
ASSERT_EQ(fcu->totvert, 10) << "sorting should not influence number of keys";
EXPECT_V2_NEAR(fcu->bezt[0].vec[1], float2(1.0f, 1.0f), 1e-3);
EXPECT_V2_NEAR(fcu->bezt[1].vec[1], float2(2.0f, 1.0f), 1e-3);
EXPECT_V2_NEAR(fcu->bezt[2].vec[1], float2(3.0f, 1.0f), 1e-3);
EXPECT_V2_NEAR(fcu->bezt[3].vec[1], float2(7.0f, 1.0f), 1e-3);
EXPECT_V2_NEAR(fcu->bezt[4].vec[1], float2(7.0f, 2.0f), 1e-3);
EXPECT_V2_NEAR(fcu->bezt[5].vec[1], float2(7.0f, 3.0f), 1e-3);
EXPECT_V2_NEAR(fcu->bezt[6].vec[1], float2(47.0f, 1.0f), 1e-3);
EXPECT_V2_NEAR(fcu->bezt[7].vec[1], float2(47.0f, 2.0f), 1e-3);
EXPECT_V2_NEAR(fcu->bezt[8].vec[1], float2(47.0f + BEZT_BINARYSEARCH_THRESH, 3.0f), 1e-3);
EXPECT_V2_NEAR(fcu->bezt[9].vec[1], float2(327.16f, 1.0f), 1e-3);
BKE_fcurve_free(fcu);
}
TEST(BKE_fcurve, BKE_fcurve_deduplicate_keys)
{
FCurve *fcu = testcurve_with_duplicates();
ASSERT_EQ(fcu->totvert, 10);
sort_time_fcurve(fcu);
BKE_fcurve_deduplicate_keys(fcu);
ASSERT_GE(fcu->totvert, 6); /* Protect against out-of-bounds access. */
EXPECT_EQ(fcu->totvert, 6); /* The actual expected value. */
EXPECT_V2_NEAR(fcu->bezt[0].vec[1], float2(1.0f, 1.0f), 1e-3);
EXPECT_V2_NEAR(fcu->bezt[1].vec[1], float2(2.0f, 1.0f), 1e-3);
EXPECT_V2_NEAR(fcu->bezt[2].vec[1], float2(3.0f, 1.0f), 1e-3);
EXPECT_V2_NEAR(fcu->bezt[3].vec[1], float2(7.0f, 3.0f), 1e-3);
EXPECT_V2_NEAR(fcu->bezt[4].vec[1], float2(47.0f, 3.0f), 1e-3);
EXPECT_V2_NEAR(fcu->bezt[5].vec[1], float2(327.16f, 1.0f), 1e-3);
BKE_fcurve_free(fcu);
}
TEST(BKE_fcurve, BKE_fcurve_deduplicate_keys_edge_cases)
{
FCurve *fcu = testcurve_with_duplicates();
ASSERT_EQ(fcu->totvert, 10);
/* Update the 2nd and 2nd-to-last keys to test the edge cases. */
set_key(fcu, 0, 1, 1);
set_key(fcu, 1, 1, 2);
set_key(fcu, 8, 327.16f, 1);
set_key(fcu, 9, 327.16f, 2);
sort_time_fcurve(fcu);
BKE_fcurve_deduplicate_keys(fcu);
ASSERT_EQ(fcu->totvert, 4);
EXPECT_V2_NEAR(fcu->bezt[0].vec[1], float2(1.0f, 2.0f), 1e-3);
EXPECT_V2_NEAR(fcu->bezt[1].vec[1], float2(7.0f, 3.0f), 1e-3);
EXPECT_V2_NEAR(fcu->bezt[2].vec[1], float2(47.0f, 3.0f), 1e-3);
EXPECT_V2_NEAR(fcu->bezt[3].vec[1], float2(327.16f, 2.0f), 1e-3);
BKE_fcurve_free(fcu);
}
TEST(BKE_fcurve, BKE_fcurve_deduplicate_keys_prefer_whole_frames)
{
FCurve *fcu = testcurve_with_duplicates();
ASSERT_EQ(fcu->totvert, 10);
/* Update the first key around 47.0 to be slightly before the frame. This gives us three keys on
* 47-epsilon, 47, and 47+epsilon. The keys at index 5 and 6 already have this value, so the
* `set_key` calls are unnecessary, but this way this test has a more local overview of the
* situation under test. */
set_key(fcu, 3, 47.0f - BEZT_BINARYSEARCH_THRESH, 1.0f);
set_key(fcu, 5, 47.0f, 2.0f);
set_key(fcu, 6, 47.0f + BEZT_BINARYSEARCH_THRESH, 3.0f);
sort_time_fcurve(fcu);
BKE_fcurve_deduplicate_keys(fcu);
ASSERT_EQ(fcu->totvert, 6);
EXPECT_V2_NEAR(fcu->bezt[0].vec[1], float2(1.0f, 1.0f), 1e-3);
EXPECT_V2_NEAR(fcu->bezt[1].vec[1], float2(2.0f, 1.0f), 1e-3);
EXPECT_V2_NEAR(fcu->bezt[2].vec[1], float2(3.0f, 1.0f), 1e-3);
EXPECT_V2_NEAR(fcu->bezt[3].vec[1], float2(7.0f, 3.0f), 1e-3);
EXPECT_V2_NEAR(fcu->bezt[4].vec[1], float2(47.0f, 3.0f), 1e-3);
EXPECT_V2_NEAR(fcu->bezt[5].vec[1], float2(327.16f, 1.0f), 1e-3);
BKE_fcurve_free(fcu);
}
} // namespace blender::bke::tests

View File

@ -874,7 +874,12 @@ static void id_embedded_swap(ID **embedded_id_a,
struct IDRemapper *remapper_id_b)
{
if (embedded_id_a != NULL && *embedded_id_a != NULL) {
BLI_assert(embedded_id_b != NULL && *embedded_id_b != NULL);
BLI_assert(embedded_id_b != NULL);
if (*embedded_id_b == NULL) {
/* Cannot swap anything if one of the embedded IDs is NULL. */
return;
}
/* Do not remap internal references to itself here, since embedded IDs pointers also need to be
* potentially remapped in owner ID's data, which will also handle embedded IDs data. */

View File

@ -201,6 +201,12 @@ static bool library_foreach_ID_link(Main *bmain,
LibraryForeachIDData data = {.bmain = bmain};
BLI_assert(inherit_data == NULL || data.bmain == inherit_data->bmain);
/* `IDWALK_NO_ORIG_POINTERS_ACCESS` is mutually exclusive with both `IDWALK_READONLY` and
* `IDWALK_RECURSE`. */
BLI_assert((flag & (IDWALK_NO_ORIG_POINTERS_ACCESS | IDWALK_READONLY)) !=
(IDWALK_NO_ORIG_POINTERS_ACCESS | IDWALK_READONLY));
BLI_assert((flag & (IDWALK_NO_ORIG_POINTERS_ACCESS | IDWALK_RECURSE)) !=
(IDWALK_NO_ORIG_POINTERS_ACCESS | IDWALK_RECURSE));
if (flag & IDWALK_RECURSE) {
/* For now, recursion implies read-only, and no internal pointers. */

View File

@ -483,7 +483,9 @@ static void libblock_remap_data(Main *bmain,
(((remap_flags & ID_REMAP_FORCE_INTERNAL_RUNTIME_POINTERS) != 0 ?
IDWALK_DO_INTERNAL_RUNTIME_POINTERS :
IDWALK_NOP) |
((remap_flags & ID_REMAP_FORCE_UI_POINTERS) != 0 ? IDWALK_INCLUDE_UI : IDWALK_NOP));
((remap_flags & ID_REMAP_FORCE_UI_POINTERS) != 0 ? IDWALK_INCLUDE_UI : IDWALK_NOP) |
((remap_flags & ID_REMAP_NO_ORIG_POINTERS_ACCESS) != 0 ? IDWALK_NO_ORIG_POINTERS_ACCESS :
IDWALK_NOP));
id_remap_data.id_remapper = id_remapper;
id_remap_data.type = remap_type;

View File

@ -13,12 +13,12 @@
#include "DNA_meshdata_types.h"
#include "DNA_modifier_types.h"
#include "BLI_bitmap.h"
#include "BLI_math_vector.h"
#include "BLI_task.h"
#include "BLI_utildefines.h"
#include "BKE_customdata.h"
#include "BKE_mesh.hh"
#include "BKE_multires.h"
#include "BKE_subdiv.h"
#include "BKE_subdiv_eval.h"
@ -135,8 +135,8 @@ struct MultiresReshapeSmoothContext {
* The data is actually stored as a delta, which is then to be added to the higher levels. */
LinearGrids linear_delta_grids;
/* Index i of this map indicates that base edge i is adjacent to at least one face. */
BLI_bitmap *non_loose_base_edge_map;
/* From #Mesh::loose_edges(). May be empty. */
blender::BitSpan loose_base_edges;
/* Subdivision surface created for geometry at a reshape level. */
Subdiv *reshape_subdiv;
@ -488,9 +488,9 @@ static float get_effective_crease(const MultiresReshapeSmoothContext *reshape_sm
const int base_edge_index)
{
if (!is_crease_supported(reshape_smooth_context)) {
return 255;
return 1.0f;
}
const float *creases = reshape_smooth_context->reshape_context->cd_vertex_crease;
const float *creases = reshape_smooth_context->reshape_context->cd_edge_crease;
return creases ? creases[base_edge_index] : 0.0f;
}
@ -524,7 +524,7 @@ static void context_init(MultiresReshapeSmoothContext *reshape_smooth_context,
linear_grids_init(&reshape_smooth_context->linear_delta_grids);
reshape_smooth_context->non_loose_base_edge_map = nullptr;
reshape_smooth_context->loose_base_edges = {};
reshape_smooth_context->reshape_subdiv = nullptr;
reshape_smooth_context->base_surface_grids = nullptr;
@ -556,8 +556,6 @@ static void context_free_subdiv(MultiresReshapeSmoothContext *reshape_smooth_con
static void context_free(MultiresReshapeSmoothContext *reshape_smooth_context)
{
MEM_freeN(reshape_smooth_context->non_loose_base_edge_map);
context_free_geometry(reshape_smooth_context);
context_free_subdiv(reshape_smooth_context);
base_surface_grids_free(reshape_smooth_context);
@ -619,13 +617,11 @@ static void foreach_single_vertex(const SubdivForeachContext *foreach_context,
const MultiresReshapeContext *reshape_context = reshape_smooth_context->reshape_context;
const float *cd_vertex_crease = reshape_context->cd_vertex_crease;
if (cd_vertex_crease == nullptr) {
return;
}
float crease = cd_vertex_crease[coarse_vertex_index];
if (crease == 0.0f) {
return;
}
@ -833,7 +829,8 @@ static void foreach_edge(const SubdivForeachContext *foreach_context,
return;
}
/* Ignore all loose edges as well, as they are not communicated to the OpenSubdiv. */
if (!BLI_BITMAP_TEST_BOOL(reshape_smooth_context->non_loose_base_edge_map, coarse_edge_index)) {
if (!reshape_smooth_context->loose_base_edges.is_empty() &&
reshape_smooth_context->loose_base_edges[coarse_edge_index]) {
return;
}
/* Edges without crease are to be ignored as well. */
@ -848,24 +845,20 @@ static void geometry_init_loose_information(MultiresReshapeSmoothContext *reshap
{
const MultiresReshapeContext *reshape_context = reshape_smooth_context->reshape_context;
const Mesh *base_mesh = reshape_context->base_mesh;
const blender::OffsetIndices base_polys = reshape_context->base_polys;
const blender::Span<int> base_corner_edges = reshape_context->base_corner_edges;
reshape_smooth_context->non_loose_base_edge_map = BLI_BITMAP_NEW(base_mesh->totedge,
"non_loose_base_edge_map");
const blender::bke::LooseEdgeCache &loose_edges = base_mesh->loose_edges();
reshape_smooth_context->loose_base_edges = loose_edges.is_loose_bits;
int num_used_edges = 0;
for (const int poly_index : base_polys.index_range()) {
for (const int edge : base_corner_edges.slice(base_polys[poly_index])) {
if (!BLI_BITMAP_TEST_BOOL(reshape_smooth_context->non_loose_base_edge_map, edge)) {
BLI_BITMAP_ENABLE(reshape_smooth_context->non_loose_base_edge_map, edge);
const float crease = get_effective_crease(reshape_smooth_context, edge);
if (crease > 0.0f) {
++num_used_edges;
}
}
for (const int edge : blender::IndexRange(base_mesh->totedge)) {
if (loose_edges.count > 0 && loose_edges.is_loose_bits[edge]) {
continue;
}
const float crease = get_effective_crease(reshape_smooth_context, edge);
if (crease == 0.0f) {
continue;
}
num_used_edges++;
}
const int resolution = get_reshape_level_resolution(reshape_context);

View File

@ -2718,7 +2718,10 @@ void BKE_object_transform_copy(Object *ob_tar, const Object *ob_src)
copy_v3_v3(ob_tar->scale, ob_src->scale);
}
Object *BKE_object_duplicate(Main *bmain, Object *ob, uint dupflag, uint duplicate_options)
Object *BKE_object_duplicate(Main *bmain,
Object *ob,
eDupli_ID_Flags dupflag,
uint duplicate_options)
{
const bool is_subprocess = (duplicate_options & LIB_ID_DUPLICATE_IS_SUBPROCESS) != 0;
const bool is_root_id = (duplicate_options & LIB_ID_DUPLICATE_IS_ROOT_ID) != 0;

View File

@ -758,10 +758,10 @@ static void scene_foreach_toolsettings(LibraryForeachIDData *data,
static void scene_foreach_layer_collection(LibraryForeachIDData *data, ListBase *lb)
{
const int data_flags = BKE_lib_query_foreachid_process_flags_get(data);
LISTBASE_FOREACH (LayerCollection *, lc, lb) {
/* XXX This is very weak. The whole idea of keeping pointers to private IDs is very bad
* anyway... */
const int cb_flag = (lc->collection != nullptr &&
(data_flags & IDWALK_NO_ORIG_POINTERS_ACCESS) == 0 &&
(lc->collection->id.flag & LIB_EMBEDDED_DATA) != 0) ?
IDWALK_CB_EMBEDDED_NOT_OWNING :
IDWALK_CB_NOP;

View File

@ -85,8 +85,9 @@ static void screen_foreach_id_dopesheet(LibraryForeachIDData *data, bDopeSheet *
void BKE_screen_foreach_id_screen_area(LibraryForeachIDData *data, ScrArea *area)
{
const bool is_readonly = (BKE_lib_query_foreachid_process_flags_get(data) & IDWALK_READONLY) !=
0;
const int data_flags = BKE_lib_query_foreachid_process_flags_get(data);
const bool is_readonly = (data_flags & IDWALK_READONLY) != 0;
const bool allow_pointer_access = (data_flags & IDWALK_NO_ORIG_POINTERS_ACCESS) == 0;
BKE_LIB_FOREACHID_PROCESS_IDSUPER(data, area->full, IDWALK_CB_NOP);
@ -190,7 +191,7 @@ void BKE_screen_foreach_id_screen_area(LibraryForeachIDData *data, ScrArea *area
while ((tselem = BLI_mempool_iterstep(&iter))) {
/* Do not try to restore non-ID pointers (drivers/sequence/etc.). */
if (TSE_IS_REAL_ID(tselem)) {
const int cb_flag = (tselem->id != NULL &&
const int cb_flag = (tselem->id != NULL && allow_pointer_access &&
(tselem->id->flag & LIB_EMBEDDED_DATA) != 0) ?
IDWALK_CB_EMBEDDED_NOT_OWNING :
IDWALK_CB_NOP;
@ -209,7 +210,7 @@ void BKE_screen_foreach_id_screen_area(LibraryForeachIDData *data, ScrArea *area
}
case SPACE_NODE: {
SpaceNode *snode = (SpaceNode *)sl;
const bool is_embedded_nodetree = snode->id != NULL &&
const bool is_embedded_nodetree = snode->id != NULL && allow_pointer_access &&
ntreeFromID(snode->id) == snode->nodetree;
BKE_LIB_FOREACHID_PROCESS_ID(data, snode->id, IDWALK_CB_NOP);
@ -227,8 +228,11 @@ void BKE_screen_foreach_id_screen_area(LibraryForeachIDData *data, ScrArea *area
/* Embedded ID pointers are not remapped (besides exceptions), ensure it still matches
* actual data. Note that `snode->id` was already processed (and therefore potentially
* remapped) above.*/
if (!is_readonly && path != NULL) {
path->nodetree = snode->nodetree = (snode->id == NULL) ? NULL : ntreeFromID(snode->id);
if (!is_readonly) {
snode->nodetree = (snode->id == NULL) ? NULL : ntreeFromID(snode->id);
if (path != NULL) {
path->nodetree = snode->nodetree;
}
}
}
else {
@ -238,10 +242,18 @@ void BKE_screen_foreach_id_screen_area(LibraryForeachIDData *data, ScrArea *area
}
}
/* Both `snode->id` and `snode->nodetree` have been remapped now, sotheir data can be
* accessed. */
BLI_assert(snode->id == NULL || snode->nodetree == NULL ||
(snode->nodetree->id.flag & LIB_EMBEDDED_DATA) == 0 ||
snode->nodetree == ntreeFromID(snode->id));
if (path != NULL) {
for (path = path->next; path != NULL; path = path->next) {
BLI_assert(path->nodetree != NULL);
BLI_assert((path->nodetree->id.flag & LIB_EMBEDDED_DATA) == 0);
if ((data_flags & IDWALK_NO_ORIG_POINTERS_ACCESS) == 0) {
BLI_assert((path->nodetree->id.flag & LIB_EMBEDDED_DATA) == 0);
}
BKE_LIB_FOREACHID_PROCESS_IDSUPER(data, path->nodetree, IDWALK_CB_USER_ONE);
@ -270,13 +282,16 @@ void BKE_screen_foreach_id_screen_area(LibraryForeachIDData *data, ScrArea *area
snode->edittree = NULL;
}
}
/* NOTE: It is disputable whether this should be called here, especially when editing it is
* allowed? But for now, for sake of consistency, do it in any case. */
if (is_embedded_nodetree && snode->edittree == snode->nodetree) {
BKE_LIB_FOREACHID_PROCESS_IDSUPER(data, snode->edittree, IDWALK_CB_EMBEDDED_NOT_OWNING);
}
else {
BKE_LIB_FOREACHID_PROCESS_IDSUPER(data, snode->edittree, IDWALK_CB_NOP);
/* Only process this pointer in readonly case, otherwise could lead to a bad
* double-remapping e.g. */
if (is_embedded_nodetree && snode->edittree == snode->nodetree) {
BKE_LIB_FOREACHID_PROCESS_IDSUPER(
data, snode->edittree, IDWALK_CB_EMBEDDED_NOT_OWNING);
}
else {
BKE_LIB_FOREACHID_PROCESS_IDSUPER(data, snode->edittree, IDWALK_CB_NOP);
}
}
break;
}

View File

@ -10,9 +10,9 @@
#include "DNA_mesh_types.h"
#include "DNA_meshdata_types.h"
#include "BLI_bitmap.h"
#include "BLI_math_vector.h"
#include "BLI_task.h"
#include "BLI_timeit.hh"
#include "BLI_utildefines.h"
#include "BKE_customdata.h"
@ -78,46 +78,30 @@ bool BKE_subdiv_eval_begin(Subdiv *subdiv,
}
static void set_coarse_positions(Subdiv *subdiv,
const Mesh *mesh,
const float (*coarse_vertex_cos)[3])
const blender::Span<blender::float3> positions,
const blender::bke::LooseVertCache &verts_no_face)
{
const float(*positions)[3] = BKE_mesh_vert_positions(mesh);
const blender::OffsetIndices polys = mesh->polys();
const blender::Span<int> corner_verts = mesh->corner_verts();
/* Mark vertices which needs new coordinates. */
/* TODO(sergey): This is annoying to calculate this on every update,
* maybe it's better to cache this mapping. Or make it possible to have
* OpenSubdiv's vertices match mesh ones? */
BLI_bitmap *vertex_used_map = BLI_BITMAP_NEW(mesh->totvert, "vert used map");
for (const int i : polys.index_range()) {
for (const int vert : corner_verts.slice(polys[i])) {
BLI_BITMAP_ENABLE(vertex_used_map, vert);
}
using namespace blender;
OpenSubdiv_Evaluator *evaluator = subdiv->evaluator;
if (verts_no_face.count == 0) {
evaluator->setCoarsePositions(
evaluator, reinterpret_cast<const float *>(positions.data()), 0, positions.size());
return;
}
/* Use a temporary buffer so we do not upload vertices one at a time to the GPU. */
float(*buffer)[3] = static_cast<float(*)[3]>(
MEM_mallocN(sizeof(float[3]) * mesh->totvert, __func__));
int manifold_vertex_count = 0;
for (int vertex_index = 0, manifold_vertex_index = 0; vertex_index < mesh->totvert;
vertex_index++) {
if (!BLI_BITMAP_TEST_BOOL(vertex_used_map, vertex_index)) {
Array<float3> used_vert_positions(positions.size() - verts_no_face.count);
const BitSpan bits = verts_no_face.is_loose_bits;
int used_vert_count = 0;
for (const int vert : positions.index_range()) {
if (bits[vert]) {
continue;
}
const float *vertex_co;
if (coarse_vertex_cos != nullptr) {
vertex_co = coarse_vertex_cos[vertex_index];
}
else {
vertex_co = positions[vertex_index];
}
copy_v3_v3(&buffer[manifold_vertex_index][0], vertex_co);
manifold_vertex_index++;
manifold_vertex_count++;
used_vert_positions[used_vert_count] = positions[vert];
used_vert_count++;
}
subdiv->evaluator->setCoarsePositions(
subdiv->evaluator, &buffer[0][0], 0, manifold_vertex_count);
MEM_freeN(vertex_used_map);
MEM_freeN(buffer);
evaluator->setCoarsePositions(evaluator,
reinterpret_cast<const float *>(used_vert_positions.data()),
0,
used_vert_positions.size());
}
/* Context which is used to fill face varying data in parallel. */
@ -242,13 +226,20 @@ bool BKE_subdiv_eval_refine_from_mesh(Subdiv *subdiv,
const Mesh *mesh,
const float (*coarse_vertex_cos)[3])
{
using namespace blender;
if (subdiv->evaluator == nullptr) {
/* NOTE: This situation is supposed to be handled by begin(). */
BLI_assert_msg(0, "Is not supposed to happen");
return false;
}
/* Set coordinates of base mesh vertices. */
set_coarse_positions(subdiv, mesh, coarse_vertex_cos);
set_coarse_positions(
subdiv,
coarse_vertex_cos ?
Span(reinterpret_cast<const float3 *>(coarse_vertex_cos), mesh->totvert) :
mesh->vert_positions(),
mesh->verts_no_face());
/* Set face-varying data to UV maps. */
const int num_uv_layers = CustomData_number_of_layers(&mesh->ldata, CD_PROP_FLOAT2);
for (int layer_index = 0; layer_index < num_uv_layers; layer_index++) {

View File

@ -56,6 +56,8 @@
#include "RE_texture.h"
#include "DRW_engine.h"
#include "BLO_read_write.h"
static void texture_init_data(ID *id)
@ -100,6 +102,8 @@ static void texture_copy_data(Main *bmain, ID *id_dst, const ID *id_src, const i
texture_dst->nodetree->owner_id = &texture_dst->id;
}
BLI_listbase_clear((ListBase *)&texture_dst->drawdata);
if ((flag & LIB_ID_COPY_NO_PREVIEW) == 0) {
BKE_previewimg_id_copy(&texture_dst->id, &texture_src->id);
}
@ -112,6 +116,8 @@ static void texture_free_data(ID *id)
{
Tex *texture = (Tex *)id;
DRW_drawdata_free(id);
/* is no lib link block, but texture extension */
if (texture->nodetree) {
ntreeFreeEmbeddedTree(texture->nodetree);

View File

@ -3117,8 +3117,8 @@ static void read_libblock_undo_restore_at_old_address(FileData *fd, Main *main,
id,
id_old,
true,
ID_REMAP_SKIP_NEVER_NULL_USAGE | ID_REMAP_SKIP_UPDATE_TAGGING |
ID_REMAP_SKIP_USER_REFCOUNT);
(ID_REMAP_NO_ORIG_POINTERS_ACCESS | ID_REMAP_SKIP_NEVER_NULL_USAGE |
ID_REMAP_SKIP_UPDATE_TAGGING | ID_REMAP_SKIP_USER_REFCOUNT));
/* Special temporary usage of this pointer, necessary for the `undo_preserve` call after
* lib-linking to restore some data that should never be affected by undo, e.g. the 3D cursor of

View File

@ -70,12 +70,14 @@ set(SRC
algorithms/COM_algorithm_smaa.hh
algorithms/COM_algorithm_symmetric_separable_blur.hh
cached_resources/intern/cached_texture.cc
cached_resources/intern/morphological_distance_feather_weights.cc
cached_resources/intern/smaa_precomputed_textures.cc
cached_resources/intern/symmetric_blur_weights.cc
cached_resources/intern/symmetric_separable_blur_weights.cc
cached_resources/COM_cached_resource.hh
cached_resources/COM_cached_texture.hh
cached_resources/COM_morphological_distance_feather_weights.hh
cached_resources/COM_smaa_precomputed_textures.hh
cached_resources/COM_symmetric_blur_weights.hh
@ -248,4 +250,16 @@ endforeach()
set(shader_create_info_list_file "${CMAKE_CURRENT_BINARY_DIR}/compositor_shader_create_info_list.hh")
file(GENERATE OUTPUT ${shader_create_info_list_file} CONTENT "${SHADER_CREATE_INFOS_CONTENT}")
if(WITH_TBB)
list(APPEND INC_SYS
${TBB_INCLUDE_DIRS}
)
add_definitions(-DWITH_TBB)
if(WIN32)
# TBB includes Windows.h which will define min/max macros
# that will collide with the stl versions.
add_definitions(-DNOMINMAX)
endif()
endif()
blender_add_lib(bf_realtime_compositor "${SRC}" "${INC}" "${INC_SYS}" "${LIB}")

View File

@ -5,6 +5,7 @@
#include "BLI_math_vector_types.hh"
#include "BLI_string_ref.hh"
#include "DNA_ID.h"
#include "DNA_scene_types.h"
#include "DNA_vec_types.h"
@ -70,6 +71,14 @@ class Context {
* appropriate place, which can be directly in the UI or just logged to the output stream. */
virtual void set_info_message(StringRef message) const = 0;
/* Returns the ID recalculate flag of the given ID and reset it to zero. The given ID is assumed
* to be one that has a DrawDataList and conforms to the IdDdtTemplate.
*
* The ID recalculate flag is a mechanism through which one can identify if an ID has changed
* since the last time the flag was reset, hence why the method reset the flag after querying it,
* that is, to ready it to track the next change. */
virtual IDRecalcFlag query_id_recalc_flag(ID *id) const = 0;
/* Get the size of the compositing region. See get_compositing_region(). */
int2 get_compositing_region_size() const;

View File

@ -7,6 +7,11 @@
#include "BLI_map.hh"
#include "BLI_math_vector_types.hh"
#include "DNA_scene_types.h"
#include "DNA_texture_types.h"
#include "COM_cached_texture.hh"
#include "COM_context.hh"
#include "COM_morphological_distance_feather_weights.hh"
#include "COM_smaa_precomputed_textures.hh"
#include "COM_symmetric_blur_weights.hh"
@ -14,6 +19,8 @@
namespace blender::realtime_compositor {
class Context;
/* -------------------------------------------------------------------------------------------------
* Static Cache Manager
*
@ -48,6 +55,11 @@ class StaticCacheManager {
Map<MorphologicalDistanceFeatherWeightsKey, std::unique_ptr<MorphologicalDistanceFeatherWeights>>
morphological_distance_feather_weights_;
/* A nested map that stores all CachedTexture cached resources. The outer map identifies the
* textures using their ID name, while the inner map identifies the textures using their
* parameters. */
Map<std::string, Map<CachedTextureKey, std::unique_ptr<CachedTexture>>> cached_textures_;
/* A unique pointers that stores the cached SMAAPrecomputedTextures, if one is cached. */
std::unique_ptr<SMAAPrecomputedTextures> smaa_precomputed_textures_;
@ -77,6 +89,15 @@ class StaticCacheManager {
MorphologicalDistanceFeatherWeights &get_morphological_distance_feather_weights(int type,
int radius);
/* Check if the given texture ID has changed since the last time it was retrieved through its
* recalculate flag, and if so, invalidate its corresponding cached textures and reset the
* recalculate flag to ready it to track the next change. Then, check if there is an available
* CachedTexture cached resource with the given parameters in the manager, if one exists, return
* it, otherwise, return a newly created one and add it to the manager. In both cases, tag the
* cached resource as needed to keep it cached for the next evaluation. */
CachedTexture &get_cached_texture(
Context &context, Tex *texture, const Scene *scene, int2 size, float2 offset, float2 scale);
/* Check if a cached SMAA precomputed texture exists, if it does, return it, otherwise, return
* a newly created one and store it in the manager. In both cases, tag the cached resource as
* needed to keep it cached for the next evaluation. */

View File

@ -0,0 +1,55 @@
/* SPDX-License-Identifier: GPL-2.0-or-later */
#pragma once
#include <cstdint>
#include "BLI_math_vector_types.hh"
#include "GPU_texture.h"
#include "DNA_scene_types.h"
#include "DNA_texture_types.h"
#include "COM_cached_resource.hh"
namespace blender::realtime_compositor {
/* ------------------------------------------------------------------------------------------------
* Cached Texture Key.
*/
class CachedTextureKey {
public:
int2 size;
float2 offset;
float2 scale;
CachedTextureKey(int2 size, float2 offset, float2 scale);
uint64_t hash() const;
};
bool operator==(const CachedTextureKey &a, const CachedTextureKey &b);
/* -------------------------------------------------------------------------------------------------
* Cached Texture.
*
* A cached resource that computes and caches a GPU texture containing the the result of evaluating
* the given texture ID on a space that spans the given size, modified by the given offset and
* scale. */
class CachedTexture : public CachedResource {
private:
GPUTexture *color_texture_ = nullptr;
GPUTexture *value_texture_ = nullptr;
public:
CachedTexture(Tex *texture, const Scene *scene, int2 size, float2 offset, float2 scale);
~CachedTexture();
GPUTexture *color_texture();
GPUTexture *value_texture();
};
} // namespace blender::realtime_compositor

View File

@ -0,0 +1,102 @@
/* SPDX-License-Identifier: GPL-2.0-or-later */
#include <cstdint>
#include "BLI_array.hh"
#include "BLI_hash.hh"
#include "BLI_index_range.hh"
#include "BLI_math_vector_types.hh"
#include "BLI_task.hh"
#include "GPU_texture.h"
#include "BKE_texture.h"
#include "DNA_scene_types.h"
#include "DNA_texture_types.h"
#include "RE_texture.h"
#include "COM_cached_texture.hh"
namespace blender::realtime_compositor {
/* --------------------------------------------------------------------
* Cached Texture Key.
*/
CachedTextureKey::CachedTextureKey(int2 size, float2 offset, float2 scale)
: size(size), offset(offset), scale(scale)
{
}
uint64_t CachedTextureKey::hash() const
{
return get_default_hash_3(size, offset, scale);
}
bool operator==(const CachedTextureKey &a, const CachedTextureKey &b)
{
return a.size == b.size && a.offset == b.offset && a.scale == b.scale;
}
/* --------------------------------------------------------------------
* Cached Texture.
*/
CachedTexture::CachedTexture(
Tex *texture, const Scene *scene, int2 size, float2 offset, float2 scale)
{
Array<float4> color_pixels(size.x * size.y);
Array<float> value_pixels(size.x * size.y);
threading::parallel_for(IndexRange(size.y), 1, [&](const IndexRange sub_y_range) {
for (const int64_t y : sub_y_range) {
for (const int64_t x : IndexRange(size.x)) {
/* Compute the coordinates in the [0, 1] range and add 0.5 to evaluate the texture at the
* center of pixels in case it was interpolated. */
float2 coordinates = ((float2(x, y) + 0.5f) / float2(size)) * 2.0f - 1.0f;
/* Note that it is expected that the offset is scaled by the scale. */
coordinates = (coordinates + offset) * scale;
TexResult texture_result;
BKE_texture_get_value(scene, texture, coordinates, &texture_result, true);
color_pixels[y * size.x + x] = float4(texture_result.trgba);
value_pixels[y * size.x + x] = texture_result.talpha ? texture_result.trgba[3] :
texture_result.tin;
}
}
});
color_texture_ = GPU_texture_create_2d("Cached Color Texture",
size.x,
size.y,
1,
GPU_RGBA16F,
GPU_TEXTURE_USAGE_SHADER_READ,
*color_pixels.data());
value_texture_ = GPU_texture_create_2d("Cached Value Texture",
size.x,
size.y,
1,
GPU_R16F,
GPU_TEXTURE_USAGE_SHADER_READ,
value_pixels.data());
}
CachedTexture::~CachedTexture()
{
GPU_texture_free(color_texture_);
GPU_texture_free(value_texture_);
}
GPUTexture *CachedTexture::color_texture()
{
return color_texture_;
}
GPUTexture *CachedTexture::value_texture()
{
return value_texture_;
}
} // namespace blender::realtime_compositor

View File

@ -4,6 +4,11 @@
#include "BLI_math_vector_types.hh"
#include "DNA_ID.h"
#include "DNA_scene_types.h"
#include "DNA_texture_types.h"
#include "COM_context.hh"
#include "COM_morphological_distance_feather_weights.hh"
#include "COM_smaa_precomputed_textures.hh"
#include "COM_symmetric_blur_weights.hh"
@ -23,6 +28,12 @@ void StaticCacheManager::reset()
symmetric_blur_weights_.remove_if([](auto item) { return !item.value->needed; });
symmetric_separable_blur_weights_.remove_if([](auto item) { return !item.value->needed; });
morphological_distance_feather_weights_.remove_if([](auto item) { return !item.value->needed; });
for (auto &cached_textures_for_id : cached_textures_.values()) {
cached_textures_for_id.remove_if([](auto item) { return !item.value->needed; });
}
cached_textures_.remove_if([](auto item) { return item.value.is_empty(); });
if (smaa_precomputed_textures_ && !smaa_precomputed_textures_->needed) {
smaa_precomputed_textures_.reset();
}
@ -38,6 +49,11 @@ void StaticCacheManager::reset()
for (auto &value : morphological_distance_feather_weights_.values()) {
value->needed = false;
}
for (auto &cached_textures_for_id : cached_textures_.values()) {
for (auto &value : cached_textures_for_id.values()) {
value->needed = false;
}
}
if (smaa_precomputed_textures_) {
smaa_precomputed_textures_->needed = false;
}
@ -78,6 +94,24 @@ MorphologicalDistanceFeatherWeights &StaticCacheManager::
return weights;
}
CachedTexture &StaticCacheManager::get_cached_texture(
Context &context, Tex *texture, const Scene *scene, int2 size, float2 offset, float2 scale)
{
const CachedTextureKey key(size, offset, scale);
auto &cached_textures_for_id = cached_textures_.lookup_or_add_default(texture->id.name);
if (context.query_id_recalc_flag(reinterpret_cast<ID *>(texture)) & ID_RECALC_ALL) {
cached_textures_for_id.clear();
}
auto &cached_texture = *cached_textures_for_id.lookup_or_add_cb(
key, [&]() { return std::make_unique<CachedTexture>(texture, scene, size, offset, scale); });
cached_texture.needed = true;
return cached_texture;
}
SMAAPrecomputedTextures &StaticCacheManager::get_smaa_precomputed_textures()
{
if (!smaa_precomputed_textures_) {

View File

@ -8,6 +8,7 @@
#include "BLT_translation.h"
#include "DNA_ID.h"
#include "DNA_ID_enums.h"
#include "DNA_camera_types.h"
#include "DNA_object_types.h"
@ -143,6 +144,15 @@ class Context : public realtime_compositor::Context {
{
message.copy(info_message_, GPU_INFO_SIZE);
}
IDRecalcFlag query_id_recalc_flag(ID *id) const override
{
DrawEngineType *owner = &draw_engine_compositor_type;
DrawData *draw_data = DRW_drawdata_ensure(id, owner, sizeof(DrawData), nullptr, nullptr);
IDRecalcFlag recalc_flag = IDRecalcFlag(draw_data->recalc);
draw_data->recalc = IDRecalcFlag(0);
return recalc_flag;
}
};
class Engine {

View File

@ -832,6 +832,7 @@ static bool id_type_can_have_drawdata(const short id_type)
case ID_OB:
case ID_WO:
case ID_SCE:
case ID_TE:
return true;
/* no DrawData */

View File

@ -1659,6 +1659,29 @@ int insert_keyframe(Main *bmain,
return ret;
}
void ED_keyframes_add(FCurve *fcu, int num_keys_to_add)
{
BLI_assert_msg(num_keys_to_add >= 0, "cannot remove keyframes with this function");
if (num_keys_to_add == 0) {
return;
}
fcu->bezt = MEM_recallocN(fcu->bezt, sizeof(BezTriple) * (fcu->totvert + num_keys_to_add));
BezTriple *bezt = fcu->bezt + fcu->totvert; /* Pointer to the first new one. '*/
fcu->totvert += num_keys_to_add;
/* Iterate over the new keys to update their settings. */
while (num_keys_to_add--) {
/* Defaults, ignoring user-preference gives predictable results for API. */
bezt->f1 = bezt->f2 = bezt->f3 = SELECT;
bezt->ipo = BEZT_IPO_BEZ;
bezt->h1 = bezt->h2 = HD_AUTO_ANIM;
bezt++;
}
}
/* ************************************************** */
/* KEYFRAME DELETION */

View File

@ -123,6 +123,17 @@ int insert_vert_fcurve(struct FCurve *fcu,
eBezTriple_KeyframeType keyframe_type,
eInsertKeyFlags flag);
/**
* Add the given number of keyframes to the FCurve. Their coordinates are
* uninitialized, so the curve should not be used without further attention.
*
* The newly created keys are selected, existing keys are not touched.
*
* This can be used to allocate all the keys at once, and then update them
* afterwards.
*/
void ED_keyframes_add(struct FCurve *fcu, int num_keys_to_add);
/* -------- */
/**

View File

@ -3158,12 +3158,14 @@ static bool ui_textedit_insert_buf(uiBut *but,
return changed;
}
#ifdef WITH_INPUT_IME
static bool ui_textedit_insert_ascii(uiBut *but, uiHandleButtonData *data, const char ascii)
{
BLI_assert(isascii(ascii));
const char buf[2] = {ascii, '\0'};
return ui_textedit_insert_buf(but, data, buf, sizeof(buf) - 1);
}
#endif
static void ui_textedit_move(uiBut *but,
uiHandleButtonData *data,
@ -3940,9 +3942,6 @@ static void ui_do_but_textedit(
else if (event->type == WM_IME_COMPOSITE_END) {
changed = true;
}
#else
/* Prevent the function from being unused. */
(void)ui_textedit_insert_ascii;
#endif
if (changed) {

View File

@ -260,6 +260,7 @@ void SCULPT_filter_cache_free(SculptSession *ss)
MEM_SAFE_FREE(ss->filter_cache->limit_surface_co);
MEM_SAFE_FREE(ss->filter_cache->pre_smoothed_color);
MEM_delete<FilterCache>(ss->filter_cache);
ss->filter_cache = nullptr;
}
typedef enum eSculptMeshFilterType {

View File

@ -1246,13 +1246,7 @@ static void snap_source_active_fn(TransInfo *t)
{
/* Only need to calculate once */
if ((t->tsnap.status & SNAP_SOURCE_FOUND) == 0) {
if (t->around == V3D_AROUND_ACTIVE) {
/* Just copy the already calculated active center. */
copy_v3_v3(t->tsnap.snap_source, t->center_global);
TargetSnapOffset(t, nullptr);
t->tsnap.status |= SNAP_SOURCE_FOUND;
}
else if (calculateCenterActive(t, true, t->tsnap.snap_source)) {
if (calculateCenterActive(t, true, t->tsnap.snap_source)) {
TargetSnapOffset(t, nullptr);
t->tsnap.status |= SNAP_SOURCE_FOUND;
}

View File

@ -151,13 +151,36 @@ shaderc::Compiler &VKBackend::get_shaderc_compiler()
return shaderc_compiler_;
}
void VKBackend::capabilities_init(VKContext & /*context*/)
void VKBackend::capabilities_init(VKContext &context)
{
const VkPhysicalDeviceLimits limits = context.physical_device_limits_get();
/* Reset all capabilities from previous context. */
GCaps = {};
GCaps.compute_shader_support = true;
GCaps.shader_storage_buffer_objects_support = true;
GCaps.shader_image_load_store_support = true;
GCaps.max_texture_size = max_ii(limits.maxImageDimension1D, limits.maxImageDimension2D);
GCaps.max_texture_3d_size = limits.maxImageDimension3D;
GCaps.max_texture_layers = limits.maxImageArrayLayers;
GCaps.max_textures = limits.maxDescriptorSetSampledImages;
GCaps.max_textures_vert = limits.maxPerStageDescriptorSampledImages;
GCaps.max_textures_geom = limits.maxPerStageDescriptorSampledImages;
GCaps.max_textures_frag = limits.maxPerStageDescriptorSampledImages;
GCaps.max_samplers = limits.maxSamplerAllocationCount;
for (int i = 0; i < 3; i++) {
GCaps.max_work_group_count[i] = limits.maxComputeWorkGroupCount[i];
GCaps.max_work_group_size[i] = limits.maxComputeWorkGroupSize[i];
}
GCaps.max_uniforms_vert = limits.maxPerStageDescriptorUniformBuffers;
GCaps.max_uniforms_frag = limits.maxPerStageDescriptorUniformBuffers;
GCaps.max_batch_indices = limits.maxDrawIndirectCount;
GCaps.max_batch_vertices = limits.maxDrawIndexedIndexValue;
GCaps.max_vertex_attribs = limits.maxVertexInputAttributes;
GCaps.max_varying_floats = limits.maxVertexOutputComponents;
GCaps.max_shader_storage_buffer_bindings = limits.maxPerStageDescriptorStorageBuffers;
GCaps.max_compute_shader_storage_blocks = limits.maxPerStageDescriptorStorageBuffers;
}
} // namespace blender::gpu

View File

@ -39,10 +39,7 @@ VKContext::VKContext(void *ghost_window, void *ghost_context)
/* Initialize the memory allocator. */
VmaAllocatorCreateInfo info = {};
/* Should use same vulkan version as GHOST (1.2), but set to 1.0 as 1.2 requires
* correct extensions and functions to be found by VMA, which isn't working as expected and
* requires more research. To continue development we lower the API to version 1.0. */
info.vulkanApiVersion = VK_API_VERSION_1_0;
info.vulkanApiVersion = VK_API_VERSION_1_2;
info.physicalDevice = vk_physical_device_;
info.device = vk_device_;
info.instance = vk_instance_;

View File

@ -153,6 +153,8 @@ typedef struct Tex {
ID id;
/** Animation data (must be immediately after id for utilities to use it). */
struct AnimData *adt;
/* runtime (must be immediately after id for utilities to use it). */
DrawDataList drawdata;
float noisesize, turbul;
float bright, contrast, saturation, rfac, gfac, bfac;
@ -264,14 +266,14 @@ typedef struct ColorMapping {
#define TEX_STUCCI 6
#define TEX_NOISE 7
#define TEX_IMAGE 8
//#define TEX_PLUGIN 9 /* Deprecated */
//#define TEX_ENVMAP 10 /* Deprecated */
// #define TEX_PLUGIN 9 /* Deprecated */
// #define TEX_ENVMAP 10 /* Deprecated */
#define TEX_MUSGRAVE 11
#define TEX_VORONOI 12
#define TEX_DISTNOISE 13
//#define TEX_POINTDENSITY 14 /* Deprecated */
//#define TEX_VOXELDATA 15 /* Deprecated */
//#define TEX_OCEAN 16 /* Deprecated */
// #define TEX_POINTDENSITY 14 /* Deprecated */
// #define TEX_VOXELDATA 15 /* Deprecated */
// #define TEX_OCEAN 16 /* Deprecated */
/* musgrave stype */
#define TEX_MFRACTAL 0

View File

@ -6,6 +6,8 @@
#pragma once
#include "BLI_utildefines.h"
#ifdef __cplusplus
extern "C" {
#endif
@ -50,6 +52,7 @@ typedef enum eDupli_ID_Flags {
/* Duplicate (and hence make local) linked data. */
USER_DUP_LINKED_ID = (1 << 30),
} eDupli_ID_Flags;
ENUM_OPERATORS(eDupli_ID_Flags, USER_DUP_LINKED_ID)
#ifdef __cplusplus
}

View File

@ -692,6 +692,7 @@ static void rna_tag_animation_update(Main *bmain, ID *id)
static void rna_FCurve_update_data_ex(ID *id, FCurve *fcu, Main *bmain)
{
sort_time_fcurve(fcu);
/* TODO: Blender 4.0 should call BKE_fcurve_deduplicate_keys(fcu) here. */
BKE_fcurve_handles_recalc(fcu);
rna_tag_animation_update(bmain, id);
@ -1071,24 +1072,12 @@ static BezTriple *rna_FKeyframe_points_insert(
static void rna_FKeyframe_points_add(ID *id, FCurve *fcu, Main *bmain, int tot)
{
if (tot > 0) {
BezTriple *bezt;
fcu->bezt = MEM_recallocN(fcu->bezt, sizeof(BezTriple) * (fcu->totvert + tot));
bezt = fcu->bezt + fcu->totvert;
fcu->totvert += tot;
while (tot--) {
/* Defaults, ignoring user-preference gives predictable results for API. */
bezt->f1 = bezt->f2 = bezt->f3 = SELECT;
bezt->ipo = BEZT_IPO_BEZ;
bezt->h1 = bezt->h2 = HD_AUTO_ANIM;
bezt++;
}
rna_tag_animation_update(bmain, id);
if (tot <= 0) {
return;
}
ED_keyframes_add(fcu, tot);
rna_tag_animation_update(bmain, id);
}
static void rna_FKeyframe_points_remove(
@ -1118,6 +1107,24 @@ static void rna_FKeyframe_points_clear(ID *id, FCurve *fcu, Main *bmain)
rna_tag_animation_update(bmain, id);
}
static void rna_FKeyframe_points_sort(ID *id, FCurve *fcu, Main *bmain)
{
sort_time_fcurve(fcu);
rna_tag_animation_update(bmain, id);
}
static void rna_FKeyframe_points_deduplicate(ID *id, FCurve *fcu, Main *bmain)
{
BKE_fcurve_deduplicate_keys(fcu);
rna_tag_animation_update(bmain, id);
}
static void rna_FKeyframe_points_handles_recalc(ID *id, FCurve *fcu, Main *bmain)
{
BKE_fcurve_handles_recalc(fcu);
rna_tag_animation_update(bmain, id);
}
static FCM_EnvelopeData *rna_FModifierEnvelope_points_add(
ID *id, FModifier *fmod, Main *bmain, ReportList *reports, float frame)
{
@ -2414,6 +2421,22 @@ static void rna_def_fcurve_keyframe_points(BlenderRNA *brna, PropertyRNA *cprop)
func = RNA_def_function(srna, "clear", "rna_FKeyframe_points_clear");
RNA_def_function_ui_description(func, "Remove all keyframes from an F-Curve");
RNA_def_function_flag(func, FUNC_USE_SELF_ID | FUNC_USE_MAIN);
func = RNA_def_function(srna, "sort", "rna_FKeyframe_points_sort");
RNA_def_function_ui_description(func, "Ensure all keyframe points are chronologically sorted");
RNA_def_function_flag(func, FUNC_USE_SELF_ID | FUNC_USE_MAIN);
func = RNA_def_function(srna, "deduplicate", "rna_FKeyframe_points_deduplicate");
RNA_def_function_ui_description(
func,
"Ensure there are no duplicate keys. Assumes that the points have already been sorted");
RNA_def_function_flag(func, FUNC_USE_SELF_ID | FUNC_USE_MAIN);
func = RNA_def_function(srna, "handles_recalc", "rna_FKeyframe_points_handles_recalc");
RNA_def_function_ui_description(func,
"Update handles after modifications to the keyframe points, to "
"update things like auto-clamping");
RNA_def_function_flag(func, FUNC_USE_SELF_ID | FUNC_USE_MAIN);
}
static void rna_def_fcurve(BlenderRNA *brna)

View File

@ -27,8 +27,16 @@ NODE_STORAGE_FUNCS(NodeBoxMask)
static void cmp_node_boxmask_declare(NodeDeclarationBuilder &b)
{
b.add_input<decl::Float>(N_("Mask")).default_value(0.0f).min(0.0f).max(1.0f);
b.add_input<decl::Float>(N_("Value")).default_value(1.0f).min(0.0f).max(1.0f);
b.add_input<decl::Float>(N_("Mask"))
.default_value(0.0f)
.min(0.0f)
.max(1.0f)
.compositor_domain_priority(0);
b.add_input<decl::Float>(N_("Value"))
.default_value(1.0f)
.min(0.0f)
.max(1.0f)
.compositor_domain_priority(1);
b.add_output<decl::Float>(N_("Mask"));
}

View File

@ -27,8 +27,16 @@ NODE_STORAGE_FUNCS(NodeEllipseMask)
static void cmp_node_ellipsemask_declare(NodeDeclarationBuilder &b)
{
b.add_input<decl::Float>(N_("Mask")).default_value(0.0f).min(0.0f).max(1.0f);
b.add_input<decl::Float>(N_("Value")).default_value(1.0f).min(0.0f).max(1.0f);
b.add_input<decl::Float>(N_("Mask"))
.default_value(0.0f)
.min(0.0f)
.max(1.0f)
.compositor_domain_priority(0);
b.add_input<decl::Float>(N_("Value"))
.default_value(1.0f)
.min(0.0f)
.max(1.0f)
.compositor_domain_priority(1);
b.add_output<decl::Float>(N_("Mask"));
}

View File

@ -7,7 +7,9 @@
#include "BLT_translation.h"
#include "COM_cached_texture.hh"
#include "COM_node_operation.hh"
#include "COM_utilities.hh"
#include "node_composite_util.hh"
@ -17,12 +19,17 @@ namespace blender::nodes::node_composite_texture_cc {
static void cmp_node_texture_declare(NodeDeclarationBuilder &b)
{
b.add_input<decl::Vector>(N_("Offset")).min(-2.0f).max(2.0f).subtype(PROP_TRANSLATION);
b.add_input<decl::Vector>(N_("Offset"))
.min(-2.0f)
.max(2.0f)
.subtype(PROP_TRANSLATION)
.compositor_expects_single_value();
b.add_input<decl::Vector>(N_("Scale"))
.default_value({1.0f, 1.0f, 1.0f})
.min(-10.0f)
.max(10.0f)
.subtype(PROP_XYZ);
.subtype(PROP_XYZ)
.compositor_expects_single_value();
b.add_output<decl::Float>(N_("Value"));
b.add_output<decl::Color>(N_("Color"));
}
@ -35,9 +42,46 @@ class TextureOperation : public NodeOperation {
void execute() override
{
get_result("Value").allocate_invalid();
get_result("Color").allocate_invalid();
context().set_info_message("Viewport compositor setup not fully supported");
Result &color_result = get_result("Color");
Result &value_result = get_result("Value");
if (!get_texture()) {
if (color_result.should_compute()) {
color_result.allocate_invalid();
}
if (value_result.should_compute()) {
value_result.allocate_invalid();
}
return;
}
const Domain domain = compute_domain();
CachedTexture &cached_texture = context().cache_manager().get_cached_texture(
context(),
get_texture(),
context().get_scene(),
domain.size,
get_input("Offset").get_vector_value_default(float4(0.0f)).xy(),
get_input("Scale").get_vector_value_default(float4(0.0f)).xy());
if (color_result.should_compute()) {
color_result.allocate_texture(domain);
GPU_texture_copy(color_result.texture(), cached_texture.color_texture());
}
if (value_result.should_compute()) {
value_result.allocate_texture(domain);
GPU_texture_copy(value_result.texture(), cached_texture.value_texture());
}
}
Domain compute_domain() override
{
return Domain(context().get_compositing_region_size());
}
Tex *get_texture()
{
return (Tex *)bnode().id;
}
};
@ -58,8 +102,6 @@ void register_node_type_cmp_texture()
ntype.declare = file_ns::cmp_node_texture_declare;
ntype.flag |= NODE_PREVIEW;
ntype.get_compositor_operation = file_ns::get_compositor_operation;
ntype.realtime_compositor_unsupported_message = N_(
"Node not supported in the Viewport compositor");
nodeRegisterType(&ntype);
}

View File

@ -28,18 +28,9 @@ static void node_declare(NodeDeclarationBuilder &b)
{
b.add_input<decl::String>(N_("String"));
b.add_input<decl::Float>(N_("Size")).default_value(1.0f).min(0.0f).subtype(PROP_DISTANCE);
b.add_input<decl::Float>(N_("Character Spacing"))
.default_value(1.0f)
.min(0.0f)
.subtype(PROP_DISTANCE);
b.add_input<decl::Float>(N_("Word Spacing"))
.default_value(1.0f)
.min(0.0f)
.subtype(PROP_DISTANCE);
b.add_input<decl::Float>(N_("Line Spacing"))
.default_value(1.0f)
.min(0.0f)
.subtype(PROP_DISTANCE);
b.add_input<decl::Float>(N_("Character Spacing")).default_value(1.0f).min(0.0f);
b.add_input<decl::Float>(N_("Word Spacing")).default_value(1.0f).min(0.0f);
b.add_input<decl::Float>(N_("Line Spacing")).default_value(1.0f).min(0.0f);
b.add_input<decl::Float>(N_("Text Box Width"))
.default_value(0.0f)
.min(0.0f)

View File

@ -75,7 +75,7 @@ static PyObject *bpy_rna_region_as_string(PyObject *self, PyObject *args, PyObje
return NULL;
}
if (PyDict_GET_SIZE(kwds) > 0) {
if (kwds && PyDict_GET_SIZE(kwds) > 0) {
txt_sel_set(text, region.curl, region.curc, region.sell, region.selc);
}
@ -140,7 +140,7 @@ static PyObject *bpy_rna_region_from_string(PyObject *self, PyObject *args, PyOb
return NULL;
}
if (PyDict_GET_SIZE(kwds) > 0) {
if (kwds && PyDict_GET_SIZE(kwds) > 0) {
txt_sel_set(text, region.curl, region.curc, region.sell, region.selc);
}

View File

@ -2619,6 +2619,8 @@ void *WM_opengl_context_create(void)
BLI_assert(GPU_framebuffer_active_get() == GPU_framebuffer_back_get());
GHOST_GLSettings glSettings = {0};
const eGPUBackendType gpu_backend = GPU_backend_type_selection_get();
glSettings.context_type = wm_ghost_drawing_context_type(gpu_backend);
if (G.debug & G_DEBUG_GPU) {
glSettings.flags |= GHOST_glDebugContext;
}