master uint/magnolia / source / renderer / renderer_sokol.c
  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}