#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 & ( 7 << 0); } ShaderBufferFlag ShaderBuffer_get_access_type(u64 flags) { return flags & ( 7 << 3); } ShaderBufferFlag ShaderBuffer_get_type(u64 flags) { return flags & ( 7 << 6); } ShaderBufferFlag ShaderBuffer_get_data_type(u64 flags) { return flags & (15 << 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]; // bind shader program // - set uniforms // bind vertex array // bind index buffer const RenderObject* const restrict o = dc.model; vec3 pos; glm_vec3_copy(dc.pos, pos); gl->UseProgram(o->shader.program); // TODO: Use texture atlas gl->BindTextureUnit(0, 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]); gl->UniformMatrix4fv(o->model_position, 1, GL_FALSE, &model[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.); //} } drawcall_len = 0; glfwSwapBuffers(w->window); } void window_reset_drawing(void) { drawcall_len = 0; memset(drawcalls, 0, sizeof(RenderDrawCall) * drawcall_limit); } void r_perspective(Camera *c, f32 fov, ivec2 windowsize) { const f32 ratio = (f32)windowsize[0] / (f32)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(Camera *c, f32 sz, ivec2 windowsize) { const f32 ratio = (f32)windowsize[0] / (f32)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, ivec2 windowsize) { if (c->type == Camera_Perspective) { r_perspective(c, c->parameters.perspective.fov, windowsize); } else if (c->type == Camera_Orthogonal) { r_perspective_ortho(c, c->parameters.orthogonal.sz, windowsize); } } void engine_draw_model(RenderObject* o, vec4 pos) { if (drawcall_len + 1 >= drawcall_limit) return; #ifdef _DEBUG if (o == NULL) __asm__("int3;"); #endif RenderDrawCall dc = { .model = o, .scale = pos[3], }; glm_vec3_copy(pos, dc.pos); drawcalls[drawcall_len++] = dc; } 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; u32 err = gl->GetError(); if (err) { ERROR("There's already something wrong!"); } gl->CreateTextures(GL_TEXTURE_2D, 1, &t.id); err = gl->GetError(); if (err) { if (err == GL_INVALID_ENUM) { ERROR("Failed to create texture! GL_INVALID_ENUM"); } else if (err == GL_INVALID_VALUE) { ERROR("Failed to create texture! GL_INVALID_VALUE"); } else { ERROR("Failed to create texture!"); } } /* 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->TextureStorage2D(t.id, 1, GL_RGB8, width, height); err = gl->GetError(); if (err) { char* errstr = NULL; switch (err) { case GL_INVALID_ENUM: errstr = "GL_INVALID_ENUM"; break; case GL_INVALID_VALUE: errstr = "GL_INVALID_VALUE"; break; case GL_INVALID_OPERATION: errstr = "GL_INVALID_OPERATION"; break; default: errstr = "unknown"; break; } ERROR("Failed to allocate memory for texture! %dx%d size (%s)", width, height, errstr); } gl->TextureSubImage2D(t.id, 0, // offset, size 0, 0, width, height, format, GL_UNSIGNED_BYTE, image_data); err = gl->GetError(); if (err) { ERROR("Failed to copy image data!"); } gl->TextureParameteri(t.id, GL_TEXTURE_MAG_FILTER, GL_NEAREST); gl->TextureParameteri(t.id, GL_TEXTURE_MIN_FILTER, GL_NEAREST); return t; } void r_init_renderstack( usize num_fbuf, usize num_buf, FramebufferParameters *restrict fb_params, u32 *restrict buffer_params ) { window_init_renderstack(GLOBAL_PLATFORM->window, num_fbuf, num_buf, fb_params, buffer_params); } void r_create_framebuffers(void* restrict ctx, u32* restrict framebuffer_array, usize num_targets) { const GladGLContext* gl = (GladGLContext*)ctx; gl->CreateFramebuffers(num_targets, framebuffer_array); } void r_destroy_framebuffers(void* restrict ctx, u32* restrict framebuffer_array, usize num_targets) { const GladGLContext* gl = (GladGLContext*)ctx; gl->DeleteFramebuffers(num_targets, framebuffer_array); } void r_create_textures(void* restrict ctx, u32* restrict texture_array, u32* restrict texture_types, ivec4* restrict texture_size, usize num_targets) { const GladGLContext* gl = (GladGLContext*)ctx; gl->CreateTextures(GL_TEXTURE_2D, num_targets, texture_array); for (usize i = 0; i < num_targets; i++) { ASSERT("SWITCH CASE ON BUFFER DIMENSIONALITY"); ASSERT("SWITCH CASE ON TEXTURE DATA TYPE"); gl->TextureStorage2D(texture_array[i], 1, texture_types[i], *texture_size[0], *texture_size[1]); gl->TextureParameteri(texture_array[i], GL_TEXTURE_MAG_FILTER, GL_NEAREST); gl->TextureParameteri(texture_array[i], GL_TEXTURE_MIN_FILTER, GL_NEAREST); } } void r_destroy_textures(void *restrict ctx, u32* textures, usize num_textures) { const GladGLContext* gl = (GladGLContext*)ctx; gl->DeleteTextures(num_textures, textures); }