summaryrefslogtreecommitdiff
path: root/Userland/Libraries/LibGL/SoftwareGLContext.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'Userland/Libraries/LibGL/SoftwareGLContext.cpp')
-rw-r--r--Userland/Libraries/LibGL/SoftwareGLContext.cpp522
1 files changed, 522 insertions, 0 deletions
diff --git a/Userland/Libraries/LibGL/SoftwareGLContext.cpp b/Userland/Libraries/LibGL/SoftwareGLContext.cpp
new file mode 100644
index 0000000000..bad3f8dc58
--- /dev/null
+++ b/Userland/Libraries/LibGL/SoftwareGLContext.cpp
@@ -0,0 +1,522 @@
+/*
+ * Copyright (c) 2021, Jesse Buhagiar <jooster669@gmail.com>
+ * Copyright (c) 2021, Stephan Unverwerth <s.unverwerth@gmx.de>
+ *
+ * SPDX-License-Identifier: BSD-2-Clause
+ */
+
+#include "SoftwareGLContext.h"
+#include "GLStruct.h"
+#include <AK/Assertions.h>
+#include <AK/Debug.h>
+#include <AK/Format.h>
+#include <AK/QuickSort.h>
+#include <AK/Vector.h>
+#include <LibGfx/Vector4.h>
+#include <math.h>
+
+using AK::dbgln;
+
+namespace GL {
+
+static constexpr size_t NUM_CLIP_PLANES = 6;
+
+static FloatVector4 clip_planes[] = {
+ { -1, 0, 0, 1 }, // Left Plane
+ { 1, 0, 0, 1 }, // Right Plane
+ { 0, 1, 0, 1 }, // Top Plane
+ { 0, -1, 0, 1 }, // Bottom plane
+ { 0, 0, 1, 1 }, // Near Plane
+ { 0, 0, -1, 1 } // Far Plane
+};
+
+static FloatVector4 clip_plane_normals[] = {
+ { 1, 0, 0, 1 }, // Left Plane
+ { -1, 0, 0, 1 }, // Right Plane
+ { 0, -1, 0, 1 }, // Top Plane
+ { 0, 1, 0, 1 }, // Bottom plane
+ { 0, 0, -1, 1 }, // Near Plane
+ { 0, 0, 1, 1 } // Far Plane
+};
+
+enum ClippingPlane {
+ LEFT = 0,
+ RIGHT = 1,
+ TOP = 2,
+ BOTTOM = 3,
+ NEAR = 4,
+ FAR = 5
+};
+
+// FIXME: Change this to accept a vertex!
+// Determines whether or not a vertex is inside the frustum for a given plane
+static bool vert_inside_plane(const FloatVector4& vec, ClippingPlane plane)
+{
+ switch (plane) {
+ case ClippingPlane::LEFT:
+ return vec.x() > -vec.w();
+ case ClippingPlane::RIGHT:
+ return vec.x() < vec.w();
+ case ClippingPlane::TOP:
+ return vec.y() < vec.w();
+ case ClippingPlane::BOTTOM:
+ return vec.y() > -vec.w();
+ case ClippingPlane::NEAR:
+ return vec.z() > -vec.w();
+ case ClippingPlane::FAR:
+ return vec.z() < vec.w();
+ }
+
+ return false;
+}
+
+// FIXME: This needs to interpolate color/UV data as well!
+static FloatVector4 clip_intersection_point(const FloatVector4& vec, const FloatVector4& prev_vec, ClippingPlane plane_index)
+{
+ // https://github.com/fogleman/fauxgl/blob/master/clipping.go#L20
+ FloatVector4 u, w;
+ FloatVector4 ret = prev_vec;
+ FloatVector4 plane = clip_planes[plane_index];
+ FloatVector4 plane_normal = clip_plane_normals[plane_index];
+
+ u = vec;
+ u -= prev_vec;
+ w = prev_vec;
+ w -= plane;
+ float d = plane_normal.dot(u);
+ float n = -plane_normal.dot(w);
+
+ ret += (u * (n / d));
+ return ret;
+}
+
+// https://groups.csail.mit.edu/graphics/classes/6.837/F04/lectures/07_Pipeline_II.pdf
+// This is a really rough implementation of the Sutherland-Hodgman algorithm in clip-space
+static void clip_triangle_against_frustum(Vector<FloatVector4>& in_vec)
+{
+ Vector<FloatVector4> clipped_polygon = in_vec; // in_vec = subjectPolygon, clipped_polygon = outputList
+ for (size_t i = 0; i < NUM_CLIP_PLANES; i++) // Test against each clip plane
+ {
+ ClippingPlane plane = static_cast<ClippingPlane>(i); // Hahaha, what the fuck
+ in_vec = clipped_polygon;
+ clipped_polygon.clear();
+
+ // Prevent a crash from .at() undeflow
+ if (in_vec.size() == 0)
+ return;
+
+ FloatVector4 prev_vec = in_vec.at(in_vec.size() - 1);
+
+ for (size_t j = 0; j < in_vec.size(); j++) // Perform this for each vertex
+ {
+ const FloatVector4& vec = in_vec.at(j);
+ if (vert_inside_plane(vec, plane)) {
+ if (!vert_inside_plane(prev_vec, plane)) {
+ FloatVector4 intersect = clip_intersection_point(prev_vec, vec, plane);
+ clipped_polygon.append(intersect);
+ }
+
+ clipped_polygon.append(vec);
+ } else if (vert_inside_plane(prev_vec, plane)) {
+ FloatVector4 intersect = clip_intersection_point(prev_vec, vec, plane);
+ clipped_polygon.append(intersect);
+ }
+
+ prev_vec = vec;
+ }
+ }
+}
+
+void SoftwareGLContext::gl_begin(GLenum mode)
+{
+ m_current_draw_mode = mode;
+}
+
+void SoftwareGLContext::gl_clear(GLbitfield mask)
+{
+ if (mask & GL_COLOR_BUFFER_BIT) {
+ uint8_t r = static_cast<uint8_t>(floor(m_clear_color.x() * 255.0f));
+ uint8_t g = static_cast<uint8_t>(floor(m_clear_color.y() * 255.0f));
+ uint8_t b = static_cast<uint8_t>(floor(m_clear_color.z() * 255.0f));
+
+ uint64_t color = r << 16 | g << 8 | b;
+ (void)(color);
+ } else {
+ // set gl error here!?
+ }
+}
+
+void SoftwareGLContext::gl_clear_color(GLclampf red, GLclampf green, GLclampf blue, GLclampf alpha)
+{
+ m_clear_color = { red, green, blue, alpha };
+}
+
+void SoftwareGLContext::gl_color(GLdouble r, GLdouble g, GLdouble b, GLdouble a)
+{
+ m_current_vertex_color = { (float)r, (float)g, (float)b, (float)a };
+}
+
+void SoftwareGLContext::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 co-ordinates)
+ // 6. The vertices are then sent off to the rasteriser and drawn to the screen
+
+ // FIXME: Don't assume screen dimensions
+ float scr_width = 640.0f;
+ float scr_height = 480.0f;
+
+ // 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) {
+ 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 {
+ VERIFY_NOT_REACHED();
+ }
+
+ // 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);
+ GLVertex& vertexa = triangle.vertices[0];
+ GLVertex& vertexb = triangle.vertices[1];
+ GLVertex& vertexc = triangle.vertices[2];
+
+ FloatVector4 veca({ vertexa.x, vertexa.y, vertexa.z, 1.0f });
+ FloatVector4 vecb({ vertexb.x, vertexb.y, vertexb.z, 1.0f });
+ FloatVector4 vecc({ vertexc.x, vertexc.y, vertexc.z, 1.0f });
+
+ // First multiply the vertex by the MODELVIEW matrix and then the PROJECTION matrix
+ veca = m_model_view_matrix * veca;
+ veca = m_projection_matrix * veca;
+
+ vecb = m_model_view_matrix * vecb;
+ vecb = m_projection_matrix * vecb;
+
+ vecc = m_model_view_matrix * vecc;
+ vecc = m_projection_matrix * vecc;
+
+ // 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
+
+ Vector<FloatVector4> vecs;
+ Vector<GLVertex> verts;
+
+ vecs.append(veca);
+ vecs.append(vecb);
+ vecs.append(vecc);
+ clip_triangle_against_frustum(vecs);
+
+ // TODO: Copy color and UV information too!
+ for (size_t vec_idx = 0; vec_idx < vecs.size(); vec_idx++) {
+ FloatVector4& vec = vecs.at(vec_idx);
+ GLVertex vertex;
+
+ // Perform the perspective divide
+ if (vec.w() != 0.0f) {
+ vec.set_x(vec.x() / vec.w());
+ vec.set_y(vec.y() / vec.w());
+ vec.set_z(vec.z() / vec.w());
+ }
+
+ vertex.x = vec.x();
+ vertex.y = vec.y();
+ vertex.z = vec.z();
+ vertex.w = vec.w();
+
+ // FIXME: This is to suppress any -Wunused errors
+ vertex.u = 0.0f;
+ vertex.v = 0.0f;
+
+ if (vec_idx == 0) {
+ vertex.r = vertexa.r;
+ vertex.g = vertexa.g;
+ vertex.b = vertexa.b;
+ vertex.a = vertexa.a;
+ } else if (vec_idx == 1) {
+ vertex.r = vertexb.r;
+ vertex.g = vertexb.g;
+ vertex.b = vertexb.b;
+ vertex.a = vertexb.a;
+ } else {
+ vertex.r = vertexc.r;
+ vertex.g = vertexc.g;
+ vertex.b = vertexc.b;
+ vertex.a = vertexc.a;
+ }
+
+ vertex.x = (vec.x() + 1.0f) * (scr_width / 2.0f) + 0.0f; // TODO: 0.0f should be something!?
+ vertex.y = scr_height - ((vec.y() + 1.0f) * (scr_height / 2.0f) + 0.0f);
+ vertex.z = vec.z();
+ verts.append(vertex);
+ }
+
+ if (verts.size() == 0) {
+ continue;
+ } else if (verts.size() == 3) {
+ GLTriangle tri;
+
+ tri.vertices[0] = verts.at(0);
+ tri.vertices[1] = verts.at(1);
+ tri.vertices[2] = verts.at(2);
+ processed_triangles.append(tri);
+ } else if (verts.size() == 4) {
+ GLTriangle tri1;
+ GLTriangle tri2;
+
+ tri1.vertices[0] = verts.at(0);
+ tri1.vertices[1] = verts.at(1);
+ tri1.vertices[2] = verts.at(2);
+ processed_triangles.append(tri1);
+
+ tri2.vertices[0] = verts.at(0);
+ tri2.vertices[1] = verts.at(2);
+ tri2.vertices[2] = verts.at(3);
+ processed_triangles.append(tri2);
+ }
+ }
+
+ for (size_t i = 0; i < processed_triangles.size(); i++) {
+ Vector<GLVertex> sort_vert_list;
+ GLTriangle& triangle = processed_triangles.at(i);
+
+ // Now we sort the vertices by their y values. A is the vertex that has the least y value,
+ // B is the middle and C is the bottom.
+ // These are sorted in groups of 3
+ sort_vert_list.append(triangle.vertices[0]);
+ sort_vert_list.append(triangle.vertices[1]);
+ sort_vert_list.append(triangle.vertices[2]);
+
+ AK::quick_sort(sort_vert_list.begin(), sort_vert_list.end(), [](auto& a, auto& b) { return a.y < b.y; });
+
+ triangle.vertices[0] = sort_vert_list.at(0);
+ triangle.vertices[1] = sort_vert_list.at(1);
+ triangle.vertices[2] = sort_vert_list.at(2);
+
+ // Let's calculate the (signed) area of the triangle
+ // https://cp-algorithms.com/geometry/oriented-triangle-area.html
+ float dxAB = triangle.vertices[0].x - triangle.vertices[1].x; // A.x - B.x
+ float dxBC = triangle.vertices[1].x - triangle.vertices[2].x; // B.X - C.x
+ float dyAB = triangle.vertices[0].y - triangle.vertices[1].y;
+ float dyBC = triangle.vertices[1].y - triangle.vertices[2].y;
+ float area = (dxAB * dyBC) - (dxBC * dyAB);
+
+ if (area == 0.0f)
+ continue;
+
+ int32_t vertexAx = triangle.vertices[0].x;
+ int32_t vertexAy = triangle.vertices[0].y;
+ int32_t vertexBx = triangle.vertices[1].x;
+ int32_t vertexBy = triangle.vertices[1].y;
+ int32_t vertexCx = triangle.vertices[2].x;
+ int32_t vertexCy = triangle.vertices[2].y;
+ (void)(vertexAx);
+ (void)(vertexAy);
+ (void)(vertexBx);
+ (void)(vertexBy);
+ (void)(vertexCx);
+ (void)(vertexCy);
+ }
+
+ triangle_list.clear();
+ processed_triangles.clear();
+ vertex_list.clear();
+}
+
+void SoftwareGLContext::gl_frustum(GLdouble left, GLdouble right, GLdouble bottom, GLdouble top, GLdouble near_val, GLdouble far_val)
+{
+ // Let's do some math!
+ // FIXME: Are we losing too much precision by doing this?
+ float a = static_cast<float>((right + left) / (right - left));
+ float b = static_cast<float>((top + bottom) / (top - bottom));
+ float c = static_cast<float>(-((far_val + near_val) / (far_val - near_val)));
+ float d = static_cast<float>(-((2 * (far_val * near_val)) / (far_val - near_val)));
+
+ FloatMatrix4x4 frustum {
+ ((2 * (float)near_val) / ((float)right - (float)left)), 0, a, 0,
+ 0, ((2 * (float)near_val) / ((float)top - (float)bottom)), b, 0,
+ 0, 0, c, d,
+ 0, 0, -1, 0
+ };
+
+ if (m_current_matrix_mode == GL_PROJECTION) {
+ m_projection_matrix = m_projection_matrix * frustum;
+ } else if (m_current_matrix_mode == GL_MODELVIEW) {
+ dbgln_if(GL_DEBUG, "glFrustum(): frustum created with curr_matrix_mode == GL_MODELVIEW!!!");
+ m_projection_matrix = m_model_view_matrix * frustum;
+ }
+}
+
+GLubyte* SoftwareGLContext::gl_get_string(GLenum name)
+{
+ switch (name) {
+ case GL_VENDOR:
+ return reinterpret_cast<GLubyte*>(const_cast<char*>("The SerenityOS Developers"));
+ case GL_RENDERER:
+ return reinterpret_cast<GLubyte*>(const_cast<char*>("SerenityOS OpenGL"));
+ case GL_VERSION:
+ return reinterpret_cast<GLubyte*>(const_cast<char*>("OpenGL 1.2 SerenityOS"));
+ default:
+ dbgln_if(GL_DEBUG, "glGetString(): Unknown enum name!");
+ break;
+ }
+
+ // FIXME: Set glError to GL_INVALID_ENUM here
+ return nullptr;
+}
+
+void SoftwareGLContext::gl_load_identity()
+{
+ if (m_current_matrix_mode == GL_PROJECTION)
+ m_projection_matrix = FloatMatrix4x4::identity();
+ else if (m_current_matrix_mode == GL_MODELVIEW)
+ m_model_view_matrix = FloatMatrix4x4::identity();
+ else
+ VERIFY_NOT_REACHED();
+}
+
+void SoftwareGLContext::gl_matrix_mode(GLenum mode)
+{
+ VERIFY(mode == GL_MODELVIEW || mode == GL_PROJECTION);
+ m_current_matrix_mode = mode;
+}
+
+void SoftwareGLContext::gl_push_matrix()
+{
+ dbgln_if(GL_DEBUG, "glPushMatrix(): Pushing matrix to the matrix stack (matrix_mode {})", m_current_matrix_mode);
+
+ switch (m_current_matrix_mode) {
+ case GL_PROJECTION:
+ m_projection_matrix_stack.append(m_projection_matrix);
+ break;
+ case GL_MODELVIEW:
+ m_model_view_matrix_stack.append(m_model_view_matrix);
+ break;
+ default:
+ dbgln_if(GL_DEBUG, "glPushMatrix(): Attempt to push matrix with invalid matrix mode {})", m_current_matrix_mode);
+ return;
+ }
+}
+
+void SoftwareGLContext::gl_pop_matrix()
+{
+ dbgln_if(GL_DEBUG, "glPopMatrix(): Popping matrix from matrix stack (matrix_mode = {})", m_current_matrix_mode);
+
+ // FIXME: Make sure stack::top() doesn't cause any nasty issues if it's empty (that could result in a lockup/hang)
+ switch (m_current_matrix_mode) {
+ case GL_PROJECTION:
+ m_projection_matrix = m_projection_matrix_stack.take_last();
+ break;
+ case GL_MODELVIEW:
+ m_model_view_matrix = m_model_view_matrix_stack.take_last();
+ break;
+ default:
+ dbgln_if(GL_DEBUG, "glPopMatrix(): Attempt to pop matrix with invalid matrix mode, {}", m_current_matrix_mode);
+ return;
+ }
+}
+
+void SoftwareGLContext::gl_rotate(GLdouble angle, GLdouble x, GLdouble y, GLdouble z)
+{
+ FloatVector3 axis = { (float)x, (float)y, (float)z };
+ axis.normalize();
+ auto rotation_mat = FloatMatrix4x4::rotate(axis, angle);
+
+ if (m_current_matrix_mode == GL_MODELVIEW)
+ m_model_view_matrix = m_model_view_matrix * rotation_mat;
+ else if (m_current_matrix_mode == GL_PROJECTION)
+ m_projection_matrix = m_projection_matrix * rotation_mat;
+}
+
+void SoftwareGLContext::gl_translate(GLdouble x, GLdouble y, GLdouble z)
+{
+ if (m_current_matrix_mode == GL_MODELVIEW) {
+ m_model_view_matrix = m_model_view_matrix * FloatMatrix4x4::translate({ (float)x, (float)y, (float)z });
+ } else if (m_current_matrix_mode == GL_PROJECTION) {
+ m_projection_matrix = m_projection_matrix * FloatMatrix4x4::translate({ (float)x, (float)y, (float)z });
+ }
+}
+
+void SoftwareGLContext::gl_vertex(GLdouble x, GLdouble y, GLdouble z, GLdouble w)
+{
+ GLVertex vertex;
+
+ vertex.x = x;
+ vertex.y = y;
+ vertex.z = z;
+ vertex.w = w;
+ vertex.r = m_current_vertex_color.x();
+ vertex.g = m_current_vertex_color.y();
+ vertex.b = m_current_vertex_color.z();
+ vertex.a = m_current_vertex_color.w();
+
+ // FIXME: This is to suppress any -Wunused errors
+ vertex.w = 0.0f;
+ vertex.u = 0.0f;
+ vertex.v = 0.0f;
+
+ vertex_list.append(vertex);
+}
+
+void SoftwareGLContext::gl_viewport(GLint x, GLint y, GLsizei width, GLsizei height)
+{
+ (void)(x);
+ (void)(y);
+ (void)(width);
+ (void)(height);
+}
+
+}