diff options
author | Jacques Lucke <jacques@blender.org> | 2021-06-14 13:44:13 +0300 |
---|---|---|
committer | Jacques Lucke <jacques@blender.org> | 2021-06-14 13:44:13 +0300 |
commit | dddcf1e9bbf4a6d1f4ff03eaf0cb7e9228b18ec5 (patch) | |
tree | c20defa7efd54c933d20a296abefe567909bb6c0 /source/blender/compositor/intern | |
parent | 3b162b7c185d089e93d892169a458d552196b7b6 (diff) | |
parent | c9dc55301cd7903b7ef7c045d337ada29aa809a1 (diff) |
Merge branch 'master' into temp-compact-node-prototypetemp-compact-node-prototype
Diffstat (limited to 'source/blender/compositor/intern')
57 files changed, 3057 insertions, 1224 deletions
diff --git a/source/blender/compositor/intern/COM_BufferOperation.cc b/source/blender/compositor/intern/COM_BufferOperation.cc new file mode 100644 index 00000000000..8464d01801f --- /dev/null +++ b/source/blender/compositor/intern/COM_BufferOperation.cc @@ -0,0 +1,65 @@ +/* + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * Copyright 2021, Blender Foundation. + */ + +#include "COM_BufferOperation.h" + +namespace blender::compositor { + +BufferOperation::BufferOperation(MemoryBuffer *buffer, DataType data_type) +{ + buffer_ = buffer; + /* TODO: Implement a MemoryBuffer get_size() method returning a Size2d type. Shorten following + * code to: set_resolution(buffer.get_size()) */ + unsigned int resolution[2]; + resolution[0] = buffer->getWidth(); + resolution[1] = buffer->getHeight(); + setResolution(resolution); + addOutputSocket(data_type); +} + +void *BufferOperation::initializeTileData(rcti * /*rect*/) +{ + return buffer_; +} + +void BufferOperation::executePixelSampled(float output[4], float x, float y, PixelSampler sampler) +{ + switch (sampler) { + case PixelSampler::Nearest: + buffer_->read(output, x, y); + break; + case PixelSampler::Bilinear: + default: + buffer_->readBilinear(output, x, y); + break; + case PixelSampler::Bicubic: + /* No bicubic. Same implementation as ReadBufferOperation. */ + buffer_->readBilinear(output, x, y); + break; + } +} + +void BufferOperation::executePixelFiltered( + float output[4], float x, float y, float dx[2], float dy[2]) +{ + const float uv[2] = {x, y}; + const float deriv[2][2] = {{dx[0], dx[1]}, {dy[0], dy[1]}}; + buffer_->readEWA(output, uv, deriv); +} + +} // namespace blender::compositor diff --git a/source/blender/compositor/intern/COM_SocketReader.cc b/source/blender/compositor/intern/COM_BufferOperation.h index 93c8a143b86..f87cd4db94e 100644 --- a/source/blender/compositor/intern/COM_SocketReader.cc +++ b/source/blender/compositor/intern/COM_BufferOperation.h @@ -13,7 +13,25 @@ * along with this program; if not, write to the Free Software Foundation, * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * - * Copyright 2011, Blender Foundation. + * Copyright 2021, Blender Foundation. */ -#include "COM_SocketReader.h" +#pragma once + +#include "COM_NodeOperation.h" + +namespace blender::compositor { + +class BufferOperation : public NodeOperation { + private: + MemoryBuffer *buffer_; + + public: + BufferOperation(MemoryBuffer *buffer, DataType data_type); + + void *initializeTileData(rcti *rect) override; + void executePixelSampled(float output[4], float x, float y, PixelSampler sampler) override; + void executePixelFiltered(float output[4], float x, float y, float dx[2], float dy[2]) override; +}; + +} // namespace blender::compositor diff --git a/source/blender/compositor/intern/COM_CPUDevice.cc b/source/blender/compositor/intern/COM_CPUDevice.cc index b520a437008..2ca5557e278 100644 --- a/source/blender/compositor/intern/COM_CPUDevice.cc +++ b/source/blender/compositor/intern/COM_CPUDevice.cc @@ -18,19 +18,36 @@ #include "COM_CPUDevice.h" +#include "COM_ExecutionGroup.h" + +#include "BLI_rect.h" + +namespace blender::compositor { + CPUDevice::CPUDevice(int thread_id) : m_thread_id(thread_id) { } -void CPUDevice::execute(WorkPackage *work) +void CPUDevice::execute(WorkPackage *work_package) { - const unsigned int chunkNumber = work->chunk_number; - ExecutionGroup *executionGroup = work->execution_group; - rcti rect; + switch (work_package->type) { + case eWorkPackageType::Tile: { + const unsigned int chunkNumber = work_package->chunk_number; + ExecutionGroup *executionGroup = work_package->execution_group; - executionGroup->determineChunkRect(&rect, chunkNumber); + executionGroup->getOutputOperation()->executeRegion(&work_package->rect, chunkNumber); + executionGroup->finalizeChunkExecution(chunkNumber, nullptr); + break; + } + case eWorkPackageType::CustomFunction: { + work_package->execute_fn(); + break; + } + } - executionGroup->getOutputOperation()->executeRegion(&rect, chunkNumber); - - executionGroup->finalizeChunkExecution(chunkNumber, nullptr); + if (work_package->executed_fn) { + work_package->executed_fn(); + } } + +} // namespace blender::compositor diff --git a/source/blender/compositor/intern/COM_CPUDevice.h b/source/blender/compositor/intern/COM_CPUDevice.h index 6df1f41419d..99629890b30 100644 --- a/source/blender/compositor/intern/COM_CPUDevice.h +++ b/source/blender/compositor/intern/COM_CPUDevice.h @@ -20,6 +20,8 @@ #include "COM_Device.h" +namespace blender::compositor { + /** * \brief class representing a CPU device. * \note for every hardware thread in the system a CPUDevice instance @@ -43,3 +45,5 @@ class CPUDevice : public Device { protected: int m_thread_id; }; + +} // namespace blender::compositor diff --git a/source/blender/compositor/intern/COM_ChunkOrder.cc b/source/blender/compositor/intern/COM_ChunkOrder.cc index 9687154120d..a03718d720d 100644 --- a/source/blender/compositor/intern/COM_ChunkOrder.cc +++ b/source/blender/compositor/intern/COM_ChunkOrder.cc @@ -20,6 +20,8 @@ #include "BLI_math.h" +namespace blender::compositor { + void ChunkOrder::update_distance(ChunkOrderHotspot *hotspots, unsigned int len_hotspots) { double new_distance = DBL_MAX; @@ -36,3 +38,5 @@ bool operator<(const ChunkOrder &a, const ChunkOrder &b) { return a.distance < b.distance; } + +} // namespace blender::compositor diff --git a/source/blender/compositor/intern/COM_ChunkOrder.h b/source/blender/compositor/intern/COM_ChunkOrder.h index a634309f345..a697f9231d9 100644 --- a/source/blender/compositor/intern/COM_ChunkOrder.h +++ b/source/blender/compositor/intern/COM_ChunkOrder.h @@ -24,6 +24,8 @@ #include "COM_ChunkOrderHotspot.h" +namespace blender::compositor { + /** Helper to determine the order how chunks are prioritized during execution. */ struct ChunkOrder { unsigned int index = 0; @@ -39,3 +41,5 @@ struct ChunkOrder { MEM_CXX_CLASS_ALLOC_FUNCS("COM:ChunkOrderHotspot") #endif }; + +} // namespace blender::compositor diff --git a/source/blender/compositor/intern/COM_ChunkOrderHotspot.cc b/source/blender/compositor/intern/COM_ChunkOrderHotspot.cc index d31ff518ecd..b8e19fc2c34 100644 --- a/source/blender/compositor/intern/COM_ChunkOrderHotspot.cc +++ b/source/blender/compositor/intern/COM_ChunkOrderHotspot.cc @@ -19,6 +19,8 @@ #include "COM_ChunkOrderHotspot.h" #include <cmath> +namespace blender::compositor { + double ChunkOrderHotspot::calc_distance(int x, int y) { int dx = this->x - x; @@ -27,3 +29,5 @@ double ChunkOrderHotspot::calc_distance(int x, int y) result += (double)this->addition; return result; } + +} // namespace blender::compositor diff --git a/source/blender/compositor/intern/COM_ChunkOrderHotspot.h b/source/blender/compositor/intern/COM_ChunkOrderHotspot.h index d7f40921836..249b328f5c2 100644 --- a/source/blender/compositor/intern/COM_ChunkOrderHotspot.h +++ b/source/blender/compositor/intern/COM_ChunkOrderHotspot.h @@ -22,6 +22,8 @@ # include "MEM_guardedalloc.h" #endif +namespace blender::compositor { + struct ChunkOrderHotspot { int x; int y; @@ -37,3 +39,5 @@ struct ChunkOrderHotspot { MEM_CXX_CLASS_ALLOC_FUNCS("COM:ChunkOrderHotspot") #endif }; + +} // namespace blender::compositor diff --git a/source/blender/compositor/intern/COM_CompositorContext.cc b/source/blender/compositor/intern/COM_CompositorContext.cc index e2447fb5c13..61e299c045e 100644 --- a/source/blender/compositor/intern/COM_CompositorContext.cc +++ b/source/blender/compositor/intern/COM_CompositorContext.cc @@ -20,22 +20,43 @@ #include "COM_defines.h" #include <cstdio> +#include "BLI_assert.h" +#include "DNA_userdef_types.h" + +namespace blender::compositor { + CompositorContext::CompositorContext() { this->m_scene = nullptr; this->m_rd = nullptr; - this->m_quality = CompositorQuality::High; + this->m_quality = eCompositorQuality::High; this->m_hasActiveOpenCLDevices = false; this->m_fastCalculation = false; this->m_viewSettings = nullptr; this->m_displaySettings = nullptr; + this->m_bnodetree = nullptr; } int CompositorContext::getFramenumber() const { - if (this->m_rd) { - return this->m_rd->cfra; - } + BLI_assert(m_rd); + return m_rd->cfra; +} - return -1; /* this should never happen */ +eExecutionModel CompositorContext::get_execution_model() const +{ + if (U.experimental.use_full_frame_compositor) { + BLI_assert(m_bnodetree != nullptr); + switch (m_bnodetree->execution_mode) { + case 1: + return eExecutionModel::FullFrame; + case 0: + return eExecutionModel::Tiled; + default: + BLI_assert(!"Invalid execution mode"); + } + } + return eExecutionModel::Tiled; } + +} // namespace blender::compositor diff --git a/source/blender/compositor/intern/COM_CompositorContext.h b/source/blender/compositor/intern/COM_CompositorContext.h index 46cf65bbb79..56251511576 100644 --- a/source/blender/compositor/intern/COM_CompositorContext.h +++ b/source/blender/compositor/intern/COM_CompositorContext.h @@ -19,13 +19,18 @@ #pragma once #include "BLI_rect.h" -#include "COM_defines.h" + +#include "COM_Enums.h" + #include "DNA_color_types.h" #include "DNA_node_types.h" #include "DNA_scene_types.h" + #include <string> #include <vector> +namespace blender::compositor { + /** * \brief Overall context of the compositor */ @@ -33,8 +38,8 @@ class CompositorContext { private: /** * \brief The rendering field describes if we are rendering (F12) or if we are editing (Node - * editor) This field is initialized in ExecutionSystem and must only be read from that point on. - * \see ExecutionSystem + * editor) This field is initialized in ExecutionSystem and must only be read from that point + * on. \see ExecutionSystem */ bool m_rendering; @@ -43,7 +48,7 @@ class CompositorContext { * This field is initialized in ExecutionSystem and must only be read from that point on. * \see ExecutionSystem */ - CompositorQuality m_quality; + eCompositorQuality m_quality; Scene *m_scene; @@ -200,7 +205,7 @@ class CompositorContext { /** * \brief set the quality */ - void setQuality(CompositorQuality quality) + void setQuality(eCompositorQuality quality) { this->m_quality = quality; } @@ -208,7 +213,7 @@ class CompositorContext { /** * \brief get the quality */ - CompositorQuality getQuality() const + eCompositorQuality getQuality() const { return this->m_quality; } @@ -276,4 +281,11 @@ class CompositorContext { { return m_rd->size * 0.01f; } + + /** + * Get active execution model. + */ + eExecutionModel get_execution_model() const; }; + +} // namespace blender::compositor diff --git a/source/blender/compositor/intern/COM_Converter.cc b/source/blender/compositor/intern/COM_Converter.cc index d5bce636b8c..af593b2e1b5 100644 --- a/source/blender/compositor/intern/COM_Converter.cc +++ b/source/blender/compositor/intern/COM_Converter.cc @@ -26,6 +26,7 @@ #include "COM_NodeOperationBuilder.h" #include "COM_AlphaOverNode.h" +#include "COM_AntiAliasingNode.h" #include "COM_BilateralBlurNode.h" #include "COM_BlurNode.h" #include "COM_BokehBlurNode.h" @@ -115,6 +116,8 @@ #include "COM_ViewerNode.h" #include "COM_ZCombineNode.h" +namespace blender::compositor { + bool COM_bnode_is_fast_node(const bNode &b_node) { return !ELEM(b_node.type, @@ -418,6 +421,9 @@ Node *COM_convert_bnode(bNode *b_node) case CMP_NODE_EXPOSURE: node = new ExposureNode(b_node); break; + case CMP_NODE_ANTIALIASING: + node = new AntiAliasingNode(b_node); + break; } return node; } @@ -454,7 +460,7 @@ void COM_convert_resolution(NodeOperationBuilder &builder, NodeOperationOutput *fromSocket, NodeOperationInput *toSocket) { - InputResizeMode mode = toSocket->getResizeMode(); + ResizeMode mode = toSocket->getResizeMode(); NodeOperation *toOperation = &toSocket->getOperation(); const float toWidth = toOperation->getWidth(); @@ -470,22 +476,22 @@ void COM_convert_resolution(NodeOperationBuilder &builder, float scaleY = 0; switch (mode) { - case COM_SC_NO_RESIZE: + case ResizeMode::None: break; - case COM_SC_CENTER: + case ResizeMode::Center: doCenter = true; break; - case COM_SC_FIT_WIDTH: + case ResizeMode::FitWidth: doCenter = true; doScale = true; scaleX = scaleY = toWidth / fromWidth; break; - case COM_SC_FIT_HEIGHT: + case ResizeMode::FitHeight: doCenter = true; doScale = true; scaleX = scaleY = toHeight / fromHeight; break; - case COM_SC_FIT: + case ResizeMode::FitAny: doCenter = true; doScale = true; scaleX = toWidth / fromWidth; @@ -497,7 +503,7 @@ void COM_convert_resolution(NodeOperationBuilder &builder, scaleY = scaleX; } break; - case COM_SC_STRETCH: + case ResizeMode::Stretch: doCenter = true; doScale = true; scaleX = toWidth / fromWidth; @@ -510,8 +516,8 @@ void COM_convert_resolution(NodeOperationBuilder &builder, ScaleOperation *scaleOperation = nullptr; if (doScale) { scaleOperation = new ScaleOperation(); - scaleOperation->getInputSocket(1)->setResizeMode(COM_SC_NO_RESIZE); - scaleOperation->getInputSocket(2)->setResizeMode(COM_SC_NO_RESIZE); + scaleOperation->getInputSocket(1)->setResizeMode(ResizeMode::None); + scaleOperation->getInputSocket(2)->setResizeMode(ResizeMode::None); first = scaleOperation; SetValueOperation *sxop = new SetValueOperation(); sxop->setValue(scaleX); @@ -530,8 +536,8 @@ void COM_convert_resolution(NodeOperationBuilder &builder, } TranslateOperation *translateOperation = new TranslateOperation(); - translateOperation->getInputSocket(1)->setResizeMode(COM_SC_NO_RESIZE); - translateOperation->getInputSocket(2)->setResizeMode(COM_SC_NO_RESIZE); + translateOperation->getInputSocket(1)->setResizeMode(ResizeMode::None); + translateOperation->getInputSocket(2)->setResizeMode(ResizeMode::None); if (!first) { first = translateOperation; } @@ -551,15 +557,17 @@ void COM_convert_resolution(NodeOperationBuilder &builder, builder.addOperation(translateOperation); if (doScale) { - translateOperation->getInputSocket(0)->setResizeMode(COM_SC_NO_RESIZE); + translateOperation->getInputSocket(0)->setResizeMode(ResizeMode::None); builder.addLink(scaleOperation->getOutputSocket(), translateOperation->getInputSocket(0)); } /* remove previous link and replace */ builder.removeInputLink(toSocket); - first->getInputSocket(0)->setResizeMode(COM_SC_NO_RESIZE); - toSocket->setResizeMode(COM_SC_NO_RESIZE); + first->getInputSocket(0)->setResizeMode(ResizeMode::None); + toSocket->setResizeMode(ResizeMode::None); builder.addLink(fromSocket, first->getInputSocket(0)); builder.addLink(translateOperation->getOutputSocket(), toSocket); } } + +} // namespace blender::compositor diff --git a/source/blender/compositor/intern/COM_Converter.h b/source/blender/compositor/intern/COM_Converter.h index 59be34bf0e3..28136437103 100644 --- a/source/blender/compositor/intern/COM_Converter.h +++ b/source/blender/compositor/intern/COM_Converter.h @@ -18,14 +18,17 @@ #pragma once +#include "COM_NodeOperation.h" + #ifdef WITH_CXX_GUARDEDALLOC # include "MEM_guardedalloc.h" #endif struct bNode; +namespace blender::compositor { + class Node; -class NodeOperation; class NodeOperationInput; class NodeOperationOutput; class NodeOperationBuilder; @@ -65,3 +68,5 @@ NodeOperation *COM_convert_data_type(const NodeOperationOutput &from, void COM_convert_resolution(NodeOperationBuilder &builder, NodeOperationOutput *fromSocket, NodeOperationInput *toSocket); + +} // namespace blender::compositor diff --git a/source/blender/compositor/intern/COM_Debug.cc b/source/blender/compositor/intern/COM_Debug.cc index c299bd1c72d..4cf7e09a7d8 100644 --- a/source/blender/compositor/intern/COM_Debug.cc +++ b/source/blender/compositor/intern/COM_Debug.cc @@ -18,30 +18,29 @@ #include "COM_Debug.h" -#ifdef COM_DEBUG - -# include <map> -# include <typeinfo> -# include <vector> +#include <map> +#include <typeinfo> +#include <vector> extern "C" { -# include "BLI_fileops.h" -# include "BLI_path_util.h" -# include "BLI_string.h" -# include "BLI_sys_types.h" - -# include "BKE_appdir.h" -# include "BKE_node.h" -# include "DNA_node_types.h" +#include "BLI_fileops.h" +#include "BLI_path_util.h" +#include "BLI_string.h" +#include "BLI_sys_types.h" + +#include "BKE_appdir.h" +#include "BKE_node.h" +#include "DNA_node_types.h" } -# include "COM_ExecutionGroup.h" -# include "COM_ExecutionSystem.h" -# include "COM_Node.h" +#include "COM_ExecutionSystem.h" +#include "COM_Node.h" + +#include "COM_ReadBufferOperation.h" +#include "COM_ViewerOperation.h" +#include "COM_WriteBufferOperation.h" -# include "COM_ReadBufferOperation.h" -# include "COM_ViewerOperation.h" -# include "COM_WriteBufferOperation.h" +namespace blender::compositor { int DebugInfo::m_file_index = 0; DebugInfo::NodeNameMap DebugInfo::m_node_names; @@ -68,52 +67,8 @@ std::string DebugInfo::operation_name(const NodeOperation *op) return ""; } -void DebugInfo::convert_started() -{ - m_op_names.clear(); -} - -void DebugInfo::execute_started(const ExecutionSystem *system) -{ - m_file_index = 1; - m_group_states.clear(); - for (ExecutionGroup *execution_group : system->m_groups) { - m_group_states[execution_group] = EG_WAIT; - } -} - -void DebugInfo::node_added(const Node *node) -{ - m_node_names[node] = std::string(node->getbNode() ? node->getbNode()->name : ""); -} - -void DebugInfo::node_to_operations(const Node *node) -{ - m_current_node_name = m_node_names[node]; -} - -void DebugInfo::operation_added(const NodeOperation *operation) -{ - m_op_names[operation] = m_current_node_name; -} - -void DebugInfo::operation_read_write_buffer(const NodeOperation *operation) -{ - m_current_op_name = m_op_names[operation]; -} - -void DebugInfo::execution_group_started(const ExecutionGroup *group) -{ - m_group_states[group] = EG_RUNNING; -} - -void DebugInfo::execution_group_finished(const ExecutionGroup *group) -{ - m_group_states[group] = EG_FINISHED; -} - int DebugInfo::graphviz_operation(const ExecutionSystem *system, - const NodeOperation *operation, + NodeOperation *operation, const ExecutionGroup *group, char *str, int maxlen) @@ -121,7 +76,7 @@ int DebugInfo::graphviz_operation(const ExecutionSystem *system, int len = 0; std::string fillcolor = "gainsboro"; - if (operation->isViewerOperation()) { + if (operation->get_flags().is_viewer_operation) { const ViewerOperation *viewer = (const ViewerOperation *)operation; if (viewer->isActiveViewerOutput()) { fillcolor = "lightskyblue1"; @@ -133,13 +88,13 @@ int DebugInfo::graphviz_operation(const ExecutionSystem *system, else if (operation->isOutputOperation(system->getContext().isRendering())) { fillcolor = "dodgerblue1"; } - else if (operation->isSetOperation()) { + else if (operation->get_flags().is_set_operation) { fillcolor = "khaki1"; } - else if (operation->isReadBufferOperation()) { + else if (operation->get_flags().is_read_buffer_operation) { fillcolor = "darkolivegreen3"; } - else if (operation->isWriteBufferOperation()) { + else if (operation->get_flags().is_write_buffer_operation) { fillcolor = "darkorange"; } @@ -256,12 +211,14 @@ int DebugInfo::graphviz_legend_group( return len; } -int DebugInfo::graphviz_legend(char *str, int maxlen) +int DebugInfo::graphviz_legend(char *str, int maxlen, const bool has_execution_groups) { int len = 0; len += snprintf(str + len, maxlen > len ? maxlen - len : 0, "{\r\n"); - len += snprintf(str + len, maxlen > len ? maxlen - len : 0, "rank = sink;\r\n"); + if (has_execution_groups) { + len += snprintf(str + len, maxlen > len ? maxlen - len : 0, "rank = sink;\r\n"); + } len += snprintf( str + len, maxlen > len ? maxlen - len : 0, "Legend [shape=none, margin=0, label=<\r\n"); @@ -281,21 +238,24 @@ int DebugInfo::graphviz_legend(char *str, int maxlen) "Viewer", "lightskyblue3", str + len, maxlen > len ? maxlen - len : 0); len += graphviz_legend_color( "Active Viewer", "lightskyblue1", str + len, maxlen > len ? maxlen - len : 0); - len += graphviz_legend_color( - "Write Buffer", "darkorange", str + len, maxlen > len ? maxlen - len : 0); - len += graphviz_legend_color( - "Read Buffer", "darkolivegreen3", str + len, maxlen > len ? maxlen - len : 0); + if (has_execution_groups) { + len += graphviz_legend_color( + "Write Buffer", "darkorange", str + len, maxlen > len ? maxlen - len : 0); + len += graphviz_legend_color( + "Read Buffer", "darkolivegreen3", str + len, maxlen > len ? maxlen - len : 0); + } len += graphviz_legend_color( "Input Value", "khaki1", str + len, maxlen > len ? maxlen - len : 0); - len += snprintf(str + len, maxlen > len ? maxlen - len : 0, "<TR><TD></TD></TR>\r\n"); - - len += graphviz_legend_group( - "Group Waiting", "white", "dashed", str + len, maxlen > len ? maxlen - len : 0); - len += graphviz_legend_group( - "Group Running", "firebrick1", "solid", str + len, maxlen > len ? maxlen - len : 0); - len += graphviz_legend_group( - "Group Finished", "chartreuse4", "solid", str + len, maxlen > len ? maxlen - len : 0); + if (has_execution_groups) { + len += snprintf(str + len, maxlen > len ? maxlen - len : 0, "<TR><TD></TD></TR>\r\n"); + len += graphviz_legend_group( + "Group Waiting", "white", "dashed", str + len, maxlen > len ? maxlen - len : 0); + len += graphviz_legend_group( + "Group Running", "firebrick1", "solid", str + len, maxlen > len ? maxlen - len : 0); + len += graphviz_legend_group( + "Group Finished", "chartreuse4", "solid", str + len, maxlen > len ? maxlen - len : 0); + } len += snprintf(str + len, maxlen > len ? maxlen - len : 0, "</TABLE>\r\n"); len += snprintf(str + len, maxlen > len ? maxlen - len : 0, ">];\r\n"); @@ -360,7 +320,7 @@ bool DebugInfo::graphviz_system(const ExecutionSystem *system, char *str, int ma } for (NodeOperation *operation : system->m_operations) { - if (operation->isReadBufferOperation()) { + if (operation->get_flags().is_read_buffer_operation) { ReadBufferOperation *read = (ReadBufferOperation *)operation; WriteBufferOperation *write = read->getMemoryProxy()->getWriteBufferOperation(); std::vector<std::string> &read_groups = op_groups[read]; @@ -381,8 +341,8 @@ bool DebugInfo::graphviz_system(const ExecutionSystem *system, char *str, int ma } for (NodeOperation *op : system->m_operations) { - for (NodeOperationInput *to : op->m_inputs) { - NodeOperationOutput *from = to->getLink(); + for (NodeOperationInput &to : op->m_inputs) { + NodeOperationOutput *from = to.getLink(); if (!from) { continue; @@ -401,7 +361,7 @@ bool DebugInfo::graphviz_system(const ExecutionSystem *system, char *str, int ma break; } - NodeOperation *to_op = &to->getOperation(); + NodeOperation *to_op = &to.getOperation(); NodeOperation *from_op = &from->getOperation(); std::vector<std::string> &from_groups = op_groups[from_op]; std::vector<std::string> &to_groups = op_groups[to_op]; @@ -412,7 +372,7 @@ bool DebugInfo::graphviz_system(const ExecutionSystem *system, char *str, int ma from_op, from, to_op, - to); + &to); for (int k = 0; k < from_groups.size(); k++) { for (int l = 0; l < to_groups.size(); l++) { len += snprintf(str + len, @@ -423,7 +383,7 @@ bool DebugInfo::graphviz_system(const ExecutionSystem *system, char *str, int ma from, to_op, to_groups[l].c_str(), - to); + &to); len += snprintf( str + len, maxlen > len ? maxlen - len : 0, " [color=%s]", color.c_str()); len += snprintf(str + len, maxlen > len ? maxlen - len : 0, "\r\n"); @@ -432,7 +392,9 @@ bool DebugInfo::graphviz_system(const ExecutionSystem *system, char *str, int ma } } - len += graphviz_legend(str + len, maxlen > len ? maxlen - len : 0); + const bool has_execution_groups = system->getContext().get_execution_model() == + eExecutionModel::Tiled; + len += graphviz_legend(str + len, maxlen > len ? maxlen - len : 0, has_execution_groups); len += snprintf(str + len, maxlen > len ? maxlen - len : 0, "}\r\n"); @@ -441,6 +403,9 @@ bool DebugInfo::graphviz_system(const ExecutionSystem *system, char *str, int ma void DebugInfo::graphviz(const ExecutionSystem *system) { + if (!COM_EXPORT_GRAPHVIZ) { + return; + } char str[1000000]; if (graphviz_system(system, str, sizeof(str) - 1)) { char basename[FILE_MAX]; @@ -450,48 +415,12 @@ void DebugInfo::graphviz(const ExecutionSystem *system) BLI_join_dirfile(filename, sizeof(filename), BKE_tempdir_session(), basename); m_file_index++; + std::cout << "Writing compositor debug to: " << filename << "\n"; + FILE *fp = BLI_fopen(filename, "wb"); fputs(str, fp); fclose(fp); } } -#else - -std::string DebugInfo::node_name(const Node * /*node*/) -{ - return ""; -} -std::string DebugInfo::operation_name(const NodeOperation * /*op*/) -{ - return ""; -} -void DebugInfo::convert_started() -{ -} -void DebugInfo::execute_started(const ExecutionSystem * /*system*/) -{ -} -void DebugInfo::node_added(const Node * /*node*/) -{ -} -void DebugInfo::node_to_operations(const Node * /*node*/) -{ -} -void DebugInfo::operation_added(const NodeOperation * /*operation*/) -{ -} -void DebugInfo::operation_read_write_buffer(const NodeOperation * /*operation*/) -{ -} -void DebugInfo::execution_group_started(const ExecutionGroup * /*group*/) -{ -} -void DebugInfo::execution_group_finished(const ExecutionGroup * /*group*/) -{ -} -void DebugInfo::graphviz(const ExecutionSystem * /*system*/) -{ -} - -#endif +} // namespace blender::compositor diff --git a/source/blender/compositor/intern/COM_Debug.h b/source/blender/compositor/intern/COM_Debug.h index 0107d8b396d..0de3a5e39dc 100644 --- a/source/blender/compositor/intern/COM_Debug.h +++ b/source/blender/compositor/intern/COM_Debug.h @@ -21,10 +21,14 @@ #include <map> #include <string> +#include "COM_ExecutionSystem.h" +#include "COM_NodeOperation.h" #include "COM_defines.h" +namespace blender::compositor { + +static constexpr bool COM_EXPORT_GRAPHVIZ = false; class Node; -class NodeOperation; class ExecutionSystem; class ExecutionGroup; @@ -39,23 +43,84 @@ class DebugInfo { static std::string node_name(const Node *node); static std::string operation_name(const NodeOperation *op); - static void convert_started(); - static void execute_started(const ExecutionSystem *system); + private: + static int m_file_index; + /** Map nodes to usable names for debug output. */ + static NodeNameMap m_node_names; + /** Map operations to usable names for debug output. */ + static OpNameMap m_op_names; + /** Base name for all operations added by a node. */ + static std::string m_current_node_name; + /** Base name for automatic sub-operations. */ + static std::string m_current_op_name; + /** For visualizing group states. */ + static GroupStateMap m_group_states; + + public: + static void convert_started() + { + if (COM_EXPORT_GRAPHVIZ) { + m_op_names.clear(); + } + } + + static void execute_started(const ExecutionSystem *system) + { + if (COM_EXPORT_GRAPHVIZ) { + m_file_index = 1; + m_group_states.clear(); + for (ExecutionGroup *execution_group : system->m_groups) { + m_group_states[execution_group] = EG_WAIT; + } + } + }; + + static void node_added(const Node *node) + { + if (COM_EXPORT_GRAPHVIZ) { + m_node_names[node] = std::string(node->getbNode() ? node->getbNode()->name : ""); + } + } - static void node_added(const Node *node); - static void node_to_operations(const Node *node); - static void operation_added(const NodeOperation *operation); - static void operation_read_write_buffer(const NodeOperation *operation); + static void node_to_operations(const Node *node) + { + if (COM_EXPORT_GRAPHVIZ) { + m_current_node_name = m_node_names[node]; + } + } - static void execution_group_started(const ExecutionGroup *group); - static void execution_group_finished(const ExecutionGroup *group); + static void operation_added(const NodeOperation *operation) + { + if (COM_EXPORT_GRAPHVIZ) { + m_op_names[operation] = m_current_node_name; + } + }; + + static void operation_read_write_buffer(const NodeOperation *operation) + { + if (COM_EXPORT_GRAPHVIZ) { + m_current_op_name = m_op_names[operation]; + } + }; + + static void execution_group_started(const ExecutionGroup *group) + { + if (COM_EXPORT_GRAPHVIZ) { + m_group_states[group] = EG_RUNNING; + } + }; + static void execution_group_finished(const ExecutionGroup *group) + { + if (COM_EXPORT_GRAPHVIZ) { + m_group_states[group] = EG_FINISHED; + } + }; static void graphviz(const ExecutionSystem *system); -#ifdef COM_DEBUG protected: static int graphviz_operation(const ExecutionSystem *system, - const NodeOperation *operation, + NodeOperation *operation, const ExecutionGroup *group, char *str, int maxlen); @@ -64,20 +129,8 @@ class DebugInfo { const char *name, const char *color, const char *style, char *str, int maxlen); static int graphviz_legend_group( const char *name, const char *color, const char *style, char *str, int maxlen); - static int graphviz_legend(char *str, int maxlen); + static int graphviz_legend(char *str, int maxlen, bool has_execution_groups); static bool graphviz_system(const ExecutionSystem *system, char *str, int maxlen); - - private: - static int m_file_index; - /** Map nodes to usable names for debug output. */ - static NodeNameMap m_node_names; - /** Map operations to usable names for debug output. */ - static OpNameMap m_op_names; - /** Base name for all operations added by a node. */ - static std::string m_current_node_name; - /** Base name for automatic sub-operations. */ - static std::string m_current_op_name; - /** For visualizing group states. */ - static GroupStateMap m_group_states; -#endif }; + +} // namespace blender::compositor diff --git a/source/blender/compositor/intern/COM_Device.h b/source/blender/compositor/intern/COM_Device.h index 0a456760045..c848672a405 100644 --- a/source/blender/compositor/intern/COM_Device.h +++ b/source/blender/compositor/intern/COM_Device.h @@ -20,6 +20,8 @@ #include "COM_WorkPackage.h" +namespace blender::compositor { + /** * \brief Abstract class for device implementations to be used by the Compositor. * devices are queried, initialized and used by the WorkScheduler. @@ -28,6 +30,14 @@ class Device { public: + Device() = default; + + Device(const Device &other) = delete; + Device(Device &&other) noexcept = default; + + Device &operator=(const Device &other) = delete; + Device &operator=(Device &&other) = delete; + /** * \brief Declaration of the virtual destructor * \note resolve warning gcc 4.7 @@ -46,3 +56,5 @@ class Device { MEM_CXX_CLASS_ALLOC_FUNCS("COM:Device") #endif }; + +} // namespace blender::compositor diff --git a/source/blender/compositor/intern/COM_Enums.cc b/source/blender/compositor/intern/COM_Enums.cc new file mode 100644 index 00000000000..d218de92544 --- /dev/null +++ b/source/blender/compositor/intern/COM_Enums.cc @@ -0,0 +1,61 @@ +/* + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * Copyright 2021, Blender Foundation. + */ + +#include "COM_Enums.h" + +namespace blender::compositor { + +std::ostream &operator<<(std::ostream &os, const eCompositorPriority &priority) +{ + switch (priority) { + case eCompositorPriority::High: { + os << "Priority::High"; + break; + } + case eCompositorPriority::Medium: { + os << "Priority::Medium"; + break; + } + case eCompositorPriority::Low: { + os << "Priority::Low"; + break; + } + } + return os; +} + +std::ostream &operator<<(std::ostream &os, const eWorkPackageState &execution_state) +{ + switch (execution_state) { + case eWorkPackageState::NotScheduled: { + os << "ExecutionState::NotScheduled"; + break; + } + case eWorkPackageState::Scheduled: { + os << "ExecutionState::Scheduled"; + break; + } + case eWorkPackageState::Executed: { + os << "ExecutionState::Executed"; + break; + } + } + return os; +} + +} // namespace blender::compositor diff --git a/source/blender/compositor/intern/COM_Enums.h b/source/blender/compositor/intern/COM_Enums.h new file mode 100644 index 00000000000..519e7df940e --- /dev/null +++ b/source/blender/compositor/intern/COM_Enums.h @@ -0,0 +1,91 @@ +/* + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * Copyright 2021, Blender Foundation. + */ + +#pragma once + +#include "COM_defines.h" + +#include <ostream> + +namespace blender::compositor { + +/** + * \brief Possible quality settings + * \see CompositorContext.quality + * \ingroup Execution + */ +enum class eCompositorQuality { + /** \brief High quality setting */ + High = 0, + /** \brief Medium quality setting */ + Medium = 1, + /** \brief Low quality setting */ + Low = 2, +}; + +/** + * \brief Possible priority settings + * \ingroup Execution + */ +enum class eCompositorPriority { + /** \brief High quality setting */ + High = 2, + /** \brief Medium quality setting */ + Medium = 1, + /** \brief Low quality setting */ + Low = 0, +}; + +/** + * \brief the execution state of a chunk in an ExecutionGroup + * \ingroup Execution + */ +enum class eWorkPackageState { + /** + * \brief chunk is not yet scheduled + */ + NotScheduled = 0, + /** + * \brief chunk is scheduled, but not yet executed + */ + Scheduled = 1, + /** + * \brief chunk is executed. + */ + Executed = 2, +}; + +/** + * \brief Work type to execute. + * \ingroup Execution + */ +enum class eWorkPackageType { + /** + * \brief Executes an execution group tile. + */ + Tile = 0, + /** + * \brief Executes a custom function. + */ + CustomFunction = 1 +}; + +std::ostream &operator<<(std::ostream &os, const eCompositorPriority &priority); +std::ostream &operator<<(std::ostream &os, const eWorkPackageState &execution_state); + +} // namespace blender::compositor diff --git a/source/blender/compositor/intern/COM_ExecutionGroup.cc b/source/blender/compositor/intern/COM_ExecutionGroup.cc index f500327b7a7..68bda8c70d6 100644 --- a/source/blender/compositor/intern/COM_ExecutionGroup.cc +++ b/source/blender/compositor/intern/COM_ExecutionGroup.cc @@ -46,10 +46,31 @@ #include "WM_api.h" #include "WM_types.h" -ExecutionGroup::ExecutionGroup() +namespace blender::compositor { + +std::ostream &operator<<(std::ostream &os, const ExecutionGroupFlags &flags) +{ + if (flags.initialized) { + os << "init,"; + } + if (flags.is_output) { + os << "output,"; + } + if (flags.complex) { + os << "complex,"; + } + if (flags.open_cl) { + os << "open_cl,"; + } + if (flags.single_threaded) { + os << "single_threaded,"; + } + return os; +} + +ExecutionGroup::ExecutionGroup(int id) { - this->m_is_output = false; - this->m_complex = false; + m_id = id; this->m_bTree = nullptr; this->m_height = 0; this->m_width = 0; @@ -57,42 +78,48 @@ ExecutionGroup::ExecutionGroup() this->m_x_chunks_len = 0; this->m_y_chunks_len = 0; this->m_chunks_len = 0; - this->m_initialized = false; - this->m_openCL = false; - this->m_singleThreaded = false; this->m_chunks_finished = 0; BLI_rcti_init(&this->m_viewerBorder, 0, 0, 0, 0); this->m_executionStartTime = 0; } -CompositorPriority ExecutionGroup::getRenderPriority() +std::ostream &operator<<(std::ostream &os, const ExecutionGroup &execution_group) +{ + os << "ExecutionGroup(id=" << execution_group.get_id(); + os << ",flags={" << execution_group.get_flags() << "}"; + os << ",operation=" << *execution_group.getOutputOperation() << ""; + os << ")"; + return os; +} + +eCompositorPriority ExecutionGroup::getRenderPriority() { return this->getOutputOperation()->getRenderPriority(); } bool ExecutionGroup::can_contain(NodeOperation &operation) { - if (!this->m_initialized) { + if (!m_flags.initialized) { return true; } - if (operation.isReadBufferOperation()) { + if (operation.get_flags().is_read_buffer_operation) { return true; } - if (operation.isWriteBufferOperation()) { + if (operation.get_flags().is_write_buffer_operation) { return false; } - if (operation.isSetOperation()) { + if (operation.get_flags().is_set_operation) { return true; } /* complex groups don't allow further ops (except read buffer and values, see above) */ - if (m_complex) { + if (m_flags.complex) { return false; } /* complex ops can't be added to other groups (except their own, which they initialize, see * above) */ - if (operation.isComplex()) { + if (operation.get_flags().complex) { return false; } @@ -105,11 +132,12 @@ bool ExecutionGroup::addOperation(NodeOperation *operation) return false; } - if (!operation->isReadBufferOperation() && !operation->isWriteBufferOperation()) { - m_complex = operation->isComplex(); - m_openCL = operation->isOpenCL(); - m_singleThreaded = operation->isSingleThreaded(); - m_initialized = true; + if (!operation->get_flags().is_read_buffer_operation && + !operation->get_flags().is_write_buffer_operation) { + m_flags.complex = operation->get_flags().complex; + m_flags.open_cl = operation->get_flags().open_cl; + m_flags.single_threaded = operation->get_flags().single_threaded; + m_flags.initialized = true; } m_operations.append(operation); @@ -123,20 +151,26 @@ NodeOperation *ExecutionGroup::getOutputOperation() const ->m_operations[0]; /* the first operation of the group is always the output operation. */ } -void ExecutionGroup::initExecution() +void ExecutionGroup::init_work_packages() { - m_chunk_execution_states.clear(); - determineNumberOfChunks(); - + m_work_packages.clear(); if (this->m_chunks_len != 0) { - m_chunk_execution_states.resize(this->m_chunks_len); - m_chunk_execution_states.fill(eChunkExecutionState::NOT_SCHEDULED); + m_work_packages.resize(this->m_chunks_len); + for (unsigned int index = 0; index < m_chunks_len; index++) { + m_work_packages[index].type = eWorkPackageType::Tile; + m_work_packages[index].state = eWorkPackageState::NotScheduled; + m_work_packages[index].execution_group = this; + m_work_packages[index].chunk_number = index; + determineChunkRect(&m_work_packages[index].rect, index); + } } +} +void ExecutionGroup::init_read_buffer_operations() +{ unsigned int max_offset = 0; - for (NodeOperation *operation : m_operations) { - if (operation->isReadBufferOperation()) { + if (operation->get_flags().is_read_buffer_operation) { ReadBufferOperation *readOperation = static_cast<ReadBufferOperation *>(operation); this->m_read_operations.append(readOperation); max_offset = MAX2(max_offset, readOperation->getOffset()); @@ -146,15 +180,23 @@ void ExecutionGroup::initExecution() this->m_max_read_buffer_offset = max_offset; } +void ExecutionGroup::initExecution() +{ + init_number_of_chunks(); + init_work_packages(); + init_read_buffer_operations(); +} + void ExecutionGroup::deinitExecution() { - m_chunk_execution_states.clear(); + m_work_packages.clear(); this->m_chunks_len = 0; this->m_x_chunks_len = 0; this->m_y_chunks_len = 0; this->m_read_operations.clear(); this->m_bTree = nullptr; } + void ExecutionGroup::determineResolution(unsigned int resolution[2]) { NodeOperation *operation = this->getOutputOperation(); @@ -164,9 +206,9 @@ void ExecutionGroup::determineResolution(unsigned int resolution[2]) BLI_rcti_init(&this->m_viewerBorder, 0, this->m_width, 0, this->m_height); } -void ExecutionGroup::determineNumberOfChunks() +void ExecutionGroup::init_number_of_chunks() { - if (this->m_singleThreaded) { + if (this->m_flags.single_threaded) { this->m_x_chunks_len = 1; this->m_y_chunks_len = 1; this->m_chunks_len = 1; @@ -181,7 +223,7 @@ void ExecutionGroup::determineNumberOfChunks() } } -blender::Array<unsigned int> ExecutionGroup::determine_chunk_execution_order() const +blender::Array<unsigned int> ExecutionGroup::get_execution_order() const { blender::Array<unsigned int> chunk_order(m_chunks_len); for (int chunk_index = 0; chunk_index < this->m_chunks_len; chunk_index++) { @@ -193,7 +235,7 @@ blender::Array<unsigned int> ExecutionGroup::determine_chunk_execution_order() c float centerY = 0.5f; ChunkOrdering order_type = ChunkOrdering::Default; - if (operation->isViewerOperation()) { + if (operation->get_flags().is_viewer_operation) { ViewerOperation *viewer = (ViewerOperation *)operation; centerX = viewer->getCenterX(); centerY = viewer->getCenterY(); @@ -216,11 +258,10 @@ blender::Array<unsigned int> ExecutionGroup::determine_chunk_execution_order() c ChunkOrderHotspot hotspot(border_width * centerX, border_height * centerY, 0.0f); blender::Array<ChunkOrder> chunk_orders(m_chunks_len); for (index = 0; index < this->m_chunks_len; index++) { - rcti rect; - determineChunkRect(&rect, index); + const WorkPackage &work_package = m_work_packages[index]; chunk_orders[index].index = index; - chunk_orders[index].x = rect.xmin - this->m_viewerBorder.xmin; - chunk_orders[index].y = rect.ymin - this->m_viewerBorder.ymin; + chunk_orders[index].x = work_package.rect.xmin - this->m_viewerBorder.xmin; + chunk_orders[index].y = work_package.rect.ymin - this->m_viewerBorder.ymin; chunk_orders[index].update_distance(&hotspot, 1); } @@ -254,11 +295,10 @@ blender::Array<unsigned int> ExecutionGroup::determine_chunk_execution_order() c blender::Array<ChunkOrder> chunk_orders(m_chunks_len); for (index = 0; index < this->m_chunks_len; index++) { - rcti rect; - determineChunkRect(&rect, index); + const WorkPackage &work_package = m_work_packages[index]; chunk_orders[index].index = index; - chunk_orders[index].x = rect.xmin - this->m_viewerBorder.xmin; - chunk_orders[index].y = rect.ymin - this->m_viewerBorder.ymin; + chunk_orders[index].x = work_package.rect.xmin - this->m_viewerBorder.xmin; + chunk_orders[index].y = work_package.rect.ymin - this->m_viewerBorder.ymin; chunk_orders[index].update_distance(hotspots, 9); } @@ -301,7 +341,7 @@ void ExecutionGroup::execute(ExecutionSystem *graph) this->m_chunks_finished = 0; this->m_bTree = bTree; - blender::Array<unsigned int> chunk_order = determine_chunk_execution_order(); + blender::Array<unsigned int> chunk_order = get_execution_order(); DebugInfo::execution_group_started(this); DebugInfo::graphviz(graph); @@ -322,8 +362,9 @@ void ExecutionGroup::execute(ExecutionSystem *graph) chunk_index = chunk_order[index]; int yChunk = chunk_index / this->m_x_chunks_len; int xChunk = chunk_index - (yChunk * this->m_x_chunks_len); - switch (m_chunk_execution_states[chunk_index]) { - case eChunkExecutionState::NOT_SCHEDULED: { + const WorkPackage &work_package = m_work_packages[chunk_index]; + switch (work_package.state) { + case eWorkPackageState::NotScheduled: { scheduleChunkWhenPossible(graph, xChunk, yChunk); finished = false; startEvaluated = true; @@ -334,13 +375,13 @@ void ExecutionGroup::execute(ExecutionSystem *graph) } break; } - case eChunkExecutionState::SCHEDULED: { + case eWorkPackageState::Scheduled: { finished = false; startEvaluated = true; numberEvaluated++; break; } - case eChunkExecutionState::EXECUTED: { + case eWorkPackageState::Executed: { if (!startEvaluated) { startIndex = index + 1; } @@ -360,15 +401,14 @@ void ExecutionGroup::execute(ExecutionSystem *graph) MemoryBuffer **ExecutionGroup::getInputBuffersOpenCL(int chunkNumber) { - rcti rect; - determineChunkRect(&rect, chunkNumber); + WorkPackage &work_package = m_work_packages[chunkNumber]; MemoryBuffer **memoryBuffers = (MemoryBuffer **)MEM_callocN( sizeof(MemoryBuffer *) * this->m_max_read_buffer_offset, __func__); rcti output; for (ReadBufferOperation *readOperation : m_read_operations) { MemoryProxy *memoryProxy = readOperation->getMemoryProxy(); - this->determineDependingAreaOfInterest(&rect, readOperation, &output); + this->determineDependingAreaOfInterest(&work_package.rect, readOperation, &output); MemoryBuffer *memoryBuffer = memoryProxy->getExecutor()->constructConsolidatedMemoryBuffer( *memoryProxy, output); memoryBuffers[readOperation->getOffset()] = memoryBuffer; @@ -387,8 +427,9 @@ MemoryBuffer *ExecutionGroup::constructConsolidatedMemoryBuffer(MemoryProxy &mem void ExecutionGroup::finalizeChunkExecution(int chunkNumber, MemoryBuffer **memoryBuffers) { - if (this->m_chunk_execution_states[chunkNumber] == eChunkExecutionState::SCHEDULED) { - this->m_chunk_execution_states[chunkNumber] = eChunkExecutionState::EXECUTED; + WorkPackage &work_package = m_work_packages[chunkNumber]; + if (work_package.state == eWorkPackageState::Scheduled) { + work_package.state = eWorkPackageState::Executed; } atomic_add_and_fetch_u(&this->m_chunks_finished, 1); @@ -420,23 +461,23 @@ void ExecutionGroup::finalizeChunkExecution(int chunkNumber, MemoryBuffer **memo } } -inline void ExecutionGroup::determineChunkRect(rcti *rect, +inline void ExecutionGroup::determineChunkRect(rcti *r_rect, const unsigned int xChunk, const unsigned int yChunk) const { const int border_width = BLI_rcti_size_x(&this->m_viewerBorder); const int border_height = BLI_rcti_size_y(&this->m_viewerBorder); - if (this->m_singleThreaded) { + if (this->m_flags.single_threaded) { BLI_rcti_init( - rect, this->m_viewerBorder.xmin, border_width, this->m_viewerBorder.ymin, border_height); + r_rect, this->m_viewerBorder.xmin, border_width, this->m_viewerBorder.ymin, border_height); } else { const unsigned int minx = xChunk * this->m_chunkSize + this->m_viewerBorder.xmin; const unsigned int miny = yChunk * this->m_chunkSize + this->m_viewerBorder.ymin; const unsigned int width = MIN2((unsigned int)this->m_viewerBorder.xmax, this->m_width); const unsigned int height = MIN2((unsigned int)this->m_viewerBorder.ymax, this->m_height); - BLI_rcti_init(rect, + BLI_rcti_init(r_rect, MIN2(minx, this->m_width), MIN2(minx + this->m_chunkSize, width), MIN2(miny, this->m_height), @@ -444,18 +485,18 @@ inline void ExecutionGroup::determineChunkRect(rcti *rect, } } -void ExecutionGroup::determineChunkRect(rcti *rect, const unsigned int chunkNumber) const +void ExecutionGroup::determineChunkRect(rcti *r_rect, const unsigned int chunkNumber) const { const unsigned int yChunk = chunkNumber / this->m_x_chunks_len; const unsigned int xChunk = chunkNumber - (yChunk * this->m_x_chunks_len); - determineChunkRect(rect, xChunk, yChunk); + determineChunkRect(r_rect, xChunk, yChunk); } MemoryBuffer *ExecutionGroup::allocateOutputBuffer(rcti &rect) { // we assume that this method is only called from complex execution groups. NodeOperation *operation = this->getOutputOperation(); - if (operation->isWriteBufferOperation()) { + if (operation->get_flags().is_write_buffer_operation) { WriteBufferOperation *writeOperation = (WriteBufferOperation *)operation; MemoryBuffer *buffer = new MemoryBuffer( writeOperation->getMemoryProxy(), rect, MemoryBufferState::Temporary); @@ -466,7 +507,7 @@ MemoryBuffer *ExecutionGroup::allocateOutputBuffer(rcti &rect) bool ExecutionGroup::scheduleAreaWhenPossible(ExecutionSystem *graph, rcti *area) { - if (this->m_singleThreaded) { + if (this->m_flags.single_threaded) { return scheduleChunkWhenPossible(graph, 0, 0); } // find all chunks inside the rect @@ -500,9 +541,10 @@ bool ExecutionGroup::scheduleAreaWhenPossible(ExecutionSystem *graph, rcti *area bool ExecutionGroup::scheduleChunk(unsigned int chunkNumber) { - if (this->m_chunk_execution_states[chunkNumber] == eChunkExecutionState::NOT_SCHEDULED) { - this->m_chunk_execution_states[chunkNumber] = eChunkExecutionState::SCHEDULED; - WorkScheduler::schedule(this, chunkNumber); + WorkPackage &work_package = m_work_packages[chunkNumber]; + if (work_package.state == eWorkPackageState::NotScheduled) { + work_package.state = eWorkPackageState::Scheduled; + WorkScheduler::schedule(&work_package); return true; } return false; @@ -521,22 +563,21 @@ bool ExecutionGroup::scheduleChunkWhenPossible(ExecutionSystem *graph, // Check if chunk is already executed or scheduled and not yet executed. const int chunk_index = chunk_y * this->m_x_chunks_len + chunk_x; - if (this->m_chunk_execution_states[chunk_index] == eChunkExecutionState::EXECUTED) { + WorkPackage &work_package = m_work_packages[chunk_index]; + if (work_package.state == eWorkPackageState::Executed) { return true; } - if (this->m_chunk_execution_states[chunk_index] == eChunkExecutionState::SCHEDULED) { + if (work_package.state == eWorkPackageState::Scheduled) { return false; } - rcti rect; - determineChunkRect(&rect, chunk_x, chunk_y); bool can_be_executed = true; rcti area; for (ReadBufferOperation *read_operation : m_read_operations) { BLI_rcti_init(&area, 0, 0, 0, 0); MemoryProxy *memory_proxy = read_operation->getMemoryProxy(); - determineDependingAreaOfInterest(&rect, read_operation, &area); + determineDependingAreaOfInterest(&work_package.rect, read_operation, &area); ExecutionGroup *group = memory_proxy->getExecutor(); if (!group->scheduleAreaWhenPossible(graph, &area)) { @@ -558,16 +599,10 @@ void ExecutionGroup::determineDependingAreaOfInterest(rcti *input, this->getOutputOperation()->determineDependingAreaOfInterest(input, readOperation, output); } -bool ExecutionGroup::isOpenCL() -{ - return this->m_openCL; -} - void ExecutionGroup::setViewerBorder(float xmin, float xmax, float ymin, float ymax) { - NodeOperation *operation = this->getOutputOperation(); - - if (operation->isViewerOperation() || operation->isPreviewOperation()) { + const NodeOperation &operation = *this->getOutputOperation(); + if (operation.get_flags().use_viewer_border) { BLI_rcti_init(&this->m_viewerBorder, xmin * this->m_width, xmax * this->m_width, @@ -578,32 +613,14 @@ void ExecutionGroup::setViewerBorder(float xmin, float xmax, float ymin, float y void ExecutionGroup::setRenderBorder(float xmin, float xmax, float ymin, float ymax) { - NodeOperation *operation = this->getOutputOperation(); - - if (operation->isOutputOperation(true)) { - /* Basically, setting border need to happen for only operations - * which operates in render resolution buffers (like compositor - * output nodes). - * - * In this cases adding border will lead to mapping coordinates - * from output buffer space to input buffer spaces when executing - * operation. - * - * But nodes like viewer and file output just shall display or - * safe the same exact buffer which goes to their input, no need - * in any kind of coordinates mapping. - */ - - bool operationNeedsBorder = !(operation->isViewerOperation() || - operation->isPreviewOperation() || - operation->isFileOutputOperation()); - - if (operationNeedsBorder) { - BLI_rcti_init(&this->m_viewerBorder, - xmin * this->m_width, - xmax * this->m_width, - ymin * this->m_height, - ymax * this->m_height); - } + const NodeOperation &operation = *this->getOutputOperation(); + if (operation.isOutputOperation(true) && operation.get_flags().use_render_border) { + BLI_rcti_init(&this->m_viewerBorder, + xmin * this->m_width, + xmax * this->m_width, + ymin * this->m_height, + ymax * this->m_height); } } + +} // namespace blender::compositor diff --git a/source/blender/compositor/intern/COM_ExecutionGroup.h b/source/blender/compositor/intern/COM_ExecutionGroup.h index 13ff06cd5d1..cb593feabb0 100644 --- a/source/blender/compositor/intern/COM_ExecutionGroup.h +++ b/source/blender/compositor/intern/COM_ExecutionGroup.h @@ -31,32 +31,50 @@ #include "COM_MemoryProxy.h" #include "COM_Node.h" #include "COM_NodeOperation.h" +#include "COM_WorkPackage.h" #include <vector> +namespace blender::compositor { + class ExecutionSystem; class MemoryProxy; +class MemoryBuffer; class ReadBufferOperation; class Device; -/** - * \brief the execution state of a chunk in an ExecutionGroup - * \ingroup Execution - */ -enum class eChunkExecutionState { +struct ExecutionGroupFlags { + bool initialized : 1; /** - * \brief chunk is not yet scheduled + * Is this ExecutionGroup an output ExecutionGroup + * An OutputExecution group are groups containing a + * ViewerOperation, CompositeOperation, PreviewOperation. */ - NOT_SCHEDULED = 0, + bool is_output : 1; + bool complex : 1; + /** - * \brief chunk is scheduled, but not yet executed + * Can this ExecutionGroup be scheduled on an OpenCLDevice. */ - SCHEDULED = 1, + bool open_cl : 1; + /** - * \brief chunk is executed. + * Schedule this execution group as a single chunk. This + * chunk will be executed by a single thread. */ - EXECUTED = 2, + bool single_threaded : 1; + + ExecutionGroupFlags() + { + initialized = false; + is_output = false; + complex = false; + open_cl = false; + single_threaded = false; + } }; +std::ostream &operator<<(std::ostream &os, const ExecutionGroupFlags &flags); + /** * \brief Class ExecutionGroup is a group of Operations that are executed as one. * This grouping is used to combine Operations that can be executed as one whole when @@ -66,18 +84,17 @@ enum class eChunkExecutionState { class ExecutionGroup { private: // fields - /** - * \brief list of operations in this ExecutionGroup + * Id of the execution group. For debugging purposes. */ - blender::Vector<NodeOperation *> m_operations; + int m_id; /** - * \brief is this ExecutionGroup an input ExecutionGroup - * an input execution group is a group that is at the end of the calculation - * (the output is important for the user). + * \brief list of operations in this ExecutionGroup */ - bool m_is_output; + Vector<NodeOperation *> m_operations; + + ExecutionGroupFlags m_flags; /** * \brief Width of the output @@ -111,21 +128,6 @@ class ExecutionGroup { unsigned int m_chunks_len; /** - * \brief contains this ExecutionGroup a complex NodeOperation. - */ - bool m_complex; - - /** - * \brief can this ExecutionGroup be scheduled on an OpenCLDevice - */ - bool m_openCL; - - /** - * \brief Is this Execution group SingleThreaded - */ - bool m_singleThreaded; - - /** * \brief what is the maximum number field of all ReadBufferOperation in this ExecutionGroup. * \note this is used to construct the MemoryBuffers that will be passed during execution. */ @@ -134,7 +136,7 @@ class ExecutionGroup { /** * \brief All read operations of this execution group. */ - blender::Vector<ReadBufferOperation *> m_read_operations; + Vector<ReadBufferOperation *> m_read_operations; /** * \brief reference to the original bNodeTree, @@ -149,24 +151,9 @@ class ExecutionGroup { unsigned int m_chunks_finished; /** - * \brief m_chunk_execution_states holds per chunk the execution state. this state can be - * - eChunkExecutionState::NOT_SCHEDULED: not scheduled - * - eChunkExecutionState::SCHEDULED: scheduled - * - eChunkExecutionState::EXECUTED: executed - */ - blender::Vector<eChunkExecutionState> m_chunk_execution_states; - - /** - * \brief indicator when this ExecutionGroup has valid Operations in its vector for Execution - * \note When building the ExecutionGroup Operations are added via recursion. - * First a WriteBufferOperations is added, then the. - * \note Operation containing the settings that is important for the ExecutiongGroup is added, - * \note When this occurs, these settings are copied over from the node to the ExecutionGroup - * \note and the Initialized flag is set to true. - * \see complex - * \see openCL + * \brief m_work_packages holds all unit of work. */ - bool m_initialized; + Vector<WorkPackage> m_work_packages; /** * \brief denotes boundary for border compositing @@ -187,25 +174,17 @@ class ExecutionGroup { bool can_contain(NodeOperation &operation); /** - * \brief calculate the actual chunk size of this execution group. - * \note A chunk size is an unsigned int that is both the height and width of a chunk. - * \note The chunk size will not be stored in the chunkSize field. This needs to be done - * \note by the calling method. - */ - unsigned int determineChunkSize(); - - /** * \brief Determine the rect (minx, maxx, miny, maxy) of a chunk at a position. - * \note Only gives useful results after the determination of the chunksize - * \see determineChunkSize() */ - void determineChunkRect(rcti *rect, const unsigned int xChunk, const unsigned int yChunk) const; + void determineChunkRect(rcti *r_rect, + const unsigned int xChunk, + const unsigned int yChunk) const; /** * \brief determine the number of chunks, based on the chunkSize, width and height. * \note The result are stored in the fields numberOfChunks, numberOfXChunks, numberOfYChunks */ - void determineNumberOfChunks(); + void init_number_of_chunks(); /** * \brief try to schedule a specific chunk. @@ -252,11 +231,24 @@ class ExecutionGroup { /** * Return the execution order of the user visible chunks. */ - blender::Array<unsigned int> determine_chunk_execution_order() const; + blender::Array<unsigned int> get_execution_order() const; + + void init_read_buffer_operations(); + void init_work_packages(); public: // constructors - ExecutionGroup(); + ExecutionGroup(int id); + + int get_id() const + { + return m_id; + } + + const ExecutionGroupFlags get_flags() const + { + return m_flags; + } // methods /** @@ -270,23 +262,12 @@ class ExecutionGroup { bool addOperation(NodeOperation *operation); /** - * \brief is this ExecutionGroup an output ExecutionGroup - * \note An OutputExecution group are groups containing a - * \note ViewerOperation, CompositeOperation, PreviewOperation. - * \see NodeOperation.isOutputOperation - */ - bool isOutputExecutionGroup() const - { - return this->m_is_output; - } - - /** * \brief set whether this ExecutionGroup is an output * \param isOutput: */ void setOutputExecutionGroup(bool is_output) { - this->m_is_output = is_output; + this->m_flags.is_output = is_output; } /** @@ -322,14 +303,6 @@ class ExecutionGroup { } /** - * \brief does this ExecutionGroup contains a complex NodeOperation - */ - bool isComplex() const - { - return m_complex; - } - - /** * \brief get the output operation of this ExecutionGroup * \return NodeOperation *output operation */ @@ -404,16 +377,8 @@ class ExecutionGroup { /** * \brief Determine the rect (minx, maxx, miny, maxy) of a chunk. - * \note Only gives useful results after the determination of the chunksize - * \see determineChunkSize() - */ - void determineChunkRect(rcti *rect, const unsigned int chunkNumber) const; - - /** - * \brief can this ExecutionGroup be scheduled on an OpenCLDevice - * \see WorkScheduler.schedule */ - bool isOpenCL(); + void determineChunkRect(rcti *r_rect, const unsigned int chunkNumber) const; void setChunksize(int chunksize) { @@ -424,7 +389,7 @@ class ExecutionGroup { * \brief get the Render priority of this ExecutionGroup * \see ExecutionSystem.execute */ - CompositorPriority getRenderPriority(); + eCompositorPriority getRenderPriority(); /** * \brief set border for viewer operation @@ -441,3 +406,7 @@ class ExecutionGroup { MEM_CXX_CLASS_ALLOC_FUNCS("COM:ExecutionGroup") #endif }; + +std::ostream &operator<<(std::ostream &os, const ExecutionGroup &execution_group); + +} // namespace blender::compositor diff --git a/source/blender/compositor/intern/COM_ExecutionModel.cc b/source/blender/compositor/intern/COM_ExecutionModel.cc new file mode 100644 index 00000000000..4d7f62e091b --- /dev/null +++ b/source/blender/compositor/intern/COM_ExecutionModel.cc @@ -0,0 +1,48 @@ +/* + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * Copyright 2021, Blender Foundation. + */ + +#include "COM_ExecutionModel.h" + +namespace blender::compositor { + +ExecutionModel::ExecutionModel(CompositorContext &context, Span<NodeOperation *> operations) + : context_(context), operations_(operations) +{ + const bNodeTree *node_tree = context_.getbNodeTree(); + + const rctf *viewer_border = &node_tree->viewer_border; + border_.use_viewer_border = (node_tree->flag & NTREE_VIEWER_BORDER) && + viewer_border->xmin < viewer_border->xmax && + viewer_border->ymin < viewer_border->ymax; + border_.viewer_border = viewer_border; + + const RenderData *rd = context_.getRenderData(); + /* Case when cropping to render border happens is handled in + * compositor output and render layer nodes. */ + border_.use_render_border = context.isRendering() && (rd->mode & R_BORDER) && + !(rd->mode & R_CROP); + border_.render_border = &rd->border; +} + +bool ExecutionModel::is_breaked() const +{ + const bNodeTree *btree = context_.getbNodeTree(); + return btree->test_break(btree->tbh); +} + +} // namespace blender::compositor diff --git a/source/blender/compositor/intern/COM_ExecutionModel.h b/source/blender/compositor/intern/COM_ExecutionModel.h new file mode 100644 index 00000000000..9e8466b9282 --- /dev/null +++ b/source/blender/compositor/intern/COM_ExecutionModel.h @@ -0,0 +1,84 @@ +/* + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * Copyright 2021, Blender Foundation. + */ + +#pragma once + +#include "BLI_rect.h" +#include "BLI_vector.hh" + +#include "COM_ExecutionSystem.h" + +#include <functional> + +#ifdef WITH_CXX_GUARDEDALLOC +# include "MEM_guardedalloc.h" +#endif + +namespace blender::compositor { + +class NodeOperation; + +/** + * Base class for execution models. Contains shared implementation. + */ +class ExecutionModel { + protected: + /** + * Render and viewer border info. Coordinates are normalized. + */ + struct { + bool use_render_border; + const rctf *render_border; + bool use_viewer_border; + const rctf *viewer_border; + } border_; + + /** + * Context used during execution. + */ + CompositorContext &context_; + + /** + * All operations being executed. + */ + Span<NodeOperation *> operations_; + + public: + ExecutionModel(CompositorContext &context, Span<NodeOperation *> operations); + + virtual ~ExecutionModel() + { + } + + virtual void execute(ExecutionSystem &exec_system) = 0; + + virtual void execute_work(const rcti &UNUSED(work_rect), + std::function<void(const rcti &split_rect)> UNUSED(work_func)) + { + BLI_assert(!"Method not supported by current execution model"); + } + + protected: + bool is_breaked() const; + +#ifdef WITH_CXX_GUARDEDALLOC + MEM_CXX_CLASS_ALLOC_FUNCS("COM:BaseExecutionModel") +#endif +}; + +} // namespace blender::compositor diff --git a/source/blender/compositor/intern/COM_ExecutionSystem.cc b/source/blender/compositor/intern/COM_ExecutionSystem.cc index df97b8079b2..a12ec774032 100644 --- a/source/blender/compositor/intern/COM_ExecutionSystem.cc +++ b/source/blender/compositor/intern/COM_ExecutionSystem.cc @@ -21,22 +21,19 @@ #include "BLI_utildefines.h" #include "PIL_time.h" -#include "BKE_node.h" - -#include "BLT_translation.h" - -#include "COM_Converter.h" #include "COM_Debug.h" -#include "COM_ExecutionGroup.h" +#include "COM_FullFrameExecutionModel.h" #include "COM_NodeOperation.h" #include "COM_NodeOperationBuilder.h" -#include "COM_ReadBufferOperation.h" +#include "COM_TiledExecutionModel.h" #include "COM_WorkScheduler.h" #ifdef WITH_CXX_GUARDEDALLOC # include "MEM_guardedalloc.h" #endif +namespace blender::compositor { + ExecutionSystem::ExecutionSystem(RenderData *rd, Scene *scene, bNodeTree *editingtree, @@ -53,10 +50,10 @@ ExecutionSystem::ExecutionSystem(RenderData *rd, this->m_context.setFastCalculation(fastcalculation); /* initialize the CompositorContext */ if (rendering) { - this->m_context.setQuality((CompositorQuality)editingtree->render_quality); + this->m_context.setQuality((eCompositorQuality)editingtree->render_quality); } else { - this->m_context.setQuality((CompositorQuality)editingtree->edit_quality); + this->m_context.setQuality((eCompositorQuality)editingtree->edit_quality); } this->m_context.setRendering(rendering); this->m_context.setHasActiveOpenCLDevices(WorkScheduler::has_gpu_devices() && @@ -71,41 +68,23 @@ ExecutionSystem::ExecutionSystem(RenderData *rd, builder.convertToOperations(this); } - unsigned int resolution[2]; - - rctf *viewer_border = &editingtree->viewer_border; - bool use_viewer_border = (editingtree->flag & NTREE_VIEWER_BORDER) && - viewer_border->xmin < viewer_border->xmax && - viewer_border->ymin < viewer_border->ymax; - - editingtree->stats_draw(editingtree->sdh, TIP_("Compositing | Determining resolution")); - - for (ExecutionGroup *executionGroup : m_groups) { - resolution[0] = 0; - resolution[1] = 0; - executionGroup->determineResolution(resolution); - - if (rendering) { - /* case when cropping to render border happens is handled in - * compositor output and render layer nodes - */ - if ((rd->mode & R_BORDER) && !(rd->mode & R_CROP)) { - executionGroup->setRenderBorder( - rd->border.xmin, rd->border.xmax, rd->border.ymin, rd->border.ymax); - } - } - - if (use_viewer_border) { - executionGroup->setViewerBorder( - viewer_border->xmin, viewer_border->xmax, viewer_border->ymin, viewer_border->ymax); - } + switch (m_context.get_execution_model()) { + case eExecutionModel::Tiled: + execution_model_ = new TiledExecutionModel(m_context, m_operations, m_groups); + break; + case eExecutionModel::FullFrame: + execution_model_ = new FullFrameExecutionModel(m_context, active_buffers_, m_operations); + break; + default: + BLI_assert(!"Non implemented execution model"); + break; } - - // DebugInfo::graphviz(this); } ExecutionSystem::~ExecutionSystem() { + delete execution_model_; + for (NodeOperation *operation : m_operations) { delete operation; } @@ -117,105 +96,23 @@ ExecutionSystem::~ExecutionSystem() this->m_groups.clear(); } -void ExecutionSystem::set_operations(const blender::Vector<NodeOperation *> &operations, - const blender::Vector<ExecutionGroup *> &groups) +void ExecutionSystem::set_operations(const Vector<NodeOperation *> &operations, + const Vector<ExecutionGroup *> &groups) { m_operations = operations; m_groups = groups; } -static void update_read_buffer_offset(blender::Vector<NodeOperation *> &operations) -{ - unsigned int order = 0; - for (NodeOperation *operation : operations) { - if (operation->isReadBufferOperation()) { - ReadBufferOperation *readOperation = (ReadBufferOperation *)operation; - readOperation->setOffset(order); - order++; - } - } -} - -static void init_write_operations_for_execution(blender::Vector<NodeOperation *> &operations, - const bNodeTree *bTree) -{ - for (NodeOperation *operation : operations) { - if (operation->isWriteBufferOperation()) { - operation->setbNodeTree(bTree); - operation->initExecution(); - } - } -} - -static void link_write_buffers(blender::Vector<NodeOperation *> &operations) -{ - for (NodeOperation *operation : operations) { - if (operation->isReadBufferOperation()) { - ReadBufferOperation *readOperation = static_cast<ReadBufferOperation *>(operation); - readOperation->updateMemoryBuffer(); - } - } -} - -static void init_non_write_operations_for_execution(blender::Vector<NodeOperation *> &operations, - const bNodeTree *bTree) -{ - for (NodeOperation *operation : operations) { - if (!operation->isWriteBufferOperation()) { - operation->setbNodeTree(bTree); - operation->initExecution(); - } - } -} - -static void init_execution_groups_for_execution(blender::Vector<ExecutionGroup *> &groups, - const int chunk_size) -{ - for (ExecutionGroup *execution_group : groups) { - execution_group->setChunksize(chunk_size); - execution_group->initExecution(); - } -} - void ExecutionSystem::execute() { - const bNodeTree *editingtree = this->m_context.getbNodeTree(); - editingtree->stats_draw(editingtree->sdh, TIP_("Compositing | Initializing execution")); - DebugInfo::execute_started(this); - update_read_buffer_offset(m_operations); - - init_write_operations_for_execution(m_operations, m_context.getbNodeTree()); - link_write_buffers(m_operations); - init_non_write_operations_for_execution(m_operations, m_context.getbNodeTree()); - init_execution_groups_for_execution(m_groups, m_context.getChunksize()); - - WorkScheduler::start(this->m_context); - execute_groups(CompositorPriority::High); - if (!this->getContext().isFastCalculation()) { - execute_groups(CompositorPriority::Medium); - execute_groups(CompositorPriority::Low); - } - WorkScheduler::finish(); - WorkScheduler::stop(); - - editingtree->stats_draw(editingtree->sdh, TIP_("Compositing | De-initializing execution")); - - for (NodeOperation *operation : m_operations) { - operation->deinitExecution(); - } - - for (ExecutionGroup *execution_group : m_groups) { - execution_group->deinitExecution(); - } + execution_model_->execute(*this); } -void ExecutionSystem::execute_groups(CompositorPriority priority) +void ExecutionSystem::execute_work(const rcti &work_rect, + std::function<void(const rcti &split_rect)> work_func) { - for (ExecutionGroup *execution_group : m_groups) { - if (execution_group->isOutputExecutionGroup() && - execution_group->getRenderPriority() == priority) { - execution_group->execute(this); - } - } + execution_model_->execute_work(work_rect, work_func); } + +} // namespace blender::compositor diff --git a/source/blender/compositor/intern/COM_ExecutionSystem.h b/source/blender/compositor/intern/COM_ExecutionSystem.h index c12380fe839..e106209651c 100644 --- a/source/blender/compositor/intern/COM_ExecutionSystem.h +++ b/source/blender/compositor/intern/COM_ExecutionSystem.h @@ -25,12 +25,15 @@ class ExecutionGroup; #include "COM_ExecutionGroup.h" #include "COM_Node.h" #include "COM_NodeOperation.h" +#include "COM_SharedOperationBuffers.h" #include "DNA_color_types.h" #include "DNA_node_types.h" #include "BLI_vector.hh" +namespace blender::compositor { + /** * \page execution Execution model * In order to get to an efficient model for execution, several steps are being done. these steps @@ -70,17 +73,17 @@ class ExecutionGroup; * * - Image size conversions: the system can automatically convert when resolutions do not match. * An NodeInput has a resize mode. This can be any of the following settings. - * - [@ref InputSocketResizeMode.COM_SC_CENTER]: + * - [@ref InputSocketResizeMode.ResizeMode::Center]: * The center of both images are aligned - * - [@ref InputSocketResizeMode.COM_SC_FIT_WIDTH]: + * - [@ref InputSocketResizeMode.ResizeMode::FitWidth]: * The width of both images are aligned - * - [@ref InputSocketResizeMode.COM_SC_FIT_HEIGHT]: + * - [@ref InputSocketResizeMode.ResizeMode::FitHeight]: * The height of both images are aligned - * - [@ref InputSocketResizeMode.COM_SC_FIT]: + * - [@ref InputSocketResizeMode.ResizeMode::FitAny]: * The width, or the height of both images are aligned to make sure that it fits. - * - [@ref InputSocketResizeMode.COM_SC_STRETCH]: + * - [@ref InputSocketResizeMode.ResizeMode::Stretch]: * The width and the height of both images are aligned. - * - [@ref InputSocketResizeMode.COM_SC_NO_RESIZE]: + * - [@ref InputSocketResizeMode.ResizeMode::None]: * Bottom left of the images are aligned. * * \see COM_convert_data_type Datatype conversions @@ -113,13 +116,21 @@ class ExecutionGroup; * \see ExecutionGroup class representing the ExecutionGroup */ +/* Forward declarations. */ +class ExecutionModel; + /** * \brief the ExecutionSystem contains the whole compositor tree. */ class ExecutionSystem { - private: /** + * Contains operations active buffers data. Buffers will be disposed once reader operations are + * finished. + */ + SharedOperationBuffers active_buffers_; + + /** * \brief the context used during execution */ CompositorContext m_context; @@ -127,12 +138,17 @@ class ExecutionSystem { /** * \brief vector of operations */ - blender::Vector<NodeOperation *> m_operations; + Vector<NodeOperation *> m_operations; /** * \brief vector of groups */ - blender::Vector<ExecutionGroup *> m_groups; + Vector<ExecutionGroup *> m_groups; + + /** + * Active execution model implementation. + */ + ExecutionModel *execution_model_; private: // methods public: @@ -157,8 +173,8 @@ class ExecutionSystem { */ ~ExecutionSystem(); - void set_operations(const blender::Vector<NodeOperation *> &operations, - const blender::Vector<ExecutionGroup *> &groups); + void set_operations(const Vector<NodeOperation *> &operations, + const Vector<ExecutionGroup *> &groups); /** * \brief execute this system @@ -176,9 +192,14 @@ class ExecutionSystem { return this->m_context; } - private: - void execute_groups(CompositorPriority priority); + SharedOperationBuffers &get_active_buffers() + { + return active_buffers_; + } + void execute_work(const rcti &work_rect, std::function<void(const rcti &split_rect)> work_func); + + private: /* allow the DebugInfo class to look at internals */ friend class DebugInfo; @@ -186,3 +207,5 @@ class ExecutionSystem { MEM_CXX_CLASS_ALLOC_FUNCS("COM:ExecutionSystem") #endif }; + +} // namespace blender::compositor diff --git a/source/blender/compositor/intern/COM_FullFrameExecutionModel.cc b/source/blender/compositor/intern/COM_FullFrameExecutionModel.cc new file mode 100644 index 00000000000..21075bb7255 --- /dev/null +++ b/source/blender/compositor/intern/COM_FullFrameExecutionModel.cc @@ -0,0 +1,362 @@ +/* + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * Copyright 2021, Blender Foundation. + */ + +#include "COM_FullFrameExecutionModel.h" +#include "COM_Debug.h" +#include "COM_ExecutionGroup.h" +#include "COM_ReadBufferOperation.h" +#include "COM_WorkScheduler.h" + +#include "BLT_translation.h" + +#ifdef WITH_CXX_GUARDEDALLOC +# include "MEM_guardedalloc.h" +#endif + +namespace blender::compositor { + +FullFrameExecutionModel::FullFrameExecutionModel(CompositorContext &context, + SharedOperationBuffers &shared_buffers, + Span<NodeOperation *> operations) + : ExecutionModel(context, operations), + active_buffers_(shared_buffers), + num_operations_finished_(0), + work_mutex_(), + work_finished_cond_() +{ + priorities_.append(eCompositorPriority::High); + if (!context.isFastCalculation()) { + priorities_.append(eCompositorPriority::Medium); + priorities_.append(eCompositorPriority::Low); + } + + BLI_mutex_init(&work_mutex_); + BLI_condition_init(&work_finished_cond_); +} + +FullFrameExecutionModel::~FullFrameExecutionModel() +{ + BLI_condition_end(&work_finished_cond_); + BLI_mutex_end(&work_mutex_); +} + +void FullFrameExecutionModel::execute(ExecutionSystem &exec_system) +{ + const bNodeTree *node_tree = this->context_.getbNodeTree(); + node_tree->stats_draw(node_tree->sdh, TIP_("Compositing | Initializing execution")); + + DebugInfo::graphviz(&exec_system); + + determine_areas_to_render_and_reads(); + render_operations(exec_system); +} + +void FullFrameExecutionModel::determine_areas_to_render_and_reads() +{ + const bool is_rendering = context_.isRendering(); + const bNodeTree *node_tree = context_.getbNodeTree(); + + rcti area; + for (eCompositorPriority priority : priorities_) { + for (NodeOperation *op : operations_) { + op->setbNodeTree(node_tree); + if (op->isOutputOperation(is_rendering) && op->getRenderPriority() == priority) { + get_output_render_area(op, area); + determine_areas_to_render(op, area); + determine_reads(op); + } + } + } +} + +Vector<MemoryBuffer *> FullFrameExecutionModel::get_input_buffers(NodeOperation *op) +{ + const int num_inputs = op->getNumberOfInputSockets(); + Vector<MemoryBuffer *> inputs_buffers(num_inputs); + for (int i = 0; i < num_inputs; i++) { + NodeOperation *input_op = op->get_input_operation(i); + inputs_buffers[i] = active_buffers_.get_rendered_buffer(input_op); + } + return inputs_buffers; +} + +MemoryBuffer *FullFrameExecutionModel::create_operation_buffer(NodeOperation *op) +{ + rcti op_rect; + BLI_rcti_init(&op_rect, 0, op->getWidth(), 0, op->getHeight()); + + const DataType data_type = op->getOutputSocket(0)->getDataType(); + /* TODO: We should check if the operation is constant instead of is_set_operation. Finding a way + * to know if an operation is constant has to be implemented yet. */ + const bool is_a_single_elem = op->get_flags().is_set_operation; + return new MemoryBuffer(data_type, op_rect, is_a_single_elem); +} + +void FullFrameExecutionModel::render_operation(NodeOperation *op, ExecutionSystem &exec_system) +{ + Vector<MemoryBuffer *> input_bufs = get_input_buffers(op); + + const bool has_outputs = op->getNumberOfOutputSockets() > 0; + MemoryBuffer *op_buf = has_outputs ? create_operation_buffer(op) : nullptr; + Span<rcti> areas = active_buffers_.get_areas_to_render(op); + op->render(op_buf, areas, input_bufs, exec_system); + active_buffers_.set_rendered_buffer(op, std::unique_ptr<MemoryBuffer>(op_buf)); + + operation_finished(op); +} + +/** + * Render output operations in order of priority. + */ +void FullFrameExecutionModel::render_operations(ExecutionSystem &exec_system) +{ + const bool is_rendering = context_.isRendering(); + + WorkScheduler::start(this->context_); + for (eCompositorPriority priority : priorities_) { + for (NodeOperation *op : operations_) { + if (op->isOutputOperation(is_rendering) && op->getRenderPriority() == priority) { + render_output_dependencies(op, exec_system); + render_operation(op, exec_system); + } + } + } + WorkScheduler::stop(); +} + +/** + * Returns all dependencies from inputs to outputs. A dependency may be repeated when + * several operations depend on it. + */ +static Vector<NodeOperation *> get_operation_dependencies(NodeOperation *operation) +{ + /* Get dependencies from outputs to inputs. */ + Vector<NodeOperation *> dependencies; + Vector<NodeOperation *> next_outputs; + next_outputs.append(operation); + while (next_outputs.size() > 0) { + Vector<NodeOperation *> outputs(next_outputs); + next_outputs.clear(); + for (NodeOperation *output : outputs) { + for (int i = 0; i < output->getNumberOfInputSockets(); i++) { + next_outputs.append(output->get_input_operation(i)); + } + } + dependencies.extend(next_outputs); + } + + /* Reverse to get dependencies from inputs to outputs. */ + std::reverse(dependencies.begin(), dependencies.end()); + + return dependencies; +} + +void FullFrameExecutionModel::render_output_dependencies(NodeOperation *output_op, + ExecutionSystem &exec_system) +{ + BLI_assert(output_op->isOutputOperation(context_.isRendering())); + Vector<NodeOperation *> dependencies = get_operation_dependencies(output_op); + for (NodeOperation *op : dependencies) { + if (!active_buffers_.is_operation_rendered(op)) { + render_operation(op, exec_system); + } + } +} + +/** + * Determines all operations areas needed to render given output area. + */ +void FullFrameExecutionModel::determine_areas_to_render(NodeOperation *output_op, + const rcti &output_area) +{ + BLI_assert(output_op->isOutputOperation(context_.isRendering())); + + Vector<std::pair<NodeOperation *, const rcti>> stack; + stack.append({output_op, output_area}); + while (stack.size() > 0) { + std::pair<NodeOperation *, rcti> pair = stack.pop_last(); + NodeOperation *operation = pair.first; + const rcti &render_area = pair.second; + if (active_buffers_.is_area_registered(operation, render_area)) { + continue; + } + + active_buffers_.register_area(operation, render_area); + + const int num_inputs = operation->getNumberOfInputSockets(); + for (int i = 0; i < num_inputs; i++) { + NodeOperation *input_op = operation->get_input_operation(i); + rcti input_op_rect, input_area; + BLI_rcti_init(&input_op_rect, 0, input_op->getWidth(), 0, input_op->getHeight()); + operation->get_area_of_interest(input_op, render_area, input_area); + + /* Ensure area of interest is within operation bounds, cropping areas outside. */ + BLI_rcti_isect(&input_area, &input_op_rect, &input_area); + + stack.append({input_op, input_area}); + } + } +} + +/** + * Determines reads to receive by operations in output operation tree (i.e: Number of dependent + * operations each operation has). + */ +void FullFrameExecutionModel::determine_reads(NodeOperation *output_op) +{ + BLI_assert(output_op->isOutputOperation(context_.isRendering())); + + Vector<NodeOperation *> stack; + stack.append(output_op); + while (stack.size() > 0) { + NodeOperation *operation = stack.pop_last(); + const int num_inputs = operation->getNumberOfInputSockets(); + for (int i = 0; i < num_inputs; i++) { + NodeOperation *input_op = operation->get_input_operation(i); + if (!active_buffers_.has_registered_reads(input_op)) { + stack.append(input_op); + } + active_buffers_.register_read(input_op); + } + } +} + +/** + * Calculates given output operation area to be rendered taking into account viewer and render + * borders. + */ +void FullFrameExecutionModel::get_output_render_area(NodeOperation *output_op, rcti &r_area) +{ + BLI_assert(output_op->isOutputOperation(context_.isRendering())); + + /* By default return operation bounds (no border). */ + const int op_width = output_op->getWidth(); + const int op_height = output_op->getHeight(); + BLI_rcti_init(&r_area, 0, op_width, 0, op_height); + + const bool has_viewer_border = border_.use_viewer_border && + (output_op->get_flags().is_viewer_operation || + output_op->get_flags().is_preview_operation); + const bool has_render_border = border_.use_render_border; + if (has_viewer_border || has_render_border) { + /* Get border with normalized coordinates. */ + const rctf *norm_border = has_viewer_border ? border_.viewer_border : border_.render_border; + + /* Return de-normalized border. */ + BLI_rcti_init(&r_area, + norm_border->xmin * op_width, + norm_border->xmax * op_width, + norm_border->ymin * op_height, + norm_border->ymax * op_height); + } +} + +/** + * Multi-threadedly execute given work function passing work_rect splits as argument. + */ +void FullFrameExecutionModel::execute_work(const rcti &work_rect, + std::function<void(const rcti &split_rect)> work_func) +{ + if (is_breaked()) { + return; + } + + /* Split work vertically to maximize continuous memory. */ + const int work_height = BLI_rcti_size_y(&work_rect); + const int num_sub_works = MIN2(WorkScheduler::get_num_cpu_threads(), work_height); + const int split_height = num_sub_works == 0 ? 0 : work_height / num_sub_works; + int remaining_height = work_height - split_height * num_sub_works; + + Vector<WorkPackage> sub_works(num_sub_works); + int sub_work_y = work_rect.ymin; + int num_sub_works_finished = 0; + for (int i = 0; i < num_sub_works; i++) { + int sub_work_height = split_height; + + /* Distribute remaining height between sub-works. */ + if (remaining_height > 0) { + sub_work_height++; + remaining_height--; + } + + WorkPackage &sub_work = sub_works[i]; + sub_work.type = eWorkPackageType::CustomFunction; + sub_work.execute_fn = [=, &work_func, &work_rect]() { + if (is_breaked()) { + return; + } + rcti split_rect; + BLI_rcti_init( + &split_rect, work_rect.xmin, work_rect.xmax, sub_work_y, sub_work_y + sub_work_height); + work_func(split_rect); + }; + sub_work.executed_fn = [&]() { + BLI_mutex_lock(&work_mutex_); + num_sub_works_finished++; + if (num_sub_works_finished == num_sub_works) { + BLI_condition_notify_one(&work_finished_cond_); + } + BLI_mutex_unlock(&work_mutex_); + }; + WorkScheduler::schedule(&sub_work); + sub_work_y += sub_work_height; + } + BLI_assert(sub_work_y == work_rect.ymax); + + WorkScheduler::finish(); + + /* Ensure all sub-works finished. + * TODO: This a workaround for WorkScheduler::finish() not waiting all works on queue threading + * model. Sync code should be removed once it's fixed. */ + BLI_mutex_lock(&work_mutex_); + if (num_sub_works_finished < num_sub_works) { + BLI_condition_wait(&work_finished_cond_, &work_mutex_); + } + BLI_mutex_unlock(&work_mutex_); +} + +void FullFrameExecutionModel::operation_finished(NodeOperation *operation) +{ + /* Report inputs reads so that buffers may be freed/reused. */ + const int num_inputs = operation->getNumberOfInputSockets(); + for (int i = 0; i < num_inputs; i++) { + active_buffers_.read_finished(operation->get_input_operation(i)); + } + + num_operations_finished_++; + update_progress_bar(); +} + +void FullFrameExecutionModel::update_progress_bar() +{ + const bNodeTree *tree = context_.getbNodeTree(); + if (tree) { + const float progress = num_operations_finished_ / static_cast<float>(operations_.size()); + tree->progress(tree->prh, progress); + + char buf[128]; + BLI_snprintf(buf, + sizeof(buf), + TIP_("Compositing | Operation %i-%li"), + num_operations_finished_ + 1, + operations_.size()); + tree->stats_draw(tree->sdh, buf); + } +} + +} // namespace blender::compositor diff --git a/source/blender/compositor/intern/COM_FullFrameExecutionModel.h b/source/blender/compositor/intern/COM_FullFrameExecutionModel.h new file mode 100644 index 00000000000..e68ad93b407 --- /dev/null +++ b/source/blender/compositor/intern/COM_FullFrameExecutionModel.h @@ -0,0 +1,88 @@ +/* + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * Copyright 2021, Blender Foundation. + */ + +#pragma once + +#include "COM_ExecutionModel.h" + +#ifdef WITH_CXX_GUARDEDALLOC +# include "MEM_guardedalloc.h" +#endif + +namespace blender::compositor { + +/* Forward declarations. */ +class ExecutionGroup; + +/** + * Fully renders operations in order from inputs to outputs. + */ +class FullFrameExecutionModel : public ExecutionModel { + private: + /** + * Contains operations active buffers data. Buffers will be disposed once reader operations are + * finished. + */ + SharedOperationBuffers &active_buffers_; + + /** + * Number of operations finished. + */ + int num_operations_finished_; + + /** + * Order of priorities for output operations execution. + */ + Vector<eCompositorPriority> priorities_; + + ThreadMutex work_mutex_; + ThreadCondition work_finished_cond_; + + public: + FullFrameExecutionModel(CompositorContext &context, + SharedOperationBuffers &shared_buffers, + Span<NodeOperation *> operations); + ~FullFrameExecutionModel(); + + void execute(ExecutionSystem &exec_system) override; + + void execute_work(const rcti &work_rect, + std::function<void(const rcti &split_rect)> work_func) override; + + private: + void determine_areas_to_render_and_reads(); + void render_operations(ExecutionSystem &exec_system); + void render_output_dependencies(NodeOperation *output_op, ExecutionSystem &exec_system); + Vector<MemoryBuffer *> get_input_buffers(NodeOperation *op); + MemoryBuffer *create_operation_buffer(NodeOperation *op); + void render_operation(NodeOperation *op, ExecutionSystem &exec_system); + + void operation_finished(NodeOperation *operation); + + void get_output_render_area(NodeOperation *output_op, rcti &r_area); + void determine_areas_to_render(NodeOperation *output_op, const rcti &output_area); + void determine_reads(NodeOperation *output_op); + + void update_progress_bar(); + +#ifdef WITH_CXX_GUARDEDALLOC + MEM_CXX_CLASS_ALLOC_FUNCS("COM:FullFrameExecutionModel") +#endif +}; + +} // namespace blender::compositor diff --git a/source/blender/compositor/intern/COM_MemoryBuffer.cc b/source/blender/compositor/intern/COM_MemoryBuffer.cc index 0b28168720e..8c30d3215d7 100644 --- a/source/blender/compositor/intern/COM_MemoryBuffer.cc +++ b/source/blender/compositor/intern/COM_MemoryBuffer.cc @@ -20,45 +20,54 @@ #include "MEM_guardedalloc.h" -static unsigned int determine_num_channels(DataType datatype) -{ - switch (datatype) { - case DataType::Value: - return COM_NUM_CHANNELS_VALUE; - case DataType::Vector: - return COM_NUM_CHANNELS_VECTOR; - case DataType::Color: - default: - return COM_NUM_CHANNELS_COLOR; - } -} +namespace blender::compositor { MemoryBuffer::MemoryBuffer(MemoryProxy *memoryProxy, const rcti &rect, MemoryBufferState state) { m_rect = rect; + this->m_is_a_single_elem = false; this->m_memoryProxy = memoryProxy; - this->m_num_channels = determine_num_channels(memoryProxy->getDataType()); + this->m_num_channels = COM_data_type_num_channels(memoryProxy->getDataType()); this->m_buffer = (float *)MEM_mallocN_aligned( sizeof(float) * buffer_len() * this->m_num_channels, 16, "COM_MemoryBuffer"); this->m_state = state; this->m_datatype = memoryProxy->getDataType(); + + set_strides(); } -MemoryBuffer::MemoryBuffer(DataType dataType, const rcti &rect) +MemoryBuffer::MemoryBuffer(DataType dataType, const rcti &rect, bool is_a_single_elem) { m_rect = rect; + this->m_is_a_single_elem = is_a_single_elem; this->m_memoryProxy = nullptr; - this->m_num_channels = determine_num_channels(dataType); + this->m_num_channels = COM_data_type_num_channels(dataType); this->m_buffer = (float *)MEM_mallocN_aligned( sizeof(float) * buffer_len() * this->m_num_channels, 16, "COM_MemoryBuffer"); this->m_state = MemoryBufferState::Temporary; this->m_datatype = dataType; + + set_strides(); } MemoryBuffer::MemoryBuffer(const MemoryBuffer &src) - : MemoryBuffer(src.m_memoryProxy, src.m_rect, MemoryBufferState::Temporary) + : MemoryBuffer(src.m_datatype, src.m_rect, false) +{ + m_memoryProxy = src.m_memoryProxy; + /* src may be single elem buffer */ + fill_from(src); +} + +void MemoryBuffer::set_strides() { - memcpy(m_buffer, src.m_buffer, buffer_len() * m_num_channels * sizeof(float)); + if (m_is_a_single_elem) { + this->elem_stride = 0; + this->row_stride = 0; + } + else { + this->elem_stride = m_num_channels; + this->row_stride = getWidth() * m_num_channels; + } } void MemoryBuffer::clear() @@ -111,6 +120,8 @@ MemoryBuffer::~MemoryBuffer() void MemoryBuffer::fill_from(const MemoryBuffer &src) { + BLI_assert(!this->is_a_single_elem()); + unsigned int otherY; unsigned int minX = MAX2(this->m_rect.xmin, src.m_rect.xmin); unsigned int maxX = MIN2(this->m_rect.xmax, src.m_rect.xmax); @@ -120,10 +131,8 @@ void MemoryBuffer::fill_from(const MemoryBuffer &src) int otherOffset; for (otherY = minY; otherY < maxY; otherY++) { - otherOffset = ((otherY - src.m_rect.ymin) * src.getWidth() + minX - src.m_rect.xmin) * - this->m_num_channels; - offset = ((otherY - this->m_rect.ymin) * getWidth() + minX - this->m_rect.xmin) * - this->m_num_channels; + otherOffset = src.get_coords_offset(minX, otherY); + offset = this->get_coords_offset(minX, otherY); memcpy(&this->m_buffer[offset], &src.m_buffer[otherOffset], (maxX - minX) * this->m_num_channels * sizeof(float)); @@ -134,8 +143,7 @@ void MemoryBuffer::writePixel(int x, int y, const float color[4]) { if (x >= this->m_rect.xmin && x < this->m_rect.xmax && y >= this->m_rect.ymin && y < this->m_rect.ymax) { - const int offset = (getWidth() * (y - this->m_rect.ymin) + x - this->m_rect.xmin) * - this->m_num_channels; + const int offset = get_coords_offset(x, y); memcpy(&this->m_buffer[offset], color, sizeof(float) * this->m_num_channels); } } @@ -144,8 +152,7 @@ void MemoryBuffer::addPixel(int x, int y, const float color[4]) { if (x >= this->m_rect.xmin && x < this->m_rect.xmax && y >= this->m_rect.ymin && y < this->m_rect.ymax) { - const int offset = (getWidth() * (y - this->m_rect.ymin) + x - this->m_rect.xmin) * - this->m_num_channels; + const int offset = get_coords_offset(x, y); float *dst = &this->m_buffer[offset]; const float *src = color; for (int i = 0; i < this->m_num_channels; i++, dst++, src++) { @@ -162,24 +169,31 @@ static void read_ewa_pixel_sampled(void *userdata, int x, int y, float result[4] void MemoryBuffer::readEWA(float *result, const float uv[2], const float derivatives[2][2]) { - BLI_assert(this->m_datatype == DataType::Color); - float inv_width = 1.0f / (float)this->getWidth(), inv_height = 1.0f / (float)this->getHeight(); - /* TODO(sergey): Render pipeline uses normalized coordinates and derivatives, - * but compositor uses pixel space. For now let's just divide the values and - * switch compositor to normalized space for EWA later. - */ - float uv_normal[2] = {uv[0] * inv_width, uv[1] * inv_height}; - float du_normal[2] = {derivatives[0][0] * inv_width, derivatives[0][1] * inv_height}; - float dv_normal[2] = {derivatives[1][0] * inv_width, derivatives[1][1] * inv_height}; - - BLI_ewa_filter(this->getWidth(), - this->getHeight(), - false, - true, - uv_normal, - du_normal, - dv_normal, - read_ewa_pixel_sampled, - this, - result); + if (m_is_a_single_elem) { + memcpy(result, m_buffer, sizeof(float) * this->m_num_channels); + } + else { + BLI_assert(this->m_datatype == DataType::Color); + float inv_width = 1.0f / (float)this->getWidth(), inv_height = 1.0f / (float)this->getHeight(); + /* TODO(sergey): Render pipeline uses normalized coordinates and derivatives, + * but compositor uses pixel space. For now let's just divide the values and + * switch compositor to normalized space for EWA later. + */ + float uv_normal[2] = {uv[0] * inv_width, uv[1] * inv_height}; + float du_normal[2] = {derivatives[0][0] * inv_width, derivatives[0][1] * inv_height}; + float dv_normal[2] = {derivatives[1][0] * inv_width, derivatives[1][1] * inv_height}; + + BLI_ewa_filter(this->getWidth(), + this->getHeight(), + false, + true, + uv_normal, + du_normal, + dv_normal, + read_ewa_pixel_sampled, + this, + result); + } } + +} // namespace blender::compositor diff --git a/source/blender/compositor/intern/COM_MemoryBuffer.h b/source/blender/compositor/intern/COM_MemoryBuffer.h index 6f719b61122..97b220508e0 100644 --- a/source/blender/compositor/intern/COM_MemoryBuffer.h +++ b/source/blender/compositor/intern/COM_MemoryBuffer.h @@ -16,17 +16,16 @@ * Copyright 2011, Blender Foundation. */ -class MemoryBuffer; - #pragma once #include "COM_ExecutionGroup.h" #include "COM_MemoryProxy.h" -#include "COM_SocketReader.h" #include "BLI_math.h" #include "BLI_rect.h" +namespace blender::compositor { + /** * \brief state of a memory buffer * \ingroup Memory @@ -51,6 +50,25 @@ class MemoryProxy; * \brief a MemoryBuffer contains access to the data of a chunk */ class MemoryBuffer { + public: + /** + * Offset between elements. + * + * Should always be used for the x dimension when calculating buffer offsets. + * It will be 0 when is_a_single_elem=true. + * e.g: buffer_index = y * buffer.row_stride + x * buffer.elem_stride + */ + int elem_stride; + + /** + * Offset between rows. + * + * Should always be used for the y dimension when calculating buffer offsets. + * It will be 0 when is_a_single_elem=true. + * e.g: buffer_index = y * buffer.row_stride + x * buffer.elem_stride + */ + int row_stride; + private: /** * \brief proxy of the memory (same for all chunks in the same buffer) @@ -83,6 +101,11 @@ class MemoryBuffer { */ uint8_t m_num_channels; + /** + * Whether buffer is a single element in memory. + */ + bool m_is_a_single_elem; + public: /** * \brief construct new temporarily MemoryBuffer for an area @@ -92,7 +115,7 @@ class MemoryBuffer { /** * \brief construct new temporarily MemoryBuffer for an area */ - MemoryBuffer(DataType datatype, const rcti &rect); + MemoryBuffer(DataType datatype, const rcti &rect, bool is_a_single_elem = false); /** * Copy constructor @@ -104,6 +127,102 @@ class MemoryBuffer { */ ~MemoryBuffer(); + /** + * Whether buffer is a single element in memory independently of its resolution. True for set + * operations buffers. + */ + bool is_a_single_elem() const + { + return m_is_a_single_elem; + } + + float &operator[](int index) + { + BLI_assert(m_is_a_single_elem ? index < m_num_channels : + index < get_coords_offset(getWidth(), getHeight())); + return m_buffer[index]; + } + + const float &operator[](int index) const + { + BLI_assert(m_is_a_single_elem ? index < m_num_channels : + index < get_coords_offset(getWidth(), getHeight())); + return m_buffer[index]; + } + + /** + * Get offset needed to jump from buffer start to given coordinates. + */ + int get_coords_offset(int x, int y) const + { + return (y - m_rect.ymin) * row_stride + (x - m_rect.xmin) * elem_stride; + } + + /** + * Get buffer element at given coordinates. + */ + float *get_elem(int x, int y) + { + BLI_assert(x >= m_rect.xmin && x < m_rect.xmax && y >= m_rect.ymin && y < m_rect.ymax); + return m_buffer + get_coords_offset(x, y); + } + + /** + * Get buffer element at given coordinates. + */ + const float *get_elem(int x, int y) const + { + BLI_assert(x >= m_rect.xmin && x < m_rect.xmax && y >= m_rect.ymin && y < m_rect.ymax); + return m_buffer + get_coords_offset(x, y); + } + + /** + * Get channel value at given coordinates. + */ + float &get_value(int x, int y, int channel) + { + BLI_assert(x >= m_rect.xmin && x < m_rect.xmax && y >= m_rect.ymin && y < m_rect.ymax && + channel >= 0 && channel < m_num_channels); + return m_buffer[get_coords_offset(x, y) + channel]; + } + + /** + * Get channel value at given coordinates. + */ + const float &get_value(int x, int y, int channel) const + { + BLI_assert(x >= m_rect.xmin && x < m_rect.xmax && y >= m_rect.ymin && y < m_rect.ymax && + channel >= 0 && channel < m_num_channels); + return m_buffer[get_coords_offset(x, y) + channel]; + } + + /** + * Get the buffer row end. + */ + const float *get_row_end(int y) const + { + BLI_assert(y >= 0 && y < getHeight()); + return m_buffer + (is_a_single_elem() ? m_num_channels : get_coords_offset(getWidth(), y)); + } + + /** + * Get the number of elements in memory for a row. For single element buffers it will always + * be 1. + */ + int get_memory_width() const + { + return is_a_single_elem() ? 1 : getWidth(); + } + + /** + * Get number of elements in memory for a column. For single element buffers it will + * always be 1. + */ + int get_memory_height() const + { + return is_a_single_elem() ? 1 : getHeight(); + } + uint8_t get_num_channels() { return this->m_num_channels; @@ -217,7 +336,7 @@ class MemoryBuffer { int u = x; int v = y; this->wrap_pixel(u, v, extend_x, extend_y); - const int offset = (getWidth() * y + x) * this->m_num_channels; + const int offset = get_coords_offset(u, v); float *buffer = &this->m_buffer[offset]; memcpy(result, buffer, sizeof(float) * this->m_num_channels); } @@ -233,7 +352,7 @@ class MemoryBuffer { int v = y; this->wrap_pixel(u, v, extend_x, extend_y); - const int offset = (getWidth() * v + u) * this->m_num_channels; + const int offset = get_coords_offset(u, v); BLI_assert(offset >= 0); BLI_assert(offset < this->buffer_len() * this->m_num_channels); @@ -259,15 +378,20 @@ class MemoryBuffer { copy_vn_fl(result, this->m_num_channels, 0.0f); return; } - BLI_bilinear_interpolation_wrap_fl(this->m_buffer, - result, - getWidth(), - getHeight(), - this->m_num_channels, - u, - v, - extend_x == MemoryBufferExtend::Repeat, - extend_y == MemoryBufferExtend::Repeat); + if (m_is_a_single_elem) { + memcpy(result, m_buffer, sizeof(float) * this->m_num_channels); + } + else { + BLI_bilinear_interpolation_wrap_fl(this->m_buffer, + result, + getWidth(), + getHeight(), + this->m_num_channels, + u, + v, + extend_x == MemoryBufferExtend::Repeat, + extend_y == MemoryBufferExtend::Repeat); + } } void readEWA(float *result, const float uv[2], const float derivatives[2][2]); @@ -322,12 +446,15 @@ class MemoryBuffer { float get_max_value(const rcti &rect) const; private: + void set_strides(); const int buffer_len() const { - return getWidth() * getHeight(); + return get_memory_width() * get_memory_height(); } #ifdef WITH_CXX_GUARDEDALLOC MEM_CXX_CLASS_ALLOC_FUNCS("COM:MemoryBuffer") #endif }; + +} // namespace blender::compositor diff --git a/source/blender/compositor/intern/COM_MemoryProxy.cc b/source/blender/compositor/intern/COM_MemoryProxy.cc index 8ef834e1efe..6023850c944 100644 --- a/source/blender/compositor/intern/COM_MemoryProxy.cc +++ b/source/blender/compositor/intern/COM_MemoryProxy.cc @@ -18,6 +18,12 @@ #include "COM_MemoryProxy.h" +#include "COM_MemoryBuffer.h" + +#include "BLI_rect.h" + +namespace blender::compositor { + MemoryProxy::MemoryProxy(DataType datatype) { this->m_writeBufferOperation = nullptr; @@ -43,3 +49,5 @@ void MemoryProxy::free() this->m_buffer = nullptr; } } + +} // namespace blender::compositor diff --git a/source/blender/compositor/intern/COM_MemoryProxy.h b/source/blender/compositor/intern/COM_MemoryProxy.h index ee98ff41630..6814afada74 100644 --- a/source/blender/compositor/intern/COM_MemoryProxy.h +++ b/source/blender/compositor/intern/COM_MemoryProxy.h @@ -16,13 +16,18 @@ * Copyright 2011, Blender Foundation. */ -class MemoryProxy; - #pragma once -#include "COM_ExecutionGroup.h" -#include "COM_MemoryBuffer.h" +#ifdef WITH_CXX_GUARDEDALLOC +# include "MEM_guardedalloc.h" +#endif + +#include "COM_defines.h" + +namespace blender::compositor { +/* Forward declarations. */ +class MemoryBuffer; class ExecutionGroup; class WriteBufferOperation; @@ -69,7 +74,7 @@ class MemoryProxy { /** * \brief get the ExecutionGroup that can be scheduled to calculate a certain chunk. */ - ExecutionGroup *getExecutor() + ExecutionGroup *getExecutor() const { return this->m_executor; } @@ -87,7 +92,7 @@ class MemoryProxy { * \brief get the WriteBufferOperation that is responsible for writing to this MemoryProxy * \return WriteBufferOperation */ - WriteBufferOperation *getWriteBufferOperation() + WriteBufferOperation *getWriteBufferOperation() const { return this->m_writeBufferOperation; } @@ -119,3 +124,5 @@ class MemoryProxy { MEM_CXX_CLASS_ALLOC_FUNCS("COM:MemoryProxy") #endif }; + +} // namespace blender::compositor diff --git a/source/blender/compositor/intern/COM_MetaData.cc b/source/blender/compositor/intern/COM_MetaData.cc index ad72b242c8c..88bfa385514 100644 --- a/source/blender/compositor/intern/COM_MetaData.cc +++ b/source/blender/compositor/intern/COM_MetaData.cc @@ -24,6 +24,8 @@ #include <string_view> +namespace blender::compositor { + void MetaData::add(const blender::StringRef key, const blender::StringRef value) { entries_.add(key, value); @@ -64,7 +66,7 @@ void MetaData::replaceHashNeutralCryptomatteKeys(const blender::StringRef layer_ void MetaData::addToRenderResult(RenderResult *render_result) const { - for (blender::Map<std::string, std::string>::Item entry : entries_.items()) { + for (Map<std::string, std::string>::Item entry : entries_.items()) { BKE_render_result_stamp_data(render_result, entry.key.c_str(), entry.value.c_str()); } } @@ -104,3 +106,5 @@ void MetaDataExtractCallbackData::extract_cryptomatte_meta_data(void *_data, data->addMetaData(META_DATA_KEY_CRYPTOMATTE_MANIFEST, propvalue); } } + +} // namespace blender::compositor diff --git a/source/blender/compositor/intern/COM_MetaData.h b/source/blender/compositor/intern/COM_MetaData.h index fa3de895b4e..a76540dc3af 100644 --- a/source/blender/compositor/intern/COM_MetaData.h +++ b/source/blender/compositor/intern/COM_MetaData.h @@ -28,6 +28,8 @@ /* Forward declarations. */ struct RenderResult; +namespace blender::compositor { + /* Cryptomatte includes hash in its meta data keys. The hash is generated from the render * layer/pass name. Compositing happens without the knowledge of the original layer and pass. The * next keys are used to transfer the cryptomatte meta data in a neutral way. The file output node @@ -41,7 +43,7 @@ constexpr blender::StringRef META_DATA_KEY_CRYPTOMATTE_NAME("cryptomatte/{hash}/ class MetaData { private: - blender::Map<std::string, std::string> entries_; + Map<std::string, std::string> entries_; void addCryptomatteEntry(const blender::StringRef layer_name, const blender::StringRefNull key, const blender::StringRef value); @@ -69,3 +71,5 @@ struct MetaDataExtractCallbackData { char *propvalue, int UNUSED(len)); }; + +} // namespace blender::compositor diff --git a/source/blender/compositor/intern/COM_MultiThreadedOperation.cc b/source/blender/compositor/intern/COM_MultiThreadedOperation.cc new file mode 100644 index 00000000000..c54c2edccb0 --- /dev/null +++ b/source/blender/compositor/intern/COM_MultiThreadedOperation.cc @@ -0,0 +1,26 @@ +#include "COM_MultiThreadedOperation.h" +#include "COM_ExecutionSystem.h" + +namespace blender::compositor { + +MultiThreadedOperation::MultiThreadedOperation() +{ + m_num_passes = 1; + flags.is_fullframe_operation = true; +} + +void MultiThreadedOperation::update_memory_buffer(MemoryBuffer *output, + const rcti &output_area, + blender::Span<MemoryBuffer *> inputs, + ExecutionSystem &exec_system) +{ + for (int current_pass = 0; current_pass < m_num_passes; current_pass++) { + update_memory_buffer_started(output, output_area, inputs, exec_system, current_pass); + exec_system.execute_work(output_area, [=, &exec_system](const rcti &split_rect) { + update_memory_buffer_partial(output, split_rect, inputs, exec_system, current_pass); + }); + update_memory_buffer_finished(output, output_area, inputs, exec_system, current_pass); + } +} + +} // namespace blender::compositor diff --git a/source/blender/compositor/intern/COM_MultiThreadedOperation.h b/source/blender/compositor/intern/COM_MultiThreadedOperation.h new file mode 100644 index 00000000000..97c5fba4ead --- /dev/null +++ b/source/blender/compositor/intern/COM_MultiThreadedOperation.h @@ -0,0 +1,73 @@ +/* + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * Copyright 2021, Blender Foundation. + */ + +#pragma once + +#include "COM_NodeOperation.h" + +namespace blender::compositor { + +class MultiThreadedOperation : public NodeOperation { + protected: + /** + * Number of execution passes. + */ + int m_num_passes; + + protected: + MultiThreadedOperation(); + + /** + * Called before an update memory buffer pass is executed. Single-threaded calls. + */ + virtual void update_memory_buffer_started(MemoryBuffer *UNUSED(output), + const rcti &UNUSED(output_rect), + blender::Span<MemoryBuffer *> UNUSED(inputs), + ExecutionSystem &UNUSED(exec_system), + int UNUSED(current_pass)) + { + } + + /** + * Executes operation updating output memory buffer on output_rect area. Multi-threaded calls. + */ + virtual void update_memory_buffer_partial(MemoryBuffer *output, + const rcti &output_rect, + blender::Span<MemoryBuffer *> inputs, + ExecutionSystem &exec_system, + int current_pass) = 0; + + /** + * Called after an update memory buffer pass is executed. Single-threaded calls. + */ + virtual void update_memory_buffer_finished(MemoryBuffer *UNUSED(output), + const rcti &UNUSED(output_rect), + blender::Span<MemoryBuffer *> UNUSED(inputs), + ExecutionSystem &UNUSED(exec_system), + int UNUSED(current_pass)) + { + } + + private: + void update_memory_buffer(MemoryBuffer *output, + const rcti &output_area, + blender::Span<MemoryBuffer *> inputs, + ExecutionSystem &exec_system) override; +}; + +} // namespace blender::compositor diff --git a/source/blender/compositor/intern/COM_Node.cc b/source/blender/compositor/intern/COM_Node.cc index 819f1c02b14..6ac48e3646c 100644 --- a/source/blender/compositor/intern/COM_Node.cc +++ b/source/blender/compositor/intern/COM_Node.cc @@ -32,6 +32,8 @@ #include "COM_Node.h" /* own include */ +namespace blender::compositor { + /************** **** Node **** **************/ @@ -74,13 +76,11 @@ Node::Node(bNode *editorNode, bool create_sockets) Node::~Node() { - while (!this->m_outputsockets.empty()) { - delete (this->m_outputsockets.back()); - this->m_outputsockets.pop_back(); + while (!this->outputs.is_empty()) { + delete (this->outputs.pop_last()); } - while (!this->m_inputsockets.empty()) { - delete (this->m_inputsockets.back()); - this->m_inputsockets.pop_back(); + while (!this->inputs.is_empty()) { + delete (this->inputs.pop_last()); } } @@ -92,7 +92,7 @@ void Node::addInputSocket(DataType datatype) void Node::addInputSocket(DataType datatype, bNodeSocket *bSocket) { NodeInput *socket = new NodeInput(this, bSocket, datatype); - this->m_inputsockets.push_back(socket); + this->inputs.append(socket); } void Node::addOutputSocket(DataType datatype) @@ -102,19 +102,17 @@ void Node::addOutputSocket(DataType datatype) void Node::addOutputSocket(DataType datatype, bNodeSocket *bSocket) { NodeOutput *socket = new NodeOutput(this, bSocket, datatype); - this->m_outputsockets.push_back(socket); + outputs.append(socket); } NodeOutput *Node::getOutputSocket(unsigned int index) const { - BLI_assert(index < this->m_outputsockets.size()); - return this->m_outputsockets[index]; + return outputs[index]; } NodeInput *Node::getInputSocket(unsigned int index) const { - BLI_assert(index < this->m_inputsockets.size()); - return this->m_inputsockets[index]; + return inputs[index]; } bNodeSocket *Node::getEditorInputSocket(int editorNodeInputSocketIndex) @@ -208,3 +206,5 @@ void NodeOutput::getEditorValueVector(float *value) RNA_pointer_create((ID *)getNode()->getbNodeTree(), &RNA_NodeSocket, getbNodeSocket(), &ptr); return RNA_float_get_array(&ptr, "default_value", value); } + +} // namespace blender::compositor diff --git a/source/blender/compositor/intern/COM_Node.h b/source/blender/compositor/intern/COM_Node.h index 99f1b58b5ef..28b59397af9 100644 --- a/source/blender/compositor/intern/COM_Node.h +++ b/source/blender/compositor/intern/COM_Node.h @@ -18,10 +18,12 @@ #pragma once +#include "BLI_vector.hh" + #include "DNA_node_types.h" + #include <algorithm> #include <string> -#include <vector> /* common node includes * added here so node files don't have to include themselves @@ -29,7 +31,8 @@ #include "COM_CompositorContext.h" #include "COM_NodeConverter.h" -class Node; +namespace blender::compositor { + class NodeOperation; class NodeConverter; @@ -37,10 +40,6 @@ class NodeConverter; * My node documentation. */ class Node { - public: - typedef std::vector<NodeInput *> Inputs; - typedef std::vector<NodeOutput *> Outputs; - private: /** * \brief stores the reference to the SDNA bNode struct @@ -53,16 +52,6 @@ class Node { bNode *m_editorNode; /** - * \brief the list of actual inputsockets \see NodeInput - */ - Inputs m_inputsockets; - - /** - * \brief the list of actual outputsockets \see NodeOutput - */ - Outputs m_outputsockets; - - /** * \brief Is this node part of the active group */ bool m_inActiveGroup; @@ -74,20 +63,14 @@ class Node { protected: /** - * \brief get access to the vector of input sockets + * \brief the list of actual input-sockets \see NodeInput */ - const Inputs &getInputSockets() const - { - return this->m_inputsockets; - } + Vector<NodeInput *> inputs; /** - * \brief get access to the vector of input sockets + * \brief the list of actual output-sockets \see NodeOutput */ - const Outputs &getOutputSockets() const - { - return this->m_outputsockets; - } + Vector<NodeOutput *> outputs; public: Node(bNode *editorNode, bool create_sockets = true); @@ -130,19 +113,19 @@ class Node { } /** - * \brief Return the number of input sockets of this node. + * \brief get access to the vector of input sockets */ - unsigned int getNumberOfInputSockets() const + const Vector<NodeInput *> &getInputSockets() const { - return this->m_inputsockets.size(); + return this->inputs; } /** - * \brief Return the number of output sockets of this node. + * \brief get access to the vector of input sockets */ - unsigned int getNumberOfOutputSockets() const + const Vector<NodeOutput *> &getOutputSockets() const { - return this->m_outputsockets.size(); + return this->outputs; } /** @@ -150,17 +133,7 @@ class Node { * \param index: * the index of the needed outputsocket */ - NodeOutput *getOutputSocket(const unsigned int index) const; - - /** - * get the reference to the first outputsocket - * \param index: - * the index of the needed outputsocket - */ - inline NodeOutput *getOutputSocket() const - { - return getOutputSocket(0); - } + NodeOutput *getOutputSocket(const unsigned int index = 0) const; /** * get the reference to a certain inputsocket @@ -169,14 +142,6 @@ class Node { */ NodeInput *getInputSocket(const unsigned int index) const; - /** Check if this is an input node - * An input node is a node that only has output sockets and no input sockets - */ - bool isInputNode() const - { - return m_inputsockets.empty(); - } - /** * \brief Is this node in the active group (the group that is being edited) * \param isInActiveGroup: @@ -219,7 +184,7 @@ class Node { protected: /** - * \brief add an NodeInput to the collection of inputsockets + * \brief add an NodeInput to the collection of input-sockets * \note may only be called in an constructor * \param socket: the NodeInput to add */ @@ -227,7 +192,7 @@ class Node { void addInputSocket(DataType datatype, bNodeSocket *socket); /** - * \brief add an NodeOutput to the collection of outputsockets + * \brief add an NodeOutput to the collection of output-sockets * \note may only be called in an constructor * \param socket: the NodeOutput to add */ @@ -317,3 +282,5 @@ class NodeOutput { void getEditorValueColor(float *value); void getEditorValueVector(float *value); }; + +} // namespace blender::compositor diff --git a/source/blender/compositor/intern/COM_NodeConverter.cc b/source/blender/compositor/intern/COM_NodeConverter.cc index 2db31bd4133..49a2e7988c4 100644 --- a/source/blender/compositor/intern/COM_NodeConverter.cc +++ b/source/blender/compositor/intern/COM_NodeConverter.cc @@ -29,6 +29,8 @@ #include "COM_NodeConverter.h" /* own include */ +namespace blender::compositor { + NodeConverter::NodeConverter(NodeOperationBuilder *builder) : m_builder(builder) { } @@ -160,3 +162,5 @@ ViewerOperation *NodeConverter::active_viewer() const { return m_builder->active_viewer(); } + +} // namespace blender::compositor diff --git a/source/blender/compositor/intern/COM_NodeConverter.h b/source/blender/compositor/intern/COM_NodeConverter.h index e9b05184857..b3f03485249 100644 --- a/source/blender/compositor/intern/COM_NodeConverter.h +++ b/source/blender/compositor/intern/COM_NodeConverter.h @@ -22,6 +22,8 @@ # include "MEM_guardedalloc.h" #endif +namespace blender::compositor { + class NodeInput; class NodeOutput; @@ -120,3 +122,5 @@ class NodeConverter { MEM_CXX_CLASS_ALLOC_FUNCS("COM:NodeCompiler") #endif }; + +} // namespace blender::compositor diff --git a/source/blender/compositor/intern/COM_NodeGraph.cc b/source/blender/compositor/intern/COM_NodeGraph.cc index d8220099f1f..fbe56dd4b5a 100644 --- a/source/blender/compositor/intern/COM_NodeGraph.cc +++ b/source/blender/compositor/intern/COM_NodeGraph.cc @@ -33,19 +33,16 @@ #include "COM_NodeGraph.h" /* own include */ +namespace blender::compositor { + /******************* **** NodeGraph **** *******************/ -NodeGraph::NodeGraph() -{ -} - NodeGraph::~NodeGraph() { - for (int index = 0; index < this->m_nodes.size(); index++) { - Node *node = this->m_nodes[index]; - delete node; + while (m_nodes.size()) { + delete m_nodes.pop_last(); } } @@ -83,7 +80,7 @@ void NodeGraph::add_node(Node *node, node->setInstanceKey(key); node->setIsInActiveGroup(is_active_group); - m_nodes.push_back(node); + m_nodes.append(node); DebugInfo::node_added(node); } @@ -153,27 +150,11 @@ void NodeGraph::add_bNode(const CompositorContext &context, } } -NodeGraph::NodeInputs NodeGraph::find_inputs(const NodeRange &node_range, bNodeSocket *b_socket) -{ - NodeInputs result; - for (NodeGraph::NodeIterator it = node_range.first; it != node_range.second; ++it) { - Node *node = *it; - for (int index = 0; index < node->getNumberOfInputSockets(); index++) { - NodeInput *input = node->getInputSocket(index); - if (input->getbNodeSocket() == b_socket) { - result.push_back(input); - } - } - } - return result; -} - NodeOutput *NodeGraph::find_output(const NodeRange &node_range, bNodeSocket *b_socket) { - for (NodeGraph::NodeIterator it = node_range.first; it != node_range.second; ++it) { + for (Vector<Node *>::iterator it = node_range.first; it != node_range.second; ++it) { Node *node = *it; - for (int index = 0; index < node->getNumberOfOutputSockets(); index++) { - NodeOutput *output = node->getOutputSocket(index); + for (NodeOutput *output : node->getOutputSockets()) { if (output->getbNodeSocket() == b_socket) { return output; } @@ -202,12 +183,13 @@ void NodeGraph::add_bNodeLink(const NodeRange &node_range, bNodeLink *b_nodelink return; } - NodeInputs inputs = find_inputs(node_range, b_nodelink->tosock); - for (NodeInput *input : inputs) { - if (input->isLinked()) { - continue; + for (Vector<Node *>::iterator it = node_range.first; it != node_range.second; ++it) { + Node *node = *it; + for (NodeInput *input : node->getInputSockets()) { + if (input->getbNodeSocket() == b_nodelink->tosock && !input->isLinked()) { + add_link(output, input); + } } - add_link(output, input); } } @@ -331,3 +313,5 @@ void NodeGraph::add_proxies_reroute(bNodeTree *b_ntree, b_node, (bNodeSocket *)b_node->inputs.first, (bNodeSocket *)b_node->outputs.first, false); add_node(proxy, b_ntree, key, is_active_group); } + +} // namespace blender::compositor diff --git a/source/blender/compositor/intern/COM_NodeGraph.h b/source/blender/compositor/intern/COM_NodeGraph.h index 990e3a30831..7fa01593e1e 100644 --- a/source/blender/compositor/intern/COM_NodeGraph.h +++ b/source/blender/compositor/intern/COM_NodeGraph.h @@ -22,7 +22,6 @@ #include <map> #include <set> -#include <vector> #include "DNA_node_types.h" @@ -30,6 +29,8 @@ # include "MEM_guardedalloc.h" #endif +namespace blender::compositor { + class CompositorContext; class Node; class NodeInput; @@ -50,22 +51,18 @@ class NodeGraph { } }; - typedef std::vector<Node *> Nodes; - typedef Nodes::iterator NodeIterator; - private: - Nodes m_nodes; - blender::Vector<Link> m_links; + Vector<Node *> m_nodes; + Vector<Link> m_links; public: - NodeGraph(); ~NodeGraph(); - const Nodes &nodes() const + const Vector<Node *> &nodes() const { return m_nodes; } - const blender::Vector<Link> &links() const + const Vector<Link> &links() const { return m_links; } @@ -73,8 +70,7 @@ class NodeGraph { void from_bNodeTree(const CompositorContext &context, bNodeTree *tree); protected: - typedef std::pair<NodeIterator, NodeIterator> NodeRange; - typedef std::vector<NodeInput *> NodeInputs; + typedef std::pair<Vector<Node *>::iterator, Vector<Node *>::iterator> NodeRange; static bNodeSocket *find_b_node_input(bNode *b_node, const char *identifier); static bNodeSocket *find_b_node_output(bNode *b_node, const char *identifier); @@ -93,7 +89,6 @@ class NodeGraph { bNodeInstanceKey key, bool is_active_group); - NodeInputs find_inputs(const NodeRange &node_range, bNodeSocket *b_socket); NodeOutput *find_output(const NodeRange &node_range, bNodeSocket *b_socket); void add_bNodeLink(const NodeRange &node_range, bNodeLink *b_nodelink); @@ -124,3 +119,5 @@ class NodeGraph { MEM_CXX_CLASS_ALLOC_FUNCS("COM:NodeGraph") #endif }; + +} // namespace blender::compositor diff --git a/source/blender/compositor/intern/COM_NodeOperation.cc b/source/blender/compositor/intern/COM_NodeOperation.cc index 0cc642479ac..83de8a751c4 100644 --- a/source/blender/compositor/intern/COM_NodeOperation.cc +++ b/source/blender/compositor/intern/COM_NodeOperation.cc @@ -17,13 +17,18 @@ */ #include <cstdio> +#include <memory> #include <typeinfo> +#include "COM_BufferOperation.h" #include "COM_ExecutionSystem.h" +#include "COM_ReadBufferOperation.h" #include "COM_defines.h" #include "COM_NodeOperation.h" /* own include */ +namespace blender::compositor { + /******************* **** NodeOperation **** *******************/ @@ -31,76 +36,66 @@ NodeOperation::NodeOperation() { this->m_resolutionInputSocketIndex = 0; - this->m_complex = false; this->m_width = 0; this->m_height = 0; - this->m_isResolutionSet = false; - this->m_openCL = false; this->m_btree = nullptr; } -NodeOperation::~NodeOperation() +NodeOperationOutput *NodeOperation::getOutputSocket(unsigned int index) { - while (!this->m_outputs.empty()) { - delete (this->m_outputs.back()); - this->m_outputs.pop_back(); - } - while (!this->m_inputs.empty()) { - delete (this->m_inputs.back()); - this->m_inputs.pop_back(); - } + return &m_outputs[index]; } -NodeOperationOutput *NodeOperation::getOutputSocket(unsigned int index) const +NodeOperationInput *NodeOperation::getInputSocket(unsigned int index) { - BLI_assert(index < m_outputs.size()); - return m_outputs[index]; + return &m_inputs[index]; } -NodeOperationInput *NodeOperation::getInputSocket(unsigned int index) const +void NodeOperation::addInputSocket(DataType datatype, ResizeMode resize_mode) { - BLI_assert(index < m_inputs.size()); - return m_inputs[index]; -} - -void NodeOperation::addInputSocket(DataType datatype, InputResizeMode resize_mode) -{ - NodeOperationInput *socket = new NodeOperationInput(this, datatype, resize_mode); - m_inputs.push_back(socket); + m_inputs.append(NodeOperationInput(this, datatype, resize_mode)); } void NodeOperation::addOutputSocket(DataType datatype) { - NodeOperationOutput *socket = new NodeOperationOutput(this, datatype); - m_outputs.push_back(socket); + m_outputs.append(NodeOperationOutput(this, datatype)); } void NodeOperation::determineResolution(unsigned int resolution[2], unsigned int preferredResolution[2]) { - unsigned int temp[2]; - unsigned int temp2[2]; - - for (unsigned int index = 0; index < m_inputs.size(); index++) { - NodeOperationInput *input = m_inputs[index]; - if (input->isConnected()) { - if (index == this->m_resolutionInputSocketIndex) { - input->determineResolution(resolution, preferredResolution); - temp2[0] = resolution[0]; - temp2[1] = resolution[1]; + unsigned int used_resolution_index = 0; + if (m_resolutionInputSocketIndex == RESOLUTION_INPUT_ANY) { + for (NodeOperationInput &input : m_inputs) { + unsigned int any_resolution[2] = {0, 0}; + input.determineResolution(any_resolution, preferredResolution); + if (any_resolution[0] * any_resolution[1] > 0) { + resolution[0] = any_resolution[0]; + resolution[1] = any_resolution[1]; break; } + used_resolution_index += 1; } } + else if (m_resolutionInputSocketIndex < m_inputs.size()) { + NodeOperationInput &input = m_inputs[m_resolutionInputSocketIndex]; + input.determineResolution(resolution, preferredResolution); + used_resolution_index = m_resolutionInputSocketIndex; + } + unsigned int temp2[2] = {resolution[0], resolution[1]}; + + unsigned int temp[2]; for (unsigned int index = 0; index < m_inputs.size(); index++) { - NodeOperationInput *input = m_inputs[index]; - if (input->isConnected()) { - if (index != this->m_resolutionInputSocketIndex) { - input->determineResolution(temp, temp2); - } + if (index == used_resolution_index) { + continue; + } + NodeOperationInput &input = m_inputs[index]; + if (input.isConnected()) { + input.determineResolution(temp, temp2); } } } + void NodeOperation::setResolutionInputSocketIndex(unsigned int index) { this->m_resolutionInputSocketIndex = index; @@ -149,20 +144,11 @@ NodeOperation *NodeOperation::getInputOperation(unsigned int inputSocketIndex) return nullptr; } -void NodeOperation::getConnectedInputSockets(Inputs *sockets) -{ - for (NodeOperationInput *input : m_inputs) { - if (input->isConnected()) { - sockets->push_back(input); - } - } -} - bool NodeOperation::determineDependingAreaOfInterest(rcti *input, ReadBufferOperation *readOperation, rcti *output) { - if (isInputOperation()) { + if (m_inputs.size() == 0) { BLI_rcti_init(output, input->xmin, input->xmax, input->ymin, input->ymax); return false; } @@ -191,13 +177,182 @@ bool NodeOperation::determineDependingAreaOfInterest(rcti *input, return !first; } +/* -------------------------------------------------------------------- */ +/** \name Full Frame Methods + * \{ */ + +/** + * \brief Get input operation area being read by this operation on rendering given output area. + * + * Implementation don't need to ensure r_input_area is within input operation bounds. The + * caller must clamp it. + * TODO: See if it's possible to use parameter overloading (input_id for example). + * + * \param input_op_idx: Input operation index for which we want to calculate the area being read. + * \param output_area: Area being rendered by this operation. + * \param r_input_area: Returned input operation area that needs to be read in order to render + * given output area. + */ +void NodeOperation::get_area_of_interest(const int input_op_idx, + const rcti &output_area, + rcti &r_input_area) +{ + if (get_flags().is_fullframe_operation) { + r_input_area = output_area; + } + else { + /* Non full-frame operations never implement this method. To ensure correctness assume + * whole area is used. */ + NodeOperation *input_op = getInputOperation(input_op_idx); + BLI_rcti_init(&r_input_area, 0, input_op->getWidth(), 0, input_op->getHeight()); + } +} + +void NodeOperation::get_area_of_interest(NodeOperation *input_op, + const rcti &output_area, + rcti &r_input_area) +{ + for (int i = 0; i < getNumberOfInputSockets(); i++) { + if (input_op == getInputOperation(i)) { + get_area_of_interest(i, output_area, r_input_area); + return; + } + } + BLI_assert(!"input_op is not an input operation."); +} + +/** + * Executes operation image manipulation algorithm rendering given areas. + * \param output_buf: Buffer to write result to. + * \param areas: Areas within this operation bounds to render. + * \param inputs_bufs: Inputs operations buffers. + * \param exec_system: Execution system. + */ +void NodeOperation::render(MemoryBuffer *output_buf, + Span<rcti> areas, + Span<MemoryBuffer *> inputs_bufs, + ExecutionSystem &exec_system) +{ + if (get_flags().is_fullframe_operation) { + render_full_frame(output_buf, areas, inputs_bufs, exec_system); + } + else { + render_full_frame_fallback(output_buf, areas, inputs_bufs, exec_system); + } +} + +/** + * Renders given areas using operations full frame implementation. + */ +void NodeOperation::render_full_frame(MemoryBuffer *output_buf, + Span<rcti> areas, + Span<MemoryBuffer *> inputs_bufs, + ExecutionSystem &exec_system) +{ + initExecution(); + for (const rcti &area : areas) { + update_memory_buffer(output_buf, area, inputs_bufs, exec_system); + } + deinitExecution(); +} + +/** + * Renders given areas using operations tiled implementation. + */ +void NodeOperation::render_full_frame_fallback(MemoryBuffer *output_buf, + Span<rcti> areas, + Span<MemoryBuffer *> inputs_bufs, + ExecutionSystem &exec_system) +{ + Vector<NodeOperationOutput *> orig_input_links = replace_inputs_with_buffers(inputs_bufs); + + initExecution(); + const bool is_output_operation = getNumberOfOutputSockets() == 0; + if (!is_output_operation && output_buf->is_a_single_elem()) { + float *output_elem = output_buf->get_elem(0, 0); + readSampled(output_elem, 0, 0, PixelSampler::Nearest); + } + else { + for (const rcti &rect : areas) { + exec_system.execute_work(rect, [=](const rcti &split_rect) { + rcti tile_rect = split_rect; + if (is_output_operation) { + executeRegion(&tile_rect, 0); + } + else { + render_tile(output_buf, &tile_rect); + } + }); + } + } + deinitExecution(); + + remove_buffers_and_restore_original_inputs(orig_input_links); +} + +void NodeOperation::render_tile(MemoryBuffer *output_buf, rcti *tile_rect) +{ + const bool is_complex = get_flags().complex; + void *tile_data = is_complex ? initializeTileData(tile_rect) : nullptr; + const int elem_stride = output_buf->elem_stride; + for (int y = tile_rect->ymin; y < tile_rect->ymax; y++) { + float *output_elem = output_buf->get_elem(tile_rect->xmin, y); + if (is_complex) { + for (int x = tile_rect->xmin; x < tile_rect->xmax; x++) { + read(output_elem, x, y, tile_data); + output_elem += elem_stride; + } + } + else { + for (int x = tile_rect->xmin; x < tile_rect->xmax; x++) { + readSampled(output_elem, x, y, PixelSampler::Nearest); + output_elem += elem_stride; + } + } + } + if (tile_data) { + deinitializeTileData(tile_rect, tile_data); + } +} + +/** + * \return Replaced inputs links. + */ +Vector<NodeOperationOutput *> NodeOperation::replace_inputs_with_buffers( + Span<MemoryBuffer *> inputs_bufs) +{ + BLI_assert(inputs_bufs.size() == getNumberOfInputSockets()); + Vector<NodeOperationOutput *> orig_links(inputs_bufs.size()); + for (int i = 0; i < inputs_bufs.size(); i++) { + NodeOperationInput *input_socket = getInputSocket(i); + BufferOperation *buffer_op = new BufferOperation(inputs_bufs[i], input_socket->getDataType()); + orig_links[i] = input_socket->getLink(); + input_socket->setLink(buffer_op->getOutputSocket()); + } + return orig_links; +} + +void NodeOperation::remove_buffers_and_restore_original_inputs( + Span<NodeOperationOutput *> original_inputs_links) +{ + BLI_assert(original_inputs_links.size() == getNumberOfInputSockets()); + for (int i = 0; i < original_inputs_links.size(); i++) { + NodeOperation *buffer_op = get_input_operation(i); + BLI_assert(buffer_op != nullptr); + BLI_assert(typeid(*buffer_op) == typeid(BufferOperation)); + NodeOperationInput *input_socket = getInputSocket(i); + input_socket->setLink(original_inputs_links[i]); + delete buffer_op; + } +} + +/** \} */ + /***************** **** OpInput **** *****************/ -NodeOperationInput::NodeOperationInput(NodeOperation *op, - DataType datatype, - InputResizeMode resizeMode) +NodeOperationInput::NodeOperationInput(NodeOperation *op, DataType datatype, ResizeMode resizeMode) : m_operation(op), m_datatype(datatype), m_resizeMode(resizeMode), m_link(nullptr) { } @@ -232,12 +387,88 @@ void NodeOperationOutput::determineResolution(unsigned int resolution[2], unsigned int preferredResolution[2]) { NodeOperation &operation = getOperation(); - if (operation.isResolutionSet()) { + if (operation.get_flags().is_resolution_set) { resolution[0] = operation.getWidth(); resolution[1] = operation.getHeight(); } else { operation.determineResolution(resolution, preferredResolution); - operation.setResolution(resolution); + if (resolution[0] > 0 && resolution[1] > 0) { + operation.setResolution(resolution); + } + } +} + +std::ostream &operator<<(std::ostream &os, const NodeOperationFlags &node_operation_flags) +{ + if (node_operation_flags.complex) { + os << "complex,"; + } + if (node_operation_flags.open_cl) { + os << "open_cl,"; + } + if (node_operation_flags.single_threaded) { + os << "single_threaded,"; + } + if (node_operation_flags.use_render_border) { + os << "render_border,"; + } + if (node_operation_flags.use_viewer_border) { + os << "view_border,"; + } + if (node_operation_flags.is_resolution_set) { + os << "resolution_set,"; + } + if (node_operation_flags.is_set_operation) { + os << "set_operation,"; + } + if (node_operation_flags.is_write_buffer_operation) { + os << "write_buffer,"; + } + if (node_operation_flags.is_read_buffer_operation) { + os << "read_buffer,"; + } + if (node_operation_flags.is_proxy_operation) { + os << "proxy,"; + } + if (node_operation_flags.is_viewer_operation) { + os << "viewer,"; } + if (node_operation_flags.is_preview_operation) { + os << "preview,"; + } + if (!node_operation_flags.use_datatype_conversion) { + os << "no_conversion,"; + } + if (node_operation_flags.is_fullframe_operation) { + os << "full_frame,"; + } + + return os; } + +std::ostream &operator<<(std::ostream &os, const NodeOperation &node_operation) +{ + NodeOperationFlags flags = node_operation.get_flags(); + os << "NodeOperation("; + os << "id=" << node_operation.get_id(); + if (!node_operation.get_name().empty()) { + os << ",name=" << node_operation.get_name(); + } + os << ",flags={" << flags << "}"; + if (flags.is_read_buffer_operation) { + const ReadBufferOperation *read_operation = (const ReadBufferOperation *)&node_operation; + const MemoryProxy *proxy = read_operation->getMemoryProxy(); + if (proxy) { + const WriteBufferOperation *write_operation = proxy->getWriteBufferOperation(); + if (write_operation) { + os << ",write=" << (NodeOperation &)*write_operation; + } + } + } + os << ")"; + + return os; +} + +} // namespace blender::compositor diff --git a/source/blender/compositor/intern/COM_NodeOperation.h b/source/blender/compositor/intern/COM_NodeOperation.h index 7857837a95d..5c4d6dd19ba 100644 --- a/source/blender/compositor/intern/COM_NodeOperation.h +++ b/source/blender/compositor/intern/COM_NodeOperation.h @@ -26,77 +26,258 @@ #include "BLI_math_vector.h" #include "BLI_threads.h" +#include "COM_Enums.h" #include "COM_MemoryBuffer.h" #include "COM_MemoryProxy.h" +#include "COM_MetaData.h" #include "COM_Node.h" -#include "COM_SocketReader.h" #include "clew.h" +namespace blender::compositor { + class OpenCLDevice; class ReadBufferOperation; class WriteBufferOperation; +class ExecutionSystem; + +class NodeOperation; +typedef NodeOperation SocketReader; -class NodeOperationInput; -class NodeOperationOutput; +/** + * RESOLUTION_INPUT_ANY is a wildcard when any resolution of an input can be used. + * This solves the issue that the FileInputNode in a group node cannot find the + * correct resolution. + */ +static constexpr unsigned int RESOLUTION_INPUT_ANY = 999999; /** * \brief Resize modes of inputsockets * How are the input and working resolutions matched * \ingroup Model */ -typedef enum InputResizeMode { +enum class ResizeMode { /** \brief Center the input image to the center of the working area of the node, no resizing * occurs */ - COM_SC_CENTER = NS_CR_CENTER, + Center = NS_CR_CENTER, /** \brief The bottom left of the input image is the bottom left of the working area of the node, * no resizing occurs */ - COM_SC_NO_RESIZE = NS_CR_NONE, + None = NS_CR_NONE, /** \brief Fit the width of the input image to the width of the working area of the node */ - COM_SC_FIT_WIDTH = NS_CR_FIT_WIDTH, + FitWidth = NS_CR_FIT_WIDTH, /** \brief Fit the height of the input image to the height of the working area of the node */ - COM_SC_FIT_HEIGHT = NS_CR_FIT_HEIGHT, + FitHeight = NS_CR_FIT_HEIGHT, /** \brief Fit the width or the height of the input image to the width or height of the working * area of the node, image will be larger than the working area */ - COM_SC_FIT = NS_CR_FIT, + FitAny = NS_CR_FIT, /** \brief Fit the width and the height of the input image to the width and height of the working * area of the node, image will be equally larger than the working area */ - COM_SC_STRETCH = NS_CR_STRETCH, -} InputResizeMode; + Stretch = NS_CR_STRETCH, +}; + +enum class PixelSampler { + Nearest = 0, + Bilinear = 1, + Bicubic = 2, +}; + +class NodeOperationInput { + private: + NodeOperation *m_operation; + + /** Datatype of this socket. Is used for automatically data transformation. + * \section data-conversion + */ + DataType m_datatype; + + /** Resize mode of this socket */ + ResizeMode m_resizeMode; + + /** Connected output */ + NodeOperationOutput *m_link; -/** - * \brief NodeOperation contains calculation logic - * - * Subclasses needs to implement the execution method (defined in SocketReader) to implement logic. - * \ingroup Model - */ -class NodeOperation : public SocketReader { public: - typedef std::vector<NodeOperationInput *> Inputs; - typedef std::vector<NodeOperationOutput *> Outputs; + NodeOperationInput(NodeOperation *op, + DataType datatype, + ResizeMode resizeMode = ResizeMode::Center); + NodeOperation &getOperation() const + { + return *m_operation; + } + DataType getDataType() const + { + return m_datatype; + } + + void setLink(NodeOperationOutput *link) + { + m_link = link; + } + NodeOperationOutput *getLink() const + { + return m_link; + } + bool isConnected() const + { + return m_link; + } + + void setResizeMode(ResizeMode resizeMode) + { + this->m_resizeMode = resizeMode; + } + ResizeMode getResizeMode() const + { + return this->m_resizeMode; + } + + SocketReader *getReader(); + + void determineResolution(unsigned int resolution[2], unsigned int preferredResolution[2]); + +#ifdef WITH_CXX_GUARDEDALLOC + MEM_CXX_CLASS_ALLOC_FUNCS("COM:NodeOperation") +#endif +}; + +class NodeOperationOutput { private: - Inputs m_inputs; - Outputs m_outputs; + NodeOperation *m_operation; + + /** Datatype of this socket. Is used for automatically data transformation. + * \section data-conversion + */ + DataType m_datatype; + + public: + NodeOperationOutput(NodeOperation *op, DataType datatype); + + NodeOperation &getOperation() const + { + return *m_operation; + } + DataType getDataType() const + { + return m_datatype; + } /** - * \brief the index of the input socket that will be used to determine the resolution + * \brief determine the resolution of this data going through this socket + * \param resolution: the result of this operation + * \param preferredResolution: the preferable resolution as no resolution could be determined */ - unsigned int m_resolutionInputSocketIndex; + void determineResolution(unsigned int resolution[2], unsigned int preferredResolution[2]); +#ifdef WITH_CXX_GUARDEDALLOC + MEM_CXX_CLASS_ALLOC_FUNCS("COM:NodeOperation") +#endif +}; + +struct NodeOperationFlags { /** - * \brief is this operation a complex one. + * Is this an complex operation. + * + * The input and output buffers of Complex operations are stored in buffers. It allows + * sequential and read/write. * * Complex operations are typically doing many reads to calculate the output of a single pixel. * Mostly Filter types (Blurs, Convolution, Defocus etc) need this to be set to true. */ - bool m_complex; + bool complex : 1; + + /** + * Does this operation support OpenCL. + */ + bool open_cl : 1; + + /** + * TODO: Remove this flag and #SingleThreadedOperation if tiled implementation is removed. + * Full-frame implementation doesn't need it. + */ + bool single_threaded : 1; /** - * \brief can this operation be scheduled on an OpenCL device. - * \note Only applicable if complex is True + * Does the operation needs a viewer border. + * Basically, setting border need to happen for only operations + * which operates in render resolution buffers (like compositor + * output nodes). + * + * In this cases adding border will lead to mapping coordinates + * from output buffer space to input buffer spaces when executing + * operation. + * + * But nodes like viewer and file output just shall display or + * safe the same exact buffer which goes to their input, no need + * in any kind of coordinates mapping. + */ + bool use_render_border : 1; + bool use_viewer_border : 1; + + /** + * Is the resolution of the operation set. */ - bool m_openCL; + bool is_resolution_set : 1; + + /** + * Is this a set operation (value, color, vector). + */ + bool is_set_operation : 1; + bool is_write_buffer_operation : 1; + bool is_read_buffer_operation : 1; + bool is_proxy_operation : 1; + bool is_viewer_operation : 1; + bool is_preview_operation : 1; + + /** + * When set additional data conversion operations are added to + * convert the data. SocketProxyOperation don't always need to do data conversions. + * + * By default data conversions are enabled. + */ + bool use_datatype_conversion : 1; + + /** + * Has this operation fullframe implementation. + */ + bool is_fullframe_operation : 1; + + NodeOperationFlags() + { + complex = false; + single_threaded = false; + open_cl = false; + use_render_border = false; + use_viewer_border = false; + is_resolution_set = false; + is_set_operation = false; + is_read_buffer_operation = false; + is_write_buffer_operation = false; + is_proxy_operation = false; + is_viewer_operation = false; + is_preview_operation = false; + use_datatype_conversion = true; + is_fullframe_operation = false; + } +}; + +/** + * \brief NodeOperation contains calculation logic + * + * Subclasses needs to implement the execution method (defined in SocketReader) to implement logic. + * \ingroup Model + */ +class NodeOperation { + private: + int m_id; + std::string m_name; + Vector<NodeOperationInput> m_inputs; + Vector<NodeOperationOutput> m_outputs; + + /** + * \brief the index of the input socket that will be used to determine the resolution + */ + unsigned int m_resolutionInputSocketIndex; /** * \brief mutex reference for very special node initializations @@ -114,13 +295,61 @@ class NodeOperation : public SocketReader { */ const bNodeTree *m_btree; + protected: /** - * \brief set to truth when resolution for this operation is set + * Compositor execution model. */ - bool m_isResolutionSet; + eExecutionModel execution_model_; + + /** + * Width of the output of this operation. + */ + unsigned int m_width; + + /** + * Height of the output of this operation. + */ + unsigned int m_height; + + /** + * Flags how to evaluate this operation. + */ + NodeOperationFlags flags; public: - virtual ~NodeOperation(); + virtual ~NodeOperation() + { + } + + void set_execution_model(const eExecutionModel model) + { + execution_model_ = model; + } + + void set_name(const std::string name) + { + m_name = name; + } + + const std::string get_name() const + { + return m_name; + } + + void set_id(const int id) + { + m_id = id; + } + + const int get_id() const + { + return m_id; + } + + const NodeOperationFlags get_flags() const + { + return flags; + } unsigned int getNumberOfInputSockets() const { @@ -130,19 +359,14 @@ class NodeOperation : public SocketReader { { return m_outputs.size(); } - NodeOperationOutput *getOutputSocket(unsigned int index) const; - NodeOperationOutput *getOutputSocket() const - { - return getOutputSocket(0); - } - NodeOperationInput *getInputSocket(unsigned int index) const; + NodeOperationOutput *getOutputSocket(unsigned int index = 0); + NodeOperationInput *getInputSocket(unsigned int index); - /** Check if this is an input operation - * An input operation is an operation that only has output sockets and no input sockets - */ - bool isInputOperation() const + NodeOperation *get_input_operation(int index) { - return m_inputs.empty(); + /* TODO: Rename protected getInputOperation to get_input_operation and make it public replacing + * this method. */ + return getInputOperation(index); } /** @@ -155,11 +379,11 @@ class NodeOperation : public SocketReader { unsigned int preferredResolution[2]); /** - * \brief isOutputOperation determines whether this operation is an output of the ExecutionSystem - * during rendering or editing. + * \brief isOutputOperation determines whether this operation is an output of the + * ExecutionSystem during rendering or editing. * - * Default behavior if not overridden, this operation will not be evaluated as being an output of - * the ExecutionSystem. + * Default behavior if not overridden, this operation will not be evaluated as being an output + * of the ExecutionSystem. * * \see ExecutionSystem * \ingroup check @@ -174,11 +398,6 @@ class NodeOperation : public SocketReader { return false; } - virtual int isSingleThreaded() - { - return false; - } - void setbNodeTree(const bNodeTree *tree) { this->m_btree = tree; @@ -241,62 +460,19 @@ class NodeOperation : public SocketReader { } virtual void deinitExecution(); - bool isResolutionSet() - { - return this->m_isResolutionSet; - } - /** * \brief set the resolution * \param resolution: the resolution to set */ void setResolution(unsigned int resolution[2]) { - if (!isResolutionSet()) { + if (!this->flags.is_resolution_set) { this->m_width = resolution[0]; this->m_height = resolution[1]; - this->m_isResolutionSet = true; + this->flags.is_resolution_set = true; } } - void getConnectedInputSockets(Inputs *sockets); - - /** - * \brief is this operation complex - * - * Complex operations are typically doing many reads to calculate the output of a single pixel. - * Mostly Filter types (Blurs, Convolution, Defocus etc) need this to be set to true. - */ - bool isComplex() const - { - return this->m_complex; - } - - virtual bool isSetOperation() const - { - return false; - } - - /** - * \brief is this operation of type ReadBufferOperation - * \return [true:false] - * \see ReadBufferOperation - */ - virtual bool isReadBufferOperation() const - { - return false; - } - - /** - * \brief is this operation of type WriteBufferOperation - * \return [true:false] - * \see WriteBufferOperation - */ - virtual bool isWriteBufferOperation() const - { - return false; - } - /** * \brief is this operation the active viewer output * user can select an ViewerNode to be active @@ -314,80 +490,123 @@ class NodeOperation : public SocketReader { rcti *output); /** - * \brief set the index of the input socket that will determine the resolution of this operation - * \param index: the index to set + * \brief set the index of the input socket that will determine the resolution of this + * operation \param index: the index to set */ void setResolutionInputSocketIndex(unsigned int index); /** * \brief get the render priority of this node. * \note only applicable for output operations like ViewerOperation - * \return CompositorPriority + * \return eCompositorPriority */ - virtual CompositorPriority getRenderPriority() const + virtual eCompositorPriority getRenderPriority() const { - return CompositorPriority::Low; + return eCompositorPriority::Low; } - /** - * \brief can this NodeOperation be scheduled on an OpenCLDevice - * \see WorkScheduler.schedule - * \see ExecutionGroup.addOperation - */ - bool isOpenCL() const + inline bool isBraked() const { - return this->m_openCL; + return this->m_btree->test_break(this->m_btree->tbh); } - virtual bool isViewerOperation() const + inline void updateDraw() { - return false; + if (this->m_btree->update_draw) { + this->m_btree->update_draw(this->m_btree->udh); + } } - virtual bool isPreviewOperation() const + + unsigned int getWidth() const { - return false; + return m_width; } - virtual bool isFileOutputOperation() const + + unsigned int getHeight() const { - return false; + return m_height; } - virtual bool isProxyOperation() const + + inline void readSampled(float result[4], float x, float y, PixelSampler sampler) { - return false; + executePixelSampled(result, x, y, sampler); } - virtual bool useDatatypeConversion() const + inline void readFiltered(float result[4], float x, float y, float dx[2], float dy[2]) { - return true; + executePixelFiltered(result, x, y, dx, dy); } - inline bool isBraked() const + inline void read(float result[4], int x, int y, void *chunkData) { - return this->m_btree->test_break(this->m_btree->tbh); + executePixel(result, x, y, chunkData); } - inline void updateDraw() + virtual void *initializeTileData(rcti * /*rect*/) + { + return 0; + } + + virtual void deinitializeTileData(rcti * /*rect*/, void * /*data*/) { - if (this->m_btree->update_draw) { - this->m_btree->update_draw(this->m_btree->udh); - } } + virtual MemoryBuffer *getInputMemoryBuffer(MemoryBuffer ** /*memoryBuffers*/) + { + return 0; + } + + /** + * Return the meta data associated with this branch. + * + * The return parameter holds an instance or is an nullptr. */ + virtual std::unique_ptr<MetaData> getMetaData() + { + return std::unique_ptr<MetaData>(); + } + + /* -------------------------------------------------------------------- */ + /** \name Full Frame Methods + * \{ */ + + void render(MemoryBuffer *output_buf, + Span<rcti> areas, + Span<MemoryBuffer *> inputs_bufs, + ExecutionSystem &exec_system); + + /** + * Executes operation updating output memory buffer. Single-threaded calls. + */ + virtual void update_memory_buffer(MemoryBuffer *UNUSED(output), + const rcti &UNUSED(output_area), + Span<MemoryBuffer *> UNUSED(inputs), + ExecutionSystem &UNUSED(exec_system)) + { + } + + /** + * Get input operation area being read by this operation on rendering given output area. + */ + virtual void get_area_of_interest(int input_op_idx, const rcti &output_area, rcti &r_input_area); + void get_area_of_interest(NodeOperation *input_op, const rcti &output_area, rcti &r_input_area); + + /** \} */ + protected: NodeOperation(); - void addInputSocket(DataType datatype, InputResizeMode resize_mode = COM_SC_CENTER); + void addInputSocket(DataType datatype, ResizeMode resize_mode = ResizeMode::Center); void addOutputSocket(DataType datatype); void setWidth(unsigned int width) { this->m_width = width; - this->m_isResolutionSet = true; + this->flags.is_resolution_set = true; } void setHeight(unsigned int height) { this->m_height = height; - this->m_isResolutionSet = true; + this->flags.is_resolution_set = true; } SocketReader *getInputSocketReader(unsigned int inputSocketindex); NodeOperation *getInputOperation(unsigned int inputSocketindex); @@ -405,114 +624,83 @@ class NodeOperation : public SocketReader { */ void setComplex(bool complex) { - this->m_complex = complex; + this->flags.complex = complex; } /** - * \brief set if this NodeOperation can be scheduled on a OpenCLDevice + * \brief calculate a single pixel + * \note this method is called for non-complex + * \param result: is a float[4] array to store the result + * \param x: the x-coordinate of the pixel to calculate in image space + * \param y: the y-coordinate of the pixel to calculate in image space + * \param inputBuffers: chunks that can be read by their ReadBufferOperation. */ - void setOpenCL(bool openCL) + virtual void executePixelSampled(float /*output*/[4], + float /*x*/, + float /*y*/, + PixelSampler /*sampler*/) { - this->m_openCL = openCL; } - /* allow the DebugInfo class to look at internals */ - friend class DebugInfo; - -#ifdef WITH_CXX_GUARDEDALLOC - MEM_CXX_CLASS_ALLOC_FUNCS("COM:NodeOperation") -#endif -}; - -class NodeOperationInput { - private: - NodeOperation *m_operation; - - /** Datatype of this socket. Is used for automatically data transformation. - * \section data-conversion + /** + * \brief calculate a single pixel + * \note this method is called for complex + * \param result: is a float[4] array to store the result + * \param x: the x-coordinate of the pixel to calculate in image space + * \param y: the y-coordinate of the pixel to calculate in image space + * \param inputBuffers: chunks that can be read by their ReadBufferOperation. + * \param chunkData: chunk specific data a during execution time. */ - DataType m_datatype; - - /** Resize mode of this socket */ - InputResizeMode m_resizeMode; - - /** Connected output */ - NodeOperationOutput *m_link; - - public: - NodeOperationInput(NodeOperation *op, - DataType datatype, - InputResizeMode resizeMode = COM_SC_CENTER); - - NodeOperation &getOperation() const - { - return *m_operation; - } - DataType getDataType() const - { - return m_datatype; - } - - void setLink(NodeOperationOutput *link) + virtual void executePixel(float output[4], int x, int y, void * /*chunkData*/) { - m_link = link; - } - NodeOperationOutput *getLink() const - { - return m_link; - } - bool isConnected() const - { - return m_link; + executePixelSampled(output, x, y, PixelSampler::Nearest); } - void setResizeMode(InputResizeMode resizeMode) - { - this->m_resizeMode = resizeMode; - } - InputResizeMode getResizeMode() const + /** + * \brief calculate a single pixel using an EWA filter + * \note this method is called for complex + * \param result: is a float[4] array to store the result + * \param x: the x-coordinate of the pixel to calculate in image space + * \param y: the y-coordinate of the pixel to calculate in image space + * \param dx: + * \param dy: + * \param inputBuffers: chunks that can be read by their ReadBufferOperation. + */ + virtual void executePixelFiltered( + float /*output*/[4], float /*x*/, float /*y*/, float /*dx*/[2], float /*dy*/[2]) { - return this->m_resizeMode; } - SocketReader *getReader(); + private: + /* -------------------------------------------------------------------- */ + /** \name Full Frame Methods + * \{ */ + + void render_full_frame(MemoryBuffer *output_buf, + Span<rcti> areas, + Span<MemoryBuffer *> inputs_bufs, + ExecutionSystem &exec_system); + + void render_full_frame_fallback(MemoryBuffer *output_buf, + Span<rcti> areas, + Span<MemoryBuffer *> inputs, + ExecutionSystem &exec_system); + void render_tile(MemoryBuffer *output_buf, rcti *tile_rect); + Vector<NodeOperationOutput *> replace_inputs_with_buffers(Span<MemoryBuffer *> inputs_bufs); + void remove_buffers_and_restore_original_inputs( + Span<NodeOperationOutput *> original_inputs_links); + + /** \} */ - void determineResolution(unsigned int resolution[2], unsigned int preferredResolution[2]); + /* allow the DebugInfo class to look at internals */ + friend class DebugInfo; #ifdef WITH_CXX_GUARDEDALLOC MEM_CXX_CLASS_ALLOC_FUNCS("COM:NodeOperation") #endif }; -class NodeOperationOutput { - private: - NodeOperation *m_operation; - - /** Datatype of this socket. Is used for automatically data transformation. - * \section data-conversion - */ - DataType m_datatype; - - public: - NodeOperationOutput(NodeOperation *op, DataType datatype); +std::ostream &operator<<(std::ostream &os, const NodeOperationFlags &node_operation_flags); +std::ostream &operator<<(std::ostream &os, const NodeOperation &node_operation); - NodeOperation &getOperation() const - { - return *m_operation; - } - DataType getDataType() const - { - return m_datatype; - } - - /** - * \brief determine the resolution of this data going through this socket - * \param resolution: the result of this operation - * \param preferredResolution: the preferable resolution as no resolution could be determined - */ - void determineResolution(unsigned int resolution[2], unsigned int preferredResolution[2]); - -#ifdef WITH_CXX_GUARDEDALLOC - MEM_CXX_CLASS_ALLOC_FUNCS("COM:NodeOperation") -#endif -}; +} // namespace blender::compositor diff --git a/source/blender/compositor/intern/COM_NodeOperationBuilder.cc b/source/blender/compositor/intern/COM_NodeOperationBuilder.cc index 1c741283c7b..1beb83bb477 100644 --- a/source/blender/compositor/intern/COM_NodeOperationBuilder.cc +++ b/source/blender/compositor/intern/COM_NodeOperationBuilder.cc @@ -38,24 +38,20 @@ #include "COM_NodeOperationBuilder.h" /* own include */ +namespace blender::compositor { + NodeOperationBuilder::NodeOperationBuilder(const CompositorContext *context, bNodeTree *b_nodetree) : m_context(context), m_current_node(nullptr), m_active_viewer(nullptr) { m_graph.from_bNodeTree(*context, b_nodetree); } -NodeOperationBuilder::~NodeOperationBuilder() -{ -} - void NodeOperationBuilder::convertToOperations(ExecutionSystem *system) { /* interface handle for nodes */ NodeConverter converter(this); - for (int index = 0; index < m_graph.nodes().size(); index++) { - Node *node = (Node *)m_graph.nodes()[index]; - + for (Node *node : m_graph.nodes()) { m_current_node = node; DebugInfo::node_to_operations(node); @@ -69,7 +65,7 @@ void NodeOperationBuilder::convertToOperations(ExecutionSystem *system) * so multiple operations can use the same node input. */ blender::MultiValueMap<NodeInput *, NodeOperationInput *> inverse_input_map; - for (blender::Map<NodeOperationInput *, NodeInput *>::MutableItem item : m_input_map.items()) { + for (Map<NodeOperationInput *, NodeInput *>::MutableItem item : m_input_map.items()) { inverse_input_map.add(item.value, item.key); } @@ -103,8 +99,10 @@ void NodeOperationBuilder::convertToOperations(ExecutionSystem *system) determineResolutions(); - /* surround complex ops with read/write buffer */ - add_complex_operation_buffers(); + if (m_context->get_execution_model() == eExecutionModel::Tiled) { + /* surround complex ops with read/write buffer */ + add_complex_operation_buffers(); + } /* links not available from here on */ /* XXX make m_links a local variable to avoid confusion! */ @@ -115,8 +113,10 @@ void NodeOperationBuilder::convertToOperations(ExecutionSystem *system) /* ensure topological (link-based) order of nodes */ /*sort_operations();*/ /* not needed yet */ - /* create execution groups */ - group_operations(); + if (m_context->get_execution_model() == eExecutionModel::Tiled) { + /* create execution groups */ + group_operations(); + } /* transfer resulting operations to the system */ system->set_operations(m_operations, m_groups); @@ -124,7 +124,12 @@ void NodeOperationBuilder::convertToOperations(ExecutionSystem *system) void NodeOperationBuilder::addOperation(NodeOperation *operation) { + operation->set_id(m_operations.size()); m_operations.append(operation); + if (m_current_node) { + operation->set_name(m_current_node->getbNode()->name); + } + operation->set_execution_model(m_context->get_execution_model()); } void NodeOperationBuilder::mapInputSocket(NodeInput *node_socket, @@ -251,12 +256,13 @@ void NodeOperationBuilder::registerViewer(ViewerOperation *viewer) void NodeOperationBuilder::add_datatype_conversions() { - blender::Vector<Link> convert_links; + Vector<Link> convert_links; for (const Link &link : m_links) { /* proxy operations can skip data type conversion */ NodeOperation *from_op = &link.from()->getOperation(); NodeOperation *to_op = &link.to()->getOperation(); - if (!(from_op->useDatatypeConversion() || to_op->useDatatypeConversion())) { + if (!(from_op->get_flags().use_datatype_conversion || + to_op->get_flags().use_datatype_conversion)) { continue; } @@ -281,7 +287,7 @@ void NodeOperationBuilder::add_operation_input_constants() /* Note: unconnected inputs cached first to avoid modifying * m_operations while iterating over it */ - blender::Vector<NodeOperationInput *> pending_inputs; + Vector<NodeOperationInput *> pending_inputs; for (NodeOperation *op : m_operations) { for (int k = 0; k < op->getNumberOfInputSockets(); ++k) { NodeOperationInput *input = op->getInputSocket(k); @@ -349,11 +355,11 @@ void NodeOperationBuilder::add_input_constant_value(NodeOperationInput *input, void NodeOperationBuilder::resolve_proxies() { - blender::Vector<Link> proxy_links; + Vector<Link> proxy_links; for (const Link &link : m_links) { /* don't replace links from proxy to proxy, since we may need them for replacing others! */ - if (link.from()->getOperation().isProxyOperation() && - !link.to()->getOperation().isProxyOperation()) { + if (link.from()->getOperation().get_flags().is_proxy_operation && + !link.to()->getOperation().get_flags().is_proxy_operation) { proxy_links.append(link); } } @@ -364,7 +370,7 @@ void NodeOperationBuilder::resolve_proxies() do { /* walk upstream bypassing the proxy operation */ from = from->getOperation().getInputSocket(0)->getLink(); - } while (from && from->getOperation().isProxyOperation()); + } while (from && from->getOperation().get_flags().is_proxy_operation); removeInputLink(to); /* we may not have a final proxy input link, @@ -380,7 +386,7 @@ void NodeOperationBuilder::determineResolutions() { /* determine all resolutions of the operations (Width/Height) */ for (NodeOperation *op : m_operations) { - if (op->isOutputOperation(m_context->isRendering()) && !op->isPreviewOperation()) { + if (op->isOutputOperation(m_context->isRendering()) && !op->get_flags().is_preview_operation) { unsigned int resolution[2] = {0, 0}; unsigned int preferredResolution[2] = {0, 0}; op->determineResolution(resolution, preferredResolution); @@ -389,7 +395,7 @@ void NodeOperationBuilder::determineResolutions() } for (NodeOperation *op : m_operations) { - if (op->isOutputOperation(m_context->isRendering()) && op->isPreviewOperation()) { + if (op->isOutputOperation(m_context->isRendering()) && op->get_flags().is_preview_operation) { unsigned int resolution[2] = {0, 0}; unsigned int preferredResolution[2] = {0, 0}; op->determineResolution(resolution, preferredResolution); @@ -399,9 +405,9 @@ void NodeOperationBuilder::determineResolutions() /* add convert resolution operations when needed */ { - blender::Vector<Link> convert_links; + Vector<Link> convert_links; for (const Link &link : m_links) { - if (link.to()->getResizeMode() != COM_SC_NO_RESIZE) { + if (link.to()->getResizeMode() != ResizeMode::None) { NodeOperation &from_op = link.from()->getOperation(); NodeOperation &to_op = link.to()->getOperation(); if (from_op.getWidth() != to_op.getWidth() || from_op.getHeight() != to_op.getHeight()) { @@ -415,10 +421,10 @@ void NodeOperationBuilder::determineResolutions() } } -blender::Vector<NodeOperationInput *> NodeOperationBuilder::cache_output_links( +Vector<NodeOperationInput *> NodeOperationBuilder::cache_output_links( NodeOperationOutput *output) const { - blender::Vector<NodeOperationInput *> inputs; + Vector<NodeOperationInput *> inputs; for (const Link &link : m_links) { if (link.from() == output) { inputs.append(link.to()); @@ -433,7 +439,7 @@ WriteBufferOperation *NodeOperationBuilder::find_attached_write_buffer_operation for (const Link &link : m_links) { if (link.from() == output) { NodeOperation &op = link.to()->getOperation(); - if (op.isWriteBufferOperation()) { + if (op.get_flags().is_write_buffer_operation) { return (WriteBufferOperation *)(&op); } } @@ -449,7 +455,7 @@ void NodeOperationBuilder::add_input_buffers(NodeOperation * /*operation*/, } NodeOperationOutput *output = input->getLink(); - if (output->getOperation().isReadBufferOperation()) { + if (output->getOperation().get_flags().is_read_buffer_operation) { /* input is already buffered, no need to add another */ return; } @@ -483,7 +489,7 @@ void NodeOperationBuilder::add_output_buffers(NodeOperation *operation, NodeOperationOutput *output) { /* cache connected sockets, so we can safely remove links first before replacing them */ - blender::Vector<NodeOperationInput *> targets = cache_output_links(output); + Vector<NodeOperationInput *> targets = cache_output_links(output); if (targets.is_empty()) { return; } @@ -491,7 +497,7 @@ void NodeOperationBuilder::add_output_buffers(NodeOperation *operation, WriteBufferOperation *writeOperation = nullptr; for (NodeOperationInput *target : targets) { /* try to find existing write buffer operation */ - if (target->getOperation().isWriteBufferOperation()) { + if (target->getOperation().get_flags().is_write_buffer_operation) { BLI_assert(writeOperation == nullptr); /* there should only be one write op connected */ writeOperation = (WriteBufferOperation *)(&target->getOperation()); } @@ -534,9 +540,9 @@ void NodeOperationBuilder::add_complex_operation_buffers() /* note: complex ops and get cached here first, since adding operations * will invalidate iterators over the main m_operations */ - blender::Vector<NodeOperation *> complex_ops; + Vector<NodeOperation *> complex_ops; for (NodeOperation *operation : m_operations) { - if (operation->isComplex()) { + if (operation->get_flags().complex) { complex_ops.append(operation); } } @@ -571,7 +577,7 @@ static void find_reachable_operations_recursive(Tags &reachable, NodeOperation * } /* associated write-buffer operations are executed as well */ - if (op->isReadBufferOperation()) { + if (op->get_flags().is_read_buffer_operation) { ReadBufferOperation *read_op = (ReadBufferOperation *)op; MemoryProxy *memproxy = read_op->getMemoryProxy(); find_reachable_operations_recursive(reachable, memproxy->getWriteBufferOperation()); @@ -589,7 +595,7 @@ void NodeOperationBuilder::prune_operations() } /* delete unreachable operations */ - blender::Vector<NodeOperation *> reachable_ops; + Vector<NodeOperation *> reachable_ops; for (NodeOperation *op : m_operations) { if (reachable.find(op) != reachable.end()) { reachable_ops.append(op); @@ -603,7 +609,7 @@ void NodeOperationBuilder::prune_operations() } /* topological (depth-first) sorting of operations */ -static void sort_operations_recursive(blender::Vector<NodeOperation *> &sorted, +static void sort_operations_recursive(Vector<NodeOperation *> &sorted, Tags &visited, NodeOperation *op) { @@ -624,7 +630,7 @@ static void sort_operations_recursive(blender::Vector<NodeOperation *> &sorted, void NodeOperationBuilder::sort_operations() { - blender::Vector<NodeOperation *> sorted; + Vector<NodeOperation *> sorted; sorted.reserve(m_operations.size()); Tags visited; @@ -657,7 +663,7 @@ static void add_group_operations_recursive(Tags &visited, NodeOperation *op, Exe ExecutionGroup *NodeOperationBuilder::make_group(NodeOperation *op) { - ExecutionGroup *group = new ExecutionGroup(); + ExecutionGroup *group = new ExecutionGroup(this->m_groups.size()); m_groups.append(group); Tags visited; @@ -675,7 +681,7 @@ void NodeOperationBuilder::group_operations() } /* add new groups for associated memory proxies where needed */ - if (op->isReadBufferOperation()) { + if (op->get_flags().is_read_buffer_operation) { ReadBufferOperation *read_op = (ReadBufferOperation *)op; MemoryProxy *memproxy = read_op->getMemoryProxy(); @@ -686,3 +692,42 @@ void NodeOperationBuilder::group_operations() } } } + +/** Create a graphviz representation of the NodeOperationBuilder. */ +std::ostream &operator<<(std::ostream &os, const NodeOperationBuilder &builder) +{ + os << "# Builder start\n"; + os << "digraph G {\n"; + os << " rankdir=LR;\n"; + os << " node [shape=box];\n"; + for (const NodeOperation *operation : builder.get_operations()) { + os << " op" << operation->get_id() << " [label=\"" << *operation << "\"];\n"; + } + + os << "\n"; + for (const NodeOperationBuilder::Link &link : builder.get_links()) { + os << " op" << link.from()->getOperation().get_id() << " -> op" + << link.to()->getOperation().get_id() << ";\n"; + } + for (const NodeOperation *operation : builder.get_operations()) { + if (operation->get_flags().is_read_buffer_operation) { + const ReadBufferOperation &read_operation = static_cast<const ReadBufferOperation &>( + *operation); + const WriteBufferOperation &write_operation = + *read_operation.getMemoryProxy()->getWriteBufferOperation(); + os << " op" << write_operation.get_id() << " -> op" << read_operation.get_id() << ";\n"; + } + } + + os << "}\n"; + os << "# Builder end\n"; + return os; +} + +std::ostream &operator<<(std::ostream &os, const NodeOperationBuilder::Link &link) +{ + os << link.from()->getOperation().get_id() << " -> " << link.to()->getOperation().get_id(); + return os; +} + +} // namespace blender::compositor diff --git a/source/blender/compositor/intern/COM_NodeOperationBuilder.h b/source/blender/compositor/intern/COM_NodeOperationBuilder.h index 8f55574fa88..b2fb822af25 100644 --- a/source/blender/compositor/intern/COM_NodeOperationBuilder.h +++ b/source/blender/compositor/intern/COM_NodeOperationBuilder.h @@ -24,6 +24,8 @@ #include "COM_NodeGraph.h" +namespace blender::compositor { + class CompositorContext; class Node; @@ -66,14 +68,14 @@ class NodeOperationBuilder { const CompositorContext *m_context; NodeGraph m_graph; - blender::Vector<NodeOperation *> m_operations; - blender::Vector<Link> m_links; - blender::Vector<ExecutionGroup *> m_groups; + Vector<NodeOperation *> m_operations; + Vector<Link> m_links; + Vector<ExecutionGroup *> m_groups; /** Maps operation inputs to node inputs */ - blender::Map<NodeOperationInput *, NodeInput *> m_input_map; + Map<NodeOperationInput *, NodeInput *> m_input_map; /** Maps node outputs to operation outputs */ - blender::Map<NodeOutput *, NodeOperationOutput *> m_output_map; + Map<NodeOutput *, NodeOperationOutput *> m_output_map; Node *m_current_node; @@ -85,7 +87,6 @@ class NodeOperationBuilder { public: NodeOperationBuilder(const CompositorContext *context, bNodeTree *b_nodetree); - ~NodeOperationBuilder(); const CompositorContext &context() const { @@ -117,6 +118,16 @@ class NodeOperationBuilder { return m_active_viewer; } + const Vector<NodeOperation *> &get_operations() const + { + return m_operations; + } + + const Vector<Link> &get_links() const + { + return m_links; + } + protected: /** Add datatype conversion where needed */ void add_datatype_conversions(); @@ -132,7 +143,7 @@ class NodeOperationBuilder { void determineResolutions(); /** Helper function to store connected inputs for replacement */ - blender::Vector<NodeOperationInput *> cache_output_links(NodeOperationOutput *output) const; + Vector<NodeOperationInput *> cache_output_links(NodeOperationOutput *output) const; /** Find a connected write buffer operation to an OpOutput */ WriteBufferOperation *find_attached_write_buffer_operation(NodeOperationOutput *output) const; /** Add read/write buffer operations around complex operations */ @@ -157,3 +168,8 @@ class NodeOperationBuilder { MEM_CXX_CLASS_ALLOC_FUNCS("COM:NodeCompilerImpl") #endif }; + +std::ostream &operator<<(std::ostream &os, const NodeOperationBuilder &builder); +std::ostream &operator<<(std::ostream &os, const NodeOperationBuilder::Link &link); + +} // namespace blender::compositor diff --git a/source/blender/compositor/intern/COM_OpenCLDevice.cc b/source/blender/compositor/intern/COM_OpenCLDevice.cc index 4ac6bd50380..0f6ed0dbd2b 100644 --- a/source/blender/compositor/intern/COM_OpenCLDevice.cc +++ b/source/blender/compositor/intern/COM_OpenCLDevice.cc @@ -19,6 +19,8 @@ #include "COM_OpenCLDevice.h" #include "COM_WorkScheduler.h" +namespace blender::compositor { + enum COM_VendorID { NVIDIA = 0x10DE, AMD = 0x1002 }; const cl_image_format IMAGE_FORMAT_COLOR = { CL_RGBA, @@ -48,6 +50,16 @@ OpenCLDevice::OpenCLDevice(cl_context context, this->m_queue = clCreateCommandQueue(this->m_context, this->m_device, 0, &error); } +OpenCLDevice::OpenCLDevice(OpenCLDevice &&other) noexcept + : m_context(other.m_context), + m_device(other.m_device), + m_program(other.m_program), + m_queue(other.m_queue), + m_vendorID(other.m_vendorID) +{ + other.m_queue = nullptr; +} + OpenCLDevice::~OpenCLDevice() { if (this->m_queue) { @@ -55,18 +67,16 @@ OpenCLDevice::~OpenCLDevice() } } -void OpenCLDevice::execute(WorkPackage *work) +void OpenCLDevice::execute(WorkPackage *work_package) { - const unsigned int chunkNumber = work->chunk_number; - ExecutionGroup *executionGroup = work->execution_group; - rcti rect; + const unsigned int chunkNumber = work_package->chunk_number; + ExecutionGroup *executionGroup = work_package->execution_group; - executionGroup->determineChunkRect(&rect, chunkNumber); MemoryBuffer **inputBuffers = executionGroup->getInputBuffersOpenCL(chunkNumber); - MemoryBuffer *outputBuffer = executionGroup->allocateOutputBuffer(rect); + MemoryBuffer *outputBuffer = executionGroup->allocateOutputBuffer(work_package->rect); executionGroup->getOutputOperation()->executeOpenCLRegion( - this, &rect, chunkNumber, inputBuffers, outputBuffer); + this, &work_package->rect, chunkNumber, inputBuffers, outputBuffer); delete outputBuffer; @@ -270,3 +280,5 @@ cl_kernel OpenCLDevice::COM_clCreateKernel(const char *kernelname, } return kernel; } + +} // namespace blender::compositor diff --git a/source/blender/compositor/intern/COM_OpenCLDevice.h b/source/blender/compositor/intern/COM_OpenCLDevice.h index 30d9a59d182..826b0457a49 100644 --- a/source/blender/compositor/intern/COM_OpenCLDevice.h +++ b/source/blender/compositor/intern/COM_OpenCLDevice.h @@ -25,6 +25,8 @@ class OpenCLDevice; #include "COM_WorkScheduler.h" #include "clew.h" +namespace blender::compositor { + /** * \brief device representing an GPU OpenCL device. * an instance of this class represents a single cl_device @@ -65,6 +67,9 @@ class OpenCLDevice : public Device { * \param vendorID: */ OpenCLDevice(cl_context context, cl_device_id device, cl_program program, cl_int vendorId); + + OpenCLDevice(OpenCLDevice &&other) noexcept; + ~OpenCLDevice(); /** @@ -117,3 +122,5 @@ class OpenCLDevice : public Device { NodeOperation *operation); cl_kernel COM_clCreateKernel(const char *kernelname, std::list<cl_kernel> *clKernelsToCleanUp); }; + +} // namespace blender::compositor diff --git a/source/blender/compositor/intern/COM_SharedOperationBuffers.cc b/source/blender/compositor/intern/COM_SharedOperationBuffers.cc new file mode 100644 index 00000000000..7e0486b0f54 --- /dev/null +++ b/source/blender/compositor/intern/COM_SharedOperationBuffers.cc @@ -0,0 +1,129 @@ +/* + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * Copyright 2021, Blender Foundation. + */ + +#include "COM_SharedOperationBuffers.h" +#include "BLI_rect.h" +#include "COM_NodeOperation.h" + +namespace blender::compositor { + +SharedOperationBuffers::BufferData::BufferData() + : buffer(nullptr), registered_reads(0), received_reads(0), is_rendered(false) +{ +} + +SharedOperationBuffers::BufferData &SharedOperationBuffers::get_buffer_data(NodeOperation *op) +{ + return buffers_.lookup_or_add_cb(op, []() { return BufferData(); }); +} + +/** + * Whether given operation area to render is already registered. + * TODO: Possibly refactor to "request_area". Current implementation is incomplete: partial + * overlapping, etc. Leading to more rendering than necessary. + */ +bool SharedOperationBuffers::is_area_registered(NodeOperation *op, const rcti &area_to_render) +{ + BufferData &buf_data = get_buffer_data(op); + for (rcti ®_rect : buf_data.render_areas) { + if (BLI_rcti_inside_rcti(®_rect, &area_to_render)) { + return true; + } + } + return false; +} + +/** + * Registers an operation area to render. + */ +void SharedOperationBuffers::register_area(NodeOperation *op, const rcti &area_to_render) +{ + get_buffer_data(op).render_areas.append(area_to_render); +} + +/** + * Whether given operation has any registered reads (other operation registered it depends on given + * operation). + */ +bool SharedOperationBuffers::has_registered_reads(NodeOperation *op) +{ + return get_buffer_data(op).registered_reads > 0; +} + +/** + * Registers an operation read (other operation depends on given operation). + */ +void SharedOperationBuffers::register_read(NodeOperation *read_op) +{ + get_buffer_data(read_op).registered_reads++; +} + +/** + * Get registered areas given operation needs to render. + */ +blender::Span<rcti> SharedOperationBuffers::get_areas_to_render(NodeOperation *op) +{ + return get_buffer_data(op).render_areas.as_span(); +} + +/** + * Whether this operation buffer has already been rendered. + */ +bool SharedOperationBuffers::is_operation_rendered(NodeOperation *op) +{ + return get_buffer_data(op).is_rendered; +} + +/** + * Stores given operation rendered buffer. + */ +void SharedOperationBuffers::set_rendered_buffer(NodeOperation *op, + std::unique_ptr<MemoryBuffer> buffer) +{ + BufferData &buf_data = get_buffer_data(op); + BLI_assert(buf_data.received_reads == 0); + BLI_assert(buf_data.buffer == nullptr); + buf_data.buffer = std::move(buffer); + buf_data.is_rendered = true; +} + +/** + * Get given operation rendered buffer. + */ +MemoryBuffer *SharedOperationBuffers::get_rendered_buffer(NodeOperation *op) +{ + BLI_assert(is_operation_rendered(op)); + return get_buffer_data(op).buffer.get(); +} + +/** + * Reports an operation has finished reading given operation. If all given operation dependencies + * have finished its buffer will be disposed. + */ +void SharedOperationBuffers::read_finished(NodeOperation *read_op) +{ + BufferData &buf_data = get_buffer_data(read_op); + buf_data.received_reads++; + BLI_assert(buf_data.received_reads > 0 && buf_data.received_reads <= buf_data.registered_reads); + if (buf_data.received_reads == buf_data.registered_reads) { + /* Dispose buffer. */ + buf_data.buffer = nullptr; + } +} + +} // namespace blender::compositor diff --git a/source/blender/compositor/intern/COM_SharedOperationBuffers.h b/source/blender/compositor/intern/COM_SharedOperationBuffers.h new file mode 100644 index 00000000000..f7763cd8ae4 --- /dev/null +++ b/source/blender/compositor/intern/COM_SharedOperationBuffers.h @@ -0,0 +1,71 @@ +/* + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * Copyright 2021, Blender Foundation. + */ + +#pragma once + +#include "BLI_map.hh" +#include "BLI_span.hh" +#include "BLI_vector.hh" +#include "COM_MemoryBuffer.h" +#ifdef WITH_CXX_GUARDEDALLOC +# include "MEM_guardedalloc.h" +#endif +#include <memory> + +namespace blender::compositor { + +/** + * Stores and shares operations rendered buffers including render data. Buffers are + * disposed once all dependent operations have finished reading them. + */ +class SharedOperationBuffers { + private: + typedef struct BufferData { + public: + BufferData(); + std::unique_ptr<MemoryBuffer> buffer; + blender::Vector<rcti> render_areas; + int registered_reads; + int received_reads; + bool is_rendered; + } BufferData; + blender::Map<NodeOperation *, BufferData> buffers_; + + public: + bool is_area_registered(NodeOperation *op, const rcti &area_to_render); + void register_area(NodeOperation *op, const rcti &area_to_render); + + bool has_registered_reads(NodeOperation *op); + void register_read(NodeOperation *read_op); + + blender::Span<rcti> get_areas_to_render(NodeOperation *op); + bool is_operation_rendered(NodeOperation *op); + void set_rendered_buffer(NodeOperation *op, std::unique_ptr<MemoryBuffer> buffer); + MemoryBuffer *get_rendered_buffer(NodeOperation *op); + + void read_finished(NodeOperation *read_op); + + private: + BufferData &get_buffer_data(NodeOperation *op); + +#ifdef WITH_CXX_GUARDEDALLOC + MEM_CXX_CLASS_ALLOC_FUNCS("COM:SharedOperationBuffers") +#endif +}; + +} // namespace blender::compositor diff --git a/source/blender/compositor/intern/COM_SingleThreadedOperation.cc b/source/blender/compositor/intern/COM_SingleThreadedOperation.cc index 5febf3802de..01be6e1afed 100644 --- a/source/blender/compositor/intern/COM_SingleThreadedOperation.cc +++ b/source/blender/compositor/intern/COM_SingleThreadedOperation.cc @@ -18,10 +18,13 @@ #include "COM_SingleThreadedOperation.h" +namespace blender::compositor { + SingleThreadedOperation::SingleThreadedOperation() { this->m_cachedInstance = nullptr; - setComplex(true); + flags.complex = true; + flags.single_threaded = true; } void SingleThreadedOperation::initExecution() @@ -56,3 +59,5 @@ void *SingleThreadedOperation::initializeTileData(rcti *rect) unlockMutex(); return this->m_cachedInstance; } + +} // namespace blender::compositor diff --git a/source/blender/compositor/intern/COM_SingleThreadedOperation.h b/source/blender/compositor/intern/COM_SingleThreadedOperation.h index 82326b57b57..9945f938ff9 100644 --- a/source/blender/compositor/intern/COM_SingleThreadedOperation.h +++ b/source/blender/compositor/intern/COM_SingleThreadedOperation.h @@ -20,6 +20,8 @@ #include "COM_NodeOperation.h" +namespace blender::compositor { + class SingleThreadedOperation : public NodeOperation { private: MemoryBuffer *m_cachedInstance; @@ -51,9 +53,6 @@ class SingleThreadedOperation : public NodeOperation { void *initializeTileData(rcti *rect) override; virtual MemoryBuffer *createMemoryBuffer(rcti *rect) = 0; - - int isSingleThreaded() override - { - return true; - } }; + +} // namespace blender::compositor diff --git a/source/blender/compositor/intern/COM_SocketReader.h b/source/blender/compositor/intern/COM_SocketReader.h deleted file mode 100644 index 7c4132efe60..00000000000 --- a/source/blender/compositor/intern/COM_SocketReader.h +++ /dev/null @@ -1,153 +0,0 @@ -/* - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU General Public License - * as published by the Free Software Foundation; either version 2 - * of the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software Foundation, - * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - * - * Copyright 2011, Blender Foundation. - */ - -#pragma once - -#include "BLI_rect.h" -#include "COM_MetaData.h" -#include "COM_defines.h" - -#include <memory> -#include <optional> - -#ifdef WITH_CXX_GUARDEDALLOC -# include "MEM_guardedalloc.h" -#endif - -typedef enum PixelSampler { - COM_PS_NEAREST = 0, - COM_PS_BILINEAR = 1, - COM_PS_BICUBIC = 2, -} PixelSampler; - -class MemoryBuffer; - -/** - * \brief Helper class for reading socket data. - * Only use this class for dispatching (un-ary and n-ary) executions. - * \ingroup Execution - */ -class SocketReader { - private: - protected: - /** - * \brief Holds the width of the output of this operation. - */ - unsigned int m_width; - - /** - * \brief Holds the height of the output of this operation. - */ - unsigned int m_height; - - /** - * \brief calculate a single pixel - * \note this method is called for non-complex - * \param result: is a float[4] array to store the result - * \param x: the x-coordinate of the pixel to calculate in image space - * \param y: the y-coordinate of the pixel to calculate in image space - * \param inputBuffers: chunks that can be read by their ReadBufferOperation. - */ - virtual void executePixelSampled(float /*output*/[4], - float /*x*/, - float /*y*/, - PixelSampler /*sampler*/) - { - } - - /** - * \brief calculate a single pixel - * \note this method is called for complex - * \param result: is a float[4] array to store the result - * \param x: the x-coordinate of the pixel to calculate in image space - * \param y: the y-coordinate of the pixel to calculate in image space - * \param inputBuffers: chunks that can be read by their ReadBufferOperation. - * \param chunkData: chunk specific data a during execution time. - */ - virtual void executePixel(float output[4], int x, int y, void * /*chunkData*/) - { - executePixelSampled(output, x, y, COM_PS_NEAREST); - } - - /** - * \brief calculate a single pixel using an EWA filter - * \note this method is called for complex - * \param result: is a float[4] array to store the result - * \param x: the x-coordinate of the pixel to calculate in image space - * \param y: the y-coordinate of the pixel to calculate in image space - * \param dx: - * \param dy: - * \param inputBuffers: chunks that can be read by their ReadBufferOperation. - */ - virtual void executePixelFiltered( - float /*output*/[4], float /*x*/, float /*y*/, float /*dx*/[2], float /*dy*/[2]) - { - } - - public: - inline void readSampled(float result[4], float x, float y, PixelSampler sampler) - { - executePixelSampled(result, x, y, sampler); - } - inline void read(float result[4], int x, int y, void *chunkData) - { - executePixel(result, x, y, chunkData); - } - inline void readFiltered(float result[4], float x, float y, float dx[2], float dy[2]) - { - executePixelFiltered(result, x, y, dx, dy); - } - - virtual void *initializeTileData(rcti * /*rect*/) - { - return 0; - } - virtual void deinitializeTileData(rcti * /*rect*/, void * /*data*/) - { - } - - virtual ~SocketReader() - { - } - - virtual MemoryBuffer *getInputMemoryBuffer(MemoryBuffer ** /*memoryBuffers*/) - { - return 0; - } - - inline unsigned int getWidth() const - { - return this->m_width; - } - inline unsigned int getHeight() const - { - return this->m_height; - } - - /* Return the meta data associated with this branch. - * - * The return parameter holds an instance or is an nullptr. */ - virtual std::unique_ptr<MetaData> getMetaData() const - { - return std::unique_ptr<MetaData>(); - } - -#ifdef WITH_CXX_GUARDEDALLOC - MEM_CXX_CLASS_ALLOC_FUNCS("COM:SocketReader") -#endif -}; diff --git a/source/blender/compositor/intern/COM_TiledExecutionModel.cc b/source/blender/compositor/intern/COM_TiledExecutionModel.cc new file mode 100644 index 00000000000..d025ce53330 --- /dev/null +++ b/source/blender/compositor/intern/COM_TiledExecutionModel.cc @@ -0,0 +1,158 @@ +/* + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * Copyright 2021, Blender Foundation. + */ + +#include "COM_TiledExecutionModel.h" +#include "COM_Debug.h" +#include "COM_ExecutionGroup.h" +#include "COM_ReadBufferOperation.h" +#include "COM_WorkScheduler.h" + +#include "BLT_translation.h" + +#ifdef WITH_CXX_GUARDEDALLOC +# include "MEM_guardedalloc.h" +#endif + +namespace blender::compositor { + +TiledExecutionModel::TiledExecutionModel(CompositorContext &context, + Span<NodeOperation *> operations, + Span<ExecutionGroup *> groups) + : ExecutionModel(context, operations), groups_(groups) +{ + const bNodeTree *node_tree = context.getbNodeTree(); + node_tree->stats_draw(node_tree->sdh, TIP_("Compositing | Determining resolution")); + + unsigned int resolution[2]; + for (ExecutionGroup *group : groups_) { + resolution[0] = 0; + resolution[1] = 0; + group->determineResolution(resolution); + + if (border_.use_render_border) { + const rctf *render_border = border_.viewer_border; + group->setRenderBorder( + render_border->xmin, render_border->xmax, render_border->ymin, render_border->ymax); + } + + if (border_.use_viewer_border) { + const rctf *viewer_border = border_.viewer_border; + group->setViewerBorder( + viewer_border->xmin, viewer_border->xmax, viewer_border->ymin, viewer_border->ymax); + } + } +} + +static void update_read_buffer_offset(Span<NodeOperation *> operations) +{ + unsigned int order = 0; + for (NodeOperation *operation : operations) { + if (operation->get_flags().is_read_buffer_operation) { + ReadBufferOperation *readOperation = (ReadBufferOperation *)operation; + readOperation->setOffset(order); + order++; + } + } +} + +static void init_write_operations_for_execution(Span<NodeOperation *> operations, + const bNodeTree *bTree) +{ + for (NodeOperation *operation : operations) { + if (operation->get_flags().is_write_buffer_operation) { + operation->setbNodeTree(bTree); + operation->initExecution(); + } + } +} + +static void link_write_buffers(Span<NodeOperation *> operations) +{ + for (NodeOperation *operation : operations) { + if (operation->get_flags().is_read_buffer_operation) { + ReadBufferOperation *readOperation = static_cast<ReadBufferOperation *>(operation); + readOperation->updateMemoryBuffer(); + } + } +} + +static void init_non_write_operations_for_execution(Span<NodeOperation *> operations, + const bNodeTree *bTree) +{ + for (NodeOperation *operation : operations) { + if (!operation->get_flags().is_write_buffer_operation) { + operation->setbNodeTree(bTree); + operation->initExecution(); + } + } +} + +static void init_execution_groups_for_execution(Span<ExecutionGroup *> groups, + const int chunk_size) +{ + for (ExecutionGroup *execution_group : groups) { + execution_group->setChunksize(chunk_size); + execution_group->initExecution(); + } +} + +void TiledExecutionModel::execute(ExecutionSystem &exec_system) +{ + const bNodeTree *editingtree = this->context_.getbNodeTree(); + + editingtree->stats_draw(editingtree->sdh, TIP_("Compositing | Initializing execution")); + + update_read_buffer_offset(operations_); + + init_write_operations_for_execution(operations_, context_.getbNodeTree()); + link_write_buffers(operations_); + init_non_write_operations_for_execution(operations_, context_.getbNodeTree()); + init_execution_groups_for_execution(groups_, context_.getChunksize()); + + WorkScheduler::start(context_); + execute_groups(eCompositorPriority::High, exec_system); + if (!context_.isFastCalculation()) { + execute_groups(eCompositorPriority::Medium, exec_system); + execute_groups(eCompositorPriority::Low, exec_system); + } + WorkScheduler::finish(); + WorkScheduler::stop(); + + editingtree->stats_draw(editingtree->sdh, TIP_("Compositing | De-initializing execution")); + + for (NodeOperation *operation : operations_) { + operation->deinitExecution(); + } + + for (ExecutionGroup *execution_group : groups_) { + execution_group->deinitExecution(); + } +} + +void TiledExecutionModel::execute_groups(eCompositorPriority priority, + ExecutionSystem &exec_system) +{ + for (ExecutionGroup *execution_group : groups_) { + if (execution_group->get_flags().is_output && + execution_group->getRenderPriority() == priority) { + execution_group->execute(&exec_system); + } + } +} + +} // namespace blender::compositor diff --git a/source/blender/compositor/intern/COM_TiledExecutionModel.h b/source/blender/compositor/intern/COM_TiledExecutionModel.h new file mode 100644 index 00000000000..05a795b9f07 --- /dev/null +++ b/source/blender/compositor/intern/COM_TiledExecutionModel.h @@ -0,0 +1,54 @@ +/* + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * Copyright 2021, Blender Foundation. + */ + +#pragma once + +#include "COM_ExecutionModel.h" + +#ifdef WITH_CXX_GUARDEDALLOC +# include "MEM_guardedalloc.h" +#endif + +namespace blender::compositor { + +class ExecutionGroup; + +/** + * Operations are executed from outputs to inputs grouped in execution groups and rendered in + * tiles. + */ +class TiledExecutionModel : public ExecutionModel { + private: + Span<ExecutionGroup *> groups_; + + public: + TiledExecutionModel(CompositorContext &context, + Span<NodeOperation *> operations, + Span<ExecutionGroup *> groups); + + void execute(ExecutionSystem &exec_system) override; + + private: + void execute_groups(eCompositorPriority priority, ExecutionSystem &exec_system); + +#ifdef WITH_CXX_GUARDEDALLOC + MEM_CXX_CLASS_ALLOC_FUNCS("COM:TiledExecutionModel") +#endif +}; + +} // namespace blender::compositor diff --git a/source/blender/compositor/intern/COM_WorkPackage.cc b/source/blender/compositor/intern/COM_WorkPackage.cc index 60684f2c45c..ea78c0d6333 100644 --- a/source/blender/compositor/intern/COM_WorkPackage.cc +++ b/source/blender/compositor/intern/COM_WorkPackage.cc @@ -18,8 +18,20 @@ #include "COM_WorkPackage.h" -WorkPackage::WorkPackage(ExecutionGroup *execution_group, unsigned int chunk_number) +#include "COM_Enums.h" +#include "COM_ExecutionGroup.h" + +namespace blender::compositor { + +std::ostream &operator<<(std::ostream &os, const WorkPackage &work_package) { - this->execution_group = execution_group; - this->chunk_number = chunk_number; + os << "WorkPackage(execution_group=" << *work_package.execution_group; + os << ",chunk=" << work_package.chunk_number; + os << ",state=" << work_package.state; + os << ",rect=(" << work_package.rect.xmin << "," << work_package.rect.ymin << ")-(" + << work_package.rect.xmax << "," << work_package.rect.ymax << ")"; + os << ")"; + return os; } + +} // namespace blender::compositor diff --git a/source/blender/compositor/intern/COM_WorkPackage.h b/source/blender/compositor/intern/COM_WorkPackage.h index db5eb3d3072..f0f53f300a5 100644 --- a/source/blender/compositor/intern/COM_WorkPackage.h +++ b/source/blender/compositor/intern/COM_WorkPackage.h @@ -18,14 +18,30 @@ #pragma once +#ifdef WITH_CXX_GUARDEDALLOC +# include "MEM_guardedalloc.h" +#endif + +#include "COM_Enums.h" + +#include "BLI_rect.h" + +#include <functional> +#include <ostream> + +namespace blender::compositor { +// Forward Declarations. class ExecutionGroup; -#include "COM_ExecutionGroup.h" /** * \brief contains data about work that can be scheduled * \see WorkScheduler */ struct WorkPackage { + eWorkPackageType type; + + eWorkPackageState state = eWorkPackageState::NotScheduled; + /** * \brief executionGroup with the operations-setup to be evaluated */ @@ -37,13 +53,25 @@ struct WorkPackage { unsigned int chunk_number; /** - * constructor - * \param group: the ExecutionGroup - * \param chunk_number: the number of the chunk + * Area of the execution group that the work package calculates. */ - WorkPackage(ExecutionGroup *group, unsigned int chunk_number); + rcti rect; + + /** + * Custom function to execute when work package type is CustomFunction. + */ + std::function<void()> execute_fn; + + /** + * Called when work execution is finished. + */ + std::function<void()> executed_fn; #ifdef WITH_CXX_GUARDEDALLOC MEM_CXX_CLASS_ALLOC_FUNCS("COM:WorkPackage") #endif }; + +std::ostream &operator<<(std::ostream &os, const WorkPackage &work_package); + +} // namespace blender::compositor diff --git a/source/blender/compositor/intern/COM_WorkScheduler.cc b/source/blender/compositor/intern/COM_WorkScheduler.cc index 7d9dd502762..cd0139fd18e 100644 --- a/source/blender/compositor/intern/COM_WorkScheduler.cc +++ b/source/blender/compositor/intern/COM_WorkScheduler.cc @@ -37,6 +37,8 @@ #include "BKE_global.h" +namespace blender::compositor { + enum class ThreadingModel { /** Everything is executed in the caller thread. easy for debugging. */ SingleThreaded, @@ -70,7 +72,7 @@ static struct { /** \brief list of all CPUDevices. for every hardware thread an instance of CPUDevice is * created */ - blender::Vector<CPUDevice> devices; + Vector<CPUDevice> devices; /** \brief list of all thread for every CPUDevice in cpudevices a thread exists. */ ListBase threads; @@ -89,13 +91,15 @@ static struct { cl_program program; /** \brief list of all OpenCLDevices. for every OpenCL GPU device an instance of OpenCLDevice * is created. */ - blender::Vector<OpenCLDevice> devices; + Vector<OpenCLDevice> devices; /** \brief list of all thread for every GPUDevice in cpudevices a thread exists. */ ListBase threads; /** \brief all scheduled work for the GPU. */ bool active = false; bool initialized = false; } opencl; + + int num_cpu_threads; } g_work_scheduler; /* -------------------------------------------------------------------- */ @@ -117,7 +121,6 @@ static void *thread_execute_gpu(void *data) while ((work = (WorkPackage *)BLI_thread_queue_pop(g_work_scheduler.opencl.queue))) { device->execute(work); - delete work; } return nullptr; @@ -142,7 +145,8 @@ static void opencl_start(CompositorContext &context) static bool opencl_schedule(WorkPackage *package) { - if (package->execution_group->isOpenCL() && g_work_scheduler.opencl.active) { + if (package->type == eWorkPackageType::Tile && package->execution_group->get_flags().open_cl && + g_work_scheduler.opencl.active) { BLI_thread_queue_push(g_work_scheduler.opencl.queue, package); return true; } @@ -262,10 +266,10 @@ static void opencl_initialize(const bool use_opencl) if (error2 != CL_SUCCESS) { printf("CLERROR[%d]: %s\n", error2, clewErrorString(error2)); } - g_work_scheduler.opencl.devices.append(OpenCLDevice(g_work_scheduler.opencl.context, - device, - g_work_scheduler.opencl.program, - vendorID)); + g_work_scheduler.opencl.devices.append_as(g_work_scheduler.opencl.context, + device, + g_work_scheduler.opencl.program, + vendorID); } } MEM_freeN(cldevices); @@ -304,7 +308,6 @@ static void threading_model_single_thread_execute(WorkPackage *package) { CPUDevice device(0); device.execute(package); - delete package; } /* \} */ @@ -320,7 +323,6 @@ static void *threading_model_queue_execute(void *data) BLI_thread_local_set(g_thread_device, device); while ((work = (WorkPackage *)BLI_thread_queue_pop(g_work_scheduler.queue.queue))) { device->execute(work); - delete work; } return nullptr; @@ -369,7 +371,7 @@ static void threading_model_queue_initialize(const int num_cpu_threads) /* Initialize CPU threads. */ if (!g_work_scheduler.queue.initialized) { for (int index = 0; index < num_cpu_threads; index++) { - g_work_scheduler.queue.devices.append(CPUDevice(index)); + g_work_scheduler.queue.devices.append_as(index); } BLI_thread_local_create(g_thread_device); g_work_scheduler.queue.initialized = true; @@ -398,7 +400,6 @@ static void threading_model_task_execute(TaskPool *__restrict UNUSED(pool), void CPUDevice device(BLI_task_parallel_thread_id(nullptr)); BLI_thread_local_set(g_thread_device, &device); device.execute(package); - delete package; } static void threading_model_task_schedule(WorkPackage *package) @@ -410,7 +411,8 @@ static void threading_model_task_schedule(WorkPackage *package) static void threading_model_task_start() { BLI_thread_local_create(g_thread_device); - g_work_scheduler.task.pool = BLI_task_pool_create(nullptr, TASK_PRIORITY_HIGH); + g_work_scheduler.task.pool = BLI_task_pool_create( + nullptr, TASK_PRIORITY_HIGH, TASK_ISOLATION_ON); } static void threading_model_task_finish() @@ -431,10 +433,8 @@ static void threading_model_task_stop() /** \name Public API * \{ */ -void WorkScheduler::schedule(ExecutionGroup *group, int chunkNumber) +void WorkScheduler::schedule(WorkPackage *package) { - WorkPackage *package = new WorkPackage(group, chunkNumber); - if (COM_is_opencl_enabled()) { if (opencl_schedule(package)) { return; @@ -536,11 +536,12 @@ void WorkScheduler::initialize(bool use_opencl, int num_cpu_threads) opencl_initialize(use_opencl); } + g_work_scheduler.num_cpu_threads = num_cpu_threads; switch (COM_threading_model()) { case ThreadingModel::SingleThreaded: + g_work_scheduler.num_cpu_threads = 1; /* Nothing to do. */ break; - case ThreadingModel::Queue: threading_model_queue_initialize(num_cpu_threads); break; @@ -572,6 +573,11 @@ void WorkScheduler::deinitialize() } } +int WorkScheduler::get_num_cpu_threads() +{ + return g_work_scheduler.num_cpu_threads; +} + int WorkScheduler::current_thread_id() { if (COM_threading_model() == ThreadingModel::SingleThreaded) { @@ -583,3 +589,5 @@ int WorkScheduler::current_thread_id() } /* \} */ + +} // namespace blender::compositor diff --git a/source/blender/compositor/intern/COM_WorkScheduler.h b/source/blender/compositor/intern/COM_WorkScheduler.h index 6b53cc3efd6..be88859be7c 100644 --- a/source/blender/compositor/intern/COM_WorkScheduler.h +++ b/source/blender/compositor/intern/COM_WorkScheduler.h @@ -24,6 +24,8 @@ #include "COM_WorkPackage.h" #include "COM_defines.h" +namespace blender::compositor { + /** \brief the workscheduler * \ingroup execution */ @@ -31,13 +33,11 @@ struct WorkScheduler { /** * \brief schedule a chunk of a group to be calculated. * An execution group schedules a chunk in the WorkScheduler - * when ExecutionGroup.isOpenCL is set the work will be handled by a OpenCLDevice + * when ExecutionGroup.get_flags().open_cl is set the work will be handled by a OpenCLDevice * otherwise the work is scheduled for an CPUDevice * \see ExecutionGroup.execute - * \param group: the execution group - * \param chunkNumber: the number of the chunk in the group to be executed */ - static void schedule(ExecutionGroup *group, int chunkNumber); + static void schedule(WorkPackage *package); /** * \brief initialize the WorkScheduler @@ -87,9 +87,13 @@ struct WorkScheduler { */ static bool has_gpu_devices(); + static int get_num_cpu_threads(); + static int current_thread_id(); #ifdef WITH_CXX_GUARDEDALLOC MEM_CXX_CLASS_ALLOC_FUNCS("COM:WorkScheduler") #endif }; + +} // namespace blender::compositor diff --git a/source/blender/compositor/intern/COM_compositor.cc b/source/blender/compositor/intern/COM_compositor.cc index 68e4f80f91f..5839f53976b 100644 --- a/source/blender/compositor/intern/COM_compositor.cc +++ b/source/blender/compositor/intern/COM_compositor.cc @@ -46,12 +46,12 @@ static void compositor_init_node_previews(const RenderData *render_data, bNodeTr 1.0f; int preview_width, preview_height; if (aspect < 1.0f) { - preview_width = COM_PREVIEW_SIZE; - preview_height = (int)(COM_PREVIEW_SIZE * aspect); + preview_width = blender::compositor::COM_PREVIEW_SIZE; + preview_height = (int)(blender::compositor::COM_PREVIEW_SIZE * aspect); } else { - preview_width = (int)(COM_PREVIEW_SIZE / aspect); - preview_height = COM_PREVIEW_SIZE; + preview_width = (int)(blender::compositor::COM_PREVIEW_SIZE / aspect); + preview_height = blender::compositor::COM_PREVIEW_SIZE; } BKE_node_preview_init_tree(node_tree, preview_width, preview_height, false); } @@ -92,12 +92,12 @@ void COM_execute(RenderData *render_data, /* Initialize workscheduler. */ const bool use_opencl = (node_tree->flag & NTREE_COM_OPENCL) != 0; - WorkScheduler::initialize(use_opencl, BKE_render_num_threads(render_data)); + blender::compositor::WorkScheduler::initialize(use_opencl, BKE_render_num_threads(render_data)); /* Execute. */ const bool twopass = (node_tree->flag & NTREE_TWO_PASS) && !rendering; if (twopass) { - ExecutionSystem fast_pass( + blender::compositor::ExecutionSystem fast_pass( render_data, scene, node_tree, rendering, true, viewSettings, displaySettings, viewName); fast_pass.execute(); @@ -107,7 +107,7 @@ void COM_execute(RenderData *render_data, } } - ExecutionSystem system( + blender::compositor::ExecutionSystem system( render_data, scene, node_tree, rendering, false, viewSettings, displaySettings, viewName); system.execute(); @@ -118,7 +118,7 @@ void COM_deinitialize() { if (g_compositor.is_initialized) { BLI_mutex_lock(&g_compositor.mutex); - WorkScheduler::deinitialize(); + blender::compositor::WorkScheduler::deinitialize(); g_compositor.is_initialized = false; BLI_mutex_unlock(&g_compositor.mutex); BLI_mutex_end(&g_compositor.mutex); |