From 549e2b7539826ec1d4532b9fd07118ace82195ae Mon Sep 17 00:00:00 2001 From: Manuel Castilla Date: Fri, 30 Jul 2021 20:17:50 +0200 Subject: Compositor: Buffer iterators tests See D11882 for a description of the iterators. Reviewed By: jbakker Differential Revision: https://developer.blender.org/D12001 --- source/blender/compositor/CMakeLists.txt | 16 ++ .../compositor/tests/COM_BufferArea_test.cc | 141 ++++++++++ .../compositor/tests/COM_BufferRange_test.cc | 98 +++++++ .../compositor/tests/COM_BuffersIterator_test.cc | 299 +++++++++++++++++++++ 4 files changed, 554 insertions(+) create mode 100644 source/blender/compositor/tests/COM_BufferArea_test.cc create mode 100644 source/blender/compositor/tests/COM_BufferRange_test.cc create mode 100644 source/blender/compositor/tests/COM_BuffersIterator_test.cc (limited to 'source/blender/compositor') diff --git a/source/blender/compositor/CMakeLists.txt b/source/blender/compositor/CMakeLists.txt index 830792a2a48..000ba298c2d 100644 --- a/source/blender/compositor/CMakeLists.txt +++ b/source/blender/compositor/CMakeLists.txt @@ -635,3 +635,19 @@ if(CXX_WARN_NO_SUGGEST_OVERRIDE) endif() add_dependencies(bf_compositor smaa_areatex_header) + +if(WITH_GTESTS) + set(TEST_SRC + tests/COM_BufferArea_test.cc + tests/COM_BufferRange_test.cc + tests/COM_BuffersIterator_test.cc + ) + set(TEST_INC + ) + set(TEST_LIB + bf_compositor + ) + include(GTestTesting) + blender_add_test_lib(bf_compositor_tests "${TEST_SRC}" "${INC};${TEST_INC}" "${INC_SYS}" "${LIB};${TEST_LIB}") +endif() + diff --git a/source/blender/compositor/tests/COM_BufferArea_test.cc b/source/blender/compositor/tests/COM_BufferArea_test.cc new file mode 100644 index 00000000000..8dad0b0fea2 --- /dev/null +++ b/source/blender/compositor/tests/COM_BufferArea_test.cc @@ -0,0 +1,141 @@ +/* + * 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 "COM_BufferArea.h" + +namespace blender::compositor::tests { + +static rcti create_rect(int width, int height) +{ + rcti rect; + BLI_rcti_init(&rect, 0, width, 0, height); + return rect; +} + +static rcti create_rect(int width, int height, int offset) +{ + rcti rect; + BLI_rcti_init(&rect, offset, offset + width, offset, offset + height); + return rect; +} + +TEST(BufferArea, BufferConstructor) +{ + const int width = 2; + const int height = 3; + BufferArea area(nullptr, width, height, 4); + EXPECT_EQ(area.width(), width); + EXPECT_EQ(area.height(), height); + rcti rect = create_rect(width, height); + EXPECT_TRUE(BLI_rcti_compare(&area.get_rect(), &rect)); +} + +TEST(BufferArea, AreaConstructor) +{ + const int buf_width = 5; + const int area_width = 1; + const int area_height = 3; + rcti area_rect = create_rect(area_width, area_height, 1); + BufferArea area(nullptr, buf_width, area_rect, 4); + EXPECT_EQ(area.width(), area_width); + EXPECT_EQ(area.height(), area_height); + EXPECT_TRUE(BLI_rcti_compare(&area.get_rect(), &area_rect)); +} + +static void fill_buffer_with_indexes(float *buf, int buf_len) +{ + for (int i = 0; i < buf_len; i++) { + buf[i] = i; + } +} + +static void test_single_elem_iteration(float *buffer, BufferArea area) +{ + int elems_count = 0; + for (float *elem : area) { + EXPECT_EQ(elem, buffer); + elems_count++; + } + EXPECT_EQ(elems_count, 1); +} + +static void test_full_buffer_iteration( + float *buf, int buf_width, int buf_len, int num_channels, BufferArea area) +{ + fill_buffer_with_indexes(buf, buf_len); + rcti rect = area.get_rect(); + int x = rect.xmin; + int y = rect.ymin; + for (float *elem : area) { + for (int ch = 0; ch < num_channels; ch++) { + const int buf_index = y * buf_width * num_channels + x * num_channels + ch; + EXPECT_NEAR(elem[ch], buf_index, FLT_EPSILON); + } + x++; + if (x == rect.xmax) { + y++; + x = rect.xmin; + } + } + EXPECT_EQ(x, rect.xmin); + EXPECT_EQ(y, rect.ymax); +} + +TEST(BufferArea, SingleElemBufferIteration) +{ + const int buf_width = 4; + const int buf_height = 5; + const int area_width = 2; + const int area_height = 3; + const int num_channels = 4; + const int stride = 0; + float buf[num_channels]; + { + BufferArea area(buf, buf_width, buf_height, stride); + test_single_elem_iteration(buf, area); + } + { + rcti area_rect = create_rect(area_width, area_height, 1); + BufferArea area(buf, buf_width, area_rect, stride); + test_single_elem_iteration(buf, area); + } +} + +TEST(BufferArea, FullBufferIteration) +{ + const int buf_width = 4; + const int area_width = 2; + const int area_height = 3; + const int buf_height = (area_height + 1); + const int num_channels = 4; + const int buf_len = buf_height * buf_width * num_channels; + float buf[buf_len]; + { + BufferArea area(buf, buf_width, buf_height, num_channels); + test_full_buffer_iteration(buf, buf_width, buf_len, num_channels, area); + } + { + rcti area_rect = create_rect(area_width, area_height, 1); + BufferArea area(buf, buf_width, area_rect, num_channels); + test_full_buffer_iteration(buf, buf_width, buf_len, num_channels, area); + } +} + +} // namespace blender::compositor::tests diff --git a/source/blender/compositor/tests/COM_BufferRange_test.cc b/source/blender/compositor/tests/COM_BufferRange_test.cc new file mode 100644 index 00000000000..9ebeb181fa6 --- /dev/null +++ b/source/blender/compositor/tests/COM_BufferRange_test.cc @@ -0,0 +1,98 @@ +/* + * 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 "COM_BufferRange.h" + +namespace blender::compositor::tests { + +TEST(BufferRange, Constructor) +{ + const int size = 5; + BufferRange range(nullptr, 1, size, 4); + EXPECT_EQ(range.size(), size); +} + +static void fill_buffer_with_indexes(float *buf, int buf_len) +{ + for (int i = 0; i < buf_len; i++) { + buf[i] = i; + } +} + +TEST(BufferRange, Subscript) +{ + const int start = 2; + const int size = 4; + const int num_channels = 3; + const int buf_len = (start + size) * num_channels; + float buf[buf_len]; + + BufferRange range(buf, start, size, num_channels); + + fill_buffer_with_indexes(buf, buf_len); + int buf_index = start * num_channels; + for (int i = 0; i < size; i++) { + const float *elem = range[i]; + for (int ch = 0; ch < num_channels; ch++) { + EXPECT_NEAR(elem[ch], buf_index, FLT_EPSILON); + buf_index++; + } + } + EXPECT_EQ(buf_index, buf_len); +} + +TEST(BufferRange, SingleElemBufferIteration) +{ + const int start = 1; + const int size = 3; + const int num_channels = 4; + float buf[num_channels]; + const int stride = 0; + BufferRange range(buf, start, size, stride); + + int elems_count = 0; + for (float *elem : range) { + EXPECT_EQ(elem, buf); + elems_count++; + } + EXPECT_EQ(elems_count, 1); +} + +TEST(BufferRange, FullBufferIteration) +{ + const int start = 2; + const int size = 5; + const int num_channels = 4; + const int buf_len = (start + size) * num_channels; + float buf[buf_len]; + BufferRange range(buf, start, size, num_channels); + + fill_buffer_with_indexes(buf, buf_len); + int buf_index = start * num_channels; + for (float *elem : range) { + for (int ch = 0; ch < num_channels; ch++) { + EXPECT_NEAR(elem[ch], buf_index, FLT_EPSILON); + buf_index++; + } + } + EXPECT_EQ(buf_index, buf_len); +} + +} // namespace blender::compositor::tests 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 &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 -- cgit v1.2.3