1#include <sokol_gfx.h>
2#include <stb_image_c89.h>
3
4#include "util.h"
5#include "types.h"
6#include "platform/platform.h"
7#include "renderer/renderer.h"
8#include "renderer_backend.h"
9
10#define MAX_MESHES (512) /* TODO; random value ig */
11#define MAX_TEXTURES (512) /* TODO; random value ig */
12
13typedef struct {
14 sg_buffer vb; /* vertex buffer */
15 sg_buffer ib; /* index buffer */
16 u32 ic; /* index count */
17 i8 used;
18} RendererMesh;
19
20typedef struct {
21 sg_image image;
22 sg_view view;
23 i8 used;
24} RendererTexture;
25
26typedef struct {
27 MGContext* ctx;
28 i8 pass; /* basically a bool; in
29 render pass or not */
30
31 sg_shader shader;
32 sg_pipeline pipeline;
33 sg_sampler sampler;
34
35 RendererMesh meshes[MAX_MESHES];
36 RendererTexture textures[MAX_MESHES];
37} RendererState;
38
39/* create the shader and render pipeline */
40static void create_pipelines(void);
41
42/* create texture sampler */
43static void create_sampler(void);
44
45/* logging function for passing to sokol */
46static void sokol_log(const char* tag, u32 level, u32 item, const char* msg, u32 ln,
47 const char* file, void* user);
48
49static RendererState ren;
50/* TODO: textures */
51static const char* vsdr = "#version 330\n"
52 "layout(location=0) in vec3 position;\n"
53 "layout(location=1) in vec2 texcoord0;\n"
54 "layout(location=2) in vec4 color0;\n"
55 "\n"
56 "uniform mat4 mvp;\n"
57 "\n"
58 "out vec2 uv;\n"
59 "out vec4 colour;\n"
60 "\n"
61 "void main(void)\n"
62 "{\n"
63 " gl_Position = mvp * vec4(position, 1.0);\n"
64 " uv = texcoord0;\n"
65 " colour = color0;\n"
66 "}\n";
67static const char* fsdr = "#version 330\n"
68 "in vec2 uv;\n"
69 "in vec4 colour;\n"
70 "\n"
71 "uniform sampler2D tex;\n"
72 "\n"
73 "out vec4 frag_colour;\n"
74 "\n"
75 "void main(void)\n"
76 "{\n"
77 " frag_colour = texture(tex, uv);\n"
78 "}\n";
79
80static void create_pipelines(void)
81{
82 sg_shader_desc shader_desc = { 0 };
83 sg_pipeline_desc pipeline_desc = { 0 };
84
85 /* shader descriptor */
86 shader_desc.vertex_func.source = vsdr;
87 shader_desc.fragment_func.source = fsdr;
88 shader_desc.attrs[0].glsl_name = "position";
89 shader_desc.attrs[1].glsl_name = "texcoord0";
90 shader_desc.attrs[2].glsl_name = "color0";
91 shader_desc.uniform_blocks[0].stage = SG_SHADERSTAGE_VERTEX;
92 shader_desc.uniform_blocks[0].size = sizeof(float) * 16; /* size of mvp matrix */
93 shader_desc.uniform_blocks[0].layout = SG_UNIFORMLAYOUT_STD140;
94 shader_desc.uniform_blocks[0].glsl_uniforms[0].glsl_name = "mvp";
95 shader_desc.uniform_blocks[0].glsl_uniforms[0].type = SG_UNIFORMTYPE_MAT4;
96
97 /* textures */
98 shader_desc.views[0].texture.stage = SG_SHADERSTAGE_FRAGMENT;
99 shader_desc.views[0].texture.image_type = SG_IMAGETYPE_2D;
100 shader_desc.views[0].texture.sample_type = SG_IMAGESAMPLETYPE_FLOAT;
101 shader_desc.samplers[0].stage = SG_SHADERSTAGE_FRAGMENT;
102 shader_desc.samplers[0].sampler_type = SG_SAMPLERTYPE_FILTERING;
103 shader_desc.texture_sampler_pairs[0].stage = SG_SHADERSTAGE_FRAGMENT;
104 shader_desc.texture_sampler_pairs[0].view_slot = 0;
105 shader_desc.texture_sampler_pairs[0].sampler_slot = 0;
106 shader_desc.texture_sampler_pairs[0].glsl_name = "tex";
107
108 ren.shader = sg_make_shader(&shader_desc);
109 if (sg_query_shader_state(ren.shader) != SG_RESOURCESTATE_VALID)
110 mg_die(MG_ERR_SHADER_CREATE, "Failed to create shader");
111
112 /* pipeline descriptor */
113 pipeline_desc.shader = ren.shader;
114 pipeline_desc.layout.buffers[0].stride = sizeof(MGVertex);
115 pipeline_desc.layout.attrs[0].format = SG_VERTEXFORMAT_FLOAT3;
116 pipeline_desc.layout.attrs[1].format = SG_VERTEXFORMAT_FLOAT2;
117 pipeline_desc.layout.attrs[2].format = SG_VERTEXFORMAT_UBYTE4N;
118 pipeline_desc.index_type = SG_INDEXTYPE_UINT16;
119 pipeline_desc.depth.compare = SG_COMPAREFUNC_LESS_EQUAL;
120 pipeline_desc.depth.write_enabled = true;
121 pipeline_desc.cull_mode = SG_CULLMODE_BACK;
122 pipeline_desc.face_winding = SG_FACEWINDING_CCW;
123
124 ren.pipeline = sg_make_pipeline(&pipeline_desc);
125 if (sg_query_pipeline_state(ren.pipeline) != SG_RESOURCESTATE_VALID)
126 mg_die(MG_ERR_PIPELINE_CREATE, "Failed to create pipeline");
127}
128
129static void create_sampler(void)
130{
131 sg_sampler_desc desc = { 0 };
132
133 desc.min_filter = SG_FILTER_NEAREST;
134 desc.mag_filter = SG_FILTER_NEAREST;
135 desc.wrap_u = SG_WRAP_REPEAT;
136 desc.wrap_v = SG_WRAP_REPEAT;
137
138 ren.sampler = sg_make_sampler(&desc);
139
140 if (sg_query_sampler_state(ren.sampler) != SG_RESOURCESTATE_VALID)
141 mg_die(MG_ERR_SAMPLER_CREATE, "Failed to create sampler");
142}
143
144static void sokol_log(const char* tag, u32 level, u32 item, const char* msg, u32 ln,
145 const char* file, void* user)
146{
147 LogType type;
148 (void)item;
149 (void)user;
150
151 type = level <= 1 ? LOG_ERR : LOG_DBG;
152
153 mg_log(type, "%s: %s [%s:%u]", tag ? tag : "Sokol", msg ? msg : "No message",
154 file ? file : "Unknown", ln);
155}
156
157void renderer_backend_create(MGContext* ctx)
158{
159 sg_desc desc = { 0 };
160
161 ren.ctx = ctx;
162 ren.pass = 0;
163
164 desc.environment.defaults.color_format = SG_PIXELFORMAT_RGBA8;
165 desc.environment.defaults.depth_format = SG_PIXELFORMAT_DEPTH;
166 desc.environment.defaults.sample_count = 1;
167 desc.logger.func = sokol_log;
168 desc.buffer_pool_size = MAX_MESHES * 2;
169
170 sg_setup(&desc);
171
172 if (!sg_isvalid())
173 mg_die(MG_ERR_REN_CREATE, "Failed to initialise renderer");
174
175 create_pipelines();
176 create_sampler();
177}
178
179void renderer_backend_begin(void)
180{
181 sg_pass pass = { 0 };
182 i32 width;
183 i32 height;
184
185 ren.pass = 0;
186
187 platform_get_drawable_size(ren.ctx, &width, &height);
188 /* (minimized window?) no need to draw */
189 if (width <= 0 || height <= 0)
190 return;
191
192 pass.action.colors[0].load_action = SG_LOADACTION_CLEAR;
193 pass.action.colors[0].clear_value.r = 0.90f;
194 pass.action.colors[0].clear_value.g = 0.80f;
195 pass.action.colors[0].clear_value.b = 0.70f;
196 pass.action.colors[0].clear_value.a = 1.00f;
197
198 pass.action.depth.load_action = SG_LOADACTION_CLEAR;
199 pass.action.depth.clear_value = 1.0f;
200
201 /* swapchain descriptor */
202 pass.swapchain.width = width;
203 pass.swapchain.height = height;
204 pass.swapchain.sample_count = 1;
205 pass.swapchain.color_format = SG_PIXELFORMAT_RGBA8;
206 pass.swapchain.depth_format = SG_PIXELFORMAT_DEPTH;
207
208 pass.swapchain.gl.framebuffer = 0;
209
210 sg_begin_pass(&pass);
211 ren.pass = 1;
212}
213
214void renderer_backend_end(void)
215{
216 if (!ren.pass)
217 return;
218
219 sg_end_pass();
220 sg_commit();
221
222 ren.pass = 0;
223}
224
225void renderer_backend_destroy(void)
226{
227 u32 id;
228
229 /* the values could be different in future so
230 probably should keep them separate */
231 for (id = 1; id < MAX_MESHES; id++)
232 renderer_backend_mesh_destroy(id);
233 for (id = 1; id < MAX_TEXTURES; id++)
234 renderer_backend_texture_destroy(id);
235
236 sg_destroy_sampler(ren.sampler);
237 sg_destroy_pipeline(ren.pipeline);
238 sg_destroy_shader(ren.shader);
239
240 if (sg_isvalid())
241 sg_shutdown();
242
243 ren.ctx = NULL;
244 ren.pass = 0;
245}
246
247MGMesh renderer_backend_mesh_create(const MGMeshDesc* desc)
248{
249 sg_buffer_desc buffer_desc = { 0 };
250 RendererMesh* mesh;
251 u32 id;
252
253 if (!desc || !desc->vtc || !desc->idc || desc->vtcc == 0 || desc->idcc == 0)
254 return MGMESH_INVALID;
255
256 /* find free mesh slot */
257 for (id = 1; id < MAX_MESHES; id++) {
258 if (!ren.meshes[id].used)
259 break;
260 }
261
262 if (id == MAX_MESHES) {
263 mg_log(LOG_ERR, "Maximum mesh limit reached");
264 return MGMESH_INVALID;
265 }
266
267 /* vertex buffer */
268 mesh = &ren.meshes[id];
269 buffer_desc.data.ptr = desc->vtc;
270 buffer_desc.data.size = desc->vtcc * sizeof(MGVertex);
271 mesh->vb = sg_make_buffer(&buffer_desc);
272
273 /* index buffer */
274 buffer_desc = (sg_buffer_desc){ 0 };
275 buffer_desc.usage.vertex_buffer = false;
276 buffer_desc.usage.index_buffer = true;
277 buffer_desc.usage.immutable = true;
278 buffer_desc.data.ptr = desc->idc;
279 buffer_desc.data.size = desc->idcc * sizeof(u16);
280 mesh->ib = sg_make_buffer(&buffer_desc);
281
282 i8 valid = sg_query_buffer_state(mesh->vb) == SG_RESOURCESTATE_VALID &&
283 sg_query_buffer_state(mesh->ib) == SG_RESOURCESTATE_VALID;
284 if (!valid) {
285 if (sg_query_buffer_state(mesh->vb) == SG_RESOURCESTATE_VALID)
286 sg_destroy_buffer(mesh->vb);
287 if (sg_query_buffer_state(mesh->ib) == SG_RESOURCESTATE_VALID)
288 sg_destroy_buffer(mesh->ib);
289
290 *mesh = (RendererMesh){ 0 };
291 mg_log(LOG_ERR, "Failed to create mesh buffers");
292 return MGMESH_INVALID;
293 }
294
295 mesh->ic = desc->idcc;
296 mesh->used = 1;
297 return id;
298}
299
300void renderer_backend_mesh_draw(MGMesh mesh, MGTexture texture, const MGMat4* mvp)
301{
302 RendererMesh* rmesh;
303 RendererTexture* rtexture;
304 sg_bindings bindings = { 0 };
305 sg_range uniforms;
306
307 if (!ren.pass || !mvp)
308 return;
309
310 if (mesh == MGMESH_INVALID || mesh >= MAX_MESHES)
311 return;
312
313 if (texture == MGTEXTURE_INVALID || texture >= MAX_TEXTURES)
314 return;
315
316 rmesh = &ren.meshes[mesh];
317 rtexture = &ren.textures[texture];
318 if (!rmesh->used || !rtexture->used)
319 return;
320
321 bindings.vertex_buffers[0] = rmesh->vb;
322 bindings.index_buffer = rmesh->ib;
323 bindings.views[0] = rtexture->view;
324 bindings.samplers[0] = ren.sampler;
325
326 uniforms.ptr = &mvp->Elements[0][0];
327 uniforms.size = sizeof(float) * 16;
328
329 sg_apply_pipeline(ren.pipeline);
330 sg_apply_bindings(&bindings);
331 sg_apply_uniforms(0, &uniforms);
332
333 sg_draw(0, rmesh->ic, 1);
334}
335
336void renderer_backend_mesh_destroy(MGMesh id)
337{
338 RendererMesh* mesh;
339
340 if (id == MGMESH_INVALID || id >= MAX_MESHES)
341 return;
342
343 mesh = &ren.meshes[id];
344 if (!mesh->used)
345 return;
346
347 sg_destroy_buffer(mesh->ib);
348 sg_destroy_buffer(mesh->vb);
349
350 *mesh = (RendererMesh){ 0 };
351}
352
353MGTexture renderer_backend_texture_load(const char* path)
354{
355 stbi_uc* pixels;
356 int width;
357 int height;
358 int channels;
359 MGTextureDesc desc;
360 MGTexture texture;
361
362 if (!path)
363 return MGTEXTURE_INVALID;
364
365 pixels = stbi_load(path, &width, &height, &channels, 4);
366 if (!pixels) {
367 mg_log(LOG_ERR, "Failed to load texture: %s", path);
368 return MGTEXTURE_INVALID;
369 }
370
371 desc.pixels = pixels;
372 desc.width = width;
373 desc.height = height;
374 texture = renderer_backend_texture_create(&desc);
375 stbi_image_free(pixels);
376 return texture;
377}
378
379MGTexture renderer_backend_texture_create(const MGTextureDesc* desc)
380{
381 sg_image_desc image_desc = { 0 };
382 sg_view_desc view_desc = { 0 };
383 RendererTexture* texture;
384 u32 id;
385
386 if (!desc || !desc->pixels || desc->width <= 0 || desc->height <= 0)
387 return MGTEXTURE_INVALID;
388
389 for (id = 1; id < MAX_TEXTURES; id++) {
390 if (!ren.textures[id].used)
391 break;
392 }
393
394 if (id == MAX_TEXTURES) {
395 mg_log(LOG_ERR, "Maximum texture limit reached");
396 return MGTEXTURE_INVALID;
397 }
398
399 texture = &ren.textures[id];
400 image_desc.width = desc->width;
401 image_desc.height = desc->height;
402 image_desc.pixel_format = SG_PIXELFORMAT_RGBA8;
403 image_desc.data.mip_levels[0].ptr = desc->pixels;
404 image_desc.data.mip_levels[0].size = (size_t)desc->width * (size_t)desc->height * 4;
405
406 texture->image = sg_make_image(&image_desc);
407 if (sg_query_image_state(texture->image) != SG_RESOURCESTATE_VALID) {
408 *texture = (RendererTexture){ 0 };
409 mg_log(LOG_ERR, "Failed to create texture");
410 return MGTEXTURE_INVALID;
411 }
412
413 view_desc.texture.image = texture->image;
414 texture->view = sg_make_view(&view_desc);
415
416 if (sg_query_view_state(texture->view) != SG_RESOURCESTATE_VALID) {
417 sg_destroy_image(texture->image);
418 *texture = (RendererTexture){ 0 };
419 mg_log(LOG_ERR, "Failed to create texture view");
420 return MGTEXTURE_INVALID;
421 }
422
423 texture->used = 1;
424
425 return id;
426}
427
428void renderer_backend_texture_destroy(MGTexture id)
429{
430 RendererTexture* texture;
431
432 if (id == MGTEXTURE_INVALID || id >= MAX_TEXTURES)
433 return;
434
435 texture = &ren.textures[id];
436
437 if (!texture->used)
438 return;
439
440 sg_destroy_view(texture->view);
441 sg_destroy_image(texture->image);
442
443 *texture = (RendererTexture){ 0 };
444}