Welcome to mirror list, hosted at ThFree Co, Russian Federation.

git.blender.org/blender.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
path: root/source
diff options
context:
space:
mode:
Diffstat (limited to 'source')
-rw-r--r--source/blender/imbuf/CMakeLists.txt14
-rw-r--r--source/blender/imbuf/IMB_rasterizer.hh710
-rw-r--r--source/blender/imbuf/intern/rasterizer_blending.hh32
-rw-r--r--source/blender/imbuf/intern/rasterizer_clamping.hh82
-rw-r--r--source/blender/imbuf/intern/rasterizer_stats.hh89
-rw-r--r--source/blender/imbuf/intern/rasterizer_target.hh74
-rw-r--r--source/blender/imbuf/intern/rasterizer_test.cc429
m---------source/tools0
8 files changed, 1430 insertions, 0 deletions
diff --git a/source/blender/imbuf/CMakeLists.txt b/source/blender/imbuf/CMakeLists.txt
index c97eead0159..ef996eb2288 100644
--- a/source/blender/imbuf/CMakeLists.txt
+++ b/source/blender/imbuf/CMakeLists.txt
@@ -190,3 +190,17 @@ set_source_files_properties(
)
blender_add_lib(bf_imbuf "${SRC}" "${INC}" "${INC_SYS}" "${LIB}")
+
+
+if(WITH_GTESTS)
+ set(TEST_SRC
+ intern/rasterizer_test.cc
+ )
+ set(TEST_INC
+ )
+ set(TEST_LIB
+ )
+ include(GTestTesting)
+ blender_add_test_lib(bf_imbuf_tests "${TEST_SRC}" "${INC};${TEST_INC}" "${INC_SYS}" "${LIB};${TEST_LIB}")
+endif()
+
diff --git a/source/blender/imbuf/IMB_rasterizer.hh b/source/blender/imbuf/IMB_rasterizer.hh
new file mode 100644
index 00000000000..09e0e54e52e
--- /dev/null
+++ b/source/blender/imbuf/IMB_rasterizer.hh
@@ -0,0 +1,710 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later
+ * Copyright 2022 Blender Foundation. All rights reserved. */
+
+/** \file
+ * \ingroup imbuf
+ *
+ * Rasterizer to render triangles onto an image buffer.
+ *
+ * The implementation is template based and follows a (very limited) OpenGL pipeline.
+ *
+ * ## Basic usage
+ *
+ * In order to use it you have to define the data structure for a single vertex.
+ *
+ * \code{.cc}
+ * struct VertexInput {
+ * float2 uv;
+ * };
+ * \endcode
+ *
+ * A vertex shader is required to transfer the vertices to actual coordinates in the image buffer.
+ * The vertex shader will store vertex specific data in a VertexOutInterface.
+ *
+ * \code{.cc}
+ * class MyVertexShader : public AbstractVertexShader<VertexInput, float> {
+ * public:
+ * float4x4 mat;
+ * void vertex(const VertexInputType &input, VertexOutputType *r_output) override
+ * {
+ * float2 coord = float2(mat * float3(input.uv[0], input.uv[1], 0.0));
+ * r_output->coord = coord * image_size;
+ * r_output->data = 1.0;
+ * }
+ * };
+ * \endcode
+ *
+ * A fragment shader is required to actually generate the pixel that will be stored in the buffer.
+ *
+ * \code{.cc}
+ * class FragmentShader : public AbstractFragmentShader<float, float4> {
+ * public:
+ * void fragment(const FragmentInputType &input, FragmentOutputType *r_output) override
+ * {
+ * *r_output = float4(input, input, input, 1.0);
+ * }
+ * };
+ * \endcode
+ *
+ * Create a rasterizer with the vertex and fragment shader and start drawing.
+ * It is required to call flush to make sure that all triangles are drawn to the image buffer.
+ *
+ * \code{.cc}
+ * Rasterizer<MyVertexShader, MyFragmentShader> rasterizer(&image_buffer);
+ * rasterizer.activate_drawing_target(&image_buffer);
+ * rasterizer.get_vertex_shader().mat = float4x4::identity();
+ * rasterizer.draw_triangle(
+ * VertexInput{float2(0.0, 1.0)},
+ * VertexInput{float2(1.0, 1.0)},
+ * VertexInput{float2(1.0, 0.0)},
+ * );
+ * rasterizer.flush();
+ * \endcode
+ */
+
+#pragma once
+
+#include "BLI_math.h"
+#include "BLI_math_vec_types.hh"
+#include "BLI_vector.hh"
+
+#include "IMB_imbuf.h"
+#include "IMB_imbuf_types.h"
+
+#include "intern/rasterizer_blending.hh"
+#include "intern/rasterizer_clamping.hh"
+#include "intern/rasterizer_stats.hh"
+#include "intern/rasterizer_target.hh"
+
+#include <optional>
+
+// #define DEBUG_PRINT
+
+namespace blender::imbuf::rasterizer {
+
+/** The default number of rasterlines to buffer before flushing to the image buffer. */
+constexpr int64_t DefaultRasterlinesBufferSize = 4096;
+
+/**
+ * Interface data of the vertex stage.
+ */
+template<
+ /**
+ * Data type per vertex generated by the vertex shader and transferred to the fragment shader.
+ *
+ * The data type should implement the +=, +, =, -, / and * operator.
+ */
+ typename Inner>
+class VertexOutInterface {
+ public:
+ using InnerType = Inner;
+ using Self = VertexOutInterface<InnerType>;
+ /** Coordinate of a vertex inside the image buffer. (0..image_buffer.x, 0..image_buffer.y). */
+ float2 coord;
+ InnerType data;
+
+ Self &operator+=(const Self &other)
+ {
+ coord += other.coord;
+ data += other.data;
+ return *this;
+ }
+
+ Self &operator=(const Self &other)
+ {
+ coord = other.coord;
+ data = other.data;
+ return *this;
+ }
+
+ Self operator-(const Self &other) const
+ {
+ Self result;
+ result.coord = coord - other.coord;
+ result.data = data - other.data;
+ return result;
+ }
+
+ Self operator+(const Self &other) const
+ {
+ Self result;
+ result.coord = coord + other.coord;
+ result.data = data + other.data;
+ return result;
+ }
+
+ Self operator/(const float divider) const
+ {
+ Self result;
+ result.coord = coord / divider;
+ result.data = data / divider;
+ return result;
+ }
+
+ Self operator*(const float multiplier) const
+ {
+ Self result;
+ result.coord = coord * multiplier;
+ result.data = data * multiplier;
+ return result;
+ }
+};
+
+/**
+ * Vertex shader
+ */
+template<typename VertexInput, typename VertexOutput> class AbstractVertexShader {
+ public:
+ using VertexInputType = VertexInput;
+ using VertexOutputType = VertexOutInterface<VertexOutput>;
+
+ virtual void vertex(const VertexInputType &input, VertexOutputType *r_output) = 0;
+};
+
+/**
+ * Fragment shader will render a single fragment onto the ImageBuffer.
+ * FragmentInput - The input data from the vertex stage.
+ * FragmentOutput points to the memory location to write to in the image buffer.
+ */
+template<typename FragmentInput, typename FragmentOutput> class AbstractFragmentShader {
+ public:
+ using FragmentInputType = FragmentInput;
+ using FragmentOutputType = FragmentOutput;
+
+ virtual void fragment(const FragmentInputType &input, FragmentOutputType *r_output) = 0;
+};
+
+/**
+ * RasterLine - data to render a single rasterline of a triangle.
+ */
+template<typename FragmentInput> class Rasterline {
+ public:
+ /** Row where this rasterline will be rendered. */
+ uint32_t y;
+ /** Starting X coordinate of the rasterline. */
+ uint32_t start_x;
+ /** Ending X coordinate of the rasterline. */
+ uint32_t end_x;
+ /** Input data for the fragment shader on (start_x, y). */
+ FragmentInput start_data;
+ /** Delta to add to the start_input to create the data for the next fragment. */
+ FragmentInput fragment_add;
+
+ Rasterline(uint32_t y,
+ uint32_t start_x,
+ uint32_t end_x,
+ FragmentInput start_data,
+ FragmentInput fragment_add)
+ : y(y), start_x(start_x), end_x(end_x), start_data(start_data), fragment_add(fragment_add)
+ {
+ }
+};
+
+template<typename Rasterline, int64_t BufferSize> class Rasterlines {
+ public:
+ Vector<Rasterline, BufferSize> buffer;
+
+ explicit Rasterlines()
+ {
+ buffer.reserve(BufferSize);
+ }
+
+ virtual ~Rasterlines() = default;
+
+ void append(const Rasterline &value)
+ {
+ buffer.append(value);
+ BLI_assert(buffer.capacity() == BufferSize);
+ }
+
+ bool is_empty() const
+ {
+ return buffer.is_empty();
+ }
+
+ bool has_items() const
+ {
+ return buffer.has_items();
+ }
+
+ bool is_full() const
+ {
+ return buffer.size() == BufferSize;
+ }
+
+ void clear()
+ {
+ buffer.clear();
+ BLI_assert(buffer.size() == 0);
+ BLI_assert(buffer.capacity() == BufferSize);
+ }
+};
+
+template<typename VertexShader,
+ typename FragmentShader,
+ /**
+ * A blend mode integrates the result of the fragment shader with the drawing target.
+ */
+ typename BlendMode = CopyBlendMode,
+ typename DrawingTarget = ImageBufferDrawingTarget,
+
+ /**
+ * To improve branching performance the rasterlines are buffered and flushed when this
+ * treshold is reached.
+ */
+ int64_t RasterlinesSize = DefaultRasterlinesBufferSize,
+
+ /**
+ * Statistic collector. Should be a subclass of AbstractStats or implement the same
+ * interface.
+ *
+ * Is used in test cases to check what decision was made.
+ */
+ typename Statistics = NullStats>
+class Rasterizer {
+ public:
+ using InterfaceInnerType = typename VertexShader::VertexOutputType::InnerType;
+ using RasterlineType = Rasterline<InterfaceInnerType>;
+ using VertexInputType = typename VertexShader::VertexInputType;
+ using VertexOutputType = typename VertexShader::VertexOutputType;
+ using FragmentInputType = typename FragmentShader::FragmentInputType;
+ using FragmentOutputType = typename FragmentShader::FragmentOutputType;
+ using TargetBufferType = typename DrawingTarget::InnerType;
+
+ /** Check if the vertex shader and the fragment shader can be linked together. */
+ static_assert(std::is_same_v<InterfaceInnerType, FragmentInputType>);
+ /** Check if the output of the fragment shader can be used as source of the Blend Mode. */
+ static_assert(std::is_same_v<FragmentOutputType, typename BlendMode::SourceType>);
+
+ private:
+ VertexShader vertex_shader_;
+ FragmentShader fragment_shader_;
+ Rasterlines<RasterlineType, RasterlinesSize> rasterlines_;
+ const CenterPixelClampingMethod clamping_method;
+ const BlendMode blending_mode_;
+ DrawingTarget drawing_target_;
+
+ public:
+ Statistics stats;
+
+ explicit Rasterizer()
+ {
+ }
+
+ /** Activate the giver image buffer to be used as the active drawing target. */
+ void activate_drawing_target(TargetBufferType *new_drawing_target)
+ {
+ deactivate_drawing_target();
+ drawing_target_.activate(new_drawing_target);
+ }
+
+ /**
+ * Deactivate active drawing target.
+ *
+ * Will flush any rasterlines before deactivating.
+ */
+ void deactivate_drawing_target()
+ {
+ if (has_active_drawing_target()) {
+ flush();
+ }
+ drawing_target_.deactivate();
+ BLI_assert(!has_active_drawing_target());
+ }
+
+ bool has_active_drawing_target() const
+ {
+ return drawing_target_.has_active_target();
+ }
+
+ virtual ~Rasterizer() = default;
+
+ VertexShader &vertex_shader()
+ {
+ return vertex_shader_;
+ }
+ VertexShader &fragment_shader()
+ {
+ return fragment_shader_;
+ }
+
+ void draw_triangle(const VertexInputType &p1,
+ const VertexInputType &p2,
+ const VertexInputType &p3)
+ {
+ BLI_assert_msg(has_active_drawing_target(),
+ "Drawing requires an active drawing target. Use `activate_drawing_target` to "
+ "activate a drawing target.");
+ stats.increase_triangles();
+
+ std::array<VertexOutputType, 3> vertex_out;
+
+ vertex_shader_.vertex(p1, &vertex_out[0]);
+ vertex_shader_.vertex(p2, &vertex_out[1]);
+ vertex_shader_.vertex(p3, &vertex_out[2]);
+
+ /* Early check if all coordinates are on a single of the buffer it is imposible to render into
+ * the buffer*/
+ const VertexOutputType &p1_out = vertex_out[0];
+ const VertexOutputType &p2_out = vertex_out[1];
+ const VertexOutputType &p3_out = vertex_out[2];
+ const bool triangle_not_visible = (p1_out.coord[0] < 0.0 && p2_out.coord[0] < 0.0 &&
+ p3_out.coord[0] < 0.0) ||
+ (p1_out.coord[1] < 0.0 && p2_out.coord[1] < 0.0 &&
+ p3_out.coord[1] < 0.0) ||
+ (p1_out.coord[0] >= drawing_target_.get_width() &&
+ p2_out.coord[0] >= drawing_target_.get_width() &&
+ p3_out.coord[0] >= drawing_target_.get_width()) ||
+ (p1_out.coord[1] >= drawing_target_.get_height() &&
+ p2_out.coord[1] >= drawing_target_.get_height() &&
+ p3_out.coord[1] >= drawing_target_.get_height());
+ if (triangle_not_visible) {
+ stats.increase_discarded_triangles();
+ return;
+ }
+
+ rasterize_triangle(vertex_out);
+ }
+
+ /**
+ * Flush any not drawn rasterlines onto the active drawing target.
+ */
+ void flush()
+ {
+ if (rasterlines_.is_empty()) {
+ return;
+ }
+
+ stats.increase_flushes();
+ for (const RasterlineType &rasterline : rasterlines_.buffer) {
+ render_rasterline(rasterline);
+ }
+ rasterlines_.clear();
+ }
+
+ private:
+ void rasterize_triangle(std::array<VertexOutputType, 3> &vertex_out)
+ {
+#ifdef DEBUG_PRINT
+ printf("%s 1: (%.4f,%.4f) 2: (%.4f,%.4f) 3: (%.4f %.4f)\n",
+ __func__,
+ vertex_out[0].coord[0],
+ vertex_out[0].coord[1],
+ vertex_out[1].coord[0],
+ vertex_out[1].coord[1],
+ vertex_out[2].coord[0],
+ vertex_out[2].coord[1]);
+#endif
+ std::array<VertexOutputType *, 3> sorted_vertices = order_triangle_vertices(vertex_out);
+
+ const int min_rasterline_y = clamping_method.scanline_for(sorted_vertices[0]->coord[1]);
+ const int mid_rasterline_y = clamping_method.scanline_for(sorted_vertices[1]->coord[1]);
+ const int max_rasterline_y = clamping_method.scanline_for(sorted_vertices[2]->coord[1]) - 1;
+
+ /* left and right branch. */
+ VertexOutputType left = *sorted_vertices[0];
+ VertexOutputType right = *sorted_vertices[0];
+
+ VertexOutputType *left_target;
+ VertexOutputType *right_target;
+ if (sorted_vertices[1]->coord[0] < sorted_vertices[2]->coord[0]) {
+ left_target = sorted_vertices[1];
+ right_target = sorted_vertices[2];
+ }
+ else {
+ left_target = sorted_vertices[2];
+ right_target = sorted_vertices[1];
+ }
+
+ VertexOutputType left_add = calc_branch_delta(left, *left_target);
+ VertexOutputType right_add = calc_branch_delta(right, *right_target);
+
+ /* Change winding order to match the steepness of the edges. */
+ if (right_add.coord[0] < left_add.coord[0]) {
+ std::swap(left_add, right_add);
+ std::swap(left_target, right_target);
+ }
+
+ /* Calculate the adder for each x pixel. This is constant for the whole triangle. It is
+ * calculated at the midline to reduce edge cases. */
+ const InterfaceInnerType fragment_add = calc_fragment_delta(
+ sorted_vertices, left, right, left_add, right_add, left_target);
+
+ /* Perform a substep to make sure that the data of left and right match the data on the anchor
+ * point (center of the pixel). */
+ update_branches_to_min_anchor_line(*sorted_vertices[0], left, right, left_add, right_add);
+
+ /* Add rasterlines from min_rasterline_y to mid_rasterline_y. */
+ rasterize_loop(
+ min_rasterline_y, mid_rasterline_y, left, right, left_add, right_add, fragment_add);
+
+ /* Special case when mid vertex is on the same rasterline as the min vertex.
+ * In this case we need to split the right/left branches. Comparing the x coordinate to find
+ * the branch that should hold the mid vertex.
+ */
+ if (min_rasterline_y == mid_rasterline_y) {
+ update_branch_for_flat_bottom(*sorted_vertices[0], *sorted_vertices[1], left, right);
+ }
+
+ update_branches_at_mid_anchor_line(
+ *sorted_vertices[1], *sorted_vertices[2], left, right, left_add, right_add);
+
+ /* Add rasterlines from mid_rasterline_y to max_rasterline_y. */
+ rasterize_loop(
+ mid_rasterline_y, max_rasterline_y, left, right, left_add, right_add, fragment_add);
+ }
+
+ /**
+ * Rasterize multiple sequential lines.
+ *
+ * Create and buffer rasterlines between #from_y and #to_y.
+ * The #left and #right branches are incremented for each rasterline.
+ */
+ void rasterize_loop(int32_t from_y,
+ int32_t to_y,
+ VertexOutputType &left,
+ VertexOutputType &right,
+ const VertexOutputType &left_add,
+ const VertexOutputType &right_add,
+ const InterfaceInnerType &fragment_add)
+ {
+ for (int y = from_y; y < to_y; y++) {
+ if (y >= 0 && y < drawing_target_.get_height()) {
+ std::optional<RasterlineType> rasterline = clamped_rasterline(
+ y, left.coord[0], right.coord[0], left.data, fragment_add);
+ if (rasterline) {
+ append(*rasterline);
+ }
+ }
+ left += left_add;
+ right += right_add;
+ }
+ }
+
+ /**
+ * Update the left or right branch for when the mid vertex is on the same rasterline as the min
+ * vertex.
+ */
+ void update_branch_for_flat_bottom(const VertexOutputType &min_vertex,
+ const VertexOutputType &mid_vertex,
+ VertexOutputType &r_left,
+ VertexOutputType &r_right) const
+ {
+ if (min_vertex.coord[0] > mid_vertex.coord[0]) {
+ r_left = mid_vertex;
+ }
+ else {
+ r_right = mid_vertex;
+ }
+ }
+
+ void update_branches_to_min_anchor_line(const VertexOutputType &min_vertex,
+ VertexOutputType &r_left,
+ VertexOutputType &r_right,
+ const VertexOutputType &left_add,
+ const VertexOutputType &right_add)
+ {
+ const float distance_to_minline_anchor_point = clamping_method.distance_to_scanline_anchor(
+ min_vertex.coord[1]);
+ r_left += left_add * distance_to_minline_anchor_point;
+ r_right += right_add * distance_to_minline_anchor_point;
+ }
+
+ void update_branches_at_mid_anchor_line(const VertexOutputType &mid_vertex,
+ const VertexOutputType &max_vertex,
+ VertexOutputType &r_left,
+ VertexOutputType &r_right,
+ VertexOutputType &r_left_add,
+ VertexOutputType &r_right_add)
+ {
+ /* When both are the same we should the left/right branches are the same. */
+ const float distance_to_midline_anchor_point = clamping_method.distance_to_scanline_anchor(
+ mid_vertex.coord[1]);
+ /* Use the x coordinate to identify which branch should be modified. */
+ const float distance_to_left = abs(r_left.coord[0] - mid_vertex.coord[0]);
+ const float distance_to_right = abs(r_right.coord[0] - mid_vertex.coord[0]);
+ if (distance_to_left < distance_to_right) {
+ r_left = mid_vertex;
+ r_left_add = calc_branch_delta(r_left, max_vertex);
+ r_left += r_left_add * distance_to_midline_anchor_point;
+ }
+ else {
+ r_right = mid_vertex;
+ r_right_add = calc_branch_delta(r_right, max_vertex);
+ r_right += r_right_add * distance_to_midline_anchor_point;
+ }
+ }
+
+ /**
+ * Calculate the delta adder between two sequential fragments in the x-direction.
+ *
+ * Fragment adder is constant and can be calculated once and reused for each rasterline of
+ * the same triangle. However the calculation requires a distance that might not be known at
+ * the first scanline that is added. Therefore this method uses the mid scanline as there is
+ * the max x_distance.
+ *
+ * \returns the adder that can be added the previous fragment data.
+ */
+ InterfaceInnerType calc_fragment_delta(const std::array<VertexOutputType *, 3> &sorted_vertices,
+ const VertexOutputType &left,
+ const VertexOutputType &right,
+ const VertexOutputType &left_add,
+ const VertexOutputType &right_add,
+ const VertexOutputType *left_target)
+ {
+ const float distance_min_to_mid = sorted_vertices[1]->coord[1] - sorted_vertices[0]->coord[1];
+ if (distance_min_to_mid == 0.0f) {
+ VertexOutputType *mid_left = (sorted_vertices[1]->coord[0] < sorted_vertices[0]->coord[0]) ?
+ sorted_vertices[1] :
+ sorted_vertices[0];
+ VertexOutputType *mid_right = (sorted_vertices[1]->coord[0] < sorted_vertices[0]->coord[0]) ?
+ sorted_vertices[0] :
+ sorted_vertices[1];
+ return (mid_right->data - mid_left->data) / (mid_right->coord[0] - mid_left->coord[0]);
+ }
+
+ if (left_target == sorted_vertices[1]) {
+ VertexOutputType mid_right = right + right_add * distance_min_to_mid;
+ return (mid_right.data - sorted_vertices[1]->data) /
+ (mid_right.coord[0] - sorted_vertices[1]->coord[0]);
+ }
+
+ VertexOutputType mid_left = left + left_add * distance_min_to_mid;
+ return (sorted_vertices[1]->data - mid_left.data) /
+ (sorted_vertices[1]->coord[0] - mid_left.coord[0]);
+ }
+
+ /**
+ * Calculate the delta adder between two rasterlines for the given edge.
+ */
+ VertexOutputType calc_branch_delta(const VertexOutputType &from, const VertexOutputType &to)
+ {
+ const float num_rasterlines = max_ff(to.coord[1] - from.coord[1], 1.0f);
+ VertexOutputType result = (to - from) / num_rasterlines;
+ return result;
+ }
+
+ std::array<VertexOutputType *, 3> order_triangle_vertices(
+ std::array<VertexOutputType, 3> &vertex_out)
+ {
+ std::array<VertexOutputType *, 3> sorted;
+ /* Find min v-coordinate and store at index 0. */
+ sorted[0] = &vertex_out[0];
+ for (int i = 1; i < 3; i++) {
+ if (vertex_out[i].coord[1] < sorted[0]->coord[1]) {
+ sorted[0] = &vertex_out[i];
+ }
+ }
+
+ /* Find max v-coordinate and store at index 2. */
+ sorted[2] = &vertex_out[0];
+ for (int i = 1; i < 3; i++) {
+ if (vertex_out[i].coord[1] > sorted[2]->coord[1]) {
+ sorted[2] = &vertex_out[i];
+ }
+ }
+
+ /* Exit when all 3 have the same v coordinate. Use the original input order. */
+ if (sorted[0]->coord[1] == sorted[2]->coord[1]) {
+ for (int i = 0; i < 3; i++) {
+ sorted[i] = &vertex_out[i];
+ }
+ BLI_assert(sorted[0] != sorted[1] && sorted[0] != sorted[2] && sorted[1] != sorted[2]);
+ return sorted;
+ }
+
+ /* Find mid v-coordinate and store at index 1. */
+ sorted[1] = &vertex_out[0];
+ for (int i = 0; i < 3; i++) {
+ if (sorted[0] != &vertex_out[i] && sorted[2] != &vertex_out[i]) {
+ sorted[1] = &vertex_out[i];
+ break;
+ }
+ }
+
+ BLI_assert(sorted[0] != sorted[1] && sorted[0] != sorted[2] && sorted[1] != sorted[2]);
+ BLI_assert(sorted[0]->coord[1] <= sorted[1]->coord[1]);
+ BLI_assert(sorted[0]->coord[1] < sorted[2]->coord[1]);
+ BLI_assert(sorted[1]->coord[1] <= sorted[2]->coord[1]);
+ return sorted;
+ }
+
+ std::optional<RasterlineType> clamped_rasterline(int32_t y,
+ float start_x,
+ float end_x,
+ InterfaceInnerType start_data,
+ const InterfaceInnerType fragment_add)
+ {
+ BLI_assert(y >= 0 && y < drawing_target_.get_height());
+
+ stats.increase_rasterlines();
+ if (start_x >= end_x) {
+ stats.increase_discarded_rasterlines();
+ return std::nullopt;
+ }
+ if (end_x < 0) {
+ stats.increase_discarded_rasterlines();
+ return std::nullopt;
+ }
+ if (start_x >= drawing_target_.get_width()) {
+ stats.increase_discarded_rasterlines();
+ return std::nullopt;
+ }
+
+ /* Is created rasterline clamped and should be added to the statistics. */
+ bool is_clamped = false;
+
+ /* Clamp the start_x to the first visible column anchor. */
+ int32_t start_xi = clamping_method.column_for(start_x);
+ float delta_to_anchor = clamping_method.distance_to_column_anchor(start_x);
+ if (start_xi < 0) {
+ delta_to_anchor += -start_xi;
+ start_xi = 0;
+ is_clamped = true;
+ }
+ start_data += fragment_add * delta_to_anchor;
+
+ uint32_t end_xi = clamping_method.column_for(end_x);
+ if (end_xi > drawing_target_.get_width()) {
+ end_xi = drawing_target_.get_width();
+ is_clamped = true;
+ }
+
+ if (is_clamped) {
+ stats.increase_clamped_rasterlines();
+ }
+
+#ifdef DEBUG_PRINT
+ printf("%s y(%d) x(%d-%u)\n", __func__, y, start_xi, end_xi);
+#endif
+
+ return RasterlineType(y, (uint32_t)start_xi, end_xi, start_data, fragment_add);
+ }
+
+ void render_rasterline(const RasterlineType &rasterline)
+ {
+ FragmentInputType data = rasterline.start_data;
+ float *pixel_ptr = drawing_target_.get_pixel_ptr(rasterline.start_x, rasterline.y);
+ for (uint32_t x = rasterline.start_x; x < rasterline.end_x; x++) {
+
+ FragmentOutputType fragment_out;
+ fragment_shader_.fragment(data, &fragment_out);
+ blending_mode_.blend(pixel_ptr, fragment_out);
+
+ data += rasterline.fragment_add;
+ pixel_ptr += drawing_target_.get_pixel_stride();
+ }
+
+ stats.increase_drawn_fragments(rasterline.end_x - rasterline.start_x);
+ }
+
+ void append(const RasterlineType &rasterline)
+ {
+ rasterlines_.append(rasterline);
+ if (rasterlines_.is_full()) {
+ flush();
+ }
+ }
+};
+
+} // namespace blender::imbuf::rasterizer
diff --git a/source/blender/imbuf/intern/rasterizer_blending.hh b/source/blender/imbuf/intern/rasterizer_blending.hh
new file mode 100644
index 00000000000..14dbaadfc95
--- /dev/null
+++ b/source/blender/imbuf/intern/rasterizer_blending.hh
@@ -0,0 +1,32 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later
+ * Copyright 2022 Blender Foundation. All rights reserved. */
+
+#pragma once
+
+#include "BLI_math.h"
+#include "BLI_math_vec_types.hh"
+#include "BLI_sys_types.h"
+
+namespace blender::imbuf::rasterizer {
+
+/** How to integrate the result of a fragment shader into its drawing target. */
+template<typename Source, typename Destination> class AbstractBlendMode {
+ public:
+ using SourceType = Source;
+ using DestinationType = Destination;
+
+ virtual void blend(Destination *dest, const Source &source) const = 0;
+};
+
+/**
+ * Copy the result of the fragment shader into float[4] without any modifications.
+ */
+class CopyBlendMode : public AbstractBlendMode<float4, float> {
+ public:
+ void blend(float *dest, const float4 &source) const override
+ {
+ copy_v4_v4(dest, source);
+ }
+};
+
+} // namespace blender::imbuf::rasterizer
diff --git a/source/blender/imbuf/intern/rasterizer_clamping.hh b/source/blender/imbuf/intern/rasterizer_clamping.hh
new file mode 100644
index 00000000000..30aa9daec36
--- /dev/null
+++ b/source/blender/imbuf/intern/rasterizer_clamping.hh
@@ -0,0 +1,82 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later
+ * Copyright 2022 Blender Foundation. All rights reserved. */
+
+#pragma once
+
+/** \file
+ * \ingroup imbuf
+ *
+ * Pixel clamping determines how the edges of geometry is clamped to pixels.
+ */
+
+#include "BLI_sys_types.h"
+
+namespace blender::imbuf::rasterizer {
+
+class AbstractPixelClampingMethod {
+ public:
+ virtual float distance_to_scanline_anchor(float y) const = 0;
+ virtual float distance_to_column_anchor(float y) const = 0;
+ virtual int scanline_for(float y) const = 0;
+ virtual int column_for(float x) const = 0;
+};
+
+class CenterPixelClampingMethod : public AbstractPixelClampingMethod {
+ public:
+ float distance_to_scanline_anchor(float y) const override
+ {
+ return distance_to_anchor(y);
+ }
+ float distance_to_column_anchor(float x) const override
+ {
+ return distance_to_anchor(x);
+ }
+
+ int scanline_for(float y) const override
+ {
+ return this->round(y);
+ }
+
+ int column_for(float x) const override
+ {
+ return this->round(x);
+ }
+
+ private:
+ float distance_to_anchor(float value) const
+ {
+ float fract = to_fract(value);
+ float result;
+ if (fract <= 0.5f) {
+ result = 0.5f - fract;
+ }
+ else {
+ result = 1.5f - fract;
+ }
+ BLI_assert(result >= 0.0f);
+ BLI_assert(result < 1.0f);
+ return result;
+ }
+
+ int round(float value) const
+ {
+ /* Cannot use std::round as it rounds away from 0. */
+ float fract = to_fract(value);
+ int result;
+
+ if (fract > 0.5f) {
+ result = ceilf(value);
+ }
+ else {
+ result = floorf(value);
+ }
+ return result;
+ }
+
+ float to_fract(float value) const
+ {
+ return value - floor(value);
+ }
+};
+
+} // namespace blender::imbuf::rasterizer
diff --git a/source/blender/imbuf/intern/rasterizer_stats.hh b/source/blender/imbuf/intern/rasterizer_stats.hh
new file mode 100644
index 00000000000..4a8ad4bff73
--- /dev/null
+++ b/source/blender/imbuf/intern/rasterizer_stats.hh
@@ -0,0 +1,89 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later
+ * Copyright 2022 Blender Foundation. All rights reserved. */
+
+#pragma once
+
+#include "BLI_sys_types.h"
+
+namespace blender::imbuf::rasterizer {
+
+class AbstractStats {
+ public:
+ virtual void increase_triangles() = 0;
+ virtual void increase_discarded_triangles() = 0;
+ virtual void increase_flushes() = 0;
+ virtual void increase_rasterlines() = 0;
+ virtual void increase_clamped_rasterlines() = 0;
+ virtual void increase_discarded_rasterlines() = 0;
+ virtual void increase_drawn_fragments(uint64_t fragments_drawn) = 0;
+};
+
+class Stats : public AbstractStats {
+ public:
+ int64_t triangles = 0;
+ int64_t discarded_triangles = 0;
+ int64_t flushes = 0;
+ int64_t rasterlines = 0;
+ int64_t clamped_rasterlines = 0;
+ int64_t discarded_rasterlines = 0;
+ int64_t drawn_fragments = 0;
+
+ void increase_triangles() override
+ {
+ triangles += 1;
+ }
+
+ void increase_discarded_triangles() override
+ {
+ discarded_triangles += 1;
+ }
+
+ void increase_flushes() override
+ {
+ flushes += 1;
+ }
+
+ void increase_rasterlines() override
+ {
+ rasterlines += 1;
+ }
+
+ void increase_clamped_rasterlines() override
+ {
+ clamped_rasterlines += 1;
+ }
+ void increase_discarded_rasterlines() override
+ {
+ discarded_rasterlines += 1;
+ }
+ void increase_drawn_fragments(uint64_t fragments_drawn) override
+ {
+ drawn_fragments += fragments_drawn;
+ }
+
+ void reset()
+ {
+ triangles = 0;
+ discarded_triangles = 0;
+ flushes = 0;
+ rasterlines = 0;
+ clamped_rasterlines = 0;
+ discarded_rasterlines = 0;
+ drawn_fragments = 0;
+ }
+};
+
+class NullStats : public AbstractStats {
+ public:
+ void increase_triangles() override{};
+ void increase_discarded_triangles() override{};
+ void increase_flushes() override{};
+ void increase_rasterlines() override{};
+ void increase_clamped_rasterlines() override{};
+ void increase_discarded_rasterlines() override{};
+ void increase_drawn_fragments(uint64_t UNUSED(fragments_drawn)) override
+ {
+ }
+};
+
+} // namespace blender::imbuf::rasterizer
diff --git a/source/blender/imbuf/intern/rasterizer_target.hh b/source/blender/imbuf/intern/rasterizer_target.hh
new file mode 100644
index 00000000000..25830b4006a
--- /dev/null
+++ b/source/blender/imbuf/intern/rasterizer_target.hh
@@ -0,0 +1,74 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later
+ * Copyright 2022 Blender Foundation. All rights reserved. */
+
+#pragma once
+
+/** \file
+ * \ingroup imbuf
+ *
+ * Rasterizer drawing target.
+ */
+
+#include "BLI_sys_types.h"
+
+namespace blender::imbuf::rasterizer {
+
+/**
+ * An abstract implementation of a drawing target. Will make it possible to switch to other render
+ * targets then only ImBuf types.
+ */
+template<typename Inner> class AbstractDrawingTarget {
+ public:
+ using InnerType = Inner;
+ virtual uint64_t get_width() const = 0;
+ virtual uint64_t get_height() const = 0;
+ virtual float *get_pixel_ptr(uint64_t x, uint64_t y) = 0;
+ virtual int64_t get_pixel_stride() const = 0;
+ virtual bool has_active_target() const = 0;
+ virtual void activate(Inner *instance) = 0;
+ virtual void deactivate() = 0;
+};
+
+class ImageBufferDrawingTarget : public AbstractDrawingTarget<ImBuf> {
+ private:
+ ImBuf *image_buffer_ = nullptr;
+
+ public:
+ bool has_active_target() const override
+ {
+ return image_buffer_ != nullptr;
+ }
+
+ void activate(ImBuf *image_buffer) override
+ {
+ image_buffer_ = image_buffer;
+ }
+
+ void deactivate() override
+ {
+ image_buffer_ = nullptr;
+ }
+
+ uint64_t get_width() const override
+ {
+ return image_buffer_->x;
+ };
+
+ uint64_t get_height() const override
+ {
+ return image_buffer_->y;
+ }
+
+ float *get_pixel_ptr(uint64_t x, uint64_t y) override
+ {
+ BLI_assert(has_active_target());
+ uint64_t pixel_index = y * image_buffer_->x + x;
+ return &image_buffer_->rect_float[pixel_index * 4];
+ }
+ int64_t get_pixel_stride() const override
+ {
+ return 4;
+ }
+};
+
+} // namespace blender::imbuf::rasterizer
diff --git a/source/blender/imbuf/intern/rasterizer_test.cc b/source/blender/imbuf/intern/rasterizer_test.cc
new file mode 100644
index 00000000000..ccc61d55ed9
--- /dev/null
+++ b/source/blender/imbuf/intern/rasterizer_test.cc
@@ -0,0 +1,429 @@
+/* SPDX-License-Identifier: Apache-2.0 */
+
+#include "testing/testing.h"
+
+#include "BLI_float4x4.hh"
+#include "BLI_path_util.h"
+
+#include "IMB_rasterizer.hh"
+
+namespace blender::imbuf::rasterizer::tests {
+
+const uint32_t IMBUF_SIZE = 256;
+
+struct VertexInput {
+ float2 uv;
+ float value;
+
+ VertexInput(float2 uv, float value) : uv(uv), value(value)
+ {
+ }
+};
+
+class VertexShader : public AbstractVertexShader<VertexInput, float4> {
+ public:
+ float2 image_size;
+ float4x4 vp_mat;
+ void vertex(const VertexInputType &input, VertexOutputType *r_output) override
+ {
+ float2 coord = float2(vp_mat * float3(input.uv[0], input.uv[1], 0.0));
+ r_output->coord = coord * image_size;
+ r_output->data = float4(input.value, input.value, input.value, 1.0);
+ }
+};
+
+class FragmentShader : public AbstractFragmentShader<float4, float4> {
+ public:
+ void fragment(const FragmentInputType &input, FragmentOutputType *r_output) override
+ {
+ *r_output = input;
+ }
+};
+
+using RasterizerType = Rasterizer<VertexShader,
+ FragmentShader,
+ CopyBlendMode,
+ ImageBufferDrawingTarget,
+ DefaultRasterlinesBufferSize,
+ Stats>;
+
+/* Draw 2 triangles that fills the entire image buffer and see if each pixel is touched. */
+TEST(imbuf_rasterizer, draw_triangle_edge_alignment_quality)
+{
+ ImBuf image_buffer;
+ IMB_initImBuf(&image_buffer, IMBUF_SIZE, IMBUF_SIZE, 32, IB_rectfloat);
+
+ RasterizerType rasterizer;
+ rasterizer.activate_drawing_target(&image_buffer);
+
+ VertexShader &vertex_shader = rasterizer.vertex_shader();
+ vertex_shader.image_size = float2(image_buffer.x, image_buffer.y);
+
+ float clear_color[4] = {0.0f, 0.0f, 0.0f, 0.0f};
+
+ float3 location(0.5, 0.5, 0.0);
+ float3 rotation(0.0, 0.0, 0.0);
+ float3 scale(1.0, 1.0, 1.0);
+
+ for (int i = 0; i < 1000; i++) {
+ rasterizer.stats.reset();
+
+ IMB_rectfill(&image_buffer, clear_color);
+ rotation[2] = (i / 1000.0) * M_PI * 2;
+
+ vertex_shader.vp_mat = float4x4::from_loc_eul_scale(location, rotation, scale);
+ rasterizer.draw_triangle(VertexInput(float2(-1.0, -1.0), 0.2),
+ VertexInput(float2(-1.0, 1.0), 0.5),
+ VertexInput(float2(1.0, -1.0), 1.0));
+ rasterizer.draw_triangle(VertexInput(float2(1.0, 1.0), 0.2),
+ VertexInput(float2(-1.0, 1.0), 0.5),
+ VertexInput(float2(1.0, -1.0), 1.0));
+ rasterizer.flush();
+
+ /* Check if each pixel has been drawn exactly once. */
+ EXPECT_EQ(rasterizer.stats.drawn_fragments, IMBUF_SIZE * IMBUF_SIZE) << i;
+
+#ifdef DEBUG_SAVE
+ char file_name[FILE_MAX];
+ BLI_path_sequence_encode(file_name, "/tmp/test_", ".png", 4, i);
+ IMB_saveiff(&image_buffer, file_name, IB_rectfloat);
+ imb_freerectImBuf(&image_buffer);
+#endif
+ }
+
+ imb_freerectImbuf_all(&image_buffer);
+}
+
+/**
+ * This test case renders 3 images that should have the same coverage. But using a different edge.
+ *
+ * The results should be identical.
+ */
+TEST(imbuf_rasterizer, edge_pixel_clamping)
+{
+ float clear_color[4] = {0.0f, 0.0f, 0.0f, 0.0f};
+
+ ImBuf image_buffer_a;
+ ImBuf image_buffer_b;
+ ImBuf image_buffer_c;
+ int fragments_drawn_a;
+ int fragments_drawn_b;
+ int fragments_drawn_c;
+
+ RasterizerType rasterizer;
+
+ {
+ IMB_initImBuf(&image_buffer_a, IMBUF_SIZE, IMBUF_SIZE, 32, IB_rectfloat);
+
+ rasterizer.stats.reset();
+ rasterizer.activate_drawing_target(&image_buffer_a);
+ VertexShader &vertex_shader = rasterizer.vertex_shader();
+ vertex_shader.image_size = float2(image_buffer_a.x, image_buffer_a.y);
+ vertex_shader.vp_mat = float4x4::identity();
+ IMB_rectfill(&image_buffer_a, clear_color);
+ rasterizer.draw_triangle(VertexInput(float2(0.2, -0.2), 1.0),
+ VertexInput(float2(1.2, 1.2), 1.0),
+ VertexInput(float2(1.5, -0.3), 1.0));
+ rasterizer.flush();
+ fragments_drawn_a = rasterizer.stats.drawn_fragments;
+ }
+ {
+ IMB_initImBuf(&image_buffer_b, IMBUF_SIZE, IMBUF_SIZE, 32, IB_rectfloat);
+
+ rasterizer.stats.reset();
+ rasterizer.activate_drawing_target(&image_buffer_b);
+ VertexShader &vertex_shader = rasterizer.vertex_shader();
+ vertex_shader.image_size = float2(image_buffer_b.x, image_buffer_b.y);
+ vertex_shader.vp_mat = float4x4::identity();
+ IMB_rectfill(&image_buffer_b, clear_color);
+ rasterizer.draw_triangle(VertexInput(float2(0.2, -0.2), 1.0),
+ VertexInput(float2(1.2, 1.2), 1.0),
+ VertexInput(float2(1.5, -0.3), 1.0));
+ rasterizer.flush();
+ fragments_drawn_b = rasterizer.stats.drawn_fragments;
+ }
+
+ {
+ IMB_initImBuf(&image_buffer_c, IMBUF_SIZE, IMBUF_SIZE, 32, IB_rectfloat);
+
+ rasterizer.stats.reset();
+ rasterizer.activate_drawing_target(&image_buffer_c);
+ VertexShader &vertex_shader = rasterizer.vertex_shader();
+ vertex_shader.image_size = float2(image_buffer_c.x, image_buffer_c.y);
+ vertex_shader.vp_mat = float4x4::identity();
+ IMB_rectfill(&image_buffer_c, clear_color);
+ rasterizer.draw_triangle(VertexInput(float2(0.2, -0.2), 1.0),
+ VertexInput(float2(1.2, 1.2), 1.0),
+ VertexInput(float2(10.0, 1.3), 1.0));
+ rasterizer.flush();
+ fragments_drawn_c = rasterizer.stats.drawn_fragments;
+ }
+
+ EXPECT_EQ(fragments_drawn_a, fragments_drawn_b);
+ EXPECT_EQ(memcmp(image_buffer_a.rect_float,
+ image_buffer_b.rect_float,
+ sizeof(float) * 4 * IMBUF_SIZE * IMBUF_SIZE),
+ 0);
+ EXPECT_EQ(fragments_drawn_a, fragments_drawn_c);
+ EXPECT_EQ(memcmp(image_buffer_a.rect_float,
+ image_buffer_c.rect_float,
+ sizeof(float) * 4 * IMBUF_SIZE * IMBUF_SIZE),
+ 0);
+ EXPECT_EQ(fragments_drawn_b, fragments_drawn_c);
+ EXPECT_EQ(memcmp(image_buffer_b.rect_float,
+ image_buffer_c.rect_float,
+ sizeof(float) * 4 * IMBUF_SIZE * IMBUF_SIZE),
+ 0);
+
+ imb_freerectImbuf_all(&image_buffer_a);
+ imb_freerectImbuf_all(&image_buffer_b);
+ imb_freerectImbuf_all(&image_buffer_c);
+}
+
+/** Use one rasterizer and switch between multiple drawing targets. */
+TEST(imbuf_rasterizer, switch_drawing_target)
+{
+ float clear_color[4] = {0.0f, 0.0f, 0.0f, 0.0f};
+
+ ImBuf image_buffer_a;
+ ImBuf image_buffer_b;
+ ImBuf image_buffer_c;
+
+ RasterizerType rasterizer;
+
+ IMB_initImBuf(&image_buffer_a, IMBUF_SIZE, IMBUF_SIZE, 32, IB_rectfloat);
+ IMB_rectfill(&image_buffer_a, clear_color);
+
+ VertexShader &vertex_shader = rasterizer.vertex_shader();
+ vertex_shader.image_size = float2(image_buffer_a.x, image_buffer_a.y);
+ vertex_shader.vp_mat = float4x4::identity();
+
+ rasterizer.activate_drawing_target(&image_buffer_a);
+ rasterizer.draw_triangle(VertexInput(float2(0.2, -0.2), 1.0),
+ VertexInput(float2(1.2, 1.2), 1.0),
+ VertexInput(float2(1.5, -0.3), 1.0));
+
+ IMB_initImBuf(&image_buffer_b, IMBUF_SIZE, IMBUF_SIZE, 32, IB_rectfloat);
+ IMB_rectfill(&image_buffer_b, clear_color);
+ rasterizer.activate_drawing_target(&image_buffer_b);
+ rasterizer.draw_triangle(VertexInput(float2(0.2, -0.2), 1.0),
+ VertexInput(float2(1.2, 1.2), 1.0),
+ VertexInput(float2(1.5, -0.3), 1.0));
+
+ IMB_initImBuf(&image_buffer_c, IMBUF_SIZE, IMBUF_SIZE, 32, IB_rectfloat);
+ IMB_rectfill(&image_buffer_c, clear_color);
+ rasterizer.activate_drawing_target(&image_buffer_c);
+ rasterizer.draw_triangle(VertexInput(float2(0.2, -0.2), 1.0),
+ VertexInput(float2(1.2, 1.2), 1.0),
+ VertexInput(float2(10.0, 1.3), 1.0));
+ rasterizer.flush();
+
+ EXPECT_EQ(memcmp(image_buffer_a.rect_float,
+ image_buffer_b.rect_float,
+ sizeof(float) * 4 * IMBUF_SIZE * IMBUF_SIZE),
+ 0);
+ EXPECT_EQ(memcmp(image_buffer_a.rect_float,
+ image_buffer_c.rect_float,
+ sizeof(float) * 4 * IMBUF_SIZE * IMBUF_SIZE),
+ 0);
+ EXPECT_EQ(memcmp(image_buffer_b.rect_float,
+ image_buffer_c.rect_float,
+ sizeof(float) * 4 * IMBUF_SIZE * IMBUF_SIZE),
+ 0);
+
+ imb_freerectImbuf_all(&image_buffer_a);
+ imb_freerectImbuf_all(&image_buffer_b);
+ imb_freerectImbuf_all(&image_buffer_c);
+}
+
+TEST(imbuf_rasterizer, center_pixel_clamper_scanline_for)
+{
+ CenterPixelClampingMethod clamper;
+
+ EXPECT_EQ(clamper.scanline_for(-2.0f), -2);
+ EXPECT_EQ(clamper.scanline_for(-1.9f), -2);
+ EXPECT_EQ(clamper.scanline_for(-1.8f), -2);
+ EXPECT_EQ(clamper.scanline_for(-1.7f), -2);
+ EXPECT_EQ(clamper.scanline_for(-1.6f), -2);
+ EXPECT_EQ(clamper.scanline_for(-1.5f), -2);
+ EXPECT_EQ(clamper.scanline_for(-1.4f), -1);
+ EXPECT_EQ(clamper.scanline_for(-1.3f), -1);
+ EXPECT_EQ(clamper.scanline_for(-1.2f), -1);
+ EXPECT_EQ(clamper.scanline_for(-1.1f), -1);
+ EXPECT_EQ(clamper.scanline_for(-1.0f), -1);
+ EXPECT_EQ(clamper.scanline_for(-0.9f), -1);
+ EXPECT_EQ(clamper.scanline_for(-0.8f), -1);
+ EXPECT_EQ(clamper.scanline_for(-0.7f), -1);
+ EXPECT_EQ(clamper.scanline_for(-0.6f), -1);
+ EXPECT_EQ(clamper.scanline_for(-0.5f), -1);
+ EXPECT_EQ(clamper.scanline_for(-0.4f), 0);
+ EXPECT_EQ(clamper.scanline_for(-0.3f), 0);
+ EXPECT_EQ(clamper.scanline_for(-0.2f), 0);
+ EXPECT_EQ(clamper.scanline_for(-0.1f), 0);
+ EXPECT_EQ(clamper.scanline_for(0.0f), 0);
+ EXPECT_EQ(clamper.scanline_for(0.1f), 0);
+ EXPECT_EQ(clamper.scanline_for(0.2f), 0);
+ EXPECT_EQ(clamper.scanline_for(0.3f), 0);
+ EXPECT_EQ(clamper.scanline_for(0.4f), 0);
+ EXPECT_EQ(clamper.scanline_for(0.5f), 0);
+ EXPECT_EQ(clamper.scanline_for(0.6f), 1);
+ EXPECT_EQ(clamper.scanline_for(0.7f), 1);
+ EXPECT_EQ(clamper.scanline_for(0.8f), 1);
+ EXPECT_EQ(clamper.scanline_for(0.9f), 1);
+ EXPECT_EQ(clamper.scanline_for(1.0f), 1);
+ EXPECT_EQ(clamper.scanline_for(1.0f), 1);
+ EXPECT_EQ(clamper.scanline_for(1.1f), 1);
+ EXPECT_EQ(clamper.scanline_for(1.2f), 1);
+ EXPECT_EQ(clamper.scanline_for(1.3f), 1);
+ EXPECT_EQ(clamper.scanline_for(1.4f), 1);
+ EXPECT_EQ(clamper.scanline_for(1.5f), 1);
+ EXPECT_EQ(clamper.scanline_for(1.6f), 2);
+ EXPECT_EQ(clamper.scanline_for(1.7f), 2);
+ EXPECT_EQ(clamper.scanline_for(1.8f), 2);
+ EXPECT_EQ(clamper.scanline_for(1.9f), 2);
+ EXPECT_EQ(clamper.scanline_for(2.0f), 2);
+}
+
+TEST(imbuf_rasterizer, center_pixel_clamper_column_for)
+{
+ CenterPixelClampingMethod clamper;
+
+ EXPECT_EQ(clamper.column_for(-2.0f), -2);
+ EXPECT_EQ(clamper.column_for(-1.9f), -2);
+ EXPECT_EQ(clamper.column_for(-1.8f), -2);
+ EXPECT_EQ(clamper.column_for(-1.7f), -2);
+ EXPECT_EQ(clamper.column_for(-1.6f), -2);
+ EXPECT_EQ(clamper.column_for(-1.5f), -2);
+ EXPECT_EQ(clamper.column_for(-1.4f), -1);
+ EXPECT_EQ(clamper.column_for(-1.3f), -1);
+ EXPECT_EQ(clamper.column_for(-1.2f), -1);
+ EXPECT_EQ(clamper.column_for(-1.1f), -1);
+ EXPECT_EQ(clamper.column_for(-1.0f), -1);
+ EXPECT_EQ(clamper.column_for(-0.9f), -1);
+ EXPECT_EQ(clamper.column_for(-0.8f), -1);
+ EXPECT_EQ(clamper.column_for(-0.7f), -1);
+ EXPECT_EQ(clamper.column_for(-0.6f), -1);
+ EXPECT_EQ(clamper.column_for(-0.5f), -1);
+ EXPECT_EQ(clamper.column_for(-0.4f), 0);
+ EXPECT_EQ(clamper.column_for(-0.3f), 0);
+ EXPECT_EQ(clamper.column_for(-0.2f), 0);
+ EXPECT_EQ(clamper.column_for(-0.1f), 0);
+ EXPECT_EQ(clamper.column_for(0.0f), 0);
+ EXPECT_EQ(clamper.column_for(0.1f), 0);
+ EXPECT_EQ(clamper.column_for(0.2f), 0);
+ EXPECT_EQ(clamper.column_for(0.3f), 0);
+ EXPECT_EQ(clamper.column_for(0.4f), 0);
+ EXPECT_EQ(clamper.column_for(0.5f), 0);
+ EXPECT_EQ(clamper.column_for(0.6f), 1);
+ EXPECT_EQ(clamper.column_for(0.7f), 1);
+ EXPECT_EQ(clamper.column_for(0.8f), 1);
+ EXPECT_EQ(clamper.column_for(0.9f), 1);
+ EXPECT_EQ(clamper.column_for(1.0f), 1);
+ EXPECT_EQ(clamper.column_for(1.0f), 1);
+ EXPECT_EQ(clamper.column_for(1.1f), 1);
+ EXPECT_EQ(clamper.column_for(1.2f), 1);
+ EXPECT_EQ(clamper.column_for(1.3f), 1);
+ EXPECT_EQ(clamper.column_for(1.4f), 1);
+ EXPECT_EQ(clamper.column_for(1.5f), 1);
+ EXPECT_EQ(clamper.column_for(1.6f), 2);
+ EXPECT_EQ(clamper.column_for(1.7f), 2);
+ EXPECT_EQ(clamper.column_for(1.8f), 2);
+ EXPECT_EQ(clamper.column_for(1.9f), 2);
+ EXPECT_EQ(clamper.column_for(2.0f), 2);
+}
+
+TEST(imbuf_rasterizer, center_pixel_clamper_distance_to_scanline_anchorpoint)
+{
+ CenterPixelClampingMethod clamper;
+
+ EXPECT_FLOAT_EQ(clamper.distance_to_scanline_anchor(-2.0f), 0.5f);
+ EXPECT_FLOAT_EQ(clamper.distance_to_scanline_anchor(-1.9f), 0.4f);
+ EXPECT_FLOAT_EQ(clamper.distance_to_scanline_anchor(-1.8f), 0.3f);
+ EXPECT_FLOAT_EQ(clamper.distance_to_scanline_anchor(-1.7f), 0.2f);
+ EXPECT_FLOAT_EQ(clamper.distance_to_scanline_anchor(-1.6f), 0.1f);
+ EXPECT_FLOAT_EQ(clamper.distance_to_scanline_anchor(-1.5f), 0.0f);
+ EXPECT_FLOAT_EQ(clamper.distance_to_scanline_anchor(-1.4f), 0.9f);
+ EXPECT_FLOAT_EQ(clamper.distance_to_scanline_anchor(-1.3f), 0.8f);
+ EXPECT_FLOAT_EQ(clamper.distance_to_scanline_anchor(-1.2f), 0.7f);
+ EXPECT_FLOAT_EQ(clamper.distance_to_scanline_anchor(-1.1f), 0.6f);
+ EXPECT_FLOAT_EQ(clamper.distance_to_scanline_anchor(-1.0f), 0.5f);
+ EXPECT_FLOAT_EQ(clamper.distance_to_scanline_anchor(-0.9f), 0.4f);
+ EXPECT_FLOAT_EQ(clamper.distance_to_scanline_anchor(-0.8f), 0.3f);
+ EXPECT_FLOAT_EQ(clamper.distance_to_scanline_anchor(-0.7f), 0.2f);
+ EXPECT_FLOAT_EQ(clamper.distance_to_scanline_anchor(-0.6f), 0.1f);
+ EXPECT_FLOAT_EQ(clamper.distance_to_scanline_anchor(-0.5f), 0.0f);
+ EXPECT_FLOAT_EQ(clamper.distance_to_scanline_anchor(-0.4f), 0.9f);
+ EXPECT_FLOAT_EQ(clamper.distance_to_scanline_anchor(-0.3f), 0.8f);
+ EXPECT_FLOAT_EQ(clamper.distance_to_scanline_anchor(-0.2f), 0.7f);
+ EXPECT_FLOAT_EQ(clamper.distance_to_scanline_anchor(-0.1f), 0.6f);
+ EXPECT_FLOAT_EQ(clamper.distance_to_scanline_anchor(0.0f), 0.5f);
+ EXPECT_FLOAT_EQ(clamper.distance_to_scanline_anchor(0.1f), 0.4f);
+ EXPECT_FLOAT_EQ(clamper.distance_to_scanline_anchor(0.2f), 0.3f);
+ EXPECT_FLOAT_EQ(clamper.distance_to_scanline_anchor(0.3f), 0.2f);
+ EXPECT_FLOAT_EQ(clamper.distance_to_scanline_anchor(0.4f), 0.1f);
+ EXPECT_FLOAT_EQ(clamper.distance_to_scanline_anchor(0.5f), 0.0f);
+ EXPECT_FLOAT_EQ(clamper.distance_to_scanline_anchor(0.6f), 0.9f);
+ EXPECT_FLOAT_EQ(clamper.distance_to_scanline_anchor(0.7f), 0.8f);
+ EXPECT_FLOAT_EQ(clamper.distance_to_scanline_anchor(0.8f), 0.7f);
+ EXPECT_FLOAT_EQ(clamper.distance_to_scanline_anchor(0.9f), 0.6f);
+ EXPECT_FLOAT_EQ(clamper.distance_to_scanline_anchor(1.0f), 0.5f);
+ EXPECT_FLOAT_EQ(clamper.distance_to_scanline_anchor(1.1f), 0.4f);
+ EXPECT_FLOAT_EQ(clamper.distance_to_scanline_anchor(1.2f), 0.3f);
+ EXPECT_FLOAT_EQ(clamper.distance_to_scanline_anchor(1.3f), 0.2f);
+ EXPECT_FLOAT_EQ(clamper.distance_to_scanline_anchor(1.4f), 0.1f);
+ EXPECT_FLOAT_EQ(clamper.distance_to_scanline_anchor(1.5f), 0.0f);
+ EXPECT_FLOAT_EQ(clamper.distance_to_scanline_anchor(1.6f), 0.9f);
+ EXPECT_FLOAT_EQ(clamper.distance_to_scanline_anchor(1.7f), 0.8f);
+ EXPECT_FLOAT_EQ(clamper.distance_to_scanline_anchor(1.8f), 0.7f);
+ EXPECT_FLOAT_EQ(clamper.distance_to_scanline_anchor(1.9f), 0.6f);
+ EXPECT_FLOAT_EQ(clamper.distance_to_scanline_anchor(2.0f), 0.5f);
+}
+
+TEST(imbuf_rasterizer, center_pixel_clamper_distance_to_column_anchorpoint)
+{
+ CenterPixelClampingMethod clamper;
+
+ EXPECT_FLOAT_EQ(clamper.distance_to_column_anchor(-2.0f), 0.5f);
+ EXPECT_FLOAT_EQ(clamper.distance_to_column_anchor(-1.9f), 0.4f);
+ EXPECT_FLOAT_EQ(clamper.distance_to_column_anchor(-1.8f), 0.3f);
+ EXPECT_FLOAT_EQ(clamper.distance_to_column_anchor(-1.7f), 0.2f);
+ EXPECT_FLOAT_EQ(clamper.distance_to_column_anchor(-1.6f), 0.1f);
+ EXPECT_FLOAT_EQ(clamper.distance_to_column_anchor(-1.5f), 0.0f);
+ EXPECT_FLOAT_EQ(clamper.distance_to_column_anchor(-1.4f), 0.9f);
+ EXPECT_FLOAT_EQ(clamper.distance_to_column_anchor(-1.3f), 0.8f);
+ EXPECT_FLOAT_EQ(clamper.distance_to_column_anchor(-1.2f), 0.7f);
+ EXPECT_FLOAT_EQ(clamper.distance_to_column_anchor(-1.1f), 0.6f);
+ EXPECT_FLOAT_EQ(clamper.distance_to_column_anchor(-1.0f), 0.5f);
+ EXPECT_FLOAT_EQ(clamper.distance_to_column_anchor(-0.9f), 0.4f);
+ EXPECT_FLOAT_EQ(clamper.distance_to_column_anchor(-0.8f), 0.3f);
+ EXPECT_FLOAT_EQ(clamper.distance_to_column_anchor(-0.7f), 0.2f);
+ EXPECT_FLOAT_EQ(clamper.distance_to_column_anchor(-0.6f), 0.1f);
+ EXPECT_FLOAT_EQ(clamper.distance_to_column_anchor(-0.5f), 0.0f);
+ EXPECT_FLOAT_EQ(clamper.distance_to_column_anchor(-0.4f), 0.9f);
+ EXPECT_FLOAT_EQ(clamper.distance_to_column_anchor(-0.3f), 0.8f);
+ EXPECT_FLOAT_EQ(clamper.distance_to_column_anchor(-0.2f), 0.7f);
+ EXPECT_FLOAT_EQ(clamper.distance_to_column_anchor(-0.1f), 0.6f);
+ EXPECT_FLOAT_EQ(clamper.distance_to_column_anchor(0.0f), 0.5f);
+ EXPECT_FLOAT_EQ(clamper.distance_to_column_anchor(0.1f), 0.4f);
+ EXPECT_FLOAT_EQ(clamper.distance_to_column_anchor(0.2f), 0.3f);
+ EXPECT_FLOAT_EQ(clamper.distance_to_column_anchor(0.3f), 0.2f);
+ EXPECT_FLOAT_EQ(clamper.distance_to_column_anchor(0.4f), 0.1f);
+ EXPECT_FLOAT_EQ(clamper.distance_to_column_anchor(0.5f), 0.0f);
+ EXPECT_FLOAT_EQ(clamper.distance_to_column_anchor(0.6f), 0.9f);
+ EXPECT_FLOAT_EQ(clamper.distance_to_column_anchor(0.7f), 0.8f);
+ EXPECT_FLOAT_EQ(clamper.distance_to_column_anchor(0.8f), 0.7f);
+ EXPECT_FLOAT_EQ(clamper.distance_to_column_anchor(0.9f), 0.6f);
+ EXPECT_FLOAT_EQ(clamper.distance_to_column_anchor(1.0f), 0.5f);
+ EXPECT_FLOAT_EQ(clamper.distance_to_column_anchor(1.1f), 0.4f);
+ EXPECT_FLOAT_EQ(clamper.distance_to_column_anchor(1.2f), 0.3f);
+ EXPECT_FLOAT_EQ(clamper.distance_to_column_anchor(1.3f), 0.2f);
+ EXPECT_FLOAT_EQ(clamper.distance_to_column_anchor(1.4f), 0.1f);
+ EXPECT_FLOAT_EQ(clamper.distance_to_column_anchor(1.5f), 0.0f);
+ EXPECT_FLOAT_EQ(clamper.distance_to_column_anchor(1.6f), 0.9f);
+ EXPECT_FLOAT_EQ(clamper.distance_to_column_anchor(1.7f), 0.8f);
+ EXPECT_FLOAT_EQ(clamper.distance_to_column_anchor(1.8f), 0.7f);
+ EXPECT_FLOAT_EQ(clamper.distance_to_column_anchor(1.9f), 0.6f);
+ EXPECT_FLOAT_EQ(clamper.distance_to_column_anchor(2.0f), 0.5f);
+}
+
+} // namespace blender::imbuf::rasterizer::tests
diff --git a/source/tools b/source/tools
-Subproject 7fd2ed908b4f50140670caf6786e5ed245b7913
+Subproject 5715a567950258c17089c9b9cb175a6ef8c602c