#include #include #include // Important wrt. what direction we're favoring when readjusting FOV/cameras. // Also makes it more vulkan/metal friendly according to docs? #define CGLM_FORCE_DEPTH_ZERO_TO_ONE #include #include #include #include #include #include #include #include static f32 default_quad[8] = { -1.0f, -1.0f, 1.0f, -1.0f, 1.0f, 1.0f, -1.0f, 1.0f, }; static f32 default_quad_uv[8] = { 0.f, 0.f, 1.f, 0.f, 1.f, 1.f, 0.f, 1.f, }; static u8 default_quad_ibo[6] = { 0, 1, 2, 2, 3, 0, }; #define DEFAULT_CAMERA { .pos = {3, 0, 0}, .dir = {1, 1, 1}, } static Camera default_camera = DEFAULT_CAMERA; // TODO: CREATE DEFAULT SHADERBUFF #define COUNT(a) sizeof(a) / sizeof(a[0]) static ShaderBuffer shaderbuf[3] = { SHADERBUFFER_NEW(f32, COUNT(default_quad), 2, default_quad, ShaderBuffer_AccessFrequency_static | ShaderBuffer_AccessType_draw | ShaderBuffer_Type_vertexPosition), SHADERBUFFER_NEW(f32, COUNT(default_quad_uv), 2, default_quad_uv, ShaderBuffer_AccessFrequency_static | ShaderBuffer_AccessType_draw), SHADERBUFFER_NEW(u8, COUNT(default_quad_ibo), 3, default_quad_ibo, ShaderBuffer_AccessFrequency_static | ShaderBuffer_AccessType_draw | ShaderBuffer_Type_vertexIndex), }; #undef COUNT static const char* default_quad_shader_vertex_src = "#version 330 core\n" "\n" "layout(location = 0) in vec2 vertexPosition_modelspace;\n" "layout(location = 1) in vec2 vertexUV;\n" "\n" "out vec2 UV;\n" "\n" "void main() {\n" " gl_Position = vec4(vertexPosition_modelspace, 0, 1);\n" "\n" " UV = vertexUV;\n" "}"; static const char* default_quad_shader_fragment_src = "#version 330 core\n" "\n" "// Ouput data\n" "in vec2 UV;\n" "out vec3 color;\n" "\n" "uniform sampler2D textureSampler;\n" "\n" "void main() {\n" " color = texture(textureSampler, UV).rgb;\n" "}"; /* Extern globals */ extern Instance* GLOBAL_PLATFORM; 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)->BindFramebuffer(GL_FRAMEBUFFER, 0); ((GladGLContext*)w->context)->ClearColor(1.f, 0.f, 1.f, 1.f); ((GladGLContext*)w->context)->Clear(GL_COLOR_BUFFER_BIT); } void r_clear_buffer(void *restrict context, RenderTargets *restrict t, u32 framebuffer_idx) { ((GladGLContext*)context)->BindFramebuffer(GL_FRAMEBUFFER, t->framebuffer[framebuffer_idx]); ((GladGLContext*)context)->ClearColor(0.f, 0.f, 0.f, 0.f); ((GladGLContext*)context)->Clear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); } void render_present(Window* w, RenderObject *restrict default_quad) { const GladGLContext *restrict gl = w->context; gl->BindFramebuffer(GL_DRAW_FRAMEBUFFER, 0); gl->Disable(GL_DEPTH_TEST); gl->UseProgram(default_quad->shader.program); gl->BindTextureUnit(0, default_quad->texture); gl->BindVertexArray(default_quad->vao); ShaderBuffer* ibo = NULL; for (usize i = 0; i < default_quad->buffer_len; i++) { const u32 b_gl_type = ShaderBuffer_get_gl_type(default_quad->buffer[i].buffertype); if (b_gl_type == GL_ELEMENT_ARRAY_BUFFER) { ibo = &default_quad->buffer[i]; } gl->EnableVertexAttribArray((u32)i); gl->BindBuffer(b_gl_type, default_quad->buffer[i].buffername); gl->VertexAttribPointer( // index of the attribute (u32)i, // number of component (i32)default_quad->buffer[i].components, // type ShaderBuffer_get_gl_datatype(default_quad->buffer[i].buffertype), // normalized? GL_FALSE, // stride 0, // array buffer offset (void*)0 ); } // Draw the model ! const i32 sz = (i32)(default_quad->buffer->count * default_quad->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 < default_quad->buffer_len; i++) { gl->DisableVertexAttribArray(i); } gl->BindVertexArray(0); glfwSwapBuffers(w->window); } void window_reset_drawing(void) { // Clear } 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(RenderTargets *restrict t, i32 framebuffer_idx, Camera *restrict c) { ASSERT(t != NULL); ASSERT(framebuffer_idx >= 0 || framebuffer_idx < (i32)t->framebuffer_len); t->cam[framebuffer_idx] = 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 r_draw_model(void *restrict context, RenderTargets *restrict t, u32 framebuffer_idx, RenderObject* o, vec4 pos) { ASSERT(context != NULL); ASSERT(t != NULL); ASSERT(t->framebuffer != NULL); ASSERT(t->cam != NULL); ASSERT(t->cam[framebuffer_idx] != NULL); const GladGLContext *restrict gl = context; Camera c = *t->cam[framebuffer_idx]; gl->BindFramebuffer(GL_FRAMEBUFFER, t->framebuffer[framebuffer_idx]); gl->Enable(GL_DEPTH_TEST); gl->DepthFunc(GL_LEQUAL); //const f32 s = pos[3]; 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); gl->UseProgram(o->shader.program); // ENUMERATE FRAMEBUFFER TEXTURES 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(camera_matrix, model, 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); } 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_setup_framebuffer(void *restrict ctx, u32 framebuffer_id, FramebufferParameters param, u32 *restrict buffer_array, u32 *restrict buffer_parameters) { r_create_textures(ctx, buffer_array, buffer_parameters, ¶m.dimensions, param.num_textures); r_create_renderbuffers(ctx, &buffer_array[param.num_textures], &buffer_parameters[param.num_textures], ¶m.dimensions, param.num_renderbuffers); r_attach_buffers(ctx, framebuffer_id, buffer_array, buffer_parameters, param.num_textures + param.num_renderbuffers); } 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); } static u32 gl_texture_format(u32 texture_format) { switch (texture_format) { case BUFFERPARAMETER_FMT_RGBA8: return GL_RGBA8; case BUFFERPARAMETER_FMT_RGB8: return GL_RGB8; case BUFFERPARAMETER_FMT_SRGB8: return GL_SRGB; case BUFFERPARAMETER_FMT_SRGBA8: return GL_SRGB8_ALPHA8; case BUFFERPARAMETER_FMT_DEPTH24_STENCIL8: return GL_DEPTH24_STENCIL8; case BUFFERPARAMETER_FMT_DEPTH16: return GL_DEPTH_COMPONENT16; case BUFFERPARAMETER_FMT_DEPTH24: return GL_DEPTH_COMPONENT24; case BUFFERPARAMETER_FMT_DEPTH32: return GL_DEPTH_COMPONENT32; case BUFFERPARAMETER_FMT_DEPTH32F: return GL_DEPTH_COMPONENT32F; case BUFFERPARAMETER_FMT_STENCIL8: return GL_STENCIL_INDEX8; default: ERROR("Failed to convert format to GL internal format!"); exit(EXIT_FAILURE); } } void r_create_textures(void* restrict ctx, u32* restrict texture_array, u32* restrict texture_parameters, ivec3* restrict texture_size, usize num_targets) { const GladGLContext* gl = (GladGLContext*)ctx; i8 texture_dim = 2; u32 err = 0; ivec3 texture_sz; ASSERT(texture_size != NULL); glm_ivec3_copy(*texture_size, texture_sz); while (texture_dim > 0 && texture_sz[texture_dim] == 0) { texture_dim--; } // Check that if texture_dim == 0 then texture_sz[0] > 0. ASSERT(texture_sz[texture_dim] > 0); switch (texture_dim) { case 0: gl->CreateTextures(GL_TEXTURE_1D, num_targets, texture_array); ASSERT(!gl->GetError()); for (usize i = 0; i < num_targets; i++) { gl->TextureStorage1D(texture_array[i], 1, gl_texture_format(BUFFERPARAMETER_GET_PARAMETER(texture_parameters[i])), texture_sz[0]); } break; case 1: gl->CreateTextures(GL_TEXTURE_2D, num_targets, texture_array); ASSERT(!gl->GetError()); for (usize i = 0; i < num_targets; i++) { gl->TextureStorage2D(texture_array[i], 1, gl_texture_format(BUFFERPARAMETER_GET_PARAMETER(texture_parameters[i])), texture_sz[0], texture_sz[1]); } break; case 2: gl->CreateTextures(GL_TEXTURE_3D, num_targets, texture_array); ASSERT(!gl->GetError()); for (usize i = 0; i < num_targets; i++) { gl->TextureStorage3D(texture_array[i], 1, gl_texture_format(BUFFERPARAMETER_GET_PARAMETER(texture_parameters[i])), texture_sz[0], texture_sz[1], texture_sz[2]); } break; default: ERROR("Failed to convert dimensionality to GL_TEXTURE_XD"); exit(EXIT_FAILURE); } err = gl->GetError(); if (err) { switch (err) { case GL_INVALID_OPERATION: ERROR("Failed to create textures! GL_INVALID_OPERATION"); break; case GL_INVALID_ENUM: ERROR("Failed to create textures! GL_INVALID_ENUM"); break; case GL_INVALID_VALUE: ERROR("Failed to create textures! GL_INVALID_VALUE"); break; default: ERROR("Failed to create textures!"); break; } } // This should probably be changed in the future for (usize i = 0; i < num_targets; i++) { gl->TextureParameteri(texture_array[i], GL_TEXTURE_MAG_FILTER, GL_NEAREST); gl->TextureParameteri(texture_array[i], GL_TEXTURE_MIN_FILTER, GL_NEAREST); } err = gl->GetError(); if (err) { ERROR("Failed to set texture parameters!"); } } void r_destroy_textures(void *restrict ctx, u32* textures, usize num_textures) { const GladGLContext* gl = (GladGLContext*)ctx; gl->DeleteTextures(num_textures, textures); } // I know there's a "size too many", but it is included for easier // interopability with r_create_textures void r_create_renderbuffers(void *restrict ctx, u32* restrict buffer_array, u32* restrict buffer_types, ivec3* restrict buffer_sizes, usize num_buffers) { GladGLContext *restrict gl = (GladGLContext*)ctx; gl->CreateRenderbuffers(num_buffers, buffer_array); for (usize i = 0; i < num_buffers; i++) { gl->NamedRenderbufferStorage( buffer_array[i], gl_texture_format(BUFFERPARAMETER_GET_PARAMETER(buffer_types[i])), buffer_sizes[i][0], buffer_sizes[i][1] ); } } void r_destroy_renderbuffers(void *restrict ctx, u32* buffers, usize num_buffers) { ((GladGLContext *restrict)ctx)->DeleteRenderbuffers(num_buffers, buffers); } static u32 gl_format_attachment(u32 format) { if ((format & BUFFERPARAMETER_FMT_COLOR_MASK) != 0) { return GL_COLOR_ATTACHMENT0; } switch (format) { case BUFFERPARAMETER_FMT_DEPTH24_STENCIL8: return GL_DEPTH_STENCIL_ATTACHMENT; case BUFFERPARAMETER_FMT_DEPTH16: case BUFFERPARAMETER_FMT_DEPTH24: case BUFFERPARAMETER_FMT_DEPTH32: case BUFFERPARAMETER_FMT_DEPTH32F: return GL_DEPTH_ATTACHMENT; case BUFFERPARAMETER_FMT_STENCIL8: return GL_STENCIL_ATTACHMENT; default: UNIMPLEMENTED; return 0; } } void r_attach_buffers(void *restrict ctx, u32 fbo, u32* buffers, u32* buffer_parameters, i32 num_buffers) { const GladGLContext *restrict gl = (GladGLContext*)ctx; u32 err; u32 color_ofst = 0; for (i32 i = 0; i < num_buffers; i++) { u32 attachment = gl_format_attachment(BUFFERPARAMETER_GET_PARAMETER(buffer_parameters[i])); ASSERT(attachment != 0); switch (BUFFERPARAMETER_GET_TYPE(buffer_parameters[i])) { case BufferType_frame: UNIMPLEMENTED; case BufferType_texture: if (attachment == GL_COLOR_ATTACHMENT0) { gl->NamedFramebufferTexture(fbo, GL_COLOR_ATTACHMENT0 + color_ofst, buffers[i], 0); color_ofst++; } else { gl->NamedFramebufferTexture(fbo, attachment, buffers[i], 0); } break; case BufferType_render: gl->NamedFramebufferRenderbuffer(fbo, attachment, GL_RENDERBUFFER, buffers[i]); break; case BufferType_buffer: UNIMPLEMENTED; default: UNIMPLEMENTED; } err = gl->GetError(); if (err) { ERROR("Failed to attach buffer! glError: %u", err); } } } void framebuffer_size_callback_default(ivec3* dst, ivec2 src) { glm_ivec2_copy(src, *dst); } void camera_reset_callback_default(void* _, Camera* dst, void* state, ivec2 src) { r_reset_camera(dst, src); } RenderObject init_default_renderobject(Window *restrict w, Shader s) { u32 first_texture = 0; for (usize i = 0; i < w->render_targets->buffer_len; i++) { if (BUFFERPARAMETER_GET_TYPE(w->render_targets->buffer_parameters[i]) == BufferType_texture) { first_texture = w->render_targets->buffer[i]; break; } } // WHAT?! // --- Should _really_ make renderobjects heap allocated..! return RenderObject_new( // Shader &s, // Texture first_texture, // Vertices shaderbuf, sizeof(shaderbuf) / sizeof(ShaderBuffer) ); } Shader init_default_shader(void) { // TODO: MOVE TO INIT Shader default_quad_shaders[] = { compile_shader(default_quad_shader_vertex_src, Shader_Vertex), compile_shader(default_quad_shader_fragment_src, Shader_Fragment), (Shader){0}, }; return compose_shader(default_quad_shaders, 2); } void r_reset_pipeline(Window* w) { if (w->render_targets == NULL) { // Create only 1 additional framebuffer, in addition to the default // one. This is used to render a texture that is represented as a quad // on the default framebuffer. u32 t[] = { BUFFERPARAMETER_SET_PARAMETER(BUFFERPARAMETER_SET_TYPE(0, BufferType_texture), BUFFERPARAMETER_FMT_RGB8), // The depth buffer could also be a texture like so: // BUFFERPARAMETER_SET_PARAMETER( BUFFERPARAMETER_SET_TYPE(0, BufferType_texture), BUFFERPARAMETER_FMT_DEPTH32), BUFFERPARAMETER_SET_PARAMETER(BUFFERPARAMETER_SET_TYPE(0, BufferType_render), BUFFERPARAMETER_FMT_DEPTH32F), }; FramebufferParameters p[] = { // 16 by 16 is just some bogus values, but they cannot be zero, as they're // needed to be set when resetting cameras. {.num_textures = 1, .num_renderbuffers = 1, .dimensions = {16, 16, 0}}, }; window_init_renderstack(w, 1, sizeof(t) / sizeof(t[0]), p, t); // Set default callbacks and camera for default framebuffer if (w->render_targets->framebuffer_size_callback[0] == NULL) { w->render_targets->framebuffer_size_callback[0] = framebuffer_size_callback_default; } if (w->render_targets->camera_reset_callback[0] == NULL) { w->render_targets->camera_reset_callback[0] = camera_reset_callback_default; } if (w->render_targets->camera_reset_callback[0 == NULL]) { w->render_targets->cam[0] = &default_camera; } } window_reset_cameras(w, GLOBAL_PLATFORM->mem->data, w->render_targets); // TODO: Both the shader & renderobject should only be created here if it is // overwritten in Instance->Window. // TODO: Add default quad & renderobject to Instance->Window. Should allow // for customization. //default_quad_renderobject = init_default_renderobject(p->window, default_quad_shader); }