Jelajahi Sumber

LibGL+LibSoftGPU: Move primitive assembly and clipping into LibSoftGPU

Stephan Unverwerth 3 tahun lalu
induk
melakukan
73ba208ee7

+ 10 - 153
Userland/Libraries/LibGL/SoftwareGLContext.cpp

@@ -208,175 +208,32 @@ void SoftwareGLContext::gl_end()
 {
     APPEND_TO_CALL_LIST_AND_RETURN_IF_NEEDED(gl_end);
 
-    // At this point, the user has effectively specified that they are done with defining the geometry
-    // of what they want to draw. We now need to do a few things (https://www.khronos.org/opengl/wiki/Rendering_Pipeline_Overview):
-    //
-    // 1.   Transform all of the vertices in the current vertex list into eye space by mulitplying the model-view matrix
-    // 2.   Transform all of the vertices from eye space into clip space by multiplying by the projection matrix
-    // 3.   If culling is enabled, we cull the desired faces (https://learnopengl.com/Advanced-OpenGL/Face-culling)
-    // 4.   Each element of the vertex is then divided by w to bring the positions into NDC (Normalized Device Coordinates)
-    // 5.   The vertices are sorted (for the rasteriser, how are we doing this? 3Dfx did this top to bottom in terms of vertex y coordinates)
-    // 6.   The vertices are then sent off to the rasteriser and drawn to the screen
-
-    float scr_width = m_frontbuffer->width();
-    float scr_height = m_frontbuffer->height();
-
     // Make sure we had a `glBegin` before this call...
     RETURN_WITH_ERROR_IF(!m_in_draw_state, GL_INVALID_OPERATION);
 
     m_in_draw_state = false;
 
-    triangle_list.clear_with_capacity();
-    processed_triangles.clear_with_capacity();
-
-    // Let's construct some triangles
-    if (m_current_draw_mode == GL_TRIANGLES) {
-        GLTriangle triangle;
-        for (size_t i = 0; i < vertex_list.size(); i += 3) {
-            triangle.vertices[0] = vertex_list.at(i);
-            triangle.vertices[1] = vertex_list.at(i + 1);
-            triangle.vertices[2] = vertex_list.at(i + 2);
+    // FIXME: Add support for the remaining primitive types.
+    if (m_current_draw_mode != GL_TRIANGLES
+        && m_current_draw_mode != GL_TRIANGLE_FAN
+        && m_current_draw_mode != GL_TRIANGLE_STRIP
+        && m_current_draw_mode != GL_QUADS
+        && m_current_draw_mode != GL_POLYGON) {
 
-            triangle_list.append(triangle);
-        }
-    } else if (m_current_draw_mode == GL_QUADS) {
-        // We need to construct two triangles to form the quad
-        GLTriangle triangle;
-        VERIFY(vertex_list.size() % 4 == 0);
-        for (size_t i = 0; i < vertex_list.size(); i += 4) {
-            // Triangle 1
-            triangle.vertices[0] = vertex_list.at(i);
-            triangle.vertices[1] = vertex_list.at(i + 1);
-            triangle.vertices[2] = vertex_list.at(i + 2);
-            triangle_list.append(triangle);
-
-            // Triangle 2
-            triangle.vertices[0] = vertex_list.at(i + 2);
-            triangle.vertices[1] = vertex_list.at(i + 3);
-            triangle.vertices[2] = vertex_list.at(i);
-            triangle_list.append(triangle);
-        }
-    } else if (m_current_draw_mode == GL_TRIANGLE_FAN || m_current_draw_mode == GL_POLYGON) {
-        GLTriangle triangle;
-        triangle.vertices[0] = vertex_list.at(0); // Root vertex is always the vertex defined first
-
-        for (size_t i = 1; i < vertex_list.size() - 1; i++) // This is technically `n-2` triangles. We start at index 1
-        {
-            triangle.vertices[1] = vertex_list.at(i);
-            triangle.vertices[2] = vertex_list.at(i + 1);
-            triangle_list.append(triangle);
-        }
-    } else if (m_current_draw_mode == GL_TRIANGLE_STRIP) {
-        GLTriangle triangle;
-        for (size_t i = 0; i < vertex_list.size() - 2; i++) {
-            triangle.vertices[0] = vertex_list.at(i);
-            triangle.vertices[1] = vertex_list.at(i + 1);
-            triangle.vertices[2] = vertex_list.at(i + 2);
-            triangle_list.append(triangle);
-        }
-    } else {
-        vertex_list.clear_with_capacity();
+        m_vertex_list.clear_with_capacity();
         dbgln_if(GL_DEBUG, "gl_end: draw mode {:#x} unsupported", m_current_draw_mode);
         RETURN_WITH_ERROR_IF(true, GL_INVALID_ENUM);
     }
 
-    vertex_list.clear_with_capacity();
-
-    auto mvp = m_projection_matrix * m_model_view_matrix;
-
-    // Now let's transform each triangle and send that to the GPU
-    for (size_t i = 0; i < triangle_list.size(); i++) {
-        GLTriangle& triangle = triangle_list.at(i);
-
-        // First multiply the vertex by the MODELVIEW matrix and then the PROJECTION matrix
-        triangle.vertices[0].position = mvp * triangle.vertices[0].position;
-        triangle.vertices[1].position = mvp * triangle.vertices[1].position;
-        triangle.vertices[2].position = mvp * triangle.vertices[2].position;
-
-        // Apply texture transformation
-        // FIXME: implement multi-texturing: texcoords should be stored per texture unit
-        triangle.vertices[0].tex_coord = m_texture_matrix * triangle.vertices[0].tex_coord;
-        triangle.vertices[1].tex_coord = m_texture_matrix * triangle.vertices[1].tex_coord;
-        triangle.vertices[2].tex_coord = m_texture_matrix * triangle.vertices[2].tex_coord;
-
-        // At this point, we're in clip space
-        // Here's where we do the clipping. This is a really crude implementation of the
-        // https://learnopengl.com/Getting-started/Coordinate-Systems
-        // "Note that if only a part of a primitive e.g. a triangle is outside the clipping volume OpenGL
-        // will reconstruct the triangle as one or more triangles to fit inside the clipping range. "
-        //
-        // ALL VERTICES ARE DEFINED IN A CLOCKWISE ORDER
-
-        // Okay, let's do some face culling first
-
-        m_clipped_vertices.clear_with_capacity();
-        m_clipped_vertices.append(triangle.vertices[0]);
-        m_clipped_vertices.append(triangle.vertices[1]);
-        m_clipped_vertices.append(triangle.vertices[2]);
-        m_clipper.clip_triangle_against_frustum(m_clipped_vertices);
-
-        if (m_clipped_vertices.size() < 3)
-            continue;
-
-        for (auto& vec : m_clipped_vertices) {
-            // perspective divide
-            float w = vec.position.w();
-            vec.position.set_x(vec.position.x() / w);
-            vec.position.set_y(vec.position.y() / w);
-            vec.position.set_z(vec.position.z() / w);
-            vec.position.set_w(1 / w);
-
-            // to screen space
-            vec.position.set_x(scr_width / 2 + vec.position.x() * scr_width / 2);
-            vec.position.set_y(scr_height / 2 - vec.position.y() * scr_height / 2);
-        }
-
-        GLTriangle tri;
-        tri.vertices[0] = m_clipped_vertices[0];
-        for (size_t i = 1; i < m_clipped_vertices.size() - 1; i++) {
-
-            tri.vertices[1] = m_clipped_vertices[i];
-            tri.vertices[2] = m_clipped_vertices[i + 1];
-            processed_triangles.append(tri);
-        }
-    }
-
     m_bound_texture_units.clear();
     for (auto& texture_unit : m_texture_units) {
         if (texture_unit.is_bound())
             m_bound_texture_units.append(texture_unit);
     }
 
-    for (size_t i = 0; i < processed_triangles.size(); i++) {
-        GLTriangle& triangle = processed_triangles.at(i);
-
-        // Let's calculate the (signed) area of the triangle
-        // https://cp-algorithms.com/geometry/oriented-triangle-area.html
-        float dxAB = triangle.vertices[0].position.x() - triangle.vertices[1].position.x(); // A.x - B.x
-        float dxBC = triangle.vertices[1].position.x() - triangle.vertices[2].position.x(); // B.X - C.x
-        float dyAB = triangle.vertices[0].position.y() - triangle.vertices[1].position.y();
-        float dyBC = triangle.vertices[1].position.y() - triangle.vertices[2].position.y();
-        float area = (dxAB * dyBC) - (dxBC * dyAB);
-
-        if (area == 0.0f)
-            continue;
-
-        if (m_cull_faces) {
-            bool is_front = (m_front_face == GL_CCW ? area < 0 : area > 0);
-
-            if (is_front && (m_culled_sides == GL_FRONT || m_culled_sides == GL_FRONT_AND_BACK))
-                continue;
-
-            if (!is_front && (m_culled_sides == GL_BACK || m_culled_sides == GL_FRONT_AND_BACK))
-                continue;
-        }
-
-        if (area > 0) {
-            swap(triangle.vertices[0], triangle.vertices[1]);
-        }
+    m_rasterizer.draw_primitives(m_current_draw_mode, m_projection_matrix * m_model_view_matrix, m_texture_matrix, m_vertex_list, m_bound_texture_units);
 
-        m_rasterizer.submit_triangle(triangle, m_bound_texture_units);
-    }
+    m_vertex_list.clear_with_capacity();
 }
 
 void SoftwareGLContext::gl_frustum(GLdouble left, GLdouble right, GLdouble bottom, GLdouble top, GLdouble near_val, GLdouble far_val)
@@ -650,7 +507,7 @@ void SoftwareGLContext::gl_vertex(GLdouble x, GLdouble y, GLdouble z, GLdouble w
     vertex.tex_coord = m_current_vertex_tex_coord;
     vertex.normal = m_current_vertex_normal;
 
-    vertex_list.append(vertex);
+    m_vertex_list.append(vertex);
 }
 
 // FIXME: We need to add `r` and `q` to our GLVertex?!

+ 1 - 6
Userland/Libraries/LibGL/SoftwareGLContext.h

@@ -176,10 +176,7 @@ private:
     FloatVector4 m_current_vertex_tex_coord = { 0.0f, 0.0f, 0.0f, 1.0f };
     FloatVector3 m_current_vertex_normal = { 0.0f, 0.0f, 1.0f };
 
-    Vector<GLVertex, 96> vertex_list;
-    Vector<GLTriangle, 32> triangle_list;
-    Vector<GLTriangle, 32> processed_triangles;
-    Vector<GLVertex> m_clipped_vertices;
+    Vector<GLVertex, 96> m_vertex_list;
 
     GLenum m_error = GL_NO_ERROR;
     bool m_in_draw_state = false;
@@ -229,8 +226,6 @@ private:
 
     NonnullRefPtr<Gfx::Bitmap> m_frontbuffer;
 
-    SoftGPU::Clipper m_clipper;
-
     // Texture objects
     TextureNameAllocator m_name_allocator;
     HashMap<GLuint, RefPtr<Texture>> m_allocated_textures;

+ 155 - 1
Userland/Libraries/LibSoftGPU/SoftwareRasterizer.cpp

@@ -1,5 +1,6 @@
 /*
  * Copyright (c) 2021, Stephan Unverwerth <s.unverwerth@serenityos.org>
+ * Copyright (c) 2021, Jesse Buhagiar <jooster669@gmail.com>
  *
  * SPDX-License-Identifier: BSD-2-Clause
  */
@@ -494,7 +495,160 @@ SoftwareRasterizer::SoftwareRasterizer(const Gfx::IntSize& min_size)
     m_options.scissor_box = m_render_target->rect();
 }
 
-void SoftwareRasterizer::submit_triangle(GL::GLTriangle const& triangle, GL::TextureUnit::BoundList const& bound_texture_units)
+void SoftwareRasterizer::draw_primitives(GLenum primitive_type, FloatMatrix4x4 const& transform, FloatMatrix4x4 const& texture_matrix, Vector<GL::GLVertex> const& vertices, GL::TextureUnit::BoundList const& bound_texture_units)
+{
+    // At this point, the user has effectively specified that they are done with defining the geometry
+    // of what they want to draw. We now need to do a few things (https://www.khronos.org/opengl/wiki/Rendering_Pipeline_Overview):
+    //
+    // 1.   Transform all of the vertices in the current vertex list into eye space by mulitplying the model-view matrix
+    // 2.   Transform all of the vertices from eye space into clip space by multiplying by the projection matrix
+    // 3.   If culling is enabled, we cull the desired faces (https://learnopengl.com/Advanced-OpenGL/Face-culling)
+    // 4.   Each element of the vertex is then divided by w to bring the positions into NDC (Normalized Device Coordinates)
+    // 5.   The vertices are sorted (for the rasteriser, how are we doing this? 3Dfx did this top to bottom in terms of vertex y coordinates)
+    // 6.   The vertices are then sent off to the rasteriser and drawn to the screen
+
+    float scr_width = m_render_target->width();
+    float scr_height = m_render_target->height();
+
+    m_triangle_list.clear_with_capacity();
+    m_processed_triangles.clear_with_capacity();
+
+    // Let's construct some triangles
+    if (primitive_type == GL_TRIANGLES) {
+        GL::GLTriangle triangle;
+        for (size_t i = 0; i < vertices.size(); i += 3) {
+            triangle.vertices[0] = vertices.at(i);
+            triangle.vertices[1] = vertices.at(i + 1);
+            triangle.vertices[2] = vertices.at(i + 2);
+
+            m_triangle_list.append(triangle);
+        }
+    } else if (primitive_type == GL_QUADS) {
+        // We need to construct two triangles to form the quad
+        GL::GLTriangle triangle;
+        VERIFY(vertices.size() % 4 == 0);
+        for (size_t i = 0; i < vertices.size(); i += 4) {
+            // Triangle 1
+            triangle.vertices[0] = vertices.at(i);
+            triangle.vertices[1] = vertices.at(i + 1);
+            triangle.vertices[2] = vertices.at(i + 2);
+            m_triangle_list.append(triangle);
+
+            // Triangle 2
+            triangle.vertices[0] = vertices.at(i + 2);
+            triangle.vertices[1] = vertices.at(i + 3);
+            triangle.vertices[2] = vertices.at(i);
+            m_triangle_list.append(triangle);
+        }
+    } else if (primitive_type == GL_TRIANGLE_FAN || primitive_type == GL_POLYGON) {
+        GL::GLTriangle triangle;
+        triangle.vertices[0] = vertices.at(0); // Root vertex is always the vertex defined first
+
+        for (size_t i = 1; i < vertices.size() - 1; i++) // This is technically `n-2` triangles. We start at index 1
+        {
+            triangle.vertices[1] = vertices.at(i);
+            triangle.vertices[2] = vertices.at(i + 1);
+            m_triangle_list.append(triangle);
+        }
+    } else if (primitive_type == GL_TRIANGLE_STRIP) {
+        GL::GLTriangle triangle;
+        for (size_t i = 0; i < vertices.size() - 2; i++) {
+            triangle.vertices[0] = vertices.at(i);
+            triangle.vertices[1] = vertices.at(i + 1);
+            triangle.vertices[2] = vertices.at(i + 2);
+            m_triangle_list.append(triangle);
+        }
+    }
+
+    // Now let's transform each triangle and send that to the GPU
+    for (size_t i = 0; i < m_triangle_list.size(); i++) {
+        GL::GLTriangle& triangle = m_triangle_list.at(i);
+
+        // First multiply the vertex by the MODELVIEW matrix and then the PROJECTION matrix
+        triangle.vertices[0].position = transform * triangle.vertices[0].position;
+        triangle.vertices[1].position = transform * triangle.vertices[1].position;
+        triangle.vertices[2].position = transform * triangle.vertices[2].position;
+
+        // Apply texture transformation
+        // FIXME: implement multi-texturing: texcoords should be stored per texture unit
+        triangle.vertices[0].tex_coord = texture_matrix * triangle.vertices[0].tex_coord;
+        triangle.vertices[1].tex_coord = texture_matrix * triangle.vertices[1].tex_coord;
+        triangle.vertices[2].tex_coord = texture_matrix * triangle.vertices[2].tex_coord;
+
+        // At this point, we're in clip space
+        // Here's where we do the clipping. This is a really crude implementation of the
+        // https://learnopengl.com/Getting-started/Coordinate-Systems
+        // "Note that if only a part of a primitive e.g. a triangle is outside the clipping volume OpenGL
+        // will reconstruct the triangle as one or more triangles to fit inside the clipping range. "
+        //
+        // ALL VERTICES ARE DEFINED IN A CLOCKWISE ORDER
+
+        // Okay, let's do some face culling first
+
+        m_clipped_vertices.clear_with_capacity();
+        m_clipped_vertices.append(triangle.vertices[0]);
+        m_clipped_vertices.append(triangle.vertices[1]);
+        m_clipped_vertices.append(triangle.vertices[2]);
+        m_clipper.clip_triangle_against_frustum(m_clipped_vertices);
+
+        if (m_clipped_vertices.size() < 3)
+            continue;
+
+        for (auto& vec : m_clipped_vertices) {
+            // perspective divide
+            float w = vec.position.w();
+            vec.position.set_x(vec.position.x() / w);
+            vec.position.set_y(vec.position.y() / w);
+            vec.position.set_z(vec.position.z() / w);
+            vec.position.set_w(1 / w);
+
+            // to screen space
+            vec.position.set_x(scr_width / 2 + vec.position.x() * scr_width / 2);
+            vec.position.set_y(scr_height / 2 - vec.position.y() * scr_height / 2);
+        }
+
+        GL::GLTriangle tri;
+        tri.vertices[0] = m_clipped_vertices[0];
+        for (size_t i = 1; i < m_clipped_vertices.size() - 1; i++) {
+            tri.vertices[1] = m_clipped_vertices[i];
+            tri.vertices[2] = m_clipped_vertices[i + 1];
+            m_processed_triangles.append(tri);
+        }
+    }
+
+    for (size_t i = 0; i < m_processed_triangles.size(); i++) {
+        GL::GLTriangle& triangle = m_processed_triangles.at(i);
+
+        // Let's calculate the (signed) area of the triangle
+        // https://cp-algorithms.com/geometry/oriented-triangle-area.html
+        float dxAB = triangle.vertices[0].position.x() - triangle.vertices[1].position.x(); // A.x - B.x
+        float dxBC = triangle.vertices[1].position.x() - triangle.vertices[2].position.x(); // B.X - C.x
+        float dyAB = triangle.vertices[0].position.y() - triangle.vertices[1].position.y();
+        float dyBC = triangle.vertices[1].position.y() - triangle.vertices[2].position.y();
+        float area = (dxAB * dyBC) - (dxBC * dyAB);
+
+        if (area == 0.0f)
+            continue;
+
+        if (m_options.enable_culling) {
+            bool is_front = (m_options.front_face == GL_CCW ? area < 0 : area > 0);
+
+            if (is_front && (m_options.culled_sides == GL_FRONT || m_options.culled_sides == GL_FRONT_AND_BACK))
+                continue;
+
+            if (!is_front && (m_options.culled_sides == GL_BACK || m_options.culled_sides == GL_FRONT_AND_BACK))
+                continue;
+        }
+
+        if (area > 0) {
+            swap(triangle.vertices[0], triangle.vertices[1]);
+        }
+
+        submit_triangle(triangle, bound_texture_units);
+    }
+}
+
+void SoftwareRasterizer::submit_triangle(const GL::GLTriangle& triangle, GL::TextureUnit::BoundList const& bound_texture_units)
 {
     rasterize_triangle(m_options, *m_render_target, *m_depth_buffer, triangle, [this, &bound_texture_units](FloatVector4 const& uv, FloatVector4 const& color, float z) -> FloatVector4 {
         FloatVector4 fragment = color;

+ 10 - 1
Userland/Libraries/LibSoftGPU/SoftwareRasterizer.h

@@ -13,8 +13,10 @@
 #include <LibGL/Tex/Texture2D.h>
 #include <LibGL/Tex/TextureUnit.h>
 #include <LibGfx/Bitmap.h>
+#include <LibGfx/Matrix4x4.h>
 #include <LibGfx/Rect.h>
 #include <LibGfx/Vector4.h>
+#include <LibSoftGPU/Clipper.h>
 #include <LibSoftGPU/DepthBuffer.h>
 
 namespace SoftGPU {
@@ -59,7 +61,7 @@ class SoftwareRasterizer final {
 public:
     SoftwareRasterizer(const Gfx::IntSize& min_size);
 
-    void submit_triangle(GL::GLTriangle const& triangle, GL::TextureUnit::BoundList const& bound_texture_units);
+    void draw_primitives(GLenum primitive_type, FloatMatrix4x4 const& transform, FloatMatrix4x4 const& texture_matrix, Vector<GL::GLVertex> const& vertices, GL::TextureUnit::BoundList const& bound_texture_units);
     void resize(const Gfx::IntSize& min_size);
     void clear_color(const FloatVector4&);
     void clear_depth(float);
@@ -71,10 +73,17 @@ public:
     Gfx::RGBA32 get_backbuffer_pixel(int x, int y);
     float get_depthbuffer_value(int x, int y);
 
+private:
+    void submit_triangle(GL::GLTriangle const& triangle, GL::TextureUnit::BoundList const& bound_texture_units);
+
 private:
     RefPtr<Gfx::Bitmap> m_render_target;
     OwnPtr<DepthBuffer> m_depth_buffer;
     RasterizerOptions m_options;
+    Clipper m_clipper;
+    Vector<GL::GLTriangle, 32> m_triangle_list;
+    Vector<GL::GLTriangle, 32> m_processed_triangles;
+    Vector<GL::GLVertex> m_clipped_vertices;
 };
 
 }