summaryrefslogtreecommitdiff
path: root/micro/renderer.c
diff options
context:
space:
mode:
Diffstat (limited to 'micro/renderer.c')
-rw-r--r--micro/renderer.c360
1 files changed, 360 insertions, 0 deletions
diff --git a/micro/renderer.c b/micro/renderer.c
new file mode 100644
index 0000000..d65c3dc
--- /dev/null
+++ b/micro/renderer.c
@@ -0,0 +1,360 @@
+#include "renderer.h"
+
+#include <stdio.h>
+#include <string.h>
+
+#include <glad/glad.h>
+#include <GLFW/glfw3.h>
+
+#include "context.h"
+#include "dyn_arr.h"
+
+char vert_shader[] =
+ "#version 320 es\n"
+ "\n"
+ "layout (location = 0) in vec3 v_pos;\n"
+ "layout (location = 1) in vec2 v_uv;\n"
+ "layout (location = 2) in vec4 v_col;\n"
+ "layout (location = 3) in int v_tex;\n"
+ "\n"
+ "out vec2 f_uv;\n"
+ "out vec4 f_col;\n"
+ "out flat int f_tex;\n"
+ "\n"
+ "uniform mat4 projection;"
+ "\n"
+ "void main()\n"
+ "{\n"
+ " gl_Position = projection * vec4(v_pos, 1.0);\n"
+ " f_uv = v_uv;\n"
+ " f_col = v_col;\n"
+ " f_tex = v_tex;\n"
+ "}\n"
+;
+
+char frag_shader[] =
+ "#version 320 es\n"
+ "precision mediump float;\n"
+ "\n"
+ "in vec2 f_uv;\n"
+ "in vec4 f_col;\n"
+ "in flat int f_tex;\n"
+ "\n"
+ "out vec4 out_color;\n"
+ "\n"
+ "uniform sampler2D texs[16];"
+ "\n"
+ "void main()\n"
+ "{\n"
+ " out_color = texture(texs[f_tex], f_uv) * f_col;\n"
+ "}\n"
+;
+
+static
+u32 load_shader(const char *src, u32 type)
+{
+ u32 shader = glCreateShader(type);
+ glShaderSource(shader, 1, &src, NULL);
+ glCompileShader(shader);
+
+ int ok;
+ glGetShaderiv(shader, GL_COMPILE_STATUS, &ok);
+ if (!ok) {
+ char data[1024];
+ glGetShaderInfoLog(shader, 1024, NULL, data);
+ if (type == GL_FRAGMENT_SHADER)
+ log_fatal(ERR_OPENGL_SHADER, "FRAG: %s", data);
+ else if (type == GL_VERTEX_SHADER)
+ log_fatal(ERR_OPENGL_SHADER, "VERT: %s", data);
+ }
+
+ return shader;
+}
+
+static
+u32 load_shader_prog(const char *vert_src, const char *frag_src)
+{
+ u32 vert = load_shader(vert_src, GL_VERTEX_SHADER);
+ u32 frag = load_shader(frag_src, GL_FRAGMENT_SHADER);
+
+ u32 prog = glCreateProgram();
+ glAttachShader(prog, vert);
+ glAttachShader(prog, frag);
+
+ glLinkProgram(prog);
+
+ glDeleteShader(vert);
+ glDeleteShader(frag);
+
+ int ok;
+ glGetProgramiv(prog, GL_LINK_STATUS, &ok);
+ if (!ok) {
+ char data[1024];
+ glGetProgramInfoLog(prog, 1024, NULL, data);
+ log_fatal(ERR_OPENGL_SHADER, "%s", data);
+ }
+
+ return prog;
+}
+
+void init_renderer(struct renderer *r, struct ctx *ctx)
+{
+ r->ctx = ctx;
+
+ glfwMakeContextCurrent(ctx->win);
+
+ if (!gladLoadGLES2Loader((GLADloadproc)glfwGetProcAddress)) {
+ log_fatal(ERR_OPENGL_INIT, "could not load OpenGL ES");
+ }
+
+ u8 white_1x1_data[] = {
+ 255, 255, 255, 255,
+ };
+ r->white_1x1 = load_texture_from_mem(white_1x1_data, 1, 1);
+
+ r->shader_program = load_shader_prog(vert_shader, frag_shader);
+ glUseProgram(r->shader_program);
+
+ r->dc.next_tex_bind = 0;
+ r->dc.cur_tex_bind = 0;
+ r->dc.mesh_start = 0;
+ r->dc.vert_count = 0;
+ r->dc.xyz = da_create(struct vec3, 0);
+ r->dc.uv = da_create(struct vec2, 0);
+ r->dc.col = da_create(struct color, 0);
+ r->dc.tex = da_create(int, 0);
+ r->dc.indices = da_create(u16, 0);
+
+ r->cur_col = (struct color){1, 1, 1, 1};
+
+ glEnable(GL_BLEND);
+
+ set_blend_mode(r, BLEND_ALPHA);
+ bind_render_texture(r, NULL);
+}
+
+void deinit_renderer(struct renderer *r)
+{
+ free_texture(r->white_1x1);
+ glDeleteProgram(r->shader_program);
+
+ da_free(r->dc.xyz);
+ da_free(r->dc.uv);
+ da_free(r->dc.col);
+ da_free(r->dc.tex);
+ da_free(r->dc.indices);
+}
+
+void flush_pending(struct renderer *r)
+{
+ int handles[MAX_BOUND_TEX];
+ memset(handles, 0, sizeof(handles));
+ for (int i = 0; i < r->dc.next_tex_bind; i++) {
+ glActiveTexture(GL_TEXTURE0 + i);
+ glBindTexture(GL_TEXTURE_2D, r->dc.textures[i].handle);
+ handles[i] = i;
+ }
+
+ // TODO: store this location
+ int loc = glGetUniformLocation(r->shader_program, "texs");
+ glUniform1iv(loc, MAX_BOUND_TEX, handles);
+
+ glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 0, r->dc.xyz);
+ glEnableVertexAttribArray(0);
+ glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, 0, r->dc.uv);
+ glEnableVertexAttribArray(1);
+ glVertexAttribPointer(2, 4, GL_FLOAT, GL_FALSE, 0, r->dc.col);
+ glEnableVertexAttribArray(2);
+ glVertexAttribIPointer(3, 1, GL_INT, 0, r->dc.tex);
+ glEnableVertexAttribArray(3);
+
+ glDrawElements(
+ GL_TRIANGLES,
+ da_len(r->dc.indices),
+ GL_UNSIGNED_SHORT,
+ r->dc.indices
+ );
+
+ da_clear(r->dc.xyz);
+ da_clear(r->dc.uv);
+ da_clear(r->dc.col);
+ da_clear(r->dc.tex);
+ da_clear(r->dc.indices);
+ r->dc.next_tex_bind = 0;
+ r->dc.cur_tex_bind = 0;
+ r->dc.mesh_start = 0;
+ r->dc.vert_count = 0;
+}
+
+static
+void start_new_mesh(struct renderer *r, struct texture tex)
+{
+ struct draw_call *dc = &r->dc;
+ int found_binding = -1;
+ for (int i = 0; i < dc->next_tex_bind; i++) {
+ if (dc->textures[i].handle == tex.handle) {
+ found_binding = i;
+ break;
+ }
+ }
+
+ if (found_binding < 0) {
+ if (dc->next_tex_bind + 1 > MAX_BOUND_TEX)
+ flush_pending(r);
+
+ dc->cur_tex_bind = dc->next_tex_bind++;
+ dc->textures[dc->cur_tex_bind] = tex;
+ } else {
+ dc->cur_tex_bind = found_binding;
+ }
+
+ dc->mesh_start = dc->vert_count;
+}
+
+static
+void add_vertex(struct renderer *r, struct vertex vert)
+{
+ da_append(struct vec3, &r->dc.xyz, vert.xyz);
+ da_append(struct vec2, &r->dc.uv, vert.uv);
+ da_append(struct color, &r->dc.col, vert.col);
+ da_append(int, &r->dc.tex, r->dc.cur_tex_bind);
+ r->dc.vert_count++;
+}
+
+static
+void add_index(struct renderer *r, u16 idx)
+{
+ da_append(u16, &r->dc.indices, r->dc.mesh_start + idx);
+}
+
+void draw_clear(struct color col)
+{
+ glClearColor(col.r, col.g, col.b, col.a);
+ glClear(GL_COLOR_BUFFER_BIT);
+}
+
+static
+void draw_quad(
+ struct renderer *r,
+ struct texture tex,
+ float x, float y,
+ float w, float h
+) {
+ float brx = x + w;
+ float bry = y + h;
+
+ start_new_mesh(r, tex);
+
+ // tl
+ add_vertex(r, (struct vertex){
+ .xyz = {x, y, 0},
+ .uv = {0, 0},
+ .col = r->cur_col,
+ });
+
+ // tr
+ add_vertex(r, (struct vertex){
+ .xyz = {brx, y, 0},
+ .uv = {1, 0},
+ .col = r->cur_col,
+ });
+
+ // bl
+ add_vertex(r, (struct vertex){
+ .xyz = {x, bry, 0},
+ .uv = {0, 1},
+ .col = r->cur_col,
+ });
+
+ // br
+ add_vertex(r, (struct vertex){
+ .xyz = {brx, bry, 0},
+ .uv = {1, 1},
+ .col = r->cur_col,
+ });
+
+ add_index(r, 0);
+ add_index(r, 1);
+ add_index(r, 2);
+
+ add_index(r, 2);
+ add_index(r, 1);
+ add_index(r, 3);
+}
+
+void draw_rect(struct renderer *r, float x, float y, float w, float h)
+{
+ draw_quad(r, r->white_1x1, x, y, w, h);
+}
+
+void draw_texture(struct renderer *r, struct texture tex, float x, float y)
+{
+ draw_quad(r, tex, x, y, tex.width, tex.height);
+}
+
+static
+void display_errors(void)
+{
+#ifdef UE_DEBUG
+ u32 err;
+ while ((err = glGetError()) != GL_NO_ERROR) {
+ switch (err) {
+ case GL_INVALID_ENUM: printf("GL_INVALID_ENUM\n"); break;
+ case GL_INVALID_VALUE: printf("GL_INVALID_VALUE\n"); break;
+ case GL_INVALID_OPERATION: printf("GL_INVALID_OPERATION\n"); break;
+ case GL_OUT_OF_MEMORY: printf("GL_OUT_OF_MEMORY\n"); break;
+ default: printf("OPENGL ERROR: %x\n", err); break;
+ }
+ }
+#endif
+}
+
+void set_blend_mode(struct renderer *r, enum blend_mode mode)
+{
+ struct draw_call *dc = &r->dc;
+
+ if (mode != dc->blend_mode) {
+ flush_pending(r);
+ dc->blend_mode = mode;
+ }
+
+ switch (mode) {
+ case BLEND_ALPHA:
+ glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
+ break;
+ case BLEND_ADD:
+ glBlendFunc(GL_ONE, GL_ONE);
+ break;
+ }
+}
+
+void bind_render_texture(struct renderer *r, struct render_texture *rtex)
+{
+ flush_pending(r);
+
+ u32 handle = 0;
+ int width;
+ int height;
+
+ if (rtex == NULL) {
+ glfwGetWindowSize(r->ctx->win, &width, &height);
+ mat4_ortho(r->projection, 0, width, 0, height, 0, 1);
+ } else {
+ handle = rtex->handle;
+ width = rtex->width;
+ height = rtex->height;
+ mat4_ortho(r->projection, 0, width, height, 0, 0, 1);
+ }
+
+ glViewport(0, 0, width, height);
+ glBindFramebuffer(GL_FRAMEBUFFER, handle);
+
+ int loc = glGetUniformLocation(r->shader_program, "projection");
+ glUniformMatrix4fv(loc, 1, GL_TRUE, (float*)r->projection);
+}
+
+void end_draw(struct renderer *r)
+{
+ flush_pending(r);
+ display_errors();
+}