commit 3ba4d91b99b06a9cab754313d6b7440ddf84350b
parent 19e837898397527fff66fd4d2e956b73ed4bbe02
Author: rexim <reximkut@gmail.com>
Date: Sun, 4 Jul 2021 22:59:22 +0700
Quick and rough implementation of the OpenGL renderer
Diffstat:
10 files changed, 523 insertions(+), 3 deletions(-)
diff --git a/Makefile b/Makefile
@@ -1,6 +1,7 @@
-CFLAGS=-Wall -Wextra -std=c11 -pedantic -ggdb `pkg-config --cflags sdl2`
-LIBS=`pkg-config --libs sdl2` -lm
-SRCS=src/main.c src/la.c src/editor.c src/font.c src/sdl_extra.c
+PKGS=sdl2 glew
+CFLAGS=-Wall -Wextra -std=c11 -pedantic -ggdb `pkg-config --cflags $(PKGS)`
+LIBS=`pkg-config --libs $(PKGS)` -lm
+SRCS=src/main.c src/la.c src/editor.c src/font.c src/sdl_extra.c src/file.c src/gl_extra.c
te: $(SRCS)
$(CC) $(CFLAGS) -o te $(SRCS) $(LIBS)
diff --git a/shaders/font.frag b/shaders/font.frag
@@ -0,0 +1,36 @@
+#version 330 core
+
+#define FONT_WIDTH 128
+#define FONT_HEIGHT 64
+#define FONT_COLS 18
+#define FONT_ROWS 7
+#define FONT_CHAR_WIDTH (FONT_WIDTH / FONT_COLS)
+#define FONT_CHAR_HEIGHT (FONT_HEIGHT / FONT_ROWS)
+#define FONT_CHAR_WIDTH_UV (float(FONT_CHAR_WIDTH) / float(FONT_WIDTH))
+#define FONT_CHAR_HEIGHT_UV (float(FONT_CHAR_HEIGHT) / float(FONT_HEIGHT))
+
+#define ASCII_DISPLAY_LOW 32
+#define ASCII_DISPLAY_HIGH 126
+
+uniform sampler2D font;
+uniform float time;
+
+in vec2 uv;
+in float glyph_ch;
+in vec4 glyph_color;
+
+void main() {
+ int ch = int(glyph_ch);
+ if (!(ASCII_DISPLAY_LOW <= ch && ch <= ASCII_DISPLAY_HIGH)) {
+ ch = 63;
+ }
+
+ int index = ch - 32;
+ float x = float(index % FONT_COLS) * FONT_CHAR_WIDTH_UV;
+ float y = float(index / FONT_COLS) * FONT_CHAR_HEIGHT_UV;
+ vec2 pos = vec2(x, y + FONT_CHAR_HEIGHT_UV);
+ vec2 size = vec2(FONT_CHAR_WIDTH_UV, -FONT_CHAR_HEIGHT_UV);
+ vec2 t = pos + size * uv;
+
+ gl_FragColor = texture(font, t)* glyph_color;
+}
diff --git a/shaders/font.vert b/shaders/font.vert
@@ -0,0 +1,32 @@
+#version 330 core
+
+#define FONT_WIDTH 128
+#define FONT_HEIGHT 64
+#define FONT_COLS 18
+#define FONT_ROWS 7
+#define FONT_CHAR_WIDTH (FONT_WIDTH / FONT_COLS)
+#define FONT_CHAR_HEIGHT (FONT_HEIGHT / FONT_ROWS)
+
+uniform vec2 resolution;
+
+layout(location = 0) in vec2 pos;
+layout(location = 1) in float scale;
+layout(location = 2) in float ch;
+layout(location = 3) in vec4 color;
+
+out vec2 uv;
+out float glyph_ch;
+out vec4 glyph_color;
+
+vec2 project_point(vec2 point)
+{
+ return 2.0 * point / resolution;
+}
+
+void main() {
+ uv = vec2(float(gl_VertexID & 1), float((gl_VertexID >> 1) & 1));
+ vec2 char_size = vec2(float(FONT_CHAR_WIDTH), float(FONT_CHAR_HEIGHT));
+ gl_Position = vec4(project_point(uv * char_size * scale + pos), 0.0, 1.0);
+ glyph_ch = ch;
+ glyph_color = color;
+}
diff --git a/src/file.c b/src/file.c
@@ -0,0 +1,36 @@
+#include <stdio.h>
+#include <stdlib.h>
+#include <errno.h>
+#include <string.h>
+#include "./file.h"
+
+char *slurp_file(const char *file_path)
+{
+#define SLURP_FILE_PANIC \
+ do { \
+ fprintf(stderr, "Could not read file `%s`: %s\n", file_path, strerror(errno)); \
+ exit(1); \
+ } while (0)
+
+ FILE *f = fopen(file_path, "r");
+ if (f == NULL) SLURP_FILE_PANIC;
+ if (fseek(f, 0, SEEK_END) < 0) SLURP_FILE_PANIC;
+
+ long size = ftell(f);
+ if (size < 0) SLURP_FILE_PANIC;
+
+ char *buffer = malloc(size + 1);
+ if (buffer == NULL) SLURP_FILE_PANIC;
+
+ if (fseek(f, 0, SEEK_SET) < 0) SLURP_FILE_PANIC;
+
+ fread(buffer, 1, size, f);
+ if (ferror(f) < 0) SLURP_FILE_PANIC;
+
+ buffer[size] = '\0';
+
+ if (fclose(f) < 0) SLURP_FILE_PANIC;
+
+ return buffer;
+#undef SLURP_FILE_PANIC
+}
diff --git a/src/file.h b/src/file.h
@@ -0,0 +1,6 @@
+#ifndef FILE_H_
+#define FILE_H_
+
+char *slurp_file(const char *file_path);
+
+#endif // FILE_H_
diff --git a/src/gl_extra.c b/src/gl_extra.c
@@ -0,0 +1,72 @@
+#include <stdio.h>
+#include <stdlib.h>
+#include "file.h"
+#include "gl_extra.h"
+
+const char *shader_type_as_cstr(GLuint shader)
+{
+ switch (shader) {
+ case GL_VERTEX_SHADER:
+ return "GL_VERTEX_SHADER";
+ case GL_FRAGMENT_SHADER:
+ return "GL_FRAGMENT_SHADER";
+ default:
+ return "(Unknown)";
+ }
+}
+
+bool compile_shader_source(const GLchar *source, GLenum shader_type, GLuint *shader)
+{
+ *shader = glCreateShader(shader_type);
+ glShaderSource(*shader, 1, &source, NULL);
+ glCompileShader(*shader);
+
+ GLint compiled = 0;
+ glGetShaderiv(*shader, GL_COMPILE_STATUS, &compiled);
+
+ if (!compiled) {
+ GLchar message[1024];
+ GLsizei message_size = 0;
+ glGetShaderInfoLog(*shader, sizeof(message), &message_size, message);
+ fprintf(stderr, "ERROR: could not compile %s\n", shader_type_as_cstr(shader_type));
+ fprintf(stderr, "%.*s\n", message_size, message);
+ return false;
+ }
+
+ return true;
+}
+
+bool compile_shader_file(const char *file_path, GLenum shader_type, GLuint *shader)
+{
+ char *source = slurp_file(file_path);
+ bool ok = compile_shader_source(source, shader_type, shader);
+ if (!ok) {
+ fprintf(stderr, "ERROR: failed to compile `%s` shader file\n", file_path);
+ }
+ free(source);
+ return ok;
+}
+
+bool link_program(GLuint vert_shader, GLuint frag_shader, GLuint *program)
+{
+ *program = glCreateProgram();
+
+ glAttachShader(*program, vert_shader);
+ glAttachShader(*program, frag_shader);
+ glLinkProgram(*program);
+
+ GLint linked = 0;
+ glGetProgramiv(*program, GL_LINK_STATUS, &linked);
+ if (!linked) {
+ GLsizei message_size = 0;
+ GLchar message[1024];
+
+ glGetProgramInfoLog(*program, sizeof(message), &message_size, message);
+ fprintf(stderr, "Program Linking: %.*s\n", message_size, message);
+ }
+
+ glDeleteShader(vert_shader);
+ glDeleteShader(frag_shader);
+
+ return program;
+}
diff --git a/src/gl_extra.h b/src/gl_extra.h
@@ -0,0 +1,13 @@
+#ifndef GL_EXTRA_H_
+#define GL_EXTRA_H_
+
+#define GL_GLEXT_PROTOTYPES
+#include <SDL2/SDL_opengl.h>
+
+#include <stdbool.h>
+
+bool compile_shader_source(const GLchar *source, GLenum shader_type, GLuint *shader);
+bool compile_shader_file(const char *file_path, GLenum shader_type, GLuint *shader);
+bool link_program(GLuint vert_shader, GLuint frag_shader, GLuint *program);
+
+#endif // GL_EXTRA_H_
diff --git a/src/la.c b/src/la.c
@@ -28,8 +28,47 @@ Vec2f vec2f_mul(Vec2f a, Vec2f b)
return vec2f(a.x * b.x, a.y * b.y);
}
+Vec2f vec2f_mul3(Vec2f a, Vec2f b, Vec2f c)
+{
+ return vec2f_mul(vec2f_mul(a, b), c);
+}
+
Vec2f vec2f_div(Vec2f a, Vec2f b)
{
return vec2f(a.x / b.x, a.y / b.y);
}
+Vec4f vec4f(float x, float y, float z, float w)
+{
+ return (Vec4f) {
+ .x = x,
+ .y = y,
+ .z = z,
+ .w = w,
+ };
+}
+
+Vec4f vec4fs(float x)
+{
+ return vec4f(x, x, x, x);
+}
+
+Vec4f vec4f_add(Vec4f a, Vec4f b)
+{
+ return vec4f(a.x + b.x, a.y + b.y, a.z + b.z, a.w + b.w);
+}
+
+Vec4f vec4f_sub(Vec4f a, Vec4f b)
+{
+ return vec4f(a.x - b.x, a.y - b.y, a.z - b.z, a.w - b.w);
+}
+
+Vec4f vec4f_mul(Vec4f a, Vec4f b)
+{
+ return vec4f(a.x * b.x, a.y * b.y, a.z * b.z, a.w * b.w);
+}
+
+Vec4f vec4f_div(Vec4f a, Vec4f b)
+{
+ return vec4f(a.x / b.x, a.y / b.y, a.z / b.z, a.w / b.w);
+}
diff --git a/src/la.h b/src/la.h
@@ -10,6 +10,18 @@ Vec2f vec2fs(float x);
Vec2f vec2f_add(Vec2f a, Vec2f b);
Vec2f vec2f_sub(Vec2f a, Vec2f b);
Vec2f vec2f_mul(Vec2f a, Vec2f b);
+Vec2f vec2f_mul3(Vec2f a, Vec2f b, Vec2f c);
Vec2f vec2f_div(Vec2f a, Vec2f b);
+typedef struct {
+ float x, y, z, w;
+} Vec4f;
+
+Vec4f vec4f(float x, float y, float z, float w);
+Vec4f vec4fs(float x);
+Vec4f vec4f_add(Vec4f a, Vec4f b);
+Vec4f vec4f_sub(Vec4f a, Vec4f b);
+Vec4f vec4f_mul(Vec4f a, Vec4f b);
+Vec4f vec4f_div(Vec4f a, Vec4f b);
+
#endif // LA_H_
diff --git a/src/main.c b/src/main.c
@@ -4,6 +4,12 @@
#include <SDL.h>
+#include <SDL2/SDL.h>
+#define GLEW_STATIC
+#include <GL/glew.h>
+#define GL_GLEXT_PROTOTYPES
+#include <SDL2/SDL_opengl.h>
+
#define STB_IMAGE_IMPLEMENTATION
#include "./stb_image.h"
@@ -13,6 +19,7 @@
#include "./editor.h"
#include "./la.h"
#include "./sdl_extra.h"
+#include "./gl_extra.h"
#include "./font.h"
#define SCREEN_WIDTH 800
@@ -70,6 +77,271 @@ void usage(FILE *stream)
// TODO: Delete line
// TODO: Split the line on Enter
+// #define OPENGL_RENDERER
+
+#ifdef OPENGL_RENDERER
+void MessageCallback(GLenum source,
+ GLenum type,
+ GLuint id,
+ GLenum severity,
+ GLsizei length,
+ const GLchar* message,
+ const void* userParam)
+{
+ (void) source;
+ (void) id;
+ (void) length;
+ (void) userParam;
+ fprintf(stderr, "GL CALLBACK: %s type = 0x%x, severity = 0x%x, message = %s\n",
+ (type == GL_DEBUG_TYPE_ERROR ? "** GL ERROR **" : ""),
+ type, severity, message);
+}
+
+typedef struct {
+ Vec2f pos;
+ float scale;
+ float ch;
+ Vec4f color;
+} Glyph;
+
+typedef enum {
+ GLYPH_ATTR_POS = 0,
+ GLYPH_ATTR_SCALE,
+ GLYPH_ATTR_CH,
+ GLYPH_ATTR_COLOR,
+ COUNT_GLYPH_ATTRS,
+} Glyph_Attr;
+
+typedef struct {
+ size_t offset;
+ size_t comps;
+} Glyph_Attr_Def;
+
+static const Glyph_Attr_Def glyph_attr_defs[COUNT_GLYPH_ATTRS] = {
+ [GLYPH_ATTR_POS] = {
+ .offset = offsetof(Glyph, pos),
+ .comps = 2,
+ },
+ [GLYPH_ATTR_SCALE] = {
+ .offset = offsetof(Glyph, scale),
+ .comps = 1,
+ },
+ [GLYPH_ATTR_CH] = {
+ .offset = offsetof(Glyph, ch),
+ .comps = 1,
+ },
+ [GLYPH_ATTR_COLOR] = {
+ .offset = offsetof(Glyph, color),
+ .comps = 4,
+ },
+};
+static_assert(COUNT_GLYPH_ATTRS == 4, "The amount of glyph vertex attributes have changed");
+
+#define GLYPH_BUFFER_CAP 1024
+
+Glyph glyph_buffer[GLYPH_BUFFER_CAP];
+size_t glyph_buffer_count = 0;
+
+void glyph_buffer_push(Glyph glyph)
+{
+ assert(glyph_buffer_count < GLYPH_BUFFER_CAP);
+ glyph_buffer[glyph_buffer_count++] = glyph;
+}
+
+void glyph_buffer_sync(void)
+{
+ glBufferSubData(GL_ARRAY_BUFFER,
+ 0,
+ glyph_buffer_count * sizeof(Glyph),
+ glyph_buffer);
+}
+
+void gl_render_text(const char *text, size_t text_size,
+ Vec2f pos, float scale, Vec4f color)
+{
+ for (size_t i = 0; i < text_size; ++i) {
+ const Vec2f char_size = vec2f(FONT_CHAR_WIDTH, FONT_CHAR_HEIGHT);
+ const Glyph glyph = {
+ .pos = vec2f_add(pos, vec2f_mul3(char_size,
+ vec2f((float) i, 0.0f),
+ vec2fs(scale))),
+ .scale = scale,
+ .ch = (float) text[i],
+ .color = color
+ };
+ glyph_buffer_push(glyph);
+ }
+}
+
+int main(int argc, char **argv)
+{
+ (void) argc;
+ (void) argv;
+
+ scc(SDL_Init(SDL_INIT_VIDEO));
+
+ SDL_Window *window =
+ scp(SDL_CreateWindow("Text Editor",
+ 0, 0,
+ SCREEN_WIDTH, SCREEN_HEIGHT,
+ SDL_WINDOW_RESIZABLE | SDL_WINDOW_OPENGL));
+
+ {
+ SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 3);
+ SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 3);
+ SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, SDL_GL_CONTEXT_PROFILE_CORE);
+
+ int major;
+ int minor;
+ SDL_GL_GetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, &major);
+ SDL_GL_GetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, &minor);
+ printf("GL version %d.%d\n", major, minor);
+ }
+
+ scp(SDL_GL_CreateContext(window));
+
+ if (GLEW_OK != glewInit()) {
+ fprintf(stderr, "Could not initialize GLEW!");
+ exit(1);
+ }
+
+ if (!GLEW_ARB_draw_instanced) {
+ fprintf(stderr, "ARB_draw_instanced is not supported; game may not work properly!!\n");
+ exit(1);
+ }
+
+ if (!GLEW_ARB_instanced_arrays) {
+ fprintf(stderr, "ARB_instanced_arrays is not supported; game may not work properly!!\n");
+ exit(1);
+ }
+
+ glEnable(GL_BLEND);
+ glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
+
+ if (GLEW_ARB_debug_output) {
+ glEnable(GL_DEBUG_OUTPUT);
+ glDebugMessageCallback(MessageCallback, 0);
+ } else {
+ fprintf(stderr, "WARNING! GLEW_ARB_debug_output is not available");
+ }
+
+ GLint time_uniform;
+ GLint resolution_uniform;
+
+ // Initialize Shaders
+ {
+ GLuint vert_shader = 0;
+ if (!compile_shader_file("./shaders/font.vert", GL_VERTEX_SHADER, &vert_shader)) {
+ exit(1);
+ }
+ GLuint frag_shader = 0;
+ if (!compile_shader_file("./shaders/font.frag", GL_FRAGMENT_SHADER, &frag_shader)) {
+ exit(1);
+ }
+
+ GLuint program = 0;
+ if (!link_program(vert_shader, frag_shader, &program)) {
+ exit(1);
+ }
+
+ glUseProgram(program);
+
+ time_uniform = glGetUniformLocation(program, "time");
+ resolution_uniform = glGetUniformLocation(program, "resolution");
+
+ glUniform2f(resolution_uniform, SCREEN_WIDTH, SCREEN_HEIGHT);
+ }
+
+ // Init Font Texture
+ {
+ const char *file_path = "charmap-oldschool_white.png";
+ int width, height, n;
+ unsigned char *pixels = stbi_load(file_path, &width, &height, &n, STBI_rgb_alpha);
+ if (pixels == NULL) {
+ fprintf(stderr, "ERROR: could not load file %s: %s\n",
+ file_path, stbi_failure_reason());
+ exit(1);
+ }
+
+ glActiveTexture(GL_TEXTURE0);
+
+ GLuint font_texture = 0;
+ glGenTextures(1, &font_texture);
+ glBindTexture(GL_TEXTURE_2D, font_texture);
+
+ glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
+ glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
+ glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
+ glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
+
+ glTexImage2D(GL_TEXTURE_2D,
+ 0,
+ GL_RGBA,
+ width,
+ height,
+ 0,
+ GL_RGBA,
+ GL_UNSIGNED_BYTE,
+ pixels);
+ }
+
+ // Init Buffers
+ {
+ GLuint vao = 0;
+ glGenVertexArrays(1, &vao);
+ glBindVertexArray(vao);
+
+ GLuint vbo = 0;
+ glGenBuffers(1, &vbo);
+ glBindBuffer(GL_ARRAY_BUFFER, vbo);
+ glBufferData(GL_ARRAY_BUFFER,
+ sizeof(glyph_buffer),
+ glyph_buffer,
+ GL_DYNAMIC_DRAW);
+
+ for (Glyph_Attr attr = 0; attr < COUNT_GLYPH_ATTRS; ++attr) {
+ glEnableVertexAttribArray(attr);
+ glVertexAttribPointer(
+ attr,
+ glyph_attr_defs[attr].comps,
+ GL_FLOAT,
+ GL_FALSE,
+ sizeof(Glyph),
+ (void*) glyph_attr_defs[attr].offset);
+ glVertexAttribDivisor(attr, 1);
+ }
+ }
+
+ const char *text = "Hello, World";
+ Vec4f color = vec4f(1.0f, 0.0f, 0.0f, 1.0f);
+ gl_render_text(text, strlen(text), vec2fs(0.0f), 5.0f, color);
+ glyph_buffer_sync();
+
+ bool quit = false;
+ while (!quit) {
+ SDL_Event event = {0};
+ while (SDL_PollEvent(&event)) {
+ switch (event.type) {
+ case SDL_QUIT: {
+ quit = true;
+ }
+ break;
+ }
+ }
+
+ glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
+ glClear(GL_COLOR_BUFFER_BIT);
+
+ glUniform1f(time_uniform, (float) SDL_GetTicks() / 1000.0f);
+
+ glDrawArraysInstanced(GL_TRIANGLE_STRIP, 0, 4, glyph_buffer_count);
+
+ SDL_GL_SwapWindow(window);
+ }
+
+ return 0;
+}
+#else
int main(int argc, char **argv)
{
const char *file_path = NULL;
@@ -205,3 +477,4 @@ int main(int argc, char **argv)
return 0;
}
+#endif // OPENGL_RENDERER