small3dlib uint/magnolia / source / renderer / renderer_small3dlib.c
  1#include <SDL2/SDL.h>
  2#include <stdlib.h>
  3#include <string.h>
  4#include <stb_image_c89.h>
  5
  6#include "util.h"
  7#include "types.h"
  8#include "platform/platform.h"
  9#include "renderer/renderer.h"
 10#include "renderer_backend.h"
 11
 12#define MAX_MESHES (512)
 13#define MAX_TEXTURES (512)
 14#define S3L_MAX_WIDTH (4096)
 15#define S3L_MAX_HEIGHT (4096)
 16
 17#define S3L_MAX_PIXELS (S3L_MAX_WIDTH * S3L_MAX_HEIGHT)
 18#define S3L_PERSPECTIVE_CORRECTION 2
 19#define S3L_NEAR_CROSS_STRATEGY 1
 20#define S3L_Z_BUFFER 1
 21#define S3L_PIXEL_FUNCTION small3dlib_pixel
 22#include <small3dlib.h>
 23
 24typedef struct {
 25	MGVertex* vtc; /* vertices */
 26	u16*      idc; /* indices */
 27	u32       ic;  /* index count */
 28	i8        used;
 29} RendererMesh;
 30
 31typedef struct {
 32	u8* pixels;
 33	i32 width;
 34	i32 height;
 35	i8  used;
 36} RendererTexture;
 37
 38typedef struct {
 39	MGContext*  ctx;
 40	i8         pass; /* basically a bool; in
 41	                    render pass or not */
 42
 43	SDL_Texture* screen_texture;
 44	u32*        framebuffer;
 45	i32         width;
 46	i32         height;
 47
 48	RendererMesh    meshes[MAX_MESHES];
 49	RendererTexture textures[MAX_TEXTURES];
 50
 51	RendererMesh*    active_mesh;
 52	RendererTexture* active_texture;
 53	u32              active_triangle;
 54	i32              tri_uvs[6];
 55} RendererState;
 56
 57typedef struct {
 58	float x;
 59	float y;
 60	float z;
 61	float w;
 62} ClipVertex;
 63
 64static RendererState ren;
 65
 66/* create or resize the SDL texture used to present the software framebuffer */
 67static void create_screen_texture(i32 width, i32 height);
 68
 69/* destroy the SDL texture and software framebuffer */
 70static void destroy_screen_texture(void);
 71
 72static ClipVertex transform_vertex(const MGMat4* mvp, const MGVertex* v);
 73static i8 triangle_outside(const ClipVertex* a, const ClipVertex* b, const ClipVertex* c);
 74static i8 triangle_front_facing(const ClipVertex* a, const ClipVertex* b, const ClipVertex* c);
 75static S3L_Vec4 clip_to_screen(const ClipVertex* v);
 76static u32 sample_texture(const RendererMesh* mesh, const RendererTexture* texture, const S3L_PixelInfo* pixel);
 77static u32 rgba_to_sdl(u32 rgba);
 78
 79static inline void small3dlib_pixel(S3L_PixelInfo* pixel)
 80{
 81	u32 index;
 82
 83	if (!ren.active_mesh || !ren.active_texture || !ren.framebuffer)
 84		return;
 85
 86	index = (u32)pixel->y * (u32)ren.width + (u32)pixel->x;
 87	ren.framebuffer[index] = rgba_to_sdl(sample_texture(ren.active_mesh, ren.active_texture, pixel));
 88}
 89
 90static void create_screen_texture(i32 width, i32 height)
 91{
 92	u64 pixel_count;
 93	u32* framebuffer;
 94
 95	if (width == ren.width && height == ren.height && ren.framebuffer && ren.screen_texture)
 96		return;
 97
 98	if (width <= 0 || height <= 0)
 99		return;
100
101	if (width > S3L_MAX_WIDTH || height > S3L_MAX_HEIGHT)
102		mg_die(MG_ERR_REN_CREATE, "small3dlib framebuffer exceeds %dx%d", S3L_MAX_WIDTH, S3L_MAX_HEIGHT);
103
104	pixel_count = (u64)width * (u64)height;
105	framebuffer = malloc(pixel_count * sizeof(*framebuffer));
106	if (!framebuffer)
107		mg_die(MG_ERR_REN_CREATE, "Failed to allocate small3dlib framebuffer");
108
109	if (ren.screen_texture)
110		SDL_DestroyTexture(ren.screen_texture);
111
112	ren.screen_texture = SDL_CreateTexture(
113		ren.ctx->vbe.renderer,
114		SDL_PIXELFORMAT_RGBA8888,
115		SDL_TEXTUREACCESS_STREAMING,
116		width,
117		height
118	);
119
120	if (!ren.screen_texture)
121		mg_die(MG_ERR_REN_CREATE, "Failed to create small3dlib texture: %s", SDL_GetError());
122
123	free(ren.framebuffer);
124	ren.framebuffer = framebuffer;
125	ren.width = width;
126	ren.height = height;
127
128	S3L_resolutionX = (uint16_t)width;
129	S3L_resolutionY = (uint16_t)height;
130}
131
132static void destroy_screen_texture(void)
133{
134	if (ren.screen_texture) {
135		SDL_DestroyTexture(ren.screen_texture);
136		ren.screen_texture = NULL;
137	}
138
139	free(ren.framebuffer);
140	ren.framebuffer = NULL;
141	ren.width = 0;
142	ren.height = 0;
143}
144
145static ClipVertex transform_vertex(const MGMat4* mvp, const MGVertex* v)
146{
147	ClipVertex r;
148
149	r.x = mvp->Elements[0][0] * v->x + mvp->Elements[1][0] * v->y +
150	      mvp->Elements[2][0] * v->z + mvp->Elements[3][0];
151	r.y = mvp->Elements[0][1] * v->x + mvp->Elements[1][1] * v->y +
152	      mvp->Elements[2][1] * v->z + mvp->Elements[3][1];
153	r.z = mvp->Elements[0][2] * v->x + mvp->Elements[1][2] * v->y +
154	      mvp->Elements[2][2] * v->z + mvp->Elements[3][2];
155	r.w = mvp->Elements[0][3] * v->x + mvp->Elements[1][3] * v->y +
156	      mvp->Elements[2][3] * v->z + mvp->Elements[3][3];
157
158	return r;
159}
160
161static i8 triangle_outside(const ClipVertex* a, const ClipVertex* b, const ClipVertex* c)
162{
163	if (a->w <= 0.0001f || b->w <= 0.0001f || c->w <= 0.0001f)
164		return 1;
165
166	if (a->x < -a->w && b->x < -b->w && c->x < -c->w)
167		return 1;
168	if (a->x > a->w && b->x > b->w && c->x > c->w)
169		return 1;
170	if (a->y < -a->w && b->y < -b->w && c->y < -c->w)
171		return 1;
172	if (a->y > a->w && b->y > b->w && c->y > c->w)
173		return 1;
174	if (a->z < -a->w || b->z < -b->w || c->z < -c->w)
175		return 1;
176	if (a->z > a->w && b->z > b->w && c->z > c->w)
177		return 1;
178
179	return 0;
180}
181
182static i8 triangle_front_facing(const ClipVertex* a, const ClipVertex* b, const ClipVertex* c)
183{
184	float ax = a->x / a->w;
185	float ay = a->y / a->w;
186	float bx = b->x / b->w;
187	float by = b->y / b->w;
188	float cx = c->x / c->w;
189	float cy = c->y / c->w;
190	float area = (bx - ax) * (cy - ay) - (by - ay) * (cx - ax);
191
192	return area > 0.0f;
193}
194
195static S3L_Vec4 clip_to_screen(const ClipVertex* v)
196{
197	S3L_Vec4 r;
198	float inv_w = 1.0f / v->w;
199	float ndc_x = v->x * inv_w;
200	float ndc_y = v->y * inv_w;
201
202	r.x = (S3L_Unit)((ndc_x * 0.5f + 0.5f) * (float)(ren.width - 1));
203	r.y = (S3L_Unit)((0.5f - ndc_y * 0.5f) * (float)(ren.height - 1));
204	r.z = (S3L_Unit)(v->w * (float)S3L_F);
205	if (r.z < 1)
206		r.z = 1;
207	r.w = S3L_F;
208
209	return r;
210}
211
212static u32 sample_texture(const RendererMesh* mesh, const RendererTexture* texture, const S3L_PixelInfo* pixel)
213{
214	u32 tri;
215	const MGVertex* v[3];
216	int barycentric[3];
217	i32 x;
218	i32 y;
219	u8* texel;
220
221	if (!texture || !texture->pixels)
222		return 0xffffffff;
223
224	if (ren.active_triangle != (u32)pixel->triangleIndex) {
225		ren.active_triangle = pixel->triangleIndex;
226		tri = (u32)pixel->triangleIndex * 3;
227		v[0] = &mesh->vtc[mesh->idc[tri + 0]];
228		v[1] = &mesh->vtc[mesh->idc[tri + 1]];
229		v[2] = &mesh->vtc[mesh->idc[tri + 2]];
230
231		ren.tri_uvs[0] = (i32)(v[0]->u * (float)texture->width);
232		ren.tri_uvs[1] = (i32)(v[0]->v * (float)texture->height);
233		ren.tri_uvs[2] = (i32)(v[1]->u * (float)texture->width);
234		ren.tri_uvs[3] = (i32)(v[1]->v * (float)texture->height);
235		ren.tri_uvs[4] = (i32)(v[2]->u * (float)texture->width);
236		ren.tri_uvs[5] = (i32)(v[2]->v * (float)texture->height);
237	}
238
239	/*licar taught me*/
240	/*https://en.wikipedia.org/wiki/Barycentric_coordinate_system*/
241	barycentric[0] = pixel->barycentric[0] / 8;
242	barycentric[1] = pixel->barycentric[1] / 8;
243	barycentric[2] = pixel->barycentric[2] / 8;
244
245	x = (barycentric[0] * ren.tri_uvs[0] +
246	     barycentric[1] * ren.tri_uvs[2] +
247	     barycentric[2] * ren.tri_uvs[4]) / (S3L_F / 8);
248	y = (barycentric[0] * ren.tri_uvs[1] +
249	     barycentric[1] * ren.tri_uvs[3] +
250	     barycentric[2] * ren.tri_uvs[5]) / (S3L_F / 8);
251
252	x %= texture->width;
253	y %= texture->height;
254	if (x < 0)
255		x += texture->width;
256	if (y < 0)
257		y += texture->height;
258
259	texel = texture->pixels + ((y * texture->width + x) * 4);
260	return ((u32)texel[0]) |
261	       ((u32)texel[1] << 8) |
262	       ((u32)texel[2] << 16) |
263	       ((u32)texel[3] << 24);
264}
265
266static u32 rgba_to_sdl(u32 rgba)
267{
268	u32 r = (rgba >> 0) & 0xff;
269	u32 g = (rgba >> 8) & 0xff;
270	u32 b = (rgba >> 16) & 0xff;
271	u32 a = (rgba >> 24) & 0xff;
272
273	return (r << 24) | (g << 16) | (b << 8) | a;
274}
275
276void renderer_backend_create(MGContext* ctx)
277{
278	ren.ctx = ctx;
279	ren.pass = 0;
280	ren.active_mesh = NULL;
281	ren.active_texture = NULL;
282	ren.active_triangle = (u32)-1;
283}
284
285void renderer_backend_begin(void)
286{
287	i32 width;
288	i32 height;
289	u64 i;
290	u64 pixel_count;
291	u32 clear_colour = rgba_to_sdl(0xffb3cce6);
292
293	ren.pass = 0;
294	platform_get_drawable_size(ren.ctx, &width, &height);
295	if (width <= 0 || height <= 0)
296		return;
297
298	create_screen_texture(width, height);
299
300	pixel_count = (u64)ren.width * (u64)ren.height;
301	for (i = 0; i < pixel_count; i++)
302		ren.framebuffer[i] = clear_colour;
303
304	S3L_newFrame();
305	ren.pass = 1;
306}
307
308void renderer_backend_end(void)
309{
310	if (!ren.pass)
311		return;
312
313	SDL_UpdateTexture(ren.screen_texture, NULL, ren.framebuffer, ren.width * (i32)sizeof(*ren.framebuffer));
314	SDL_RenderClear(ren.ctx->vbe.renderer);
315	SDL_RenderCopy(ren.ctx->vbe.renderer, ren.screen_texture, NULL, NULL);
316	ren.pass = 0;
317}
318
319void renderer_backend_destroy(void)
320{
321	u32 id;
322
323	/* the values could be different in future so
324	   probably should keep them separate */
325	for (id = 1; id < MAX_MESHES; id++)
326		renderer_backend_mesh_destroy(id);
327	for (id = 1; id < MAX_TEXTURES; id++)
328		renderer_backend_texture_destroy(id);
329
330	destroy_screen_texture();
331
332	ren.ctx = NULL;
333	ren.active_mesh = NULL;
334	ren.active_texture = NULL;
335	ren.active_triangle = (u32)-1;
336	ren.pass = 0;
337}
338
339MGMesh renderer_backend_mesh_create(const MGMeshDesc* desc)
340{
341	RendererMesh* mesh;
342	u32 id;
343
344	if (!desc || !desc->vtc || !desc->idc || desc->vtcc == 0 || desc->idcc == 0)
345		return MGMESH_INVALID;
346
347	if (desc->idcc % 3 != 0)
348		return MGMESH_INVALID;
349
350	/* find free mesh slot */
351	for (id = 1; id < MAX_MESHES; id++) {
352		if (!ren.meshes[id].used)
353			break;
354	}
355
356	if (id == MAX_MESHES) {
357		mg_log(LOG_ERR, "Maximum mesh limit reached");
358		return MGMESH_INVALID;
359	}
360
361	/* copy vertex and index data */
362	mesh = &ren.meshes[id];
363	mesh->vtc = malloc(desc->vtcc * sizeof(*mesh->vtc));
364	mesh->idc = malloc(desc->idcc * sizeof(*mesh->idc));
365	if (!mesh->vtc || !mesh->idc) {
366		free(mesh->vtc);
367		free(mesh->idc);
368		*mesh = (RendererMesh){0};
369		mg_log(LOG_ERR, "Failed to allocate mesh data");
370		return MGMESH_INVALID;
371	}
372
373	memcpy(mesh->vtc, desc->vtc, desc->vtcc * sizeof(*mesh->vtc));
374	memcpy(mesh->idc, desc->idc, desc->idcc * sizeof(*mesh->idc));
375	mesh->ic = desc->idcc;
376	mesh->used = 1;
377
378	return id;
379}
380
381void renderer_backend_mesh_draw(MGMesh mesh, MGTexture texture, const MGMat4* mvp)
382{
383	RendererMesh* rmesh;
384	RendererTexture* rtexture;
385	u32 tri;
386
387	if (!ren.pass || !mvp)
388		return;
389
390	if (mesh == MGMESH_INVALID || mesh >= MAX_MESHES)
391		return;
392
393	if (texture == MGTEXTURE_INVALID || texture >= MAX_TEXTURES)
394		return;
395
396	rmesh = &ren.meshes[mesh];
397	rtexture = &ren.textures[texture];
398	if (!rmesh->used || !rtexture->used)
399		return;
400
401	ren.active_mesh = rmesh;
402	ren.active_texture = rtexture;
403	ren.active_triangle = (u32)-1;
404
405	for (tri = 0; tri < rmesh->ic; tri += 3) {
406		ClipVertex ca;
407		ClipVertex cb;
408		ClipVertex cc;
409		S3L_Vec4 sa;
410		S3L_Vec4 sb;
411		S3L_Vec4 sc;
412
413		ca = transform_vertex(mvp, &rmesh->vtc[rmesh->idc[tri + 0]]);
414		cb = transform_vertex(mvp, &rmesh->vtc[rmesh->idc[tri + 1]]);
415		cc = transform_vertex(mvp, &rmesh->vtc[rmesh->idc[tri + 2]]);
416
417		if (triangle_outside(&ca, &cb, &cc))
418			continue;
419		if (!triangle_front_facing(&ca, &cb, &cc))
420			continue;
421
422		sa = clip_to_screen(&ca);
423		sb = clip_to_screen(&cb);
424		sc = clip_to_screen(&cc);
425
426		S3L_drawTriangle(sa, sb, sc, 0, (S3L_Index)(tri / 3));
427	}
428
429	ren.active_mesh = NULL;
430	ren.active_texture = NULL;
431	ren.active_triangle = (u32)-1;
432}
433
434void renderer_backend_mesh_destroy(MGMesh id)
435{
436	RendererMesh* mesh;
437
438	if (id == MGMESH_INVALID || id >= MAX_MESHES)
439		return;
440
441	mesh = &ren.meshes[id];
442	if (!mesh->used)
443		return;
444
445	free(mesh->idc);
446	free(mesh->vtc);
447	*mesh = (RendererMesh){0};
448}
449
450MGTexture renderer_backend_texture_load(const char* path)
451{
452	RendererTexture* texture;
453	int width;
454	int height;
455	int channels;
456	u32 id;
457
458	if (!path)
459		return MGTEXTURE_INVALID;
460
461	/* find free texture slot */
462	for (id = 1; id < MAX_TEXTURES; id++) {
463		if (!ren.textures[id].used)
464			break;
465	}
466
467	if (id == MAX_TEXTURES) {
468		mg_log(LOG_ERR, "Maximum texture limit reached");
469		return MGTEXTURE_INVALID;
470	}
471
472	texture = &ren.textures[id];
473	texture->pixels = stbi_load(path, &width, &height, &channels, 4);
474	if (!texture->pixels) {
475		mg_log(LOG_ERR, "Failed to load texture: %s", path);
476		return MGTEXTURE_INVALID;
477	}
478
479	texture->width = width;
480	texture->height = height;
481	texture->used = 1;
482
483	return id;
484}
485
486void renderer_backend_texture_destroy(MGTexture id)
487{
488	RendererTexture* texture;
489
490	if (id == MGTEXTURE_INVALID || id >= MAX_TEXTURES)
491		return;
492
493	texture = &ren.textures[id];
494	if (!texture->used)
495		return;
496
497	stbi_image_free(texture->pixels);
498	*texture = (RendererTexture){0};
499}