1
1

Compare commits

...

18 Commits

Author SHA1 Message Date
a41c2a5137 Merge branch 'master' into temp-image-buffer-rasterizer 2022-03-02 16:03:01 +01:00
a23b442991 Added empty line at end of file. 2022-03-02 15:09:41 +01:00
0f784485bd Added blending modes. 2022-03-02 15:08:04 +01:00
1ddde2d83c Split rasterizer target to support other drawing targets. 2022-03-02 13:54:43 +01:00
6afa238ced Reverted default constructor. 2022-03-02 10:17:06 +01:00
82c0dbe793 Fix incorrect buffer operations. 2022-03-02 10:14:13 +01:00
b5176e90bf Improved rasterizing quality. 2022-03-01 15:54:54 +01:00
6d1eaf2d87 Check for quality. 2022-02-21 13:42:21 +01:00
15182f0941 Use a constant fragment adder per triangle. 2022-02-21 09:52:58 +01:00
65b806d5f3 Fix glitches around mid vertex. 2022-02-21 08:09:23 +01:00
2778b0690e Added back check when 2 verts are on the same rasterline. 2022-02-18 17:03:48 +01:00
89cecb088f Improve quality by center pixel clamping. 2022-02-18 16:43:38 +01:00
80144db42d Add check if vertex and fragment shader can be linked. 2022-02-18 12:57:51 +01:00
7a5cde3074 Added documentation. 2022-02-18 12:36:07 +01:00
4e721dcd07 Renamed uv->coords, small fixes. 2022-02-18 10:07:03 +01:00
29a3d61df3 Fixed issue with sorting verices. 2022-02-16 16:45:55 +01:00
c51692a568 WIP - Testing other winding order. 2022-02-16 14:24:37 +01:00
8839d30989 Image buffer rasterizer using CPP templates.
For the 3d texture brush project we need a fast CPU based rasterizer.
This is an initial implementation for a rasterizer that easy to extend
and optimize.

The idea is to implement a rasterizer on top of the ImBuf structure. The
implementation uses CPP templates, resulting each usage to be optimized
by the compiler individually.

A user of the rasterizer can define a vertex shader, fragment shader,
the inputs and interface, similar to existing concepts when using
OpenGL.

The rasterizer only supports triangles.

[Future extensions]

Currently the rasterlines are buffered and when the buffer is full it
will be flushed. This is a tradeoff between local memory and branch
prediction. We expect that adding triangles are typically done by a loop
by the caller. But in certain cases we could buffer the input triangles
and take this responsibility for additional performance.

Configurable clamping. When rasterizing the clamping is done to a corner
of a image pixel. Ideally clamping should consired center pixels or use
a pixel coverage to identify how to clamp during rasterization.

Currently only supports float4 as a fragment output type. float, byte
and int textures aren't supported.

Rasterline discard function. For cases that rasterlines don't need to be
drawn based on vertex data. A use case could be that an influence factor
is 0 for the whole triangle.

Current implementation is single threaded. When using multiple threads
with their own rasterizer could lead to render artifacts. We could
provide a scheduler that collects work in buckets based on the
rasterline y.

[Todos]

* Only supports one winding directional. Should be able to support any
  winding direction.
* Use coord as name for the frag position. Current UV is too related to
  a specific usecase.
* Add more test cases.

Differential Revision: https://developer.blender.org/D14126
2022-02-16 12:04:57 +01:00
9 changed files with 1432 additions and 2 deletions

View File

@@ -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()

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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