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