From d38deeef3af2316a666f8fc0173940bd769b748e Mon Sep 17 00:00:00 2001 From: onelin Date: Sat, 1 Nov 2025 00:55:42 +0100 Subject: Flatten project structure This will make it easier to break up the code into smaller chunks again later. One would think doing this seems fun to me at this point. --- src/rendering.c | 696 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 696 insertions(+) create mode 100644 src/rendering.c (limited to 'src/rendering.c') diff --git a/src/rendering.c b/src/rendering.c new file mode 100644 index 0000000..54dc7b4 --- /dev/null +++ b/src/rendering.c @@ -0,0 +1,696 @@ +#include +#include + +#include +#include +#include +#include +#include + + +#include +#include +#include + + +/* Extern globals */ +extern Instance* GLOBAL_PLATFORM; + +/* Globals */ +#define drawcall_limit (64 * 1024) +RenderDrawCall drawcalls[drawcall_limit]; +i32 drawcall_len = 0; + +usize +ShaderBufferDataType_size(u16 flags) { + const ShaderBufferFlag t = ShaderBuffer_get_data_type(flags); + switch (t) { + case ShaderBuffer_DataType_nil: return 0; + case ShaderBuffer_DataType_f32: return sizeof(f32); + case ShaderBuffer_DataType_f64: return sizeof(f64); + case ShaderBuffer_DataType_i8: return sizeof(i8); + case ShaderBuffer_DataType_i16: return sizeof(i16); + case ShaderBuffer_DataType_i32: return sizeof(i32); + case ShaderBuffer_DataType_i64: return sizeof(i64); + case ShaderBuffer_DataType_u8: return sizeof(u8); + case ShaderBuffer_DataType_u16: return sizeof(u16); + case ShaderBuffer_DataType_u32: return sizeof(u32); + case ShaderBuffer_DataType_u64: return sizeof(u64); + default: return 0; + } +} + +ShaderBufferFlag ShaderBuffer_get_access_frequency(u64 flags) { return flags & (0b111 << 0); } +ShaderBufferFlag ShaderBuffer_get_access_type(u64 flags) { return flags & (0b111 << 3); } +ShaderBufferFlag ShaderBuffer_get_type(u64 flags) { return flags & (0b111 << 6); } +ShaderBufferFlag ShaderBuffer_get_data_type(u64 flags) { return flags & (0b1111 << 9); } + +u32 ShaderBuffer_get_gl_type(u64 flags) { + switch(ShaderBuffer_get_type(flags)) { + case ShaderBuffer_Type_vertexData: + return GL_ARRAY_BUFFER; + case ShaderBuffer_Type_vertexPosition: + return GL_ARRAY_BUFFER; + case ShaderBuffer_Type_vertexIndex: + return GL_ELEMENT_ARRAY_BUFFER; + default: + return GL_ARRAY_BUFFER; + } +} + + +u32 ShaderBuffer_get_gl_accesstype(u64 flags) { + switch (ShaderBuffer_get_access_frequency(flags)) { + case ShaderBuffer_AccessFrequency_stream: + switch (ShaderBuffer_get_access_type(flags)) { + case ShaderBuffer_AccessType_draw: return GL_STREAM_DRAW; + case ShaderBuffer_AccessType_read: return GL_STREAM_READ; + case ShaderBuffer_AccessType_copy: return GL_STREAM_COPY; + default: return 0; + } + + case ShaderBuffer_AccessFrequency_static: + switch (ShaderBuffer_get_access_type(flags)) { + case ShaderBuffer_AccessType_draw: return GL_STATIC_DRAW; + case ShaderBuffer_AccessType_read: return GL_STATIC_READ; + case ShaderBuffer_AccessType_copy: return GL_STATIC_COPY; + default: return 0; + } + case ShaderBuffer_AccessFrequency_dynamic: + switch (ShaderBuffer_get_access_type(flags)) { + case ShaderBuffer_AccessType_draw: return GL_DYNAMIC_DRAW; + case ShaderBuffer_AccessType_read: return GL_DYNAMIC_READ; + case ShaderBuffer_AccessType_copy: return GL_DYNAMIC_COPY; + default: return 0; + } + default: return 0; + } +} + +ShaderBufferFlag ShaderBuffer_get_gl_datatype(u64 flags) { + switch (ShaderBuffer_get_data_type(flags)) { + case ShaderBuffer_DataType_nil: return GL_NONE; + + case ShaderBuffer_DataType_f32: return GL_FLOAT; + case ShaderBuffer_DataType_f64: return GL_DOUBLE; + + case ShaderBuffer_DataType_i8: return GL_BYTE; + case ShaderBuffer_DataType_i16: return GL_SHORT; + case ShaderBuffer_DataType_i32: return GL_INT; + case ShaderBuffer_DataType_i64: return GL_INT64_ARB; + + case ShaderBuffer_DataType_u8: return GL_UNSIGNED_BYTE; + case ShaderBuffer_DataType_u16: return GL_UNSIGNED_SHORT; + case ShaderBuffer_DataType_u32: return GL_UNSIGNED_INT; + case ShaderBuffer_DataType_u64: return GL_UNSIGNED_INT64_ARB; + + default: return GL_NONE; + } +} + +// `RenderBatch` is used for batch rendering. The struct is used as a +// "management" parent structure to keep track of multiple `RenderObject`s that +// are put into a final `RenderObject` to render. +// `RenderObject`s are copied to the internal `models` array, to which the +// pointer to the copied RenderObject is returned, or NULL if an error occurred. +// If changes are made to a render object the batch should be refreshed. +// Renderbatches assumes that all buffer layouts are the same. + +// renderbatch_new: Create a new render batch with space for `count` models. +int renderbatch_new(RenderBatch* renderbatch, usize count) { + /* TODO: Make it such that you can add identical models with different + * transforms, so you instead of relying on renderobject[n] to copy to the + * renderobject we have something like + * + * model { + * renderobj_idx // index in renderobj[n] that this model represents + * transform { + * size; + * pos; + * rotation; + * }; + * }; + * + * For this to work we will likely need to extend the shaderbuffer struct to + * also hold what type of data the buffer contains, s.t. we can apply the + * transformation to only geometry data. + * + * We'll therefore have both data type and buffer type stored somehow, + * maybe like we did the ShaderBufferDataType. + * TODO: Also use shaderbuffertype. + * */ + if (renderbatch == NULL) { + ERROR("renderbatch was null!"); + return -1; + } + + usize numisnstances = count; + + if (count == 0) { + // Just allocate enough for a couple hundred + count = 256; + numisnstances = count * 4; + } + + + renderbatch->msize = sizeof(RenderObject) * count; + renderbatch->mcount = 0; + renderbatch->inst_size = sizeof(BatchModelInstance) * numisnstances; + renderbatch->inst_count = 0; + renderbatch->models = (RenderObject**)calloc(count, sizeof(RenderObject*)); + + if (renderbatch->models == NULL) { + ERROR("Failed to allocate %lu size of bytes for models array!", sizeof(RenderObject*) * count); + return -1; + } + + renderbatch->instances = (BatchModelInstance*)calloc(numisnstances, sizeof(BatchModelInstance)); + + if (renderbatch->instances == NULL) { + ERROR("Failed to allocate %lu size of bytes for batch instances array!", sizeof(BatchModelInstance) * numisnstances); + return -1; + } + + memset(&(renderbatch->renderobj), 0, sizeof(RenderObject)); + + return 0; +} + +// Appends the data in src onto dst. More space for `data` is allocated if +// necessary, in which case a pointer to the new ShaderBuffer is returned. +ShaderBuffer* shaderbuffer_cat(ShaderBuffer* dst, ShaderBuffer *restrict src) { + if (dst == NULL) { + ERROR("dst is null"); + } + else if (src == NULL) { + ERROR("src is null"); + } + + if (ShaderBuffer_get_data_type(dst->buffertype) != ShaderBuffer_get_data_type(src->buffertype)) { + ERROR("Failed to concatenate shader buffers, incompatible datatypes: %d != %d", dst->buffertype, src->buffertype); + } + if (dst->components != src->components) { + ERROR("Failed to concatenate shader buffers, incompatible number of components: %d != %d", dst->components, src->components); + } + + // Assume that we single-handedly control the pointer to the data, copy and + // free the stuff. + + // Verify the size + const usize sz_src = src->size_elem * src->count; + const usize sz_dst = dst->size_elem * dst->count; + if (dst->data == NULL || sz_dst + sz_src >= dst->size) { + const usize sz_new = (1 + ((sz_src + sz_dst) / 4096)) * 4096; + // Resize dst size + dst->data = realloc(dst->data, sz_new); + dst->size = sz_new; + } + + memcpy(dst->data + sz_dst, src->data, sz_src); + + dst->count += src->count; + + return dst; +} + +// Add a render object to the render batch. +i32 renderbatch_add(RenderBatch* renderbatch, RenderObject* obj, Transform* t) { + // Check if its a valid renderbatch + if (renderbatch == NULL) { + ERROR("renderbatch was null!"); + return -1; + } + + // Check whether we have initialized models & instance memory + if (renderbatch->models == NULL) { + const usize sz = 8 * sizeof(RenderObject*); + renderbatch->models = calloc(8, sizeof(RenderObject*)); + renderbatch->msize = sz; + renderbatch->mcount = 0; + } + + if (renderbatch->instances == NULL) { + // Allocate enough for 4 times the models + const usize modelbufsz = renderbatch->msize / sizeof(RenderObject*); + const usize sz = 4 * modelbufsz * sizeof(BatchModelInstance); + renderbatch->instances = calloc(4 * modelbufsz, sizeof(BatchModelInstance)); + renderbatch->inst_size = sz; + renderbatch->inst_count = 0; + } + + // The index of the model + isize model_idx = -1; + + // Find the model, to check if it already exists + for (usize i = 0; i < renderbatch->mcount; i++) { + // Compare the model pointers + if (obj == renderbatch->models[i]) { + model_idx = (isize)i; + break; + } + } + + // Model doesn't exist, add it + if (-1 == model_idx) { + // Check if there's room enough + if ((1 + renderbatch->mcount) * sizeof(RenderObject*) > renderbatch->msize) { + // Realloc if necessary + const usize sz = renderbatch->msize * 2; + renderbatch->models = realloc(renderbatch->models, sz); + renderbatch->msize = sz; + } + + // If this is the first model, we want to copy the renderobj, and + // shaderbuffer parameters. + if (renderbatch->mcount == 0) { + // Shader, VAO, modelviewprojection, and texture, are set when the shaderobj + // is actually created with RenderObject_new later. + // The number of buffers should be the same. + //renderbatch->renderobj.shader = obj->shader; + //renderbatch->renderobj.texture = obj->texture; + + renderbatch->renderobj.buffer_len = obj->buffer_len; + if (renderbatch->renderobj.buffer == NULL) { + renderbatch->renderobj.buffer = calloc(obj->buffer_len, sizeof(ShaderBuffer)); + } else { + ERROR("RenderObj buffer is already initialized!"); + return -1; + } + + // Copy each buffers parameters + for (usize i = 0; i < renderbatch->renderobj.buffer_len; i++) { + renderbatch->renderobj.buffer[i].buffername = 0; + renderbatch->renderobj.buffer[i].buffertype = obj->buffer[i].buffertype; + // Size and count should be zero + + renderbatch->renderobj.buffer[i].components = obj->buffer[i].components; + renderbatch->renderobj.buffer[i].size_elem = obj->buffer[i].size_elem; + // Data should also be null + } + } + + //// Only concatenate the buffers once we refresh + //for (usize i = 0; i < renderbatch->renderobj.buffer_len; i++) { + // shaderbuffer_cat(&renderbatch->renderobj.buffer[i], &obj->buffer[i]); + //} + + model_idx = (isize)renderbatch->mcount; + renderbatch->models[renderbatch->mcount++] = obj; + } + + // Create batch instance + // Check if there's room enough + if ((1 + renderbatch->inst_count) * sizeof(BatchModelInstance) > renderbatch->inst_size) { + // Realloc if necessary + const usize sz = renderbatch->inst_size * 2; + renderbatch->instances = realloc(renderbatch->instances, sz); + renderbatch->inst_size = sz; + } + + BatchModelInstance inst = { + .model_idx = (usize)model_idx, + .transform = *t, + }; + + // Add it to the batch + renderbatch->instances[renderbatch->inst_count++] = inst; + + // Return instance index + return (i32)renderbatch->inst_count - 1; +} + +void renderbatch_transform(RenderBatch* renderbatch, usize obj_idx, Transform* t) { + // TODO: Combine transformation, ie. pos' += pos, etc. + const usize m = renderbatch->instances[obj_idx].model_idx; + const RenderObject* model = renderbatch->models[m]; + renderbatch->instances[obj_idx].transform = *t; + + if(renderbatch->inst_count < obj_idx) { + ERROR("renderbatch_transform: object index is outside range!"); + return; + } + /* TODO: Update the model data, we might need to + * 0. Iteratively go through each renderobj buffer, to find a vertexPosition + * buffer, + * 1. Calculate the models start index in the renderobj, + * 2. Apply transformation to the model in the renderobj buffer. + * */ + usize b; + for (b = 0; b < renderbatch->renderobj.buffer_len; b++) { + if (ShaderBuffer_Type_vertexPosition != ShaderBuffer_get_type(renderbatch->renderobj.buffer[b].buffertype) + || ShaderBuffer_DataType_f32 != ShaderBuffer_get_data_type(renderbatch->renderobj.buffer[b].buffertype)) { + continue; + } + } + + usize offset = 0; + for (usize i = 0; i < obj_idx; i++) { + const usize idx = renderbatch->instances[i].model_idx; + offset += renderbatch->models[idx]->buffer->size_elem + * renderbatch->models[idx]->buffer->count; + } + + float *data = renderbatch->renderobj.buffer[b].data; + data = &data[offset]; + const usize len = model->buffer[b].count; + + Transform tt = renderbatch->instances[obj_idx].transform; + if (model->buffer[b].components == 2) { + for (usize v = 0; v < len; v += 2) { + // scale + // rotate + // offset + glm_vec2_add(&data[v], tt.position, &data[v]); + } + } + else if (model->buffer[b].components == 3) { + for (usize v = 0; v < len; v += 3) { + // scale + // rotate + // offset + glm_vec3_add(&data[v], tt.position, &data[v]); + } + } +} + +// renderbatch_refresh: Copy all instances/models in the renderbatch to the +// batchs' model. +int renderbatch_refresh(RenderBatch* renderbatch) { + const usize bufs = renderbatch->renderobj.buffer_len; + usize *offsets = calloc(bufs, sizeof(usize)); + + // Reset renderobj buffers + for (usize b = 0; b < renderbatch->renderobj.buffer_len; b++) { + // Zero the old data + renderbatch->renderobj.buffer[b].count = 0; + memset(renderbatch->renderobj.buffer[b].data, 0, renderbatch->renderobj.buffer[b].size); + } + + // Copy the instances models buffers, and vertex position buffers with translations applied + for (usize i = 0; i < renderbatch->inst_count; i++) { + const usize m = renderbatch->instances[i].model_idx; + const RenderObject* model = renderbatch->models[m]; + Transform t = renderbatch->instances[i].transform; + + for (usize b = 0; b < renderbatch->renderobj.buffer_len; b++) { + shaderbuffer_cat(&renderbatch->renderobj.buffer[b], &model->buffer[b]); + + if (ShaderBuffer_Type_vertexPosition == ShaderBuffer_get_type(renderbatch->renderobj.buffer[b].buffertype)) { + if (ShaderBuffer_DataType_f32 != ShaderBuffer_get_data_type(renderbatch->renderobj.buffer[b].buffertype)) { + WARN("Buffer data type is not f32, skipping transformation..."); + continue; + } + // Apply transformation in renderbatch buffer-memory + + float *data = renderbatch->renderobj.buffer[b].data; + const usize len = model->buffer[b].count; + // Data points to the start of the model in renderobj + data = &data[renderbatch->renderobj.buffer[b].count - len]; + + if (model->buffer[b].components == 2) { + for (usize v = 0; v < len; v += 2) { + // scale + // rotate + // offset + glm_vec2_add(&data[v], t.position, &data[v]); + } + } + else if (model->buffer[b].components == 3) { + for (usize v = 0; v < len; v += 3) { + // scale + // rotate + // offset + glm_vec3_add(&data[v], t.position, &data[v]); + } + } + } + } + } + + free(offsets); + return 0; +} + + +/* Implementations */ + +/* Clear the screen, + * To be used inbetween draw calls */ +void render_begin(Window* w) { + glfwMakeContextCurrent(w->window); + ((GladGLContext*)(w->context))->Clear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); +} + +void render_present(Window* w) { + /* This is GL specific, TODO: move the GL-specific code elsewhere. Maybe make + * this whole present GL specific? assign it as a fn ptr in the Window struct? */ + GladGLContext *restrict gl = w->context; + Camera c = *GLOBAL_PLATFORM->cam; + + mat4 view; // view + vec3 angle; // viewing angle / direction of the camera + mat4 camera_matrix; + + glm_vec3_sub(c.pos, c.dir, angle); + glm_lookat(c.pos, angle, GLM_YUP, view); + glm_mat4_mul(c.per, view, camera_matrix); + + for (i32 drawcall_idx = 0; drawcall_idx < drawcall_len; drawcall_idx++) { + RenderDrawCall dc = drawcalls[drawcall_idx]; + switch (dc.type) { + case RenderDrawCallType_Sprite: { + // TODO render a quad + } break; + + case RenderDrawCallType_Model: { + + // bind shader program + // - set uniforms + // bind vertex array + // bind index buffer + + + RenderObject* o = dc.data.model.model; + vec3 pos; + glm_vec3_copy(dc.data.model.pos, pos); + + gl->UseProgram(o->shader.program); + // TODO: Use texture atlas + gl->ActiveTexture(GL_TEXTURE0); + gl->BindTexture(GL_TEXTURE_2D, o->texture); + + { + mat4 model = GLM_MAT4_IDENTITY_INIT; + mat4 modelviewprojection; + + model[3][0] = pos[0]; + model[3][1] = pos[1]; + model[3][2] = pos[2]; + + // modelviewprojection = p * view * model + glm_mat4_mul(model, camera_matrix, modelviewprojection); + + // TODO: Do this only once during initialization + gl->UniformMatrix4fv(o->mvp, 1, GL_FALSE, &modelviewprojection[0][0]); + } + + // TODO the buffers need to be abstracted a bit more + gl->BindVertexArray(o->vao); + + ShaderBuffer* ibo = NULL; + for (usize i = 0; i < o->buffer_len; i++) { + const u32 b_gl_type = ShaderBuffer_get_gl_type(o->buffer[i].buffertype); + if (b_gl_type == GL_ELEMENT_ARRAY_BUFFER) { + ibo = &o->buffer[i]; + } + + gl->EnableVertexAttribArray((u32)i); + gl->BindBuffer(b_gl_type, o->buffer[i].buffername); + gl->VertexAttribPointer( + // index of the attribute + (u32)i, + // number of component + (i32)o->buffer[i].components, + // type + ShaderBuffer_get_gl_datatype(o->buffer[i].buffertype), + // normalized? + GL_FALSE, + // stride + 0, + // array buffer offset + (void*)0 + ); + } + + // Draw the model ! + const i32 sz = (i32)(o->buffer->count * o->buffer->size_elem); + if (ibo) { + gl->DrawElements( + GL_TRIANGLES, + (i32)ibo->count, + ShaderBuffer_get_gl_datatype(ibo->buffertype), + (void*)0 + ); + } else { + // Starting from vertex 0; 3 vertices total -> 1 triangle + gl->DrawArrays(GL_TRIANGLES, 0, sz); + } + + for (u32 i = 0; i < o->buffer_len; i++) { + gl->DisableVertexAttribArray(i); + } + + gl->BindVertexArray(0); + + //if (i == 8) { + // printf("\r obj: %.3f", (double)(get_time() - t) * 1000.); + //} + } break; + default: + break; + } + } + + drawcall_len = 0; + + glfwSwapBuffers(w->window); +} + +void drawcall_reset(void) { + drawcall_len = 0; + memset(drawcalls, 0, sizeof(RenderDrawCall) * drawcall_limit); +} + +void r_perspective(f32 fov, Camera *c) { + const f32 ratio = (f32)GLOBAL_PLATFORM->window->windowsize[0] + / (f32)GLOBAL_PLATFORM->window->windowsize[1]; + + c->type = Camera_Perspective; + c->parameters.perspective.fov = fov; + + glm_perspective(glm_rad(fov), ratio, 0.1f, 100.0f, c->per); +} + +void r_perspective_ortho(f32 sz, Camera *c) { + const f32 ratio = (f32)GLOBAL_PLATFORM->window->windowsize[0] + / (f32)GLOBAL_PLATFORM->window->windowsize[1]; + + c->type = Camera_Orthogonal; + c->parameters.orthogonal.sz = sz; + + glm_ortho(-sz * ratio, sz * ratio, -sz, sz, -sz * 10.f, sz * 10.f, c->per); +} + +void r_set_camera(Camera* c) { + GLOBAL_PLATFORM->cam = c; +} + + +void r_reset_camera(Camera* c) { + if (c->type == Camera_Perspective) { + r_perspective(c->parameters.perspective.fov, c); + } + else if (c->type == Camera_Orthogonal) { + r_perspective_ortho(c->parameters.orthogonal.sz, c); + } +} + +void engine_draw_sprite(Sprite* s, ivec2* pos, f32 scale) { + if (drawcall_len + 1 >= drawcall_limit) return; +#ifdef _DEBUG + if (s == NULL) __asm__("int3;"); +#endif + drawcalls[drawcall_len++] = + (RenderDrawCall){.type = RenderDrawCallType_Sprite, + .data.sprite = { + .sprite = s, + .x = *pos[0], + .y = *pos[1], + .scale = scale, + //.mod = {0xFF, 0xFF, 0xFF, 0xFF}, + }}; +} + +void engine_draw_sprite_ex(Sprite* s, ivec2* pos, f32 scale, + Engine_color colormod) { + if (drawcall_len + 1 >= drawcall_limit) return; +#ifdef _DEBUG + if (s == NULL) __asm__("int3;"); +#endif + drawcalls[drawcall_len++] = (RenderDrawCall){ + .type = RenderDrawCallType_Sprite, + .data.sprite = { + .sprite = s, + .x = *pos[0], + .y = *pos[1], + .scale = scale, + //.mod = {colormod.r, colormod.g, colormod.b, colormod.a}, + }}; +} + +void engine_draw_model(RenderObject* o, vec3 pos) { + if (drawcall_len + 1 >= drawcall_limit) return; +#ifdef _DEBUG + if (o == NULL) __asm__("int3;"); +#endif + RenderDrawCall dc = { + .type = RenderDrawCallType_Model, + .data.model = { + .model = o, + .scale = 1.f, + }}; + + + glm_vec3_copy(pos, dc.data.model.pos); + + drawcalls[drawcall_len++] = dc; +} + +Sprite sprite_new(u64 tid, u8 coord) { + const i32 ts = 16; + // FIXME; used to be + //((struct Resources*)GLOBAL_PLATFORM->data)->textures[tid]->tilesize; + return (Sprite){ + .texture_id = (u32)tid, + { + ts * (coord & 0x0F), + ts * ((coord & 0xF0) >> 4), + }}; +} + +Texture createTextureFromImageData(unsigned char* image_data, i32 width, i32 height, u8 components) { + Window* restrict w = GLOBAL_PLATFORM->window; + Texture t; + t.width = width; + t.height = height; + + if (w->renderer != WINDOW_RENDERER_OPENGL) { + ERROR("createTextureFromImageData not implemented for chosen renderer!"); + return (Texture){.id = 0, .width = 0, .height = 0}; + } + + const GladGLContext* gl = w->context; + + gl->GenTextures(1, &t.id); + gl->BindTexture(GL_TEXTURE_2D, t.id); + u32 err = gl->GetError(); + if (err) { + ERROR("Failed to bind texture from image data!"); + } + + /* TODO: Support more formats than rgb and rgba, such as gray, gray/alpha, etc.*/ + u32 format = GL_RGB; + if (components == 4) format = GL_RGBA; + + /* TODO: Don't force internal format to RGB */ + + gl->TexImage2D(GL_TEXTURE_2D, 0, GL_RGB, width, height, 0, format, + GL_UNSIGNED_BYTE, image_data); + + gl->TexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); + gl->TexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); + + gl->BindTexture(GL_TEXTURE_2D, 0); + + return t; +} -- cgit v1.3