summaryrefslogtreecommitdiff
path: root/src/rendering.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/rendering.c')
-rw-r--r--src/rendering.c696
1 files changed, 696 insertions, 0 deletions
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 <stdio.h>
+#include <string.h>
+
+#include <glad/gl.h>
+#include <GLFW/glfw3.h>
+#include <cglm/cam.h>
+#include <cglm/vec2.h>
+#include <cglm/mat4.h>
+
+
+#include <daw/daw.h>
+#include <daw/utils.h>
+#include <daw/rendering.h>
+
+
+/* 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;
+}