Implement functional OpenGL 3.3 renderer

w/ crude batching
This commit is contained in:
2022-11-27 18:04:46 +11:00
parent e91404b02f
commit 5ee752669b
6 changed files with 531 additions and 23 deletions

29
cmake/BinHelper.cmake Normal file
View File

@ -0,0 +1,29 @@
include(CMakeParseArguments) # 3.4 and lower compatibility
find_package(Python REQUIRED COMPONENTS Interpreter)
function (bin2h_compile)
set(oneValueArgs OUTPUT)
set(multiValueArgs BIN TXT)
cmake_parse_arguments(ARGS "" "${oneValueArgs}" "${multiValueArgs}" ${ARGN})
find_file(BIN2H_EXECUTABLE bin2h.py PATHS ${CMAKE_SOURCE_DIR}/tools)
set(DEPENDS)
set(COMMAND ${BIN2H_EXECUTABLE})
foreach (BIN ${ARGS_BIN})
set(SOURCE ${CMAKE_CURRENT_SOURCE_DIR}/${BIN})
list(APPEND DEPENDS ${SOURCE})
list(APPEND COMMAND "-b" "${SOURCE}")
endforeach()
foreach (TXT ${ARGS_TXT})
set(SOURCE ${CMAKE_CURRENT_SOURCE_DIR}/${TXT})
list(APPEND DEPENDS ${SOURCE})
list(APPEND COMMAND "-t" "${SOURCE}")
endforeach()
list(APPEND COMMAND ${ARGS_OUTPUT})
add_custom_command(COMMAND Python::Interpreter ARGS ${COMMAND}
DEPENDS Python::Interpreter ${BIN2H_EXECUTABLE} ${DEPENDS}
OUTPUT ${ARGS_OUTPUT})
endfunction()

View File

@ -12,9 +12,14 @@ set(SOURCES
if (USE_OPENGL)
include(GL3WHelper)
add_gl3w(gl3w)
include(BinHelper)
bin2h_compile(OUTPUT glslShaders.h TXT vert.glsl frag.glsl)
list(APPEND SOURCES ${CMAKE_CURRENT_BINARY_DIR}/glslShaders.h)
endif()
add_executable(${TARGET} ${SOURCES})
target_include_directories(${TARGET} PRIVATE
$<$<BOOL:${USE_OPENGL}>:${CMAKE_CURRENT_BINARY_DIR}>)
target_link_libraries(${TARGET}
$<$<PLATFORM_ID:Windows>:SDL2::SDL2main>
SDL2::SDL2

View File

@ -1,20 +1,47 @@
#include "draw.h"
#include "glslShaders.h"
#include "maths.h"
#include <GL/gl3w.h>
#include <SDL_video.h>
#include <stdbool.h>
#include <stdio.h>
typedef struct { float x, y; } vertex;
enum { ATTRIB_VERTPOS, NUM_ATTRIBS };
static const char* const attribNames[] =
{
[ATTRIB_VERTPOS] = "inPos",
// [ATTRIB_COLOUR] = "inColour"
};
#define OPENGL_VERSION_MAJOR 3
#define OPENGL_VERSION_MINOR 3
static SDL_GLContext* ctx = NULL;
static SDL_Window* window = NULL;
static uint32_t colour = 0x00000000;
static uint32_t clrColour = 0x00000000;
static uint32_t
colour = 0x00000000,
drawColour = 0x00000000,
clrColour = 0x00000000;
static bool antialias = false;
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wunused-function"
#define DRAWLIST_MAX_SIZE 480
static vertex drawListVerts[DRAWLIST_MAX_SIZE];
static uint16_t drawListIndices[DRAWLIST_MAX_SIZE];
static uint16_t drawListCount = 0, drawListVertNum = 0;
static GLuint vao = 0, drawListVbo = 0, drawListIbo = 0;
static GLuint program = 0;
static GLint uView, uColour;
#if DRAWLIST_MAX_SIZE < 2 || DRAWLIST_MAX_SIZE >= UINT16_MAX
#error DRAWLIST_MAX_SIZE must be larger than 1 and smaller than 65535
#endif
#if !defined NDEBUG && !defined __APPLE__
static void GlErrorCb(
GLenum source,
GLenum type,
@ -27,18 +54,81 @@ static void GlErrorCb(
if (severity != GL_DEBUG_SEVERITY_NOTIFICATION)
printf("%s\n", message);
}
#pragma clang diagnostic pop
#endif
void DrawWindowHints(void)
{
SDL_GL_SetAttribute(SDL_GL_MULTISAMPLEBUFFERS, 1); // Enable MSAA
SDL_GL_SetAttribute(SDL_GL_MULTISAMPLESAMPLES, 8); // 8x MSAA
SDL_GL_SetAttribute(SDL_GL_MULTISAMPLESAMPLES, 16); // 16x MSAA
// Legacy OpenGL profile
// Modern OpenGL profile
SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, OPENGL_VERSION_MAJOR);
SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, OPENGL_VERSION_MINOR);
SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, SDL_GL_CONTEXT_PROFILE_CORE);
SDL_GL_SetAttribute(SDL_GL_ALPHA_SIZE, 0);
SDL_GL_SetAttribute(SDL_GL_DOUBLEBUFFER, 1);
}
static inline GLuint CompilerShader(const char* src, GLenum type)
{
GLuint shader = glCreateShader(type);
glShaderSource(shader, 1, &src, NULL);
glCompileShader(shader);
int res, logLen;
glGetShaderiv(shader, GL_COMPILE_STATUS, &res);
if (res != GL_TRUE)
{
glGetShaderiv(shader, GL_INFO_LOG_LENGTH, &logLen);
if (logLen > 0)
{
char* log = malloc(logLen);
glGetShaderInfoLog(shader, logLen, NULL, log);
fprintf(stderr, "Shader type %#06X compilation failed:\n%s\n", type, log);
free(log);
}
glDeleteShader(shader);
return 0;
}
return shader;
}
static inline GLuint LinkProgram(
GLuint vertShader, GLuint fragShader,
const char* const attrNames[], GLuint attrCount)
{
GLuint progId = glCreateProgram();
// Bind attributes
for (GLuint i = 0; i < attrCount; ++i)
glBindAttribLocation(progId, i, (const GLchar*)attrNames[i]);
// Attach shaders & link program
glAttachShader(progId, vertShader);
glAttachShader(progId, fragShader);
glLinkProgram(progId);
int res, logLen;
glGetProgramiv(progId, GL_LINK_STATUS, &res);
if (res != GL_TRUE)
{
glGetProgramiv(progId, GL_INFO_LOG_LENGTH, &logLen);
if (logLen > 0)
{
char* log = malloc(logLen);
glGetProgramInfoLog(progId, logLen, NULL, log);
fprintf(stderr, "Program link failed:\n%s\n", log);
free(log);
}
glDeleteProgram(progId);
return 0;
}
return progId;
}
int InitDraw(SDL_Window* _window)
@ -51,6 +141,14 @@ int InitDraw(SDL_Window* _window)
return -1;
}
SDL_GL_SetSwapInterval(1); // Enable vsync
// Detect if MSAA is available & active
int res;
if (SDL_GL_GetAttribute(SDL_GL_MULTISAMPLEBUFFERS, &res) == 0 && res == 1)
if (SDL_GL_GetAttribute(SDL_GL_MULTISAMPLESAMPLES, &res) == 0 && res > 0)
antialias = true;
// Load Core profile extensions
if (gl3wInit() != GL3W_OK)
{
@ -65,23 +163,98 @@ int InitDraw(SDL_Window* _window)
// Set debug callback
#if !defined NDEBUG && !defined __APPLE__
glDebugMessageCallback(GlErrorCb, nullptr);
glDebugMessageCallback(GlErrorCb, NULL);
glEnable(GL_DEBUG_OUTPUT);
#endif
SDL_GL_SetSwapInterval(1); // Enable vsync
// Ensure culling & depth testing are off
glDisable(GL_CULL_FACE);
glDisable(GL_DEPTH_TEST);
//glEnable(GL_BLEND); // These will be relevant if proper anti aliased line drawing is implemented
//glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
//glBlendEquation(GL_FUNC_ADD);
// Detect if MSAA is available & active
int res;
if (SDL_GL_GetAttribute(SDL_GL_MULTISAMPLEBUFFERS, &res) == 0 && res == 1)
if (SDL_GL_GetAttribute(SDL_GL_MULTISAMPLESAMPLES, &res) == 0 && res > 0)
antialias = true;
// Compile shaders
GLuint vert = CompilerShader(vert_glsl, GL_VERTEX_SHADER);
if (!vert)
return -1;
GLuint frag = CompilerShader(frag_glsl, GL_FRAGMENT_SHADER);
if (!frag)
{
glDeleteShader(vert);
return -1;
}
// Link program
program = LinkProgram(vert, frag, attribNames, NUM_ATTRIBS);
glDeleteShader(frag);
glDeleteShader(vert);
if (!program)
return -1;
// Get uniforms
uView = glGetUniformLocation(program, "uView");
uColour = glGetUniformLocation(program, "uColour");
glUseProgram(program); // Use program
// Create & bind vertex attribute array
glGenVertexArrays(1, &vao);
glBindVertexArray(vao);
// Setup draw list
glGenBuffers(1, &drawListVbo);
glBindBuffer(GL_ARRAY_BUFFER, drawListVbo);
glGenBuffers(1, &drawListIbo);
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, drawListIbo);
glBufferData(GL_ARRAY_BUFFER, DRAWLIST_MAX_SIZE * sizeof(vertex), NULL, GL_DYNAMIC_DRAW);
glBufferData(GL_ELEMENT_ARRAY_BUFFER, DRAWLIST_MAX_SIZE * sizeof(uint16_t), NULL, GL_DYNAMIC_DRAW);
// Setup draw list vertex attributes
glEnableVertexAttribArray(ATTRIB_VERTPOS);
glVertexAttribPointer(ATTRIB_VERTPOS, 2, GL_FLOAT, GL_FALSE, sizeof(vertex), (GLvoid*)0);
// Reset viewport & clear
SetDrawViewport(GetDrawSizeInPixels());
glUniform4f(uColour, 1.0f, 1.0f, 1.0f, 1.0f);
glClearColor(0.0f, 0.0f, 0.0f, 0.0f);
glClear(GL_COLOR_BUFFER_BIT);
return 0;
}
void QuitDraw(void)
{
if (drawListVbo)
{
glBindBuffer(GL_ARRAY_BUFFER, 0);
glDeleteBuffers(1, &drawListVbo);
drawListVbo = 0;
}
if (drawListIbo)
{
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);
glDeleteBuffers(1, &drawListIbo);
drawListIbo = 0;
}
if (vao)
{
glDisableVertexAttribArray(ATTRIB_VERTPOS);
glBindVertexArray(0);
glDeleteVertexArrays(1, &vao);
vao = 0;
}
if (program)
{
glUseProgram(0);
glDeleteProgram(program);
program = 0;
}
SDL_GL_MakeCurrent(window, NULL);
SDL_GL_DeleteContext(ctx);
ctx = NULL;
@ -99,6 +272,49 @@ size GetDrawSizeInPixels(void)
void SetDrawViewport(size size)
{
glViewport(0, 0, size.w, size.h);
vertex s = (vertex){2.0f / (float)size.w, 2.0f / (float)size.h};
float mat[16] = {
s.x, 0.0f, 0.0f, 0.0f,
0.0f, -s.y, 0.0f, 0.0f,
0.0f, 0.0f, 0.0f, 0.0f,
-1.0f, 1.0f, 0.0f, 1.0f};
glUniformMatrix4fv(uView, 1, GL_FALSE, mat);
}
static int drawCount = 0;
static void FlushDrawBuffers()
{
if (!drawListCount)
return;
GLsizeiptr vboSize = drawListVertNum * (GLsizeiptr)sizeof(vertex);
GLsizeiptr iboSize = drawListCount * (GLsizeiptr)sizeof(uint16_t);
glBufferSubData(GL_ARRAY_BUFFER, (GLintptr)0, vboSize, drawListVerts);
glBufferSubData(GL_ELEMENT_ARRAY_BUFFER, (GLintptr)0, iboSize, drawListIndices);
glDrawElements(GL_LINES, drawListCount, GL_UNSIGNED_SHORT, (GLvoid*)0);
drawListVertNum = 0;
drawListCount = 0;
++drawCount;
}
static void UnpackColour(GLfloat* out)
{
const float mul = 1.0f / 255.0f;
out[0] = (GLfloat)((colour & 0xFF000000) >> 24) * mul;
out[1] = (GLfloat)((colour & 0x00FF0000) >> 16) * mul;
out[2] = (GLfloat)((colour & 0x0000FF00) >> 8) * mul;
out[3] = (GLfloat)((colour & 0x000000FF)) * mul;
}
static void UpdateDrawColour()
{
FlushDrawBuffers(); // Flush anything that might still be in the buffer
GLfloat draw[4]; UnpackColour(draw); // Convert packed RGBA32 to float
glUniform4fv(uColour, 1, draw); // Pass new colour to shader
drawColour = colour; // Mark state as up to date
}
@ -111,12 +327,8 @@ void DrawClear(void)
{
if (clrColour != colour)
{
const float mul = 1.0f / 255.0f;
glClearColor(
(GLclampf)((colour & 0xFF000000) >> 24) * mul,
(GLclampf)((colour & 0x00FF0000) >> 16) * mul,
(GLclampf)((colour & 0x0000FF00) >> 8) * mul,
(GLclampf)((colour & 0x000000FF)) * mul);
GLfloat clear[4]; UnpackColour(clear);
glClearColor(clear[0], clear[1], clear[2], clear[3]);
clrColour = colour;
}
glClear(GL_COLOR_BUFFER_BIT);
@ -124,29 +336,162 @@ void DrawClear(void)
void DrawPoint(int x, int y)
{
DrawCircleSteps(x, y, 1, 4);
}
void DrawRect(int x, int y, int w, int h)
{
#if 8 <= DRAWLIST_MAX_SIZE
if (drawColour != colour)
UpdateDrawColour();
else if (drawListCount > DRAWLIST_MAX_SIZE - 8)
FlushDrawBuffers();
uint16_t base = drawListVertNum;
drawListVerts[drawListVertNum++] = (vertex){(float)x, (float)y};
drawListVerts[drawListVertNum++] = (vertex){(float)x + (float)w, (float)y};
drawListVerts[drawListVertNum++] = (vertex){(float)x + (float)w, (float)y + (float)h};
drawListVerts[drawListVertNum++] = (vertex){(float)x, (float)y + (float)h};
drawListIndices[drawListCount++] = base;
drawListIndices[drawListCount++] = base + 1;
drawListIndices[drawListCount++] = base + 1;
drawListIndices[drawListCount++] = base + 2;
drawListIndices[drawListCount++] = base + 2;
drawListIndices[drawListCount++] = base + 3;
drawListIndices[drawListCount++] = base + 3;
drawListIndices[drawListCount++] = base;
if (drawListCount == DRAWLIST_MAX_SIZE)
FlushDrawBuffers();
#else
#pragma message("DRAWLIST_MAX_SIZE is too low, DrawRect functionality disabled")
#endif
}
void DrawLine(int x1, int y1, int x2, int y2)
{
if (drawColour != colour)
UpdateDrawColour();
else if (drawListCount > DRAWLIST_MAX_SIZE - 2)
FlushDrawBuffers();
vertex from = {x1, y1}, to = {x2, y2};
if (drawListVertNum > 0 && memcmp(&from, &drawListVerts[drawListVertNum - 1], sizeof(vertex)) == 0)
{
// Reuse last vertex
drawListIndices[drawListCount++] = drawListVertNum - 1;
}
else
{
drawListVerts[drawListVertNum] = from;
drawListIndices[drawListCount++] = drawListVertNum++;
}
drawListVerts[drawListVertNum] = to;
drawListIndices[drawListCount++] = drawListVertNum++;
}
static void ArcSubSlice(
float x, float y,
float magx, float magy,
float angle, float stride,
int num)
{
if (!num)
return;
drawListVerts[drawListVertNum] = (vertex){
x + cosf(angle) * magx,
y - sinf(angle) * magy};
for (int i = 1; i <= num; ++i)
{
const float theta = angle + stride * (float)i;
float ofsx = cosf(theta) * magx;
float ofsy = sinf(theta) * magy;
drawListIndices[drawListCount++] = drawListVertNum++;
drawListVerts[drawListVertNum] = (vertex){x + ofsx, y - ofsy};
drawListIndices[drawListCount++] = drawListVertNum;
}
drawListVertNum++;
}
static void ArcSlice(
float x, float y,
float magx, float magy,
float angle, float stride,
int steps)
{
int i = 0;
while (true)
{
const int subSteps = (MIN(drawListCount + (steps - i) * 2, DRAWLIST_MAX_SIZE) - drawListCount) / 2;
if (subSteps)
ArcSubSlice(x, y, magx, magy, angle, stride, subSteps);
i += subSteps;
if (i < steps)
FlushDrawBuffers();
else
break;
angle += stride * (float)subSteps;
}
}
void DrawCircleSteps(int x, int y, int r, int steps)
{
if (drawColour != colour)
UpdateDrawColour();
// Circles look better when offset negatively by half a pixel w/o MSAA
const float fx = (antialias ? (float)x : (float)x - 0.5f);
const float fy = (antialias ? (float)y : (float)y - 0.5f);
const float stepSz = (float)TAU / (float)abs(steps);
const float mag = (float)r;
// Check if whole circle can fit in the buffer
if (drawListCount > DRAWLIST_MAX_SIZE - steps * 2)
{
// Draw circle as segmented arcs
ArcSlice(fx, fy, mag, mag, 0.0f, stepSz, steps);
}
else
{
// Draw whole circle in a single loop
uint16_t base = drawListVertNum;
drawListVerts[drawListVertNum] = (vertex){fx + mag, fy};
drawListIndices[drawListCount++] = drawListVertNum++;
for (int i = 1; i < steps; ++i)
{
const float theta = stepSz * (float)i;
float ofsx = cosf(theta) * mag;
float ofsy = sinf(theta) * mag;
drawListVerts[drawListVertNum] = (vertex){fx + ofsx, fy + ofsy};
drawListIndices[drawListCount++] = drawListVertNum;
drawListIndices[drawListCount++] = drawListVertNum++;
}
drawListIndices[drawListCount++] = base;
}
}
void DrawArcSteps(int x, int y, int r, int startAng, int endAng, int steps)
{
if (drawColour != colour)
UpdateDrawColour();
// Arcs look better when offset negatively by half a pixel w/o MSAA
const float fx = (antialias ? (float)x : (float)x - 0.5f);
const float fy = (antialias ? (float)y : (float)y - 0.5f);
const float mag = (float)r;
const float fstart = (float)startAng * (float)DEG2RAD;
const float fstepSz = (float)(endAng - startAng) / (float)abs(steps) * (float)DEG2RAD;
ArcSlice(fx, fy, mag, mag, fstart, fstepSz, steps);
}
void DrawPresent(void)
{
FlushDrawBuffers();
SDL_GL_SwapWindow(window);
#ifndef NDEBUG
fprintf(stderr, "%d draw call(s)\n", drawCount);
#endif
drawCount = 0;
}

10
src/frag.glsl Normal file
View File

@ -0,0 +1,10 @@
#version 330 core
out vec4 outColour;
uniform vec4 uColour;
void main()
{
outColour = uColour;
}

10
src/vert.glsl Normal file
View File

@ -0,0 +1,10 @@
#version 330 core
in vec2 inPos;
uniform mat4 uView;
void main()
{
gl_Position = uView * vec4(inPos, 0.0, 1.0);
}

109
tools/bin2h.py Normal file
View File

@ -0,0 +1,109 @@
#!/usr/bin/env python3
import sys
from pathlib import Path
from typing import BinaryIO, TextIO
import re
label_invalid = re.compile(r"\W") # Turn invalid label chars into underscores
label_lstrip = re.compile(r"^[\d_]+") # Trim all underscores or numbers from the beginning
def sanitise_label(label: str) -> str:
tmp = label_invalid.sub("_", label, re.I)
return label_lstrip.sub("", tmp, re.I)
def bin2h(name: str, binf: BinaryIO, h: TextIO):
raise NotImplementedError
def txt2h(name: str, txt: TextIO, h: TextIO):
h.write(f"static const char* const {sanitise_label(name)} = \n")
h.write("\"")
remap = {
"\a": "\\a", "\b": "\\b", "\f": "\\f", "\n": "\\n", "\r": "\\r",
"\t": "\\t", "\v": "\\v", "\\": "\\\\", "\"": "\\\""}
esc_numeric = False
for idx, c in enumerate(txt.read()):
if m := remap.get(c):
h.write(m)
else:
if (i := ord(c)) < 0x7F:
if c.isprintable() and not (esc_numeric and c.isnumeric()):
h.write(c)
else:
if i < 8:
h.write(f"\\{i:o}")
else:
h.write(f"\\x{i:02X}")
esc_numeric = True
continue
elif i < 0x10000:
h.write(f"\\u{i:04X}")
else:
h.write(f"\\U{i:08X}")
esc_numeric = False
h.write("\";\n")
def main():
usage = "Usage [-b data.bin] [-t text.txt] <out.h>"
binfiles = []
txtfiles = []
out = None
opt = None
for arg in sys.argv[1:]:
if opt is not None:
if opt == "b":
binfiles.append(arg)
elif opt == "t":
txtfiles.append(arg)
else:
sys.exit(usage)
opt = None
if arg.startswith("-"):
opt = arg[1:]
else:
out = Path(arg)
if opt is not None or out is None:
sys.exit(usage)
if len(binfiles) == 0 and len(txtfiles) == 0:
sys.exit(usage)
with out.open("w") as h:
guard = f"BIN2H_{sanitise_label(out.name).upper()}"
if not guard.endswith("_H"):
guard += "_H"
h.writelines([
"/*DO NOT EDIT*/\n",
"// Autogenerated by bin2h\n",
"\n",
f"#ifndef {guard}\n",
f"#define {guard}\n",
"\n"])
# Write binaries
for i in binfiles:
path = Path(i)
with path.open("rb") as file:
bin2h(path.name, file, h)
# Write texts
for i in txtfiles:
path = Path(i)
with path.open("r") as file:
txt2h(path.name, file, h)
h.writelines([
"\n",
f"#endif//{guard}\n"])
if __name__ == "__main__":
main()