summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJesse Buhagiar <jooster669@gmail.com>2021-01-06 22:58:01 +1100
committerAndreas Kling <kling@serenityos.org>2021-05-08 10:13:22 +0200
commit4807d3213972725563304c5477c9b90d69e0b53b (patch)
tree640df271731a883312208b4e863f146a36c62283
parent1424c4651f9158f435f2985c51b965b6a755c5cd (diff)
downloadserenity-4807d3213972725563304c5477c9b90d69e0b53b.zip
LibGL: Implement a basic OpenGL 1.x compatible library
This currently (obviously) doesn't support any actual 3D hardware, hence all calls are done via software rendering. Note that any modern constructs such as shaders are unsupported, as this driver only implements Fixed Function Pipeline functionality. The library is split into a base GLContext interface and a software based renderer implementation of said interface. The global glXXX functions serve as an OpenGL compatible c-style interface to the currently bound context instance. Co-authored-by: Stephan Unverwerth <s.unverwerth@gmx.de>
-rw-r--r--AK/Debug.h.in4
-rw-r--r--Meta/CMake/all_the_debug_macros.cmake1
-rw-r--r--Userland/Libraries/CMakeLists.txt1
-rw-r--r--Userland/Libraries/LibGL/CMakeLists.txt10
-rw-r--r--Userland/Libraries/LibGL/GL/gl.h81
-rw-r--r--Userland/Libraries/LibGL/GLColor.cpp16
-rw-r--r--Userland/Libraries/LibGL/GLContext.cpp35
-rw-r--r--Userland/Libraries/LibGL/GLContext.h39
-rw-r--r--Userland/Libraries/LibGL/GLMat.cpp56
-rw-r--r--Userland/Libraries/LibGL/GLStruct.h34
-rw-r--r--Userland/Libraries/LibGL/GLUtils.cpp31
-rw-r--r--Userland/Libraries/LibGL/GLVert.cpp36
-rw-r--r--Userland/Libraries/LibGL/SoftwareGLContext.cpp522
-rw-r--r--Userland/Libraries/LibGL/SoftwareGLContext.h54
14 files changed, 920 insertions, 0 deletions
diff --git a/AK/Debug.h.in b/AK/Debug.h.in
index 882f3b011d..adb22891f9 100644
--- a/AK/Debug.h.in
+++ b/AK/Debug.h.in
@@ -150,6 +150,10 @@
#cmakedefine01 GIF_DEBUG
#endif
+#ifndef GL_DEBUG
+#cmakedefine01 GL_DEBUG
+#endif
+
#ifndef GLOBAL_DTORS_DEBUG
#cmakedefine01 GLOBAL_DTORS_DEBUG
#endif
diff --git a/Meta/CMake/all_the_debug_macros.cmake b/Meta/CMake/all_the_debug_macros.cmake
index f570864d55..8db57c43e3 100644
--- a/Meta/CMake/all_the_debug_macros.cmake
+++ b/Meta/CMake/all_the_debug_macros.cmake
@@ -175,6 +175,7 @@ set(SYSCALL_1_DEBUG ON)
set(RSA_PARSE_DEBUG ON)
set(LINE_EDITOR_DEBUG ON)
set(LANGUAGE_SERVER_DEBUG ON)
+set(GL_DEBUG ON)
# False positive: DEBUG is a flag but it works differently.
# set(DEBUG ON)
diff --git a/Userland/Libraries/CMakeLists.txt b/Userland/Libraries/CMakeLists.txt
index 3ad0e4d665..00bab9deca 100644
--- a/Userland/Libraries/CMakeLists.txt
+++ b/Userland/Libraries/CMakeLists.txt
@@ -15,6 +15,7 @@ add_subdirectory(LibDl)
add_subdirectory(LibELF)
add_subdirectory(LibGemini)
add_subdirectory(LibGfx)
+add_subdirectory(LibGL)
add_subdirectory(LibGUI)
add_subdirectory(LibHTTP)
add_subdirectory(LibImageDecoderClient)
diff --git a/Userland/Libraries/LibGL/CMakeLists.txt b/Userland/Libraries/LibGL/CMakeLists.txt
new file mode 100644
index 0000000000..c46a23d189
--- /dev/null
+++ b/Userland/Libraries/LibGL/CMakeLists.txt
@@ -0,0 +1,10 @@
+set(SOURCES
+ GLColor.cpp
+ GLMat.cpp
+ GLUtils.cpp
+ GLVert.cpp
+ SoftwareGLContext.cpp
+)
+
+serenity_lib(LibGL gl)
+target_link_libraries(LibGL LibM LibCore)
diff --git a/Userland/Libraries/LibGL/GL/gl.h b/Userland/Libraries/LibGL/GL/gl.h
new file mode 100644
index 0000000000..e494cf092a
--- /dev/null
+++ b/Userland/Libraries/LibGL/GL/gl.h
@@ -0,0 +1,81 @@
+/*
+ * Copyright (c) 2021, Jesse Buhagiar <jooster669@gmail.com>
+ *
+ * SPDX-License-Identifier: BSD-2-Clause
+ */
+
+#pragma once
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#ifndef GLAPI
+# define GLAPI extern
+#endif
+
+// OpenGL related `defines`
+#define GL_TRUE 1
+#define GL_FALSE 0
+
+// Matrix Modes
+#define GL_MODELVIEW 0x0050
+#define GL_PROJECTION 0x0051
+
+// glBegin/glEnd primitive types
+#define GL_TRIANGLES 0x0100
+#define GL_QUADS 0x0101
+#define GL_TRIANGLE_FAN 0x0102
+#define GL_TRIANGLE_STRIP 0x0103
+#define GL_POLYGON 0x0104
+
+// Buffer bits
+#define GL_COLOR_BUFFER_BIT 0x0200
+
+// Utility
+#define GL_VENDOR 0x1F00
+#define GL_RENDERER 0x1F01
+#define GL_VERSION 0x1F02
+
+//
+// OpenGL typedefs
+//
+// Defines types used by all OpenGL applications
+// https://www.khronos.org/opengl/wiki/OpenGL_Type
+typedef char GLchar;
+typedef unsigned char GLuchar;
+typedef unsigned char GLubyte;
+typedef short GLshort;
+typedef unsigned short GLushort;
+typedef int GLint;
+typedef unsigned int GLuint;
+typedef int GLfixed;
+typedef long long GLint64;
+typedef unsigned long long GLuint64;
+typedef unsigned long GLsizei;
+typedef void GLvoid;
+typedef float GLfloat;
+typedef float GLclampf;
+typedef double GLdouble;
+typedef unsigned int GLenum;
+typedef unsigned int GLbitfield;
+
+GLAPI void glBegin(GLenum mode);
+GLAPI void glClear(GLbitfield mask);
+GLAPI void glClearColor(GLclampf red, GLclampf green, GLclampf blue, GLclampf alpha);
+GLAPI void glColor3f(GLfloat r, GLfloat g, GLfloat b);
+GLAPI void glEnd();
+GLAPI void glFrustum(GLdouble left, GLdouble right, GLdouble bottom, GLdouble top, GLdouble nearVal, GLdouble farVal);
+GLAPI GLubyte* glGetString(GLenum name);
+GLAPI void glLoadIdentity();
+GLAPI void glMatrixMode(GLenum mode);
+GLAPI void glPushMatrix();
+GLAPI void glPopMatrix();
+GLAPI void glRotatef(GLfloat angle, GLfloat x, GLfloat y, GLfloat z);
+GLAPI void glTranslatef(GLfloat x, GLfloat y, GLfloat z);
+GLAPI void glVertex3f(GLfloat x, GLfloat y, GLfloat z);
+GLAPI void glViewport(GLint x, GLint y, GLsizei width, GLsizei height);
+
+#ifdef __cplusplus
+}
+#endif
diff --git a/Userland/Libraries/LibGL/GLColor.cpp b/Userland/Libraries/LibGL/GLColor.cpp
new file mode 100644
index 0000000000..f848b0890f
--- /dev/null
+++ b/Userland/Libraries/LibGL/GLColor.cpp
@@ -0,0 +1,16 @@
+/*
+ * Copyright (c) 2021, Jesse Buhagiar <jooster669@gmail.com>
+ * Copyright (c) 2021, Stephan Unverwerth <s.unverwerth@gmx.de>
+ *
+ * SPDX-License-Identifier: BSD-2-Clause
+ */
+
+#include "GL/gl.h"
+#include "GLContext.h"
+
+extern GL::GLContext* g_gl_context;
+
+void glColor3f(GLfloat r, GLfloat g, GLfloat b)
+{
+ g_gl_context->gl_color(r, g, b, 1.0);
+}
diff --git a/Userland/Libraries/LibGL/GLContext.cpp b/Userland/Libraries/LibGL/GLContext.cpp
new file mode 100644
index 0000000000..c5740ce708
--- /dev/null
+++ b/Userland/Libraries/LibGL/GLContext.cpp
@@ -0,0 +1,35 @@
+/*
+ * 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"
+
+__attribute__((visibility("hidden"))) GL::GLContext* g_gl_context;
+
+namespace GL {
+
+GLContext::~GLContext()
+{
+ if (g_gl_context == this)
+ make_context_current(nullptr);
+}
+
+OwnPtr<GLContext> create_context()
+{
+ auto context = adopt_own(*new GL::SoftwareGLContext());
+
+ if (!g_gl_context)
+ g_gl_context = context;
+
+ return context;
+}
+
+void make_context_current(GLContext* context)
+{
+ g_gl_context = context;
+}
+
+}
diff --git a/Userland/Libraries/LibGL/GLContext.h b/Userland/Libraries/LibGL/GLContext.h
new file mode 100644
index 0000000000..628e277f79
--- /dev/null
+++ b/Userland/Libraries/LibGL/GLContext.h
@@ -0,0 +1,39 @@
+/*
+ * Copyright (c) 2021, Jesse Buhagiar <jooster669@gmail.com>
+ * Copyright (c) 2021, Stephan Unverwerth <s.unverwerth@gmx.de>
+ *
+ * SPDX-License-Identifier: BSD-2-Clause
+ */
+
+#pragma once
+
+#include "GL/gl.h"
+#include <AK/OwnPtr.h>
+
+namespace GL {
+
+class GLContext {
+public:
+ virtual ~GLContext() { }
+
+ virtual void gl_begin(GLenum mode) = 0;
+ virtual void gl_clear(GLbitfield mask) = 0;
+ virtual void gl_clear_color(GLclampf red, GLclampf green, GLclampf blue, GLclampf alpha) = 0;
+ virtual void gl_color(GLdouble r, GLdouble g, GLdouble b, GLdouble a) = 0;
+ virtual void gl_end() = 0;
+ virtual void gl_frustum(GLdouble left, GLdouble right, GLdouble bottom, GLdouble top, GLdouble near_val, GLdouble far_val) = 0;
+ virtual GLubyte* gl_get_string(GLenum name) = 0;
+ virtual void gl_load_identity() = 0;
+ virtual void gl_matrix_mode(GLenum mode) = 0;
+ virtual void gl_push_matrix() = 0;
+ virtual void gl_pop_matrix() = 0;
+ virtual void gl_rotate(GLdouble angle, GLdouble x, GLdouble y, GLdouble z) = 0;
+ virtual void gl_translate(GLdouble x, GLdouble y, GLdouble z) = 0;
+ virtual void gl_vertex(GLdouble x, GLdouble y, GLdouble z, GLdouble w) = 0;
+ virtual void gl_viewport(GLint x, GLint y, GLsizei width, GLsizei height) = 0;
+};
+
+OwnPtr<GLContext> create_context();
+void make_context_current(GLContext*);
+
+}
diff --git a/Userland/Libraries/LibGL/GLMat.cpp b/Userland/Libraries/LibGL/GLMat.cpp
new file mode 100644
index 0000000000..18d7b73570
--- /dev/null
+++ b/Userland/Libraries/LibGL/GLMat.cpp
@@ -0,0 +1,56 @@
+/*
+ * Copyright (c) 2021, Jesse Buhagiar <jooster669@gmail.com>
+ * Copyright (c) 2021, Stephan Unverwerth <s.unverwerth@gmx.de>
+ *
+ * SPDX-License-Identifier: BSD-2-Clause
+ */
+
+#include "GL/gl.h"
+#include "GLContext.h"
+
+extern GL::GLContext* g_gl_context;
+
+void glMatrixMode(GLenum mode)
+{
+ g_gl_context->gl_matrix_mode(mode);
+}
+
+/*
+ * Push the current matrix (based on the current matrix mode)
+ * to its' corresponding matrix stack in the current OpenGL
+ * state context
+ */
+void glPushMatrix()
+{
+ g_gl_context->gl_push_matrix();
+}
+
+/*
+ * Pop a matrix from the corresponding matrix stack into the
+ * corresponding matrix in the state based on the current
+ * matrix mode
+ */
+void glPopMatrix()
+{
+ g_gl_context->gl_pop_matrix();
+}
+
+void glLoadIdentity()
+{
+ g_gl_context->gl_load_identity();
+}
+
+/**
+ * Create a viewing frustum (a.k.a a "Perspective Matrix") in the current matrix. This
+ * is usually done to the projection matrix. The current matrix is then multiplied
+ * by this viewing frustum matrix.
+ *
+ * https://www.khronos.org/registry/OpenGL-Refpages/gl2.1/xhtml/glFrustum.xml
+ *
+ *
+ * FIXME: We need to check for some values that could result in a division by zero
+ */
+void glFrustum(GLdouble left, GLdouble right, GLdouble bottom, GLdouble top, GLdouble nearVal, GLdouble farVal)
+{
+ g_gl_context->gl_frustum(left, right, bottom, top, nearVal, farVal);
+}
diff --git a/Userland/Libraries/LibGL/GLStruct.h b/Userland/Libraries/LibGL/GLStruct.h
new file mode 100644
index 0000000000..ded910aa08
--- /dev/null
+++ b/Userland/Libraries/LibGL/GLStruct.h
@@ -0,0 +1,34 @@
+/*
+ * Copyright (c) 2021, Jesse Buhagiar <jooster669@gmail.com>
+ *
+ * SPDX-License-Identifier: BSD-2-Clause
+ */
+
+#pragma once
+
+#include "GL/gl.h"
+
+namespace GL {
+
+struct GLColor {
+ GLclampf r, g, b, a;
+};
+
+struct GLVertex {
+ GLfloat x, y, z, w;
+ GLfloat r, g, b, a;
+ GLfloat u, v;
+};
+
+struct GLTriangle {
+ GLVertex vertices[3];
+};
+
+struct GLEdge {
+ GLfloat x1;
+ GLfloat y1;
+ GLfloat x2;
+ GLfloat y2;
+};
+
+}
diff --git a/Userland/Libraries/LibGL/GLUtils.cpp b/Userland/Libraries/LibGL/GLUtils.cpp
new file mode 100644
index 0000000000..d128a534a1
--- /dev/null
+++ b/Userland/Libraries/LibGL/GLUtils.cpp
@@ -0,0 +1,31 @@
+/*
+ * Copyright (c) 2021, Jesse Buhagiar <jooster669@gmail.com>
+ * Copyright (c) 2021, Stephan Unverwerth <s.unverwerth@gmx.de>
+ *
+ * SPDX-License-Identifier: BSD-2-Clause
+ */
+
+#include "GL/gl.h"
+#include "GLContext.h"
+
+extern GL::GLContext* g_gl_context;
+
+void glClear(GLbitfield mask)
+{
+ g_gl_context->gl_clear(mask);
+}
+
+void glClearColor(GLclampf red, GLclampf green, GLclampf blue, GLclampf alpha)
+{
+ g_gl_context->gl_clear_color(red, green, blue, alpha);
+}
+
+GLubyte* glGetString(GLenum name)
+{
+ return g_gl_context->gl_get_string(name);
+}
+
+void glViewport(GLint x, GLint y, GLsizei width, GLsizei height)
+{
+ g_gl_context->gl_viewport(x, y, width, height);
+}
diff --git a/Userland/Libraries/LibGL/GLVert.cpp b/Userland/Libraries/LibGL/GLVert.cpp
new file mode 100644
index 0000000000..394c39861f
--- /dev/null
+++ b/Userland/Libraries/LibGL/GLVert.cpp
@@ -0,0 +1,36 @@
+/*
+ * Copyright (c) 2021, Jesse Buhagiar <jooster669@gmail.com>
+ * Copyright (c) 2021, Stephan Unverwerth <s.unverwerth@gmx.de>
+ *
+ * SPDX-License-Identifier: BSD-2-Clause
+ */
+
+#include "GL/gl.h"
+#include "GLContext.h"
+
+extern GL::GLContext* g_gl_context;
+
+void glBegin(GLenum mode)
+{
+ g_gl_context->gl_begin(mode);
+}
+
+void glEnd()
+{
+ g_gl_context->gl_end();
+}
+
+void glVertex3f(GLfloat x, GLfloat y, GLfloat z)
+{
+ g_gl_context->gl_vertex(x, y, z, 1.0);
+}
+
+void glRotatef(GLfloat angle, GLfloat x, GLfloat y, GLfloat z)
+{
+ g_gl_context->gl_rotate(angle, x, y, z);
+}
+
+void glTranslatef(GLfloat x, GLfloat y, GLfloat z)
+{
+ g_gl_context->gl_translate(x, y, z);
+}
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);
+}
+
+}
diff --git a/Userland/Libraries/LibGL/SoftwareGLContext.h b/Userland/Libraries/LibGL/SoftwareGLContext.h
new file mode 100644
index 0000000000..a8c1d7df8b
--- /dev/null
+++ b/Userland/Libraries/LibGL/SoftwareGLContext.h
@@ -0,0 +1,54 @@
+/*
+ * Copyright (c) 2021, Stephan Unverwerth <s.unverwerth@gmx.de>
+ *
+ * SPDX-License-Identifier: BSD-2-Clause
+ */
+
+#pragma once
+
+#include "GLContext.h"
+#include "GLStruct.h"
+#include <AK/Vector.h>
+#include <LibGfx/Matrix4x4.h>
+#include <LibGfx/Vector3.h>
+
+namespace GL {
+
+class SoftwareGLContext : public GLContext {
+public:
+ virtual void gl_begin(GLenum mode) override;
+ virtual void gl_clear(GLbitfield mask) override;
+ virtual void gl_clear_color(GLclampf red, GLclampf green, GLclampf blue, GLclampf alpha) override;
+ virtual void gl_color(GLdouble r, GLdouble g, GLdouble b, GLdouble a) override;
+ virtual void gl_end() override;
+ virtual void gl_frustum(GLdouble left, GLdouble right, GLdouble bottom, GLdouble top, GLdouble near_val, GLdouble far_val) override;
+ virtual GLubyte* gl_get_string(GLenum name) override;
+ virtual void gl_load_identity() override;
+ virtual void gl_matrix_mode(GLenum mode) override;
+ virtual void gl_push_matrix() override;
+ virtual void gl_pop_matrix() override;
+ virtual void gl_rotate(GLdouble angle, GLdouble x, GLdouble y, GLdouble z) override;
+ virtual void gl_translate(GLdouble x, GLdouble y, GLdouble z) override;
+ virtual void gl_vertex(GLdouble x, GLdouble y, GLdouble z, GLdouble w) override;
+ virtual void gl_viewport(GLint x, GLint y, GLsizei width, GLsizei height) override;
+
+private:
+ GLenum m_current_draw_mode;
+ GLenum m_current_matrix_mode;
+ FloatMatrix4x4 m_projection_matrix;
+ FloatMatrix4x4 m_model_view_matrix;
+
+ FloatMatrix4x4 m_current_matrix;
+
+ Vector<FloatMatrix4x4> m_projection_matrix_stack;
+ Vector<FloatMatrix4x4> m_model_view_matrix_stack;
+
+ FloatVector4 m_clear_color = { 0.0f, 0.0f, 0.0f, 0.0f };
+ FloatVector4 m_current_vertex_color = { 1.0f, 1.0f, 1.0f, 1.0f };
+
+ Vector<GLVertex> vertex_list;
+ Vector<GLTriangle> triangle_list;
+ Vector<GLTriangle> processed_triangles;
+};
+
+}