USD: Skeleton and blend shape import #110912

Merged
Michael Kowalski merged 38 commits from makowalski/blender:usdskel_import into main 2023-08-17 20:11:58 +02:00
87 changed files with 1599 additions and 238 deletions
Showing only changes of commit 4c91b30530 - Show all commits

View File

@ -19,6 +19,11 @@ float safe_modulo(float a, float b)
return (b != 0.0) ? fmod(a, b) : 0.0;
}
float safe_floored_modulo(float a, float b)
{
return (b != 0.0) ? a - floor(a / b) * b : 0.0;
}
float fract(float a)
{
return a - floor(a);

View File

@ -52,6 +52,8 @@ shader node_math(string math_type = "add",
Value = Value1 - floor(Value1);
else if (math_type == "modulo")
Value = safe_modulo(Value1, Value2);
else if (math_type == "floored_modulo")
Value = safe_floored_modulo(Value1, Value2);
else if (math_type == "trunc")
Value = trunc(Value1);
else if (math_type == "snap")

View File

@ -145,6 +145,8 @@ ccl_device float svm_math(NodeMathType type, float a, float b, float c)
return a - floorf(a);
case NODE_MATH_MODULO:
return safe_modulo(a, b);
case NODE_MATH_FLOORED_MODULO:
return safe_floored_modulo(a, b);
case NODE_MATH_TRUNC:
return a >= 0.0f ? floorf(a) : ceilf(a);
case NODE_MATH_SNAP:

View File

@ -182,6 +182,7 @@ typedef enum NodeMathType {
NODE_MATH_PINGPONG,
NODE_MATH_SMOOTH_MIN,
NODE_MATH_SMOOTH_MAX,
NODE_MATH_FLOORED_MODULO,
} NodeMathType;
typedef enum NodeVectorMathType {

View File

@ -6292,6 +6292,7 @@ NODE_DEFINE(MathNode)
type_enum.insert("less_than", NODE_MATH_LESS_THAN);
type_enum.insert("greater_than", NODE_MATH_GREATER_THAN);
type_enum.insert("modulo", NODE_MATH_MODULO);
type_enum.insert("floored_modulo", NODE_MATH_FLOORED_MODULO);
type_enum.insert("absolute", NODE_MATH_ABSOLUTE);
type_enum.insert("arctan2", NODE_MATH_ARCTAN2);
type_enum.insert("floor", NODE_MATH_FLOOR);

View File

@ -732,6 +732,11 @@ ccl_device float safe_modulo(float a, float b)
return (b != 0.0f) ? fmodf(a, b) : 0.0f;
}
ccl_device float safe_floored_modulo(float a, float b)
{
return (b != 0.0f) ? a - floorf(a / b) * b : 0.0f;
}
ccl_device_inline float sqr(float a)
{
return a * a;

View File

@ -219,6 +219,7 @@ class GHOST_DeviceVK {
device_features.dualSrcBlend = VK_TRUE;
device_features.logicOp = VK_TRUE;
device_features.imageCubeArray = VK_TRUE;
device_features.multiViewport = VK_TRUE;
#endif
VkDeviceCreateInfo device_create_info = {};

View File

@ -18,7 +18,7 @@ luma: [0.2126, 0.7152, 0.0722]
description: RRT version ut33
roles:
reference: Linear
reference: Linear CIE-XYZ E
# Internal scene linear space
scene_linear: Linear
@ -41,6 +41,7 @@ roles:
# For interop between configs, and to determine XYZ for rendering
aces_interchange: Linear ACES
cie_xyz_d65_interchange: Linear CIE-XYZ D65
# Specified by OCIO, not used in Blender
color_timing: Filmic Log
@ -61,6 +62,27 @@ active_displays: [sRGB]
active_views: [Standard, Filmic, Filmic Log, False Color, Raw]
colorspaces:
- !<ColorSpace>
name: Linear CIE-XYZ E
aliases: ["FilmLight: Linear - XYZ", Linear CIE-XYZ I-E]
family: Chromaticity
equalitygroup:
bitdepth: 32f
description: |
1931 CIE XYZ standard with assumed illuminant E white point
isdata: false
- !<ColorSpace>
name: Linear CIE-XYZ D65
aliases: [cie_xyz_d65, CIE-XYZ-D65, XYZ, Linear CIE-XYZ D65]
family: Chromaticity
equalitygroup:
bitdepth: 32f
description: |
1931 CIE XYZ with adapted illuminant D65 white point
isdata: false
from_scene_reference: !<FileTransform> {src: xyz_E_to_D65.spimtx, interpolation: linear}
- !<ColorSpace>
name: Linear
family: linear
@ -69,6 +91,10 @@ colorspaces:
description: |
Rec. 709 (Full Range), Blender native linear space
isdata: false
from_scene_reference: !<GroupTransform>
children:
- !<ColorSpaceTransform> {src: Linear CIE-XYZ E, dst: Linear CIE-XYZ D65}
- !<MatrixTransform> {matrix: [ 3.2410032329763587, -1.5373989694887855, -0.4986158819963629, 0, -0.9692242522025164, 1.8759299836951759, 0.0415542263400847, 0, 0.0556394198519755, -0.2040112061239099, 1.0571489771875333, 0, 0, 0, 0, 1]}
- !<ColorSpace>
name: Linear ACES
@ -80,7 +106,7 @@ colorspaces:
isdata: false
from_reference: !<GroupTransform>
children:
- !<FileTransform> {src: srgb_to_xyz.spimtx, interpolation: linear}
- !<ColorSpaceTransform> {src: Linear CIE-XYZ E, dst: Linear CIE-XYZ D65}
- !<BuiltinTransform> {style: "UTILITY - ACES-AP0_to_CIE-XYZ-D65_BFD", direction: inverse}
- !<ColorSpace>
@ -93,19 +119,9 @@ colorspaces:
isdata: false
from_reference: !<GroupTransform>
children:
- !<FileTransform> {src: srgb_to_xyz.spimtx, interpolation: linear}
- !<ColorSpaceTransform> {src: Linear CIE-XYZ E, dst: Linear CIE-XYZ D65}
- !<BuiltinTransform> {style: "UTILITY - ACES-AP1_to_CIE-XYZ-D65_BFD", direction: inverse}
- !<ColorSpace>
name: XYZ
family: linear
equalitygroup:
bitdepth: 32f
isdata: false
from_reference: !<GroupTransform>
children:
- !<FileTransform> {src: srgb_to_xyz.spimtx, interpolation: linear}
- !<ColorSpace>
name: sRGB
family:
@ -114,7 +130,10 @@ colorspaces:
description: |
sRGB display space
isdata: false
from_reference: !<ExponentWithLinearTransform> {gamma: 2.4, offset: 0.055, direction: inverse}
from_scene_reference: !<GroupTransform>
children:
- !<ColorSpaceTransform> {src: Linear CIE-XYZ E, dst: Linear}
- !<ExponentWithLinearTransform> {gamma: 2.4, offset: 0.055, direction: inverse}
- !<ColorSpace>
name: Non-Color
@ -135,11 +154,15 @@ colorspaces:
Log based filmic shaper with 16.5 stops of latitude, and 25 stops of dynamic range
isdata: false
from_reference: !<GroupTransform>
children:
- !<AllocationTransform> {allocation: lg2, vars: [-12.473931188, 12.526068812]}
- !<FileTransform> {src: filmic_desat65cube.spi3d, interpolation: best}
- !<AllocationTransform> {allocation: uniform, vars: [0, 0.66]}
to_reference: !<AllocationTransform> {allocation: lg2, vars: [-12.473931188, 4.026068812], direction: inverse}
children:
- !<ColorSpaceTransform> {src: Linear CIE-XYZ E, dst: Linear}
- !<AllocationTransform> {allocation: lg2, vars: [-12.473931188, 12.526068812]}
- !<FileTransform> {src: filmic_desat65cube.spi3d, interpolation: best}
- !<AllocationTransform> {allocation: uniform, vars: [0, 0.66]}
to_scene_reference: !<GroupTransform>
children:
- !<AllocationTransform> {allocation: lg2, vars: [-12.473931188, 4.026068812], direction: inverse}
- !<ColorSpaceTransform> {src: Linear CIE-XYZ E, dst: Linear, direction: inverse}
- !<ColorSpace>
name: Filmic sRGB
@ -150,9 +173,9 @@ colorspaces:
sRGB display space with Filmic view transform
isdata: false
from_reference: !<GroupTransform>
children:
- !<ColorSpaceTransform> {src: Linear, dst: Filmic Log}
- !<FileTransform> {src: filmic_to_0-70_1-03.spi1d, interpolation: linear}
children:
- !<ColorSpaceTransform> {src: Linear CIE-XYZ E, dst: Filmic Log}
- !<FileTransform> {src: filmic_to_0-70_1-03.spi1d, interpolation: linear}
- !<ColorSpace>
name: False Color
@ -163,61 +186,61 @@ colorspaces:
Filmic false color view transform
isdata: false
from_reference: !<GroupTransform>
children:
- !<ColorSpaceTransform> {src: Linear, dst: Filmic Log}
- !<MatrixTransform> {matrix: [0.2126729, 0.7151521, 0.0721750, 0, 0.2126729, 0.7151521, 0.0721750, 0, 0.2126729, 0.7151521, 0.0721750, 0, 0, 0, 0, 1]}
- !<FileTransform> {src: filmic_false_color.spi3d, interpolation: best}
children:
- !<ColorSpaceTransform> {src: Linear CIE-XYZ E, dst: Filmic Log}
- !<MatrixTransform> {matrix: [0.2126729, 0.7151521, 0.0721750, 0, 0.2126729, 0.7151521, 0.0721750, 0, 0.2126729, 0.7151521, 0.0721750, 0, 0, 0, 0, 1]}
- !<FileTransform> {src: filmic_false_color.spi3d, interpolation: best}
looks:
- !<Look>
name: Very High Contrast
process_space: Filmic Log
transform: !<GroupTransform>
children:
- !<FileTransform> {src: filmic_to_1.20_1-00.spi1d, interpolation: linear}
- !<FileTransform> {src: filmic_to_0-70_1-03.spi1d, interpolation: linear, direction: inverse}
children:
- !<FileTransform> {src: filmic_to_1.20_1-00.spi1d, interpolation: linear}
- !<FileTransform> {src: filmic_to_0-70_1-03.spi1d, interpolation: linear, direction: inverse}
- !<Look>
name: High Contrast
process_space: Filmic Log
transform: !<GroupTransform>
children:
- !<FileTransform> {src: filmic_to_0.99_1-0075.spi1d, interpolation: linear}
- !<FileTransform> {src: filmic_to_0-70_1-03.spi1d, interpolation: linear, direction: inverse}
children:
- !<FileTransform> {src: filmic_to_0.99_1-0075.spi1d, interpolation: linear}
- !<FileTransform> {src: filmic_to_0-70_1-03.spi1d, interpolation: linear, direction: inverse}
- !<Look>
name: Medium High Contrast
process_space: Filmic Log
transform: !<GroupTransform>
children:
- !<FileTransform> {src: filmic_to_0-85_1-011.spi1d, interpolation: best}
- !<FileTransform> {src: filmic_to_0-70_1-03.spi1d, interpolation: linear, direction: inverse}
children:
- !<FileTransform> {src: filmic_to_0-85_1-011.spi1d, interpolation: best}
- !<FileTransform> {src: filmic_to_0-70_1-03.spi1d, interpolation: linear, direction: inverse}
- !<Look>
name: Medium Contrast
process_space: Filmic Log
transform: !<GroupTransform>
children:
children:
- !<Look>
name: Medium Low Contrast
process_space: Filmic Log
transform: !<GroupTransform>
children:
- !<FileTransform> {src: filmic_to_0-60_1-04.spi1d, interpolation: linear}
- !<FileTransform> {src: filmic_to_0-70_1-03.spi1d, interpolation: linear, direction: inverse}
children:
- !<FileTransform> {src: filmic_to_0-60_1-04.spi1d, interpolation: linear}
- !<FileTransform> {src: filmic_to_0-70_1-03.spi1d, interpolation: linear, direction: inverse}
- !<Look>
name: Low Contrast
process_space: Filmic Log
transform: !<GroupTransform>
children:
- !<FileTransform> {src: filmic_to_0-48_1-09.spi1d, interpolation: linear}
- !<FileTransform> {src: filmic_to_0-70_1-03.spi1d, interpolation: linear, direction: inverse}
children:
- !<FileTransform> {src: filmic_to_0-48_1-09.spi1d, interpolation: linear}
- !<FileTransform> {src: filmic_to_0-70_1-03.spi1d, interpolation: linear, direction: inverse}
- !<Look>
name: Very Low Contrast
process_space: Filmic Log
transform: !<GroupTransform>
children:
- !<FileTransform> {src: filmic_to_0-35_1-30.spi1d, interpolation: linear}
- !<FileTransform> {src: filmic_to_0-70_1-03.spi1d, interpolation: linear, direction: inverse}
children:
- !<FileTransform> {src: filmic_to_0-35_1-30.spi1d, interpolation: linear}
- !<FileTransform> {src: filmic_to_0-70_1-03.spi1d, interpolation: linear, direction: inverse}

View File

@ -1,3 +0,0 @@
0.4124564 0.3575761 0.1804375 0
0.2126729 0.7151522 0.0721750 0
0.0193339 0.1191920 0.9503041 0

View File

@ -1,3 +0,0 @@
1.0521111 0.0000000 0.0000000 0
0.0000000 1.0000000 0.0000000 0
0.0000000 0.0000000 0.9184170 0

View File

@ -0,0 +1,3 @@
0.95318743 -0.02659057 0.02387315 0
-0.03824666 1.02884062 0.00940604 0
0.00260677 -0.00303325 1.08925647 0

View File

@ -79,6 +79,7 @@ const UserDef U_default = {
.scrollback = 256,
.node_margin = 80,
.node_preview_res = 120,
.transopts = USER_TR_TOOLTIPS,
.menuthreshold1 = 5,
.menuthreshold2 = 2,

View File

@ -846,7 +846,7 @@ class NODE_PT_overlay(Panel):
col.prop(overlay, "show_context_path", text="Context Path")
col.prop(snode, "show_annotation", text="Annotations")
if snode.supports_preview:
if snode.supports_previews:
col.separator()
col.prop(overlay, "show_previews", text="Previews")

View File

@ -501,6 +501,7 @@ class USERPREF_PT_edit_misc(EditingPanel, CenterAlignMixIn, Panel):
col = layout.column()
col.prop(edit, "sculpt_paint_overlay_color", text="Sculpt Overlay Color")
col.prop(edit, "node_margin", text="Node Auto-Offset Margin")
col.prop(edit, "node_preview_resolution", text="Node Preview Resolution")
# -----------------------------------------------------------------------------
@ -2407,6 +2408,7 @@ class USERPREF_PT_experimental_new_features(ExperimentalPanel, Panel):
({"property": "use_new_volume_nodes"}, ("blender/blender/issues/103248", "#103248")),
({"property": "use_rotation_socket"}, ("/blender/blender/issues/92967", "#92967")),
({"property": "use_node_group_operators"}, ("/blender/blender/issues/101778", "#101778")),
({"property": "use_shader_node_previews"}, ("blender/blender/issues/110353", "#110353")),
),
)

View File

@ -17,6 +17,7 @@
extern "C" {
#endif
struct bDeformGroup;
struct CustomData;
struct CustomDataLayer;
struct ID;
@ -117,6 +118,12 @@ void BKE_id_attributes_default_color_set(struct ID *id, const char *name);
const struct CustomDataLayer *BKE_id_attributes_color_find(const struct ID *id, const char *name);
typedef struct AttributeAndDefgroupUniqueNameData {
struct ID *id;
struct bDeformGroup *dg;
} AttributeAndDefgroupUniqueNameData;
bool BKE_id_attribute_and_defgroup_unique_name_check(void *arg, const char *name);
bool BKE_id_attribute_calc_unique_name(struct ID *id, const char *name, char *outname);
const char *BKE_uv_map_vert_select_name_get(const char *uv_map_name, char *buffer);

View File

@ -29,7 +29,7 @@ extern "C" {
/* Blender file format version. */
#define BLENDER_FILE_VERSION BLENDER_VERSION
#define BLENDER_FILE_SUBVERSION 14
#define BLENDER_FILE_SUBVERSION 15
/* Minimum Blender version that supports reading file written with the current
* version. Older Blender versions will test this and cancel loading the file, showing a warning to

View File

@ -89,6 +89,7 @@ int *BKE_object_defgroup_flip_map_single(const struct Object *ob,
int BKE_object_defgroup_flip_index(const struct Object *ob, int index, bool use_default);
int BKE_object_defgroup_name_index(const struct Object *ob, const char *name);
void BKE_object_defgroup_unique_name(struct bDeformGroup *dg, struct Object *ob);
bool BKE_defgroup_unique_name_check(void *arg, const char *name);
struct MDeformWeight *BKE_defvert_find_index(const struct MDeformVert *dv, int defgroup);
/**

View File

@ -30,6 +30,12 @@ typedef struct Global {
*/
struct Main *main;
/**
* Preview main is stored to avoid loading the preview file in multiple scenarios.
* It is actually shared between shader node previews and asset previews.
*/
struct Main *pr_main;
/** Last saved location for images. */
char ima[1024]; /* 1024 = FILE_MAX */
/** Last used location for library link/append. */

View File

@ -57,6 +57,7 @@ void BKE_object_material_from_eval_data(struct Main *bmain,
struct Material *BKE_material_add(struct Main *bmain, const char *name);
struct Material *BKE_gpencil_material_add(struct Main *bmain, const char *name);
void BKE_gpencil_material_attr_init(struct Material *ma);
void BKE_material_make_node_previews_dirty(struct Material *ma);
/* UNUSED */
// void automatname(struct Material *);

View File

@ -94,6 +94,12 @@ class bNodeTreeRuntime : NonCopyable, NonMovable {
*/
uint8_t runtime_flag = 0;
/**
* Contains a number increased for each nodetree update.
* Store a state variable in the `NestedTreePreviews` structure to compare if they differ.
*/
uint32_t previews_refresh_state = 0;
/**
* Storage of nodes based on their identifier. Also used as a contiguous array of nodes to
* allow simpler and more cache friendly iteration. Supports lookup by integer or by node.

View File

@ -235,6 +235,8 @@ bool BKE_scene_uses_blender_eevee(const struct Scene *scene);
bool BKE_scene_uses_blender_workbench(const struct Scene *scene);
bool BKE_scene_uses_cycles(const struct Scene *scene);
bool BKE_scene_uses_shader_previews(const struct Scene *scene);
/**
* Return whether the Cycles experimental feature is enabled. It is invalid to call without first
* ensuring that Cycles is the active render engine (e.g. with #BKE_scene_uses_cycles).

View File

@ -31,6 +31,7 @@
#include "BKE_attribute.hh"
#include "BKE_curves.hh"
#include "BKE_customdata.h"
#include "BKE_deform.h"
#include "BKE_editmesh.h"
#include "BKE_mesh.hh"
#include "BKE_pointcloud.h"
@ -223,13 +224,14 @@ bool BKE_id_attribute_rename(ID *id,
return true;
}
struct AttrUniqueData {
ID *id;
};
static bool unique_name_cb(void *arg, const char *name)
bool BKE_id_attribute_and_defgroup_unique_name_check(void *arg, const char *name)
{
AttrUniqueData *data = (AttrUniqueData *)arg;
AttributeAndDefgroupUniqueNameData *data = static_cast<AttributeAndDefgroupUniqueNameData *>(
arg);
if (BKE_defgroup_unique_name_check(data, name)) {
return true;
}
DomainInfo info[ATTR_DOMAIN_NUM];
get_domains(data->id, info);
@ -254,15 +256,18 @@ static bool unique_name_cb(void *arg, const char *name)
bool BKE_id_attribute_calc_unique_name(ID *id, const char *name, char *outname)
{
AttrUniqueData data{id};
AttributeAndDefgroupUniqueNameData data{id, nullptr};
const int name_maxncpy = CustomData_name_maxncpy_calc(name);
/* Set default name if none specified.
* NOTE: We only call IFACE_() if needed to avoid locale lookup overhead. */
BLI_strncpy_utf8(outname, (name && name[0]) ? name : IFACE_("Attribute"), name_maxncpy);
/* Avoid name collisions with vertex groups and other attributes. */
const char *defname = ""; /* Dummy argument, never used as `name` is never zero length. */
return BLI_uniquename_cb(unique_name_cb, &data, defname, '.', outname, name_maxncpy);
return BLI_uniquename_cb(
BKE_id_attribute_and_defgroup_unique_name_check, &data, defname, '.', outname, name_maxncpy);
}
CustomDataLayer *BKE_id_attribute_new(ID *id,
@ -301,6 +306,10 @@ CustomDataLayer *BKE_id_attribute_new(ID *id,
attributes->add(uniquename, domain, eCustomDataType(type), AttributeInitDefaultValue());
const int index = CustomData_get_named_layer_index(customdata, type, uniquename);
if (index == -1) {
BKE_reportf(reports, RPT_WARNING, "Layer '%s' could not be created", uniquename);
}
return (index == -1) ? nullptr : &(customdata->layers[index]);
}

View File

@ -393,7 +393,6 @@ void BKE_collection_compat_blend_read_expand(BlendExpander *expander, SceneColle
void BKE_collection_blend_read_expand(BlendExpander *expander, Collection *collection)
{
BLI_assert(collection->runtime.gobject_hash == nullptr);
LISTBASE_FOREACH (CollectionObject *, cob, &collection->gobject) {
BLO_expand(expander, cob->ob);
}

View File

@ -29,6 +29,7 @@
#include "BLT_translation.h"
#include "BKE_attribute.hh"
#include "BKE_customdata.h"
#include "BKE_data_transfer.h"
#include "BKE_deform.h" /* own include */
@ -681,9 +682,9 @@ int BKE_object_defgroup_flip_index(const Object *ob, int index, const bool use_d
return (flip_index == -1 && use_default) ? index : flip_index;
}
static bool defgroup_find_name_dupe(const char *name, bDeformGroup *dg, Object *ob)
static bool defgroup_find_name_dupe(const char *name, bDeformGroup *dg, ID *id)
{
const ListBase *defbase = BKE_object_defgroup_list(ob);
const ListBase *defbase = BKE_id_defgroup_list_get(id);
LISTBASE_FOREACH (bDeformGroup *, curdef, defbase) {
if (dg != curdef) {
@ -696,21 +697,31 @@ static bool defgroup_find_name_dupe(const char *name, bDeformGroup *dg, Object *
return false;
}
struct DeformGroupUniqueNameData {
Object *ob;
bDeformGroup *dg;
};
static bool defgroup_unique_check(void *arg, const char *name)
bool BKE_defgroup_unique_name_check(void *arg, const char *name)
{
DeformGroupUniqueNameData *data = static_cast<DeformGroupUniqueNameData *>(arg);
return defgroup_find_name_dupe(name, data->dg, data->ob);
AttributeAndDefgroupUniqueNameData *data = static_cast<AttributeAndDefgroupUniqueNameData *>(
arg);
return defgroup_find_name_dupe(name, data->dg, data->id);
}
void BKE_object_defgroup_unique_name(bDeformGroup *dg, Object *ob)
{
DeformGroupUniqueNameData data{ob, dg};
BLI_uniquename_cb(defgroup_unique_check, &data, DATA_("Group"), '.', dg->name, sizeof(dg->name));
/* Avoid name collisions with other vertex groups and (mesh) attributes. */
if (ob->type == OB_MESH) {
Mesh *me = static_cast<Mesh *>(ob->data);
AttributeAndDefgroupUniqueNameData data{&me->id, dg};
BLI_uniquename_cb(BKE_id_attribute_and_defgroup_unique_name_check,
&data,
DATA_("Group"),
'.',
dg->name,
sizeof(dg->name));
}
else {
AttributeAndDefgroupUniqueNameData data{static_cast<ID *>(ob->data), dg};
BLI_uniquename_cb(
BKE_defgroup_unique_name_check, &data, DATA_("Group"), '.', dg->name, sizeof(dg->name));
}
}
float BKE_defvert_find_weight(const MDeformVert *dvert, const int defgroup)

View File

@ -1728,6 +1728,14 @@ void GreasePencil::set_active_layer(const blender::bke::greasepencil::Layer *lay
reinterpret_cast<const GreasePencilLayer *>(layer));
}
bool GreasePencil::is_layer_active(const blender::bke::greasepencil::Layer *layer) const
{
if (layer == nullptr) {
return false;
}
return this->get_active_layer() == layer;
}
static blender::VectorSet<blender::StringRefNull> get_node_names(GreasePencil &grease_pencil)
{
using namespace blender;

View File

@ -304,6 +304,27 @@ void BKE_gpencil_material_attr_init(Material *ma)
}
}
static void nodetree_mark_previews_dirty_reccursive(bNodeTree *tree)
{
if (tree == nullptr) {
return;
}
tree->runtime->previews_refresh_state++;
for (bNode *node : tree->all_nodes()) {
if (node->type == NODE_GROUP) {
bNodeTree *nested_tree = reinterpret_cast<bNodeTree *>(node->id);
nodetree_mark_previews_dirty_reccursive(nested_tree);
}
}
}
void BKE_material_make_node_previews_dirty(Material *ma)
{
if (ma && ma->nodetree) {
nodetree_mark_previews_dirty_reccursive(ma->nodetree);
}
}
Material *BKE_material_add(Main *bmain, const char *name)
{
Material *ma;

View File

@ -478,6 +478,7 @@ class NodeTreeMainUpdater {
this->update_internal_links(ntree);
this->update_generic_callback(ntree);
this->remove_unused_previews_when_necessary(ntree);
this->make_node_previews_dirty(ntree);
this->propagate_runtime_flags(ntree);
if (ntree.type == NTREE_GEOMETRY) {
@ -723,6 +724,19 @@ class NodeTreeMainUpdater {
blender::bke::node_preview_remove_unused(&ntree);
}
void make_node_previews_dirty(bNodeTree &ntree)
{
ntree.runtime->previews_refresh_state++;
for (bNode *node : ntree.all_nodes()) {
if (node->type != NODE_GROUP) {
continue;
}
if (bNodeTree *nested_tree = reinterpret_cast<bNodeTree *>(node->id)) {
this->make_node_previews_dirty(*nested_tree);
}
}
}
void propagate_runtime_flags(const bNodeTree &ntree)
{
ntree.ensure_topology_cache();

View File

@ -870,6 +870,13 @@ static void scene_foreach_id(ID *id, LibraryForeachIDData *data)
LISTBASE_FOREACH (ViewLayer *, view_layer, &scene->view_layers) {
BKE_LIB_FOREACHID_PROCESS_IDSUPER(data, view_layer->mat_override, IDWALK_CB_USER);
BKE_LIB_FOREACHID_PROCESS_FUNCTION_CALL(
data,
IDP_foreach_property(view_layer->id_properties,
IDP_TYPE_FILTER_ID,
BKE_lib_query_idpropertiesForeachIDLink_callback,
data));
BKE_view_layer_synced_ensure(scene, view_layer);
LISTBASE_FOREACH (Base *, base, BKE_view_layer_object_bases_get(view_layer)) {
BKE_LIB_FOREACHID_PROCESS_IDSUPER(
@ -3017,6 +3024,11 @@ bool BKE_scene_uses_cycles(const Scene *scene)
return STREQ(scene->r.engine, RE_engine_id_CYCLES);
}
bool BKE_scene_uses_shader_previews(const Scene *scene)
{
return BKE_scene_uses_blender_eevee(scene) || BKE_scene_uses_cycles(scene);
}
/* This enumeration has to match the one defined in the Cycles addon. */
enum eCyclesFeatureSet {
CYCLES_FEATURES_SUPPORTED = 0,

View File

@ -22,6 +22,11 @@ MINLINE float safe_modf(float a, float b)
return (b != 0.0f) ? fmodf(a, b) : 0.0f;
}
MINLINE float safe_floored_modf(float a, float b)
{
return (b != 0.0f) ? a - floorf(a / b) * b : 0.0f;
}
MINLINE float safe_logf(float a, float base)
{
if (UNLIKELY(a <= 0.0f || base <= 0.0f)) {

View File

@ -847,6 +847,10 @@ void blo_do_versions_userdef(UserDef *userdef)
#endif
}
if (!USER_VERSION_ATLEAST(400, 15)) {
userdef->node_preview_res = 120;
}
/**
* Versioning code until next subversion bump goes here.
*

View File

@ -76,6 +76,9 @@ void MathNode::convert_to_operations(NodeConverter &converter,
case NODE_MATH_MODULO:
operation = new MathModuloOperation();
break;
case NODE_MATH_FLOORED_MODULO:
operation = new MathFlooredModuloOperation();
break;
case NODE_MATH_ABSOLUTE:
operation = new MathAbsoluteOperation();
break;

View File

@ -589,6 +589,36 @@ void MathModuloOperation::update_memory_buffer_partial(BuffersIterator<float> &i
}
}
void MathFlooredModuloOperation::execute_pixel_sampled(float output[4],
float x,
float y,
PixelSampler sampler)
{
float input_value1[4];
float input_value2[4];
input_value1_operation_->read_sampled(input_value1, x, y, sampler);
input_value2_operation_->read_sampled(input_value2, x, y, sampler);
if (input_value2[0] == 0) {
output[0] = 0.0;
}
else {
output[0] = input_value1[0] - floorf(input_value1[0] / input_value2[0]) * input_value2[0];
}
clamp_if_needed(output);
}
void MathFlooredModuloOperation::update_memory_buffer_partial(BuffersIterator<float> &it)
{
for (; !it.is_end(); ++it) {
const float value2 = *it.in(1);
*it.out = (value2 == 0) ? 0 : *it.in(0) - floorf(*it.in(0) / value2) * value2;
clamp_when_enabled(it.out);
}
}
void MathAbsoluteOperation::execute_pixel_sampled(float output[4],
float x,
float y,

View File

@ -224,6 +224,14 @@ class MathModuloOperation : public MathBaseOperation {
void update_memory_buffer_partial(BuffersIterator<float> &it) override;
};
class MathFlooredModuloOperation : public MathBaseOperation {
public:
void execute_pixel_sampled(float output[4], float x, float y, PixelSampler sampler) override;
protected:
void update_memory_buffer_partial(BuffersIterator<float> &it) override;
};
class MathAbsoluteOperation : public MathBaseOperation {
public:
void execute_pixel_sampled(float output[4], float x, float y, PixelSampler sampler) override;

View File

@ -49,7 +49,6 @@ class World {
Instance &inst_;
DefaultWorldNodeTree default_tree;
bool has_volume_ = false;
/* Used to detect if world change. */
::World *prev_original_world = nullptr;

View File

@ -28,6 +28,7 @@
#include "BKE_context.h"
#include "BKE_fcurve.h"
#include "BKE_gpencil_legacy.h"
#include "BKE_grease_pencil.hh"
#include "BKE_main.h"
#include "BKE_node.h"
@ -291,9 +292,10 @@ void ANIM_sync_animchannels_to_data(const bContext *C)
animchan_sync_gplayer(ale);
break;
case ANIMTYPE_GREASE_PENCIL_LAYER:
using namespace blender::bke::greasepencil;
GreasePencil *grease_pencil = reinterpret_cast<GreasePencil *>(ale->id);
GreasePencilLayer *layer = static_cast<GreasePencilLayer *>(ale->data);
if (grease_pencil->active_layer == layer) {
Layer *layer = static_cast<Layer *>(ale->data);
if (grease_pencil->is_layer_active(layer)) {
layer->base.flag |= GP_LAYER_TREE_NODE_SELECT;
}
else {

View File

@ -1767,7 +1767,7 @@ static size_t animdata_filter_grease_pencil_layers_data(ListBase *anim_data,
}
/* Only if the layer is active. */
if ((filter_mode & ANIMFILTER_ACTIVE) && (grease_pencil->active_layer == layer)) {
if ((filter_mode & ANIMFILTER_ACTIVE) && grease_pencil->is_layer_active(layer)) {
continue;
}

View File

@ -0,0 +1,47 @@
/* SPDX-FileCopyrightText: 2023 Blender Foundation
*
* SPDX-License-Identifier: GPL-2.0-or-later */
#pragma once
#include "RE_pipeline.h"
#include "IMB_imbuf.h"
struct bContext;
struct bNodeTree;
struct ImBuf;
struct Render;
namespace blender::ed::space_node {
struct NestedTreePreviews {
Render *previews_render = nullptr;
/* Use this map to keep track of the latest ImBuf used (after freeing the renderresult). */
blender::Map<int32_t, ImBuf *> previews_map;
int preview_size;
bool rendering = false;
bool restart_needed = false;
uint32_t cached_previews_refresh_state = -1;
uint32_t rendering_previews_refresh_state = -1;
NestedTreePreviews(const int size) : preview_size(size) {}
~NestedTreePreviews()
{
if (this->previews_render) {
RE_FreeRender(this->previews_render);
}
for (ImBuf *ibuf : this->previews_map.values()) {
IMB_freeImBuf(ibuf);
}
}
};
void free_previews(wmWindowManager &wm, SpaceNode &snode);
ImBuf *node_preview_acquire_ibuf(bNodeTree &ntree,
NestedTreePreviews &tree_previews,
const bNode &node);
void node_release_preview_ibuf(NestedTreePreviews &tree_previews);
NestedTreePreviews *get_nested_previews(const bContext &C, SpaceNode &snode);
void stop_preview_job(wmWindowManager &wm);
} // namespace blender::ed::space_node

View File

@ -9,6 +9,7 @@
#pragma once
#include "DNA_ID_enums.h"
#include "DNA_material_types.h"
#include "DNA_vec_types.h"
struct DEGEditorUpdateContext;
@ -22,6 +23,8 @@ struct ScrArea;
struct bContext;
struct bScreen;
struct PreviewImage;
struct ViewLayer;
struct World;
struct wmWindow;
struct wmWindowManager;
@ -64,7 +67,10 @@ enum ePreviewRenderMethod {
PR_ICON_DEFERRED = 2,
};
void ED_preview_ensure_dbase();
bool ED_check_engine_supports_preview(const Scene *scene);
const char *ED_preview_collection_name(ePreviewType pr_type);
void ED_preview_ensure_dbase(bool with_gpencil);
void ED_preview_free_dbase();
/**
@ -72,6 +78,17 @@ void ED_preview_free_dbase();
*/
bool ED_preview_id_is_supported(const ID *id);
void ED_preview_set_visibility(Main *pr_main,
Scene *scene,
ViewLayer *view_layer,
ePreviewType pr_type,
ePreviewRenderMethod pr_method);
struct World *ED_preview_prepare_world(Main *pr_main,
const Scene *scene,
const World *world,
ID_Type id_type,
ePreviewRenderMethod pr_method);
void ED_preview_shader_job(const bContext *C,
void *owner,
ID *id,

View File

@ -157,7 +157,6 @@ struct IconPreview {
/** \name Preview for Buttons
* \{ */
static Main *G_pr_main = nullptr;
static Main *G_pr_main_grease_pencil = nullptr;
#ifndef WITH_HEADLESS
@ -180,21 +179,25 @@ static Main *load_main_from_memory(const void *blend, int blend_size)
}
#endif
void ED_preview_ensure_dbase()
void ED_preview_ensure_dbase(const bool with_gpencil)
{
#ifndef WITH_HEADLESS
static bool base_initialized = false;
static bool base_initialized_gpencil = false;
BLI_assert(BLI_thread_is_main());
if (!base_initialized) {
G_pr_main = load_main_from_memory(datatoc_preview_blend, datatoc_preview_blend_size);
G.pr_main = load_main_from_memory(datatoc_preview_blend, datatoc_preview_blend_size);
base_initialized = true;
}
if (!base_initialized_gpencil && with_gpencil) {
G_pr_main_grease_pencil = load_main_from_memory(datatoc_preview_grease_pencil_blend,
datatoc_preview_grease_pencil_blend_size);
base_initialized = true;
base_initialized_gpencil = true;
}
#endif
}
static bool check_engine_supports_preview(Scene *scene)
bool ED_check_engine_supports_preview(const Scene *scene)
{
RenderEngineType *type = RE_engines_find(scene->r.engine);
return (type->flag & RE_USE_PREVIEW) != 0;
@ -207,8 +210,8 @@ static bool preview_method_is_render(const ePreviewRenderMethod pr_method)
void ED_preview_free_dbase()
{
if (G_pr_main) {
BKE_main_free(G_pr_main);
if (G.pr_main) {
BKE_main_free(G.pr_main);
}
if (G_pr_main_grease_pencil) {
@ -225,7 +228,7 @@ static Scene *preview_get_scene(Main *pr_main)
return static_cast<Scene *>(pr_main->scenes.first);
}
static const char *preview_collection_name(const ePreviewType pr_type)
const char *ED_preview_collection_name(const ePreviewType pr_type)
{
switch (pr_type) {
case MA_FLAT:
@ -265,7 +268,7 @@ static void switch_preview_collection_visibility(ViewLayer *view_layer, const eP
{
/* Set appropriate layer as visible. */
LayerCollection *lc = static_cast<LayerCollection *>(view_layer->layer_collections.first);
const char *collection_name = preview_collection_name(pr_type);
const char *collection_name = ED_preview_collection_name(pr_type);
for (lc = static_cast<LayerCollection *>(lc->layer_collections.first); lc; lc = lc->next) {
if (STREQ(lc->collection->id.name + 2, collection_name)) {
@ -326,11 +329,11 @@ static void switch_preview_floor_visibility(Main *pr_main,
}
}
static void set_preview_visibility(Main *pr_main,
Scene *scene,
ViewLayer *view_layer,
const ePreviewType pr_type,
const ePreviewRenderMethod pr_method)
void ED_preview_set_visibility(Main *pr_main,
Scene *scene,
ViewLayer *view_layer,
const ePreviewType pr_type,
const ePreviewRenderMethod pr_method)
{
switch_preview_collection_visibility(view_layer, pr_type);
switch_preview_floor_visibility(pr_main, scene, view_layer, pr_method);
@ -443,13 +446,13 @@ static void preview_sync_exposure(World *dst, const World *src)
dst->range = src->range;
}
static World *preview_prepare_world(Main *pr_main,
const Scene *sce,
const World *world,
const ID_Type id_type,
const ePreviewRenderMethod pr_method)
World *ED_preview_prepare_world(Main *pr_main,
const Scene *scene,
const World *world,
const ID_Type id_type,
const ePreviewRenderMethod pr_method)
{
World *result = preview_get_world(pr_main, sce, id_type, pr_method);
World *result = preview_get_world(pr_main, scene, id_type, pr_method);
if (world) {
preview_sync_exposure(result, world);
}
@ -494,7 +497,7 @@ static Scene *preview_prepare_scene(
sce->r.cfra = scene->r.cfra;
/* Setup the world. */
sce->world = preview_prepare_world(
sce->world = ED_preview_prepare_world(
pr_main, sce, scene->world, static_cast<ID_Type>(id_type), sp->pr_method);
if (id_type == ID_TE) {
@ -531,7 +534,7 @@ static Scene *preview_prepare_scene(
(sp->pr_method == PR_ICON_RENDER && sp->pr_main == G_pr_main_grease_pencil) ?
MA_SPHERE_A :
(ePreviewType)mat->pr_type);
set_preview_visibility(pr_main, sce, view_layer, preview_type, sp->pr_method);
ED_preview_set_visibility(pr_main, sce, view_layer, preview_type, sp->pr_method);
}
else {
sce->display.render_aa = SCE_DISPLAY_AA_OFF;
@ -579,7 +582,7 @@ static Scene *preview_prepare_scene(
BLI_addtail(&pr_main->lights, la);
}
set_preview_visibility(pr_main, sce, view_layer, MA_LAMP, sp->pr_method);
ED_preview_set_visibility(pr_main, sce, view_layer, MA_LAMP, sp->pr_method);
if (sce->world) {
/* Only use lighting from the light. */
@ -608,7 +611,7 @@ static Scene *preview_prepare_scene(
BLI_addtail(&pr_main->worlds, wrld);
}
set_preview_visibility(pr_main, sce, view_layer, MA_SKY, sp->pr_method);
ED_preview_set_visibility(pr_main, sce, view_layer, MA_SKY, sp->pr_method);
sce->world = wrld;
}
@ -1522,7 +1525,7 @@ static void other_id_types_preview_render(IconPreview *ip,
}
if ((ma == nullptr) || (ma->gp_style == nullptr)) {
sp->pr_main = G_pr_main;
sp->pr_main = G.pr_main;
}
else {
sp->pr_main = G_pr_main_grease_pencil;
@ -1581,7 +1584,7 @@ static void icon_preview_startjob_all_sizes(void *customdata,
* necessary to know here what happens inside lower-level functions. */
const bool use_solid_render_mode = (ip->id != nullptr) && ELEM(GS(ip->id->name), ID_OB, ID_AC);
if (!use_solid_render_mode && preview_method_is_render(pr_method) &&
!check_engine_supports_preview(ip->scene))
!ED_check_engine_supports_preview(ip->scene))
{
continue;
}
@ -1929,7 +1932,7 @@ void ED_preview_icon_render(
bool stop = false, update = false;
float progress = 0.0f;
ED_preview_ensure_dbase();
ED_preview_ensure_dbase(true);
ip.bmain = CTX_data_main(C);
ip.scene = scene;
@ -1973,7 +1976,7 @@ void ED_preview_icon_job(
IconPreview *ip, *old_ip;
ED_preview_ensure_dbase();
ED_preview_ensure_dbase(true);
/* suspended start means it starts after 1 timer step, see WM_jobs_timer below */
wmJob *wm_job = WM_jobs_get(CTX_wm_manager(C),
@ -2038,11 +2041,11 @@ void ED_preview_shader_job(const bContext *C,
/* Use workspace render only for buttons Window,
* since the other previews are related to the datablock. */
if (preview_method_is_render(method) && !check_engine_supports_preview(scene)) {
if (preview_method_is_render(method) && !ED_check_engine_supports_preview(scene)) {
return;
}
ED_preview_ensure_dbase();
ED_preview_ensure_dbase(true);
wm_job = WM_jobs_get(CTX_wm_manager(C),
CTX_wm_window(C),
@ -2075,7 +2078,7 @@ void ED_preview_shader_job(const bContext *C,
}
if ((ma == nullptr) || (ma->gp_style == nullptr)) {
sp->pr_main = G_pr_main;
sp->pr_main = G.pr_main;
}
else {
sp->pr_main = G_pr_main_grease_pencil;

View File

@ -41,6 +41,7 @@
#include "RE_pipeline.h"
#include "ED_node.hh"
#include "ED_node_preview.hh"
#include "ED_paint.hh"
#include "ED_render.hh"
#include "ED_view3d.hh"
@ -177,6 +178,11 @@ void ED_render_engine_changed(Main *bmain, const bool update_scene_data)
ED_render_engine_area_exit(bmain, area);
}
}
/* Invalidate all shader previews. */
blender::ed::space_node::stop_preview_job(*static_cast<wmWindowManager *>(bmain->wm.first));
LISTBASE_FOREACH (Material *, ma, &bmain->materials) {
BKE_material_make_node_previews_dirty(ma);
}
RE_FreePersistentData(nullptr);
/* Inform all render engines and draw managers. */
DEGEditorUpdateContext update_ctx = {nullptr};

View File

@ -43,6 +43,7 @@ set(SRC
node_ops.cc
node_relationships.cc
node_select.cc
node_shader_preview.cc
node_templates.cc
node_view.cc
space_node.cc

View File

@ -45,6 +45,7 @@
#include "BKE_node_tree_update.h"
#include "BKE_node_tree_zones.hh"
#include "BKE_object.h"
#include "BKE_scene.h"
#include "BKE_type_conversions.hh"
#include "IMB_imbuf.h"
@ -68,6 +69,7 @@
#include "ED_gpencil_legacy.hh"
#include "ED_node.hh"
#include "ED_node_preview.hh"
#include "ED_screen.hh"
#include "ED_space_api.hh"
#include "ED_viewer_path.hh"
@ -98,6 +100,7 @@
namespace geo_log = blender::nodes::geo_eval_log;
using blender::bke::bNodeTreeZone;
using blender::bke::bNodeTreeZones;
using blender::ed::space_node::NestedTreePreviews;
/**
* This is passed to many functions which draw the node editor.
@ -116,6 +119,8 @@ struct TreeDrawContext {
* currently drawn node tree can be retrieved from the log below.
*/
blender::Map<const bNodeTreeZone *, geo_log::GeoTreeLog *> geo_log_by_zone;
NestedTreePreviews *nested_group_infos = nullptr;
/**
* True if there is an active realtime compositor using the node tree, false otherwise.
*/
@ -351,8 +356,7 @@ static void node_update_basis(const bContext &C,
RNA_pointer_create(&ntree.id, &RNA_Node, &node, &nodeptr);
const bool node_options = node.typeinfo->draw_buttons && (node.flag & NODE_OPTIONS);
const bool inputs_first = node.inputs.first &&
!(node.outputs.first || (node.flag & NODE_PREVIEW) || node_options);
const bool inputs_first = node.inputs.first && !(node.outputs.first || node_options);
/* Get "global" coordinates. */
float2 loc = node_to_view(node, float2(0));
@ -529,7 +533,7 @@ static void node_update_basis(const bContext &C,
}
/* Little bit of space in end. */
if (node.inputs.first || (node.flag & (NODE_OPTIONS | NODE_PREVIEW)) == 0) {
if (node.inputs.first || (node.flag & NODE_OPTIONS) == 0) {
dy -= NODE_DYS / 2;
}
@ -2107,6 +2111,10 @@ static void node_draw_extra_info_panel(const Scene *scene,
if (!(snode.overlay.flag & SN_OVERLAY_SHOW_OVERLAYS)) {
return;
}
if (preview && !(preview->x > 0 && preview->y > 0)) {
/* If the preview has an non-drawable size, just dont draw it. */
preview = nullptr;
}
Vector<NodeExtraInfoRow> extra_info_rows = node_get_extra_info(tree_draw_ctx, snode, node);
if (extra_info_rows.size() == 0 && !preview) {
return;
@ -2205,12 +2213,14 @@ static void node_draw_basis(const bContext &C,
bNodeInstanceKey key)
{
const float iconbutw = NODE_HEADER_ICON_SIZE;
bNodeInstanceHash *previews = static_cast<bNodeInstanceHash *>(
CTX_data_pointer_get(&C, "node_previews").data);
const bool show_preview = (snode.overlay.flag & SN_OVERLAY_SHOW_PREVIEWS) &&
(node.flag & NODE_PREVIEW) &&
(U.experimental.use_shader_node_previews ||
ntree.type != NTREE_SHADER);
/* Skip if out of view. */
rctf rect_with_preview = node.runtime->totr;
if (node.flag & NODE_PREVIEW && previews && snode.overlay.flag & SN_OVERLAY_SHOW_PREVIEWS) {
if (show_preview) {
rect_with_preview.ymax += NODE_WIDTH(node);
}
if (BLI_rctf_isect(&rect_with_preview, &v2d.cur, nullptr) == false) {
@ -2234,19 +2244,36 @@ static void node_draw_basis(const bContext &C,
GPU_line_width(1.0f);
ImBuf *preview = nullptr;
if (node.flag & NODE_PREVIEW && previews && snode.overlay.flag & SN_OVERLAY_SHOW_PREVIEWS) {
bNodePreview *preview_compositor = static_cast<bNodePreview *>(
BKE_node_instance_hash_lookup(previews, key));
if (preview_compositor) {
preview = preview_compositor->ibuf;
/* Overlay atop the node. */
{
bool drawn_with_previews = false;
if (show_preview) {
bNodeInstanceHash *previews_compo = static_cast<bNodeInstanceHash *>(
CTX_data_pointer_get(&C, "node_previews").data);
NestedTreePreviews *previews_shader = tree_draw_ctx.nested_group_infos;
if (previews_shader) {
ImBuf *preview = node_preview_acquire_ibuf(ntree, *previews_shader, node);
node_draw_extra_info_panel(CTX_data_scene(&C), tree_draw_ctx, snode, node, preview, block);
node_release_preview_ibuf(*previews_shader);
drawn_with_previews = true;
}
else if (previews_compo) {
bNodePreview *preview_compositor = static_cast<bNodePreview *>(
BKE_node_instance_hash_lookup(previews_compo, key));
if (preview_compositor) {
node_draw_extra_info_panel(
CTX_data_scene(&C), tree_draw_ctx, snode, node, preview_compositor->ibuf, block);
drawn_with_previews = true;
}
}
}
if (drawn_with_previews == false) {
node_draw_extra_info_panel(CTX_data_scene(&C), tree_draw_ctx, snode, node, nullptr, block);
}
}
if (!preview || !(preview->x && preview->y)) {
preview = nullptr;
}
node_draw_extra_info_panel(CTX_data_scene(&C), tree_draw_ctx, snode, node, preview, block);
/* Header. */
{
@ -2275,7 +2302,7 @@ static void node_draw_basis(const bContext &C,
float iconofs = rct.xmax - 0.35f * U.widget_unit;
/* Preview. */
if (node.typeinfo->flag & NODE_PREVIEW) {
if (node_is_previewable(ntree, node)) {
iconofs -= iconbutw;
UI_block_emboss_set(&block, UI_EMBOSS_NONE);
uiBut *but = uiDefIconBut(&block,
@ -3482,6 +3509,12 @@ static void draw_nodetree(const bContext &C,
else if (ntree.type == NTREE_COMPOSIT) {
tree_draw_ctx.used_by_realtime_compositor = realtime_compositor_is_in_use(C);
}
else if (ntree.type == NTREE_SHADER && U.experimental.use_shader_node_previews &&
BKE_scene_uses_shader_previews(CTX_data_scene(&C)) &&
U.experimental.use_shader_node_previews)
{
tree_draw_ctx.nested_group_infos = get_nested_previews(C, *snode);
}
node_update_nodetree(C, tree_draw_ctx, ntree, nodes, blocks);
node_draw_zones(tree_draw_ctx, region, *snode, ntree);

View File

@ -504,7 +504,8 @@ bool ED_node_is_geometry(SpaceNode *snode)
bool ED_node_supports_preview(SpaceNode *snode)
{
return ED_node_is_compositor(snode);
return ED_node_is_compositor(snode) ||
(U.experimental.use_shader_node_previews && ED_node_is_shader(snode));
}
void ED_node_shader_default(const bContext *C, ID *id)
@ -1123,6 +1124,16 @@ void node_set_hidden_sockets(bNode *node, int set)
}
}
bool node_is_previewable(const bNodeTree &ntree, const bNode &node)
{
if (ntree.type == NTREE_SHADER) {
return U.experimental.use_shader_node_previews &&
!(node.is_frame() || node.is_group_input() || node.is_group_output() ||
node.type == SH_NODE_OUTPUT_MATERIAL);
}
return node.typeinfo->flag & NODE_PREVIEW;
}
static bool cursor_isect_multi_input_socket(const float2 &cursor, const bNodeSocket &socket)
{
const float node_socket_height = node_socket_calculate_height(socket);
@ -1567,7 +1578,7 @@ static void node_flag_toggle_exec(SpaceNode *snode, int toggle_flag)
for (bNode *node : snode->edittree->all_nodes()) {
if (node->flag & SELECT) {
if (toggle_flag == NODE_PREVIEW && (node->typeinfo->flag & NODE_PREVIEW) == 0) {
if (toggle_flag == NODE_PREVIEW && !node_is_previewable(*snode->edittree, *node)) {
continue;
}
if (toggle_flag == NODE_OPTIONS &&
@ -1587,7 +1598,7 @@ static void node_flag_toggle_exec(SpaceNode *snode, int toggle_flag)
for (bNode *node : snode->edittree->all_nodes()) {
if (node->flag & SELECT) {
if (toggle_flag == NODE_PREVIEW && (node->typeinfo->flag & NODE_PREVIEW) == 0) {
if (toggle_flag == NODE_PREVIEW && !node_is_previewable(*snode->edittree, *node)) {
continue;
}
if (toggle_flag == NODE_OPTIONS &&
@ -1646,8 +1657,6 @@ static int node_preview_toggle_exec(bContext *C, wmOperator * /*op*/)
return OPERATOR_CANCELLED;
}
ED_preview_kill_jobs(CTX_wm_manager(C), CTX_data_main(C));
node_flag_toggle_exec(snode, NODE_PREVIEW);
ED_node_tree_propagate_change(C, CTX_data_main(C), snode->edittree);
@ -1655,6 +1664,17 @@ static int node_preview_toggle_exec(bContext *C, wmOperator * /*op*/)
return OPERATOR_FINISHED;
}
static bool node_previewable(bContext *C)
{
if (ED_operator_node_active(C)) {
SpaceNode *snode = CTX_wm_space_node(C);
if (ED_node_supports_preview(snode)) {
return true;
}
}
return false;
}
void NODE_OT_preview_toggle(wmOperatorType *ot)
{
/* identifiers */
@ -1664,7 +1684,7 @@ void NODE_OT_preview_toggle(wmOperatorType *ot)
/* callbacks */
ot->exec = node_preview_toggle_exec;
ot->poll = composite_node_active;
ot->poll = node_previewable;
/* flags */
ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;

View File

@ -8,6 +8,7 @@
#pragma once
#include "BLI_compute_context.hh"
#include "BLI_math_vector.h"
#include "BLI_math_vector.hh"
#include "BLI_vector.hh"
@ -39,6 +40,7 @@ struct AssetItemTree;
}
namespace blender::ed::space_node {
struct NestedTreePreviews;
/** Temporary data used in node link drag modal operator. */
struct bNodeLinkDrag {
@ -106,6 +108,13 @@ struct SpaceNode_Runtime {
/** Temporary data for node insert offset (in UI called Auto-offset). */
NodeInsertOfsData *iofsd;
/**
* Use this to store data for the displayed node tree. It has an entry for every distinct
* nested nodegroup.
*/
Map<ComputeContextHash, std::unique_ptr<space_node::NestedTreePreviews>>
tree_previews_per_context;
/**
* Temporary data for node add menu in order to provide longer-term storage for context pointers.
* Recreated every time the root menu is opened. In the future this will be replaced with an "all
@ -330,6 +339,7 @@ bool composite_node_editable(bContext *C);
bool node_has_hidden_sockets(bNode *node);
void node_set_hidden_sockets(bNode *node, int set);
bool node_is_previewable(const bNodeTree &ntree, const bNode &node);
int node_render_changed_exec(bContext *, wmOperator *);
bNodeSocket *node_find_indicated_socket(SpaceNode &snode,
const float2 &cursor,

View File

@ -0,0 +1,769 @@
/* SPDX-FileCopyrightText: 2023 Blender Foundation
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup edrend
*
* This file implements shader node previews which rely on a structure owned by each SpaceNode.
* We take advantage of the RenderResult available as ImBuf images to store a Render for every
* viewed nested node tree present in a SpaceNode. The computation is initiated at the moment of
* drawing nodes overlays. One render is started for the current nodetree, having a ViewLayer
* associated with each previewed node.
*
* We separate the previewed nodes in two categories: the shader ones and the non-shader ones.
* - for non-shader nodes, we use AOVs(Arbitrary Output Variable) which highly speed up the
* rendering process by rendering every non-shader node at the same time. They are rendered in the
* first ViewLayer.
* - for shader nodes, we render them each in a different ViewLayer, by routing the node to the
* output of the material in the preview scene.
*
* At the moment of drawing, we take the Render of the viewed node tree and extract the ImBuf of
* the wanted viewlayer/pass for each previewed node.
*/
#include "DNA_camera_types.h"
#include "DNA_material_types.h"
#include "DNA_world_types.h"
#include "BKE_colortools.h"
#include "BKE_compute_contexts.hh"
#include "BKE_context.h"
#include "BKE_global.h"
#include "BKE_layer.h"
#include "BKE_lib_id.h"
#include "BKE_main.h"
#include "BKE_material.h"
#include "BKE_node.hh"
#include "BKE_node_runtime.hh"
#include "BKE_node_tree_update.h"
#include "BKE_scene.h"
#include "DEG_depsgraph.h"
#include "IMB_imbuf.h"
#include "WM_api.hh"
#include "ED_datafiles.h"
#include "ED_node_preview.hh"
#include "ED_render.hh"
#include "ED_screen.hh"
#include "node_intern.hh"
namespace blender::ed::space_node {
/* -------------------------------------------------------------------- */
/** \name Local Structs
* \{ */
using NodeSocketPair = std::pair<bNode *, bNodeSocket *>;
struct ShaderNodesPreviewJob {
NestedTreePreviews *tree_previews;
Scene *scene;
/* Pointer to the job's stop variable which is used to know when the job is asked for finishing.
* The idea is that the renderer will read this value frequently and abort the render if it is
* true. */
bool *stop;
/* Pointer to the job's update variable which is set to true to refresh the UI when the renderer
* is delivering a fresh result. It allows the job to give some UI refresh tags to the WM. */
bool *do_update;
Material *mat_copy;
bNode *mat_output_copy;
NodeSocketPair mat_displacement_copy;
/* TreePath used to locate the nodetree.
* bNodeTreePath elements have some listbase pointers which should not be used. */
Vector<bNodeTreePath *> treepath_copy;
Vector<bNode *> AOV_nodes;
Vector<bNode *> shader_nodes;
bNode *rendering_node;
bool rendering_AOVs;
Main *bmain;
};
/** \} */
/* -------------------------------------------------------------------- */
/** \name Compute Context functions
* \{ */
static void ensure_nodetree_previews(const bContext &C,
NestedTreePreviews &tree_previews,
Material &material,
ListBase &treepath);
static std::optional<ComputeContextHash> get_compute_context_hash_for_node_editor(
const SpaceNode &snode)
{
Vector<const bNodeTreePath *> treepath = snode.treepath;
if (treepath.is_empty()) {
return std::nullopt;
}
if (treepath.size() == 1) {
/* Top group. */
ComputeContextHash hash;
hash.v1 = hash.v2 = 0;
return hash;
}
ComputeContextBuilder compute_context_builder;
for (const int i : treepath.index_range().drop_back(1)) {
/* The tree path contains the name of the node but not its ID. */
const bNode *node = nodeFindNodebyName(treepath[i]->nodetree, treepath[i + 1]->node_name);
if (node == nullptr) {
/* The current tree path is invalid, probably because some parent group node has been
* deleted. */
return std::nullopt;
}
compute_context_builder.push<bke::NodeGroupComputeContext>(*node);
}
return compute_context_builder.hash();
}
/*
* This function returns the `NestedTreePreviews *` for the nodetree shown in the SpaceNode.
* This is the first function in charge of the previews by calling `ensure_nodetree_previews`.
*/
NestedTreePreviews *get_nested_previews(const bContext &C, SpaceNode &snode)
{
if (snode.id == nullptr || GS(snode.id->name) != ID_MA) {
return nullptr;
}
NestedTreePreviews *tree_previews = nullptr;
if (auto hash = get_compute_context_hash_for_node_editor(snode)) {
tree_previews = snode.runtime->tree_previews_per_context
.lookup_or_add_cb(*hash,
[&]() {
return std::make_unique<NestedTreePreviews>(
U.node_preview_res);
})
.get();
Material *ma = reinterpret_cast<Material *>(snode.id);
ensure_nodetree_previews(C, *tree_previews, *ma, snode.treepath);
}
return tree_previews;
}
/** \} */
/* -------------------------------------------------------------------- */
/** \name Preview scene
* \{ */
static Material *duplicate_material(const Material &mat)
{
Material *ma_copy = reinterpret_cast<Material *>(
BKE_id_copy_ex(nullptr,
&mat.id,
nullptr,
LIB_ID_CREATE_LOCAL | LIB_ID_COPY_LOCALIZE | LIB_ID_COPY_NO_ANIMDATA));
return ma_copy;
}
static Scene *preview_prepare_scene(const Main *bmain,
const Scene *scene_orig,
Main *pr_main,
Material *mat_copy)
{
Scene *scene_preview;
memcpy(pr_main->filepath, BKE_main_blendfile_path(bmain), sizeof(pr_main->filepath));
if (pr_main == nullptr) {
return nullptr;
}
scene_preview = static_cast<Scene *>(pr_main->scenes.first);
if (scene_preview == nullptr) {
return nullptr;
}
ViewLayer *view_layer = static_cast<ViewLayer *>(scene_preview->view_layers.first);
/* Only enable the combined render-pass. */
view_layer->passflag = SCE_PASS_COMBINED;
view_layer->eevee.render_passes = 0;
/* This flag tells render to not execute depsgraph or F-Curves etc. */
scene_preview->r.scemode |= R_BUTS_PREVIEW;
scene_preview->r.mode |= R_PERSISTENT_DATA;
STRNCPY(scene_preview->r.engine, scene_orig->r.engine);
scene_preview->r.color_mgt_flag = scene_orig->r.color_mgt_flag;
BKE_color_managed_display_settings_copy(&scene_preview->display_settings,
&scene_orig->display_settings);
BKE_color_managed_view_settings_free(&scene_preview->view_settings);
BKE_color_managed_view_settings_copy(&scene_preview->view_settings, &scene_orig->view_settings);
scene_preview->r.alphamode = R_ADDSKY;
scene_preview->r.cfra = scene_orig->r.cfra;
/* Setup the world. */
scene_preview->world = ED_preview_prepare_world(
pr_main, scene_preview, scene_orig->world, ID_MA, PR_BUTS_RENDER);
BLI_addtail(&pr_main->materials, mat_copy);
scene_preview->world->use_nodes = false;
scene_preview->world->horr = 0.05f;
scene_preview->world->horg = 0.05f;
scene_preview->world->horb = 0.05f;
ED_preview_set_visibility(
pr_main, scene_preview, view_layer, ePreviewType(mat_copy->pr_type), PR_BUTS_RENDER);
BKE_view_layer_synced_ensure(scene_preview, view_layer);
LISTBASE_FOREACH (Base *, base, BKE_view_layer_object_bases_get(view_layer)) {
if (base->object->id.name[2] == 'p') {
if (OB_TYPE_SUPPORT_MATERIAL(base->object->type)) {
/* Don't use BKE_object_material_assign, it changed mat->id.us, which shows in the UI. */
Material ***matar = BKE_object_material_array_p(base->object);
int actcol = max_ii(base->object->actcol - 1, 0);
if (matar && actcol < base->object->totcol) {
(*matar)[actcol] = mat_copy;
}
}
else if (base->object->type == OB_LAMP) {
base->flag |= BASE_ENABLED_AND_MAYBE_VISIBLE_IN_VIEWPORT;
}
}
}
return scene_preview;
}
/** \} */
/* -------------------------------------------------------------------- */
/** \name Preview rendering
* \{ */
/* Return the socket used for previewing the node (should probably follow more precise rules). */
static const bNodeSocket *node_find_preview_socket(const bNode &node)
{
LISTBASE_FOREACH (bNodeSocket *, socket, &node.outputs) {
if (socket->is_visible()) {
return socket;
}
}
return nullptr;
}
static bool node_use_aov(const bNode &node)
{
const bNodeSocket *socket = node_find_preview_socket(node);
return socket != nullptr && socket->type != SOCK_SHADER;
}
static ImBuf *get_image_from_viewlayer_and_pass(RenderResult &rr,
const char *layer_name,
const char *pass_name)
{
RenderLayer *rl;
if (layer_name) {
rl = RE_GetRenderLayer(&rr, layer_name);
}
else {
rl = static_cast<RenderLayer *>(rr.layers.first);
}
if (rl == nullptr) {
return nullptr;
}
RenderPass *rp;
if (pass_name) {
rp = RE_pass_find_by_name(rl, pass_name, nullptr);
}
else {
rp = static_cast<RenderPass *>(rl->passes.first);
}
ImBuf *ibuf = rp ? rp->ibuf : nullptr;
return ibuf;
}
/* `node_release_preview_ibuf` should be called after this. */
ImBuf *node_preview_acquire_ibuf(bNodeTree &ntree,
NestedTreePreviews &tree_previews,
const bNode &node)
{
if (tree_previews.previews_render == nullptr) {
return nullptr;
}
RenderResult *rr = RE_AcquireResultRead(tree_previews.previews_render);
ImBuf *&image_cached = tree_previews.previews_map.lookup_or_add(node.identifier, nullptr);
if (rr == nullptr) {
return image_cached;
}
if (image_cached == nullptr) {
if (tree_previews.rendering == false) {
ntree.runtime->previews_refresh_state++;
}
else {
/* When the render process is started, the user must see that the preview area is open. */
ImBuf *image_latest = nullptr;
if (node_use_aov(node)) {
image_latest = get_image_from_viewlayer_and_pass(*rr, nullptr, node.name);
}
else {
image_latest = get_image_from_viewlayer_and_pass(*rr, node.name, nullptr);
}
if (image_latest) {
IMB_refImBuf(image_latest);
image_cached = image_latest;
}
}
}
return image_cached;
}
void node_release_preview_ibuf(NestedTreePreviews &tree_previews)
{
if (tree_previews.previews_render == nullptr) {
return;
}
RE_ReleaseResult(tree_previews.previews_render);
}
/* Get a link to the node outside the nested nodegroups by creating a new output socket for each
* nested nodegroup. To do so we cover all nested nodetrees starting from the farthest, and
* update the `nested_node_iter` pointer to the current nodegroup instance used for linking. We
* stop before getting to the main nodetree because the output type is different. */
static void connect_nested_node_to_node(const Span<bNodeTreePath *> treepath,
bNode &nested_node,
bNodeSocket &nested_socket,
bNode &final_node,
bNodeSocket &final_socket)
{
bNode *nested_node_iter = &nested_node;
bNodeSocket *nested_socket_iter = &nested_socket;
for (int i = treepath.size() - 1; i > 0; --i) {
bNodeTreePath *path = treepath[i];
bNodeTreePath *path_prev = treepath[i - 1];
bNodeTree *nested_nt = path->nodetree;
bNode *output_node = nullptr;
for (bNode *iter_node : nested_nt->all_nodes()) {
if (iter_node->is_group_output() && iter_node->flag & NODE_DO_OUTPUT) {
output_node = iter_node;
break;
}
}
if (output_node == nullptr) {
output_node = nodeAddStaticNode(nullptr, nested_nt, NODE_GROUP_OUTPUT);
output_node->flag |= NODE_DO_OUTPUT;
}
ntreeAddSocketInterface(nested_nt, SOCK_OUT, nested_socket_iter->idname, nested_node.name);
BKE_ntree_update_main_tree(G.pr_main, nested_nt, nullptr);
bNodeSocket *out_socket = bke::node_find_enabled_input_socket(*output_node, nested_node.name);
nodeAddLink(nested_nt, nested_node_iter, nested_socket_iter, output_node, out_socket);
BKE_ntree_update_main_tree(G.pr_main, nested_nt, nullptr);
/* Change the `nested_node` pointer to the nested nodegroup instance node. The tree path
* contains the name of the instance node but not its ID. */
nested_node_iter = nodeFindNodebyName(path_prev->nodetree, path->node_name);
/* Update the sockets of the node because we added a new interface. */
BKE_ntree_update_tag_node_property(path_prev->nodetree, nested_node_iter);
BKE_ntree_update_main_tree(G.pr_main, path_prev->nodetree, nullptr);
/* Now use the newly created socket of the nodegroup as previewing socket of the nodegroup
* instance node. */
nested_socket_iter = bke::node_find_enabled_output_socket(*nested_node_iter, nested_node.name);
}
nodeAddLink(treepath.first()->nodetree,
nested_node_iter,
nested_socket_iter,
&final_node,
&final_socket);
}
/* Connect the node to the output of the first nodetree from `treepath`. Last element of `treepath`
* should be the path to the node's nodetree */
static void connect_node_to_surface_output(const Span<bNodeTreePath *> treepath,
bNode &node,
bNode &output_node)
{
bNodeSocket *out_surface_socket = nullptr;
bNodeTree *main_nt = treepath.first()->nodetree;
bNodeSocket *node_preview_socket = const_cast<bNodeSocket *>(node_find_preview_socket(node));
if (node_preview_socket == nullptr) {
return;
}
/* Ensure output is usable. */
out_surface_socket = nodeFindSocket(&output_node, SOCK_IN, "Surface");
if (out_surface_socket->link) {
/* Make sure no node is already wired to the output before wiring. */
nodeRemLink(main_nt, out_surface_socket->link);
}
connect_nested_node_to_node(
treepath, node, *node_preview_socket, output_node, *out_surface_socket);
BKE_ntree_update_main_tree(G.pr_main, main_nt, nullptr);
}
/* Connect the nodes to some aov nodes located in the first nodetree from `treepath`. Last element
* of `treepath` should be the path to the nodes nodetree. */
static void connect_nodes_to_aovs(const Span<bNodeTreePath *> treepath, const Span<bNode *> &nodes)
{
if (nodes.size() == 0) {
return;
}
bNodeTree *main_nt = treepath.first()->nodetree;
for (bNode *node : nodes) {
bNodeSocket *node_preview_socket = const_cast<bNodeSocket *>(node_find_preview_socket(*node));
bNode *aov_node = nodeAddStaticNode(nullptr, main_nt, SH_NODE_OUTPUT_AOV);
strcpy(reinterpret_cast<NodeShaderOutputAOV *>(aov_node->storage)->name, node->name);
if (node_preview_socket == nullptr) {
continue;
}
bNodeSocket *aov_socket = nodeFindSocket(aov_node, SOCK_IN, "Color");
connect_nested_node_to_node(treepath, *node, *node_preview_socket, *aov_node, *aov_socket);
}
BKE_ntree_update_main_tree(G.pr_main, main_nt, nullptr);
}
/* Called by renderer, checks job stops. */
static bool nodetree_previews_break(void *spv)
{
ShaderNodesPreviewJob *job_data = static_cast<ShaderNodesPreviewJob *>(spv);
return *(job_data->stop);
}
static bool prepare_viewlayer_update(void *pvl_data, ViewLayer *vl, Depsgraph *depsgraph)
{
bNode *node = nullptr;
ShaderNodesPreviewJob *job_data = static_cast<ShaderNodesPreviewJob *>(pvl_data);
for (bNode *node_iter : job_data->shader_nodes) {
if (STREQ(vl->name, node_iter->name)) {
node = node_iter;
job_data->rendering_node = node_iter;
job_data->rendering_AOVs = false;
break;
}
}
if (node == nullptr) {
job_data->rendering_node = nullptr;
job_data->rendering_AOVs = true;
/* The AOV layer is the default `ViewLayer` of the scene(which should be the first one). */
return job_data->AOV_nodes.size() > 0 && !vl->prev;
}
bNodeSocket *displacement_socket = nodeFindSocket(
job_data->mat_output_copy, SOCK_IN, "Displacement");
if (job_data->mat_displacement_copy.first != nullptr && displacement_socket->link == nullptr) {
nodeAddLink(job_data->treepath_copy.first()->nodetree,
job_data->mat_displacement_copy.first,
job_data->mat_displacement_copy.second,
job_data->mat_output_copy,
displacement_socket);
}
connect_node_to_surface_output(job_data->treepath_copy, *node, *job_data->mat_output_copy);
if (depsgraph != nullptr) {
/* Used to refresh the dependency graph so that the material can be updated. */
for (bNodeTreePath *path_iter : job_data->treepath_copy) {
DEG_graph_id_tag_update(
G.pr_main, depsgraph, &path_iter->nodetree->id, ID_RECALC_NTREE_OUTPUT);
}
}
return true;
}
/* Called by renderer, refresh the UI. */
static void all_nodes_preview_update(void *npv, RenderResult *rr, struct rcti * /*rect*/)
{
ShaderNodesPreviewJob *job_data = static_cast<ShaderNodesPreviewJob *>(npv);
*job_data->do_update = true;
if (bNode *node = job_data->rendering_node) {
ImBuf *&image_cached = job_data->tree_previews->previews_map.lookup_or_add(node->identifier,
nullptr);
ImBuf *image_latest = get_image_from_viewlayer_and_pass(*rr, node->name, nullptr);
if (image_latest == nullptr) {
return;
}
if (image_cached != image_latest) {
if (image_cached != nullptr) {
IMB_freeImBuf(image_cached);
}
IMB_refImBuf(image_latest);
image_cached = image_latest;
}
}
if (job_data->rendering_AOVs) {
for (bNode *node : job_data->AOV_nodes) {
ImBuf *&image_cached = job_data->tree_previews->previews_map.lookup_or_add(node->identifier,
nullptr);
ImBuf *image_latest = get_image_from_viewlayer_and_pass(*rr, nullptr, node->name);
if (image_latest == nullptr) {
continue;
}
if (image_cached != image_latest) {
if (image_cached != nullptr) {
IMB_freeImBuf(image_cached);
}
IMB_refImBuf(image_latest);
image_cached = image_latest;
}
}
}
}
static void preview_render(ShaderNodesPreviewJob &job_data)
{
/* Get the stuff from the builtin preview dbase. */
Scene *scene = preview_prepare_scene(
job_data.bmain, job_data.scene, G.pr_main, job_data.mat_copy);
if (scene == nullptr) {
return;
}
Span<bNodeTreePath *> treepath = job_data.treepath_copy;
/* Disconnect all input sockets of the material output node, but keep track of the displacment
* node. */
bNodeSocket *disp_socket = nodeFindSocket(job_data.mat_output_copy, SOCK_IN, "Displacement");
if (disp_socket->link != nullptr) {
job_data.mat_displacement_copy = std::make_pair(disp_socket->link->fromnode,
disp_socket->link->fromsock);
}
LISTBASE_FOREACH (bNodeSocket *, socket_iter, &job_data.mat_output_copy->inputs) {
if (socket_iter->link != nullptr) {
nodeRemLink(treepath.first()->nodetree, socket_iter->link);
}
}
/* AOV nodes are rendered in the first RenderLayer so we route them now. */
connect_nodes_to_aovs(treepath, job_data.AOV_nodes);
/* Create the AOV passes for the viewlayer. */
ViewLayer *AOV_layer = static_cast<ViewLayer *>(scene->view_layers.first);
for (bNode *node : job_data.shader_nodes) {
ViewLayer *vl = BKE_view_layer_add(scene, node->name, AOV_layer, VIEWLAYER_ADD_COPY);
strcpy(vl->name, node->name);
}
for (bNode *node : job_data.AOV_nodes) {
ViewLayerAOV *aov = BKE_view_layer_add_aov(AOV_layer);
strcpy(aov->name, node->name);
}
scene->r.xsch = job_data.tree_previews->preview_size;
scene->r.ysch = job_data.tree_previews->preview_size;
scene->r.size = 100;
if (job_data.tree_previews->previews_render == nullptr) {
char name[32];
SNPRINTF(name, "Preview %p", &job_data.tree_previews);
job_data.tree_previews->previews_render = RE_NewRender(name);
}
Render *re = job_data.tree_previews->previews_render;
/* `sce->r` gets copied in RE_InitState. */
scene->r.scemode &= ~(R_MATNODE_PREVIEW | R_TEXNODE_PREVIEW);
scene->r.scemode &= ~R_NO_IMAGE_LOAD;
scene->display.render_aa = SCE_DISPLAY_AA_SAMPLES_8;
RE_display_update_cb(re, &job_data, all_nodes_preview_update);
RE_test_break_cb(re, &job_data, nodetree_previews_break);
RE_prepare_viewlayer_cb(re, &job_data, prepare_viewlayer_update);
/* Lens adjust. */
float oldlens = reinterpret_cast<Camera *>(scene->camera->data)->lens;
RE_ClearResult(re);
RE_PreviewRender(re, G.pr_main, scene);
reinterpret_cast<Camera *>(scene->camera->data)->lens = oldlens;
/* Free the aov layers and the layers generated for each node. */
BLI_freelistN(&AOV_layer->aovs);
ViewLayer *vl = AOV_layer->next;
while (vl) {
ViewLayer *vl_rem = vl;
vl = vl->next;
BLI_remlink(&scene->view_layers, vl_rem);
BKE_view_layer_free(vl_rem);
}
}
/** \} */
/* -------------------------------------------------------------------- */
/** \name Preview job management
* \{ */
static void update_needed_flag(const bNodeTree &nt, NestedTreePreviews &tree_previews)
{
if (tree_previews.rendering) {
if (nt.runtime->previews_refresh_state != tree_previews.rendering_previews_refresh_state) {
tree_previews.restart_needed = true;
return;
}
}
else {
if (nt.runtime->previews_refresh_state != tree_previews.cached_previews_refresh_state) {
tree_previews.restart_needed = true;
return;
}
}
if (tree_previews.preview_size != U.node_preview_res) {
tree_previews.restart_needed = true;
return;
}
}
static void shader_preview_startjob(void *customdata,
bool *stop,
bool *do_update,
float * /*progress*/)
{
ShaderNodesPreviewJob *job_data = static_cast<ShaderNodesPreviewJob *>(customdata);
job_data->stop = stop;
job_data->do_update = do_update;
*do_update = true;
bool size_changed = job_data->tree_previews->preview_size != U.node_preview_res;
if (size_changed) {
job_data->tree_previews->preview_size = U.node_preview_res;
}
/* Find the shader output node. */
for (bNode *node_iter : job_data->mat_copy->nodetree->all_nodes()) {
if (node_iter->type == SH_NODE_OUTPUT_MATERIAL && node_iter->flag & NODE_DO_OUTPUT) {
job_data->mat_output_copy = node_iter;
break;
}
}
if (job_data->mat_output_copy == nullptr) {
job_data->mat_output_copy = nodeAddStaticNode(
nullptr, job_data->mat_copy->nodetree, SH_NODE_OUTPUT_MATERIAL);
}
bNodeTree *active_nodetree = job_data->treepath_copy.last()->nodetree;
for (bNode *node : active_nodetree->all_nodes()) {
if (!(node->flag & NODE_PREVIEW)) {
/* Clear the cached preview for this node to be sure that the preview is rerendered if
* needed. */
if (ImBuf **ibuf = job_data->tree_previews->previews_map.lookup_ptr(node->identifier)) {
IMB_freeImBuf(*ibuf);
*ibuf = nullptr;
}
continue;
}
if (node_use_aov(*node)) {
job_data->AOV_nodes.append(node);
}
else {
job_data->shader_nodes.append(node);
}
}
if (job_data->tree_previews->preview_size > 0) {
preview_render(*job_data);
}
}
static void shader_preview_free(void *customdata)
{
ShaderNodesPreviewJob *job_data = static_cast<ShaderNodesPreviewJob *>(customdata);
for (bNodeTreePath *path : job_data->treepath_copy) {
MEM_freeN(path);
}
job_data->treepath_copy.clear();
job_data->tree_previews->rendering = false;
job_data->tree_previews->cached_previews_refresh_state =
job_data->tree_previews->rendering_previews_refresh_state;
if (job_data->mat_copy != nullptr) {
BLI_remlink(&G.pr_main->materials, job_data->mat_copy);
BKE_id_free(G.pr_main, &job_data->mat_copy->id);
job_data->mat_copy = nullptr;
}
MEM_delete(job_data);
}
static void ensure_nodetree_previews(const bContext &C,
NestedTreePreviews &tree_previews,
Material &material,
ListBase &treepath)
{
Scene *scene = CTX_data_scene(&C);
if (!ED_check_engine_supports_preview(scene)) {
return;
}
bNodeTree *displayed_nodetree = static_cast<bNodeTreePath *>(treepath.last)->nodetree;
update_needed_flag(*displayed_nodetree, tree_previews);
if (!(tree_previews.restart_needed)) {
return;
}
if (tree_previews.rendering) {
WM_jobs_stop(CTX_wm_manager(&C),
CTX_wm_space_node(&C),
reinterpret_cast<void *>(shader_preview_startjob));
return;
}
tree_previews.rendering = true;
tree_previews.restart_needed = false;
tree_previews.rendering_previews_refresh_state =
displayed_nodetree->runtime->previews_refresh_state;
ED_preview_ensure_dbase(false);
wmJob *wm_job = WM_jobs_get(CTX_wm_manager(&C),
CTX_wm_window(&C),
CTX_wm_space_node(&C),
"Shader Previews",
WM_JOB_EXCL_RENDER,
WM_JOB_TYPE_RENDER_PREVIEW);
ShaderNodesPreviewJob *job_data = MEM_new<ShaderNodesPreviewJob>(__func__);
job_data->scene = scene;
job_data->tree_previews = &tree_previews;
job_data->bmain = CTX_data_main(&C);
job_data->mat_copy = duplicate_material(material);
job_data->rendering_node = nullptr;
job_data->rendering_AOVs = false;
/* Update the treepath copied to fit the structure of the nodetree copied. */
bNodeTreePath *root_path = MEM_cnew<bNodeTreePath>(__func__);
root_path->nodetree = job_data->mat_copy->nodetree;
job_data->treepath_copy.append(root_path);
for (bNodeTreePath *original_path = static_cast<bNodeTreePath *>(treepath.first)->next;
original_path;
original_path = original_path->next)
{
bNodeTreePath *new_path = MEM_cnew<bNodeTreePath>(__func__);
memcpy(new_path, original_path, sizeof(bNodeTreePath));
bNode *parent = nodeFindNodebyName(job_data->treepath_copy.last()->nodetree,
original_path->node_name);
new_path->nodetree = reinterpret_cast<bNodeTree *>(parent->id);
job_data->treepath_copy.append(new_path);
}
WM_jobs_customdata_set(wm_job, job_data, shader_preview_free);
WM_jobs_timer(wm_job, 0.2, NC_NODE, NC_NODE);
WM_jobs_callbacks(wm_job, shader_preview_startjob, nullptr, nullptr, nullptr);
WM_jobs_start(CTX_wm_manager(&C), wm_job);
}
void stop_preview_job(wmWindowManager &wm)
{
WM_jobs_stop(&wm, nullptr, reinterpret_cast<void *>(shader_preview_startjob));
}
void free_previews(wmWindowManager &wm, SpaceNode &snode)
{
/* This should not be called from the drawing pass, because it will result in a deadlock. */
WM_jobs_kill(&wm, &snode, shader_preview_startjob);
snode.runtime->tree_previews_per_context.clear_and_shrink();
}
/** \} */
} // namespace blender::ed::space_node

View File

@ -24,6 +24,7 @@
#include "BKE_screen.h"
#include "ED_node.hh"
#include "ED_node_preview.hh"
#include "ED_render.hh"
#include "ED_screen.hh"
#include "ED_space_api.hh"
@ -330,6 +331,15 @@ static void node_init(wmWindowManager * /*wm*/, ScrArea *area)
}
}
static void node_exit(wmWindowManager *wm, ScrArea *area)
{
SpaceNode *snode = static_cast<SpaceNode *>(area->spacedata.first);
if (snode->runtime) {
free_previews(*wm, *snode);
}
}
static bool any_node_uses_id(const bNodeTree *ntree, const ID *id)
{
if (ELEM(nullptr, ntree, id)) {
@ -1131,6 +1141,7 @@ void ED_spacetype_node()
st->create = node_create;
st->free = node_free;
st->init = node_init;
st->exit = node_exit;
st->duplicate = node_duplicate;
st->operatortypes = node_operatortypes;
st->keymap = node_keymap;

View File

@ -66,6 +66,7 @@ set(SRC
../include/ED_mesh.hh
../include/ED_node_c.hh
../include/ED_node.hh
../include/ED_node_preview.hh
../include/ED_numinput.hh
../include/ED_object.hh
../include/ED_outliner.hh

View File

@ -571,6 +571,7 @@ set(GLSL_SRC_TEST
tests/shaders/gpu_compute_ssbo_test.glsl
tests/shaders/gpu_compute_vbo_test.glsl
tests/shaders/gpu_compute_dummy_test.glsl
tests/shaders/gpu_framebuffer_layer_viewport_test.glsl
tests/shaders/gpu_push_constants_test.glsl
)

View File

@ -35,6 +35,9 @@ typedef enum eGPUFrameBufferBits {
ENUM_OPERATORS(eGPUFrameBufferBits, GPU_STENCIL_BIT)
/* Guaranteed by the spec and is never greater than 16 on any hardware or implementation. */
#define GPU_MAX_VIEWPORTS 16
#ifdef __cplusplus
extern "C" {
#endif
@ -340,10 +343,23 @@ void GPU_framebuffer_default_size(GPUFrameBuffer *framebuffer, int width, int he
* or when binding the frame-buffer after modifying its attachments.
*
* \note Viewport and scissor size is stored per frame-buffer.
* \note Setting a singular viewport will only change the state of the first viewport.
* \note Must be called after first bind.
*/
void GPU_framebuffer_viewport_set(
GPUFrameBuffer *framebuffer, int x, int y, int width, int height);
/**
* Similar to `GPU_framebuffer_viewport_set()` but specify the bounds of all 16 viewports.
* By default geometry renders only to the first viewport. That can be changed by setting
* `gpu_ViewportIndex` in the vertex.
*
* \note Viewport and scissor size is stored per frame-buffer.
* \note Must be called after first bind.
*/
void GPU_framebuffer_multi_viewports_set(GPUFrameBuffer *gpu_fb,
const int viewport_rects[GPU_MAX_VIEWPORTS][4]);
/**
* Return the viewport offset and size in a int quadruple: (x, y, width, height).
* \note Viewport and scissor size is stored per frame-buffer.

View File

@ -391,6 +391,12 @@ void GPU_framebuffer_viewport_set(GPUFrameBuffer *gpu_fb, int x, int y, int widt
unwrap(gpu_fb)->viewport_set(viewport_rect);
}
void GPU_framebuffer_multi_viewports_set(GPUFrameBuffer *gpu_fb,
const int viewport_rects[GPU_MAX_VIEWPORTS][4])
{
unwrap(gpu_fb)->viewport_multi_set(viewport_rects);
}
void GPU_framebuffer_viewport_get(GPUFrameBuffer *gpu_fb, int r_viewport[4])
{
unwrap(gpu_fb)->viewport_get(r_viewport);

View File

@ -81,8 +81,9 @@ class FrameBuffer {
/** Debug name. */
char name_[DEBUG_NAME_LEN];
/** Frame-buffer state. */
int viewport_[4] = {0};
int viewport_[GPU_MAX_VIEWPORTS][4] = {{0}};
int scissor_[4] = {0};
bool multi_viewport_ = false;
bool scissor_test_ = false;
bool dirty_state_ = true;
@ -157,10 +158,22 @@ class FrameBuffer {
inline void viewport_set(const int viewport[4])
{
if (!equals_v4v4_int(viewport_, viewport)) {
copy_v4_v4_int(viewport_, viewport);
if (!equals_v4v4_int(viewport_[0], viewport)) {
copy_v4_v4_int(viewport_[0], viewport);
dirty_state_ = true;
}
multi_viewport_ = false;
}
inline void viewport_multi_set(const int viewports[GPU_MAX_VIEWPORTS][4])
{
for (size_t i = 0; i < GPU_MAX_VIEWPORTS; i++) {
if (!equals_v4v4_int(viewport_[i], viewports[i])) {
copy_v4_v4_int(viewport_[i], viewports[i]);
dirty_state_ = true;
}
}
multi_viewport_ = true;
}
inline void scissor_set(const int scissor[4])
@ -178,7 +191,7 @@ class FrameBuffer {
inline void viewport_get(int r_viewport[4]) const
{
copy_v4_v4_int(r_viewport, viewport_);
copy_v4_v4_int(r_viewport, viewport_[0]);
}
inline void scissor_get(int r_scissor[4]) const

View File

@ -178,6 +178,11 @@ enum class BuiltinBits {
VERTEX_ID = (1 << 14),
WORK_GROUP_ID = (1 << 15),
WORK_GROUP_SIZE = (1 << 16),
/**
* Allow setting the target viewport when using multi viewport feature.
* \note Emulated through geometry shader on older hardware.
*/
VIEWPORT_INDEX = (1 << 17),
/* Not a builtin but a flag we use to tag shaders that use the debug features. */
USE_DEBUG_DRAW = (1 << 29),

View File

@ -780,8 +780,8 @@ void MTLFrameBuffer::apply_state()
/* Ensure viewport has been set. NOTE: This should no longer happen, but kept for safety to
* track bugs. If viewport size is zero, use framebuffer size. */
int viewport_w = viewport_[2];
int viewport_h = viewport_[3];
int viewport_w = viewport_[0][2];
int viewport_h = viewport_[0][3];
if (viewport_w == 0 || viewport_h == 0) {
MTL_LOG_WARNING("Viewport had width and height of (0,0) -- Updating -- DEBUG Safety check");
viewport_w = default_width_;
@ -789,7 +789,7 @@ void MTLFrameBuffer::apply_state()
}
/* Update Context State. */
mtl_ctx->set_viewport(viewport_[0], viewport_[1], viewport_w, viewport_h);
mtl_ctx->set_viewport(viewport_[0][0], viewport_[0][1], viewport_w, viewport_h);
mtl_ctx->set_scissor(scissor_[0], scissor_[1], scissor_[2], scissor_[3]);
mtl_ctx->set_scissor_enabled(scissor_test_);

View File

@ -592,7 +592,8 @@ void GLBackend::capabilities_init()
GLContext::explicit_location_support = epoxy_gl_version() >= 43;
GLContext::geometry_shader_invocations = epoxy_has_gl_extension("GL_ARB_gpu_shader5");
GLContext::fixed_restart_index_support = epoxy_has_gl_extension("GL_ARB_ES3_compatibility");
GLContext::layered_rendering_support = epoxy_has_gl_extension("GL_AMD_vertex_shader_layer");
GLContext::layered_rendering_support = epoxy_has_gl_extension(
"GL_ARB_shader_viewport_layer_array");
GLContext::native_barycentric_support = epoxy_has_gl_extension(
"GL_AMD_shader_explicit_vertex_parameter");
GLContext::multi_bind_support = GLContext::multi_bind_image_support = epoxy_has_gl_extension(

View File

@ -43,10 +43,10 @@ GLFrameBuffer::GLFrameBuffer(
height_ = h;
srgb_ = false;
viewport_[0] = scissor_[0] = 0;
viewport_[1] = scissor_[1] = 0;
viewport_[2] = scissor_[2] = w;
viewport_[3] = scissor_[3] = h;
viewport_[0][0] = scissor_[0] = 0;
viewport_[0][1] = scissor_[1] = 0;
viewport_[0][2] = scissor_[2] = w;
viewport_[0][3] = scissor_[3] = h;
if (fbo_id_) {
debug::object_label(GL_FRAMEBUFFER, fbo_id_, name_);
@ -230,7 +230,20 @@ void GLFrameBuffer::apply_state()
return;
}
glViewport(UNPACK4(viewport_));
if (multi_viewport_ == false) {
glViewport(UNPACK4(viewport_[0]));
}
else {
/* Great API you have there! You have to convert to float values for setting int viewport
* values. **Audible Facepalm** */
float viewports_f[GPU_MAX_VIEWPORTS][4];
for (int i = 0; i < GPU_MAX_VIEWPORTS; i++) {
for (int j = 0; j < 4; j++) {
viewports_f[i][j] = viewport_[i][j];
}
}
glViewportArrayv(0, GPU_MAX_VIEWPORTS, viewports_f[0]);
}
glScissor(UNPACK4(scissor_));
if (scissor_test_) {

View File

@ -551,6 +551,10 @@ std::string GLShader::vertex_interface_declare(const ShaderCreateInfo &info) con
if (!GLContext::layered_rendering_support && bool(info.builtins_ & BuiltinBits::LAYER)) {
ss << "out int gpu_Layer;\n";
}
if (!GLContext::layered_rendering_support && bool(info.builtins_ & BuiltinBits::VIEWPORT_INDEX))
{
ss << "out int gpu_ViewportIndex;\n";
}
if (bool(info.builtins_ & BuiltinBits::BARYCENTRIC_COORD)) {
if (!GLContext::native_barycentric_support) {
/* Disabled or unsupported. */
@ -584,6 +588,13 @@ std::string GLShader::fragment_interface_declare(const ShaderCreateInfo &info) c
for (const StageInterfaceInfo *iface : in_interfaces) {
print_interface(ss, "in", *iface);
}
if (!GLContext::layered_rendering_support && bool(info.builtins_ & BuiltinBits::LAYER)) {
ss << "#define gpu_Layer gl_Layer\n";
}
if (!GLContext::layered_rendering_support && bool(info.builtins_ & BuiltinBits::VIEWPORT_INDEX))
{
ss << "#define gpu_ViewportIndex gl_ViewportIndex\n";
}
if (bool(info.builtins_ & BuiltinBits::BARYCENTRIC_COORD)) {
if (!GLContext::native_barycentric_support) {
ss << "flat in vec4 gpu_pos[3];\n";
@ -736,6 +747,8 @@ std::string GLShader::workaround_geometry_shader_source_create(
const bool do_layer_workaround = !GLContext::layered_rendering_support &&
bool(info.builtins_ & BuiltinBits::LAYER);
const bool do_viewport_workaround = !GLContext::layered_rendering_support &&
bool(info.builtins_ & BuiltinBits::VIEWPORT_INDEX);
const bool do_barycentric_workaround = !GLContext::native_barycentric_support &&
bool(info.builtins_ & BuiltinBits::BARYCENTRIC_COORD);
@ -752,6 +765,9 @@ std::string GLShader::workaround_geometry_shader_source_create(
if (do_layer_workaround) {
ss << "in int gpu_Layer[];\n";
}
if (do_viewport_workaround) {
ss << "in int gpu_ViewportIndex[];\n";
}
if (do_barycentric_workaround) {
ss << "flat out vec4 gpu_pos[3];\n";
ss << "smooth out vec3 gpu_BaryCoord;\n";
@ -764,6 +780,9 @@ std::string GLShader::workaround_geometry_shader_source_create(
if (do_layer_workaround) {
ss << " gl_Layer = gpu_Layer[0];\n";
}
if (do_viewport_workaround) {
ss << " gl_ViewportIndex = gpu_ViewportIndex[0];\n";
}
if (do_barycentric_workaround) {
ss << " gpu_pos[0] = gl_in[0].gl_Position;\n";
ss << " gpu_pos[1] = gl_in[1].gl_Position;\n";
@ -796,6 +815,9 @@ bool GLShader::do_geometry_shader_injection(const shader::ShaderCreateInfo *info
if (!GLContext::layered_rendering_support && bool(builtins & BuiltinBits::LAYER)) {
return true;
}
if (!GLContext::layered_rendering_support && bool(builtins & BuiltinBits::VIEWPORT_INDEX)) {
return true;
}
return false;
}
@ -853,8 +875,9 @@ static char *glsl_patch_default_get()
STR_CONCAT(patch, slen, "#extension GL_ARB_shading_language_420pack: enable\n");
}
if (GLContext::layered_rendering_support) {
STR_CONCAT(patch, slen, "#extension GL_AMD_vertex_shader_layer: enable\n");
STR_CONCAT(patch, slen, "#extension GL_ARB_shader_viewport_layer_array: enable\n");
STR_CONCAT(patch, slen, "#define gpu_Layer gl_Layer\n");
STR_CONCAT(patch, slen, "#define gpu_ViewportIndex gl_ViewportIndex\n");
}
if (GLContext::native_barycentric_support) {
STR_CONCAT(patch, slen, "#extension GL_AMD_shader_explicit_vertex_parameter: enable\n");

View File

@ -111,6 +111,11 @@ void math_modulo(float a, float b, float c, out float result)
result = compatible_fmod(a, b);
}
void math_floored_modulo(float a, float b, float c, out float result)
{
result = (b != 0.0) ? a - floor(a / b) * b : 0.0;
}
void math_trunc(float a, float b, float c, out float result)
{
result = trunc(a);

View File

@ -6,10 +6,13 @@
#include "GPU_context.h"
#include "GPU_framebuffer.h"
#include "GPU_shader.h"
#include "gpu_testing.hh"
#include "BLI_math_vector.hh"
#include "gpu_shader_create_info.hh"
namespace blender::gpu::tests {
static void test_framebuffer_clear_color_single_attachment()
@ -248,4 +251,77 @@ static void test_framebuffer_cube()
}
GPU_TEST(framebuffer_cube)
/* Effectively tests the same way EEVEE-Next shadows are rendered. */
static void test_framebuffer_multi_viewport()
{
using namespace gpu::shader;
GPU_render_begin();
const int2 size(4, 4);
const int layers = 256;
eGPUTextureUsage usage = GPU_TEXTURE_USAGE_ATTACHMENT | GPU_TEXTURE_USAGE_HOST_READ;
GPUTexture *texture = GPU_texture_create_2d_array(
__func__, UNPACK2(size), layers, 1, GPU_RG32I, usage, nullptr);
GPUFrameBuffer *framebuffer = GPU_framebuffer_create(__func__);
GPU_framebuffer_ensure_config(&framebuffer,
{GPU_ATTACHMENT_NONE, GPU_ATTACHMENT_TEXTURE(texture)});
GPU_framebuffer_bind(framebuffer);
int viewport_rects[16][4];
for (int i = 0; i < 16; i++) {
viewport_rects[i][0] = i % 4;
viewport_rects[i][1] = i / 4;
viewport_rects[i][2] = 1;
viewport_rects[i][3] = 1;
}
GPU_framebuffer_multi_viewports_set(framebuffer, viewport_rects);
const float4 clear_color(0.0f);
GPU_framebuffer_clear_color(framebuffer, clear_color);
ShaderCreateInfo create_info("");
create_info.vertex_source("gpu_framebuffer_layer_viewport_test.glsl");
create_info.fragment_source("gpu_framebuffer_layer_viewport_test.glsl");
create_info.builtins(BuiltinBits::VIEWPORT_INDEX | BuiltinBits::LAYER);
create_info.fragment_out(0, Type::IVEC2, "out_value");
GPUShader *shader = GPU_shader_create_from_info(
reinterpret_cast<GPUShaderCreateInfo *>(&create_info));
/* TODO(fclem): remove this boilerplate. */
GPUVertFormat format{};
GPU_vertformat_attr_add(&format, "dummy", GPU_COMP_U32, 1, GPU_FETCH_INT);
GPUVertBuf *verts = GPU_vertbuf_create_with_format(&format);
GPU_vertbuf_data_alloc(verts, 3);
GPUBatch *batch = GPU_batch_create_ex(GPU_PRIM_TRIS, verts, nullptr, GPU_BATCH_OWNS_VBO);
GPU_batch_set_shader(batch, shader);
int tri_count = size.x * size.y * layers;
GPU_batch_draw_advanced(batch, 0, tri_count * 3, 0, 1);
GPU_batch_discard(batch);
GPU_finish();
int2 *read_data = static_cast<int2 *>(GPU_texture_read(texture, GPU_DATA_INT, 0));
for (auto layer : IndexRange(layers)) {
for (auto viewport : IndexRange(16)) {
int2 expected_color(layer, viewport);
int2 pixel_color = read_data[viewport + layer * 16];
EXPECT_EQ(pixel_color, expected_color);
}
}
MEM_freeN(read_data);
GPU_framebuffer_free(framebuffer);
GPU_texture_free(texture);
GPU_shader_free(shader);
GPU_render_end();
}
GPU_TEST(framebuffer_multi_viewport)
} // namespace blender::gpu::tests

View File

@ -0,0 +1,23 @@
#ifdef GPU_VERTEX_SHADER
void main()
{
/* Fullscreen triangle. */
int v = gl_VertexID % 3;
float x = -1.0 + float((v & 1) << 2);
float y = -1.0 + float((v & 2) << 1);
/* NOTE: Make it cover more than one viewport to test default scissors. */
gl_Position = vec4(x * 2.0, y * 2.0, 1.0, 1.0);
int index = gl_VertexID / 3;
gpu_ViewportIndex = index % 16;
gpu_Layer = index / 16;
}
#endif
#ifdef GPU_FRAGMENT_SHADER
void main()
{
out_value = ivec2(gpu_Layer, gpu_ViewportIndex);
}
#endif

View File

@ -376,7 +376,7 @@ void VKCommandBuffer::ensure_active_framebuffer()
render_pass_begin_info.sType = VK_STRUCTURE_TYPE_RENDER_PASS_BEGIN_INFO;
render_pass_begin_info.renderPass = state.framebuffer_->vk_render_pass_get();
render_pass_begin_info.framebuffer = state.framebuffer_->vk_framebuffer_get();
render_pass_begin_info.renderArea = state.framebuffer_->vk_render_area_get();
render_pass_begin_info.renderArea = state.framebuffer_->vk_render_areas_get()[0];
/* We don't use clear ops, but vulkan wants to have at least one. */
VkClearValue clear_value = {};
render_pass_begin_info.clearValueCount = 1;

View File

@ -71,52 +71,53 @@ void VKFrameBuffer::bind(bool /*enabled_srgb*/)
context.activate_framebuffer(*this);
}
VkViewport VKFrameBuffer::vk_viewport_get() const
Array<VkViewport, 16> VKFrameBuffer::vk_viewports_get() const
{
VkViewport viewport;
int viewport_rect[4];
viewport_get(viewport_rect);
Array<VkViewport, 16> viewports(this->multi_viewport_ ? GPU_MAX_VIEWPORTS : 1);
viewport.x = viewport_rect[0];
viewport.y = viewport_rect[1];
viewport.width = viewport_rect[2];
viewport.height = viewport_rect[3];
viewport.minDepth = 0.0f;
viewport.maxDepth = 1.0f;
/*
* Vulkan has origin to the top left, Blender bottom left. We counteract this by using a negative
* viewport when flip_viewport_ is set. This flips the viewport making any draw/blit use the
* correct orientation.
*/
if (flip_viewport_) {
viewport.y = height_ - viewport_rect[1];
viewport.height = -viewport_rect[3];
int index = 0;
for (VkViewport &viewport : viewports) {
viewport.x = viewport_[index][0];
viewport.y = viewport_[index][1];
viewport.width = viewport_[index][2];
viewport.height = viewport_[index][3];
viewport.minDepth = 0.0f;
viewport.maxDepth = 1.0f;
/*
* Vulkan has origin to the top left, Blender bottom left. We counteract this by using a
* negative viewport when flip_viewport_ is set. This flips the viewport making any draw/blit
* use the correct orientation.
*/
if (flip_viewport_) {
viewport.y = height_ - viewport_[index][1];
viewport.height = -viewport_[index][3];
}
index++;
}
return viewport;
return viewports;
}
VkRect2D VKFrameBuffer::vk_render_area_get() const
Array<VkRect2D, 16> VKFrameBuffer::vk_render_areas_get() const
{
VkRect2D render_area = {};
Array<VkRect2D, 16> render_areas(this->multi_viewport_ ? GPU_MAX_VIEWPORTS : 1);
if (scissor_test_get()) {
int scissor_rect[4];
scissor_get(scissor_rect);
render_area.offset.x = scissor_rect[0];
render_area.offset.y = scissor_rect[1];
render_area.extent.width = scissor_rect[2];
render_area.extent.height = scissor_rect[3];
for (VkRect2D &render_area : render_areas) {
if (scissor_test_get()) {
int scissor_rect[4];
scissor_get(scissor_rect);
render_area.offset.x = scissor_rect[0];
render_area.offset.y = scissor_rect[1];
render_area.extent.width = scissor_rect[2];
render_area.extent.height = scissor_rect[3];
}
else {
render_area.offset.x = 0;
render_area.offset.y = 0;
render_area.extent.width = width_;
render_area.extent.height = height_;
}
}
else {
render_area.offset.x = 0;
render_area.offset.y = 0;
render_area.extent.width = width_;
render_area.extent.height = height_;
}
return render_area;
return render_areas;
}
bool VKFrameBuffer::check(char /*err_out*/[256])
@ -170,7 +171,7 @@ void VKFrameBuffer::clear(const Vector<VkClearAttachment> &attachments) const
return;
}
VkClearRect clear_rect = {};
clear_rect.rect = vk_render_area_get();
clear_rect.rect = vk_render_areas_get()[0];
clear_rect.baseArrayLayer = 0;
clear_rect.layerCount = 1;

View File

@ -8,6 +8,7 @@
#pragma once
#include "BLI_array.hh"
#include "BLI_math_vector.hh"
#include "BLI_span.hh"
#include "BLI_vector.hh"
@ -107,8 +108,8 @@ class VKFrameBuffer : public FrameBuffer {
BLI_assert(vk_render_pass_ != VK_NULL_HANDLE);
return vk_render_pass_;
}
VkViewport vk_viewport_get() const;
VkRect2D vk_render_area_get() const;
Array<VkViewport, 16> vk_viewports_get() const;
Array<VkRect2D, 16> vk_render_areas_get() const;
VkImage vk_image_get() const
{
BLI_assert(vk_image_ != VK_NULL_HANDLE);

View File

@ -164,12 +164,12 @@ void VKPipeline::finalize(VKContext &context,
/* Viewport state. */
VkPipelineViewportStateCreateInfo viewport_state = {};
viewport_state.sType = VK_STRUCTURE_TYPE_PIPELINE_VIEWPORT_STATE_CREATE_INFO;
VkViewport viewport = framebuffer.vk_viewport_get();
viewport_state.pViewports = &viewport;
viewport_state.viewportCount = 1;
VkRect2D scissor = framebuffer.vk_render_area_get();
viewport_state.pScissors = &scissor;
viewport_state.scissorCount = 1;
Array<VkViewport, 16> viewports = framebuffer.vk_viewports_get();
viewport_state.pViewports = &viewports[0];
viewport_state.viewportCount = viewports.size();
Array<VkRect2D, 16> scissors = framebuffer.vk_render_areas_get();
viewport_state.pScissors = &scissors[0];
viewport_state.scissorCount = scissors.size();
pipeline_create_info.pViewportState = &viewport_state;
/* Multi-sample state. */

View File

@ -500,6 +500,11 @@ static char *glsl_patch_get()
STR_CONCAT(patch, slen, "#define gl_InstanceID gpu_InstanceIndex\n");
/* TODO(fclem): This creates a validation error and should be already part of Vulkan 1.2. */
STR_CONCAT(patch, slen, "#extension GL_ARB_shader_viewport_layer_array: enable\n");
STR_CONCAT(patch, slen, "#define gpu_Layer gl_Layer\n");
STR_CONCAT(patch, slen, "#define gpu_ViewportIndex gl_ViewportIndex\n");
STR_CONCAT(patch, slen, "#define DFDX_SIGN 1.0\n");
STR_CONCAT(patch, slen, "#define DFDY_SIGN 1.0\n");
@ -526,6 +531,7 @@ Vector<uint32_t> VKShader::compile_glsl_to_spirv(Span<const char *> sources,
shaderc::Compiler &compiler = backend.get_shaderc_compiler();
shaderc::CompileOptions options;
options.SetOptimizationLevel(shaderc_optimization_level_performance);
options.SetTargetEnvironment(shaderc_target_env_vulkan, shaderc_env_version_vulkan_1_2);
if (G.debug & G_DEBUG_GPU_RENDERDOC) {
options.SetOptimizationLevel(shaderc_optimization_level_zero);
options.SetGenerateDebugInfo();

View File

@ -83,7 +83,6 @@ bool ArmatureExporter::add_instance_controller(Object *ob)
}
/* write root bone URLs */
Bone *bone;
LISTBASE_FOREACH (Bone *, bone, &arm->bonebase) {
write_bone_URLs(ins, ob_arm, bone);
}

View File

@ -154,7 +154,6 @@ BCSample &BCAnimationSampler::sample_object(Object *ob, int frame_index, bool fo
#endif
if (ob->type == OB_ARMATURE) {
bPoseChannel *pchan;
LISTBASE_FOREACH (bPoseChannel *, pchan, &ob->pose->chanbase) {
Bone *bone = pchan->bone;
Matrix bmat;
@ -222,7 +221,6 @@ bool BCAnimationSampler::is_animated_by_constraint(Object *ob,
ListBase *conlist,
std::set<Object *> &animated_objects)
{
bConstraint *con;
LISTBASE_FOREACH (bConstraint *, con, conlist) {
ListBase targets = {nullptr, nullptr};
@ -231,7 +229,6 @@ bool BCAnimationSampler::is_animated_by_constraint(Object *ob,
}
if (BKE_constraint_targets_get(con, &targets)) {
bConstraintTarget *ct;
Object *obtar;
bool found = false;

View File

@ -70,7 +70,6 @@ bool ControllerExporter::add_instance_controller(Object *ob)
}
/* write root bone URLs */
Bone *bone;
LISTBASE_FOREACH (Bone *, bone, &arm->bonebase) {
write_bone_URLs(ins, ob_arm, bone);
}
@ -424,7 +423,6 @@ std::string ControllerExporter::add_joints_source(Object *ob_arm,
std::string source_id = controller_id + JOINTS_SOURCE_ID_SUFFIX;
int totjoint = 0;
bDeformGroup *def;
LISTBASE_FOREACH (bDeformGroup *, def, defbase) {
if (is_bone_defgroup(ob_arm, def)) {
totjoint++;

View File

@ -198,7 +198,6 @@ void SceneExporter::writeNode(Object *ob)
ListBase targets = {nullptr, nullptr};
if (BKE_constraint_targets_get(con, &targets)) {
bConstraintTarget *ct;
Object *obtar;
LISTBASE_FOREACH (bConstraintTarget *, ct, &targets) {

View File

@ -253,7 +253,6 @@ Object *bc_get_assigned_armature(Object *ob)
ob_arm = ob->parent;
}
else {
ModifierData *mod;
LISTBASE_FOREACH (ModifierData *, mod, &ob->modifiers) {
if (mod->type == eModifierType_Armature) {
ob_arm = ((ArmatureModifierData *)mod)->object;
@ -435,8 +434,6 @@ bool bc_is_leaf_bone(Bone *bone)
EditBone *bc_get_edit_bone(bArmature *armature, char *name)
{
EditBone *eBone;
LISTBASE_FOREACH (EditBone *, eBone, armature->edbo) {
if (STREQ(name, eBone->name)) {
return eBone;
@ -765,7 +762,6 @@ static bool has_custom_props(Bone *bone, bool enabled, std::string key)
void bc_enable_fcurves(bAction *act, char *bone_name)
{
FCurve *fcu;
char prefix[200];
if (bone_name) {

View File

@ -49,8 +49,8 @@ void USD_unregister_hook(struct USDHook *hook)
USDHook *USD_find_hook_name(const char name[])
{
/* sanity checks */
if (g_usd_hooks.empty() || (name == NULL) || (name[0] == 0)) {
return NULL;
if (g_usd_hooks.empty() || (name == nullptr) || (name[0] == 0)) {
return nullptr;
}
USDHookList::iterator hook_iter = std::find_if(
@ -58,7 +58,7 @@ USDHook *USD_find_hook_name(const char name[])
return strcmp(hook->idname, name) == 0;
});
return (hook_iter == g_usd_hooks.end()) ? NULL : *hook_iter;
return (hook_iter == g_usd_hooks.end()) ? nullptr : *hook_iter;
}
namespace blender::io::usd {
@ -81,7 +81,7 @@ struct USDSceneExportContext {
USDSceneExportContext(pxr::UsdStageRefPtr in_stage, Depsgraph *depsgraph) : stage(in_stage)
{
RNA_pointer_create(NULL, &RNA_Depsgraph, depsgraph, &depsgraph_ptr);
RNA_pointer_create(nullptr, &RNA_Depsgraph, depsgraph, &depsgraph_ptr);
}
pxr::UsdStageRefPtr get_stage()
@ -259,7 +259,7 @@ class OnMaterialExportInvoker : public USDHookInvoker {
pxr::UsdShadeMaterial &usd_material)
: hook_context_(stage), usd_material_(usd_material)
{
RNA_pointer_create(NULL, &RNA_Material, material, &material_ptr_);
RNA_pointer_create(nullptr, &RNA_Material, material, &material_ptr_);
}
protected:

View File

@ -267,9 +267,9 @@ void set_normal_texture_range(pxr::UsdShadeShader &usd_shader, const InputSpec &
}
/* Create USD Shade Material network from Blender viewport display settings. */
void create_usd_viewport_material(const USDExporterContext &usd_export_context,
Material *material,
pxr::UsdShadeMaterial &usd_material)
static void create_usd_viewport_material(const USDExporterContext &usd_export_context,
Material *material,
pxr::UsdShadeMaterial &usd_material)
{
/* Construct the shader. */
pxr::SdfPath shader_path = usd_material.GetPath().AppendChild(usdtokens::preview_shader);

View File

@ -451,6 +451,7 @@ typedef struct GreasePencil {
const blender::bke::greasepencil::Layer *get_active_layer() const;
blender::bke::greasepencil::Layer *get_active_layer_for_write();
void set_active_layer(const blender::bke::greasepencil::Layer *layer);
bool is_layer_active(const blender::bke::greasepencil::Layer *layer) const;
blender::bke::greasepencil::Layer &add_layer(blender::bke::greasepencil::LayerGroup &group,
blender::StringRefNull name);

View File

@ -2124,6 +2124,7 @@ typedef enum NodeMathOperation {
NODE_MATH_PINGPONG = 37,
NODE_MATH_SMOOTH_MIN = 38,
NODE_MATH_SMOOTH_MAX = 39,
NODE_MATH_FLOORED_MODULO = 40,
} NodeMathOperation;
typedef enum NodeVectorMathOperation {

View File

@ -692,7 +692,9 @@ typedef struct UserDef_Experimental {
char use_new_volume_nodes;
char use_rotation_socket;
char use_node_group_operators;
char use_shader_node_previews;
char use_asset_shelf;
char _pad[7];
/** `makesdna` does not allow empty structs. */
} UserDef_Experimental;
@ -794,7 +796,7 @@ typedef struct UserDef {
int scrollback;
/** Node insert offset (aka auto-offset) margin, but might be useful for later stuff as well. */
char node_margin;
char _pad2[1];
char node_preview_res;
/** #eUserpref_Translation_Flags. */
short transopts;
short menuthreshold1, menuthreshold2;

View File

@ -4751,7 +4751,7 @@ static RNAProcessItem PROCESS_ITEMS[] = {
{"rna_sound.cc", "rna_sound_api.cc", RNA_def_sound},
{"rna_ui.cc", "rna_ui_api.cc", RNA_def_ui},
#ifdef WITH_USD
{"rna_usd.cc", NULL, RNA_def_usd},
{"rna_usd.cc", nullptr, RNA_def_usd},
#endif
{"rna_userdef.cc", nullptr, RNA_def_userdef},
{"rna_vfont.cc", "rna_vfont_api.cc", RNA_def_vfont},

View File

@ -95,6 +95,7 @@ static void rna_Material_update(Main * /*bmain*/, Scene * /*scene*/, PointerRNA
static void rna_Material_update_previews(Main * /*bmain*/, Scene * /*scene*/, PointerRNA *ptr)
{
Material *ma = (Material *)ptr->owner_id;
BKE_material_make_node_previews_dirty(ma);
WM_main_add_notifier(NC_MATERIAL | ND_SHADING_PREVIEW, ma);
}

View File

@ -223,7 +223,16 @@ const EnumPropertyItem rna_enum_node_math_items[] = {
{NODE_MATH_TRUNC, "TRUNC", 0, "Truncate", "The integer part of A, removing fractional digits"},
RNA_ENUM_ITEM_SEPR,
{NODE_MATH_FRACTION, "FRACT", 0, "Fraction", "The fraction part of A"},
{NODE_MATH_MODULO, "MODULO", 0, "Modulo", "Modulo using fmod(A,B)"},
{NODE_MATH_MODULO,
"MODULO",
0,
"Truncated Modulo",
"The remainder of truncated division using fmod(A,B)"},
{NODE_MATH_FLOORED_MODULO,
"FLOORED_MODULO",
0,
"Floored Modulo",
"The remainder of floored division"},
{NODE_MATH_WRAP, "WRAP", 0, "Wrap", "Wrap value to range, wrap(A,B)"},
{NODE_MATH_SNAP, "SNAP", 0, "Snap", "Snap to increment, snap(A,B)"},
{NODE_MATH_PINGPONG,

View File

@ -7659,9 +7659,12 @@ static void rna_def_space_node(BlenderRNA *brna)
RNA_def_property_ui_text(
prop, "Overlay Settings", "Settings for display of overlays in the Node Editor");
prop = RNA_def_property(srna, "supports_preview", PROP_BOOLEAN, PROP_NONE);
prop = RNA_def_property(srna, "supports_previews", PROP_BOOLEAN, PROP_NONE);
RNA_def_property_boolean_funcs(prop, "rna_SpaceNode_supports_previews", nullptr);
RNA_def_property_clear_flag(prop, PROP_EDITABLE);
RNA_def_property_ui_text(prop,
"Supports Previews",
"Whether the node editor's type supports displaying node previews");
rna_def_space_node_overlay(brna);
RNA_api_space_node(srna);

View File

@ -27,11 +27,11 @@ static StructRNA *rna_USDHook_refine(PointerRNA *ptr)
return (hook->rna_ext.srna) ? hook->rna_ext.srna : &RNA_USDHook;
}
static bool rna_USDHook_unregister(Main *bmain, StructRNA *type)
static bool rna_USDHook_unregister(Main * /* bmain */, StructRNA *type)
{
USDHook *hook = static_cast<USDHook *>(RNA_struct_blender_type_get(type));
if (hook == NULL) {
if (hook == nullptr) {
return false;
}
@ -39,7 +39,7 @@ static bool rna_USDHook_unregister(Main *bmain, StructRNA *type)
RNA_struct_free_extension(type, &hook->rna_ext);
RNA_struct_free(&BLENDER_RNA, type);
WM_main_add_notifier(NC_WINDOW, NULL);
WM_main_add_notifier(NC_WINDOW, nullptr);
/* unlink Blender-side data */
USD_unregister_hook(hook);
@ -58,16 +58,16 @@ static StructRNA *rna_USDHook_register(Main *bmain,
StructFreeFunc free)
{
const char *error_prefix = "Registering USD hook class:";
USDHook dummy_hook = {NULL};
USDHook dummy_hook = {{0}};
USDHook *hook;
PointerRNA dummy_hook_ptr = {NULL};
PointerRNA dummy_hook_ptr = {nullptr};
/* setup dummy type info to store static properties in */
RNA_pointer_create(NULL, &RNA_USDHook, &dummy_hook, &dummy_hook_ptr);
RNA_pointer_create(nullptr, &RNA_USDHook, &dummy_hook, &dummy_hook_ptr);
/* validate the python class */
if (validate(&dummy_hook_ptr, data, NULL) != 0) {
return NULL;
if (validate(&dummy_hook_ptr, data, nullptr) != 0) {
return nullptr;
}
if (strlen(identifier) >= sizeof(dummy_hook.idname)) {
@ -77,7 +77,7 @@ static StructRNA *rna_USDHook_register(Main *bmain,
error_prefix,
identifier,
(int)sizeof(dummy_hook.idname));
return NULL;
return nullptr;
}
/* check if we have registered this hook before, and remove it */
@ -92,7 +92,7 @@ static StructRNA *rna_USDHook_register(Main *bmain,
identifier,
dummy_hook.idname,
"could not be unregistered");
return NULL;
return nullptr;
}
}
@ -110,7 +110,7 @@ static StructRNA *rna_USDHook_register(Main *bmain,
/* add and register with other info as needed */
USD_register_hook(hook);
WM_main_add_notifier(NC_WINDOW, NULL);
WM_main_add_notifier(NC_WINDOW, nullptr);
/* return the struct-rna added */
return hook->rna_ext.srna;
@ -122,32 +122,30 @@ static void rna_def_usd_hook(BlenderRNA *brna)
{
StructRNA *srna;
PropertyRNA *prop;
FunctionRNA *func;
PropertyRNA *parm;
srna = RNA_def_struct(brna, "USDHook", NULL);
srna = RNA_def_struct(brna, "USDHook", nullptr);
RNA_def_struct_ui_text(srna, "USD Hook", "Defines callback functions to extend USD IO");
RNA_def_struct_sdna(srna, "USDHook");
RNA_def_struct_refine_func(srna, "rna_USDHook_refine");
RNA_def_struct_register_funcs(srna, "rna_USDHook_register", "rna_USDHook_unregister", NULL);
RNA_def_struct_register_funcs(srna, "rna_USDHook_register", "rna_USDHook_unregister", nullptr);
///* Properties --------------------- */
RNA_define_verify_sdna(0); /* not in sdna */
prop = RNA_def_property(srna, "bl_idname", PROP_STRING, PROP_NONE);
RNA_def_property_string_sdna(prop, NULL, "idname");
RNA_def_property_string_sdna(prop, nullptr, "idname");
RNA_def_property_flag(prop, PROP_REGISTER);
RNA_def_property_ui_text(prop, "ID Name", "");
prop = RNA_def_property(srna, "bl_label", PROP_STRING, PROP_NONE);
RNA_def_property_string_sdna(prop, NULL, "name");
RNA_def_property_string_sdna(prop, nullptr, "name");
RNA_def_property_ui_text(prop, "UI Name", "");
RNA_def_struct_name_property(srna, prop);
RNA_def_property_flag(prop, PROP_REGISTER);
prop = RNA_def_property(srna, "bl_description", PROP_STRING, PROP_NONE);
RNA_def_property_string_sdna(prop, NULL, "description");
RNA_def_property_string_sdna(prop, nullptr, "description");
RNA_def_property_string_maxlength(prop, RNA_DYN_DESCR_MAX); /* else it uses the pointer size! */
RNA_def_property_flag(prop, PROP_REGISTER_OPTIONAL);
RNA_def_property_ui_text(prop, "Description", "A short description of the USD hook");

View File

@ -3303,7 +3303,7 @@ static void rna_def_userdef_theme_space_seq(BlenderRNA *brna)
RNA_def_property_update(prop, 0, "rna_userdef_theme_update");
prop = RNA_def_property(srna, "transition_strip", PROP_FLOAT, PROP_COLOR_GAMMA);
RNA_def_property_float_sdna(prop, NULL, "transition");
RNA_def_property_float_sdna(prop, nullptr, "transition");
RNA_def_property_array(prop, 3);
RNA_def_property_ui_text(prop, "Transition Strip", "");
RNA_def_property_update(prop, 0, "rna_userdef_theme_update");
@ -5456,6 +5456,15 @@ static void rna_def_userdef_edit(BlenderRNA *brna)
prop, "Auto-offset Margin", "Minimum distance between nodes for Auto-offsetting nodes");
RNA_def_property_update(prop, 0, "rna_userdef_update");
prop = RNA_def_property(srna, "node_preview_resolution", PROP_INT, PROP_PIXEL);
RNA_def_property_int_sdna(prop, nullptr, "node_preview_res");
RNA_def_property_range(prop, 50, 250);
RNA_def_property_ui_text(prop,
"Node Preview Resolution",
"Resolution used for Shader node previews (should be changed for "
"performance convenience)");
RNA_def_property_update(prop, 0, "rna_userdef_update");
/* cursor */
prop = RNA_def_property(srna, "use_cursor_lock_adjust", PROP_BOOLEAN, PROP_NONE);
RNA_def_property_boolean_sdna(prop, nullptr, "uiflag", USER_LOCK_CURSOR_ADJUST);
@ -6799,6 +6808,11 @@ static void rna_def_userdef_experimental(BlenderRNA *brna)
"Enables the asset shelf regions in the 3D view. Used by the Pose "
"Library add-on in Pose Mode only");
RNA_def_property_update(prop, 0, "rna_userdef_ui_update");
prop = RNA_def_property(srna, "use_shader_node_previews", PROP_BOOLEAN, PROP_NONE);
RNA_def_property_ui_text(
prop, "Shader Node Previews", "Enables previews in the shader node editor");
RNA_def_property_update(prop, 0, "rna_userdef_ui_update");
}
static void rna_def_userdef_addon_collection(BlenderRNA *brna, PropertyRNA *cprop)

View File

@ -152,6 +152,8 @@ inline bool try_dispatch_float_math_fl_fl_to_fl(const int operation, Callback &&
return dispatch(exec_preset_fast, [](float a, float b) { return (float)(a > b); });
case NODE_MATH_MODULO:
return dispatch(exec_preset_fast, [](float a, float b) { return safe_modf(a, b); });
case NODE_MATH_FLOORED_MODULO:
return dispatch(exec_preset_fast, [](float a, float b) { return safe_floored_modf(a, b); });
case NODE_MATH_SNAP:
return dispatch(exec_preset_fast,
[](float a, float b) { return floorf(safe_divide(a, b)) * b; });

View File

@ -53,6 +53,8 @@ const FloatMathOperationInfo *get_float_math_operation_info(const int operation)
RETURN_OPERATION_INFO("Greater Than", "math_greater_than");
case NODE_MATH_MODULO:
RETURN_OPERATION_INFO("Modulo", "math_modulo");
case NODE_MATH_FLOORED_MODULO:
RETURN_OPERATION_INFO("Floored Modulo", "math_floored_modulo");
case NODE_MATH_ABSOLUTE:
RETURN_OPERATION_INFO("Absolute", "math_absolute");
case NODE_MATH_ARCTAN2:

View File

@ -175,6 +175,16 @@ static void valuefn(float *out, TexParams *p, bNode *node, bNodeStack **in, shor
break;
}
case NODE_MATH_FLOORED_MODULO: {
if (in1 == 0.0f) {
*out = 0.0f;
}
else {
*out = in0 - floorf(in0 / in1) * in1;
}
break;
}
case NODE_MATH_ABSOLUTE: {
*out = fabsf(in0);
break;

View File

@ -406,6 +406,9 @@ void RE_stats_draw_cb(struct Render *re, void *handle, void (*f)(void *handle, R
void RE_progress_cb(struct Render *re, void *handle, void (*f)(void *handle, float));
void RE_draw_lock_cb(struct Render *re, void *handle, void (*f)(void *handle, bool lock));
void RE_test_break_cb(struct Render *re, void *handle, bool (*f)(void *handle));
void RE_prepare_viewlayer_cb(struct Render *re,
void *handle,
bool (*f)(void *handle, ViewLayer *vl, struct Depsgraph *depsgraph));
void RE_current_scene_update_cb(struct Render *re,
void *handle,
void (*f)(void *handle, struct Scene *scene));

View File

@ -849,6 +849,14 @@ static void engine_render_view_layer(Render *re,
/* Create depsgraph with scene evaluated at render resolution. */
ViewLayer *view_layer = static_cast<ViewLayer *>(
BLI_findstring(&re->scene->view_layers, view_layer_iter->name, offsetof(ViewLayer, name)));
if (re->prepare_viewlayer) {
if (!re->prepare_viewlayer(re->prepare_vl_handle, view_layer, engine->depsgraph)) {
if (re->draw_lock) {
re->draw_lock(re->dlh, false);
}
return;
}
}
engine_depsgraph_init(engine, view_layer);
/* Sync data to engine, within draw lock so scene data can be accessed safely. */

View File

@ -170,6 +170,12 @@ static bool do_write_image_or_movie(Render *re,
static void result_nothing(void * /*arg*/, RenderResult * /*rr*/) {}
static void result_rcti_nothing(void * /*arg*/, RenderResult * /*rr*/, rcti * /*rect*/) {}
static void current_scene_nothing(void * /*arg*/, Scene * /*scene*/) {}
static bool prepare_viewlayer_nothing(void * /*arg*/,
ViewLayer * /*vl*/,
Depsgraph * /*depsgraph*/)
{
return true;
}
static void stats_nothing(void * /*arg*/, RenderStats * /*rs*/) {}
static void float_nothing(void * /*arg*/, float /*val*/) {}
static bool default_break(void * /*arg*/)
@ -548,6 +554,7 @@ void RE_InitRenderCB(Render *re)
re->display_clear = result_nothing;
re->display_update = result_rcti_nothing;
re->current_scene_update = current_scene_nothing;
re->prepare_viewlayer = prepare_viewlayer_nothing;
re->progress = float_nothing;
re->test_break = default_break;
if (G.background) {
@ -916,6 +923,14 @@ void RE_test_break_cb(Render *re, void *handle, bool (*f)(void *handle))
re->tbh = handle;
}
void RE_prepare_viewlayer_cb(Render *re,
void *handle,
bool (*f)(void *handle, ViewLayer *vl, Depsgraph *depsgraph))
{
re->prepare_viewlayer = f;
re->prepare_vl_handle = handle;
}
/** \} */
/* -------------------------------------------------------------------- */

View File

@ -170,6 +170,13 @@ struct Render : public BaseRender {
bool (*test_break)(void *handle) = nullptr;
void *tbh = nullptr;
/**
* Executed right before the initialisation of the depsgraph, in order to modify some stuff in
* the viewlayer. The modified ids must be tagged in the depsgraph.
*/
bool (*prepare_viewlayer)(void *handle, struct ViewLayer *vl, struct Depsgraph *depsgraph);
void *prepare_vl_handle;
RenderStats i = {};
/**