diff --git a/Userland/Libraries/LibGL/SoftwareGLContext.cpp b/Userland/Libraries/LibGL/SoftwareGLContext.cpp index 76f80a6147..94b01b7d88 100644 --- a/Userland/Libraries/LibGL/SoftwareGLContext.cpp +++ b/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(); + // 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) { - // 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); - - 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); + 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); - // 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.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?! diff --git a/Userland/Libraries/LibGL/SoftwareGLContext.h b/Userland/Libraries/LibGL/SoftwareGLContext.h index 9e53812534..1f96290988 100644 --- a/Userland/Libraries/LibGL/SoftwareGLContext.h +++ b/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 vertex_list; - Vector triangle_list; - Vector processed_triangles; - Vector m_clipped_vertices; + Vector m_vertex_list; GLenum m_error = GL_NO_ERROR; bool m_in_draw_state = false; @@ -229,8 +226,6 @@ private: NonnullRefPtr m_frontbuffer; - SoftGPU::Clipper m_clipper; - // Texture objects TextureNameAllocator m_name_allocator; HashMap> m_allocated_textures; diff --git a/Userland/Libraries/LibSoftGPU/SoftwareRasterizer.cpp b/Userland/Libraries/LibSoftGPU/SoftwareRasterizer.cpp index 2214a89b65..c582256434 100644 --- a/Userland/Libraries/LibSoftGPU/SoftwareRasterizer.cpp +++ b/Userland/Libraries/LibSoftGPU/SoftwareRasterizer.cpp @@ -1,5 +1,6 @@ /* * Copyright (c) 2021, Stephan Unverwerth + * Copyright (c) 2021, Jesse Buhagiar * * 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 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; diff --git a/Userland/Libraries/LibSoftGPU/SoftwareRasterizer.h b/Userland/Libraries/LibSoftGPU/SoftwareRasterizer.h index 2e52d7b3c8..25f1bc9950 100644 --- a/Userland/Libraries/LibSoftGPU/SoftwareRasterizer.h +++ b/Userland/Libraries/LibSoftGPU/SoftwareRasterizer.h @@ -13,8 +13,10 @@ #include #include #include +#include #include #include +#include #include 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 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 m_render_target; OwnPtr m_depth_buffer; RasterizerOptions m_options; + Clipper m_clipper; + Vector m_triangle_list; + Vector m_processed_triangles; + Vector m_clipped_vertices; }; }