summaryrefslogtreecommitdiff
path: root/src/draw/opengl.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/draw/opengl.c')
-rw-r--r--src/draw/opengl.c423
1 files changed, 423 insertions, 0 deletions
diff --git a/src/draw/opengl.c b/src/draw/opengl.c
new file mode 100644
index 0000000..76c44f9
--- /dev/null
+++ b/src/draw/opengl.c
@@ -0,0 +1,423 @@
+#define GL_GLEXT_PROTOTYPES
+#include <EGL/egl.h>
+#include <GL/gl.h>
+#include <stdio.h>
+#include <stddef.h>
+#include "util/container.h"
+#include "util/err.h"
+#include "opengl.h"
+
+struct gl_mesh {
+ GLuint vao, vbo;
+ GLuint count;
+};
+
+struct gl_shader {
+ GLuint program;
+ GLint loc_color;
+ GLint loc_model;
+ GLint loc_view_proj;
+};
+
+struct gl_texture {
+ GLuint txo;
+ GLenum type;
+};
+
+static struct {
+ EGLDisplay display;
+ struct {
+ unsigned int size[2];
+ GLuint rbo;
+ GLuint fbos[2];
+ GLuint txos[2];
+ rgba_data buffer;
+ } target;
+ struct gl_shader shaders[3];
+ arraybuf(struct gl_texture) textures;
+ arraybuf(struct gl_mesh) meshes;
+} state;
+
+static void opengl_error(GLenum err, const char *file, int line)
+{
+ switch (err) {
+ case GL_INVALID_ENUM: fprintf(stderr, "opengl error: INVALID_ENUM %s:%d\n", file, line); break;
+ case GL_INVALID_VALUE: fprintf(stderr, "opengl error: INVALID_VALUE %s:%d\n", file, line); break;
+ case GL_INVALID_OPERATION: fprintf(stderr, "opengl error: INVALID_OPERATION %s:%d\n", file, line); break;
+ case GL_STACK_OVERFLOW: fprintf(stderr, "opengl error: STACK_OVERFLOW %s:%d\n", file, line); break;
+ case GL_STACK_UNDERFLOW: fprintf(stderr, "opengl error: STACK_UNDERFLOW %s:%d\n", file, line); break;
+ case GL_OUT_OF_MEMORY: fprintf(stderr, "opengl error: OUT_OF_MEMORY %s:%d\n", file, line); break;
+ case GL_INVALID_FRAMEBUFFER_OPERATION: fprintf(stderr, "opengl error: INVALID_FRAMEBUFFER_OPERATION %s:%d\n", file, line); break;
+ default: break;
+ }
+}
+
+#define GL_DEBUG opengl_debug(__FILE__, __LINE__);
+
+__attribute__((unused)) static void opengl_debug(const char *file, int line)
+{
+ GLenum err = glGetError();
+ if (err != GL_NO_ERROR)
+ opengl_error(err, file, line);
+}
+
+static const char *debug_source_to_string(GLenum source)
+{
+ switch (source) {
+ case GL_DEBUG_SOURCE_API: return "API";
+ case GL_DEBUG_SOURCE_WINDOW_SYSTEM: return "WINDOW SYSTEM";
+ case GL_DEBUG_SOURCE_SHADER_COMPILER: return "SHADER COMPILER";
+ case GL_DEBUG_SOURCE_THIRD_PARTY: return "THIRD PARTY";
+ case GL_DEBUG_SOURCE_APPLICATION: return "APPLICATION";
+ case GL_DEBUG_SOURCE_OTHER: return "OTHER";
+ default: return "UNKNOWN";
+ }
+}
+
+static const char *debug_type_to_string(GLenum type)
+{
+ switch (type) {
+ case GL_DEBUG_TYPE_ERROR: return "ERROR";
+ case GL_DEBUG_TYPE_DEPRECATED_BEHAVIOR: return "DEPRECATED BEHAVIOR";
+ case GL_DEBUG_TYPE_UNDEFINED_BEHAVIOR: return "UDEFINED BEHAVIOR";
+ case GL_DEBUG_TYPE_PORTABILITY: return "PORTABILITY";
+ case GL_DEBUG_TYPE_PERFORMANCE: return "PERFORMANCE";
+ case GL_DEBUG_TYPE_OTHER: return "OTHER";
+ case GL_DEBUG_TYPE_MARKER: return "MARKER";
+ default: return "UNKNOWN";
+ }
+}
+
+static const char *debug_severity_to_string(GLenum severity)
+{
+ switch (severity) {
+ case GL_DEBUG_SEVERITY_HIGH: return "HIGH";
+ case GL_DEBUG_SEVERITY_MEDIUM: return "MEDIUM";
+ case GL_DEBUG_SEVERITY_LOW: return "LOW";
+ case GL_DEBUG_SEVERITY_NOTIFICATION: return "NOTIFICATION";
+ default: return "UNKNOWN";
+ }
+}
+
+static void APIENTRY debug_message_callback(GLenum source, GLenum type, GLuint id, GLenum severity, GLsizei length, const GLchar *msg, const void *data)
+{
+ (void) data, (void) length;
+ if (severity == GL_DEBUG_SEVERITY_NOTIFICATION) return;
+ fprintf(stderr, "%d: %s of %s severity, raised from %s: %s\n",
+ id, debug_type_to_string(type), debug_severity_to_string(severity),
+ debug_source_to_string(source), msg);
+}
+
+static void init_egl(unsigned int size[2])
+{
+ EGLint config_attr[] = {
+ EGL_SURFACE_TYPE, EGL_PBUFFER_BIT,
+ EGL_BLUE_SIZE, 8,
+ EGL_GREEN_SIZE, 8,
+ EGL_RED_SIZE, 8,
+ EGL_DEPTH_SIZE, 8,
+ EGL_RENDERABLE_TYPE, EGL_OPENGL_BIT,
+ EGL_NONE
+ };
+
+ EGLint pbuffer_attr[] = {
+ EGL_WIDTH, size[0],
+ EGL_HEIGHT, size[1],
+ EGL_NONE,
+ };
+
+ EGLDisplay display = eglGetDisplay(EGL_DEFAULT_DISPLAY);
+ EGLint major, minor;
+ eglInitialize(display, &major, &minor);
+
+ EGLint n_config;
+ EGLConfig config;
+ eglChooseConfig(display, config_attr, &config, 1, &n_config);
+
+ EGLSurface surface = eglCreatePbufferSurface(display, config, pbuffer_attr);
+
+ eglBindAPI(EGL_OPENGL_API);
+
+ EGLContext context = eglCreateContext(display, config, EGL_NO_CONTEXT, nullptr);
+ eglMakeCurrent(display, surface, surface, context);
+
+ state.display = display;
+}
+
+static void init_target(unsigned int size[2])
+{
+ state.target.size[0] = size[0];
+ state.target.size[1] = size[1];
+ state.target.buffer = malloc(size[0] * size[1] * 4);
+
+ glGenRenderbuffers(1, &state.target.rbo);
+ glBindRenderbuffer(GL_RENDERBUFFER, state.target.rbo);
+ glRenderbufferStorageMultisample(GL_RENDERBUFFER, 8, GL_DEPTH24_STENCIL8, size[0], size[1]);
+
+ glGenTextures(2, state.target.txos);
+
+ glBindTexture(GL_TEXTURE_2D_MULTISAMPLE, state.target.txos[0]);
+ glTexImage2DMultisample(GL_TEXTURE_2D_MULTISAMPLE, 8, GL_RGBA, size[0], size[1], GL_TRUE);
+
+ glBindTexture(GL_TEXTURE_2D, state.target.txos[1]);
+ glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, size[0], size[1], 0, GL_RGBA, GL_UNSIGNED_BYTE, nullptr);
+
+ glGenFramebuffers(2, state.target.fbos);
+
+ glBindFramebuffer(GL_FRAMEBUFFER, state.target.fbos[0]);
+ glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D_MULTISAMPLE, state.target.txos[0], 0);
+ glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT, GL_RENDERBUFFER, state.target.rbo);
+
+ glBindFramebuffer(GL_FRAMEBUFFER, state.target.fbos[1]);
+ glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, state.target.txos[1], 0);
+}
+
+static void init_shaders()
+{
+ static const char vertex_src[] = {
+#embed "../shader/vertex.glsl"
+ };
+ static const char fragment_color_src[] = {
+#embed "../shader/fragment_color.glsl"
+ };
+ static const char fragment_texture_src[] = {
+#embed "../shader/fragment_texture.glsl"
+ };
+ static const char fragment_cubemap_src[] = {
+#embed "../shader/fragment_cubemap.glsl"
+ };
+
+ struct {
+ const char *name;
+ GLenum type;
+ const char *src;
+ size_t size;
+ } src[4] = {
+ { "vertex.glsl", GL_VERTEX_SHADER, vertex_src, sizeof vertex_src },
+ { "fragment_color.glsl", GL_FRAGMENT_SHADER, fragment_color_src, sizeof fragment_color_src },
+ { "fragment_texture.glsl", GL_FRAGMENT_SHADER, fragment_texture_src, sizeof fragment_texture_src },
+ { "fragment_cubemap.glsl", GL_FRAGMENT_SHADER, fragment_cubemap_src, sizeof fragment_cubemap_src },
+ };
+ GLuint src_ids[4];
+
+ for (size_t i = 0; i < 4; i++) {
+ src_ids[i] = glCreateShader(src[i].type);
+ GLint size = src[i].size;
+ glShaderSource(src_ids[i], 1, &src[i].src, &size);
+ glCompileShader(src_ids[i]);
+
+ GLint success;
+ glGetShaderiv(src_ids[i], GL_COMPILE_STATUS, &success);
+ if (!success) {
+ char error[BUFSIZ];
+ glGetShaderInfoLog(src_ids[i], BUFSIZ, nullptr, error);
+ eprintf("failed to compile %s: %s", src[i].name, error);
+ }
+ }
+
+ const char *names[3] = { "color", "texture", "cubemap" };
+ for (size_t i = 0; i < 3; i++) {
+ struct gl_shader *shader = &state.shaders[i];
+ shader->program = glCreateProgram();
+ glAttachShader(shader->program, src_ids[0]);
+ glAttachShader(shader->program, src_ids[i+1]);
+ glLinkProgram(shader->program);
+
+ GLint success;
+ glGetProgramiv(shader->program, GL_LINK_STATUS, &success);
+ if (!success) {
+ char error[BUFSIZ];
+ glGetShaderInfoLog(shader->program, BUFSIZ, nullptr, error);
+ eprintf("failed to link %s shader: %s", names[i], error);
+ }
+
+ shader->loc_color = glGetUniformLocation(shader->program, "color");
+ shader->loc_model = glGetUniformLocation(shader->program, "model");
+ shader->loc_view_proj = glGetUniformLocation(shader->program, "view_proj");
+ }
+
+ for (size_t i = 0; i < 4; i++)
+ glDeleteShader(src_ids[i]);
+}
+
+static void opengl_init(unsigned int size[2])
+{
+ init_egl(size);
+
+ if (true) {
+ glEnable(GL_DEBUG_OUTPUT);
+ glDebugMessageCallback(debug_message_callback, nullptr);
+ }
+
+ init_target(size);
+ init_shaders();
+}
+
+static bool opengl_create_mesh(size_t n_verts, const struct draw_vertex verts[n_verts], mesh_id *id)
+{
+ struct gl_mesh mesh;
+ mesh.count = n_verts;
+ glGenVertexArrays(1, &mesh.vao);
+ glGenBuffers(1, &mesh.vbo);
+
+ glBindVertexArray(mesh.vao);
+ glBindBuffer(GL_ARRAY_BUFFER, mesh.vbo);
+
+ glBufferData(GL_ARRAY_BUFFER, mesh.count * sizeof(struct draw_vertex), verts, GL_STATIC_DRAW);
+
+ glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, sizeof(struct draw_vertex), (GLvoid *) offsetof(struct draw_vertex, pos));
+ glEnableVertexAttribArray(0);
+
+ glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, sizeof(struct draw_vertex), (GLvoid *) offsetof(struct draw_vertex, tex));
+ glEnableVertexAttribArray(1);
+
+ *id = state.meshes.len;
+ arraybuf_insert(&state.meshes, mesh);
+ return true;
+}
+
+static void image_flip(rgba_data dst, rgba_data src, unsigned int size[2])
+{
+ for (unsigned int y = 0; y < size[1]; y++)
+ memcpy((char *)dst + y*size[0]*4, (char *)src + (size[1]-y-1)*size[0]*4, size[0]*4);
+}
+
+static bool opengl_create_texture(enum texture_type type, unsigned int size[2], const rgba_data faces[], texture_id *id)
+{
+ bool bilinear = true;
+
+ struct gl_texture tex;
+ switch (type) {
+ case TEXTURE_2D: tex.type = GL_TEXTURE_2D; break;
+ case TEXTURE_CUBEMAP: tex.type = GL_TEXTURE_CUBE_MAP; break;
+ }
+ glGenTextures(1, &tex.txo);
+ glBindTexture(tex.type, tex.txo);
+
+ rgba_data flipped = malloc(size[0]*size[1]*4);
+
+ size_t n_faces = tex.type == GL_TEXTURE_CUBE_MAP ? 6 : 1;
+ for (size_t i = 0; i < n_faces; i++) {
+ GLenum target = tex.type == GL_TEXTURE_CUBE_MAP
+ ? GL_TEXTURE_CUBE_MAP_POSITIVE_X + i
+ : GL_TEXTURE_2D;
+ image_flip(flipped, faces[i], size);
+ glTexImage2D(target, 0, GL_RGBA, size[0], size[1], 0, GL_RGBA, GL_UNSIGNED_BYTE, flipped);
+ }
+
+ free(flipped);
+
+ GLint filter = bilinear ? GL_LINEAR : GL_NEAREST;
+ glTexParameteri(tex.type, GL_TEXTURE_MIN_FILTER, filter);
+ glTexParameteri(tex.type, GL_TEXTURE_MAG_FILTER, filter);
+
+ GLint wrap = tex.type == GL_TEXTURE_CUBE_MAP ? GL_CLAMP_TO_EDGE : GL_REPEAT;
+ glTexParameteri(tex.type, GL_TEXTURE_WRAP_S, wrap);
+ glTexParameteri(tex.type, GL_TEXTURE_WRAP_T, wrap);
+ if (tex.type == GL_TEXTURE_CUBE_MAP)
+ glTexParameteri(tex.type, GL_TEXTURE_WRAP_R, wrap);
+
+ *id = state.textures.len;
+ arraybuf_insert(&state.textures, tex);
+ return true;
+}
+
+static void opengl_draw_frame(struct draw_frame *frame, rgba_data out_data)
+{
+ glEnable(GL_DEPTH_TEST);
+ glEnable(GL_MULTISAMPLE);
+
+ glBindFramebuffer(GL_FRAMEBUFFER, state.target.fbos[0]);
+ glViewport(0, 0, state.target.size[0], state.target.size[1]);
+
+ if (frame->bg.present && !frame->bg.data.texture.present)
+ glClearColor(frame->bg.data.color[0], frame->bg.data.color[1], frame->bg.data.color[2], 255.0);
+ else
+ glClearColor(0.0, 0.0, 0.0, 0.0);
+ glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
+
+ for (size_t i = 0; i < 3; i++)
+ glProgramUniformMatrix4fv(state.shaders[i].program, state.shaders[i].loc_view_proj,
+ 1, GL_FALSE, &frame->view_proj[0][0]);
+
+ if (frame->bg.present && frame->bg.data.texture.present) {
+ // TODO: render background
+ }
+
+ /*
+ glEnable(GL_CULL_FACE);
+ glCullFace(GL_BACK);
+ glFrontFace(GL_CCW);
+ */
+
+ for (size_t i = 0; i < frame->calls.len; i++) {
+ struct draw_call *call = &frame->calls.ptr[i];
+ struct gl_mesh *mesh = &state.meshes.ptr[call->mesh];
+ struct gl_texture *texture;
+ struct gl_shader *shader;
+
+ if (call->surface.texture.present) {
+ texture = &state.textures.ptr[call->surface.texture.data];
+ shader = &state.shaders[texture->type == GL_TEXTURE_2D ? 1 : 2];
+ } else {
+ texture = nullptr;
+ shader = &state.shaders[0];
+ }
+
+ glUseProgram(shader->program);
+ glUniform3fv(shader->loc_color, 1, call->surface.color);
+ glUniformMatrix4fv(shader->loc_model, 1, GL_FALSE, &call->model[0][0]);
+
+ if (texture) {
+ glActiveTexture(GL_TEXTURE0);
+ glBindTexture(texture->type, texture->txo);
+ }
+
+ glBindVertexArray(mesh->vao);
+ glDrawArrays(GL_TRIANGLES, 0, mesh->count);
+ }
+
+ glBindFramebuffer(GL_READ_FRAMEBUFFER, state.target.fbos[0]);
+ glBindFramebuffer(GL_DRAW_FRAMEBUFFER, state.target.fbos[1]);
+ glBlitFramebuffer(
+ 0, 0, state.target.size[0], state.target.size[1],
+ 0, 0, state.target.size[0], state.target.size[1],
+ GL_COLOR_BUFFER_BIT, GL_NEAREST);
+
+ glBindFramebuffer(GL_FRAMEBUFFER, state.target.fbos[1]);
+ glPixelStorei(GL_PACK_ALIGNMENT, 1);
+ glReadPixels(0, 0, state.target.size[0], state.target.size[1],
+ GL_RGBA, GL_UNSIGNED_BYTE, state.target.buffer);
+
+ image_flip(out_data, state.target.buffer, state.target.size);
+}
+
+static void opengl_cleanup()
+{
+ free(state.target.buffer);
+
+ glDeleteRenderbuffers(1, &state.target.rbo);
+ glDeleteTextures(2, state.target.txos);
+ glDeleteFramebuffers(2, state.target.fbos);
+
+ for (size_t i = 0; i < 3; i++)
+ glDeleteProgram(state.shaders[i].program);
+
+ for (size_t i = 0; i < state.textures.len; i++)
+ glDeleteTextures(1, &state.textures.ptr[i].txo);
+
+ for (size_t i = 0; i < state.meshes.len; i++) {
+ glDeleteVertexArrays(1, &state.meshes.ptr[i].vao);
+ glDeleteBuffers(1, &state.meshes.ptr[i].vbo);
+ }
+
+ eglTerminate(state.display);
+}
+
+struct draw_backend draw_backend_opengl = {
+ .init = &opengl_init,
+ .create_mesh = &opengl_create_mesh,
+ .create_texture = &opengl_create_texture,
+ .draw_frame = &opengl_draw_frame,
+ .cleanup = &opengl_cleanup,
+};