Node: Gabor Noise Texture #110802

Open
Charlie Jolly wants to merge 68 commits from CharlieJolly/blender:gabor into main

When changing the target branch, be careful to rebase the branch in your fork to match. See documentation.
53 changed files with 1419 additions and 174 deletions
Showing only changes of commit 940aab5f9b - Show all commits

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

@ -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

@ -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

@ -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

@ -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

@ -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

@ -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

@ -572,6 +572,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

@ -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

@ -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

@ -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

@ -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

@ -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

@ -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 = {};
/**