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. @@ _______________________________________________ Bf-blender-cvs mailing list [email protected] List details, subscription details or unsubscribe: https://lists.blender.org/mailman/listinfo/bf-blender-cvs
