diff --git a/source/blender/blenkernel/BKE_image_save.h b/source/blender/blenkernel/BKE_image_save.h index f6337a82bcd..355c4abbbf5 100644 --- a/source/blender/blenkernel/BKE_image_save.h +++ b/source/blender/blenkernel/BKE_image_save.h @@ -74,12 +74,15 @@ bool BKE_image_render_write_exr(struct ReportList *reports, /** * \param filepath_basis: May be used as-is, or used as a basis for multi-view images. + * \param format: The image format to use for saving, if null, the scene format will be used. */ bool BKE_image_render_write(struct ReportList *reports, struct RenderResult *rr, const struct Scene *scene, const bool stamp, - const char *filepath_basis); + const char *filepath_basis, + const struct ImageFormatData *format = nullptr, + bool save_as_render = true); #ifdef __cplusplus } diff --git a/source/blender/blenkernel/intern/image_save.cc b/source/blender/blenkernel/intern/image_save.cc index b97a17e2434..ed15ca8ef53 100644 --- a/source/blender/blenkernel/intern/image_save.cc +++ b/source/blender/blenkernel/intern/image_save.cc @@ -785,6 +785,7 @@ bool BKE_image_render_write_exr(ReportList *reports, /* Other render layers. */ int nr = (rr->have_combined) ? 1 : 0; + const bool has_multiple_layers = BLI_listbase_count_at_most(&rr->layers, 2) > 1; LISTBASE_FOREACH (RenderLayer *, rl, &rr->layers) { /* Skip other render layers if requested. */ if (!multi_layer && nr != layer) { @@ -830,8 +831,18 @@ bool BKE_image_render_write_exr(ReportList *reports, char layname[EXR_PASS_MAXNAME]; if (multi_layer) { - RE_render_result_full_channel_name(passname, nullptr, rp->name, nullptr, rp->chan_id, a); - STRNCPY(layname, rl->name); + /* A single unnamed layer indicates that the pass name should be used as the layer name, + * while the pass name should be the channel ID. */ + if (!has_multiple_layers && rl->name[0] == '\0') { + passname[0] = rp->chan_id[a]; + passname[1] = '\0'; + STRNCPY(layname, rp->name); + } + else { + RE_render_result_full_channel_name( + passname, nullptr, rp->name, nullptr, rp->chan_id, a); + STRNCPY(layname, rl->name); + } } else { passname[0] = rp->chan_id[a]; @@ -920,7 +931,9 @@ bool BKE_image_render_write(ReportList *reports, RenderResult *rr, const Scene *scene, const bool stamp, - const char *filepath_basis) + const char *filepath_basis, + const ImageFormatData *format, + bool save_as_render) { bool ok = true; @@ -929,7 +942,7 @@ bool BKE_image_render_write(ReportList *reports, } ImageFormatData image_format; - BKE_image_format_init_for_write(&image_format, scene, nullptr); + BKE_image_format_init_for_write(&image_format, scene, format); const bool is_mono = BLI_listbase_count_at_most(&rr->views, 2) < 2; const bool is_exr_rr = ELEM( @@ -938,7 +951,8 @@ bool BKE_image_render_write(ReportList *reports, const float dither = scene->r.dither_intensity; if (image_format.views_format == R_IMF_VIEWS_MULTIVIEW && is_exr_rr) { - ok = BKE_image_render_write_exr(reports, rr, filepath_basis, &image_format, true, nullptr, -1); + ok = BKE_image_render_write_exr( + reports, rr, filepath_basis, &image_format, save_as_render, nullptr, -1); image_render_print_save_message(reports, filepath_basis, ok, errno); } @@ -956,7 +970,8 @@ bool BKE_image_render_write(ReportList *reports, } if (is_exr_rr) { - ok = BKE_image_render_write_exr(reports, rr, filepath, &image_format, true, rv->name, -1); + ok = BKE_image_render_write_exr( + reports, rr, filepath, &image_format, save_as_render, rv->name, -1); image_render_print_save_message(reports, filepath, ok, errno); /* optional preview images for exr */ @@ -971,7 +986,7 @@ bool BKE_image_render_write(ReportList *reports, ImBuf *ibuf = RE_render_result_rect_to_ibuf(rr, &image_format, dither, view_id); ibuf->planes = 24; - IMB_colormanagement_imbuf_for_write(ibuf, true, false, &image_format); + IMB_colormanagement_imbuf_for_write(ibuf, save_as_render, false, &image_format); ok = image_render_write_stamp_test( reports, scene, rr, ibuf, filepath, &image_format, stamp); @@ -982,7 +997,7 @@ bool BKE_image_render_write(ReportList *reports, else { ImBuf *ibuf = RE_render_result_rect_to_ibuf(rr, &image_format, dither, view_id); - IMB_colormanagement_imbuf_for_write(ibuf, true, false, &image_format); + IMB_colormanagement_imbuf_for_write(ibuf, save_as_render, false, &image_format); ok = image_render_write_stamp_test( reports, scene, rr, ibuf, filepath, &image_format, stamp); @@ -1009,7 +1024,7 @@ bool BKE_image_render_write(ReportList *reports, for (i = 0; i < 2; i++) { int view_id = BLI_findstringindex(&rr->views, names[i], offsetof(RenderView, name)); ibuf_arr[i] = RE_render_result_rect_to_ibuf(rr, &image_format, dither, view_id); - IMB_colormanagement_imbuf_for_write(ibuf_arr[i], true, false, &image_format); + IMB_colormanagement_imbuf_for_write(ibuf_arr[i], save_as_render, false, &image_format); } ibuf_arr[2] = IMB_stereo3d_ImBuf(&image_format, ibuf_arr[0], ibuf_arr[1]); diff --git a/source/blender/compositor/CMakeLists.txt b/source/blender/compositor/CMakeLists.txt index 070b27bb6e1..0a726e650a1 100644 --- a/source/blender/compositor/CMakeLists.txt +++ b/source/blender/compositor/CMakeLists.txt @@ -10,6 +10,7 @@ if(WITH_COMPOSITOR_CPU) intern nodes operations + realtime_compositor ../blenkernel ../blentranslation ../imbuf @@ -117,8 +118,6 @@ if(WITH_COMPOSITOR_CPU) nodes/COM_MaskNode.h nodes/COM_MovieClipNode.cc nodes/COM_MovieClipNode.h - nodes/COM_OutputFileNode.cc - nodes/COM_OutputFileNode.h nodes/COM_RenderLayersNode.cc nodes/COM_RenderLayersNode.h nodes/COM_SceneTimeNode.cc @@ -139,6 +138,8 @@ if(WITH_COMPOSITOR_CPU) # output nodes nodes/COM_CompositorNode.cc nodes/COM_CompositorNode.h + nodes/COM_FileOutputNode.cc + nodes/COM_FileOutputNode.h nodes/COM_ViewLevelsNode.cc nodes/COM_ViewLevelsNode.h nodes/COM_ViewerNode.cc @@ -419,10 +420,8 @@ if(WITH_COMPOSITOR_CPU) operations/COM_CompositorOperation.h operations/COM_ConvertDepthToRadiusOperation.cc operations/COM_ConvertDepthToRadiusOperation.h - operations/COM_OutputFileMultiViewOperation.cc - operations/COM_OutputFileMultiViewOperation.h - operations/COM_OutputFileOperation.cc - operations/COM_OutputFileOperation.h + operations/COM_FileOutputOperation.cc + operations/COM_FileOutputOperation.h operations/COM_PreviewOperation.cc operations/COM_PreviewOperation.h operations/COM_SplitOperation.cc @@ -602,6 +601,7 @@ if(WITH_COMPOSITOR_CPU) PRIVATE bf::dna PRIVATE bf::intern::clog PRIVATE bf::intern::guardedalloc + bf_realtime_compositor extern_clew PRIVATE bf::intern::atomic ) diff --git a/source/blender/compositor/COM_compositor.hh b/source/blender/compositor/COM_compositor.hh index 956554463ce..3f4e58e78ef 100644 --- a/source/blender/compositor/COM_compositor.hh +++ b/source/blender/compositor/COM_compositor.hh @@ -7,6 +7,10 @@ #include "DNA_color_types.h" #include "DNA_node_types.h" +namespace blender::realtime_compositor { +class RenderContext; +} + struct Render; /* Keep ascii art. */ @@ -332,7 +336,8 @@ void COM_execute(Render *render, Scene *scene, bNodeTree *node_tree, bool rendering, - const char *view_name); + const char *view_name, + blender::realtime_compositor::RenderContext *render_context); /** * \brief Deinitialize the compositor caches and allocated memory. diff --git a/source/blender/compositor/intern/COM_CompositorContext.h b/source/blender/compositor/intern/COM_CompositorContext.h index 1fc3187d680..dbe3851aede 100644 --- a/source/blender/compositor/intern/COM_CompositorContext.h +++ b/source/blender/compositor/intern/COM_CompositorContext.h @@ -12,6 +12,10 @@ struct bNodeInstanceHash; +namespace blender::realtime_compositor { +class RenderContext; +} + namespace blender::compositor { /** @@ -70,6 +74,12 @@ class CompositorContext { */ const char *view_name_; + /** + * \brief Render context that contains information about active render. Can be null if the + * compositor is not executing as part of the render pipeline. + */ + realtime_compositor::RenderContext *render_context_; + public: /** * \brief constructor initializes the context with default values. @@ -192,6 +202,22 @@ class CompositorContext { return view_name_ && view_name_[0] != '\0'; } + /** + * \brief get the render context + */ + realtime_compositor::RenderContext *get_render_context() const + { + return render_context_; + } + + /** + * \brief set the render context + */ + void set_render_context(realtime_compositor::RenderContext *render_context) + { + render_context_ = render_context; + } + /** * \brief get the active rendering view */ diff --git a/source/blender/compositor/intern/COM_Converter.cc b/source/blender/compositor/intern/COM_Converter.cc index 43d03a475fc..57f03db7e40 100644 --- a/source/blender/compositor/intern/COM_Converter.cc +++ b/source/blender/compositor/intern/COM_Converter.cc @@ -50,6 +50,7 @@ #include "COM_DistanceMatteNode.h" #include "COM_DoubleEdgeMaskNode.h" #include "COM_EllipseMaskNode.h" +#include "COM_FileOutputNode.h" #include "COM_FilterNode.h" #include "COM_FlipNode.h" #include "COM_GammaNode.h" @@ -75,7 +76,6 @@ #include "COM_MovieDistortionNode.h" #include "COM_NormalNode.h" #include "COM_NormalizeNode.h" -#include "COM_OutputFileNode.h" #include "COM_PixelateNode.h" #include "COM_PlaneTrackDeformNode.h" #include "COM_PosterizeNode.h" @@ -347,7 +347,7 @@ Node *COM_convert_bnode(bNode *b_node) node = new ColorSpillNode(b_node); break; case CMP_NODE_OUTPUT_FILE: - node = new OutputFileNode(b_node); + node = new FileOutputNode(b_node); break; case CMP_NODE_MAP_VALUE: node = new MapValueNode(b_node); diff --git a/source/blender/compositor/intern/COM_ExecutionSystem.cc b/source/blender/compositor/intern/COM_ExecutionSystem.cc index d2bd9b52a8a..5740ec26333 100644 --- a/source/blender/compositor/intern/COM_ExecutionSystem.cc +++ b/source/blender/compositor/intern/COM_ExecutionSystem.cc @@ -24,9 +24,11 @@ ExecutionSystem::ExecutionSystem(RenderData *rd, bNodeTree *editingtree, bool rendering, bool fastcalculation, - const char *view_name) + const char *view_name, + realtime_compositor::RenderContext *render_context) { num_work_threads_ = WorkScheduler::get_num_cpu_threads(); + context_.set_render_context(render_context); context_.set_view_name(view_name); context_.set_scene(scene); context_.set_bnodetree(editingtree); diff --git a/source/blender/compositor/intern/COM_ExecutionSystem.h b/source/blender/compositor/intern/COM_ExecutionSystem.h index 074afff2736..4e367a73248 100644 --- a/source/blender/compositor/intern/COM_ExecutionSystem.h +++ b/source/blender/compositor/intern/COM_ExecutionSystem.h @@ -20,6 +20,10 @@ #include "DNA_scene_types.h" #include "DNA_vec_types.h" +namespace blender::realtime_compositor { +class RenderContext; +} + namespace blender::compositor { /** @@ -161,7 +165,8 @@ class ExecutionSystem { bNodeTree *editingtree, bool rendering, bool fastcalculation, - const char *view_name); + const char *view_name, + realtime_compositor::RenderContext *render_context); /** * Destructor diff --git a/source/blender/compositor/intern/COM_MetaData.cc b/source/blender/compositor/intern/COM_MetaData.cc index 4049bf6261a..f885b201f5a 100644 --- a/source/blender/compositor/intern/COM_MetaData.cc +++ b/source/blender/compositor/intern/COM_MetaData.cc @@ -51,6 +51,14 @@ void MetaData::add_to_render_result(RenderResult *render_result) const } } +void MetaData::for_each_entry( + FunctionRef callback) const +{ + for (MapItem entry : entries_.items()) { + callback(entry.key, entry.value); + } +} + void MetaDataExtractCallbackData::add_meta_data(blender::StringRef key, blender::StringRefNull value) { diff --git a/source/blender/compositor/intern/COM_MetaData.h b/source/blender/compositor/intern/COM_MetaData.h index 20a7244dfae..bf4def6ae21 100644 --- a/source/blender/compositor/intern/COM_MetaData.h +++ b/source/blender/compositor/intern/COM_MetaData.h @@ -7,6 +7,7 @@ #include #include "BKE_cryptomatte.hh" +#include "BLI_function_ref.hh" #include "BLI_map.hh" #include "MEM_guardedalloc.h" @@ -44,6 +45,8 @@ class MetaData { */ void replace_hash_neutral_cryptomatte_keys(const blender::StringRef layer_name); void add_to_render_result(RenderResult *render_result) const; + /* Invokes the given callback on each entry of the meta data. */ + void for_each_entry(FunctionRef callback) const; #ifdef WITH_CXX_GUARDEDALLOC MEM_CXX_CLASS_ALLOC_FUNCS("COM:MetaData") #endif diff --git a/source/blender/compositor/intern/COM_compositor.cc b/source/blender/compositor/intern/COM_compositor.cc index 80a3c75e6aa..ea73f8c87d2 100644 --- a/source/blender/compositor/intern/COM_compositor.cc +++ b/source/blender/compositor/intern/COM_compositor.cc @@ -54,7 +54,8 @@ void COM_execute(Render *render, Scene *scene, bNodeTree *node_tree, bool rendering, - const char *view_name) + const char *view_name, + blender::realtime_compositor::RenderContext *render_context) { /* Initialize mutex, TODO: this mutex init is actually not thread safe and * should be done somewhere as part of blender startup, all the other @@ -80,7 +81,8 @@ void COM_execute(Render *render, node_tree->execution_mode == NTREE_EXECUTION_MODE_REALTIME) { /* Realtime GPU compositor. */ - RE_compositor_execute(*render, *scene, *render_data, *node_tree, rendering, view_name); + RE_compositor_execute( + *render, *scene, *render_data, *node_tree, rendering, view_name, render_context); } else { /* Tiled and Full Frame compositors. */ @@ -94,7 +96,7 @@ void COM_execute(Render *render, const bool twopass = (node_tree->flag & NTREE_TWO_PASS) && !rendering; if (twopass) { blender::compositor::ExecutionSystem fast_pass( - render_data, scene, node_tree, rendering, true, view_name); + render_data, scene, node_tree, rendering, true, view_name, render_context); fast_pass.execute(); if (node_tree->runtime->test_break(node_tree->runtime->tbh)) { @@ -104,7 +106,7 @@ void COM_execute(Render *render, } blender::compositor::ExecutionSystem system( - render_data, scene, node_tree, rendering, false, view_name); + render_data, scene, node_tree, rendering, false, view_name, render_context); system.execute(); } diff --git a/source/blender/compositor/nodes/COM_FileOutputNode.cc b/source/blender/compositor/nodes/COM_FileOutputNode.cc new file mode 100644 index 00000000000..df921b9bf7f --- /dev/null +++ b/source/blender/compositor/nodes/COM_FileOutputNode.cc @@ -0,0 +1,45 @@ +/* SPDX-FileCopyrightText: 2011 Blender Authors + * + * SPDX-License-Identifier: GPL-2.0-or-later */ + +#include "COM_FileOutputNode.h" + +#include "BLI_string.h" + +namespace blender::compositor { + +FileOutputNode::FileOutputNode(bNode *editor_node) : Node(editor_node) +{ + /* pass */ +} + +void FileOutputNode::convert_to_operations(NodeConverter &converter, + const CompositorContext &context) const +{ + for (NodeInput *input : inputs_) { + if (input->is_linked()) { + converter.add_node_input_preview(input); + break; + } + } + + if (!context.is_rendering()) { + return; + } + + Vector inputs; + for (NodeInput *input : inputs_) { + auto *storage = static_cast(input->get_bnode_socket()->storage); + inputs.append(FileOutputInput(storage, input->get_data_type())); + } + + auto *storage = static_cast(this->get_bnode()->storage); + auto *output_operation = new FileOutputOperation(&context, storage, inputs); + converter.add_operation(output_operation); + + for (int i = 0; i < inputs_.size(); i++) { + converter.map_input_socket(inputs_[i], output_operation->get_input_socket(i)); + } +} + +} // namespace blender::compositor diff --git a/source/blender/compositor/nodes/COM_FileOutputNode.h b/source/blender/compositor/nodes/COM_FileOutputNode.h new file mode 100644 index 00000000000..4dd991b24d2 --- /dev/null +++ b/source/blender/compositor/nodes/COM_FileOutputNode.h @@ -0,0 +1,26 @@ +/* SPDX-FileCopyrightText: 2011 Blender Authors + * + * SPDX-License-Identifier: GPL-2.0-or-later */ + +#pragma once + +#include "COM_Node.h" + +#include "COM_FileOutputOperation.h" + +#include "DNA_node_types.h" + +namespace blender::compositor { + +/** + * \brief FileOutputNode + * \ingroup Node + */ +class FileOutputNode : public Node { + public: + FileOutputNode(bNode *editor_node); + void convert_to_operations(NodeConverter &converter, + const CompositorContext &context) const override; +}; + +} // namespace blender::compositor diff --git a/source/blender/compositor/nodes/COM_OutputFileNode.cc b/source/blender/compositor/nodes/COM_OutputFileNode.cc deleted file mode 100644 index e2d82ca7d60..00000000000 --- a/source/blender/compositor/nodes/COM_OutputFileNode.cc +++ /dev/null @@ -1,160 +0,0 @@ -/* SPDX-FileCopyrightText: 2011 Blender Authors - * - * SPDX-License-Identifier: GPL-2.0-or-later */ - -#include "COM_OutputFileNode.h" - -#include "BLI_string.h" - -namespace blender::compositor { - -OutputFileNode::OutputFileNode(bNode *editor_node) : Node(editor_node) -{ - /* pass */ -} - -void OutputFileNode::add_input_sockets(OutputOpenExrMultiLayerOperation &operation) const -{ - for (NodeInput *input : inputs_) { - NodeImageMultiFileSocket *sockdata = - (NodeImageMultiFileSocket *)input->get_bnode_socket()->storage; - /* NOTE: layer becomes an empty placeholder if the input is not linked. */ - operation.add_layer(sockdata->layer, input->get_data_type(), input->is_linked()); - } -} - -void OutputFileNode::map_input_sockets(NodeConverter &converter, - OutputOpenExrMultiLayerOperation &operation) const -{ - bool preview_added = false; - int index = 0; - for (NodeInput *input : inputs_) { - converter.map_input_socket(input, operation.get_input_socket(index++)); - - if (!preview_added) { - converter.add_node_input_preview(input); - preview_added = true; - } - } -} - -void OutputFileNode::add_preview_to_first_linked_input(NodeConverter &converter) const -{ - if (get_input_sockets().is_empty()) { - return; - } - - NodeInput *first_socket = this->get_input_socket(0); - if (first_socket->is_linked()) { - converter.add_node_input_preview(first_socket); - } -} - -void OutputFileNode::convert_to_operations(NodeConverter &converter, - const CompositorContext &context) const -{ - const NodeImageMultiFile *storage = (const NodeImageMultiFile *)this->get_bnode()->storage; - const bool is_multiview = (context.get_render_data()->scemode & R_MULTIVIEW) != 0; - - add_preview_to_first_linked_input(converter); - - if (!context.is_rendering()) { - /* only output files when rendering a sequence - - * otherwise, it overwrites the output files just - * scrubbing through the timeline when the compositor updates. - */ - return; - } - - if (storage->format.imtype == R_IMF_IMTYPE_MULTILAYER) { - const bool use_half_float = (storage->format.depth == R_IMF_CHAN_DEPTH_16); - /* Single output operation for the multi-layer file. */ - OutputOpenExrMultiLayerOperation *output_operation; - - if (is_multiview && storage->format.views_format == R_IMF_VIEWS_MULTIVIEW) { - output_operation = new OutputOpenExrMultiLayerMultiViewOperation(context.get_scene(), - context.get_render_data(), - context.get_bnodetree(), - storage->base_path, - storage->format.exr_codec, - use_half_float, - context.get_view_name()); - } - else { - output_operation = new OutputOpenExrMultiLayerOperation(context.get_scene(), - context.get_render_data(), - context.get_bnodetree(), - storage->base_path, - storage->format.exr_codec, - use_half_float, - context.get_view_name()); - } - converter.add_operation(output_operation); - - /* First add all inputs. Inputs are stored in a Vector and can be moved to a different - * memory address during this time. */ - add_input_sockets(*output_operation); - /* After adding the sockets the memory addresses will stick. */ - map_input_sockets(converter, *output_operation); - } - else { /* single layer format */ - for (NodeInput *input : inputs_) { - if (input->is_linked()) { - NodeImageMultiFileSocket *sockdata = - (NodeImageMultiFileSocket *)input->get_bnode_socket()->storage; - const ImageFormatData *format = (sockdata->use_node_format ? &storage->format : - &sockdata->format); - char path[FILE_MAX]; - - /* combine file path for the input */ - if (sockdata->path[0]) { - BLI_path_join(path, FILE_MAX, storage->base_path, sockdata->path); - } - else { - STRNCPY(path, storage->base_path); - BLI_path_slash_ensure(path, FILE_MAX); - } - - NodeOperation *output_operation = nullptr; - - if (is_multiview && format->views_format == R_IMF_VIEWS_MULTIVIEW) { - output_operation = new OutputOpenExrSingleLayerMultiViewOperation( - context.get_scene(), - context.get_render_data(), - context.get_bnodetree(), - input->get_data_type(), - format, - path, - context.get_view_name(), - sockdata->save_as_render); - } - else if ((!is_multiview) || (format->views_format == R_IMF_VIEWS_INDIVIDUAL)) { - output_operation = new OutputSingleLayerOperation(context.get_scene(), - context.get_render_data(), - context.get_bnodetree(), - input->get_data_type(), - format, - path, - context.get_view_name(), - sockdata->save_as_render); - } - else { /* R_IMF_VIEWS_STEREO_3D */ - output_operation = new OutputStereoOperation(context.get_scene(), - context.get_render_data(), - context.get_bnodetree(), - input->get_data_type(), - format, - path, - sockdata->layer, - context.get_view_name(), - sockdata->save_as_render); - } - - converter.add_operation(output_operation); - converter.map_input_socket(input, output_operation->get_input_socket(0)); - } - } - } -} - -} // namespace blender::compositor diff --git a/source/blender/compositor/nodes/COM_OutputFileNode.h b/source/blender/compositor/nodes/COM_OutputFileNode.h deleted file mode 100644 index 6ada74f3057..00000000000 --- a/source/blender/compositor/nodes/COM_OutputFileNode.h +++ /dev/null @@ -1,32 +0,0 @@ -/* SPDX-FileCopyrightText: 2011 Blender Authors - * - * SPDX-License-Identifier: GPL-2.0-or-later */ - -#pragma once - -#include "COM_Node.h" - -#include "COM_OutputFileMultiViewOperation.h" - -#include "DNA_node_types.h" - -namespace blender::compositor { - -/** - * \brief OutputFileNode - * \ingroup Node - */ -class OutputFileNode : public Node { - public: - OutputFileNode(bNode *editor_node); - void convert_to_operations(NodeConverter &converter, - const CompositorContext &context) const override; - - private: - void add_preview_to_first_linked_input(NodeConverter &converter) const; - void add_input_sockets(OutputOpenExrMultiLayerOperation &operation) const; - void map_input_sockets(NodeConverter &converter, - OutputOpenExrMultiLayerOperation &operation) const; -}; - -} // namespace blender::compositor diff --git a/source/blender/compositor/operations/COM_FileOutputOperation.cc b/source/blender/compositor/operations/COM_FileOutputOperation.cc new file mode 100644 index 00000000000..8d894bac6d4 --- /dev/null +++ b/source/blender/compositor/operations/COM_FileOutputOperation.cc @@ -0,0 +1,382 @@ +/* SPDX-FileCopyrightText: 2011 Blender Authors + * + * SPDX-License-Identifier: GPL-2.0-or-later */ + +#include + +#include "BLI_fileops.h" +#include "BLI_path_util.h" +#include "BLI_string.h" +#include "BLI_string_utils.hh" +#include "BLI_utildefines.h" + +#include "DNA_node_types.h" +#include "DNA_scene_types.h" + +#include "BKE_image.h" +#include "BKE_image_format.h" +#include "BKE_main.hh" +#include "BKE_scene.h" + +#include "RE_pipeline.h" + +#include "COM_FileOutputOperation.h" +#include "COM_render_context.hh" + +namespace blender::compositor { + +FileOutputInput::FileOutputInput(NodeImageMultiFileSocket *data, DataType data_type) + : data(data), data_type(data_type) +{ +} + +static int get_channels_count(DataType datatype) +{ + switch (datatype) { + case DataType::Value: + return 1; + case DataType::Vector: + return 3; + case DataType::Color: + return 4; + default: + return 0; + } +} + +static float *initialize_buffer(uint width, uint height, DataType datatype) +{ + const int size = get_channels_count(datatype); + return static_cast( + MEM_malloc_arrayN(size_t(width) * height, sizeof(float) * size, "File Output Buffer.")); +} + +static void write_buffer_rect( + rcti *rect, SocketReader *reader, float *buffer, uint width, DataType datatype) +{ + + if (!buffer) { + return; + } + int x1 = rect->xmin; + int y1 = rect->ymin; + int x2 = rect->xmax; + int y2 = rect->ymax; + + int size = get_channels_count(datatype); + int offset = (y1 * width + x1) * size; + for (int y = y1; y < y2; y++) { + for (int x = x1; x < x2; x++) { + float color[4]; + reader->read_sampled(color, x, y, PixelSampler::Nearest); + + for (int i = 0; i < size; i++) { + buffer[offset + i] = color[i]; + } + offset += size; + } + offset += (width - (x2 - x1)) * size; + } +} + +FileOutputOperation::FileOutputOperation(const CompositorContext *context, + const NodeImageMultiFile *node_data, + Vector inputs) + : context_(context), node_data_(node_data), file_output_inputs_(inputs) +{ + for (const FileOutputInput &input : inputs) { + add_input_socket(input.data_type); + } + this->set_canvas_input_index(RESOLUTION_INPUT_ANY); +} + +void FileOutputOperation::init_execution() +{ + for (int i = 0; i < file_output_inputs_.size(); i++) { + FileOutputInput &input = file_output_inputs_[i]; + input.image_input = get_input_socket_reader(i); + if (!input.image_input) { + continue; + } + input.output_buffer = initialize_buffer(get_width(), get_height(), input.data_type); + } +} + +void FileOutputOperation::execute_region(rcti *rect, uint /*tile_number*/) +{ + for (int i = 0; i < file_output_inputs_.size(); i++) { + const FileOutputInput &input = file_output_inputs_[i]; + if (!input.image_input || !input.output_buffer) { + continue; + } + write_buffer_rect(rect, input.image_input, input.output_buffer, get_width(), input.data_type); + } +} + +void FileOutputOperation::update_memory_buffer_partial(MemoryBuffer * /*output*/, + const rcti &area, + Span inputs) +{ + for (int i = 0; i < file_output_inputs_.size(); i++) { + const FileOutputInput &input = file_output_inputs_[i]; + if (!input.output_buffer) { + continue; + } + int channels_count = get_channels_count(input.data_type); + MemoryBuffer output_buf(input.output_buffer, channels_count, get_width(), get_height()); + output_buf.copy_from(inputs[i], area, 0, inputs[i]->get_num_channels(), 0); + } +} + +static void add_meta_data_for_input(realtime_compositor::FileOutput &file_output, + const FileOutputInput &input) +{ + std::unique_ptr meta_data = input.image_input->get_meta_data(); + if (!meta_data) { + return; + } + + blender::StringRef layer_name = blender::bke::cryptomatte::BKE_cryptomatte_extract_layer_name( + blender::StringRef(input.data->layer, + BLI_strnlen(input.data->layer, sizeof(input.data->layer)))); + meta_data->replace_hash_neutral_cryptomatte_keys(layer_name); + meta_data->for_each_entry([&](const std::string &key, const std::string &value) { + file_output.add_meta_data(key, value); + }); +} + +void FileOutputOperation::deinit_execution() +{ + if (is_multi_layer()) { + execute_multi_layer(); + } + else { + execute_single_layer(); + } +} + +/* -------------------- + * Single Layer Images. + */ + +void FileOutputOperation::execute_single_layer() +{ + const int2 size = int2(get_width(), get_height()); + for (const FileOutputInput &input : file_output_inputs_) { + /* Unlinked input. */ + if (!input.image_input) { + continue; + } + + char base_path[FILE_MAX]; + get_single_layer_image_base_path(input.data->path, base_path); + + /* The image saving code expects EXR images to have a different structure than standard + * images. In particular, in EXR images, the buffers need to be stored in passes that are, in + * turn, stored in a render layer. On the other hand, in non-EXR images, the buffers need to + * be stored in views. An exception to this is stereo images, which needs to have the same + * structure as non-EXR images. */ + const auto &format = input.data->use_node_format ? node_data_->format : input.data->format; + const bool is_exr = format.imtype == R_IMF_IMTYPE_OPENEXR; + const int views_count = BKE_scene_multiview_num_views_get(context_->get_render_data()); + if (is_exr && !(format.views_format == R_IMF_VIEWS_STEREO_3D && views_count == 2)) { + execute_single_layer_multi_view_exr(input, format, base_path); + continue; + } + + char image_path[FILE_MAX]; + get_single_layer_image_path(base_path, format, image_path); + + realtime_compositor::FileOutput &file_output = context_->get_render_context()->get_file_output( + image_path, format, size, input.data->save_as_render); + + add_view_for_input(file_output, input, context_->get_view_name()); + + add_meta_data_for_input(file_output, input); + } +} + +/* ----------------------------------- + * Single Layer Multi-View EXR Images. + */ + +void FileOutputOperation::execute_single_layer_multi_view_exr(const FileOutputInput &input, + const ImageFormatData &format, + const char *base_path) +{ + const bool has_views = format.views_format != R_IMF_VIEWS_INDIVIDUAL; + + /* The EXR stores all views in the same file, so we supply an empty view to make sure the file + * name does not contain a view suffix. */ + char image_path[FILE_MAX]; + const char *path_view = has_views ? "" : context_->get_view_name(); + get_multi_layer_exr_image_path(base_path, path_view, image_path); + + const int2 size = int2(get_width(), get_height()); + realtime_compositor::FileOutput &file_output = context_->get_render_context()->get_file_output( + image_path, format, size, false); + + /* The EXR stores all views in the same file, so we add the actual render view. Otherwise, we + * add a default unnamed view. */ + const char *view_name = has_views ? context_->get_view_name() : ""; + file_output.add_view(view_name); + add_pass_for_input(file_output, input, "", view_name); + + add_meta_data_for_input(file_output, input); +} + +/* ----------------------- + * Multi-Layer EXR Images. + */ + +void FileOutputOperation::execute_multi_layer() +{ + const bool store_views_in_single_file = is_multi_view_exr(); + const char *view = context_->get_view_name(); + + /* If we are saving all views in a single multi-layer file, we supply an empty view to make + * sure the file name does not contain a view suffix. */ + char image_path[FILE_MAX]; + const char *write_view = store_views_in_single_file ? "" : view; + get_multi_layer_exr_image_path(get_base_path(), write_view, image_path); + + const int2 size = int2(get_width(), get_height()); + const ImageFormatData format = node_data_->format; + realtime_compositor::FileOutput &file_output = context_->get_render_context()->get_file_output( + image_path, format, size, false); + + /* If we are saving views in separate files, we needn't store the view in the channel names, so + * we add an unnamed view. */ + const char *pass_view = store_views_in_single_file ? view : ""; + file_output.add_view(pass_view); + + for (const FileOutputInput &input : file_output_inputs_) { + /* Unlinked input. */ + if (!input.image_input) { + continue; + } + + const char *pass_name = input.data->layer; + add_pass_for_input(file_output, input, pass_name, pass_view); + + add_meta_data_for_input(file_output, input); + } +} + +/* Add a pass of the given name, view, and input buffer. The pass channel identifiers follows the + * EXR conventions. */ +void FileOutputOperation::add_pass_for_input(realtime_compositor::FileOutput &file_output, + const FileOutputInput &input, + const char *pass_name, + const char *view_name) +{ + switch (input.data_type) { + case DataType::Color: + file_output.add_pass(pass_name, view_name, "RGBA", input.output_buffer); + break; + case DataType::Vector: + file_output.add_pass(pass_name, view_name, "XYZ", input.output_buffer); + break; + case DataType::Value: + file_output.add_pass(pass_name, view_name, "V", input.output_buffer); + break; + } +} + +/* Add a view of the given name and input buffer. */ +void FileOutputOperation::add_view_for_input(realtime_compositor::FileOutput &file_output, + const FileOutputInput &input, + const char *view_name) +{ + switch (input.data_type) { + case DataType::Color: + file_output.add_view(view_name, 4, input.output_buffer); + break; + case DataType::Vector: + file_output.add_view(view_name, 3, input.output_buffer); + break; + case DataType::Value: + file_output.add_view(view_name, 1, input.output_buffer); + break; + } +} + +/* Get the base path of the image to be saved, based on the base path of the node. The base name + * is an optional initial name of the image, which will later be concatenated with other + * information like the frame number, view, and extension. If the base name is empty, then the + * base path represents a directory, so a trailing slash is ensured. */ +void FileOutputOperation::get_single_layer_image_base_path(const char *base_name, char *base_path) +{ + if (base_name[0]) { + BLI_path_join(base_path, FILE_MAX, get_base_path(), base_name); + } + else { + BLI_strncpy(base_path, get_base_path(), FILE_MAX); + BLI_path_slash_ensure(base_path, FILE_MAX); + } +} + +/* Get the path of the image to be saved based on the given format. */ +void FileOutputOperation::get_single_layer_image_path(const char *base_path, + const ImageFormatData &format, + char *image_path) +{ + BKE_image_path_from_imformat(image_path, + base_path, + BKE_main_blendfile_path_from_global(), + context_->get_framenumber(), + &format, + use_file_extension(), + true, + nullptr); +} + +/* Get the path of the EXR image to be saved. If the given view is not empty, its corresponding + * file suffix will be appended to the name. */ +void FileOutputOperation::get_multi_layer_exr_image_path(const char *base_path, + const char *view, + char *image_path) +{ + const char *suffix = BKE_scene_multiview_view_suffix_get(context_->get_render_data(), view); + BKE_image_path_from_imtype(image_path, + base_path, + BKE_main_blendfile_path_from_global(), + context_->get_framenumber(), + R_IMF_IMTYPE_MULTILAYER, + use_file_extension(), + true, + suffix); +} + +bool FileOutputOperation::is_multi_layer() +{ + return node_data_->format.imtype == R_IMF_IMTYPE_MULTILAYER; +} + +const char *FileOutputOperation::get_base_path() +{ + return node_data_->base_path; +} + +/* Add the file format extensions to the rendered file name. */ +bool FileOutputOperation::use_file_extension() +{ + return context_->get_render_data()->scemode & R_EXTENSION; +} + +/* If true, save views in a multi-view EXR file, otherwise, save each view in its own file. */ +bool FileOutputOperation::is_multi_view_exr() +{ + if (!is_multi_view_scene()) { + return false; + } + + return node_data_->format.views_format == R_IMF_VIEWS_MULTIVIEW; +} + +bool FileOutputOperation::is_multi_view_scene() +{ + return context_->get_render_data()->scemode & R_MULTIVIEW; +} + +} // namespace blender::compositor diff --git a/source/blender/compositor/operations/COM_FileOutputOperation.h b/source/blender/compositor/operations/COM_FileOutputOperation.h new file mode 100644 index 00000000000..61d9ef0ae94 --- /dev/null +++ b/source/blender/compositor/operations/COM_FileOutputOperation.h @@ -0,0 +1,106 @@ +/* SPDX-FileCopyrightText: 2011 Blender Authors + * + * SPDX-License-Identifier: GPL-2.0-or-later */ + +#pragma once + +#include "BLI_vector.hh" + +#include "DNA_node_types.h" + +#include "COM_CompositorContext.h" +#include "COM_MultiThreadedOperation.h" + +struct StampData; + +namespace blender::realtime_compositor { +class FileOutput; +} + +namespace blender::compositor { + +struct FileOutputInput { + FileOutputInput(NodeImageMultiFileSocket *data, DataType data_type); + + NodeImageMultiFileSocket *data; + DataType data_type; + + float *output_buffer = nullptr; + SocketReader *image_input = nullptr; +}; + +class FileOutputOperation : public MultiThreadedOperation { + private: + const CompositorContext *context_; + const NodeImageMultiFile *node_data_; + Vector file_output_inputs_; + + public: + FileOutputOperation(const CompositorContext *context, + const NodeImageMultiFile *node_data, + Vector inputs); + + void execute_region(rcti *rect, unsigned int tile_number) override; + bool is_output_operation(bool /*rendering*/) const override + { + return true; + } + void init_execution() override; + void deinit_execution() override; + eCompositorPriority get_render_priority() const override + { + return eCompositorPriority::Low; + } + + void update_memory_buffer_partial(MemoryBuffer *output, + const rcti &area, + Span inputs) override; + + private: + void execute_single_layer(); + void execute_single_layer_multi_view_exr(const FileOutputInput &input, + const ImageFormatData &format, + const char *base_path); + void execute_multi_layer(); + + /* Add a pass of the given name, view, and input buffer. The pass channel identifiers follows the + * EXR conventions. */ + void add_pass_for_input(realtime_compositor::FileOutput &file_output, + const FileOutputInput &input, + const char *pass_name, + const char *view_name); + + /* Add a view of the given name and input buffer. */ + void add_view_for_input(realtime_compositor::FileOutput &file_output, + const FileOutputInput &input, + const char *view_name); + + /* Get the base path of the image to be saved, based on the base path of the node. The base name + * is an optional initial name of the image, which will later be concatenated with other + * information like the frame number, view, and extension. If the base name is empty, then the + * base path represents a directory, so a trailing slash is ensured. */ + void get_single_layer_image_base_path(const char *base_name, char *base_path); + + /* Get the path of the image to be saved based on the given format. */ + void get_single_layer_image_path(const char *base_path, + const ImageFormatData &format, + char *image_path); + + /* Get the path of the EXR image to be saved. If the given view is not empty, its corresponding + * file suffix will be appended to the name. */ + void get_multi_layer_exr_image_path(const char *base_path, const char *view, char *image_path); + + bool is_multi_layer(); + + const char *get_base_path(); + + /* Add the file format extensions to the rendered file name. */ + bool use_file_extension(); + + /* If true, save views in a multi-view EXR file, otherwise, save each view in its own file. */ + bool is_multi_view_exr(); + + bool is_multi_view_scene(); +}; + +} // namespace blender::compositor diff --git a/source/blender/compositor/operations/COM_OutputFileMultiViewOperation.cc b/source/blender/compositor/operations/COM_OutputFileMultiViewOperation.cc deleted file mode 100644 index 3dbdab3e942..00000000000 --- a/source/blender/compositor/operations/COM_OutputFileMultiViewOperation.cc +++ /dev/null @@ -1,353 +0,0 @@ -/* SPDX-FileCopyrightText: 2015 Blender Authors - * - * SPDX-License-Identifier: GPL-2.0-or-later */ - -#include "COM_OutputFileMultiViewOperation.h" - -#include "BLI_fileops.h" -#include "BLI_string.h" - -#include "BKE_image.h" -#include "BKE_image_format.h" -#include "BKE_main.hh" -#include "BKE_scene.h" - -#include "IMB_colormanagement.h" -#include "IMB_imbuf.h" -#include "IMB_imbuf_types.h" - -namespace blender::compositor { - -/************************************ OpenEXR Singlelayer Multiview ******************************/ - -OutputOpenExrSingleLayerMultiViewOperation::OutputOpenExrSingleLayerMultiViewOperation( - const Scene *scene, - const RenderData *rd, - const bNodeTree *tree, - DataType datatype, - const ImageFormatData *format, - const char *path, - const char *view_name, - const bool save_as_render) - : OutputSingleLayerOperation( - scene, rd, tree, datatype, format, path, view_name, save_as_render) -{ -} - -void *OutputOpenExrSingleLayerMultiViewOperation::get_handle(const char *filepath) -{ - size_t width = this->get_width(); - size_t height = this->get_height(); - - if (width != 0 && height != 0) { - void *exrhandle; - - exrhandle = IMB_exr_get_handle_name(filepath); - - if (!BKE_scene_multiview_is_render_view_first(rd_, view_name_)) { - return exrhandle; - } - - IMB_exr_clear_channels(exrhandle); - - LISTBASE_FOREACH (SceneRenderView *, srv, &rd_->views) { - if (BKE_scene_multiview_is_render_view_active(rd_, srv) == false) { - continue; - } - - IMB_exr_add_view(exrhandle, srv->name); - add_exr_channels(exrhandle, nullptr, datatype_, srv->name, width, false, nullptr); - } - - BLI_file_ensure_parent_dir_exists(filepath); - - /* prepare the file with all the channels */ - - if (!IMB_exr_begin_write(exrhandle, filepath, width, height, format_.exr_codec, nullptr)) { - printf("Error Writing Singlelayer Multiview Openexr\n"); - IMB_exr_close(exrhandle); - } - else { - IMB_exr_clear_channels(exrhandle); - return exrhandle; - } - } - return nullptr; -} - -void OutputOpenExrSingleLayerMultiViewOperation::deinit_execution() -{ - uint width = this->get_width(); - uint height = this->get_height(); - - if (width != 0 && height != 0) { - void *exrhandle; - char filepath[FILE_MAX]; - - BKE_image_path_from_imtype(filepath, - path_, - BKE_main_blendfile_path_from_global(), - rd_->cfra, - R_IMF_IMTYPE_OPENEXR, - (rd_->scemode & R_EXTENSION) != 0, - true, - nullptr); - - exrhandle = this->get_handle(filepath); - add_exr_channels(exrhandle, - nullptr, - datatype_, - view_name_, - width, - format_.depth == R_IMF_CHAN_DEPTH_16, - output_buffer_); - - /* memory can only be freed after we write all views to the file */ - output_buffer_ = nullptr; - image_input_ = nullptr; - - /* ready to close the file */ - if (BKE_scene_multiview_is_render_view_last(rd_, view_name_)) { - IMB_exr_write_channels(exrhandle); - - /* free buffer memory for all the views */ - free_exr_channels(exrhandle, rd_, nullptr, datatype_); - - /* remove exr handle and data */ - IMB_exr_close(exrhandle); - } - } -} - -/************************************ OpenEXR Multilayer Multiview *******************************/ - -OutputOpenExrMultiLayerMultiViewOperation::OutputOpenExrMultiLayerMultiViewOperation( - const Scene *scene, - const RenderData *rd, - const bNodeTree *tree, - const char *path, - char exr_codec, - bool exr_half_float, - const char *view_name) - : OutputOpenExrMultiLayerOperation(scene, rd, tree, path, exr_codec, exr_half_float, view_name) -{ -} - -void *OutputOpenExrMultiLayerMultiViewOperation::get_handle(const char *filepath) -{ - uint width = this->get_width(); - uint height = this->get_height(); - - if (width != 0 && height != 0) { - /* Get a new global handle. */ - void *exrhandle = IMB_exr_get_handle_name(filepath); - - if (!BKE_scene_multiview_is_render_view_first(rd_, view_name_)) { - return exrhandle; - } - - IMB_exr_clear_channels(exrhandle); - - /* check renderdata for amount of views */ - LISTBASE_FOREACH (SceneRenderView *, srv, &rd_->views) { - - if (BKE_scene_multiview_is_render_view_active(rd_, srv) == false) { - continue; - } - - IMB_exr_add_view(exrhandle, srv->name); - - for (uint i = 0; i < layers_.size(); i++) { - add_exr_channels(exrhandle, - layers_[i].name, - layers_[i].datatype, - srv->name, - width, - exr_half_float_, - nullptr); - } - } - - BLI_file_ensure_parent_dir_exists(filepath); - - /* prepare the file with all the channels for the header */ - StampData *stamp_data = create_stamp_data(); - if (!IMB_exr_begin_write(exrhandle, filepath, width, height, exr_codec_, stamp_data)) { - printf("Error Writing Multilayer Multiview Openexr\n"); - IMB_exr_close(exrhandle); - BKE_stamp_data_free(stamp_data); - } - else { - IMB_exr_clear_channels(exrhandle); - BKE_stamp_data_free(stamp_data); - return exrhandle; - } - } - return nullptr; -} - -void OutputOpenExrMultiLayerMultiViewOperation::deinit_execution() -{ - uint width = this->get_width(); - uint height = this->get_height(); - - if (width != 0 && height != 0) { - void *exrhandle; - char filepath[FILE_MAX]; - - BKE_image_path_from_imtype(filepath, - path_, - BKE_main_blendfile_path_from_global(), - rd_->cfra, - R_IMF_IMTYPE_MULTILAYER, - (rd_->scemode & R_EXTENSION) != 0, - true, - nullptr); - - exrhandle = this->get_handle(filepath); - - for (uint i = 0; i < layers_.size(); i++) { - add_exr_channels(exrhandle, - layers_[i].name, - layers_[i].datatype, - view_name_, - width, - exr_half_float_, - layers_[i].output_buffer); - } - - for (uint i = 0; i < layers_.size(); i++) { - /* memory can only be freed after we write all views to the file */ - layers_[i].output_buffer = nullptr; - layers_[i].image_input = nullptr; - } - - /* ready to close the file */ - if (BKE_scene_multiview_is_render_view_last(rd_, view_name_)) { - IMB_exr_write_channels(exrhandle); - - /* free buffer memory for all the views */ - for (uint i = 0; i < layers_.size(); i++) { - free_exr_channels(exrhandle, rd_, layers_[i].name, layers_[i].datatype); - } - - IMB_exr_close(exrhandle); - } - } -} - -/******************************** Stereo3D ******************************/ - -OutputStereoOperation::OutputStereoOperation(const Scene *scene, - const RenderData *rd, - const bNodeTree *tree, - DataType datatype, - const ImageFormatData *format, - const char *path, - const char *pass_name, - const char *view_name, - const bool save_as_render) - : OutputSingleLayerOperation( - scene, rd, tree, datatype, format, path, view_name, save_as_render) -{ - STRNCPY(pass_name_, pass_name); - channels_ = get_datatype_size(datatype); -} - -void *OutputStereoOperation::get_handle(const char *filepath) -{ - size_t width = this->get_width(); - size_t height = this->get_height(); - const char *names[2] = {STEREO_LEFT_NAME, STEREO_RIGHT_NAME}; - size_t i; - - if (width != 0 && height != 0) { - void *exrhandle; - - exrhandle = IMB_exr_get_handle_name(filepath); - - if (!BKE_scene_multiview_is_render_view_first(rd_, view_name_)) { - return exrhandle; - } - - IMB_exr_clear_channels(exrhandle); - - for (i = 0; i < 2; i++) { - IMB_exr_add_view(exrhandle, names[i]); - } - - return exrhandle; - } - return nullptr; -} - -void OutputStereoOperation::deinit_execution() -{ - uint width = this->get_width(); - uint height = this->get_height(); - - if (width != 0 && height != 0) { - void *exrhandle; - - exrhandle = this->get_handle(path_); - float *buf = output_buffer_; - - /* populate single EXR channel with view data */ - IMB_exr_add_channel(exrhandle, - nullptr, - pass_name_, - view_name_, - 1, - channels_ * width * height, - buf, - format_.depth == R_IMF_CHAN_DEPTH_16); - - image_input_ = nullptr; - output_buffer_ = nullptr; - - /* create stereo ibuf */ - if (BKE_scene_multiview_is_render_view_last(rd_, view_name_)) { - ImBuf *ibuf[3] = {nullptr}; - const char *names[2] = {STEREO_LEFT_NAME, STEREO_RIGHT_NAME}; - char filepath[FILE_MAX]; - int i; - - /* get rectf from EXR */ - for (i = 0; i < 2; i++) { - float *rectf = IMB_exr_channel_rect(exrhandle, nullptr, pass_name_, names[i]); - ibuf[i] = IMB_allocImBuf(width, height, format_.planes, 0); - - ibuf[i]->channels = channels_; - ibuf[i]->dither = rd_->dither_intensity; - - IMB_assign_float_buffer(ibuf[i], rectf, IB_TAKE_OWNERSHIP); - - /* do colormanagement in the individual views, so it doesn't need to do in the stereo */ - IMB_colormanagement_imbuf_for_write(ibuf[i], true, false, &format_); - } - - /* create stereo buffer */ - ibuf[2] = IMB_stereo3d_ImBuf(&format_, ibuf[0], ibuf[1]); - - BKE_image_path_from_imformat(filepath, - path_, - BKE_main_blendfile_path_from_global(), - rd_->cfra, - &format_, - (rd_->scemode & R_EXTENSION) != 0, - true, - nullptr); - - BKE_imbuf_write(ibuf[2], filepath, &format_); - - /* imbuf knows which rects are not part of ibuf */ - for (i = 0; i < 3; i++) { - IMB_freeImBuf(ibuf[i]); - } - - IMB_exr_close(exrhandle); - } - } -} - -} // namespace blender::compositor diff --git a/source/blender/compositor/operations/COM_OutputFileMultiViewOperation.h b/source/blender/compositor/operations/COM_OutputFileMultiViewOperation.h deleted file mode 100644 index 7084da00b7b..00000000000 --- a/source/blender/compositor/operations/COM_OutputFileMultiViewOperation.h +++ /dev/null @@ -1,71 +0,0 @@ -/* SPDX-FileCopyrightText: 2015 Blender Authors - * - * SPDX-License-Identifier: GPL-2.0-or-later */ - -#pragma once - -#include "COM_NodeOperation.h" -#include "COM_OutputFileOperation.h" - -#include "BLI_path_util.h" -#include "BLI_rect.h" - -#include "DNA_color_types.h" - -#include "IMB_openexr.h" - -namespace blender::compositor { - -class OutputOpenExrSingleLayerMultiViewOperation : public OutputSingleLayerOperation { - private: - public: - OutputOpenExrSingleLayerMultiViewOperation(const Scene *scene, - const RenderData *rd, - const bNodeTree *tree, - DataType datatype, - const ImageFormatData *format, - const char *path, - const char *view_name, - bool save_as_render); - - void *get_handle(const char *filepath); - void deinit_execution() override; -}; - -/** Writes inputs into OpenEXR multi-layer channels. */ -class OutputOpenExrMultiLayerMultiViewOperation : public OutputOpenExrMultiLayerOperation { - private: - public: - OutputOpenExrMultiLayerMultiViewOperation(const Scene *scene, - const RenderData *rd, - const bNodeTree *tree, - const char *path, - char exr_codec, - bool exr_half_float, - const char *view_name); - - void *get_handle(const char *filepath); - void deinit_execution() override; -}; - -class OutputStereoOperation : public OutputSingleLayerOperation { - private: - /* NOTE: Using FILE_MAX here is misleading, this is not a file path. */ - char pass_name_[FILE_MAX]; - size_t channels_; - - public: - OutputStereoOperation(const Scene *scene, - const RenderData *rd, - const bNodeTree *tree, - DataType datatype, - const struct ImageFormatData *format, - const char *path, - const char *pass_name, - const char *view_name, - bool save_as_render); - void *get_handle(const char *filepath); - void deinit_execution() override; -}; - -} // namespace blender::compositor diff --git a/source/blender/compositor/operations/COM_OutputFileOperation.cc b/source/blender/compositor/operations/COM_OutputFileOperation.cc deleted file mode 100644 index 7ba078d4870..00000000000 --- a/source/blender/compositor/operations/COM_OutputFileOperation.cc +++ /dev/null @@ -1,467 +0,0 @@ -/* SPDX-FileCopyrightText: 2011 Blender Authors - * - * SPDX-License-Identifier: GPL-2.0-or-later */ - -#include "COM_OutputFileOperation.h" - -#include "BLI_fileops.h" -#include "BLI_listbase.h" -#include "BLI_string.h" - -#include "BKE_image.h" -#include "BKE_image_format.h" -#include "BKE_main.hh" -#include "BKE_scene.h" - -#include "DNA_color_types.h" - -#include "IMB_colormanagement.h" -#include "IMB_imbuf.h" -#include "IMB_imbuf_types.h" - -#include "RE_pipeline.h" - -namespace blender::compositor { - -void add_exr_channels(void *exrhandle, - const char *layer_name, - const DataType datatype, - const char *view_name, - const size_t width, - bool use_half_float, - float *buf) -{ - /* create channels */ - switch (datatype) { - case DataType::Value: - IMB_exr_add_channel( - exrhandle, layer_name, "V", view_name, 1, width, buf ? buf : nullptr, use_half_float); - break; - case DataType::Vector: - IMB_exr_add_channel(exrhandle, - layer_name, - "X", - view_name, - 3, - 3 * width, - buf ? buf : nullptr, - use_half_float); - IMB_exr_add_channel(exrhandle, - layer_name, - "Y", - view_name, - 3, - 3 * width, - buf ? buf + 1 : nullptr, - use_half_float); - IMB_exr_add_channel(exrhandle, - layer_name, - "Z", - view_name, - 3, - 3 * width, - buf ? buf + 2 : nullptr, - use_half_float); - break; - case DataType::Color: - IMB_exr_add_channel(exrhandle, - layer_name, - "R", - view_name, - 4, - 4 * width, - buf ? buf : nullptr, - use_half_float); - IMB_exr_add_channel(exrhandle, - layer_name, - "G", - view_name, - 4, - 4 * width, - buf ? buf + 1 : nullptr, - use_half_float); - IMB_exr_add_channel(exrhandle, - layer_name, - "B", - view_name, - 4, - 4 * width, - buf ? buf + 2 : nullptr, - use_half_float); - IMB_exr_add_channel(exrhandle, - layer_name, - "A", - view_name, - 4, - 4 * width, - buf ? buf + 3 : nullptr, - use_half_float); - break; - default: - break; - } -} - -void free_exr_channels(void *exrhandle, - const RenderData *rd, - const char *layer_name, - const DataType datatype) -{ - /* check renderdata for amount of views */ - LISTBASE_FOREACH (SceneRenderView *, srv, &rd->views) { - float *rect = nullptr; - - if (BKE_scene_multiview_is_render_view_active(rd, srv) == false) { - continue; - } - - /* the pointer is stored in the first channel of each datatype */ - switch (datatype) { - case DataType::Value: - rect = IMB_exr_channel_rect(exrhandle, layer_name, "V", srv->name); - break; - case DataType::Vector: - rect = IMB_exr_channel_rect(exrhandle, layer_name, "X", srv->name); - break; - case DataType::Color: - rect = IMB_exr_channel_rect(exrhandle, layer_name, "R", srv->name); - break; - default: - break; - } - if (rect) { - MEM_freeN(rect); - } - } -} - -int get_datatype_size(DataType datatype) -{ - switch (datatype) { - case DataType::Value: - return 1; - case DataType::Vector: - return 3; - case DataType::Color: - return 4; - default: - return 0; - } -} - -static float *init_buffer(uint width, uint height, DataType datatype) -{ - /* When initializing the tree during initial load the width and height can be zero. */ - if (width != 0 && height != 0) { - int size = get_datatype_size(datatype); - return (float *)MEM_callocN(width * height * size * sizeof(float), "OutputFile buffer"); - } - - return nullptr; -} - -static void write_buffer_rect(rcti *rect, - const bNodeTree *tree, - SocketReader *reader, - float *buffer, - uint width, - DataType datatype) -{ - float color[4]; - int i, size = get_datatype_size(datatype); - - if (!buffer) { - return; - } - int x1 = rect->xmin; - int y1 = rect->ymin; - int x2 = rect->xmax; - int y2 = rect->ymax; - int offset = (y1 * width + x1) * size; - int x; - int y; - bool breaked = false; - - for (y = y1; y < y2 && (!breaked); y++) { - for (x = x1; x < x2 && (!breaked); x++) { - reader->read_sampled(color, x, y, PixelSampler::Nearest); - - for (i = 0; i < size; i++) { - buffer[offset + i] = color[i]; - } - offset += size; - - if (tree->runtime->test_break && tree->runtime->test_break(tree->runtime->tbh)) { - breaked = true; - } - } - offset += (width - (x2 - x1)) * size; - } -} - -OutputSingleLayerOperation::OutputSingleLayerOperation(const Scene *scene, - const RenderData *rd, - const bNodeTree *tree, - DataType datatype, - const ImageFormatData *format, - const char *path, - const char *view_name, - const bool save_as_render) -{ - rd_ = rd; - tree_ = tree; - - this->add_input_socket(datatype); - - output_buffer_ = nullptr; - datatype_ = datatype; - image_input_ = nullptr; - - BKE_image_format_init_for_write(&format_, scene, format); - if (!save_as_render) { - /* If not saving as render, stop IMB_colormanagement_imbuf_for_write using this - * colorspace for conversion. */ - format_.linear_colorspace_settings.name[0] = '\0'; - } - - STRNCPY(path_, path); - - view_name_ = view_name; - save_as_render_ = save_as_render; -} - -OutputSingleLayerOperation::~OutputSingleLayerOperation() -{ - BKE_image_format_free(&format_); -} - -void OutputSingleLayerOperation::init_execution() -{ - image_input_ = get_input_socket_reader(0); - output_buffer_ = init_buffer(this->get_width(), this->get_height(), datatype_); -} - -void OutputSingleLayerOperation::execute_region(rcti *rect, uint /*tile_number*/) -{ - write_buffer_rect(rect, tree_, image_input_, output_buffer_, this->get_width(), datatype_); -} - -void OutputSingleLayerOperation::deinit_execution() -{ - if (this->get_width() * this->get_height() != 0) { - - int size = get_datatype_size(datatype_); - ImBuf *ibuf = IMB_allocImBuf(this->get_width(), this->get_height(), format_.planes, 0); - char filepath[FILE_MAX]; - const char *suffix; - - ibuf->channels = size; - ibuf->dither = rd_->dither_intensity; - - IMB_assign_float_buffer(ibuf, output_buffer_, IB_TAKE_OWNERSHIP); - - IMB_colormanagement_imbuf_for_write(ibuf, save_as_render_, false, &format_); - - suffix = BKE_scene_multiview_view_suffix_get(rd_, view_name_); - - BKE_image_path_from_imformat(filepath, - path_, - BKE_main_blendfile_path_from_global(), - rd_->cfra, - &format_, - (rd_->scemode & R_EXTENSION) != 0, - true, - suffix); - - if (0 == BKE_imbuf_write(ibuf, filepath, &format_)) { - printf("Cannot save Node File Output to %s\n", filepath); - } - else { - printf("Saved: %s\n", filepath); - } - - IMB_freeImBuf(ibuf); - } - output_buffer_ = nullptr; - image_input_ = nullptr; -} - -void OutputSingleLayerOperation::update_memory_buffer_partial(MemoryBuffer * /*output*/, - const rcti &area, - Span inputs) -{ - if (!output_buffer_) { - return; - } - - MemoryBuffer output_buf(output_buffer_, - COM_data_type_num_channels(datatype_), - this->get_width(), - this->get_height()); - const MemoryBuffer *input_image = inputs[0]; - output_buf.copy_from(input_image, area); -} - -/******************************* MultiLayer *******************************/ - -OutputOpenExrLayer::OutputOpenExrLayer(const char *name_, DataType datatype_, bool use_layer_) -{ - STRNCPY(this->name, name_); - this->datatype = datatype_; - this->use_layer = use_layer_; - - /* these are created in init_execution */ - this->output_buffer = nullptr; - this->image_input = nullptr; -} - -OutputOpenExrMultiLayerOperation::OutputOpenExrMultiLayerOperation(const Scene *scene, - const RenderData *rd, - const bNodeTree *tree, - const char *path, - char exr_codec, - bool exr_half_float, - const char *view_name) -{ - scene_ = scene; - rd_ = rd; - tree_ = tree; - - STRNCPY(path_, path); - exr_codec_ = exr_codec; - exr_half_float_ = exr_half_float; - view_name_ = view_name; - this->set_canvas_input_index(RESOLUTION_INPUT_ANY); -} - -void OutputOpenExrMultiLayerOperation::add_layer(const char *name, - DataType datatype, - bool use_layer) -{ - this->add_input_socket(datatype); - layers_.append(OutputOpenExrLayer(name, datatype, use_layer)); -} - -StampData *OutputOpenExrMultiLayerOperation::create_stamp_data() const -{ - /* StampData API doesn't provide functions to modify an instance without having a RenderResult. - */ - RenderResult render_result; - StampData *stamp_data = BKE_stamp_info_from_scene_static(scene_); - render_result.stamp_data = stamp_data; - for (const OutputOpenExrLayer &layer : layers_) { - /* Skip unconnected sockets. */ - if (layer.image_input == nullptr) { - continue; - } - std::unique_ptr meta_data = layer.image_input->get_meta_data(); - if (meta_data) { - blender::StringRef layer_name = - blender::bke::cryptomatte::BKE_cryptomatte_extract_layer_name( - blender::StringRef(layer.name, BLI_strnlen(layer.name, sizeof(layer.name)))); - meta_data->replace_hash_neutral_cryptomatte_keys(layer_name); - meta_data->add_to_render_result(&render_result); - } - } - return stamp_data; -} - -void OutputOpenExrMultiLayerOperation::init_execution() -{ - for (uint i = 0; i < layers_.size(); i++) { - if (layers_[i].use_layer) { - SocketReader *reader = get_input_socket_reader(i); - layers_[i].image_input = reader; - layers_[i].output_buffer = init_buffer( - this->get_width(), this->get_height(), layers_[i].datatype); - } - } -} - -void OutputOpenExrMultiLayerOperation::execute_region(rcti *rect, uint /*tile_number*/) -{ - for (uint i = 0; i < layers_.size(); i++) { - OutputOpenExrLayer &layer = layers_[i]; - if (layer.image_input) { - write_buffer_rect( - rect, tree_, layer.image_input, layer.output_buffer, this->get_width(), layer.datatype); - } - } -} - -void OutputOpenExrMultiLayerOperation::deinit_execution() -{ - uint width = this->get_width(); - uint height = this->get_height(); - if (width != 0 && height != 0) { - char filepath[FILE_MAX]; - const char *suffix; - void *exrhandle = IMB_exr_get_handle(); - - suffix = BKE_scene_multiview_view_suffix_get(rd_, view_name_); - BKE_image_path_from_imtype(filepath, - path_, - BKE_main_blendfile_path_from_global(), - rd_->cfra, - R_IMF_IMTYPE_MULTILAYER, - (rd_->scemode & R_EXTENSION) != 0, - true, - suffix); - BLI_file_ensure_parent_dir_exists(filepath); - - for (uint i = 0; i < layers_.size(); i++) { - OutputOpenExrLayer &layer = layers_[i]; - if (!layer.image_input) { - continue; /* skip unconnected sockets */ - } - - add_exr_channels(exrhandle, - layers_[i].name, - layers_[i].datatype, - "", - width, - exr_half_float_, - layers_[i].output_buffer); - } - - /* When the filepath has no permissions, this can fail. */ - StampData *stamp_data = create_stamp_data(); - if (IMB_exr_begin_write(exrhandle, filepath, width, height, exr_codec_, stamp_data)) { - IMB_exr_write_channels(exrhandle); - } - else { - /* TODO: get the error from openexr's exception. */ - /* XXX: nice way to do report? */ - printf("Error Writing Render Result, see console\n"); - } - - IMB_exr_close(exrhandle); - for (uint i = 0; i < layers_.size(); i++) { - if (layers_[i].output_buffer) { - MEM_freeN(layers_[i].output_buffer); - layers_[i].output_buffer = nullptr; - } - - layers_[i].image_input = nullptr; - } - BKE_stamp_data_free(stamp_data); - } -} - -void OutputOpenExrMultiLayerOperation::update_memory_buffer_partial(MemoryBuffer * /*output*/, - const rcti &area, - Span inputs) -{ - for (int i = 0; i < layers_.size(); i++) { - OutputOpenExrLayer &layer = layers_[i]; - int layer_num_channels = COM_data_type_num_channels(layer.datatype); - if (layer.output_buffer) { - MemoryBuffer output_buf( - layer.output_buffer, layer_num_channels, this->get_width(), this->get_height()); - /* Input node always has 4 channels. Not all are needed depending on datatype. */ - output_buf.copy_from(inputs[i], area, 0, layer_num_channels, 0); - } - } -} - -} // namespace blender::compositor diff --git a/source/blender/compositor/operations/COM_OutputFileOperation.h b/source/blender/compositor/operations/COM_OutputFileOperation.h deleted file mode 100644 index fefda6a493c..00000000000 --- a/source/blender/compositor/operations/COM_OutputFileOperation.h +++ /dev/null @@ -1,131 +0,0 @@ -/* SPDX-FileCopyrightText: 2011 Blender Authors - * - * SPDX-License-Identifier: GPL-2.0-or-later */ - -#pragma once - -#include "COM_MultiThreadedOperation.h" - -#include "BLI_path_util.h" -#include "BLI_rect.h" - -#include "DNA_color_types.h" - -#include "IMB_openexr.h" - -namespace blender::compositor { - -/* Writes the image to a single-layer file. */ -class OutputSingleLayerOperation : public MultiThreadedOperation { - protected: - const RenderData *rd_; - const bNodeTree *tree_; - - ImageFormatData format_; - char path_[FILE_MAX]; - - float *output_buffer_; - DataType datatype_; - SocketReader *image_input_; - - const char *view_name_; - bool save_as_render_; - - public: - OutputSingleLayerOperation(const Scene *scene, - const RenderData *rd, - const bNodeTree *tree, - DataType datatype, - const ImageFormatData *format, - const char *path, - const char *view_name, - bool save_as_render); - ~OutputSingleLayerOperation(); - - void execute_region(rcti *rect, unsigned int tile_number) override; - bool is_output_operation(bool /*rendering*/) const override - { - return true; - } - void init_execution() override; - void deinit_execution() override; - eCompositorPriority get_render_priority() const override - { - return eCompositorPriority::Low; - } - - void update_memory_buffer_partial(MemoryBuffer *output, - const rcti &area, - Span inputs) override; -}; - -/* extra info for OpenEXR layers */ -struct OutputOpenExrLayer { - OutputOpenExrLayer(const char *name, DataType datatype, bool use_layer); - - char name[EXR_TOT_MAXNAME - 2]; - DataType datatype; - bool use_layer; - - /* internals */ - float *output_buffer; - SocketReader *image_input; -}; - -/* Writes inputs into OpenEXR multi-layer channels. */ -class OutputOpenExrMultiLayerOperation : public MultiThreadedOperation { - protected: - const Scene *scene_; - const RenderData *rd_; - const bNodeTree *tree_; - - char path_[FILE_MAX]; - char exr_codec_; - bool exr_half_float_; - Vector layers_; - const char *view_name_; - - StampData *create_stamp_data() const; - - public: - OutputOpenExrMultiLayerOperation(const Scene *scene, - const RenderData *rd, - const bNodeTree *tree, - const char *path, - char exr_codec, - bool exr_half_float, - const char *view_name); - - void add_layer(const char *name, DataType datatype, bool use_layer); - - void execute_region(rcti *rect, unsigned int tile_number) override; - bool is_output_operation(bool /*rendering*/) const override - { - return true; - } - void init_execution() override; - void deinit_execution() override; - eCompositorPriority get_render_priority() const override - { - return eCompositorPriority::Low; - } - - void update_memory_buffer_partial(MemoryBuffer *output, - const rcti &area, - Span inputs) override; -}; - -void add_exr_channels(void *exrhandle, - const char *layer_name, - const DataType datatype, - const char *view_name, - size_t width, - bool use_half_float, - float *buf); -void free_exr_channels(void *exrhandle, - const RenderData *rd, - const char *layer_name, - const DataType datatype); -int get_datatype_size(DataType datatype); - -} // namespace blender::compositor diff --git a/source/blender/compositor/realtime_compositor/CMakeLists.txt b/source/blender/compositor/realtime_compositor/CMakeLists.txt index 625cc218ae3..9132c86b079 100644 --- a/source/blender/compositor/realtime_compositor/CMakeLists.txt +++ b/source/blender/compositor/realtime_compositor/CMakeLists.txt @@ -32,6 +32,7 @@ set(SRC intern/operation.cc intern/realize_on_domain_operation.cc intern/reduce_to_single_value_operation.cc + intern/render_context.cc intern/result.cc intern/scheduler.cc intern/shader_node.cc @@ -52,6 +53,7 @@ set(SRC COM_operation.hh COM_realize_on_domain_operation.hh COM_reduce_to_single_value_operation.hh + COM_render_context.hh COM_result.hh COM_scheduler.hh COM_shader_node.hh diff --git a/source/blender/compositor/realtime_compositor/COM_context.hh b/source/blender/compositor/realtime_compositor/COM_context.hh index ff183b01e0f..3c850941b6e 100644 --- a/source/blender/compositor/realtime_compositor/COM_context.hh +++ b/source/blender/compositor/realtime_compositor/COM_context.hh @@ -14,6 +14,7 @@ #include "GPU_shader.h" #include "GPU_texture.h" +#include "COM_render_context.hh" #include "COM_result.hh" #include "COM_static_cache_manager.hh" #include "COM_texture_pool.hh" @@ -104,6 +105,11 @@ class Context { * that is, to ready it to track the next change. */ virtual IDRecalcFlag query_id_recalc_flag(ID *id) const = 0; + /* Get a pointer to the render context of this context. A render context stores information about + * the current render. It might be null if the compositor is not being evaluated as part of a + * render pipeline. */ + virtual RenderContext *render_context() const; + /* Get the size of the compositing region. See get_compositing_region(). */ int2 get_compositing_region_size() const; diff --git a/source/blender/compositor/realtime_compositor/COM_render_context.hh b/source/blender/compositor/realtime_compositor/COM_render_context.hh new file mode 100644 index 00000000000..aee19fe6aab --- /dev/null +++ b/source/blender/compositor/realtime_compositor/COM_render_context.hh @@ -0,0 +1,119 @@ +/* SPDX-FileCopyrightText: 2023 Blender Authors + * + * SPDX-License-Identifier: GPL-2.0-or-later */ + +#pragma once + +#include +#include + +#include "BLI_map.hh" +#include "BLI_math_vector_types.hh" + +#include "DNA_scene_types.h" + +struct RenderResult; + +namespace blender::realtime_compositor { + +/* ------------------------------------------------------------------------------------------------ + * File Output + * + * A FileOutput represents an image that will be saved to a file output. The image is internally + * stored as a RenderResult and saved at the path according to the image format. The image can + * either be saved as an EXR image or a non-EXR image, specified by the format. This is important + * because EXR images needs to constructed differently from other image types as will be explained + * in the following sections. + * + * For EXR images, the render result needs to be composed of passes for each layer, so the add_pass + * method should be called to add each of the passes. Additionally, an empty view should be added + * for each of the views referenced by the passes, using the single-argument overload of the + * add_view method. Those views are merely empty structure and does not hold any data aside from + * the view name. An exception to this rule is stereo EXR images, which needs to have the same + * structure as non-EXR images as explained in the following section. + * + * For non-EXR images, the render result needs to composed of views, so the multi-argument overload + * of the method add_view should be used to add each view. + * + * Color management will be applied on the images if save_as_render_ is true. + * + * Meta data can be added using the add_meta_data function. */ +class FileOutput { + private: + std::string path_; + ImageFormatData format_; + RenderResult *render_result_; + bool save_as_render_; + Map meta_data_; + + public: + /* Allocate and initialize the internal render result of the file output using the give + * parameters. See the implementation for more information. */ + FileOutput(std::string path, ImageFormatData format, int2 size, bool save_as_render); + + /* Free the internal render result. */ + ~FileOutput(); + + /* Add an empty view with the given name. An empty view is just structure and does not hold any + * data aside from the view name. This should be called for each view referenced by passes. This + * should only be called for EXR images. */ + void add_view(const char *view_name); + + /* Add a view of the given name that stores the given pixel buffer composed of the given number + * of channels. */ + void add_view(const char *view_name, int channels, float *buffer); + + /* Add a pass of the given name in the given view that stores the given pixel buffer composed of + * each of the channels given by the channels string. The channels string should contain a + * character for each channel in the pixel buffer representing the channel ID. This should only + * be called for EXR images. The given view name should be the name of an added view using the + * add_view method. */ + void add_pass(const char *pass_name, const char *view_name, const char *channels, float *buffer); + + /* Add meta data that will eventually be saved to the file if the format supports it. */ + void add_meta_data(std::string key, std::string value); + + /* Save the file to the path along with its meta data, reporting any reports to the standard + * output. */ + void save(Scene *scene); +}; + +/* ------------------------------------------------------------------------------------------------ + * Render Context + * + * A render context is created by the render pipeline and passed to the compositor to stores data + * that is specifically related to the rendering process. In particular, since the compositor is + * executed for each view separately and consecutively, it can be used to store and accumulate + * data from each of the evaluations of each view, for instance, to save all views in a single file + * for the File Output node, see the file_outputs_ member for more information. */ +class RenderContext { + private: + /* A mapping between file outputs and their image file paths. Those are constructed in the + * get_file_output method and saved in the save_file_outputs method. See those methods for more + * information. */ + Map> file_outputs_; + + public: + /* Check if there is an available file output with the given path in the context, if one exists, + * return it, otherwise, return a newly created one from the given parameters and add it to the + * context. The arguments are ignored if the file output already exist. This method is typically + * called in the File Output nodes in the compositor. + * + * Since the compositor gets executed multiple times for each view, for single view renders, the + * file output will be constructed and fully initialized in the same compositor evaluation. For + * multi-view renders, the file output will be constructed in the evaluation of the first view, + * and each view will subsequently add its data until the file output is fully initialized in the + * last view. The render pipeline code will then call the save_file_outputs method after all + * views were evaluated to write the file outputs. */ + FileOutput &get_file_output(std::string path, + ImageFormatData format, + int2 size, + bool save_as_render); + + /* Write the file outputs that were added to the context. The render pipeline code should call + * this method after all views were evaluated to write the file outputs. See the get_file_output + * method for more information. */ + void save_file_outputs(Scene *scene); +}; + +} // namespace blender::realtime_compositor diff --git a/source/blender/compositor/realtime_compositor/intern/context.cc b/source/blender/compositor/realtime_compositor/intern/context.cc index ec00a558c68..4ec7baa8ec9 100644 --- a/source/blender/compositor/realtime_compositor/intern/context.cc +++ b/source/blender/compositor/realtime_compositor/intern/context.cc @@ -9,6 +9,7 @@ #include "GPU_shader.h" #include "COM_context.hh" +#include "COM_render_context.hh" #include "COM_static_cache_manager.hh" #include "COM_texture_pool.hh" @@ -16,6 +17,11 @@ namespace blender::realtime_compositor { Context::Context(TexturePool &texture_pool) : texture_pool_(texture_pool) {} +RenderContext *Context::render_context() const +{ + return nullptr; +} + int2 Context::get_compositing_region_size() const { const rcti compositing_region = get_compositing_region(); diff --git a/source/blender/compositor/realtime_compositor/intern/render_context.cc b/source/blender/compositor/realtime_compositor/intern/render_context.cc new file mode 100644 index 00000000000..f0c241c263e --- /dev/null +++ b/source/blender/compositor/realtime_compositor/intern/render_context.cc @@ -0,0 +1,151 @@ +/* SPDX-FileCopyrightText: 2023 Blender Authors + * + * SPDX-License-Identifier: GPL-2.0-or-later */ + +#include +#include + +#include "BLI_assert.h" +#include "BLI_listbase.h" +#include "BLI_map.hh" +#include "BLI_math_base.hh" +#include "BLI_math_vector_types.hh" +#include "BLI_string.h" +#include "BLI_utildefines.h" + +#include "MEM_guardedalloc.h" + +#include "IMB_imbuf.h" +#include "IMB_imbuf_types.h" + +#include "DNA_scene_types.h" +#include "DNA_windowmanager_types.h" + +#include "BKE_image.h" +#include "BKE_image_save.h" +#include "BKE_report.h" + +#include "RE_pipeline.h" + +#include "COM_render_context.hh" + +namespace blender::realtime_compositor { + +/* ------------------------------------------------------------------------------------------------ + * File Output + */ + +FileOutput::FileOutput(std::string path, ImageFormatData format, int2 size, bool save_as_render) + : path_(path), format_(format), save_as_render_(save_as_render) +{ + render_result_ = MEM_cnew("Temporary Render Result For File Output"); + + render_result_->rectx = size.x; + render_result_->recty = size.y; + + /* File outputs are always single layer, as images are actually stored in passes on that single + * layer. Create a single unnamed layer to add the passes to. A single unnamed layer is treated + * by the EXR writer as a special case where the channel names take the form: + * .. + * Otherwise, the layer name would have preceded in the pass name in yet another section. */ + RenderLayer *render_layer = MEM_cnew("Render Layer For File Output."); + BLI_addtail(&render_result_->layers, render_layer); + render_layer->name[0] = '\0'; +} + +FileOutput::~FileOutput() +{ + RE_FreeRenderResult(render_result_); +} + +void FileOutput::add_view(const char *view_name) +{ + /* Empty views can only be added for EXR images. */ + BLI_assert(ELEM(format_.imtype, R_IMF_IMTYPE_OPENEXR, R_IMF_IMTYPE_MULTILAYER)); + + RenderView *render_view = MEM_cnew("Render View For File Output."); + BLI_addtail(&render_result_->views, render_view); + STRNCPY(render_view->name, view_name); +} + +void FileOutput::add_view(const char *view_name, int channels, float *buffer) +{ + RenderView *render_view = MEM_cnew("Render View For File Output."); + BLI_addtail(&render_result_->views, render_view); + STRNCPY(render_view->name, view_name); + + render_view->ibuf = IMB_allocImBuf( + render_result_->rectx, render_result_->recty, channels * 8, 0); + render_view->ibuf->channels = channels; + IMB_assign_float_buffer(render_view->ibuf, buffer, IB_TAKE_OWNERSHIP); +} + +void FileOutput::add_pass(const char *pass_name, + const char *view_name, + const char *channels, + float *buffer) +{ + /* Passes can only be added for EXR images. */ + BLI_assert(ELEM(format_.imtype, R_IMF_IMTYPE_OPENEXR, R_IMF_IMTYPE_MULTILAYER)); + + RenderLayer *render_layer = static_cast(render_result_->layers.first); + RenderPass *render_pass = MEM_cnew("Render Pass For File Output."); + BLI_addtail(&render_layer->passes, render_pass); + STRNCPY(render_pass->name, pass_name); + STRNCPY(render_pass->view, view_name); + STRNCPY(render_pass->chan_id, channels); + + const int channels_count = BLI_strnlen(channels, 4); + render_pass->rectx = render_result_->rectx; + render_pass->recty = render_result_->recty; + render_pass->channels = channels_count; + + render_pass->ibuf = IMB_allocImBuf( + render_result_->rectx, render_result_->recty, channels_count * 8, 0); + render_pass->ibuf->channels = channels_count; + IMB_assign_float_buffer(render_pass->ibuf, buffer, IB_TAKE_OWNERSHIP); +} + +void FileOutput::add_meta_data(std::string key, std::string value) +{ + meta_data_.add(key, value); +} + +void FileOutput::save(Scene *scene) +{ + ReportList reports; + BKE_reports_init(&reports, RPT_STORE); + + /* Add scene stamp data as meta data as well as the custom meta data. */ + BKE_render_result_stamp_info(scene, nullptr, render_result_, false); + for (const auto &field : meta_data_.items()) { + BKE_render_result_stamp_data(render_result_, field.key.c_str(), field.value.c_str()); + } + + BKE_image_render_write( + &reports, render_result_, scene, true, path_.c_str(), &format_, save_as_render_); + + BKE_reports_free(&reports); +} + +/* ------------------------------------------------------------------------------------------------ + * Render Context + */ + +FileOutput &RenderContext::get_file_output(std::string path, + ImageFormatData format, + int2 size, + bool save_as_render) +{ + return *file_outputs_.lookup_or_add_cb( + path, [&]() { return std::make_unique(path, format, size, save_as_render); }); +} + +void RenderContext::save_file_outputs(Scene *scene) +{ + for (std::unique_ptr &file_output : file_outputs_.values()) { + file_output->save(scene); + } +} + +} // namespace blender::realtime_compositor diff --git a/source/blender/editors/space_node/node_edit.cc b/source/blender/editors/space_node/node_edit.cc index cecb378d2b1..9e076582ec4 100644 --- a/source/blender/editors/space_node/node_edit.cc +++ b/source/blender/editors/space_node/node_edit.cc @@ -296,14 +296,15 @@ static void compo_startjob(void *cjv, wmJobWorkerStatus *worker_status) BKE_callback_exec_id(cj->bmain, &scene->id, BKE_CB_EVT_COMPOSITE_PRE); if ((cj->scene->r.scemode & R_MULTIVIEW) == 0) { - ntreeCompositExecTree(cj->re, cj->scene, ntree, &cj->scene->r, false, true, ""); + ntreeCompositExecTree(cj->re, cj->scene, ntree, &cj->scene->r, false, true, "", nullptr); } else { LISTBASE_FOREACH (SceneRenderView *, srv, &scene->r.views) { if (BKE_scene_multiview_is_render_view_active(&scene->r, srv) == false) { continue; } - ntreeCompositExecTree(cj->re, cj->scene, ntree, &cj->scene->r, false, true, srv->name); + ntreeCompositExecTree( + cj->re, cj->scene, ntree, &cj->scene->r, false, true, srv->name, nullptr); } } diff --git a/source/blender/nodes/NOD_composite.hh b/source/blender/nodes/NOD_composite.hh index f0d668263ec..4544c3a4fd0 100644 --- a/source/blender/nodes/NOD_composite.hh +++ b/source/blender/nodes/NOD_composite.hh @@ -10,6 +10,10 @@ #include "BKE_node.h" +namespace blender::realtime_compositor { +class RenderContext; +} + struct bNodeTreeType; struct CryptomatteSession; struct Scene; @@ -36,7 +40,8 @@ void ntreeCompositExecTree(Render *render, RenderData *rd, bool rendering, int do_previews, - const char *view_name); + const char *view_name, + blender::realtime_compositor::RenderContext *render_context); /** * Called from render pipeline, to tag render input and output. diff --git a/source/blender/nodes/composite/CMakeLists.txt b/source/blender/nodes/composite/CMakeLists.txt index a57e6e7de84..78303b4cc40 100644 --- a/source/blender/nodes/composite/CMakeLists.txt +++ b/source/blender/nodes/composite/CMakeLists.txt @@ -59,6 +59,7 @@ set(SRC nodes/node_composite_double_edge_mask.cc nodes/node_composite_ellipsemask.cc nodes/node_composite_exposure.cc + nodes/node_composite_file_output.cc nodes/node_composite_filter.cc nodes/node_composite_flip.cc nodes/node_composite_gamma.cc @@ -85,7 +86,6 @@ set(SRC nodes/node_composite_moviedistortion.cc nodes/node_composite_normal.cc nodes/node_composite_normalize.cc - nodes/node_composite_output_file.cc nodes/node_composite_pixelate.cc nodes/node_composite_planetrackdeform.cc nodes/node_composite_posterize.cc diff --git a/source/blender/nodes/composite/node_composite_tree.cc b/source/blender/nodes/composite/node_composite_tree.cc index a48ba88c77c..022eafd75e3 100644 --- a/source/blender/nodes/composite/node_composite_tree.cc +++ b/source/blender/nodes/composite/node_composite_tree.cc @@ -179,12 +179,13 @@ void ntreeCompositExecTree(Render *render, RenderData *rd, bool rendering, int do_preview, - const char *view_name) + const char *view_name, + blender::realtime_compositor::RenderContext *render_context) { #ifdef WITH_COMPOSITOR_CPU - COM_execute(render, rd, scene, ntree, rendering, view_name); + COM_execute(render, rd, scene, ntree, rendering, view_name, render_context); #else - UNUSED_VARS(render, scene, ntree, rd, rendering, view_name); + UNUSED_VARS(render, scene, ntree, rd, rendering, view_name, render_context); #endif UNUSED_VARS(do_preview); diff --git a/source/blender/nodes/composite/nodes/node_composite_output_file.cc b/source/blender/nodes/composite/nodes/node_composite_file_output.cc similarity index 57% rename from source/blender/nodes/composite/nodes/node_composite_output_file.cc rename to source/blender/nodes/composite/nodes/node_composite_file_output.cc index c1ba20f32a8..9398e8eda14 100644 --- a/source/blender/nodes/composite/nodes/node_composite_output_file.cc +++ b/source/blender/nodes/composite/nodes/node_composite_file_output.cc @@ -8,13 +8,26 @@ #include +#include "BLI_assert.h" +#include "BLI_fileops.h" +#include "BLI_index_range.hh" +#include "BLI_path_util.h" #include "BLI_string.h" #include "BLI_string_utf8.h" #include "BLI_string_utils.hh" +#include "BLI_task.hh" #include "BLI_utildefines.h" +#include "MEM_guardedalloc.h" + +#include "DNA_node_types.h" +#include "DNA_scene_types.h" + #include "BKE_context.hh" +#include "BKE_image.h" #include "BKE_image_format.h" +#include "BKE_main.hh" +#include "BKE_scene.h" #include "RNA_access.hh" #include "RNA_prototypes.h" @@ -24,8 +37,14 @@ #include "WM_api.hh" +#include "IMB_colormanagement.h" +#include "IMB_imbuf.h" +#include "IMB_imbuf_types.h" #include "IMB_openexr.h" +#include "GPU_state.h" +#include "GPU_texture.h" + #include "COM_node_operation.hh" #include "node_composite_util.hh" @@ -187,7 +206,9 @@ void ntreeCompositOutputFileSetLayer(bNode *node, bNodeSocket *sock, const char ntreeCompositOutputFileUniqueLayer(&node->inputs, sock, name, '_'); } -namespace blender::nodes::node_composite_output_file_cc { +namespace blender::nodes::node_composite_file_output_cc { + +NODE_STORAGE_FUNCS(NodeImageMultiFile) /* XXX uses initfunc_api callback, regular initfunc does not support context yet */ static void init_output_file(const bContext *C, PointerRNA *ptr) @@ -443,28 +464,291 @@ static void node_composit_buts_file_output_ex(uiLayout *layout, bContext *C, Poi using namespace blender::realtime_compositor; -class OutputFileOperation : public NodeOperation { +class FileOutputOperation : public NodeOperation { public: using NodeOperation::NodeOperation; void execute() override { - if (context().use_file_output()) { - context().set_info_message("Viewport compositor setup not fully supported"); + if (is_multi_layer()) { + execute_multi_layer(); } + else { + execute_single_layer(); + } + } + + /* -------------------- + * Single Layer Images. + */ + + void execute_single_layer() + { + const int2 size = compute_domain().size; + for (const bNodeSocket *input : this->node()->input_sockets()) { + const Result &result = get_input(input->identifier); + /* We only write images, not single values. */ + if (result.is_single_value()) { + continue; + } + + char base_path[FILE_MAX]; + const auto &socket = *static_cast(input->storage); + get_single_layer_image_base_path(socket.path, base_path); + + /* The image saving code expects EXR images to have a different structure than standard + * images. In particular, in EXR images, the buffers need to be stored in passes that are, in + * turn, stored in a render layer. On the other hand, in non-EXR images, the buffers need to + * be stored in views. An exception to this is stereo images, which needs to have the same + * structure as non-EXR images. */ + const auto &format = socket.use_node_format ? node_storage(bnode()).format : socket.format; + const bool is_exr = format.imtype == R_IMF_IMTYPE_OPENEXR; + const int views_count = BKE_scene_multiview_num_views_get(&context().get_render_data()); + if (is_exr && !(format.views_format == R_IMF_VIEWS_STEREO_3D && views_count == 2)) { + execute_single_layer_multi_view_exr(result, format, base_path); + continue; + } + + char image_path[FILE_MAX]; + get_single_layer_image_path(base_path, format, image_path); + + FileOutput &file_output = context().render_context()->get_file_output( + image_path, format, size, socket.save_as_render); + + add_view_for_result(file_output, result, context().get_view_name().data()); + } + } + + /* ----------------------------------- + * Single Layer Multi-View EXR Images. + */ + + void execute_single_layer_multi_view_exr(const Result &result, + const ImageFormatData &format, + const char *base_path) + { + const bool has_views = format.views_format != R_IMF_VIEWS_INDIVIDUAL; + + /* The EXR stores all views in the same file, so we supply an empty view to make sure the file + * name does not contain a view suffix. */ + char image_path[FILE_MAX]; + const char *path_view = has_views ? "" : context().get_view_name().data(); + get_multi_layer_exr_image_path(base_path, path_view, image_path); + + const int2 size = compute_domain().size; + FileOutput &file_output = context().render_context()->get_file_output( + image_path, format, size, false); + + /* The EXR stores all views in the same file, so we add the actual render view. Otherwise, we + * add a default unnamed view. */ + const char *view_name = has_views ? context().get_view_name().data() : ""; + file_output.add_view(view_name); + add_pass_for_result(file_output, result, "", view_name); + } + + /* ----------------------- + * Multi-Layer EXR Images. + */ + + void execute_multi_layer() + { + const bool store_views_in_single_file = is_multi_view_exr(); + const char *view = context().get_view_name().data(); + + /* If we are saving all views in a single multi-layer file, we supply an empty view to make + * sure the file name does not contain a view suffix. */ + char image_path[FILE_MAX]; + const char *write_view = store_views_in_single_file ? "" : view; + get_multi_layer_exr_image_path(get_base_path(), write_view, image_path); + + const int2 size = compute_domain().size; + const ImageFormatData format = node_storage(bnode()).format; + FileOutput &file_output = context().render_context()->get_file_output( + image_path, format, size, false); + + /* If we are saving views in separate files, we needn't store the view in the channel names, so + * we add an unnamed view. */ + const char *pass_view = store_views_in_single_file ? view : ""; + file_output.add_view(pass_view); + + for (const bNodeSocket *input : this->node()->input_sockets()) { + const Result &input_result = get_input(input->identifier); + /* We only write images, not single values. */ + if (input_result.is_single_value()) { + continue; + } + + const char *pass_name = (static_cast(input->storage))->layer; + add_pass_for_result(file_output, input_result, pass_name, pass_view); + } + } + + /* Read the data stored in the GPU texture of the given result and add a pass of the given name, + * view, and read buffer. The pass channel identifiers follows the EXR conventions. */ + void add_pass_for_result(FileOutput &file_output, + const Result &result, + const char *pass_name, + const char *view_name) + { + /* The image buffer in the file output will take ownership of this buffer and freeing it will + * be its responsibility. */ + GPU_memory_barrier(GPU_BARRIER_TEXTURE_UPDATE); + float *buffer = static_cast(GPU_texture_read(result.texture(), GPU_DATA_FLOAT, 0)); + + const int2 size = result.domain().size; + switch (result.type()) { + case ResultType::Color: + file_output.add_pass(pass_name, view_name, "RGBA", buffer); + break; + case ResultType::Vector: + file_output.add_pass(pass_name, view_name, "XYZ", float4_to_float3_image(size, buffer)); + break; + case ResultType::Float: + file_output.add_pass(pass_name, view_name, "V", buffer); + break; + default: + /* Other types are internal and needn't be handled by operations. */ + BLI_assert_unreachable(); + break; + } + } + + /* Read the data stored in the GPU texture of the given result and add a view of the given name + * and read buffer. */ + void add_view_for_result(FileOutput &file_output, const Result &result, const char *view_name) + { + /* The image buffer in the file output will take ownership of this buffer and freeing it will + * be its responsibility. */ + GPU_memory_barrier(GPU_BARRIER_TEXTURE_UPDATE); + float *buffer = static_cast(GPU_texture_read(result.texture(), GPU_DATA_FLOAT, 0)); + + const int2 size = result.domain().size; + switch (result.type()) { + case ResultType::Color: + file_output.add_view(view_name, 4, buffer); + break; + case ResultType::Vector: + file_output.add_view(view_name, 3, float4_to_float3_image(size, buffer)); + break; + case ResultType::Float: + file_output.add_view(view_name, 1, buffer); + break; + default: + /* Other types are internal and needn't be handled by operations. */ + BLI_assert_unreachable(); + break; + } + } + + /* Given a float4 image, return a newly allocated float3 image that ignores the last channel. The + * input image is freed. */ + float *float4_to_float3_image(int2 size, float *float4_image) + { + float *float3_image = static_cast(MEM_malloc_arrayN( + size_t(size.x) * size.y, sizeof(float[3]), "File Output Vector Buffer.")); + + threading::parallel_for(IndexRange(size.y), 1, [&](const IndexRange sub_y_range) { + for (const int64_t y : sub_y_range) { + for (const int64_t x : IndexRange(size.x)) { + for (int i = 0; i < 3; i++) { + const int pixel_index = y * size.x + x; + float3_image[pixel_index * 3 + i] = float4_image[pixel_index * 4 + i]; + } + } + } + }); + + MEM_freeN(float4_image); + return float3_image; + } + + /* Get the base path of the image to be saved, based on the base path of the node. The base name + * is an optional initial name of the image, which will later be concatenated with other + * information like the frame number, view, and extension. If the base name is empty, then the + * base path represents a directory, so a trailing slash is ensured. */ + void get_single_layer_image_base_path(const char *base_name, char *base_path) + { + if (base_name[0]) { + BLI_path_join(base_path, FILE_MAX, get_base_path(), base_name); + } + else { + BLI_strncpy(base_path, get_base_path(), FILE_MAX); + BLI_path_slash_ensure(base_path, FILE_MAX); + } + } + + /* Get the path of the image to be saved based on the given format. */ + void get_single_layer_image_path(const char *base_path, + const ImageFormatData &format, + char *image_path) + { + BKE_image_path_from_imformat(image_path, + base_path, + BKE_main_blendfile_path_from_global(), + context().get_frame_number(), + &format, + use_file_extension(), + true, + nullptr); + } + + /* Get the path of the EXR image to be saved. If the given view is not empty, its corresponding + * file suffix will be appended to the name. */ + void get_multi_layer_exr_image_path(const char *base_path, const char *view, char *image_path) + { + const char *suffix = BKE_scene_multiview_view_suffix_get(&context().get_render_data(), view); + BKE_image_path_from_imtype(image_path, + base_path, + BKE_main_blendfile_path_from_global(), + context().get_frame_number(), + R_IMF_IMTYPE_MULTILAYER, + use_file_extension(), + true, + suffix); + } + + bool is_multi_layer() + { + return node_storage(bnode()).format.imtype == R_IMF_IMTYPE_MULTILAYER; + } + + const char *get_base_path() + { + return node_storage(bnode()).base_path; + } + + /* Add the file format extensions to the rendered file name. */ + bool use_file_extension() + { + return context().get_render_data().scemode & R_EXTENSION; + } + + /* If true, save views in a multi-view EXR file, otherwise, save each view in its own file. */ + bool is_multi_view_exr() + { + if (!is_multi_view_scene()) { + return false; + } + + return node_storage(bnode()).format.views_format == R_IMF_VIEWS_MULTIVIEW; + } + + bool is_multi_view_scene() + { + return context().get_render_data().scemode & R_MULTIVIEW; } }; static NodeOperation *get_compositor_operation(Context &context, DNode node) { - return new OutputFileOperation(context, node); + return new FileOutputOperation(context, node); } -} // namespace blender::nodes::node_composite_output_file_cc +} // namespace blender::nodes::node_composite_file_output_cc void register_node_type_cmp_output_file() { - namespace file_ns = blender::nodes::node_composite_output_file_cc; + namespace file_ns = blender::nodes::node_composite_file_output_cc; static bNodeType ntype; @@ -477,8 +761,6 @@ void register_node_type_cmp_output_file() &ntype, "NodeImageMultiFile", file_ns::free_output_file, file_ns::copy_output_file); ntype.updatefunc = file_ns::update_output_file; ntype.get_compositor_operation = file_ns::get_compositor_operation; - ntype.realtime_compositor_unsupported_message = N_( - "Node not supported in the Viewport compositor"); nodeRegisterType(&ntype); } diff --git a/source/blender/render/RE_compositor.hh b/source/blender/render/RE_compositor.hh index ee95c95d8d3..a4d0c5c463a 100644 --- a/source/blender/render/RE_compositor.hh +++ b/source/blender/render/RE_compositor.hh @@ -6,6 +6,10 @@ #include +namespace blender::realtime_compositor { +class RenderContext; +} + struct bNodeTree; struct Depsgraph; struct Render; @@ -29,7 +33,8 @@ void RE_compositor_execute(Render &render, const RenderData &render_data, const bNodeTree &node_tree, const bool use_file_output, - const char *view_name); + const char *view_name, + blender::realtime_compositor::RenderContext *render_context); /* Free compositor caches. */ void RE_compositor_free(Render &render); diff --git a/source/blender/render/intern/compositor.cc b/source/blender/render/intern/compositor.cc index f4a421b1f54..c40fc10c7b9 100644 --- a/source/blender/render/intern/compositor.cc +++ b/source/blender/render/intern/compositor.cc @@ -27,6 +27,7 @@ #include "COM_context.hh" #include "COM_evaluator.hh" +#include "COM_render_context.hh" #include "RE_compositor.hh" #include "RE_pipeline.h" @@ -122,17 +123,20 @@ class ContextInputData { const bNodeTree *node_tree; bool use_file_output; std::string view_name; + realtime_compositor::RenderContext *render_context; ContextInputData(const Scene &scene, const RenderData &render_data, const bNodeTree &node_tree, const bool use_file_output, - const char *view_name) + const char *view_name, + realtime_compositor::RenderContext *render_context) : scene(&scene), render_data(&render_data), node_tree(&node_tree), use_file_output(use_file_output), - view_name(view_name) + view_name(view_name), + render_context(render_context) { } }; @@ -433,6 +437,11 @@ class Context : public realtime_compositor::Context { input_data_.node_tree->runtime->update_draw(input_data_.node_tree->runtime->udh); } } + + realtime_compositor::RenderContext *render_context() const override + { + return input_data_.render_context; + } }; /* Render Realtime Compositor */ @@ -507,12 +516,13 @@ void Render::compositor_execute(const Scene &scene, const RenderData &render_data, const bNodeTree &node_tree, const bool use_file_output, - const char *view_name) + const char *view_name, + blender::realtime_compositor::RenderContext *render_context) { std::unique_lock lock(gpu_compositor_mutex); blender::render::ContextInputData input_data( - scene, render_data, node_tree, use_file_output, view_name); + scene, render_data, node_tree, use_file_output, view_name, render_context); if (gpu_compositor == nullptr) { gpu_compositor = new blender::render::RealtimeCompositor(*this, input_data); @@ -536,9 +546,11 @@ void RE_compositor_execute(Render &render, const RenderData &render_data, const bNodeTree &node_tree, const bool use_file_output, - const char *view_name) + const char *view_name, + blender::realtime_compositor::RenderContext *render_context) { - render.compositor_execute(scene, render_data, node_tree, use_file_output, view_name); + render.compositor_execute( + scene, render_data, node_tree, use_file_output, view_name, render_context); } void RE_compositor_free(Render &render) diff --git a/source/blender/render/intern/pipeline.cc b/source/blender/render/intern/pipeline.cc index 1c8bfd6ac12..68da6d4631f 100644 --- a/source/blender/render/intern/pipeline.cc +++ b/source/blender/render/intern/pipeline.cc @@ -66,6 +66,8 @@ #include "NOD_composite.hh" +#include "COM_render_context.hh" + #include "DEG_depsgraph.hh" #include "DEG_depsgraph_build.hh" #include "DEG_depsgraph_debug.hh" @@ -1267,10 +1269,18 @@ static void do_render_compositor(Render *re) /* If we have consistent depsgraph now would be a time to update them. */ } + blender::realtime_compositor::RenderContext compositor_render_context; LISTBASE_FOREACH (RenderView *, rv, &re->result->views) { - ntreeCompositExecTree( - re, re->pipeline_scene_eval, ntree, &re->r, true, G.background == 0, rv->name); + ntreeCompositExecTree(re, + re->pipeline_scene_eval, + ntree, + &re->r, + true, + G.background == 0, + rv->name, + &compositor_render_context); } + compositor_render_context.save_file_outputs(re->pipeline_scene_eval); ntree->runtime->stats_draw = nullptr; ntree->runtime->test_break = nullptr; diff --git a/source/blender/render/intern/render_result.cc b/source/blender/render/intern/render_result.cc index 149e94f2ea1..be57c21cd00 100644 --- a/source/blender/render/intern/render_result.cc +++ b/source/blender/render/intern/render_result.cc @@ -1055,6 +1055,7 @@ ImBuf *RE_render_result_rect_to_ibuf(RenderResult *rr, if (rv->ibuf) { IMB_assign_byte_buffer(ibuf, rv->ibuf->byte_buffer.data, IB_DO_NOT_TAKE_OWNERSHIP); IMB_assign_float_buffer(ibuf, rv->ibuf->float_buffer.data, IB_DO_NOT_TAKE_OWNERSHIP); + ibuf->channels = rv->ibuf->channels; } /* float factor for random dither, imbuf takes care of it */ diff --git a/source/blender/render/intern/render_types.h b/source/blender/render/intern/render_types.h index 030842b082b..cd06cab4576 100644 --- a/source/blender/render/intern/render_types.h +++ b/source/blender/render/intern/render_types.h @@ -23,6 +23,10 @@ #include "tile_highlight.h" +namespace blender::realtime_compositor { +class RenderContext; +} + struct bNodeTree; struct Depsgraph; struct GSet; @@ -46,7 +50,8 @@ struct BaseRender { const RenderData &render_data, const bNodeTree &node_tree, const bool use_file_output, - const char *view_name) = 0; + const char *view_name, + blender::realtime_compositor::RenderContext *render_context) = 0; virtual void compositor_free() = 0; virtual void display_init(RenderResult *render_result) = 0; @@ -93,11 +98,13 @@ struct ViewRender : public BaseRender { return nullptr; } - void compositor_execute(const Scene & /*scene*/, - const RenderData & /*render_data*/, - const bNodeTree & /*node_tree*/, - const bool /*use_file_output*/, - const char * /*view_name*/) override + void compositor_execute( + const Scene & /*scene*/, + const RenderData & /*render_data*/, + const bNodeTree & /*node_tree*/, + const bool /*use_file_output*/, + const char * /*view_name*/, + blender::realtime_compositor::RenderContext * /*render_context*/) override { } void compositor_free() override {} @@ -141,7 +148,8 @@ struct Render : public BaseRender { const RenderData &render_data, const bNodeTree &node_tree, const bool use_file_output, - const char *view_name) override; + const char *view_name, + blender::realtime_compositor::RenderContext *render_context) override; void compositor_free() override; void display_init(RenderResult *render_result) override;