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