#define GL_GLEXT_PROTOTYPES #include #include #include #include #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, };