diff options
Diffstat (limited to 'source/blender/compositor')
21 files changed, 1535 insertions, 14 deletions
diff --git a/source/blender/compositor/CMakeLists.txt b/source/blender/compositor/CMakeLists.txt index 20b56ceb55f..830792a2a48 100644 --- a/source/blender/compositor/CMakeLists.txt +++ b/source/blender/compositor/CMakeLists.txt @@ -49,8 +49,11 @@ set(SRC COM_compositor.h COM_defines.h + intern/COM_BufferArea.h intern/COM_BufferOperation.cc intern/COM_BufferOperation.h + intern/COM_BufferRange.h + intern/COM_BuffersIterator.h intern/COM_CPUDevice.cc intern/COM_CPUDevice.h intern/COM_ChunkOrder.cc diff --git a/source/blender/compositor/COM_defines.h b/source/blender/compositor/COM_defines.h index 9f8e6f10215..0e97bafbb0b 100644 --- a/source/blender/compositor/COM_defines.h +++ b/source/blender/compositor/COM_defines.h @@ -18,6 +18,9 @@ #pragma once +#include "BLI_index_range.hh" +#include "BLI_rect.h" + namespace blender::compositor { enum class eExecutionModel { @@ -109,4 +112,24 @@ constexpr float COM_PREVIEW_SIZE = 140.f; constexpr float COM_RULE_OF_THIRDS_DIVIDER = 100.0f; constexpr float COM_BLUR_BOKEH_PIXELS = 512; +constexpr IndexRange XRange(const rcti &area) +{ + return IndexRange(area.xmin, area.xmax - area.xmin); +} + +constexpr IndexRange YRange(const rcti &area) +{ + return IndexRange(area.ymin, area.ymax - area.ymin); +} + +constexpr IndexRange XRange(const rcti *area) +{ + return XRange(*area); +} + +constexpr IndexRange YRange(const rcti *area) +{ + return YRange(*area); +} + } // namespace blender::compositor diff --git a/source/blender/compositor/intern/COM_BufferArea.h b/source/blender/compositor/intern/COM_BufferArea.h new file mode 100644 index 00000000000..621ffea5bc3 --- /dev/null +++ b/source/blender/compositor/intern/COM_BufferArea.h @@ -0,0 +1,198 @@ +/* + * 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 <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 + { + if (elem_stride_ == 0) { + /* Iterate a single element. */ + return TIterator(buffer_, 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, buffer_width_, BLI_rcti_size_x(this), elem_stride_); + } + + template<typename TIterator> constexpr TIterator end_iterator() const + { + if (elem_stride_ == 0) { + /* Iterate a single element. */ + return TIterator(buffer_ + 1, 1, 1, 1); + } + + T *end_ptr = buffer_ + (intptr_t)(this->ymax - 1) * buffer_width_ * elem_stride_ + + (intptr_t)this->xmax * elem_stride_; + return TIterator(end_ptr, buffer_width_, BLI_rcti_size_x(this), 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_; + + public: + constexpr BufferAreaIterator() = default; + + constexpr BufferAreaIterator(T *current, 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) + { + } + + constexpr BufferAreaIterator &operator++() + { + current_ += elem_stride_; + if (current_ == row_end_) { + 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_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..58dbd9c6f59 --- /dev/null +++ b/source/blender/compositor/intern/COM_BuffersIterator.h @@ -0,0 +1,164 @@ +/* + * 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 { + const T *out_end_; + const T *out_row_end_; + int out_elem_stride_; + int out_row_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: + /** + * 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; + } + if (out == out_row_end_) { + out += out_rows_gap_; + out_row_end_ += out_row_stride_; + 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_width: Number of elements in an output buffer row. + * \param area: Rectangle area to be iterated in all buffers. + * \param elem_stride: Output buffer element stride. + */ + BuffersIteratorBuilder(T *output, int buffer_width, const rcti &area, int elem_stride = 1) + : area_(area), is_built_(false) + { + iterator_.out_elem_stride_ = elem_stride; + iterator_.out_row_stride_ = buffer_width * elem_stride; + iterator_.out_rows_gap_ = iterator_.out_row_stride_ - BLI_rcti_size_x(&area) * elem_stride; + iterator_.out = output + (intptr_t)area.ymin * iterator_.out_row_stride_ + + (intptr_t)area.xmin * elem_stride; + iterator_.out_row_end_ = iterator_.out + (intptr_t)BLI_rcti_size_x(&area) * elem_stride; + iterator_.out_end_ = iterator_.out_row_end_ + + (intptr_t)iterator_.out_row_stride_ * (BLI_rcti_size_y(&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, buffer_width, {0, buffer_width, 0, buffer_height}, elem_stride) + { + } + + /** + * Add an input buffer to be iterated. Its coordinates must be correlated with the output. + */ + void add_input(const T *input, int buffer_width, int elem_stride = 1) + { + BLI_assert(!is_built_); + typename Iterator::In in; + in.elem_stride = elem_stride; + in.rows_gap = buffer_width * elem_stride - BLI_rcti_size_x(&area_) * elem_stride; + in.in = input + area_.ymin * buffer_width * elem_stride + area_.xmin * elem_stride; + iterator_.ins_.append(std::move(in)); + } + + /** + * 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_Debug.cc b/source/blender/compositor/intern/COM_Debug.cc index abef4517b3e..c0460aed4a4 100644 --- a/source/blender/compositor/intern/COM_Debug.cc +++ b/source/blender/compositor/intern/COM_Debug.cc @@ -31,6 +31,8 @@ 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" @@ -50,7 +52,7 @@ 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(NodeOperation *op) +static std::string operation_class_name(const NodeOperation *op) { std::string full_name = typeid(*op).name(); /* Remove name-spaces. */ @@ -452,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 53461e13f48..23d99c7e529 100644 --- a/source/blender/compositor/intern/COM_Debug.h +++ b/source/blender/compositor/intern/COM_Debug.h @@ -30,6 +30,9 @@ 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; @@ -75,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) @@ -118,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: @@ -133,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_FullFrameExecutionModel.cc b/source/blender/compositor/intern/COM_FullFrameExecutionModel.cc index 3b0a9172871..9f6904bb306 100644 --- a/source/blender/compositor/intern/COM_FullFrameExecutionModel.cc +++ b/source/blender/compositor/intern/COM_FullFrameExecutionModel.cc @@ -104,6 +104,7 @@ void FullFrameExecutionModel::render_operation(NodeOperation *op) op->render(op_buf, areas, input_bufs); active_buffers_.set_rendered_buffer(op, std::unique_ptr<MemoryBuffer>(op_buf)); + DebugInfo::operation_rendered(op, op_buf); operation_finished(op); } diff --git a/source/blender/compositor/intern/COM_MemoryBuffer.cc b/source/blender/compositor/intern/COM_MemoryBuffer.cc index c7bddddd0e6..f4f58146de4 100644 --- a/source/blender/compositor/intern/COM_MemoryBuffer.cc +++ b/source/blender/compositor/intern/COM_MemoryBuffer.cc @@ -129,6 +129,20 @@ 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, getWidth(), area, elem_stride); + for (MemoryBuffer *input : inputs) { + builder.add_input(input->getBuffer(), input->getWidth(), input->elem_stride); + } + return builder.build(); +} + /** * Converts a single elem buffer to a full size buffer (allocates memory for all * elements in resolution). diff --git a/source/blender/compositor/intern/COM_MemoryBuffer.h b/source/blender/compositor/intern/COM_MemoryBuffer.h index 4ad0872b0b7..048ed4c5d6e 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" @@ -239,6 +242,32 @@ class MemoryBuffer { } /** + * 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 */ diff --git a/source/blender/compositor/operations/COM_BrightnessOperation.cc b/source/blender/compositor/operations/COM_BrightnessOperation.cc index 92cab47318a..7878eca2bbd 100644 --- a/source/blender/compositor/operations/COM_BrightnessOperation.cc +++ b/source/blender/compositor/operations/COM_BrightnessOperation.cc @@ -28,6 +28,7 @@ BrightnessOperation::BrightnessOperation() this->addOutputSocket(DataType::Color); this->m_inputProgram = nullptr; this->m_use_premultiply = false; + flags.can_be_constant = true; } void BrightnessOperation::setUsePremultiply(bool use_premultiply) @@ -85,6 +86,50 @@ void BrightnessOperation::executePixelSampled(float output[4], } } +void BrightnessOperation::update_memory_buffer_partial(MemoryBuffer *output, + const rcti &area, + Span<MemoryBuffer *> inputs) +{ + float tmp_color[4]; + for (BuffersIterator<float> it = output->iterate_with(inputs, area); !it.is_end(); ++it) { + const float *in_color = it.in(0); + const float brightness = *it.in(1) / 100.0f; + const float contrast = *it.in(2); + float delta = contrast / 200.0f; + /* + * The algorithm is by Werner D. Streidt + * (http://visca.com/ffactory/archives/5-99/msg00021.html) + * Extracted of OpenCV demhist.c + */ + float a, b; + if (contrast > 0) { + a = 1.0f - delta * 2.0f; + a = 1.0f / max_ff(a, FLT_EPSILON); + b = a * (brightness - delta); + } + else { + delta *= -1; + a = max_ff(1.0f - delta * 2.0f, 0.0f); + b = a * brightness + delta; + } + const float *color; + if (this->m_use_premultiply) { + premul_to_straight_v4_v4(tmp_color, in_color); + color = tmp_color; + } + else { + color = in_color; + } + it.out[0] = a * color[0] + b; + it.out[1] = a * color[1] + b; + it.out[2] = a * color[2] + b; + it.out[3] = color[3]; + if (this->m_use_premultiply) { + straight_to_premul_v4(it.out); + } + } +} + void BrightnessOperation::deinitExecution() { this->m_inputProgram = nullptr; diff --git a/source/blender/compositor/operations/COM_BrightnessOperation.h b/source/blender/compositor/operations/COM_BrightnessOperation.h index 7c33e0b35ec..64b4fa0dbe2 100644 --- a/source/blender/compositor/operations/COM_BrightnessOperation.h +++ b/source/blender/compositor/operations/COM_BrightnessOperation.h @@ -18,11 +18,11 @@ #pragma once -#include "COM_NodeOperation.h" +#include "COM_MultiThreadedOperation.h" namespace blender::compositor { -class BrightnessOperation : public NodeOperation { +class BrightnessOperation : public MultiThreadedOperation { private: /** * Cached reference to the inputProgram @@ -52,6 +52,10 @@ class BrightnessOperation : public NodeOperation { void deinitExecution() override; void setUsePremultiply(bool use_premultiply); + + void update_memory_buffer_partial(MemoryBuffer *output, + const rcti &area, + Span<MemoryBuffer *> inputs) override; }; } // namespace blender::compositor diff --git a/source/blender/compositor/operations/COM_DoubleEdgeMaskOperation.cc b/source/blender/compositor/operations/COM_DoubleEdgeMaskOperation.cc index c4099a6d33d..1a0d7e52b0f 100644 --- a/source/blender/compositor/operations/COM_DoubleEdgeMaskOperation.cc +++ b/source/blender/compositor/operations/COM_DoubleEdgeMaskOperation.cc @@ -1318,6 +1318,7 @@ DoubleEdgeMaskOperation::DoubleEdgeMaskOperation() this->m_adjacentOnly = false; this->m_keepInside = false; this->flags.complex = true; + is_output_rendered_ = false; } bool DoubleEdgeMaskOperation::determineDependingAreaOfInterest(rcti * /*input*/, @@ -1382,4 +1383,43 @@ void DoubleEdgeMaskOperation::deinitExecution() } } +void DoubleEdgeMaskOperation::get_area_of_interest(int UNUSED(input_idx), + const rcti &UNUSED(output_area), + rcti &r_input_area) +{ + r_input_area.xmax = this->getWidth(); + r_input_area.xmin = 0; + r_input_area.ymax = this->getHeight(); + r_input_area.ymin = 0; +} + +void DoubleEdgeMaskOperation::update_memory_buffer(MemoryBuffer *output, + const rcti &UNUSED(area), + Span<MemoryBuffer *> inputs) +{ + if (!is_output_rendered_) { + /* Ensure full buffers to work with no strides. */ + MemoryBuffer *input_inner_mask = inputs[0]; + MemoryBuffer *inner_mask = input_inner_mask->is_a_single_elem() ? input_inner_mask->inflate() : + input_inner_mask; + MemoryBuffer *input_outer_mask = inputs[1]; + MemoryBuffer *outer_mask = input_outer_mask->is_a_single_elem() ? input_outer_mask->inflate() : + input_outer_mask; + + BLI_assert(output->getWidth() == this->getWidth()); + BLI_assert(output->getHeight() == this->getHeight()); + /* TODO(manzanilla): Once tiled implementation is removed, use execution system to run + * multi-threaded where possible. */ + doDoubleEdgeMask(inner_mask->getBuffer(), outer_mask->getBuffer(), output->getBuffer()); + is_output_rendered_ = true; + + if (inner_mask != input_inner_mask) { + delete inner_mask; + } + if (outer_mask != input_outer_mask) { + delete outer_mask; + } + } +} + } // namespace blender::compositor diff --git a/source/blender/compositor/operations/COM_DoubleEdgeMaskOperation.h b/source/blender/compositor/operations/COM_DoubleEdgeMaskOperation.h index e956e8edc3e..45a80bbbbf0 100644 --- a/source/blender/compositor/operations/COM_DoubleEdgeMaskOperation.h +++ b/source/blender/compositor/operations/COM_DoubleEdgeMaskOperation.h @@ -18,7 +18,7 @@ #pragma once -#include "COM_NodeOperation.h" +#include "COM_MultiThreadedOperation.h" namespace blender::compositor { @@ -31,8 +31,12 @@ class DoubleEdgeMaskOperation : public NodeOperation { SocketReader *m_inputInnerMask; bool m_adjacentOnly; bool m_keepInside; + + /* TODO(manzanilla): To be removed with tiled implementation. */ float *m_cachedInstance; + bool is_output_rendered_; + public: DoubleEdgeMaskOperation(); @@ -66,6 +70,12 @@ class DoubleEdgeMaskOperation : public NodeOperation { { this->m_keepInside = keepInside; } + + void get_area_of_interest(int input_idx, const rcti &output_area, rcti &r_input_area) override; + + void update_memory_buffer(MemoryBuffer *output, + const rcti &area, + Span<MemoryBuffer *> inputs) override; }; } // namespace blender::compositor diff --git a/source/blender/compositor/operations/COM_EllipseMaskOperation.cc b/source/blender/compositor/operations/COM_EllipseMaskOperation.cc index 5a4503fecec..eb1fd98a590 100644 --- a/source/blender/compositor/operations/COM_EllipseMaskOperation.cc +++ b/source/blender/compositor/operations/COM_EllipseMaskOperation.cc @@ -20,6 +20,8 @@ #include "BLI_math.h" #include "DNA_node_types.h" +#include <functional> + namespace blender::compositor { EllipseMaskOperation::EllipseMaskOperation() @@ -114,6 +116,77 @@ void EllipseMaskOperation::executePixelSampled(float output[4], } } +void EllipseMaskOperation::update_memory_buffer_partial(MemoryBuffer *output, + const rcti &area, + Span<MemoryBuffer *> inputs) +{ + MaskFunc mask_func; + switch (m_maskType) { + case CMP_NODE_MASKTYPE_ADD: + mask_func = [](const bool is_inside, const float *mask, const float *value) { + return is_inside ? MAX2(mask[0], value[0]) : mask[0]; + }; + break; + case CMP_NODE_MASKTYPE_SUBTRACT: + mask_func = [](const bool is_inside, const float *mask, const float *value) { + return is_inside ? CLAMPIS(mask[0] - value[0], 0, 1) : mask[0]; + }; + break; + case CMP_NODE_MASKTYPE_MULTIPLY: + mask_func = [](const bool is_inside, const float *mask, const float *value) { + return is_inside ? mask[0] * value[0] : 0; + }; + break; + case CMP_NODE_MASKTYPE_NOT: + mask_func = [](const bool is_inside, const float *mask, const float *value) { + if (is_inside) { + return mask[0] > 0.0f ? 0.0f : value[0]; + } + return mask[0]; + }; + break; + } + apply_mask(output, area, inputs, mask_func); +} + +void EllipseMaskOperation::apply_mask(MemoryBuffer *output, + const rcti &area, + Span<MemoryBuffer *> inputs, + MaskFunc mask_func) +{ + const MemoryBuffer *input_mask = inputs[0]; + const MemoryBuffer *input_value = inputs[1]; + const float op_w = this->getWidth(); + const float op_h = this->getHeight(); + const float half_w = this->m_data->width / 2.0f; + const float half_h = this->m_data->height / 2.0f; + const float tx = half_w * half_w; + const float ty = half_h * half_h; + for (const int y : YRange(area)) { + const float op_ry = y / op_h; + const float dy = (op_ry - this->m_data->y) / m_aspectRatio; + float *out = output->get_elem(area.xmin, y); + const float *mask = input_mask->get_elem(area.xmin, y); + const float *value = input_value->get_elem(area.xmin, y); + for (const int x : XRange(area)) { + const float op_rx = x / op_w; + const float dx = op_rx - this->m_data->x; + const float rx = this->m_data->x + (m_cosine * dx + m_sine * dy); + const float ry = this->m_data->y + (-m_sine * dx + m_cosine * dy); + float sx = rx - this->m_data->x; + sx *= sx; + float sy = ry - this->m_data->y; + sy *= sy; + const bool inside = ((sx / tx) + (sy / ty)) < 1.0f; + out[0] = mask_func(inside, mask, value); + + mask += input_mask->elem_stride; + value += input_value->elem_stride; + out += output->elem_stride; + } + } +} + void EllipseMaskOperation::deinitExecution() { this->m_inputMask = nullptr; diff --git a/source/blender/compositor/operations/COM_EllipseMaskOperation.h b/source/blender/compositor/operations/COM_EllipseMaskOperation.h index 64afe0145cf..fba3f979d26 100644 --- a/source/blender/compositor/operations/COM_EllipseMaskOperation.h +++ b/source/blender/compositor/operations/COM_EllipseMaskOperation.h @@ -18,12 +18,14 @@ #pragma once -#include "COM_NodeOperation.h" +#include "COM_MultiThreadedOperation.h" namespace blender::compositor { -class EllipseMaskOperation : public NodeOperation { +class EllipseMaskOperation : public MultiThreadedOperation { private: + using MaskFunc = std::function<float(bool is_inside, const float *mask, const float *value)>; + /** * Cached reference to the inputProgram */ @@ -64,6 +66,16 @@ class EllipseMaskOperation : public NodeOperation { { this->m_maskType = maskType; } + + void update_memory_buffer_partial(MemoryBuffer *output, + const rcti &area, + Span<MemoryBuffer *> inputs) override; + + private: + void apply_mask(MemoryBuffer *output, + const rcti &area, + Span<MemoryBuffer *> inputs, + MaskFunc mask_func); }; } // namespace blender::compositor diff --git a/source/blender/compositor/operations/COM_MixOperation.cc b/source/blender/compositor/operations/COM_MixOperation.cc index 58fa09fa2a8..77ecbf60356 100644 --- a/source/blender/compositor/operations/COM_MixOperation.cc +++ b/source/blender/compositor/operations/COM_MixOperation.cc @@ -35,6 +35,7 @@ MixBaseOperation::MixBaseOperation() this->m_inputColor2Operation = nullptr; this->setUseValueAlphaMultiply(false); this->setUseClamp(false); + flags.can_be_constant = true; } void MixBaseOperation::initExecution() @@ -97,6 +98,45 @@ void MixBaseOperation::deinitExecution() this->m_inputColor2Operation = nullptr; } +void MixBaseOperation::update_memory_buffer_partial(MemoryBuffer *output, + const rcti &area, + Span<MemoryBuffer *> inputs) +{ + const MemoryBuffer *input_value = inputs[0]; + const MemoryBuffer *input_color1 = inputs[1]; + const MemoryBuffer *input_color2 = inputs[2]; + const int width = BLI_rcti_size_x(&area); + PixelCursor p; + p.out_stride = output->elem_stride; + p.value_stride = input_value->elem_stride; + p.color1_stride = input_color1->elem_stride; + p.color2_stride = input_color2->elem_stride; + for (const int y : YRange(area)) { + p.out = output->get_elem(area.xmin, y); + p.row_end = p.out + width * output->elem_stride; + p.value = input_value->get_elem(area.xmin, y); + p.color1 = input_color1->get_elem(area.xmin, y); + p.color2 = input_color2->get_elem(area.xmin, y); + update_memory_buffer_row(p); + } +} + +void MixBaseOperation::update_memory_buffer_row(PixelCursor &p) +{ + while (p.out < p.row_end) { + float value = p.value[0]; + if (this->useValueAlphaMultiply()) { + value *= p.color2[3]; + } + const float value_m = 1.0f - value; + p.out[0] = value_m * p.color1[0] + value * p.color2[0]; + p.out[1] = value_m * p.color1[1] + value * p.color2[1]; + p.out[2] = value_m * p.color1[2] + value * p.color2[2]; + p.out[3] = p.color1[3]; + p.next(); + } +} + /* ******** Mix Add Operation ******** */ void MixAddOperation::executePixelSampled(float output[4], float x, float y, PixelSampler sampler) @@ -121,6 +161,23 @@ void MixAddOperation::executePixelSampled(float output[4], float x, float y, Pix clampIfNeeded(output); } +void MixAddOperation::update_memory_buffer_row(PixelCursor &p) +{ + while (p.out < p.row_end) { + float value = p.value[0]; + if (this->useValueAlphaMultiply()) { + value *= p.color2[3]; + } + p.out[0] = p.color1[0] + value * p.color2[0]; + p.out[1] = p.color1[1] + value * p.color2[1]; + p.out[2] = p.color1[2] + value * p.color2[2]; + p.out[3] = p.color1[3]; + + clampIfNeeded(p.out); + p.next(); + } +} + /* ******** Mix Blend Operation ******** */ void MixBlendOperation::executePixelSampled(float output[4], @@ -150,6 +207,24 @@ void MixBlendOperation::executePixelSampled(float output[4], clampIfNeeded(output); } +void MixBlendOperation::update_memory_buffer_row(PixelCursor &p) +{ + while (p.out < p.row_end) { + float value = p.value[0]; + if (this->useValueAlphaMultiply()) { + value *= p.color2[3]; + } + float value_m = 1.0f - value; + p.out[0] = value_m * p.color1[0] + value * p.color2[0]; + p.out[1] = value_m * p.color1[1] + value * p.color2[1]; + p.out[2] = value_m * p.color1[2] + value * p.color2[2]; + p.out[3] = p.color1[3]; + + clampIfNeeded(p.out); + p.next(); + } +} + /* ******** Mix Burn Operation ******** */ void MixColorBurnOperation::executePixelSampled(float output[4], @@ -228,6 +303,48 @@ void MixColorBurnOperation::executePixelSampled(float output[4], clampIfNeeded(output); } +void MixColorBurnOperation::update_memory_buffer_row(PixelCursor &p) +{ + while (p.out < p.row_end) { + float value = p.value[0]; + if (this->useValueAlphaMultiply()) { + value *= p.color2[3]; + } + const float value_m = 1.0f - value; + + float tmp = value_m + value * p.color2[0]; + if (tmp <= 0.0f) { + p.out[0] = 0.0f; + } + else { + tmp = 1.0f - (1.0f - p.color1[0]) / tmp; + p.out[0] = CLAMPIS(tmp, 0.0f, 1.0f); + } + + tmp = value_m + value * p.color2[1]; + if (tmp <= 0.0f) { + p.out[1] = 0.0f; + } + else { + tmp = 1.0f - (1.0f - p.color1[1]) / tmp; + p.out[1] = CLAMPIS(tmp, 0.0f, 1.0f); + } + + tmp = value_m + value * p.color2[2]; + if (tmp <= 0.0f) { + p.out[2] = 0.0f; + } + else { + tmp = 1.0f - (1.0f - p.color1[2]) / tmp; + p.out[2] = CLAMPIS(tmp, 0.0f, 1.0f); + } + p.out[3] = p.color1[3]; + + clampIfNeeded(p.out); + p.next(); + } +} + /* ******** Mix Color Operation ******** */ void MixColorOperation::executePixelSampled(float output[4], @@ -268,6 +385,36 @@ void MixColorOperation::executePixelSampled(float output[4], clampIfNeeded(output); } +void MixColorOperation::update_memory_buffer_row(PixelCursor &p) +{ + while (p.out < p.row_end) { + float value = p.value[0]; + if (this->useValueAlphaMultiply()) { + value *= p.color2[3]; + } + const float value_m = 1.0f - value; + + float colH, colS, colV; + rgb_to_hsv(p.color2[0], p.color2[1], p.color2[2], &colH, &colS, &colV); + if (colS != 0.0f) { + float rH, rS, rV; + float tmpr, tmpg, tmpb; + rgb_to_hsv(p.color1[0], p.color1[1], p.color1[2], &rH, &rS, &rV); + hsv_to_rgb(colH, colS, rV, &tmpr, &tmpg, &tmpb); + p.out[0] = (value_m * p.color1[0]) + (value * tmpr); + p.out[1] = (value_m * p.color1[1]) + (value * tmpg); + p.out[2] = (value_m * p.color1[2]) + (value * tmpb); + } + else { + copy_v3_v3(p.out, p.color1); + } + p.out[3] = p.color1[3]; + + clampIfNeeded(p.out); + p.next(); + } +} + /* ******** Mix Darken Operation ******** */ void MixDarkenOperation::executePixelSampled(float output[4], @@ -296,6 +443,24 @@ void MixDarkenOperation::executePixelSampled(float output[4], clampIfNeeded(output); } +void MixDarkenOperation::update_memory_buffer_row(PixelCursor &p) +{ + while (p.out < p.row_end) { + float value = p.value[0]; + if (this->useValueAlphaMultiply()) { + value *= p.color2[3]; + } + float value_m = 1.0f - value; + p.out[0] = min_ff(p.color1[0], p.color2[0]) * value + p.color1[0] * value_m; + p.out[1] = min_ff(p.color1[1], p.color2[1]) * value + p.color1[1] * value_m; + p.out[2] = min_ff(p.color1[2], p.color2[2]) * value + p.color1[2] * value_m; + p.out[3] = p.color1[3]; + + clampIfNeeded(p.out); + p.next(); + } +} + /* ******** Mix Difference Operation ******** */ void MixDifferenceOperation::executePixelSampled(float output[4], @@ -324,6 +489,24 @@ void MixDifferenceOperation::executePixelSampled(float output[4], clampIfNeeded(output); } +void MixDifferenceOperation::update_memory_buffer_row(PixelCursor &p) +{ + while (p.out < p.row_end) { + float value = p.value[0]; + if (this->useValueAlphaMultiply()) { + value *= p.color2[3]; + } + const float value_m = 1.0f - value; + p.out[0] = value_m * p.color1[0] + value * fabsf(p.color1[0] - p.color2[0]); + p.out[1] = value_m * p.color1[1] + value * fabsf(p.color1[1] - p.color2[1]); + p.out[2] = value_m * p.color1[2] + value * fabsf(p.color1[2] - p.color2[2]); + p.out[3] = p.color1[3]; + + clampIfNeeded(p.out); + p.next(); + } +} + /* ******** Mix Difference Operation ******** */ void MixDivideOperation::executePixelSampled(float output[4], @@ -369,6 +552,41 @@ void MixDivideOperation::executePixelSampled(float output[4], clampIfNeeded(output); } +void MixDivideOperation::update_memory_buffer_row(PixelCursor &p) +{ + while (p.out < p.row_end) { + float value = p.value[0]; + if (this->useValueAlphaMultiply()) { + value *= p.color2[3]; + } + const float value_m = 1.0f - value; + + if (p.color2[0] != 0.0f) { + p.out[0] = value_m * (p.color1[0]) + value * (p.color1[0]) / p.color2[0]; + } + else { + p.out[0] = 0.0f; + } + if (p.color2[1] != 0.0f) { + p.out[1] = value_m * (p.color1[1]) + value * (p.color1[1]) / p.color2[1]; + } + else { + p.out[1] = 0.0f; + } + if (p.color2[2] != 0.0f) { + p.out[2] = value_m * (p.color1[2]) + value * (p.color1[2]) / p.color2[2]; + } + else { + p.out[2] = 0.0f; + } + + p.out[3] = p.color1[3]; + + clampIfNeeded(p.out); + p.next(); + } +} + /* ******** Mix Dodge Operation ******** */ void MixDodgeOperation::executePixelSampled(float output[4], @@ -452,6 +670,64 @@ void MixDodgeOperation::executePixelSampled(float output[4], clampIfNeeded(output); } +void MixDodgeOperation::update_memory_buffer_row(PixelCursor &p) +{ + while (p.out < p.row_end) { + float value = p.value[0]; + if (this->useValueAlphaMultiply()) { + value *= p.color2[3]; + } + + float tmp; + if (p.color1[0] != 0.0f) { + tmp = 1.0f - value * p.color2[0]; + if (tmp <= 0.0f) { + p.out[0] = 1.0f; + } + else { + p.out[0] = p.color1[0] / tmp; + CLAMP_MAX(p.out[0], 1.0f); + } + } + else { + p.out[0] = 0.0f; + } + + if (p.color1[1] != 0.0f) { + tmp = 1.0f - value * p.color2[1]; + if (tmp <= 0.0f) { + p.out[1] = 1.0f; + } + else { + p.out[1] = p.color1[1] / tmp; + CLAMP_MAX(p.out[1], 1.0f); + } + } + else { + p.out[1] = 0.0f; + } + + if (p.color1[2] != 0.0f) { + tmp = 1.0f - value * p.color2[2]; + if (tmp <= 0.0f) { + p.out[2] = 1.0f; + } + else { + p.out[2] = p.color1[2] / tmp; + CLAMP_MAX(p.out[2], 1.0f); + } + } + else { + p.out[2] = 0.0f; + } + + p.out[3] = p.color1[3]; + + clampIfNeeded(p.out); + p.next(); + } +} + /* ******** Mix Glare Operation ******** */ void MixGlareOperation::executePixelSampled(float output[4], @@ -487,6 +763,33 @@ void MixGlareOperation::executePixelSampled(float output[4], clampIfNeeded(output); } +void MixGlareOperation::update_memory_buffer_row(PixelCursor &p) +{ + while (p.out < p.row_end) { + const float value = p.value[0]; + /* Linear interpolation between 3 cases: + * value=-1:output=input value=0:output=input+glare value=1:output=glare + */ + float input_weight; + float glare_weight; + if (value < 0.0f) { + input_weight = 1.0f; + glare_weight = 1.0f + value; + } + else { + input_weight = 1.0f - value; + glare_weight = 1.0f; + } + p.out[0] = input_weight * MAX2(p.color1[0], 0.0f) + glare_weight * p.color2[0]; + p.out[1] = input_weight * MAX2(p.color1[1], 0.0f) + glare_weight * p.color2[1]; + p.out[2] = input_weight * MAX2(p.color1[2], 0.0f) + glare_weight * p.color2[2]; + p.out[3] = p.color1[3]; + + clampIfNeeded(p.out); + p.next(); + } +} + /* ******** Mix Hue Operation ******** */ void MixHueOperation::executePixelSampled(float output[4], float x, float y, PixelSampler sampler) @@ -524,6 +827,36 @@ void MixHueOperation::executePixelSampled(float output[4], float x, float y, Pix clampIfNeeded(output); } +void MixHueOperation::update_memory_buffer_row(PixelCursor &p) +{ + while (p.out < p.row_end) { + float value = p.value[0]; + if (this->useValueAlphaMultiply()) { + value *= p.color2[3]; + } + const float value_m = 1.0f - value; + + float colH, colS, colV; + rgb_to_hsv(p.color2[0], p.color2[1], p.color2[2], &colH, &colS, &colV); + if (colS != 0.0f) { + float rH, rS, rV; + float tmpr, tmpg, tmpb; + rgb_to_hsv(p.color1[0], p.color1[1], p.color1[2], &rH, &rS, &rV); + hsv_to_rgb(colH, rS, rV, &tmpr, &tmpg, &tmpb); + p.out[0] = value_m * p.color1[0] + value * tmpr; + p.out[1] = value_m * p.color1[1] + value * tmpg; + p.out[2] = value_m * p.color1[2] + value * tmpb; + } + else { + copy_v3_v3(p.out, p.color1); + } + p.out[3] = p.color1[3]; + + clampIfNeeded(p.out); + p.next(); + } +} + /* ******** Mix Lighten Operation ******** */ void MixLightenOperation::executePixelSampled(float output[4], @@ -570,6 +903,30 @@ void MixLightenOperation::executePixelSampled(float output[4], clampIfNeeded(output); } +void MixLightenOperation::update_memory_buffer_row(PixelCursor &p) +{ + while (p.out < p.row_end) { + float value = p.value[0]; + if (this->useValueAlphaMultiply()) { + value *= p.color2[3]; + } + + float tmp = value * p.color2[0]; + p.out[0] = MAX2(tmp, p.color1[0]); + + tmp = value * p.color2[1]; + p.out[1] = MAX2(tmp, p.color1[1]); + + tmp = value * p.color2[2]; + p.out[2] = MAX2(tmp, p.color1[2]); + + p.out[3] = p.color1[3]; + + clampIfNeeded(p.out); + p.next(); + } +} + /* ******** Mix Linear Light Operation ******** */ void MixLinearLightOperation::executePixelSampled(float output[4], @@ -613,6 +970,39 @@ void MixLinearLightOperation::executePixelSampled(float output[4], clampIfNeeded(output); } +void MixLinearLightOperation::update_memory_buffer_row(PixelCursor &p) +{ + while (p.out < p.row_end) { + float value = p.value[0]; + if (this->useValueAlphaMultiply()) { + value *= p.color2[3]; + } + if (p.color2[0] > 0.5f) { + p.out[0] = p.color1[0] + value * (2.0f * (p.color2[0] - 0.5f)); + } + else { + p.out[0] = p.color1[0] + value * (2.0f * (p.color2[0]) - 1.0f); + } + if (p.color2[1] > 0.5f) { + p.out[1] = p.color1[1] + value * (2.0f * (p.color2[1] - 0.5f)); + } + else { + p.out[1] = p.color1[1] + value * (2.0f * (p.color2[1]) - 1.0f); + } + if (p.color2[2] > 0.5f) { + p.out[2] = p.color1[2] + value * (2.0f * (p.color2[2] - 0.5f)); + } + else { + p.out[2] = p.color1[2] + value * (2.0f * (p.color2[2]) - 1.0f); + } + + p.out[3] = p.color1[3]; + + clampIfNeeded(p.out); + p.next(); + } +} + /* ******** Mix Multiply Operation ******** */ void MixMultiplyOperation::executePixelSampled(float output[4], @@ -641,6 +1031,25 @@ void MixMultiplyOperation::executePixelSampled(float output[4], clampIfNeeded(output); } +void MixMultiplyOperation::update_memory_buffer_row(PixelCursor &p) +{ + while (p.out < p.row_end) { + float value = p.value[0]; + if (this->useValueAlphaMultiply()) { + value *= p.color2[3]; + } + const float value_m = 1.0f - value; + p.out[0] = p.color1[0] * (value_m + value * p.color2[0]); + p.out[1] = p.color1[1] * (value_m + value * p.color2[1]); + p.out[2] = p.color1[2] * (value_m + value * p.color2[2]); + + p.out[3] = p.color1[3]; + + clampIfNeeded(p.out); + p.next(); + } +} + /* ******** Mix Overlay Operation ******** */ void MixOverlayOperation::executePixelSampled(float output[4], @@ -686,6 +1095,40 @@ void MixOverlayOperation::executePixelSampled(float output[4], clampIfNeeded(output); } +void MixOverlayOperation::update_memory_buffer_row(PixelCursor &p) +{ + while (p.out < p.row_end) { + float value = p.value[0]; + if (this->useValueAlphaMultiply()) { + value *= p.color2[3]; + } + const float value_m = 1.0f - value; + if (p.color1[0] < 0.5f) { + p.out[0] = p.color1[0] * (value_m + 2.0f * value * p.color2[0]); + } + else { + p.out[0] = 1.0f - (value_m + 2.0f * value * (1.0f - p.color2[0])) * (1.0f - p.color1[0]); + } + if (p.color1[1] < 0.5f) { + p.out[1] = p.color1[1] * (value_m + 2.0f * value * p.color2[1]); + } + else { + p.out[1] = 1.0f - (value_m + 2.0f * value * (1.0f - p.color2[1])) * (1.0f - p.color1[1]); + } + if (p.color1[2] < 0.5f) { + p.out[2] = p.color1[2] * (value_m + 2.0f * value * p.color2[2]); + } + else { + p.out[2] = 1.0f - (value_m + 2.0f * value * (1.0f - p.color2[2])) * (1.0f - p.color1[2]); + } + + p.out[3] = p.color1[3]; + + clampIfNeeded(p.out); + p.next(); + } +} + /* ******** Mix Saturation Operation ******** */ void MixSaturationOperation::executePixelSampled(float output[4], @@ -723,6 +1166,33 @@ void MixSaturationOperation::executePixelSampled(float output[4], clampIfNeeded(output); } +void MixSaturationOperation::update_memory_buffer_row(PixelCursor &p) +{ + while (p.out < p.row_end) { + float value = p.value[0]; + if (this->useValueAlphaMultiply()) { + value *= p.color2[3]; + } + const float value_m = 1.0f - value; + + float rH, rS, rV; + rgb_to_hsv(p.color1[0], p.color1[1], p.color1[2], &rH, &rS, &rV); + if (rS != 0.0f) { + float colH, colS, colV; + rgb_to_hsv(p.color2[0], p.color2[1], p.color2[2], &colH, &colS, &colV); + hsv_to_rgb(rH, (value_m * rS + value * colS), rV, &p.out[0], &p.out[1], &p.out[2]); + } + else { + copy_v3_v3(p.out, p.color1); + } + + p.out[3] = p.color1[3]; + + clampIfNeeded(p.out); + p.next(); + } +} + /* ******** Mix Screen Operation ******** */ void MixScreenOperation::executePixelSampled(float output[4], @@ -752,6 +1222,25 @@ void MixScreenOperation::executePixelSampled(float output[4], clampIfNeeded(output); } +void MixScreenOperation::update_memory_buffer_row(PixelCursor &p) +{ + while (p.out < p.row_end) { + float value = p.value[0]; + if (this->useValueAlphaMultiply()) { + value *= p.color2[3]; + } + const float value_m = 1.0f - value; + + p.out[0] = 1.0f - (value_m + value * (1.0f - p.color2[0])) * (1.0f - p.color1[0]); + p.out[1] = 1.0f - (value_m + value * (1.0f - p.color2[1])) * (1.0f - p.color1[1]); + p.out[2] = 1.0f - (value_m + value * (1.0f - p.color2[2])) * (1.0f - p.color1[2]); + p.out[3] = p.color1[3]; + + clampIfNeeded(p.out); + p.next(); + } +} + /* ******** Mix Soft Light Operation ******** */ void MixSoftLightOperation::executePixelSampled(float output[4], @@ -793,6 +1282,34 @@ void MixSoftLightOperation::executePixelSampled(float output[4], clampIfNeeded(output); } +void MixSoftLightOperation::update_memory_buffer_row(PixelCursor &p) +{ + while (p.out < p.row_end) { + float value = p.value[0]; + if (this->useValueAlphaMultiply()) { + value *= p.color2[3]; + } + const float value_m = 1.0f - value; + float scr, scg, scb; + + /* First calculate non-fac based Screen mix. */ + scr = 1.0f - (1.0f - p.color2[0]) * (1.0f - p.color1[0]); + scg = 1.0f - (1.0f - p.color2[1]) * (1.0f - p.color1[1]); + scb = 1.0f - (1.0f - p.color2[2]) * (1.0f - p.color1[2]); + + p.out[0] = value_m * p.color1[0] + + value * ((1.0f - p.color1[0]) * p.color2[0] * p.color1[0] + p.color1[0] * scr); + p.out[1] = value_m * p.color1[1] + + value * ((1.0f - p.color1[1]) * p.color2[1] * p.color1[1] + p.color1[1] * scg); + p.out[2] = value_m * p.color1[2] + + value * ((1.0f - p.color1[2]) * p.color2[2] * p.color1[2] + p.color1[2] * scb); + p.out[3] = p.color1[3]; + + clampIfNeeded(p.out); + p.next(); + } +} + /* ******** Mix Subtract Operation ******** */ void MixSubtractOperation::executePixelSampled(float output[4], @@ -820,6 +1337,23 @@ void MixSubtractOperation::executePixelSampled(float output[4], clampIfNeeded(output); } +void MixSubtractOperation::update_memory_buffer_row(PixelCursor &p) +{ + while (p.out < p.row_end) { + float value = p.value[0]; + if (this->useValueAlphaMultiply()) { + value *= p.color2[3]; + } + p.out[0] = p.color1[0] - value * p.color2[0]; + p.out[1] = p.color1[1] - value * p.color2[1]; + p.out[2] = p.color1[2] - value * p.color2[2]; + p.out[3] = p.color1[3]; + + clampIfNeeded(p.out); + p.next(); + } +} + /* ******** Mix Value Operation ******** */ void MixValueOperation::executePixelSampled(float output[4], @@ -851,4 +1385,25 @@ void MixValueOperation::executePixelSampled(float output[4], clampIfNeeded(output); } +void MixValueOperation::update_memory_buffer_row(PixelCursor &p) +{ + while (p.out < p.row_end) { + float value = p.value[0]; + if (this->useValueAlphaMultiply()) { + value *= p.color2[3]; + } + float value_m = 1.0f - value; + + float rH, rS, rV; + float colH, colS, colV; + rgb_to_hsv(p.color1[0], p.color1[1], p.color1[2], &rH, &rS, &rV); + rgb_to_hsv(p.color2[0], p.color2[1], p.color2[2], &colH, &colS, &colV); + hsv_to_rgb(rH, rS, (value_m * rV + value * colV), &p.out[0], &p.out[1], &p.out[2]); + p.out[3] = p.color1[3]; + + clampIfNeeded(p.out); + p.next(); + } +} + } // namespace blender::compositor diff --git a/source/blender/compositor/operations/COM_MixOperation.h b/source/blender/compositor/operations/COM_MixOperation.h index 6c241bc5762..7ef9d78d58f 100644 --- a/source/blender/compositor/operations/COM_MixOperation.h +++ b/source/blender/compositor/operations/COM_MixOperation.h @@ -18,7 +18,7 @@ #pragma once -#include "COM_NodeOperation.h" +#include "COM_MultiThreadedOperation.h" namespace blender::compositor { @@ -27,8 +27,29 @@ namespace blender::compositor { * it assumes we are in sRGB color space. */ -class MixBaseOperation : public NodeOperation { +class MixBaseOperation : public MultiThreadedOperation { protected: + struct PixelCursor { + float *out; + const float *row_end; + const float *value; + const float *color1; + const float *color2; + int out_stride; + int value_stride; + int color1_stride; + int color2_stride; + + void next() + { + BLI_assert(out < row_end); + out += out_stride; + value += value_stride; + color1 += color1_stride; + color2 += color2_stride; + } + }; + /** * Prefetched reference to the inputProgram */ @@ -81,101 +102,165 @@ class MixBaseOperation : public NodeOperation { { this->m_useClamp = value; } + + void update_memory_buffer_partial(MemoryBuffer *output, + const rcti &area, + Span<MemoryBuffer *> inputs) final; + + protected: + virtual void update_memory_buffer_row(PixelCursor &p); }; class MixAddOperation : public MixBaseOperation { public: void executePixelSampled(float output[4], float x, float y, PixelSampler sampler) override; + + protected: + void update_memory_buffer_row(PixelCursor &p) override; }; class MixBlendOperation : public MixBaseOperation { public: void executePixelSampled(float output[4], float x, float y, PixelSampler sampler) override; + + protected: + void update_memory_buffer_row(PixelCursor &p) override; }; class MixColorBurnOperation : public MixBaseOperation { public: void executePixelSampled(float output[4], float x, float y, PixelSampler sampler) override; + + protected: + void update_memory_buffer_row(PixelCursor &p) override; }; class MixColorOperation : public MixBaseOperation { public: void executePixelSampled(float output[4], float x, float y, PixelSampler sampler) override; + + protected: + void update_memory_buffer_row(PixelCursor &p) override; }; class MixDarkenOperation : public MixBaseOperation { public: void executePixelSampled(float output[4], float x, float y, PixelSampler sampler) override; + + protected: + void update_memory_buffer_row(PixelCursor &p) override; }; class MixDifferenceOperation : public MixBaseOperation { public: void executePixelSampled(float output[4], float x, float y, PixelSampler sampler) override; + + protected: + void update_memory_buffer_row(PixelCursor &p) override; }; class MixDivideOperation : public MixBaseOperation { public: void executePixelSampled(float output[4], float x, float y, PixelSampler sampler) override; + + protected: + void update_memory_buffer_row(PixelCursor &p) override; }; class MixDodgeOperation : public MixBaseOperation { public: void executePixelSampled(float output[4], float x, float y, PixelSampler sampler) override; + + protected: + void update_memory_buffer_row(PixelCursor &p) override; }; class MixGlareOperation : public MixBaseOperation { public: void executePixelSampled(float output[4], float x, float y, PixelSampler sampler) override; + + protected: + void update_memory_buffer_row(PixelCursor &p) override; }; class MixHueOperation : public MixBaseOperation { public: void executePixelSampled(float output[4], float x, float y, PixelSampler sampler) override; + + protected: + void update_memory_buffer_row(PixelCursor &p) override; }; class MixLightenOperation : public MixBaseOperation { public: void executePixelSampled(float output[4], float x, float y, PixelSampler sampler) override; + + protected: + void update_memory_buffer_row(PixelCursor &p) override; }; class MixLinearLightOperation : public MixBaseOperation { public: void executePixelSampled(float output[4], float x, float y, PixelSampler sampler) override; + + protected: + void update_memory_buffer_row(PixelCursor &p) override; }; class MixMultiplyOperation : public MixBaseOperation { public: void executePixelSampled(float output[4], float x, float y, PixelSampler sampler) override; + + protected: + void update_memory_buffer_row(PixelCursor &p) override; }; class MixOverlayOperation : public MixBaseOperation { public: void executePixelSampled(float output[4], float x, float y, PixelSampler sampler) override; + + protected: + void update_memory_buffer_row(PixelCursor &p) override; }; class MixSaturationOperation : public MixBaseOperation { public: void executePixelSampled(float output[4], float x, float y, PixelSampler sampler) override; + + protected: + void update_memory_buffer_row(PixelCursor &p) override; }; class MixScreenOperation : public MixBaseOperation { public: void executePixelSampled(float output[4], float x, float y, PixelSampler sampler) override; + + protected: + void update_memory_buffer_row(PixelCursor &p) override; }; class MixSoftLightOperation : public MixBaseOperation { public: void executePixelSampled(float output[4], float x, float y, PixelSampler sampler) override; + + protected: + void update_memory_buffer_row(PixelCursor &p) override; }; class MixSubtractOperation : public MixBaseOperation { public: void executePixelSampled(float output[4], float x, float y, PixelSampler sampler) override; + + protected: + void update_memory_buffer_row(PixelCursor &p) override; }; class MixValueOperation : public MixBaseOperation { public: void executePixelSampled(float output[4], float x, float y, PixelSampler sampler) override; + + protected: + void update_memory_buffer_row(PixelCursor &p) override; }; } // namespace blender::compositor diff --git a/source/blender/compositor/operations/COM_SMAAOperation.cc b/source/blender/compositor/operations/COM_SMAAOperation.cc index 3c753591ced..b078d85372d 100644 --- a/source/blender/compositor/operations/COM_SMAAOperation.cc +++ b/source/blender/compositor/operations/COM_SMAAOperation.cc @@ -19,9 +19,9 @@ */ #include "COM_SMAAOperation.h" +#include "BKE_node.h" #include "BLI_math.h" #include "COM_SMAAAreaTexture.h" -#include "BKE_node.h" extern "C" { #include "IMB_colormanagement.h" diff --git a/source/blender/compositor/operations/COM_ViewerOperation.cc b/source/blender/compositor/operations/COM_ViewerOperation.cc index 860f56e23fa..e47396e14a1 100644 --- a/source/blender/compositor/operations/COM_ViewerOperation.cc +++ b/source/blender/compositor/operations/COM_ViewerOperation.cc @@ -191,10 +191,11 @@ void ViewerOperation::initImage() BLI_thread_unlock(LOCK_DRAW_IMAGE); } -void ViewerOperation::updateImage(rcti *rect) +void ViewerOperation::updateImage(const rcti *rect) { + float *buffer = m_outputBuffer; IMB_partial_display_buffer_update(this->m_ibuf, - this->m_outputBuffer, + buffer, nullptr, getWidth(), 0, @@ -218,4 +219,31 @@ eCompositorPriority ViewerOperation::getRenderPriority() const return eCompositorPriority::Low; } +void ViewerOperation::update_memory_buffer_partial(MemoryBuffer *UNUSED(output), + const rcti &area, + Span<MemoryBuffer *> inputs) +{ + if (!m_outputBuffer) { + return; + } + + MemoryBuffer output_buffer( + m_outputBuffer, COM_DATA_TYPE_COLOR_CHANNELS, getWidth(), getHeight()); + const MemoryBuffer *input_image = inputs[0]; + output_buffer.copy_from(input_image, area); + if (this->m_useAlphaInput) { + const MemoryBuffer *input_alpha = inputs[1]; + output_buffer.copy_from(input_alpha, area, 0, COM_DATA_TYPE_VALUE_CHANNELS, 3); + } + + if (m_depthBuffer) { + MemoryBuffer depth_buffer( + m_depthBuffer, COM_DATA_TYPE_VALUE_CHANNELS, getWidth(), getHeight()); + const MemoryBuffer *input_depth = inputs[2]; + depth_buffer.copy_from(input_depth, area); + } + + updateImage(&area); +} + } // namespace blender::compositor diff --git a/source/blender/compositor/operations/COM_ViewerOperation.h b/source/blender/compositor/operations/COM_ViewerOperation.h index c0f13ff79fc..ed05a7a282d 100644 --- a/source/blender/compositor/operations/COM_ViewerOperation.h +++ b/source/blender/compositor/operations/COM_ViewerOperation.h @@ -20,15 +20,17 @@ #include "BKE_global.h" #include "BLI_rect.h" -#include "COM_NodeOperation.h" +#include "COM_MultiThreadedOperation.h" #include "DNA_image_types.h" namespace blender::compositor { -class ViewerOperation : public NodeOperation { +class ViewerOperation : public MultiThreadedOperation { private: + /* TODO(manzanilla): To be removed together with tiled implementation. */ float *m_outputBuffer; float *m_depthBuffer; + Image *m_image; ImageUser *m_imageUser; bool m_active; @@ -125,8 +127,12 @@ class ViewerOperation : public NodeOperation { this->m_displaySettings = displaySettings; } + void update_memory_buffer_partial(MemoryBuffer *output, + const rcti &area, + Span<MemoryBuffer *> inputs) override; + private: - void updateImage(rcti *rect); + void updateImage(const rcti *rect); void initImage(); }; |