[Bf-blender-cvs] [8839d309894] temp-image-buffer-rasterizer: Image buffer rasterizer using CPP templates.
Jeroen Bakker
noreply at git.blender.org
Wed Feb 16 14:25:06 CET 2022
Commit: 8839d309894c55bb514cc7cd16026e487c55ae17
Author: Jeroen Bakker
Date: Tue Feb 15 16:21:56 2022 +0100
Branches: temp-image-buffer-rasterizer
https://developer.blender.org/rB8839d309894c55bb514cc7cd16026e487c55ae17
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
===================================================================
M source/blender/imbuf/CMakeLists.txt
A source/blender/imbuf/IMB_rasterizer.hh
A source/blender/imbuf/intern/rasterizer_stats.hh
A source/blender/imbuf/intern/rasterizer_test.cc
===================================================================
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..39f3d76c223
--- /dev/null
+++ b/source/blender/imbuf/IMB_rasterizer.hh
@@ -0,0 +1,435 @@
+/* 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.
+ */
+
+#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_stats.hh"
+
+#include <optional>
+
+namespace blender::imbuf::rasterizer {
+
+/**
+ * Interface data of the vertex stage.
+ */
+template<typename Data> class VertexOutInterface {
+ public:
+ using Self = VertexOutInterface<Data>;
+ float2 uv;
+ Data data;
+
+ Self &operator+=(const Self &other)
+ {
+ uv += other.uv;
+ data += other.data;
+ return *this;
+ }
+
+ Self &operator=(const Self &other)
+ {
+ uv = other.uv;
+ data = other.data;
+ return *this;
+ }
+
+ Self operator-(const Self &other) const
+ {
+ Self result;
+ result.uv = uv - other.uv;
+ result.data = data - other.data;
+ return result;
+ }
+
+ Self operator/(const float divider) const
+ {
+ Self result;
+ result.uv = uv / divider;
+ result.data = data / divider;
+ return result;
+ }
+
+ Self operator*(const float multiplier) const
+ {
+ Self result;
+ result.uv = uv * multiplier;
+ result.data = data * multiplier;
+ return *this;
+ }
+};
+
+/**
+ * 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
+ * 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 delta_step;
+
+ Rasterline()
+ {
+ }
+
+ Rasterline(uint32_t y,
+ uint32_t start_x,
+ uint32_t end_x,
+ FragmentInput start_data,
+ FragmentInput delta_step)
+ : y(y), start_x(start_x), end_x(end_x), start_data(start_data), delta_step(delta_step)
+ {
+ }
+};
+
+template<typename Rasterline, int64_t BufferSize> class Rasterlines {
+ public:
+ Vector<Rasterline> buffer;
+
+ explicit Rasterlines() : buffer(BufferSize)
+ {
+ }
+
+ void append(const Rasterline &value)
+ {
+ buffer.append(value);
+ }
+
+ 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();
+ }
+};
+
+template<typename VertexShader,
+ typename FragmentShader,
+
+ /**
+ * To improve branching performance the rasterlines are buffered and flushed when this
+ * treshold is reached.
+ */
+ int64_t RasterlinesSize = 4096,
+
+ /**
+ * 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 RasterlineType = Rasterline<typename FragmentShader::FragmentInputType>;
+ using VertexInputType = typename VertexShader::VertexInputType;
+ using VertexOutputType = typename VertexShader::VertexOutputType;
+ using FragmentInputType = typename FragmentShader::FragmentInputType;
+ using FragmentOutputType = typename FragmentShader::FragmentOutputType;
+
+ private:
+ VertexShader vertex_shader_;
+ FragmentShader fragment_shader_;
+ Rasterlines<RasterlineType, RasterlinesSize> rasterlines_;
+ ImBuf *image_buffer_;
+
+ public:
+ Statistics stats;
+
+ explicit Rasterizer(ImBuf *image_buffer) : image_buffer_(image_buffer)
+ {
+ }
+
+ virtual ~Rasterizer()
+ {
+ flush();
+ }
+
+ VertexShader &vertex_shader()
+ {
+ return vertex_shader_;
+ }
+ VertexShader &fragment_shader()
+ {
+ return fragment_shader_;
+ }
+
+ void draw_triangle(const VertexInputType &p1,
+ const VertexInputType &p2,
+ const VertexInputType &p3)
+ {
+ 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.uv[0] < 0.0 && p2_out.uv[0] < 0.0 && p3_out.uv[0] < 0.0) ||
+ (p1_out.uv[1] < 0.0 && p2_out.uv[1] < 0.0 && p3_out.uv[1] < 0.0) ||
+ (p1_out.uv[0] >= image_buffer_->x && p2_out.uv[0] >= image_buffer_->x &&
+ p3_out.uv[0] >= image_buffer_->x) ||
+ (p1_out.uv[1] >= image_buffer_->y && p2_out.uv[1] >= image_buffer_->y &&
+ p3_out.uv[1] >= image_buffer_->y);
+ if (triangle_not_visible) {
+ stats.increase_discarded_triangles();
+ return;
+ }
+
+ rasterize_triangle(vertex_out);
+ }
+
+ 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)
+ {
+ std::array<VertexOutputType *, 3> sorted_vertices = order_triangle_vertices(vertex_out);
+
+ /* left and right branch. */
+ VertexOutputType left = *sorted_vertices[0];
+ VertexOutputType right = *sorted_vertices[0];
+
+ const int min_v = sorted_vertices[0]->uv[1];
+ const int mid_v = sorted_vertices[1]->uv[1];
+ const int max_v = sorted_vertices[2]->uv[1];
+
+ VertexOutputType *left_target;
+ VertexOutputType *right_target;
+ if (sorted_vertices[1]->uv[0] < sorted_vertices[2]->uv[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_vertex_output_data(left, *left_target);
+ VertexOutputType right_add = calc_vertex_output_data(right, *right_target);
+
+ int v;
+ for (v = min_v; v < mid_v; v++) {
+ if (v >= 0 && v < image_buffer_->y) {
+ std::optional<RasterlineType> rasterline = clamped_rasterline(
+ v, left.uv[0], right.uv[0], left.data, right.data);
+ if (rasterline) {
+ append(*rasterline);
+ }
+ }
+ left += left_add;
+ right += right_add;
+ }
+
+ left_target = sorted_vertices[2];
+ right_target = sorted_vertices[2];
+ left_add = calc_vertex_output_data(left, *left_target);
+ right_add = calc_vertex_output_data(right, *right_target);
+
+ for (; v < max_v; v++) {
+ if (v >= 0 && v < image_buffer_->y) {
+ std::optional<RasterlineType> rasterline = clamped_rasterline(
+ v, left.uv[0], right.uv[0], left.data, right.data);
+ if (rasterline) {
+ append(*rasterline);
+ }
+ }
+ left += left_add;
+ right += right_add;
+ }
+ }
+
+ VertexOutputType calc_vertex_output_data(const VertexOutputType &from,
+ const VertexOutputType &to)
+ {
+ return (to - from) / (to.uv[1] - from.uv[1]);
+ }
+
+ 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].uv[1] < sorted[0]->uv[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].uv[1] > sorted[0]->uv[1]) {
+ sorted[2] = &vertex_out[i];
+ }
+ }
+
+ /* Exit when all 3 have the same v coordinate. Use the original input order. */
+ if (sorted[0] == sorted[2]) {
+ for (int i = 0; i < 3; i++) {
+ sorted
@@ Diff output truncated at 10240 characters. @@
More information about the Bf-blender-cvs
mailing list