/* * 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 &it, const rcti &area)>; using ValidateElemFunc = std::function 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 single_elem_inputs; static Array 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 it = iterate(); iter_func(it, buffer_area); validate_result(buffer_area, validate_elem_func); } { use_offsets_ = true; BuffersIterator 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 it = offset_iterate(area); iter_func(it, area); validate_result(area, validate_elem_func); } } void validate_result(rcti &area, ValidateElemFunc validate_elem_func) { Span inputs = get_inputs(); Array 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 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 iterate() { BLI_assert(!use_offsets_); BuffersIteratorBuilder 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 offset_iterate(const rcti &area) { BLI_assert(use_offsets_); const rcti &buf_area = buffer_offset_area; BuffersIteratorBuilder 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 BuffersIteratorTest::single_elem_inputs(NUM_INPUTS); Array BuffersIteratorTest::full_buffer_inputs(NUM_INPUTS); static void iterate_coordinates(BuffersIterator &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 &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 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 &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 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