844 lines
31 KiB
C++
844 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_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_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;
|
|
}
|
|
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. */
|
|
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_builder.push<bke::GroupNodeComputeContext>(*node, *tree);
|
|
}
|
|
return compute_context_builder.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_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)
|
|
{
|
|
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_cnew<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
|