diff options
Diffstat (limited to 'src/draw/opengl.c')
| -rw-r--r-- | src/draw/opengl.c | 423 |
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, +}; |
