[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