This is used as base class for the compute contexts for group and evaluate closure nodes. Furthermore, in the future this can be used for the compute context that is passed into field evaluation. Pull Request: https://projects.blender.org/blender/blender/pulls/139377
849 lines
31 KiB
C++
849 lines
31 KiB
C++
/* SPDX-FileCopyrightText: 2023 Blender Authors
|
|
*
|
|
* 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 "BLI_listbase.h"
|
|
#include "BLI_string.h"
|
|
|
|
#include "DNA_camera_types.h"
|
|
#include "DNA_material_types.h"
|
|
#include "DNA_world_types.h"
|
|
|
|
#include "RNA_access.hh"
|
|
#include "RNA_prototypes.hh"
|
|
|
|
#include "BKE_colortools.hh"
|
|
#include "BKE_compute_context_cache.hh"
|
|
#include "BKE_compute_contexts.hh"
|
|
#include "BKE_context.hh"
|
|
#include "BKE_global.hh"
|
|
#include "BKE_layer.hh"
|
|
#include "BKE_lib_id.hh"
|
|
#include "BKE_main.hh"
|
|
#include "BKE_material.hh"
|
|
#include "BKE_node.hh"
|
|
#include "BKE_node_legacy_types.hh"
|
|
#include "BKE_node_runtime.hh"
|
|
#include "BKE_node_tree_update.hh"
|
|
|
|
#include "DEG_depsgraph.hh"
|
|
|
|
#include "IMB_imbuf.hh"
|
|
|
|
#include "WM_api.hh"
|
|
|
|
#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;
|
|
ePreviewType preview_type;
|
|
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<NodeSocketPair> AOV_nodes;
|
|
Vector<NodeSocketPair> 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;
|
|
LISTBASE_FOREACH (const bNodeTreePath *, item, &snode.treepath) {
|
|
treepath.append(item);
|
|
}
|
|
|
|
if (treepath.is_empty()) {
|
|
return std::nullopt;
|
|
}
|
|
if (treepath.size() == 1) {
|
|
/* Top group. */
|
|
ComputeContextHash hash;
|
|
hash.v1 = hash.v2 = 0;
|
|
return hash;
|
|
}
|
|
bke::ComputeContextCache compute_context_cache;
|
|
const ComputeContext *compute_context = nullptr;
|
|
for (const int i : treepath.index_range().drop_back(1)) {
|
|
/* The tree path contains the name of the node but not its ID. */
|
|
bNodeTree *tree = treepath[i]->nodetree;
|
|
const bNode *node = bke::node_find_node_by_name(*tree, 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 = &compute_context_cache.for_group_node(
|
|
compute_context, node->identifier, tree);
|
|
}
|
|
return compute_context->hash();
|
|
}
|
|
|
|
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,
|
|
ePreviewType preview_type)
|
|
{
|
|
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, preview_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
|
|
* \{ */
|
|
|
|
/**
|
|
* Follows some rules to determine the previewed socket and node associated.
|
|
* We first seek for an output socket of the node, if none if found, the node is an output node,
|
|
* and thus seek for an input socket.
|
|
*/
|
|
static bNodeSocket *node_find_preview_socket(bNodeTree &ntree, bNode &node)
|
|
{
|
|
bNodeSocket *socket = get_main_socket(ntree, node, SOCK_OUT);
|
|
if (socket == nullptr) {
|
|
socket = get_main_socket(ntree, node, SOCK_IN);
|
|
if (socket != nullptr && socket->link == nullptr) {
|
|
if (!ELEM(socket->type, SOCK_FLOAT, SOCK_VECTOR, SOCK_RGBA)) {
|
|
/* We can not preview a socket with no link and no manual value. */
|
|
return nullptr;
|
|
}
|
|
}
|
|
}
|
|
return socket;
|
|
}
|
|
|
|
static bool socket_use_aov(const bNodeSocket *socket)
|
|
{
|
|
return socket == nullptr || socket->type != SOCK_SHADER;
|
|
}
|
|
|
|
static bool node_use_aov(bNodeTree &ntree, const bNode *node)
|
|
{
|
|
bNode *node_preview = const_cast<bNode *>(node);
|
|
bNodeSocket *socket_preview = node_find_preview_socket(ntree, *node_preview);
|
|
return socket_use_aov(socket_preview);
|
|
}
|
|
|
|
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;
|
|
}
|
|
|
|
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(ntree, &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 node-groups by creating a new output socket for each
|
|
* nested node-group. To do so we cover all nested node-trees starting from the farthest, and
|
|
* update the `nested_node_iter` pointer to the current node-group instance used for linking.
|
|
* We stop before getting to the main node-tree 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,
|
|
const char *route_name)
|
|
{
|
|
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 = bke::node_add_static_node(nullptr, *nested_nt, NODE_GROUP_OUTPUT);
|
|
output_node->flag |= NODE_DO_OUTPUT;
|
|
}
|
|
|
|
nested_nt->tree_interface.add_socket(
|
|
route_name, "", nested_socket_iter->idname, NODE_INTERFACE_SOCKET_OUTPUT, nullptr);
|
|
BKE_ntree_update_after_single_tree_change(*G.pr_main, *nested_nt);
|
|
bNodeSocket *out_socket = blender::bke::node_find_enabled_input_socket(*output_node,
|
|
route_name);
|
|
|
|
bke::node_add_link(
|
|
*nested_nt, *nested_node_iter, *nested_socket_iter, *output_node, *out_socket);
|
|
BKE_ntree_update_after_single_tree_change(*G.pr_main, *nested_nt);
|
|
|
|
/* Change the `nested_node` pointer to the nested node-group instance node. The tree path
|
|
* contains the name of the instance node but not its ID. */
|
|
nested_node_iter = bke::node_find_node_by_name(*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_after_single_tree_change(*G.pr_main, *path_prev->nodetree);
|
|
|
|
/* Now use the newly created socket of the node-group as previewing socket of the node-group
|
|
* instance node. */
|
|
nested_socket_iter = blender::bke::node_find_enabled_output_socket(*nested_node_iter,
|
|
route_name);
|
|
}
|
|
|
|
bke::node_add_link(*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,
|
|
NodeSocketPair nodesocket,
|
|
bNode &output_node)
|
|
{
|
|
bNodeSocket *out_surface_socket = nullptr;
|
|
bNodeTree *main_nt = treepath.first()->nodetree;
|
|
bNode *node_preview = nodesocket.first;
|
|
bNodeSocket *socket_preview = nodesocket.second;
|
|
if (socket_preview == nullptr) {
|
|
return;
|
|
}
|
|
if (socket_preview->in_out == SOCK_IN) {
|
|
BLI_assert(socket_preview->link != nullptr);
|
|
node_preview = socket_preview->link->fromnode;
|
|
socket_preview = socket_preview->link->fromsock;
|
|
}
|
|
/* Ensure output is usable. */
|
|
out_surface_socket = bke::node_find_socket(output_node, SOCK_IN, "Surface");
|
|
if (out_surface_socket->link) {
|
|
/* Make sure no node is already wired to the output before wiring. */
|
|
bke::node_remove_link(main_nt, *out_surface_socket->link);
|
|
}
|
|
|
|
connect_nested_node_to_node(treepath,
|
|
*node_preview,
|
|
*socket_preview,
|
|
output_node,
|
|
*out_surface_socket,
|
|
nodesocket.first->name);
|
|
BKE_ntree_update_after_single_tree_change(*G.pr_main, *main_nt);
|
|
}
|
|
|
|
/* 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<NodeSocketPair> nodesocket_span)
|
|
{
|
|
if (nodesocket_span.is_empty()) {
|
|
return;
|
|
}
|
|
bNodeTree *main_nt = treepath.first()->nodetree;
|
|
bNodeTree *active_nt = treepath.last()->nodetree;
|
|
for (NodeSocketPair nodesocket : nodesocket_span) {
|
|
bNode *node_preview = nodesocket.first;
|
|
bNodeSocket *socket_preview = nodesocket.second;
|
|
|
|
bNode *aov_node = bke::node_add_static_node(nullptr, *main_nt, SH_NODE_OUTPUT_AOV);
|
|
STRNCPY(reinterpret_cast<NodeShaderOutputAOV *>(aov_node->storage)->name,
|
|
nodesocket.first->name);
|
|
if (socket_preview == nullptr) {
|
|
continue;
|
|
}
|
|
bNodeSocket *aov_socket = bke::node_find_socket(*aov_node, SOCK_IN, "Color");
|
|
if (socket_preview->in_out == SOCK_IN) {
|
|
if (socket_preview->link == nullptr) {
|
|
/* Copy the custom value of the socket directly to the AOV node.
|
|
* If the socket does not support custom values, it will just render black. */
|
|
float vec[4] = {0., 0., 0., 1.};
|
|
PointerRNA ptr;
|
|
switch (socket_preview->type) {
|
|
case SOCK_FLOAT:
|
|
ptr = RNA_pointer_create_discrete((ID *)active_nt, &RNA_NodeSocket, socket_preview);
|
|
vec[0] = RNA_float_get(&ptr, "default_value");
|
|
vec[1] = vec[0];
|
|
vec[2] = vec[0];
|
|
break;
|
|
case SOCK_VECTOR:
|
|
case SOCK_RGBA:
|
|
ptr = RNA_pointer_create_discrete((ID *)active_nt, &RNA_NodeSocket, socket_preview);
|
|
RNA_float_get_array(&ptr, "default_value", vec);
|
|
break;
|
|
}
|
|
ptr = RNA_pointer_create_discrete((ID *)active_nt, &RNA_NodeSocket, aov_socket);
|
|
RNA_float_set_array(&ptr, "default_value", vec);
|
|
continue;
|
|
}
|
|
node_preview = socket_preview->link->fromnode;
|
|
socket_preview = socket_preview->link->fromsock;
|
|
}
|
|
connect_nested_node_to_node(
|
|
treepath, *node_preview, *socket_preview, *aov_node, *aov_socket, nodesocket.first->name);
|
|
}
|
|
BKE_ntree_update_after_single_tree_change(*G.pr_main, *main_nt);
|
|
}
|
|
|
|
/* 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)
|
|
{
|
|
NodeSocketPair nodesocket = {nullptr, nullptr};
|
|
ShaderNodesPreviewJob *job_data = static_cast<ShaderNodesPreviewJob *>(pvl_data);
|
|
for (NodeSocketPair nodesocket_iter : job_data->shader_nodes) {
|
|
if (STREQ(vl->name, nodesocket_iter.first->name)) {
|
|
nodesocket = nodesocket_iter;
|
|
job_data->rendering_node = nodesocket_iter.first;
|
|
job_data->rendering_AOVs = false;
|
|
break;
|
|
}
|
|
}
|
|
if (nodesocket.first == 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 = bke::node_find_socket(
|
|
*job_data->mat_output_copy, SOCK_IN, "Displacement");
|
|
if (job_data->mat_displacement_copy.first != nullptr && displacement_socket->link == nullptr) {
|
|
bke::node_add_link(*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, nodesocket, *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, 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 (NodeSocketPair nodesocket_iter : job_data->AOV_nodes) {
|
|
ImBuf *&image_cached = job_data->tree_previews->previews_map.lookup_or_add(
|
|
nodesocket_iter.first->identifier, nullptr);
|
|
ImBuf *image_latest = get_image_from_viewlayer_and_pass(
|
|
*rr, nullptr, nodesocket_iter.first->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, job_data.preview_type);
|
|
if (scene == nullptr) {
|
|
return;
|
|
}
|
|
Span<bNodeTreePath *> treepath = job_data.treepath_copy;
|
|
|
|
/* 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 (NodeSocketPair nodesocket_iter : job_data.shader_nodes) {
|
|
ViewLayer *vl = BKE_view_layer_add(
|
|
scene, nodesocket_iter.first->name, AOV_layer, VIEWLAYER_ADD_COPY);
|
|
STRNCPY(vl->name, nodesocket_iter.first->name);
|
|
}
|
|
for (NodeSocketPair nodesocket_iter : job_data.AOV_nodes) {
|
|
ViewLayerAOV *aov = BKE_view_layer_add_aov(AOV_layer);
|
|
STRNCPY(aov->name, nodesocket_iter.first->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(NestedTreePreviews &tree_previews,
|
|
const bNodeTree &nt,
|
|
ePreviewType preview_type)
|
|
{
|
|
if (tree_previews.rendering) {
|
|
if (nt.runtime->previews_refresh_state != tree_previews.rendering_previews_refresh_state) {
|
|
tree_previews.restart_needed = true;
|
|
return;
|
|
}
|
|
if (preview_type != tree_previews.rendering_preview_type) {
|
|
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 (preview_type != tree_previews.cached_preview_type) {
|
|
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, wmJobWorkerStatus *worker_status)
|
|
{
|
|
ShaderNodesPreviewJob *job_data = static_cast<ShaderNodesPreviewJob *>(customdata);
|
|
|
|
job_data->stop = &worker_status->stop;
|
|
job_data->do_update = &worker_status->do_update;
|
|
worker_status->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;
|
|
}
|
|
|
|
for (bNode *node_iter : job_data->mat_copy->nodetree->all_nodes()) {
|
|
if (node_iter->flag & NODE_DO_OUTPUT) {
|
|
node_iter->flag &= ~NODE_DO_OUTPUT;
|
|
bNodeSocket *disp_socket = bke::node_find_socket(*node_iter, SOCK_IN, "Displacement");
|
|
if (disp_socket != nullptr && disp_socket->link != nullptr) {
|
|
job_data->mat_displacement_copy = std::make_pair(disp_socket->link->fromnode,
|
|
disp_socket->link->fromsock);
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
|
|
/* Add a new output node used only for the previews. This is useful to keep the previously
|
|
* connected links (for previewing the output nodes for example). */
|
|
job_data->mat_output_copy = bke::node_add_static_node(
|
|
nullptr, *job_data->mat_copy->nodetree, SH_NODE_OUTPUT_MATERIAL);
|
|
job_data->mat_output_copy->flag |= NODE_DO_OUTPUT;
|
|
|
|
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 re-rendered if
|
|
* needed. */
|
|
if (ImBuf **ibuf = job_data->tree_previews->previews_map.lookup_ptr(node->identifier)) {
|
|
IMB_freeImBuf(*ibuf);
|
|
*ibuf = nullptr;
|
|
}
|
|
continue;
|
|
}
|
|
bNodeSocket *preview_socket = node_find_preview_socket(*active_nodetree, *node);
|
|
if (socket_use_aov(preview_socket)) {
|
|
job_data->AOV_nodes.append({node, preview_socket});
|
|
}
|
|
else {
|
|
job_data->shader_nodes.append({node, preview_socket});
|
|
}
|
|
}
|
|
|
|
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;
|
|
job_data->tree_previews->cached_preview_type = job_data->preview_type;
|
|
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;
|
|
ePreviewType preview_type = MA_FLAT;
|
|
if (CTX_wm_space_node(&C)->overlay.preview_shape == SN_OVERLAY_PREVIEW_3D) {
|
|
preview_type = (ePreviewType)material.pr_type;
|
|
}
|
|
update_needed_flag(tree_previews, *displayed_nodetree, preview_type);
|
|
if (!(tree_previews.restart_needed)) {
|
|
return;
|
|
}
|
|
if (tree_previews.rendering) {
|
|
WM_jobs_stop_type(CTX_wm_manager(&C), CTX_wm_space_node(&C), WM_JOB_TYPE_RENDER_PREVIEW);
|
|
return;
|
|
}
|
|
tree_previews.rendering = true;
|
|
tree_previews.restart_needed = false;
|
|
tree_previews.rendering_previews_refresh_state =
|
|
displayed_nodetree->runtime->previews_refresh_state;
|
|
tree_previews.rendering_preview_type = preview_type;
|
|
|
|
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;
|
|
job_data->preview_type = preview_type;
|
|
|
|
/* Update the treepath copied to fit the structure of the nodetree copied. */
|
|
bNodeTreePath *root_path = MEM_callocN<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)
|
|
{
|
|
bNode *parent = bke::node_find_node_by_name(*job_data->treepath_copy.last()->nodetree,
|
|
original_path->node_name);
|
|
if (parent == nullptr) {
|
|
/* In some cases (e.g. muted nodes), there may not be an equivalent node in the copied
|
|
* nodetree. In that case, just skip the node. */
|
|
continue;
|
|
}
|
|
bNodeTreePath *new_path = MEM_callocN<bNodeTreePath>(__func__);
|
|
memcpy(new_path, original_path, sizeof(bNodeTreePath));
|
|
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 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_type(&wm, &snode, WM_JOB_TYPE_RENDER_PREVIEW);
|
|
snode.runtime->tree_previews_per_context.clear();
|
|
}
|
|
|
|
/** \} */
|
|
|
|
} // namespace blender::ed::space_node
|