diff options
Diffstat (limited to 'source/blender/compositor/tests/COM_BuffersIterator_test.cc')
-rw-r--r-- | source/blender/compositor/tests/COM_BuffersIterator_test.cc | 299 |
1 files changed, 299 insertions, 0 deletions
diff --git a/source/blender/compositor/tests/COM_BuffersIterator_test.cc b/source/blender/compositor/tests/COM_BuffersIterator_test.cc new file mode 100644 index 00000000000..0a288cdc5f0 --- /dev/null +++ b/source/blender/compositor/tests/COM_BuffersIterator_test.cc @@ -0,0 +1,299 @@ +/* + * 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 "testing/testing.h" + +#include "BLI_array.hh" +#include "COM_BuffersIterator.h" + +namespace blender::compositor::tests { + +constexpr int BUFFER_WIDTH = 5; +constexpr int BUFFER_HEIGHT = 4; +constexpr int BUFFER_OFFSET_X = 5; +constexpr int BUFFER_OFFSET_Y = 6; +constexpr int NUM_CHANNELS = 4; +constexpr int FULL_BUFFER_LEN = BUFFER_WIDTH * BUFFER_HEIGHT * NUM_CHANNELS; +constexpr int SINGLE_ELEM_BUFFER_LEN = NUM_CHANNELS; +constexpr int NUM_INPUTS = 2; + +static float *create_buffer(int len) +{ + return (float *)MEM_callocN(len * sizeof(float), "COM_BuffersIteratorTest"); +} + +static const float *create_input_buffer(int input_idx, bool is_a_single_elem) +{ + const int len = is_a_single_elem ? SINGLE_ELEM_BUFFER_LEN : FULL_BUFFER_LEN; + float *buf = create_buffer(len); + /* Fill buffer with variable data. */ + for (int i = 0; i < len; i++) { + buf[i] = input_idx * 1.5f * (i + 1) + i * 0.9f; + } + return buf; +} + +using IterFunc = std::function<void(BuffersIterator<float> &it, const rcti &area)>; +using ValidateElemFunc = std::function<void(float *out, Span<const float *> ins, int x, int y)>; + +class BuffersIteratorTest : public testing::Test { + private: + float *output_; + bool use_offsets_; + bool use_single_elem_inputs_; + bool use_inputs_; + + static rcti buffer_area; + static rcti buffer_offset_area; + static Array<const float *, NUM_INPUTS> single_elem_inputs; + static Array<const float *, NUM_INPUTS> full_buffer_inputs; + + public: + void set_inputs_enabled(bool value) + { + use_inputs_ = value; + } + + void test_iteration(IterFunc iter_func, ValidateElemFunc validate_elem_func = {}) + { + use_single_elem_inputs_ = false; + validate_iteration(iter_func, validate_elem_func); + if (use_inputs_) { + use_single_elem_inputs_ = true; + validate_iteration(iter_func, validate_elem_func); + } + } + + protected: + static void SetUpTestCase() + { + BLI_rcti_init(&buffer_area, 0, BUFFER_WIDTH, 0, BUFFER_HEIGHT); + BLI_rcti_init(&buffer_offset_area, + BUFFER_OFFSET_X, + BUFFER_OFFSET_X + BUFFER_WIDTH, + BUFFER_OFFSET_Y, + BUFFER_OFFSET_Y + BUFFER_HEIGHT); + for (int i = 0; i < NUM_INPUTS; i++) { + single_elem_inputs[i] = create_input_buffer(i, true); + full_buffer_inputs[i] = create_input_buffer(i, false); + } + } + + static void TearDownTestCase() + { + for (int i = 0; i < NUM_INPUTS; i++) { + MEM_freeN((void *)single_elem_inputs[i]); + single_elem_inputs[i] = nullptr; + MEM_freeN((void *)full_buffer_inputs[i]); + full_buffer_inputs[i] = nullptr; + } + } + + void SetUp() override + { + use_offsets_ = false; + use_single_elem_inputs_ = false; + use_inputs_ = false; + output_ = create_buffer(FULL_BUFFER_LEN); + } + + void TearDown() override + { + MEM_freeN(output_); + } + + private: + void validate_iteration(IterFunc iter_func, ValidateElemFunc validate_elem_func) + { + { + use_offsets_ = false; + BuffersIterator<float> it = iterate(); + iter_func(it, buffer_area); + validate_result(buffer_area, validate_elem_func); + } + { + use_offsets_ = true; + BuffersIterator<float> it = offset_iterate(buffer_offset_area); + iter_func(it, buffer_offset_area); + validate_result(buffer_offset_area, validate_elem_func); + } + { + use_offsets_ = true; + rcti area = buffer_offset_area; + area.xmin += 1; + area.ymin += 1; + area.xmax -= 1; + area.ymax -= 1; + BuffersIterator<float> it = offset_iterate(area); + iter_func(it, area); + validate_result(area, validate_elem_func); + } + } + + void validate_result(rcti &area, ValidateElemFunc validate_elem_func) + { + Span<const float *> inputs = get_inputs(); + Array<const float *> ins(inputs.size()); + for (int y = area.ymin; y < area.ymax; y++) { + for (int x = area.xmin; x < area.xmax; x++) { + const int out_offset = get_buffer_relative_y(y) * BUFFER_WIDTH * NUM_CHANNELS + + get_buffer_relative_x(x) * NUM_CHANNELS; + float *out = &output_[out_offset]; + + const int in_offset = use_single_elem_inputs_ ? 0 : out_offset; + for (int i = 0; i < inputs.size(); i++) { + ins[i] = &inputs[i][in_offset]; + } + + if (validate_elem_func) { + validate_elem_func(out, ins, x, y); + } + } + } + } + + Span<const float *> get_inputs() + { + if (use_inputs_) { + return use_single_elem_inputs_ ? single_elem_inputs : full_buffer_inputs; + } + return {}; + } + + int get_buffer_relative_x(int x) + { + return use_offsets_ ? x - BUFFER_OFFSET_X : x; + } + int get_buffer_relative_y(int y) + { + return use_offsets_ ? y - BUFFER_OFFSET_Y : y; + } + + /** Iterates whole buffers with no offsets. */ + BuffersIterator<float> iterate() + { + BLI_assert(!use_offsets_); + BuffersIteratorBuilder<float> builder(output_, BUFFER_WIDTH, BUFFER_HEIGHT, NUM_CHANNELS); + if (use_inputs_) { + const int input_stride = use_single_elem_inputs_ ? 0 : NUM_CHANNELS; + for (const float *input : get_inputs()) { + builder.add_input(input, BUFFER_WIDTH, input_stride); + } + } + return builder.build(); + } + + /** Iterates a given buffers area with default offsets. */ + BuffersIterator<float> offset_iterate(const rcti &area) + { + BLI_assert(use_offsets_); + const rcti &buf_area = buffer_offset_area; + BuffersIteratorBuilder<float> builder(output_, buf_area, area, NUM_CHANNELS); + if (use_inputs_) { + const int input_stride = use_single_elem_inputs_ ? 0 : NUM_CHANNELS; + for (const float *input : get_inputs()) { + builder.add_input(input, buf_area, input_stride); + } + } + return builder.build(); + } +}; + +rcti BuffersIteratorTest::buffer_area; +rcti BuffersIteratorTest::buffer_offset_area; +Array<const float *, NUM_INPUTS> BuffersIteratorTest::single_elem_inputs(NUM_INPUTS); +Array<const float *, NUM_INPUTS> BuffersIteratorTest::full_buffer_inputs(NUM_INPUTS); + +static void iterate_coordinates(BuffersIterator<float> &it, const rcti &area) +{ + int x = area.xmin; + int y = area.ymin; + for (; !it.is_end(); ++it) { + EXPECT_EQ(x, it.x); + EXPECT_EQ(y, it.y); + x++; + if (x == area.xmax) { + x = area.xmin; + y++; + } + } + EXPECT_EQ(x, area.xmin); + EXPECT_EQ(y, area.ymax); +} + +TEST_F(BuffersIteratorTest, CoordinatesIterationWithNoInputs) +{ + set_inputs_enabled(false); + test_iteration(iterate_coordinates); +} + +TEST_F(BuffersIteratorTest, CoordinatesIterationWithInputs) +{ + set_inputs_enabled(true); + test_iteration(iterate_coordinates); +} + +TEST_F(BuffersIteratorTest, OutputIteration) +{ + set_inputs_enabled(false); + test_iteration( + [](BuffersIterator<float> &it, const rcti &UNUSED(area)) { + EXPECT_EQ(it.get_num_inputs(), 0); + for (; !it.is_end(); ++it) { + const int dummy = it.y * BUFFER_WIDTH + it.x; + it.out[0] = dummy + 1.0f; + it.out[1] = dummy + 2.0f; + it.out[2] = dummy + 3.0f; + it.out[3] = dummy + 4.0f; + } + }, + [](float *out, Span<const float *> UNUSED(ins), const int x, const int y) { + const int dummy = y * BUFFER_WIDTH + x; + EXPECT_NEAR(out[0], dummy + 1.0f, FLT_EPSILON); + EXPECT_NEAR(out[1], dummy + 2.0f, FLT_EPSILON); + EXPECT_NEAR(out[2], dummy + 3.0f, FLT_EPSILON); + EXPECT_NEAR(out[3], dummy + 4.0f, FLT_EPSILON); + }); +} + +TEST_F(BuffersIteratorTest, OutputAndInputsIteration) +{ + set_inputs_enabled(true); + test_iteration( + [](BuffersIterator<float> &it, const rcti &UNUSED(area)) { + EXPECT_EQ(it.get_num_inputs(), NUM_INPUTS); + for (; !it.is_end(); ++it) { + const float *in1 = it.in(0); + const float *in2 = it.in(1); + it.out[0] = in1[0] + in2[0]; + it.out[1] = in1[1] + in2[3]; + it.out[2] = in1[2] - in2[2]; + it.out[3] = in1[3] - in2[1]; + } + }, + [](float *out, Span<const float *> ins, const int UNUSED(x), const int UNUSED(y)) { + const float *in1 = ins[0]; + const float *in2 = ins[1]; + EXPECT_NEAR(out[0], in1[0] + in2[0], FLT_EPSILON); + EXPECT_NEAR(out[1], in1[1] + in2[3], FLT_EPSILON); + EXPECT_NEAR(out[2], in1[2] - in2[2], FLT_EPSILON); + EXPECT_NEAR(out[3], in1[3] - in2[1], FLT_EPSILON); + }); +} + +} // namespace blender::compositor::tests |