diff options
author | Manuel Castilla <manzanillawork@gmail.com> | 2021-07-22 22:55:42 +0300 |
---|---|---|
committer | Manuel Castilla <manzanillawork@gmail.com> | 2021-07-22 22:55:42 +0300 |
commit | 585a891ab2e73c45a7225fa854cf92c559c897fb (patch) | |
tree | 07d930f19e5eafc759ed693eb3d6fa506b3a3bdc /source/blender/compositor/intern | |
parent | 89b5731f19cc4c5ac15950c3c2ed8c7440c3c80a (diff) | |
parent | 1a91c5732032621ff58677178a93f5e6a9d2b8f8 (diff) |
Merge branch 'master' into compositor-full-frame
Diffstat (limited to 'source/blender/compositor/intern')
38 files changed, 1645 insertions, 436 deletions
diff --git a/source/blender/compositor/intern/COM_BufferArea.h b/source/blender/compositor/intern/COM_BufferArea.h new file mode 100644 index 00000000000..6f7756ecbfc --- /dev/null +++ b/source/blender/compositor/intern/COM_BufferArea.h @@ -0,0 +1,215 @@ +/* + * 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_assert.h" +#include "BLI_rect.h" +#include <iterator> + +namespace blender::compositor { + +/* Forward declarations. */ +template<typename T> class BufferAreaIterator; + +/** + * A rectangle area of buffer elements. + */ +template<typename T> class BufferArea : rcti { + public: + using Iterator = BufferAreaIterator<T>; + using ConstIterator = BufferAreaIterator<const T>; + + private: + T *buffer_; + /* Number of elements in a buffer row. */ + int buffer_width_; + /* Buffer element stride. */ + int elem_stride_; + + public: + constexpr BufferArea() = default; + + /** + * Create a buffer area containing given rectangle area. + */ + constexpr BufferArea(T *buffer, int buffer_width, const rcti &area, int elem_stride = 1) + : rcti(area), buffer_(buffer), buffer_width_(buffer_width), elem_stride_(elem_stride) + { + } + + /** + * Create a buffer area containing whole buffer with no offsets. + */ + constexpr BufferArea(T *buffer, int buffer_width, int buffer_height, int elem_stride = 1) + : buffer_(buffer), buffer_width_(buffer_width), elem_stride_(elem_stride) + { + BLI_rcti_init(this, 0, buffer_width, 0, buffer_height); + } + + constexpr friend bool operator==(const BufferArea &a, const BufferArea &b) + { + return a.buffer_ == b.buffer_ && BLI_rcti_compare(&a, &b) && a.elem_stride_ == b.elem_stride_; + } + + constexpr const rcti &get_rect() const + { + return *this; + } + + /** + * Number of elements in a row. + */ + constexpr int width() const + { + return BLI_rcti_size_x(this); + } + + /** + * Number of elements in a column. + */ + constexpr int height() const + { + return BLI_rcti_size_y(this); + } + + constexpr Iterator begin() + { + return begin_iterator<Iterator>(); + } + + constexpr Iterator end() + { + return end_iterator<Iterator>(); + } + + constexpr ConstIterator begin() const + { + return begin_iterator<ConstIterator>(); + } + + constexpr ConstIterator end() const + { + return end_iterator<ConstIterator>(); + } + + private: + template<typename TIterator> constexpr TIterator begin_iterator() const + { + T *end_ptr = get_end_ptr(); + if (elem_stride_ == 0) { + /* Iterate a single element. */ + return TIterator(buffer_, end_ptr, 1, 1, 1); + } + + T *begin_ptr = buffer_ + (intptr_t)this->ymin * buffer_width_ * elem_stride_ + + (intptr_t)this->xmin * elem_stride_; + return TIterator(begin_ptr, end_ptr, buffer_width_, BLI_rcti_size_x(this), elem_stride_); + } + + template<typename TIterator> constexpr TIterator end_iterator() const + { + T *end_ptr = get_end_ptr(); + if (elem_stride_ == 0) { + /* Iterate a single element. */ + return TIterator(end_ptr, end_ptr, 1, 1, 1); + } + + return TIterator(end_ptr, end_ptr, buffer_width_, BLI_rcti_size_x(this), elem_stride_); + } + + T *get_end_ptr() const + { + if (elem_stride_ == 0) { + return buffer_ + 1; + } + return buffer_ + (intptr_t)(this->ymax - 1) * buffer_width_ * elem_stride_ + + (intptr_t)this->xmax * elem_stride_; + } +}; + +template<typename T> class BufferAreaIterator { + public: + using iterator_category = std::input_iterator_tag; + using value_type = T *; + using pointer = T *const *; + using reference = T *const &; + using difference_type = std::ptrdiff_t; + + private: + int elem_stride_; + int row_stride_; + /* Stride between a row end and the next row start. */ + int rows_gap_; + T *current_; + const T *row_end_; + const T *end_; + + public: + constexpr BufferAreaIterator() = default; + + constexpr BufferAreaIterator( + T *current, const T *end, int buffer_width, int area_width, int elem_stride = 1) + : elem_stride_(elem_stride), + row_stride_(buffer_width * elem_stride), + rows_gap_(row_stride_ - area_width * elem_stride), + current_(current), + row_end_(current + area_width * elem_stride), + end_(end) + { + } + + constexpr BufferAreaIterator &operator++() + { + current_ += elem_stride_; + BLI_assert(current_ <= row_end_); + if (current_ == row_end_) { + BLI_assert(current_ <= end_); + if (current_ == end_) { + return *this; + } + current_ += rows_gap_; + row_end_ += row_stride_; + } + return *this; + } + + constexpr BufferAreaIterator operator++(int) const + { + BufferAreaIterator copied_iterator = *this; + ++copied_iterator; + return copied_iterator; + } + + constexpr friend bool operator!=(const BufferAreaIterator &a, const BufferAreaIterator &b) + { + return a.current_ != b.current_; + } + + constexpr friend bool operator==(const BufferAreaIterator &a, const BufferAreaIterator &b) + { + return a.current_ == b.current_; + } + + constexpr T *operator*() const + { + return current_; + } +}; + +} // namespace blender::compositor diff --git a/source/blender/compositor/intern/COM_BufferOperation.cc b/source/blender/compositor/intern/COM_BufferOperation.cc index 561a754a500..ae587fa87c4 100644 --- a/source/blender/compositor/intern/COM_BufferOperation.cc +++ b/source/blender/compositor/intern/COM_BufferOperation.cc @@ -23,6 +23,7 @@ namespace blender::compositor { BufferOperation::BufferOperation(MemoryBuffer *buffer, DataType data_type) : ConstantOperation() { buffer_ = buffer; + inflated_buffer_ = nullptr; /* TODO: Implement a MemoryBuffer get_size() method returning a Size2d type. Shorten following * code to: set_resolution(buffer.get_size()) */ unsigned int resolution[2]; @@ -30,11 +31,10 @@ BufferOperation::BufferOperation(MemoryBuffer *buffer, DataType data_type) : Con resolution[1] = buffer->getHeight(); setResolution(resolution); addOutputSocket(data_type); - flags.is_constant_operation = buffer_->is_a_single_elem(); } -float *BufferOperation::get_constant_elem() +const float *BufferOperation::get_constant_elem() { BLI_assert(buffer_->is_a_single_elem()); return buffer_->getBuffer(); @@ -42,7 +42,19 @@ float *BufferOperation::get_constant_elem() void *BufferOperation::initializeTileData(rcti * /*rect*/) { - return buffer_; + if (buffer_->is_a_single_elem() == false) { + return buffer_; + } + + if (!inflated_buffer_) { + inflated_buffer_ = buffer_->inflate(); + } + return inflated_buffer_; +} + +void BufferOperation::deinitExecution() +{ + delete inflated_buffer_; } void BufferOperation::executePixelSampled(float output[4], float x, float y, PixelSampler sampler) diff --git a/source/blender/compositor/intern/COM_BufferOperation.h b/source/blender/compositor/intern/COM_BufferOperation.h index 2fa90b872ae..705264c37b7 100644 --- a/source/blender/compositor/intern/COM_BufferOperation.h +++ b/source/blender/compositor/intern/COM_BufferOperation.h @@ -25,12 +25,14 @@ namespace blender::compositor { class BufferOperation : public ConstantOperation { private: MemoryBuffer *buffer_; + MemoryBuffer *inflated_buffer_; public: BufferOperation(MemoryBuffer *buffer, DataType data_type); - float *get_constant_elem(); + const float *get_constant_elem() override; void *initializeTileData(rcti *rect) override; + void deinitExecution() 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; }; diff --git a/source/blender/compositor/intern/COM_BufferRange.h b/source/blender/compositor/intern/COM_BufferRange.h new file mode 100644 index 00000000000..ffdf1f2f1e5 --- /dev/null +++ b/source/blender/compositor/intern/COM_BufferRange.h @@ -0,0 +1,171 @@ +/* + * 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_assert.h" +#include "BLI_rect.h" + +#include <iterator> + +namespace blender::compositor { + +/* Forward declarations. */ +template<typename T> class BufferRangeIterator; + +/** + * A range of buffer elements. + */ +template<typename T> class BufferRange { + public: + using Iterator = BufferRangeIterator<T>; + using ConstIterator = BufferRangeIterator<const T>; + + private: + T *start_; + /* Number of elements in the range. */ + int64_t size_; + /* Buffer element stride. */ + int elem_stride_; + + public: + constexpr BufferRange() = default; + + /** + * Create a buffer range of elements from a given element index. + */ + constexpr BufferRange(T *buffer, int64_t start_elem_index, int64_t size, int elem_stride = 1) + : start_(buffer + start_elem_index * elem_stride), size_(size), elem_stride_(elem_stride) + { + } + + constexpr friend bool operator==(const BufferRange &a, const BufferRange &b) + { + return a.start_ == b.start_ && a.size_ == b.size_ && a.elem_stride_ == b.elem_stride_; + } + + /** + * Access an element in the range. Index is relative to range start. + */ + constexpr T *operator[](int64_t index) const + { + BLI_assert(index >= 0); + BLI_assert(index < this->size()); + return start_ + index * elem_stride_; + } + + /** + * Get the number of elements in the range. + */ + constexpr int64_t size() const + { + return size_; + } + + constexpr Iterator begin() + { + return begin_iterator<Iterator>(); + } + + constexpr Iterator end() + { + return end_iterator<Iterator>(); + } + + constexpr ConstIterator begin() const + { + return begin_iterator<ConstIterator>(); + } + + constexpr ConstIterator end() const + { + return end_iterator<ConstIterator>(); + } + + private: + template<typename TIterator> constexpr TIterator begin_iterator() const + { + if (elem_stride_ == 0) { + /* Iterate a single element. */ + return TIterator(start_, 1); + } + + return TIterator(start_, elem_stride_); + } + + template<typename TIterator> constexpr TIterator end_iterator() const + { + if (elem_stride_ == 0) { + /* Iterate a single element. */ + return TIterator(start_ + 1, 1); + } + + return TIterator(start_ + size_ * elem_stride_, elem_stride_); + } +}; + +template<typename T> class BufferRangeIterator { + public: + using iterator_category = std::input_iterator_tag; + using value_type = T *; + using pointer = T *const *; + using reference = T *const &; + using difference_type = std::ptrdiff_t; + + private: + T *current_; + int elem_stride_; + + public: + constexpr BufferRangeIterator() = default; + + constexpr BufferRangeIterator(T *current, int elem_stride = 1) + : current_(current), elem_stride_(elem_stride) + { + } + + constexpr BufferRangeIterator &operator++() + { + current_ += elem_stride_; + return *this; + } + + constexpr BufferRangeIterator operator++(int) const + { + BufferRangeIterator copied_iterator = *this; + ++copied_iterator; + return copied_iterator; + } + + constexpr friend bool operator!=(const BufferRangeIterator &a, const BufferRangeIterator &b) + { + return a.current_ != b.current_; + } + + constexpr friend bool operator==(const BufferRangeIterator &a, const BufferRangeIterator &b) + { + return a.current_ == b.current_; + } + + constexpr T *operator*() const + { + return current_; + } +}; + +} // namespace blender::compositor diff --git a/source/blender/compositor/intern/COM_BuffersIterator.h b/source/blender/compositor/intern/COM_BuffersIterator.h new file mode 100644 index 00000000000..bfe0b7a3d45 --- /dev/null +++ b/source/blender/compositor/intern/COM_BuffersIterator.h @@ -0,0 +1,195 @@ +/* + * 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" + +namespace blender::compositor { + +/** + * Builds an iterator for simultaneously iterating an area of elements in an output buffer and any + * number of input buffers. It's not a standard C++ iterator and it does not support neither + * deference, equality or postfix increment operators. + */ +template<typename T> class BuffersIteratorBuilder { + public: + class Iterator { + int x_start_; + int x_end_; + const T *out_end_; + int out_elem_stride_; + /* Stride between an output row end and the next row start. */ + int out_rows_gap_; + + struct In { + int elem_stride; + int rows_gap; + const T *in; + }; + Vector<In, 6> ins_; + + friend class BuffersIteratorBuilder; + + public: + int x; + int y; + /** Current output element. */ + T *out; + + public: + /** + * Get current element from an input. + */ + const T *in(int input_index) const + { + BLI_assert(input_index < ins_.size()); + return ins_[input_index].in; + } + + int get_num_inputs() const + { + return ins_.size(); + } + + /** + * Has the end of the area been reached. + */ + bool is_end() const + { + return out >= out_end_; + } + + /** + * Go to the next element in the area. + */ + void next() + { + out += out_elem_stride_; + for (In &in : ins_) { + in.in += in.elem_stride; + } + x++; + if (x == x_end_) { + x = x_start_; + y++; + out += out_rows_gap_; + for (In &in : ins_) { + in.in += in.rows_gap; + } + } + } + + Iterator &operator++() + { + this->next(); + return *this; + } + }; + + private: + Iterator iterator_; + rcti area_; + bool is_built_; + + public: + /** + * Create a buffers iterator builder to iterate given output buffer area. + * \param output: Output buffer. + * \param buffer_area: Whole output buffer area (may have offset position). + * \param iterated_area: Area to be iterated in all buffers. + * \param elem_stride: Output buffer element stride. + */ + BuffersIteratorBuilder(T *output, + const rcti &buffer_area, + const rcti &iterated_area, + int elem_stride = 1) + : area_(iterated_area), is_built_(false) + { + BLI_assert(BLI_rcti_inside_rcti(&buffer_area, &iterated_area)); + iterator_.x = iterated_area.xmin; + iterator_.y = iterated_area.ymin; + iterator_.x_start_ = iterated_area.xmin; + iterator_.x_end_ = iterated_area.xmax; + + iterator_.out_elem_stride_ = elem_stride; + const int buffer_width = BLI_rcti_size_x(&buffer_area); + intptr_t out_row_stride = buffer_width * elem_stride; + iterator_.out_rows_gap_ = out_row_stride - BLI_rcti_size_x(&iterated_area) * elem_stride; + const int out_start_x = iterated_area.xmin - buffer_area.xmin; + const int out_start_y = iterated_area.ymin - buffer_area.ymin; + iterator_.out = output + (intptr_t)out_start_y * out_row_stride + + (intptr_t)out_start_x * elem_stride; + const T *out_row_end_ = iterator_.out + + (intptr_t)BLI_rcti_size_x(&iterated_area) * elem_stride; + iterator_.out_end_ = out_row_end_ + + (intptr_t)out_row_stride * (BLI_rcti_size_y(&iterated_area) - 1); + } + + /** + * Create a buffers iterator builder to iterate given output buffer with no offsets. + */ + BuffersIteratorBuilder(T *output, int buffer_width, int buffer_height, int elem_stride = 1) + : BuffersIteratorBuilder(output, + {0, buffer_width, 0, buffer_height}, + {0, buffer_width, 0, buffer_height}, + elem_stride) + { + } + + /** + * Add an input buffer to be iterated. It must contain iterated area. + */ + void add_input(const T *input, const rcti &buffer_area, int elem_stride = 1) + { + BLI_assert(!is_built_); + BLI_assert(BLI_rcti_inside_rcti(&buffer_area, &area_)); + typename Iterator::In in; + in.elem_stride = elem_stride; + const int buffer_width = BLI_rcti_size_x(&buffer_area); + in.rows_gap = buffer_width * elem_stride - BLI_rcti_size_x(&area_) * elem_stride; + const int in_start_x = area_.xmin - buffer_area.xmin; + const int in_start_y = area_.ymin - buffer_area.ymin; + in.in = input + in_start_y * buffer_width * elem_stride + in_start_x * elem_stride; + iterator_.ins_.append(std::move(in)); + } + + /** + * Add an input buffer to be iterated with no offsets. It must contain iterated area. + */ + void add_input(const T *input, int buffer_width, int elem_stride = 1) + { + rcti buffer_area; + BLI_rcti_init(&buffer_area, 0, buffer_width, 0, area_.ymax); + add_input(input, buffer_area, elem_stride); + } + + /** + * Build the iterator. + */ + BuffersIteratorBuilder::Iterator build() + { + is_built_ = true; + return iterator_; + } +}; + +template<typename T> using BuffersIterator = typename BuffersIteratorBuilder<T>::Iterator; + +} // namespace blender::compositor diff --git a/source/blender/compositor/intern/COM_CompositorContext.cc b/source/blender/compositor/intern/COM_CompositorContext.cc index 61e299c045e..a93820b66dc 100644 --- a/source/blender/compositor/intern/COM_CompositorContext.cc +++ b/source/blender/compositor/intern/COM_CompositorContext.cc @@ -53,7 +53,7 @@ eExecutionModel CompositorContext::get_execution_model() const case 0: return eExecutionModel::Tiled; default: - BLI_assert(!"Invalid execution mode"); + BLI_assert_msg(0, "Invalid execution mode"); } } return eExecutionModel::Tiled; diff --git a/source/blender/compositor/intern/COM_CompositorContext.h b/source/blender/compositor/intern/COM_CompositorContext.h index 56251511576..403ec62e359 100644 --- a/source/blender/compositor/intern/COM_CompositorContext.h +++ b/source/blender/compositor/intern/COM_CompositorContext.h @@ -171,7 +171,7 @@ class CompositorContext { } /** - * \brief set view settings of color color management + * \brief set view settings of color management */ void setViewSettings(const ColorManagedViewSettings *viewSettings) { @@ -179,7 +179,7 @@ class CompositorContext { } /** - * \brief get view settings of color color management + * \brief get view settings of color management */ const ColorManagedViewSettings *getViewSettings() const { @@ -187,7 +187,7 @@ class CompositorContext { } /** - * \brief set display settings of color color management + * \brief set display settings of color management */ void setDisplaySettings(const ColorManagedDisplaySettings *displaySettings) { @@ -195,7 +195,7 @@ class CompositorContext { } /** - * \brief get display settings of color color management + * \brief get display settings of color management */ const ColorManagedDisplaySettings *getDisplaySettings() const { diff --git a/source/blender/compositor/intern/COM_ConstantFolder.cc b/source/blender/compositor/intern/COM_ConstantFolder.cc index a2b13fc3563..15450572958 100644 --- a/source/blender/compositor/intern/COM_ConstantFolder.cc +++ b/source/blender/compositor/intern/COM_ConstantFolder.cc @@ -13,59 +13,52 @@ * 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_ConstantFolder.h" #include "BLI_rect.h" + +#include "COM_ConstantFolder.h" #include "COM_ConstantOperation.h" #include "COM_SetColorOperation.h" #include "COM_SetValueOperation.h" #include "COM_SetVectorOperation.h" - -#include <limits> +#include "COM_WorkScheduler.h" namespace blender::compositor { +using Link = NodeOperationBuilder::Link; + /** - * \param operations: Container of operations to fold. Folded operations will be replaced - * with constant operations. - * \param output_links_map: Operations output links. Folded operations links will be relinked to - * their constant operation result. + * \param operations_builder: Contains all operations to fold. * \param exec_system: Execution system. */ -ConstantFolder::ConstantFolder(Vector<NodeOperation *> &operations, - Map<NodeOperation *, Set<NodeOperation *>> &output_links, - ExecutionSystem &exec_system) - : all_operations_(operations), output_links_(output_links), exec_system_(exec_system) +ConstantFolder::ConstantFolder(NodeOperationBuilder &operations_builder) + : operations_builder_(operations_builder) { - BLI_rcti_init(&max_area_, - std::numeric_limits<int>::min(), - std::numeric_limits<int>::max(), - std::numeric_limits<int>::min(), - std::numeric_limits<int>::max()); + BLI_rcti_init(&max_area_, INT_MIN, INT_MAX, INT_MIN, INT_MAX); BLI_rcti_init(&first_elem_area_, 0, 1, 0, 1); } -static bool is_constant_foldable(NodeOperation *op) +static bool is_constant_foldable(NodeOperation *operation) { - if (!op->get_flags().can_be_constant || op->get_flags().is_constant_operation) { - return false; - } - for (int i = 0; i < op->getNumberOfInputSockets(); i++) { - if (!op->get_input_operation(i)->get_flags().is_constant_operation) { - return false; + if (operation->get_flags().can_be_constant && !operation->get_flags().is_constant_operation) { + for (int i = 0; i < operation->getNumberOfInputSockets(); i++) { + if (!operation->get_input_operation(i)->get_flags().is_constant_operation) { + return false; + } } + return true; } - return true; + return false; } -static Vector<NodeOperation *> find_constant_foldable_operations(Span<NodeOperation *> operations) +static Set<NodeOperation *> find_constant_foldable_operations(Span<NodeOperation *> operations) { - Vector<NodeOperation *> foldable_ops; + Set<NodeOperation *> foldable_ops; for (NodeOperation *op : operations) { if (is_constant_foldable(op)) { - foldable_ops.append(op); + foldable_ops.add(op); } } return foldable_ops; @@ -90,84 +83,45 @@ static ConstantOperation *create_constant_operation(DataType data_type, const fl return value_op; } default: { - BLI_assert(!"Non implemented data type"); + BLI_assert_msg(0, "Non implemented data type"); return nullptr; } } } -ConstantOperation *ConstantFolder::fold_operation(NodeOperation *op) +ConstantOperation *ConstantFolder::fold_operation(NodeOperation *operation) { - const DataType data_type = op->getOutputSocket()->getDataType(); - MemoryBuffer *fold_buf = create_constant_buffer(data_type); - Vector<MemoryBuffer *> inputs_bufs = get_constant_inputs_buffers(op); - op->render(fold_buf, {first_elem_area_}, inputs_bufs, exec_system_); - - ConstantOperation *constant_op = create_constant_operation(data_type, fold_buf->get_elem(0, 0)); - all_operations_.append(constant_op); - constants_buffers_.add_new(constant_op, fold_buf); - relink_operation_outputs_to_constant(op, constant_op); - remove_folded_operation(op); + const DataType data_type = operation->getOutputSocket()->getDataType(); + MemoryBuffer fold_buf(data_type, first_elem_area_); + Vector<MemoryBuffer *> input_bufs = get_constant_input_buffers(operation); + operation->render(&fold_buf, {first_elem_area_}, input_bufs); + + MemoryBuffer *constant_buf = create_constant_buffer(data_type); + constant_buf->copy_from(&fold_buf, first_elem_area_); + ConstantOperation *constant_op = create_constant_operation(data_type, constant_buf->getBuffer()); + operations_builder_.replace_operation_with_constant(operation, constant_op); + constant_buffers_.add_new(constant_op, constant_buf); return constant_op; } -void ConstantFolder::relink_operation_outputs_to_constant(NodeOperation *from_op, - ConstantOperation *to_op) -{ - if (!output_links_.contains(from_op)) { - return; - } - Set<NodeOperation *> outputs = output_links_.pop(from_op); - for (NodeOperation *out : outputs) { - for (int i = 0; i < out->getNumberOfInputSockets(); i++) { - NodeOperationInput *socket = out->getInputSocket(i); - NodeOperationOutput *link = socket->getLink(); - if (link && &link->getOperation() == from_op) { - socket->setLink(to_op->getOutputSocket()); - /* TODO: As resolutions are determined before constant folding we need to manually set - * constant operations resolutions. Once tiled implementation is removed constant folding - * should be done first and this code can be removed. */ - uint temp[2]; - uint resolution[2] = {out->getWidth(), out->getHeight()}; - to_op->getOutputSocket()->determineResolution(temp, resolution); - } - } - } - output_links_.add_new(to_op, std::move(outputs)); -} - -void ConstantFolder::remove_folded_operation(NodeOperation *op) -{ - output_links_.remove(op); - folded_operations_.add(op); - for (int i = 0; i < op->getNumberOfInputSockets(); i++) { - NodeOperation *input = op->get_input_operation(i); - BLI_assert(output_links_.contains(input)); - Set<NodeOperation *> &input_outputs = output_links_.lookup(input); - input_outputs.remove(op); - if (input_outputs.size() == 0) { - output_links_.remove(input); - folded_operations_.add(input); - } - } -} - -MemoryBuffer *ConstantFolder::create_constant_buffer(DataType data_type) +MemoryBuffer *ConstantFolder::create_constant_buffer(const DataType data_type) { /* Create a single elem buffer with maximum area possible so readers can read any coordinate * returning always same element. */ return new MemoryBuffer(data_type, max_area_, true); } -Vector<MemoryBuffer *> ConstantFolder::get_constant_inputs_buffers(NodeOperation *op) +Vector<MemoryBuffer *> ConstantFolder::get_constant_input_buffers(NodeOperation *operation) { - const int num_inputs = op->getNumberOfInputSockets(); + const int num_inputs = operation->getNumberOfInputSockets(); Vector<MemoryBuffer *> inputs_bufs(num_inputs); for (int i = 0; i < num_inputs; i++) { - ConstantOperation *constant_op = static_cast<ConstantOperation *>(op->get_input_operation(i)); - MemoryBuffer *constant_buf = constants_buffers_.lookup_or_add_cb(constant_op, [=] { + BLI_assert(operation->get_input_operation(i)->get_flags().is_constant_operation); + ConstantOperation *constant_op = static_cast<ConstantOperation *>( + operation->get_input_operation(i)); + MemoryBuffer *constant_buf = constant_buffers_.lookup_or_add_cb(constant_op, [=] { MemoryBuffer *buf = create_constant_buffer(constant_op->getOutputSocket()->getDataType()); - constant_op->render(buf, {first_elem_area_}, {}, exec_system_); + constant_op->render(buf, {first_elem_area_}, {}); return buf; }); inputs_bufs[i] = constant_buf; @@ -175,13 +129,14 @@ Vector<MemoryBuffer *> ConstantFolder::get_constant_inputs_buffers(NodeOperation return inputs_bufs; } -/** Returns constant operations resulted from folding. */ +/** Returns constant operations resulted from folded operations. */ Vector<ConstantOperation *> ConstantFolder::try_fold_operations(Span<NodeOperation *> operations) { - Vector<NodeOperation *> foldable_ops = find_constant_foldable_operations(operations); + Set<NodeOperation *> foldable_ops = find_constant_foldable_operations(operations); if (foldable_ops.size() == 0) { return Vector<ConstantOperation *>(); } + Vector<ConstantOperation *> new_folds; for (NodeOperation *op : foldable_ops) { ConstantOperation *constant_op = fold_operation(op); @@ -191,42 +146,46 @@ Vector<ConstantOperation *> ConstantFolder::try_fold_operations(Span<NodeOperati } /** - * Evaluate operations that have constant elements values into primitive constant operations. + * Evaluate operations with constant elements into primitive constant operations. */ int ConstantFolder::fold_operations() { - Vector<ConstantOperation *> last_folds = try_fold_operations(all_operations_); + WorkScheduler::start(operations_builder_.context()); + Vector<ConstantOperation *> last_folds = try_fold_operations( + operations_builder_.get_operations()); int folds_count = last_folds.size(); while (last_folds.size() > 0) { Vector<NodeOperation *> ops_to_fold; for (ConstantOperation *fold : last_folds) { - Set<NodeOperation *> &outputs = output_links_.lookup(fold); - ops_to_fold.extend(outputs.begin(), outputs.end()); + get_operation_output_operations(fold, ops_to_fold); } last_folds = try_fold_operations(ops_to_fold); folds_count += last_folds.size(); } - delete_constants_buffers(); - delete_folded_operations(); + WorkScheduler::stop(); + + delete_constant_buffers(); return folds_count; } -void ConstantFolder::delete_constants_buffers() +void ConstantFolder::delete_constant_buffers() { - for (MemoryBuffer *buf : constants_buffers_.values()) { + for (MemoryBuffer *buf : constant_buffers_.values()) { delete buf; } - constants_buffers_.clear(); + constant_buffers_.clear(); } -void ConstantFolder::delete_folded_operations() +void ConstantFolder::get_operation_output_operations(NodeOperation *operation, + Vector<NodeOperation *> &r_outputs) { - for (NodeOperation *op : folded_operations_) { - all_operations_.remove_first_occurrence_and_reorder(op); - delete op; + const Vector<Link> &links = operations_builder_.get_links(); + for (const Link &link : links) { + if (&link.from()->getOperation() == operation) { + r_outputs.append(&link.to()->getOperation()); + } } - folded_operations_.clear(); } } // namespace blender::compositor diff --git a/source/blender/compositor/intern/COM_ConstantFolder.h b/source/blender/compositor/intern/COM_ConstantFolder.h index 3f8b3a0abb3..2432e859a5a 100644 --- a/source/blender/compositor/intern/COM_ConstantFolder.h +++ b/source/blender/compositor/intern/COM_ConstantFolder.h @@ -13,7 +13,7 @@ * 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. */ #pragma once @@ -22,47 +22,43 @@ #include "BLI_set.hh" #include "BLI_vector.hh" -#include "COM_ExecutionSystem.h" +#include "COM_NodeOperationBuilder.h" +#include "COM_defines.h" namespace blender::compositor { + class NodeOperation; class ConstantOperation; +class MemoryBuffer; /** + * Evaluates all operations with constant elements into primitive constant operations + * (Value/Vector/Color). */ class ConstantFolder { private: - Vector<NodeOperation *> &all_operations_; - Map<NodeOperation *, Set<NodeOperation *>> &output_links_; - ExecutionSystem &exec_system_; - - /** - * Operations that has been folded (evaluated into constant operations). They are deleted - * when folding is finished. - */ - Set<NodeOperation *> folded_operations_; + NodeOperationBuilder &operations_builder_; - /** Created constant operations buffers during folding. */ - Map<ConstantOperation *, MemoryBuffer *> constants_buffers_; + /** Constant operations buffers. */ + Map<ConstantOperation *, MemoryBuffer *> constant_buffers_; rcti max_area_; rcti first_elem_area_; public: - ConstantFolder(Vector<NodeOperation *> &operations, - Map<NodeOperation *, Set<NodeOperation *>> &output_links, - ExecutionSystem &exec_system); + ConstantFolder(NodeOperationBuilder &operations_builder); int fold_operations(); private: Vector<ConstantOperation *> try_fold_operations(Span<NodeOperation *> operations); - ConstantOperation *fold_operation(NodeOperation *op); - void relink_operation_outputs_to_constant(NodeOperation *from_op, ConstantOperation *to_op); - void remove_folded_operation(NodeOperation *op); + ConstantOperation *fold_operation(NodeOperation *operation); + MemoryBuffer *create_constant_buffer(DataType data_type); - Vector<MemoryBuffer *> get_constant_inputs_buffers(NodeOperation *op); - void delete_folded_operations(); - void delete_constants_buffers(); + Vector<MemoryBuffer *> get_constant_input_buffers(NodeOperation *operation); + void delete_constant_buffers(); + + void get_operation_output_operations(NodeOperation *operation, + Vector<NodeOperation *> &r_outputs); }; } // namespace blender::compositor diff --git a/source/blender/compositor/intern/COM_Converter.cc b/source/blender/compositor/intern/COM_Converter.cc index af593b2e1b5..18973bb5a00 100644 --- a/source/blender/compositor/intern/COM_Converter.cc +++ b/source/blender/compositor/intern/COM_Converter.cc @@ -460,6 +460,9 @@ void COM_convert_resolution(NodeOperationBuilder &builder, NodeOperationOutput *fromSocket, NodeOperationInput *toSocket) { + /* Data type conversions are executed before resolutions to ensure convert operations have + * resolution. This method have to ensure same datatypes are linked for new operations. */ + BLI_assert(fromSocket->getDataType() == toSocket->getDataType()); ResizeMode mode = toSocket->getResizeMode(); NodeOperation *toOperation = &toSocket->getOperation(); @@ -515,7 +518,7 @@ void COM_convert_resolution(NodeOperationBuilder &builder, NodeOperation *first = nullptr; ScaleOperation *scaleOperation = nullptr; if (doScale) { - scaleOperation = new ScaleOperation(); + scaleOperation = new ScaleOperation(fromSocket->getDataType()); scaleOperation->getInputSocket(1)->setResizeMode(ResizeMode::None); scaleOperation->getInputSocket(2)->setResizeMode(ResizeMode::None); first = scaleOperation; @@ -535,7 +538,7 @@ void COM_convert_resolution(NodeOperationBuilder &builder, builder.addOperation(scaleOperation); } - TranslateOperation *translateOperation = new TranslateOperation(); + TranslateOperation *translateOperation = new TranslateOperation(toSocket->getDataType()); translateOperation->getInputSocket(1)->setResizeMode(ResizeMode::None); translateOperation->getInputSocket(2)->setResizeMode(ResizeMode::None); if (!first) { diff --git a/source/blender/compositor/intern/COM_Debug.cc b/source/blender/compositor/intern/COM_Debug.cc index f5af7cf9147..c0460aed4a4 100644 --- a/source/blender/compositor/intern/COM_Debug.cc +++ b/source/blender/compositor/intern/COM_Debug.cc @@ -31,12 +31,15 @@ extern "C" { #include "BKE_appdir.h" #include "BKE_node.h" #include "DNA_node_types.h" +#include "IMB_imbuf.h" +#include "IMB_imbuf_types.h" } #include "COM_ExecutionSystem.h" #include "COM_Node.h" #include "COM_ReadBufferOperation.h" +#include "COM_SetValueOperation.h" #include "COM_ViewerOperation.h" #include "COM_WriteBufferOperation.h" @@ -49,6 +52,15 @@ std::string DebugInfo::m_current_node_name; std::string DebugInfo::m_current_op_name; DebugInfo::GroupStateMap DebugInfo::m_group_states; +static std::string operation_class_name(const NodeOperation *op) +{ + std::string full_name = typeid(*op).name(); + /* Remove name-spaces. */ + size_t pos = full_name.find_last_of(':'); + BLI_assert(pos != std::string::npos); + return full_name.substr(pos + 1); +} + std::string DebugInfo::node_name(const Node *node) { NodeNameMap::const_iterator it = m_node_names.find(node); @@ -135,15 +147,23 @@ int DebugInfo::graphviz_operation(const ExecutionSystem *system, len += snprintf(str + len, maxlen > len ? maxlen - len : 0, "|"); } + if (COM_GRAPHVIZ_SHOW_NODE_NAME) { + std::string op_node_name = operation->get_name(); + if (!op_node_name.empty()) { + len += snprintf( + str + len, maxlen > len ? maxlen - len : 0, "%s\\n", (op_node_name + " Node").c_str()); + } + } + len += snprintf(str + len, maxlen > len ? maxlen - len : 0, - "%s\\n(%s)", - m_op_names[operation].c_str(), - typeid(*operation).name()); + "%s\\n", + operation_class_name(operation).c_str()); len += snprintf(str + len, maxlen > len ? maxlen - len : 0, - " (%u,%u)", + "#%d (%u,%u)", + operation->get_id(), operation->getWidth(), operation->getHeight()); @@ -159,7 +179,13 @@ int DebugInfo::graphviz_operation(const ExecutionSystem *system, len += snprintf(str + len, maxlen > len ? maxlen - len : 0, "<OUT_%p>", socket); switch (socket->getDataType()) { case DataType::Value: - len += snprintf(str + len, maxlen > len ? maxlen - len : 0, "Value"); + if (typeid(*operation) == typeid(SetValueOperation)) { + const float value = ((SetValueOperation *)operation)->getValue(); + len += snprintf(str + len, maxlen > len ? maxlen - len : 0, "Value\\n%12.4g", value); + } + else { + len += snprintf(str + len, maxlen > len ? maxlen - len : 0, "Value"); + } break; case DataType::Vector: len += snprintf(str + len, maxlen > len ? maxlen - len : 0, "Vector"); @@ -428,4 +454,45 @@ void DebugInfo::graphviz(const ExecutionSystem *system, StringRefNull name) } } +static std::string get_operations_export_dir() +{ + return std::string(BKE_tempdir_session()) + "COM_operations" + SEP_STR; +} + +void DebugInfo::export_operation(const NodeOperation *op, MemoryBuffer *render) +{ + ImBuf *ibuf = IMB_allocFromBuffer(nullptr, + render->getBuffer(), + render->getWidth(), + render->getHeight(), + render->get_num_channels()); + + const std::string file_name = operation_class_name(op) + "_" + std::to_string(op->get_id()) + + ".png"; + const std::string path = get_operations_export_dir() + file_name; + BLI_make_existing_file(path.c_str()); + IMB_saveiff(ibuf, path.c_str(), ibuf->flags); + IMB_freeImBuf(ibuf); +} + +void DebugInfo::delete_operation_exports() +{ + const std::string dir = get_operations_export_dir(); + if (BLI_exists(dir.c_str())) { + struct direntry *file_list; + int num_files = BLI_filelist_dir_contents(dir.c_str(), &file_list); + for (int i = 0; i < num_files; i++) { + direntry *file = &file_list[i]; + const eFileAttributes file_attrs = BLI_file_attributes(file->path); + if (file_attrs & FILE_ATTR_ANY_LINK) { + continue; + } + + if (BLI_is_file(file->path) && BLI_path_extension_check(file->path, ".png")) { + BLI_delete(file->path, false, false); + } + } + } +} + } // namespace blender::compositor diff --git a/source/blender/compositor/intern/COM_Debug.h b/source/blender/compositor/intern/COM_Debug.h index a2fbab45a2c..23d99c7e529 100644 --- a/source/blender/compositor/intern/COM_Debug.h +++ b/source/blender/compositor/intern/COM_Debug.h @@ -28,6 +28,11 @@ namespace blender::compositor { static constexpr bool COM_EXPORT_GRAPHVIZ = false; +static constexpr bool COM_GRAPHVIZ_SHOW_NODE_NAME = false; + +/* Saves operations results to image files. */ +static constexpr bool COM_EXPORT_OPERATION_BUFFERS = false; + class Node; class ExecutionSystem; class ExecutionGroup; @@ -73,6 +78,9 @@ class DebugInfo { m_group_states[execution_group] = EG_WAIT; } } + if (COM_EXPORT_OPERATION_BUFFERS) { + delete_operation_exports(); + } }; static void node_added(const Node *node) @@ -116,6 +124,14 @@ class DebugInfo { } }; + static void operation_rendered(const NodeOperation *op, MemoryBuffer *render) + { + /* Don't export constant operations as there are too many and it's rarely useful. */ + if (COM_EXPORT_OPERATION_BUFFERS && render && !render->is_a_single_elem()) { + export_operation(op, render); + } + } + static void graphviz(const ExecutionSystem *system, StringRefNull name = ""); protected: @@ -131,6 +147,9 @@ class DebugInfo { const char *name, const char *color, const char *style, 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); + + static void export_operation(const NodeOperation *op, MemoryBuffer *render); + static void delete_operation_exports(); }; } // namespace blender::compositor diff --git a/source/blender/compositor/intern/COM_ExecutionModel.cc b/source/blender/compositor/intern/COM_ExecutionModel.cc index 7dee3bd4bbc..08ec0585739 100644 --- a/source/blender/compositor/intern/COM_ExecutionModel.cc +++ b/source/blender/compositor/intern/COM_ExecutionModel.cc @@ -45,10 +45,4 @@ ExecutionModel::ExecutionModel(eExecutionModel model, } } -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 index 128739576b7..f2ff8f9af28 100644 --- a/source/blender/compositor/intern/COM_ExecutionModel.h +++ b/source/blender/compositor/intern/COM_ExecutionModel.h @@ -69,15 +69,6 @@ class 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 diff --git a/source/blender/compositor/intern/COM_ExecutionSystem.cc b/source/blender/compositor/intern/COM_ExecutionSystem.cc index a12ec774032..60caf22be1b 100644 --- a/source/blender/compositor/intern/COM_ExecutionSystem.cc +++ b/source/blender/compositor/intern/COM_ExecutionSystem.cc @@ -63,8 +63,11 @@ ExecutionSystem::ExecutionSystem(RenderData *rd, this->m_context.setViewSettings(viewSettings); this->m_context.setDisplaySettings(displaySettings); + BLI_mutex_init(&work_mutex_); + BLI_condition_init(&work_finished_cond_); + { - NodeOperationBuilder builder(&m_context, editingtree); + NodeOperationBuilder builder(&m_context, editingtree, this); builder.convertToOperations(this); } @@ -76,13 +79,16 @@ ExecutionSystem::ExecutionSystem(RenderData *rd, execution_model_ = new FullFrameExecutionModel(m_context, active_buffers_, m_operations); break; default: - BLI_assert(!"Non implemented execution model"); + BLI_assert_msg(0, "Non implemented execution model"); break; } } ExecutionSystem::~ExecutionSystem() { + BLI_condition_end(&work_finished_cond_); + BLI_mutex_end(&work_mutex_); + delete execution_model_; for (NodeOperation *operation : m_operations) { @@ -109,10 +115,74 @@ void ExecutionSystem::execute() execution_model_->execute(*this); } +/** + * Multi-threadedly execute given work function passing work_rect splits as argument. + */ void ExecutionSystem::execute_work(const rcti &work_rect, std::function<void(const rcti &split_rect)> work_func) { - execution_model_->execute_work(work_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_); +} + +bool ExecutionSystem::is_breaked() const +{ + const bNodeTree *btree = m_context.getbNodeTree(); + return btree->test_break(btree->tbh); } } // namespace blender::compositor diff --git a/source/blender/compositor/intern/COM_ExecutionSystem.h b/source/blender/compositor/intern/COM_ExecutionSystem.h index e106209651c..38c3432a8ec 100644 --- a/source/blender/compositor/intern/COM_ExecutionSystem.h +++ b/source/blender/compositor/intern/COM_ExecutionSystem.h @@ -150,7 +150,9 @@ class ExecutionSystem { */ ExecutionModel *execution_model_; - private: // methods + ThreadMutex work_mutex_; + ThreadCondition work_finished_cond_; + public: /** * \brief Create a new ExecutionSystem and initialize it with the @@ -199,6 +201,8 @@ class ExecutionSystem { void execute_work(const rcti &work_rect, std::function<void(const rcti &split_rect)> work_func); + bool is_breaked() const; + private: /* allow the DebugInfo class to look at internals */ friend class DebugInfo; diff --git a/source/blender/compositor/intern/COM_FullFrameExecutionModel.cc b/source/blender/compositor/intern/COM_FullFrameExecutionModel.cc index b39600f12de..00b14ff18e3 100644 --- a/source/blender/compositor/intern/COM_FullFrameExecutionModel.cc +++ b/source/blender/compositor/intern/COM_FullFrameExecutionModel.cc @@ -21,6 +21,7 @@ #include "COM_Debug.h" #include "COM_ExecutionGroup.h" #include "COM_ReadBufferOperation.h" +#include "COM_ViewerOperation.h" #include "COM_WorkScheduler.h" #include "BLI_map.hh" @@ -39,34 +40,13 @@ FullFrameExecutionModel::FullFrameExecutionModel(CompositorContext &context, Vector<NodeOperation *> &operations) : ExecutionModel(eExecutionModel::FullFrame, context, operations), active_buffers_(shared_buffers), - num_operations_finished_(0), - priorities_(), - work_mutex_(), - work_finished_cond_() + num_operations_finished_(0) { 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_); - - /* Get operations output links to other operations. */ - for (NodeOperation *op : operations_) { - for (int i = 0; i < op->getNumberOfInputSockets(); i++) { - NodeOperation *input_op = op->get_input_operation(i); - Set<NodeOperation *> &links = output_links_.lookup_or_add_default(input_op); - links.add(op); - } - } -} - -FullFrameExecutionModel::~FullFrameExecutionModel() -{ - BLI_condition_end(&work_finished_cond_); - BLI_mutex_end(&work_mutex_); } void FullFrameExecutionModel::execute(ExecutionSystem &exec_system) @@ -74,15 +54,10 @@ 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, "compositor_prior_folding"); - ConstantFolder folder(operations_, output_links_, exec_system); - folder.fold_operations(); - DebugInfo::graphviz(&exec_system, "compositor_after_folding"); + DebugInfo::graphviz(&exec_system, "compositor_prior_rendering"); determine_areas_to_render_and_reads(); - clamp_operations_to_rendered_areas(); - DebugInfo::graphviz(&exec_system, "compositor_after_clamping"); - render_operations(exec_system); + render_operations(); } void FullFrameExecutionModel::determine_areas_to_render_and_reads() @@ -103,26 +78,6 @@ void FullFrameExecutionModel::determine_areas_to_render_and_reads() } } -void FullFrameExecutionModel::clamp_operations_to_rendered_areas() -{ - for (NodeOperation *op : operations_) { - rcti bounds = active_buffers_.get_render_bounds(op); - op->set_canvas_area(bounds); - } -} - -void FullFrameExecutionModel::ensure_inputs_rendered(NodeOperation *op, - ExecutionSystem &exec_system) -{ - const int num_inputs = op->getNumberOfInputSockets(); - for (int i = 0; i < num_inputs; i++) { - NodeOperation *input_op = op->get_input_operation(i); - if (!active_buffers_.is_operation_rendered(input_op)) { - render_operation(input_op, exec_system); - } - } -} - Vector<MemoryBuffer *> FullFrameExecutionModel::get_input_buffers(NodeOperation *op) { const int num_inputs = op->getNumberOfInputSockets(); @@ -138,22 +93,22 @@ MemoryBuffer *FullFrameExecutionModel::create_operation_buffer(NodeOperation *op { const DataType data_type = op->getOutputSocket(0)->getDataType(); const bool is_a_single_elem = op->get_flags().is_constant_operation; - return new MemoryBuffer(data_type, op->get_canvas_area(), is_a_single_elem); + return new MemoryBuffer(data_type, op_rect, is_a_single_elem); } -void FullFrameExecutionModel::render_operation(NodeOperation *op, ExecutionSystem &exec_system) +void FullFrameExecutionModel::render_operation(NodeOperation *op) { - if (active_buffers_.is_operation_rendered(op)) { - return; - } - - ensure_inputs_rendered(op, 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); + if (op->getWidth() > 0 && op->getHeight() > 0) { + Span<rcti> areas = active_buffers_.get_areas_to_render(op); + op->render(op_buf, areas, input_bufs); + DebugInfo::operation_rendered(op, op_buf); + } + /* Even if operation has no resolution set the empty buffer. It will be clipped with a + * TranslateOperation from convert resolutions if linked to an operation with resolution. */ active_buffers_.set_rendered_buffer(op, std::unique_ptr<MemoryBuffer>(op_buf)); operation_finished(op); @@ -162,15 +117,22 @@ void FullFrameExecutionModel::render_operation(NodeOperation *op, ExecutionSyste /** * Render output operations in order of priority. */ -void FullFrameExecutionModel::render_operations(ExecutionSystem &exec_system) +void FullFrameExecutionModel::render_operations() { 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_operation(op, exec_system); + const bool has_size = op->getWidth() > 0 && op->getHeight() > 0; + const bool is_priority_output = op->isOutputOperation(is_rendering) && + op->getRenderPriority() == priority; + if (is_priority_output && has_size) { + render_output_dependencies(op); + render_operation(op); + } + else if (is_priority_output && !has_size && op->isActiveViewerOutput()) { + static_cast<ViewerOperation *>(op)->clear_display_buffer(); } } } @@ -178,48 +140,98 @@ void FullFrameExecutionModel::render_operations(ExecutionSystem &exec_system) } /** - * Determines all input operations areas needed to render given operation area. - * \param operation: Renderer operation. - * \param render_area: Area within given operation bounds to render. + * Returns all dependencies from inputs to outputs. A dependency may be repeated when + * several operations depend on it. */ -void FullFrameExecutionModel::determine_areas_to_render(NodeOperation *operation, - const rcti &render_area) +static Vector<NodeOperation *> get_operation_dependencies(NodeOperation *operation) { - if (active_buffers_.is_area_registered(operation, render_area)) { - return; + /* 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); } - active_buffers_.register_area(operation, render_area); + /* Reverse to get dependencies from inputs to outputs. */ + std::reverse(dependencies.begin(), dependencies.end()); - 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_op->get_canvas_area(); - rcti input_area; - operation->get_area_of_interest(input_op, render_area, input_area); + return dependencies; +} + +void FullFrameExecutionModel::render_output_dependencies(NodeOperation *output_op) +{ + 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); + } + } +} + +/** + * 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; + } - /* Ensure area of interest is within operation bounds, cropping areas outside. */ - BLI_rcti_isect(&input_area, &input_op_rect, &input_area); + active_buffers_.register_area(operation, render_area); - determine_areas_to_render(input_op, input_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 the reads given operation and its inputs will receive (i.e: Number of dependent + * Determines reads to receive by operations in output operation tree (i.e: Number of dependent * operations each operation has). */ -void FullFrameExecutionModel::determine_reads(NodeOperation *operation) +void FullFrameExecutionModel::determine_reads(NodeOperation *output_op) { - if (active_buffers_.has_registered_reads(operation)) { - return; - } + BLI_assert(output_op->isOutputOperation(context_.isRendering())); - const int num_inputs = operation->getNumberOfInputSockets(); - for (int i = 0; i < num_inputs; i++) { - NodeOperation *input_op = operation->get_input_operation(i); - determine_reads(input_op); - active_buffers_.register_read(input_op); + 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); + } } } @@ -253,70 +265,6 @@ void FullFrameExecutionModel::get_output_render_area(NodeOperation *output_op, r } } -/** - * 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. */ diff --git a/source/blender/compositor/intern/COM_FullFrameExecutionModel.h b/source/blender/compositor/intern/COM_FullFrameExecutionModel.h index 4255cb217d1..f2151206864 100644 --- a/source/blender/compositor/intern/COM_FullFrameExecutionModel.h +++ b/source/blender/compositor/intern/COM_FullFrameExecutionModel.h @@ -53,40 +53,26 @@ class FullFrameExecutionModel : public ExecutionModel { */ Vector<eCompositorPriority> priorities_; - ThreadMutex work_mutex_; - ThreadCondition work_finished_cond_; - - /** - * Operations output links to other operations. - */ - Map<NodeOperation *, Set<NodeOperation *>> output_links_; - public: FullFrameExecutionModel(CompositorContext &context, SharedOperationBuffers &shared_buffers, - Vector<NodeOperation *> &operations); - ~FullFrameExecutionModel(); + Span<NodeOperation *> operations); 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 clamp_operations_to_rendered_areas(); - void render_operations(ExecutionSystem &exec_system); - - void ensure_inputs_rendered(NodeOperation *op, ExecutionSystem &exec_system); + void render_operations(); + void render_output_dependencies(NodeOperation *output_op); Vector<MemoryBuffer *> get_input_buffers(NodeOperation *op); MemoryBuffer *create_operation_buffer(NodeOperation *op); - void render_operation(NodeOperation *op, ExecutionSystem &exec_system); + void render_operation(NodeOperation *op); void operation_finished(NodeOperation *operation); void get_output_render_area(NodeOperation *output_op, rcti &r_area); - void determine_areas_to_render(NodeOperation *operation, const rcti &render_area); - void determine_reads(NodeOperation *operation); + void determine_areas_to_render(NodeOperation *output_op, const rcti &output_area); + void determine_reads(NodeOperation *output_op); void update_progress_bar(); diff --git a/source/blender/compositor/intern/COM_MemoryBuffer.cc b/source/blender/compositor/intern/COM_MemoryBuffer.cc index 4085de0c96e..6b954072a9a 100644 --- a/source/blender/compositor/intern/COM_MemoryBuffer.cc +++ b/source/blender/compositor/intern/COM_MemoryBuffer.cc @@ -18,10 +18,31 @@ #include "COM_MemoryBuffer.h" +#include "IMB_colormanagement.h" +#include "IMB_imbuf_types.h" #include "MEM_guardedalloc.h" +#define ASSERT_BUFFER_CONTAINS_AREA(buf, area) \ + BLI_assert(BLI_rcti_inside_rcti(&(buf)->get_rect(), &(area))) + +#define ASSERT_BUFFER_CONTAINS_AREA_AT_COORDS(buf, area, x, y) \ + BLI_assert((buf)->get_rect().xmin <= (x)); \ + BLI_assert((buf)->get_rect().ymin <= (y)); \ + BLI_assert((buf)->get_rect().xmax >= (x) + BLI_rcti_size_x(&(area))); \ + BLI_assert((buf)->get_rect().ymax >= (y) + BLI_rcti_size_y(&(area))) + +#define ASSERT_VALID_ELEM_SIZE(buf, channel_offset, elem_size) \ + BLI_assert((buf)->get_num_channels() <= (channel_offset) + (elem_size)) + namespace blender::compositor { +static rcti create_rect(const int width, const int height) +{ + rcti rect; + BLI_rcti_init(&rect, 0, width, 0, height); + return rect; +} + MemoryBuffer::MemoryBuffer(MemoryProxy *memoryProxy, const rcti &rect, MemoryBufferState state) { m_rect = rect; @@ -30,7 +51,7 @@ MemoryBuffer::MemoryBuffer(MemoryProxy *memoryProxy, const rcti &rect, MemoryBuf 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"); - is_owner_ = true; + owns_data_ = true; this->m_state = state; this->m_datatype = memoryProxy->getDataType(); @@ -45,26 +66,39 @@ MemoryBuffer::MemoryBuffer(DataType dataType, const rcti &rect, bool is_a_single 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"); - is_owner_ = true; + owns_data_ = true; this->m_state = MemoryBufferState::Temporary; this->m_datatype = dataType; set_strides(); } +/** + * Construct MemoryBuffer from a float buffer. MemoryBuffer is not responsible for + * freeing it. + */ +MemoryBuffer::MemoryBuffer( + float *buffer, int num_channels, int width, int height, bool is_a_single_elem) + : MemoryBuffer(buffer, num_channels, create_rect(width, height), is_a_single_elem) +{ +} + +/** + * Construct MemoryBuffer from a float buffer area. MemoryBuffer is not responsible for + * freeing given buffer. + */ MemoryBuffer::MemoryBuffer(float *buffer, const int num_channels, - const int width, - const int height, + const rcti &rect, const bool is_a_single_elem) { - BLI_rcti_init(&m_rect, 0, width, 0, height); + m_rect = rect; m_is_a_single_elem = is_a_single_elem; m_memoryProxy = nullptr; m_num_channels = num_channels; m_datatype = COM_num_channels_data_type(num_channels); m_buffer = buffer; - is_owner_ = false; + owns_data_ = false; m_state = MemoryBufferState::Temporary; set_strides(); @@ -95,6 +129,32 @@ void MemoryBuffer::clear() memset(m_buffer, 0, buffer_len() * m_num_channels * sizeof(float)); } +BuffersIterator<float> MemoryBuffer::iterate_with(Span<MemoryBuffer *> inputs) +{ + return iterate_with(inputs, m_rect); +} + +BuffersIterator<float> MemoryBuffer::iterate_with(Span<MemoryBuffer *> inputs, const rcti &area) +{ + BuffersIteratorBuilder<float> builder(m_buffer, m_rect, area, elem_stride); + for (MemoryBuffer *input : inputs) { + builder.add_input(input->getBuffer(), input->get_rect(), input->elem_stride); + } + return builder.build(); +} + +/** + * Converts a single elem buffer to a full size buffer (allocates memory for all + * elements in resolution). + */ +MemoryBuffer *MemoryBuffer::inflate() const +{ + BLI_assert(is_a_single_elem()); + MemoryBuffer *inflated = new MemoryBuffer(this->m_datatype, this->m_rect, false); + inflated->copy_from(this, this->m_rect); + return inflated; +} + float MemoryBuffer::get_max_value() const { float result = this->m_buffer[0]; @@ -132,33 +192,197 @@ float MemoryBuffer::get_max_value(const rcti &rect) const MemoryBuffer::~MemoryBuffer() { - if (this->m_buffer && is_owner_) { + if (this->m_buffer && owns_data_) { MEM_freeN(this->m_buffer); this->m_buffer = nullptr; } } -void MemoryBuffer::fill_from(const MemoryBuffer &src) +void MemoryBuffer::copy_from(const MemoryBuffer *src, const rcti &area) { - BLI_assert(!this->is_a_single_elem()); + copy_from(src, area, area.xmin, area.ymin); +} + +void MemoryBuffer::copy_from(const MemoryBuffer *src, + const rcti &area, + const int to_x, + const int to_y) +{ + BLI_assert(this->get_num_channels() == src->get_num_channels()); + copy_from(src, area, 0, src->get_num_channels(), to_x, to_y, 0); +} + +void MemoryBuffer::copy_from(const MemoryBuffer *src, + const rcti &area, + const int channel_offset, + const int elem_size, + const int to_channel_offset) +{ + copy_from(src, area, channel_offset, elem_size, area.xmin, area.ymin, to_channel_offset); +} + +void MemoryBuffer::copy_from(const MemoryBuffer *src, + const rcti &area, + const int channel_offset, + const int elem_size, + const int to_x, + const int to_y, + const int to_channel_offset) +{ + if (this->is_a_single_elem()) { + copy_single_elem_from(src, channel_offset, elem_size, to_channel_offset); + } + else if (!src->is_a_single_elem() && elem_size == src->get_num_channels() && + elem_size == this->get_num_channels()) { + BLI_assert(to_channel_offset == 0); + BLI_assert(channel_offset == 0); + copy_rows_from(src, area, to_x, to_y); + } + else { + copy_elems_from(src, area, channel_offset, elem_size, to_x, to_y, to_channel_offset); + } +} + +void MemoryBuffer::copy_from(const uchar *src, const rcti &area) +{ + copy_from(src, area, 0, this->get_num_channels(), this->get_num_channels(), 0); +} + +void MemoryBuffer::copy_from(const uchar *src, + const rcti &area, + const int channel_offset, + const int elem_size, + const int elem_stride, + const int to_channel_offset) +{ + copy_from( + src, area, channel_offset, elem_size, elem_stride, area.xmin, area.ymin, to_channel_offset); +} + +void MemoryBuffer::copy_from(const uchar *src, + const rcti &area, + const int channel_offset, + const int elem_size, + const int elem_stride, + const int to_x, + const int to_y, + const int to_channel_offset) +{ + ASSERT_BUFFER_CONTAINS_AREA_AT_COORDS(this, area, to_x, to_y); + ASSERT_VALID_ELEM_SIZE(this, to_channel_offset, elem_size); - 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); - unsigned int minY = MAX2(this->m_rect.ymin, src.m_rect.ymin); - unsigned int maxY = MIN2(this->m_rect.ymax, src.m_rect.ymax); - int offset; - int otherOffset; + const int width = BLI_rcti_size_x(&area); + const int height = BLI_rcti_size_y(&area); + const int src_row_stride = width * elem_stride; + const uchar *const src_start = src + area.ymin * src_row_stride + channel_offset; + for (int y = 0; y < height; y++) { + const uchar *from_elem = src_start + y * src_row_stride; + float *to_elem = &this->get_value(to_x, to_y + y, to_channel_offset); + const float *row_end = to_elem + width * this->elem_stride; + while (to_elem < row_end) { + for (int i = 0; i < elem_size; i++) { + to_elem[i] = ((float)from_elem[i]) * (1.0f / 255.0f); + } + to_elem += this->elem_stride; + from_elem += elem_stride; + } + } +} + +static void colorspace_to_scene_linear(MemoryBuffer *buf, const rcti &area, ColorSpace *colorspace) +{ + const int width = BLI_rcti_size_x(&area); + const int height = BLI_rcti_size_y(&area); + float *out = buf->get_elem(area.xmin, area.ymin); + /* If area allows continuous memory do conversion in one step. Otherwise per row. */ + if (buf->getWidth() == width) { + IMB_colormanagement_colorspace_to_scene_linear( + out, width, height, buf->get_num_channels(), colorspace, false); + } + else { + for (int y = 0; y < height; y++) { + IMB_colormanagement_colorspace_to_scene_linear( + out, width, 1, buf->get_num_channels(), colorspace, false); + out += buf->row_stride; + } + } +} + +void MemoryBuffer::copy_from(const ImBuf *src, const rcti &area, const bool ensure_linear_space) +{ + copy_from(src, area, 0, this->get_num_channels(), 0, ensure_linear_space); +} + +void MemoryBuffer::copy_from(const ImBuf *src, + const rcti &area, + const int channel_offset, + const int elem_size, + const int to_channel_offset, + const bool ensure_linear_space) +{ + copy_from(src, + area, + channel_offset, + elem_size, + area.xmin, + area.ymin, + to_channel_offset, + ensure_linear_space); +} - for (otherY = minY; otherY < maxY; otherY++) { - 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)); +void MemoryBuffer::copy_from(const ImBuf *src, + const rcti &area, + const int channel_offset, + const int elem_size, + const int to_x, + const int to_y, + const int to_channel_offset, + const bool ensure_linear_space) +{ + if (src->rect_float) { + const MemoryBuffer mem_buf(src->rect_float, src->channels, src->x, src->y, false); + copy_from(&mem_buf, area, channel_offset, elem_size, to_x, to_y, to_channel_offset); + } + else if (src->rect) { + const uchar *uc_buf = (uchar *)src->rect; + const int elem_stride = src->channels; + copy_from(uc_buf, area, channel_offset, elem_size, elem_stride, to_x, to_y, to_channel_offset); + if (ensure_linear_space) { + colorspace_to_scene_linear(this, area, src->rect_colorspace); + } + } + else { + /* Empty ImBuf source. Fill destination with empty values. */ + const float *zero_elem = new float[elem_size]{0}; + fill(area, to_channel_offset, zero_elem, elem_size); + delete[] zero_elem; } } +void MemoryBuffer::fill(const rcti &area, const float *value) +{ + fill(area, 0, value, this->get_num_channels()); +} + +void MemoryBuffer::fill(const rcti &area, + const int channel_offset, + const float *value, + const int value_size) +{ + const MemoryBuffer single_elem(const_cast<float *>(value), value_size, this->get_rect(), true); + copy_from(&single_elem, area, 0, value_size, area.xmin, area.ymin, channel_offset); +} + +void MemoryBuffer::fill_from(const MemoryBuffer &src) +{ + rcti overlap; + overlap.xmin = MAX2(this->m_rect.xmin, src.m_rect.xmin); + overlap.xmax = MIN2(this->m_rect.xmax, src.m_rect.xmax); + overlap.ymin = MAX2(this->m_rect.ymin, src.m_rect.ymin); + overlap.ymax = MIN2(this->m_rect.ymax, src.m_rect.ymax); + copy_from(&src, overlap); +} + 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 && @@ -216,4 +440,70 @@ void MemoryBuffer::readEWA(float *result, const float uv[2], const float derivat } } +void MemoryBuffer::copy_single_elem_from(const MemoryBuffer *src, + const int channel_offset, + const int elem_size, + const int to_channel_offset) +{ + ASSERT_VALID_ELEM_SIZE(this, to_channel_offset, elem_size); + ASSERT_VALID_ELEM_SIZE(src, channel_offset, elem_size); + BLI_assert(this->is_a_single_elem()); + + float *to_elem = &this->get_value( + this->get_rect().xmin, this->get_rect().ymin, to_channel_offset); + const float *from_elem = &src->get_value( + src->get_rect().xmin, src->get_rect().ymin, channel_offset); + const int elem_bytes = elem_size * sizeof(float); + memcpy(to_elem, from_elem, elem_bytes); +} + +void MemoryBuffer::copy_rows_from(const MemoryBuffer *src, + const rcti &area, + const int to_x, + const int to_y) +{ + ASSERT_BUFFER_CONTAINS_AREA(src, area); + ASSERT_BUFFER_CONTAINS_AREA_AT_COORDS(this, area, to_x, to_y); + BLI_assert(this->get_num_channels() == src->get_num_channels()); + BLI_assert(!this->is_a_single_elem()); + BLI_assert(!src->is_a_single_elem()); + + const int width = BLI_rcti_size_x(&area); + const int height = BLI_rcti_size_y(&area); + const int row_bytes = this->get_num_channels() * width * sizeof(float); + for (int y = 0; y < height; y++) { + float *to_row = this->get_elem(to_x, to_y + y); + const float *from_row = src->get_elem(area.xmin, area.ymin + y); + memcpy(to_row, from_row, row_bytes); + } +} + +void MemoryBuffer::copy_elems_from(const MemoryBuffer *src, + const rcti &area, + const int channel_offset, + const int elem_size, + const int to_x, + const int to_y, + const int to_channel_offset) +{ + ASSERT_BUFFER_CONTAINS_AREA(src, area); + ASSERT_BUFFER_CONTAINS_AREA_AT_COORDS(this, area, to_x, to_y); + ASSERT_VALID_ELEM_SIZE(this, to_channel_offset, elem_size); + ASSERT_VALID_ELEM_SIZE(src, channel_offset, elem_size); + + const int width = BLI_rcti_size_x(&area); + const int height = BLI_rcti_size_y(&area); + const int elem_bytes = elem_size * sizeof(float); + for (int y = 0; y < height; y++) { + float *to_elem = &this->get_value(to_x, to_y + y, to_channel_offset); + const float *from_elem = &src->get_value(area.xmin, area.ymin + y, channel_offset); + const float *row_end = to_elem + width * this->elem_stride; + while (to_elem < row_end) { + memcpy(to_elem, from_elem, elem_bytes); + to_elem += this->elem_stride; + from_elem += src->elem_stride; + } + } +} + } // namespace blender::compositor diff --git a/source/blender/compositor/intern/COM_MemoryBuffer.h b/source/blender/compositor/intern/COM_MemoryBuffer.h index fc40708eeb9..9f68d508b2b 100644 --- a/source/blender/compositor/intern/COM_MemoryBuffer.h +++ b/source/blender/compositor/intern/COM_MemoryBuffer.h @@ -18,6 +18,9 @@ #pragma once +#include "COM_BufferArea.h" +#include "COM_BufferRange.h" +#include "COM_BuffersIterator.h" #include "COM_ExecutionGroup.h" #include "COM_MemoryProxy.h" @@ -34,7 +37,7 @@ enum class MemoryBufferState { /** \brief memory has been allocated on creator device and CPU machine, * but kernel has not been executed */ Default = 0, - /** \brief chunk is consolidated from other chunks. special state.*/ + /** \brief chunk is consolidated from other chunks. special state. */ Temporary = 6, }; @@ -109,7 +112,7 @@ class MemoryBuffer { /** * Whether MemoryBuffer owns buffer data. */ - bool is_owner_; + bool owns_data_; public: /** @@ -122,6 +125,11 @@ class MemoryBuffer { */ MemoryBuffer(DataType datatype, const rcti &rect, bool is_a_single_elem = false); + MemoryBuffer( + float *buffer, int num_channels, int width, int height, bool is_a_single_elem = false); + + MemoryBuffer(float *buffer, int num_channels, const rcti &rect, bool is_a_single_elem = false); + /** * Construct MemoryBuffer from an existent float buffer. MemoryBuffer is not responsible for * freeing given buffer. @@ -235,12 +243,38 @@ class MemoryBuffer { return is_a_single_elem() ? 1 : getHeight(); } - uint8_t get_num_channels() + uint8_t get_num_channels() const { return this->m_num_channels; } /** + * Get all buffer elements as a range with no offsets. + */ + BufferRange<float> as_range() + { + return BufferRange<float>(m_buffer, 0, buffer_len(), elem_stride); + } + + BufferRange<const float> as_range() const + { + return BufferRange<const float>(m_buffer, 0, buffer_len(), elem_stride); + } + + BufferArea<float> get_buffer_area(const rcti &area) + { + return BufferArea<float>(m_buffer, getWidth(), area, elem_stride); + } + + BufferArea<const float> get_buffer_area(const rcti &area) const + { + return BufferArea<const float>(m_buffer, getWidth(), area, elem_stride); + } + + BuffersIterator<float> iterate_with(Span<MemoryBuffer *> inputs); + BuffersIterator<float> iterate_with(Span<MemoryBuffer *> inputs, const rcti &area); + + /** * \brief get the data of this MemoryBuffer * \note buffer should already be available in memory */ @@ -249,6 +283,8 @@ class MemoryBuffer { return this->m_buffer; } + MemoryBuffer *inflate() const; + inline void wrap_pixel(int &x, int &y, MemoryBufferExtend extend_x, MemoryBufferExtend extend_y) { const int w = getWidth(); @@ -264,11 +300,14 @@ class MemoryBuffer { x = 0; } if (x >= w) { - x = w; + x = w - 1; } break; case MemoryBufferExtend::Repeat: - x = (x >= 0.0f ? (x % w) : (x % w) + w); + x %= w; + if (x < 0) { + x += w; + } break; } @@ -280,13 +319,19 @@ class MemoryBuffer { y = 0; } if (y >= h) { - y = h; + y = h - 1; } break; case MemoryBufferExtend::Repeat: - y = (y >= 0.0f ? (y % h) : (y % h) + h); + y %= h; + if (y < 0) { + y += h; + } break; } + + x = x + m_rect.xmin; + y = y + m_rect.ymin; } inline void wrap_pixel(float &x, @@ -307,11 +352,14 @@ class MemoryBuffer { x = 0.0f; } if (x >= w) { - x = w; + x = w - 1; } break; case MemoryBufferExtend::Repeat: x = fmodf(x, w); + if (x < 0.0f) { + x += w; + } break; } @@ -323,13 +371,19 @@ class MemoryBuffer { y = 0.0f; } if (y >= h) { - y = h; + y = h - 1; } break; case MemoryBufferExtend::Repeat: y = fmodf(y, h); + if (y < 0.0f) { + y += h; + } break; } + + x = x + m_rect.xmin; + y = y + m_rect.ymin; } inline void read(float *result, @@ -416,11 +470,58 @@ class MemoryBuffer { return this->m_state == MemoryBufferState::Temporary; } + void copy_from(const MemoryBuffer *src, const rcti &area); + void copy_from(const MemoryBuffer *src, const rcti &area, int to_x, int to_y); + void copy_from(const MemoryBuffer *src, + const rcti &area, + int channel_offset, + int elem_size, + int to_channel_offset); + void copy_from(const MemoryBuffer *src, + const rcti &area, + int channel_offset, + int elem_size, + int to_x, + int to_y, + int to_channel_offset); + void copy_from(const uchar *src, const rcti &area); + void copy_from(const uchar *src, + const rcti &area, + int channel_offset, + int elem_size, + int elem_stride, + int to_channel_offset); + void copy_from(const uchar *src, + const rcti &area, + int channel_offset, + int elem_size, + int elem_stride, + int to_x, + int to_y, + int to_channel_offset); + void copy_from(const struct ImBuf *src, const rcti &area, bool ensure_linear_space = false); + void copy_from(const struct ImBuf *src, + const rcti &area, + int channel_offset, + int elem_size, + int to_channel_offset, + bool ensure_linear_space = false); + void copy_from(const struct ImBuf *src, + const rcti &src_area, + int channel_offset, + int elem_size, + int to_x, + int to_y, + int to_channel_offset, + bool ensure_linear_space = false); + + void fill(const rcti &area, const float *value); + void fill(const rcti &area, int channel_offset, const float *value, int value_size); /** * \brief add the content from otherBuffer to this MemoryBuffer * \param otherBuffer: source buffer * - * \note take care when running this on a new buffer since it wont fill in + * \note take care when running this on a new buffer since it won't fill in * uninitialized values in areas where the buffers don't overlap. */ void fill_from(const MemoryBuffer &src); @@ -464,6 +565,22 @@ class MemoryBuffer { return get_memory_width() * get_memory_height(); } + void copy_single_elem_from(const MemoryBuffer *src, + int channel_offset, + int elem_size, + const int to_channel_offset); + void copy_rows_from(const MemoryBuffer *src, + const rcti &src_area, + const int to_x, + const int to_y); + void copy_elems_from(const MemoryBuffer *src, + const rcti &area, + const int channel_offset, + const int elem_size, + const int to_x, + const int to_y, + const int to_channel_offset); + #ifdef WITH_CXX_GUARDEDALLOC MEM_CXX_CLASS_ALLOC_FUNCS("COM:MemoryBuffer") #endif diff --git a/source/blender/compositor/intern/COM_MemoryProxy.h b/source/blender/compositor/intern/COM_MemoryProxy.h index 931fd8d2622..6814afada74 100644 --- a/source/blender/compositor/intern/COM_MemoryProxy.h +++ b/source/blender/compositor/intern/COM_MemoryProxy.h @@ -18,6 +18,10 @@ #pragma once +#ifdef WITH_CXX_GUARDEDALLOC +# include "MEM_guardedalloc.h" +#endif + #include "COM_defines.h" namespace blender::compositor { diff --git a/source/blender/compositor/intern/COM_MetaData.cc b/source/blender/compositor/intern/COM_MetaData.cc index 88bfa385514..a6fb84dfb87 100644 --- a/source/blender/compositor/intern/COM_MetaData.cc +++ b/source/blender/compositor/intern/COM_MetaData.cc @@ -41,7 +41,7 @@ void MetaData::addCryptomatteEntry(const blender::StringRef layer_name, /* Replace the hash neutral cryptomatte keys with hashed versions. * * When a conversion happens it will also add the cryptomatte name key with the given - * `layer_name`.*/ + * `layer_name`. */ void MetaData::replaceHashNeutralCryptomatteKeys(const blender::StringRef layer_name) { std::string cryptomatte_hash = entries_.pop_default(META_DATA_KEY_CRYPTOMATTE_HASH, ""); diff --git a/source/blender/compositor/intern/COM_MultiThreadedOperation.cc b/source/blender/compositor/intern/COM_MultiThreadedOperation.cc index c54c2edccb0..7ccf6f76d9f 100644 --- a/source/blender/compositor/intern/COM_MultiThreadedOperation.cc +++ b/source/blender/compositor/intern/COM_MultiThreadedOperation.cc @@ -5,21 +5,21 @@ namespace blender::compositor { MultiThreadedOperation::MultiThreadedOperation() { - m_num_passes = 1; + num_passes_ = 1; + current_pass_ = 0; flags.is_fullframe_operation = true; } void MultiThreadedOperation::update_memory_buffer(MemoryBuffer *output, - const rcti &output_area, - blender::Span<MemoryBuffer *> inputs, - ExecutionSystem &exec_system) + const rcti &area, + Span<MemoryBuffer *> inputs) { - 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); + for (current_pass_ = 0; current_pass_ < num_passes_; current_pass_++) { + update_memory_buffer_started(output, area, inputs); + exec_system_->execute_work(area, [=](const rcti &split_rect) { + update_memory_buffer_partial(output, split_rect, inputs); }); - update_memory_buffer_finished(output, output_area, inputs, exec_system, current_pass); + update_memory_buffer_finished(output, area, inputs); } } diff --git a/source/blender/compositor/intern/COM_MultiThreadedOperation.h b/source/blender/compositor/intern/COM_MultiThreadedOperation.h index e86b1d303f9..a7e574ca745 100644 --- a/source/blender/compositor/intern/COM_MultiThreadedOperation.h +++ b/source/blender/compositor/intern/COM_MultiThreadedOperation.h @@ -27,7 +27,11 @@ class MultiThreadedOperation : public NodeOperation { /** * Number of execution passes. */ - int m_num_passes; + int num_passes_; + /** + * Current execution pass. + */ + int current_pass_; protected: MultiThreadedOperation(); @@ -36,38 +40,31 @@ class MultiThreadedOperation : public NodeOperation { * 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)) + const rcti &UNUSED(area), + Span<MemoryBuffer *> UNUSED(inputs)) { } /** - * Executes operation updating output memory buffer on output_rect area. Multi-threaded calls. + * Executes operation updating a memory buffer 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; + const rcti &area, + Span<MemoryBuffer *> inputs) = 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)) + const rcti &UNUSED(area), + Span<MemoryBuffer *> UNUSED(inputs)) { } private: void update_memory_buffer(MemoryBuffer *output, - const rcti &output_rect, - blender::Span<MemoryBuffer *> inputs, - ExecutionSystem &exec_system) override; + const rcti &area, + Span<MemoryBuffer *> inputs) override; }; } // namespace blender::compositor diff --git a/source/blender/compositor/intern/COM_MultiThreadedRowOperation.cc b/source/blender/compositor/intern/COM_MultiThreadedRowOperation.cc new file mode 100644 index 00000000000..6bf318bb96b --- /dev/null +++ b/source/blender/compositor/intern/COM_MultiThreadedRowOperation.cc @@ -0,0 +1,50 @@ +/* + * 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_MultiThreadedRowOperation.h" + +namespace blender::compositor { + +MultiThreadedRowOperation::PixelCursor::PixelCursor(const int num_inputs) + : out(nullptr), out_stride(0), row_end(nullptr), ins(num_inputs), in_strides(num_inputs) +{ +} + +void MultiThreadedRowOperation::update_memory_buffer_partial(MemoryBuffer *output, + const rcti &area, + Span<MemoryBuffer *> inputs) +{ + BLI_assert(output != nullptr); + const int width = BLI_rcti_size_x(&area); + PixelCursor p(inputs.size()); + p.out_stride = output->elem_stride; + for (int i = 0; i < p.in_strides.size(); i++) { + p.in_strides[i] = inputs[i]->elem_stride; + } + + for (int y = area.ymin; y < area.ymax; y++) { + p.out = output->get_elem(area.xmin, y); + for (int i = 0; i < p.ins.size(); i++) { + p.ins[i] = inputs[i]->get_elem(area.xmin, y); + } + p.row_end = p.out + width * p.out_stride; + update_memory_buffer_row(p); + } +} + +} // namespace blender::compositor diff --git a/source/blender/compositor/intern/COM_MultiThreadedRowOperation.h b/source/blender/compositor/intern/COM_MultiThreadedRowOperation.h new file mode 100644 index 00000000000..3daa9eec474 --- /dev/null +++ b/source/blender/compositor/intern/COM_MultiThreadedRowOperation.h @@ -0,0 +1,60 @@ +/* + * 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_MultiThreadedOperation.h" + +namespace blender::compositor { + +/** + * Executes buffer updates per row. To be inherited only by operations with correlated coordinates + * between inputs and output. + */ +class MultiThreadedRowOperation : public MultiThreadedOperation { + protected: + struct PixelCursor { + float *out; + int out_stride; + const float *row_end; + Array<const float *> ins; + Array<int> in_strides; + + public: + PixelCursor(int num_inputs); + + void next() + { + BLI_assert(out < row_end); + out += out_stride; + for (int i = 0; i < ins.size(); i++) { + ins[i] += in_strides[i]; + } + } + }; + + protected: + virtual void update_memory_buffer_row(PixelCursor &p) = 0; + + private: + void update_memory_buffer_partial(MemoryBuffer *output, + const rcti &area, + Span<MemoryBuffer *> inputs) final; +}; + +} // namespace blender::compositor diff --git a/source/blender/compositor/intern/COM_NodeGraph.cc b/source/blender/compositor/intern/COM_NodeGraph.cc index fbe56dd4b5a..205fbcc0440 100644 --- a/source/blender/compositor/intern/COM_NodeGraph.cc +++ b/source/blender/compositor/intern/COM_NodeGraph.cc @@ -174,7 +174,7 @@ void NodeGraph::add_bNodeLink(const NodeRange &node_range, bNodeLink *b_nodelink return; } - /* Note: a DNA input socket can have multiple NodeInput in the compositor tree! (proxies) + /* NOTE: a DNA input socket can have multiple NodeInput in the compositor tree! (proxies) * The output then gets linked to each one of them. */ diff --git a/source/blender/compositor/intern/COM_NodeOperation.cc b/source/blender/compositor/intern/COM_NodeOperation.cc index c52fca81b12..43710e62496 100644 --- a/source/blender/compositor/intern/COM_NodeOperation.cc +++ b/source/blender/compositor/intern/COM_NodeOperation.cc @@ -211,12 +211,12 @@ bool NodeOperation::determineDependingAreaOfInterest(rcti *input, * 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 input_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, +void NodeOperation::get_area_of_interest(const int input_idx, const rcti &output_area, rcti &r_input_area) { @@ -226,7 +226,7 @@ void NodeOperation::get_area_of_interest(const int input_op_idx, else { /* Non full-frame operations never implement this method. To ensure correctness assume * whole area is used. */ - NodeOperation *input_op = getInputOperation(input_op_idx); + NodeOperation *input_op = getInputOperation(input_idx); BLI_rcti_init(&r_input_area, 0, input_op->getWidth(), 0, input_op->getHeight()); } } @@ -241,7 +241,7 @@ void NodeOperation::get_area_of_interest(NodeOperation *input_op, return; } } - BLI_assert(!"input_op is not an input operation."); + BLI_assert_msg(0, "input_op is not an input operation."); } /** @@ -249,18 +249,16 @@ void NodeOperation::get_area_of_interest(NodeOperation *input_op, * \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) + Span<MemoryBuffer *> inputs_bufs) { if (get_flags().is_fullframe_operation) { - render_full_frame(output_buf, areas, inputs_bufs, exec_system); + render_full_frame(output_buf, areas, inputs_bufs); } else { - render_full_frame_fallback(output_buf, areas, inputs_bufs, exec_system); + render_full_frame_fallback(output_buf, areas, inputs_bufs); } } @@ -269,12 +267,11 @@ void NodeOperation::render(MemoryBuffer *output_buf, */ void NodeOperation::render_full_frame(MemoryBuffer *output_buf, Span<rcti> areas, - Span<MemoryBuffer *> inputs_bufs, - ExecutionSystem &exec_system) + Span<MemoryBuffer *> inputs_bufs) { initExecution(); for (const rcti &area : areas) { - update_memory_buffer(output_buf, area, inputs_bufs, exec_system); + update_memory_buffer(output_buf, area, inputs_bufs); } deinitExecution(); } @@ -284,8 +281,7 @@ void NodeOperation::render_full_frame(MemoryBuffer *output_buf, */ void NodeOperation::render_full_frame_fallback(MemoryBuffer *output_buf, Span<rcti> areas, - Span<MemoryBuffer *> inputs_bufs, - ExecutionSystem &exec_system) + Span<MemoryBuffer *> inputs_bufs) { Vector<NodeOperationOutput *> orig_input_links = replace_inputs_with_buffers(inputs_bufs); @@ -299,7 +295,7 @@ void NodeOperation::render_full_frame_fallback(MemoryBuffer *output_buf, } else { for (const rcti &rect : areas) { - exec_system.execute_work(rect, [=](const rcti &split_rect) { + exec_system_->execute_work(rect, [=](const rcti &split_rect) { rcti tile_rect = split_rect; if (is_output_operation) { executeRegion(&tile_rect, 0); @@ -353,6 +349,7 @@ Vector<NodeOperationOutput *> NodeOperation::replace_inputs_with_buffers( BufferOperation *buffer_op = new BufferOperation(inputs_bufs[i], input_socket->getDataType()); orig_links[i] = input_socket->getLink(); input_socket->setLink(buffer_op->getOutputSocket()); + buffer_op->initExecution(); } return orig_links; } @@ -362,8 +359,10 @@ void NodeOperation::remove_buffers_and_restore_original_inputs( { BLI_assert(original_inputs_links.size() == getNumberOfInputSockets()); for (int i = 0; i < original_inputs_links.size(); i++) { - BLI_assert(typeid(*getInputOperation(i)) == typeid(BufferOperation)); - + NodeOperation *buffer_op = get_input_operation(i); + BLI_assert(buffer_op != nullptr); + BLI_assert(typeid(*buffer_op) == typeid(BufferOperation)); + buffer_op->deinitExecution(); NodeOperationInput *input_socket = getInputSocket(i); delete &input_socket->getLink()->getOperation(); input_socket->setLink(original_inputs_links[i]); diff --git a/source/blender/compositor/intern/COM_NodeOperation.h b/source/blender/compositor/intern/COM_NodeOperation.h index 2ed509d1250..9667d82c289 100644 --- a/source/blender/compositor/intern/COM_NodeOperation.h +++ b/source/blender/compositor/intern/COM_NodeOperation.h @@ -221,7 +221,7 @@ struct NodeOperationFlags { /** * Is this a set operation (value, color, vector). - * TODO: To be replaced by is_constant flag once tiled implementation is removed. + * TODO: To be replaced by is_constant_operation flag once tiled implementation is removed. */ bool is_set_operation : 1; bool is_write_buffer_operation : 1; @@ -313,6 +313,11 @@ class NodeOperation { eExecutionModel execution_model_; /** + * Compositor execution model. + */ + eExecutionModel execution_model_; + + /** * Width of the output of this operation. */ unsigned int m_width; @@ -337,11 +342,18 @@ class NodeOperation { */ NodeOperationFlags flags; + ExecutionSystem *exec_system_; + public: virtual ~NodeOperation() { } + void set_execution_model(const eExecutionModel model) + { + execution_model_ = model; + } + void set_name(const std::string name) { m_name = name; @@ -431,6 +443,12 @@ class NodeOperation { { this->m_btree = tree; } + + void set_execution_system(ExecutionSystem *system) + { + exec_system_ = system; + } + virtual void initExecution(); /** @@ -598,25 +616,21 @@ class NodeOperation { /** \name Full Frame Methods * \{ */ - void render(MemoryBuffer *output_buf, - Span<rcti> areas, - Span<MemoryBuffer *> inputs_bufs, - ExecutionSystem &exec_system); + void render(MemoryBuffer *output_buf, Span<rcti> areas, Span<MemoryBuffer *> inputs_bufs); /** * 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)) + const rcti &UNUSED(area), + Span<MemoryBuffer *> UNUSED(inputs)) { } /** * 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); + virtual void get_area_of_interest(int input_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); /** \} */ @@ -707,13 +721,11 @@ class NodeOperation { void render_full_frame(MemoryBuffer *output_buf, Span<rcti> areas, - Span<MemoryBuffer *> inputs_bufs, - ExecutionSystem &exec_system); + Span<MemoryBuffer *> inputs_bufs); void render_full_frame_fallback(MemoryBuffer *output_buf, Span<rcti> areas, - Span<MemoryBuffer *> inputs, - ExecutionSystem &exec_system); + Span<MemoryBuffer *> inputs); 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( diff --git a/source/blender/compositor/intern/COM_NodeOperationBuilder.cc b/source/blender/compositor/intern/COM_NodeOperationBuilder.cc index c81a5a2bd98..10a91bbcd3e 100644 --- a/source/blender/compositor/intern/COM_NodeOperationBuilder.cc +++ b/source/blender/compositor/intern/COM_NodeOperationBuilder.cc @@ -36,12 +36,15 @@ #include "COM_ViewerOperation.h" #include "COM_WriteBufferOperation.h" +#include "COM_ConstantFolder.h" #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) +NodeOperationBuilder::NodeOperationBuilder(const CompositorContext *context, + bNodeTree *b_nodetree, + ExecutionSystem *system) + : m_context(context), exec_system_(system), m_current_node(nullptr), m_active_viewer(nullptr) { m_graph.from_bNodeTree(*context, b_nodetree); } @@ -79,7 +82,7 @@ void NodeOperationBuilder::convertToOperations(ExecutionSystem *system) if (!op_from || op_to_list.is_empty()) { /* XXX allow this? error/debug message? */ // BLI_assert(false); - /* XXX note: this can happen with certain nodes (e.g. OutputFile) + /* XXX NOTE: this can happen with certain nodes (e.g. OutputFile) * which only generate operations in certain circumstances (rendering) * just let this pass silently for now ... */ @@ -97,6 +100,15 @@ void NodeOperationBuilder::convertToOperations(ExecutionSystem *system) add_datatype_conversions(); + if (m_context->get_execution_model() == eExecutionModel::FullFrame) { + /* Copy operations to system. Needed for graphviz. */ + system->set_operations(m_operations, {}); + + DebugInfo::graphviz(system, "compositor_prior_folding"); + ConstantFolder folder(*this); + folder.fold_operations(); + } + determineResolutions(); if (m_context->get_execution_model() == eExecutionModel::Tiled) { @@ -129,6 +141,30 @@ void NodeOperationBuilder::addOperation(NodeOperation *operation) if (m_current_node) { operation->set_name(m_current_node->getbNode()->name); } + operation->set_execution_model(m_context->get_execution_model()); + operation->set_execution_system(exec_system_); +} + +void NodeOperationBuilder::replace_operation_with_constant(NodeOperation *operation, + ConstantOperation *constant_operation) +{ + BLI_assert(constant_operation->getNumberOfInputSockets() == 0); + int i = 0; + while (i < m_links.size()) { + Link &link = m_links[i]; + if (&link.to()->getOperation() == operation) { + link.to()->setLink(nullptr); + m_links.remove(i); + continue; + } + + if (&link.from()->getOperation() == operation) { + link.to()->setLink(constant_operation->getOutputSocket()); + m_links[i] = Link(constant_operation->getOutputSocket(), link.to()); + } + i++; + } + addOperation(constant_operation); } void NodeOperationBuilder::mapInputSocket(NodeInput *node_socket, @@ -137,7 +173,7 @@ void NodeOperationBuilder::mapInputSocket(NodeInput *node_socket, BLI_assert(m_current_node); BLI_assert(node_socket->getNode() == m_current_node); - /* note: this maps operation sockets to node sockets. + /* NOTE: this maps operation sockets to node sockets. * for resolving links the map will be inverted first in convertToOperations, * to get a list of links for each node input socket. */ @@ -283,7 +319,7 @@ void NodeOperationBuilder::add_datatype_conversions() void NodeOperationBuilder::add_operation_input_constants() { - /* Note: unconnected inputs cached first to avoid modifying + /* NOTE: unconnected inputs cached first to avoid modifying * m_operations while iterating over it */ Vector<NodeOperationInput *> pending_inputs; @@ -536,7 +572,7 @@ void NodeOperationBuilder::add_output_buffers(NodeOperation *operation, void NodeOperationBuilder::add_complex_operation_buffers() { - /* note: complex ops and get cached here first, since adding operations + /* NOTE: complex ops and get cached here first, since adding operations * will invalidate iterators over the main m_operations */ Vector<NodeOperation *> complex_ops; diff --git a/source/blender/compositor/intern/COM_NodeOperationBuilder.h b/source/blender/compositor/intern/COM_NodeOperationBuilder.h index b2fb822af25..1f76765c846 100644 --- a/source/blender/compositor/intern/COM_NodeOperationBuilder.h +++ b/source/blender/compositor/intern/COM_NodeOperationBuilder.h @@ -41,6 +41,7 @@ class NodeOperationOutput; class PreviewOperation; class WriteBufferOperation; class ViewerOperation; +class ConstantOperation; class NodeOperationBuilder { public: @@ -67,6 +68,7 @@ class NodeOperationBuilder { private: const CompositorContext *m_context; NodeGraph m_graph; + ExecutionSystem *exec_system_; Vector<NodeOperation *> m_operations; Vector<Link> m_links; @@ -86,7 +88,9 @@ class NodeOperationBuilder { ViewerOperation *m_active_viewer; public: - NodeOperationBuilder(const CompositorContext *context, bNodeTree *b_nodetree); + NodeOperationBuilder(const CompositorContext *context, + bNodeTree *b_nodetree, + ExecutionSystem *system); const CompositorContext &context() const { @@ -96,6 +100,8 @@ class NodeOperationBuilder { void convertToOperations(ExecutionSystem *system); void addOperation(NodeOperation *operation); + void replace_operation_with_constant(NodeOperation *operation, + ConstantOperation *constant_operation); /** Map input socket of the current node to an operation socket */ void mapInputSocket(NodeInput *node_socket, NodeOperationInput *operation_socket); diff --git a/source/blender/compositor/intern/COM_OpenCLDevice.cc b/source/blender/compositor/intern/COM_OpenCLDevice.cc index 0f6ed0dbd2b..3409c8fa3bc 100644 --- a/source/blender/compositor/intern/COM_OpenCLDevice.cc +++ b/source/blender/compositor/intern/COM_OpenCLDevice.cc @@ -110,7 +110,7 @@ const cl_image_format *OpenCLDevice::determineImageFormat(MemoryBuffer *memoryBu return &IMAGE_FORMAT_COLOR; break; default: - BLI_assert(!"Unsupported num_channels."); + BLI_assert_msg(0, "Unsupported num_channels."); } return &IMAGE_FORMAT_COLOR; diff --git a/source/blender/compositor/intern/COM_SharedOperationBuffers.cc b/source/blender/compositor/intern/COM_SharedOperationBuffers.cc index b2c43147025..02e4ff546ce 100644 --- a/source/blender/compositor/intern/COM_SharedOperationBuffers.cc +++ b/source/blender/compositor/intern/COM_SharedOperationBuffers.cc @@ -26,7 +26,7 @@ SharedOperationBuffers::SharedOperationBuffers() : buffers_() { } SharedOperationBuffers::BufferData::BufferData() - : buffer(nullptr), render_areas(), render_bounds({0}), registered_reads(0), received_reads(0) + : buffer(nullptr), registered_reads(0), received_reads(0), is_rendered(false) { } @@ -99,7 +99,7 @@ const rcti &SharedOperationBuffers::get_render_bounds(NodeOperation *op) */ bool SharedOperationBuffers::is_operation_rendered(NodeOperation *op) { - return get_buffer_data(op).buffer != nullptr; + return get_buffer_data(op).is_rendered; } /** @@ -112,6 +112,7 @@ void SharedOperationBuffers::set_rendered_buffer(NodeOperation *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; } /** diff --git a/source/blender/compositor/intern/COM_SharedOperationBuffers.h b/source/blender/compositor/intern/COM_SharedOperationBuffers.h index 24c80102f9c..ddef724ff4a 100644 --- a/source/blender/compositor/intern/COM_SharedOperationBuffers.h +++ b/source/blender/compositor/intern/COM_SharedOperationBuffers.h @@ -43,6 +43,7 @@ class SharedOperationBuffers { rcti render_bounds; int registered_reads; int received_reads; + bool is_rendered; } BufferData; blender::Map<NodeOperation *, BufferData> buffers_; diff --git a/source/blender/compositor/intern/COM_WorkPackage.h b/source/blender/compositor/intern/COM_WorkPackage.h index 4d503022120..f0f53f300a5 100644 --- a/source/blender/compositor/intern/COM_WorkPackage.h +++ b/source/blender/compositor/intern/COM_WorkPackage.h @@ -18,6 +18,10 @@ #pragma once +#ifdef WITH_CXX_GUARDEDALLOC +# include "MEM_guardedalloc.h" +#endif + #include "COM_Enums.h" #include "BLI_rect.h" diff --git a/source/blender/compositor/intern/COM_WorkScheduler.cc b/source/blender/compositor/intern/COM_WorkScheduler.cc index 157ded943d6..8e49bf34b51 100644 --- a/source/blender/compositor/intern/COM_WorkScheduler.cc +++ b/source/blender/compositor/intern/COM_WorkScheduler.cc @@ -126,7 +126,7 @@ static void *thread_execute_gpu(void *data) return nullptr; } -static void opencl_start(CompositorContext &context) +static void opencl_start(const CompositorContext &context) { if (context.getHasActiveOpenCLDevices()) { g_work_scheduler.opencl.queue = BLI_thread_queue_init(); @@ -458,7 +458,7 @@ void WorkScheduler::schedule(WorkPackage *package) } } -void WorkScheduler::start(CompositorContext &context) +void WorkScheduler::start(const CompositorContext &context) { if (COM_is_opencl_enabled()) { opencl_start(context); diff --git a/source/blender/compositor/intern/COM_WorkScheduler.h b/source/blender/compositor/intern/COM_WorkScheduler.h index be88859be7c..297943aa63b 100644 --- a/source/blender/compositor/intern/COM_WorkScheduler.h +++ b/source/blender/compositor/intern/COM_WorkScheduler.h @@ -65,7 +65,7 @@ struct WorkScheduler { * for every device a thread is created. * \see initialize Initialization and query of the number of devices */ - static void start(CompositorContext &context); + static void start(const CompositorContext &context); /** * \brief stop the execution diff --git a/source/blender/compositor/intern/COM_compositor.cc b/source/blender/compositor/intern/COM_compositor.cc index 5839f53976b..c05234f3bd0 100644 --- a/source/blender/compositor/intern/COM_compositor.cc +++ b/source/blender/compositor/intern/COM_compositor.cc @@ -70,7 +70,7 @@ void COM_execute(RenderData *render_data, const ColorManagedDisplaySettings *displaySettings, const char *viewName) { - /* Initialize mutex, TODO this mutex init is actually not thread safe and + /* Initialize mutex, TODO: this mutex init is actually not thread safe and * should be done somewhere as part of blender startup, all the other * initializations can be done lazily. */ if (!g_compositor.is_initialized) { |