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