commit 9fd0111
shrub
·
2026-07-30 13:13:44 +0000 UTC
parent da175b4
add small3dlib renderer
9 files changed,
+4022,
-423
+6,
-2
1@@ -7,8 +7,13 @@
2
3 typedef SDL_Window MGWindow;
4
5-typedef struct { /* video backend !only OpenGL for now */
6+typedef struct {
7+#ifdef MG_RENDERER_SMALL3DLIB
8+ SDL_Renderer* renderer;
9+#endif
10+#ifdef MG_RENDERER_SOKOL
11 SDL_GLContext gl;
12+#endif
13 } MGVidBE;
14
15 typedef struct {
16@@ -41,4 +46,3 @@ void platform_shutdown(MGContext* ctx);
17 void platform_window_create(MGContext* ctx);
18
19 #endif /* PLATFORM_H */
20-
+39,
-20
1@@ -9,6 +9,8 @@ project(
2
3 cc = meson.get_compiler('c')
4 math_dep = cc.find_library('m', required: false)
5+sdl_dep = dependency('sdl2')
6+renderer_backend = get_option('renderer')
7
8 sources = [
9 'source/main.c',
10@@ -41,41 +43,57 @@ mgmath_dep = declare_dependency(
11 dependencies: math_dep,
12 )
13
14-opengl_dep = dependency('opengl', required: false)
15-if not opengl_dep.found() # fallback (at least for alpine)
16- opengl_dep = dependency('gl')
17-endif
18+renderer_c_args = []
19+renderer_sources = [
20+ 'source/renderer/renderer.c',
21+]
22+if renderer_backend == 'sokol'
23+ opengl_dep = dependency('opengl', required: false)
24+ if not opengl_dep.found() # fallback (at least for alpine)
25+ opengl_dep = dependency('gl')
26+ endif
27
28-sokol_gfx_lib = static_library(
29- 'sokol_gfx',
30- 'vendor/sokol_gfx.c',
31- include_directories: vendor_inc,
32- dependencies: opengl_dep,
33- override_options: [ 'c_std=c99' ],
34-)
35+ sokol_gfx_lib = static_library(
36+ 'sokol_gfx',
37+ 'vendor/sokol_gfx.c',
38+ include_directories: vendor_inc,
39+ dependencies: opengl_dep,
40+ override_options: [ 'c_std=c99' ],
41+ )
42
43-sokol_gfx_dep = declare_dependency(
44- include_directories: vendor_inc,
45- link_with: sokol_gfx_lib,
46- dependencies: opengl_dep,
47-)
48+ renderer_backend_dep = declare_dependency(
49+ include_directories: vendor_inc,
50+ link_with: sokol_gfx_lib,
51+ dependencies: [ opengl_dep, sdl_dep ],
52+ )
53+ renderer_sources += [ 'source/renderer/renderer_sokol.c' ]
54+ renderer_c_args += [ '-DMG_RENDERER_SOKOL' ]
55+else
56+ renderer_backend_dep = declare_dependency(
57+ include_directories: vendor_inc,
58+ dependencies: sdl_dep,
59+ )
60+ renderer_sources += [ 'source/renderer/renderer_small3dlib.c' ]
61+ renderer_c_args += [ '-DMG_RENDERER_SMALL3DLIB' ]
62+endif
63
64 renderer_lib = static_library(
65 'renderer',
66- 'source/renderer/renderer.c',
67+ renderer_sources,
68 include_directories: include_dirs,
69- dependencies: sokol_gfx_dep,
70+ dependencies: renderer_backend_dep,
71+ c_args: renderer_c_args,
72 override_options: [ 'c_std=c99' ],
73 )
74
75 renderer_dep = declare_dependency(
76 include_directories: include_dirs,
77 link_with: renderer_lib,
78- dependencies: sokol_gfx_dep,
79+ dependencies: renderer_backend_dep,
80 )
81
82 deps = [
83- dependency('sdl2'),
84+ sdl_dep,
85 mgmath_dep,
86 renderer_dep,
87 ]
88@@ -85,4 +103,5 @@ executable(
89 sources,
90 include_directories: include_dirs,
91 dependencies: deps,
92+ c_args: renderer_c_args,
93 )
+7,
-0
1@@ -0,0 +1,7 @@
2+option(
3+ 'renderer',
4+ type: 'combo',
5+ choices: [ 'sokol', 'small3dlib' ],
6+ value: 'sokol',
7+ description: '3D renderer backend',
8+)
+35,
-2
1@@ -5,7 +5,11 @@
2
3 void platform_get_drawable_size(MGContext* ctx, i32* width, i32* height)
4 {
5+#ifdef MG_RENDERER_SMALL3DLIB
6+ SDL_GetWindowSize(ctx->win, width, height);
7+#else
8 SDL_GL_GetDrawableSize(ctx->win, width, height);
9+#endif
10 }
11
12 void platform_init(MGContext* ctx)
13@@ -14,12 +18,14 @@ void platform_init(MGContext* ctx)
14 if (SDL_Init(SDL_INIT_VIDEO) != 0)
15 mg_die(MG_ERR_SDL_INIT, "SDL_Init failed: %s", SDL_GetError());
16
17+#ifdef MG_RENDERER_SOKOL
18 /* TODO: OpenGL specifis */
19 SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 4);
20 SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 1);
21 SDL_GL_SetAttribute( SDL_GL_CONTEXT_PROFILE_MASK, SDL_GL_CONTEXT_PROFILE_CORE);
22 SDL_GL_SetAttribute(SDL_GL_DOUBLEBUFFER, 1);
23 SDL_GL_SetAttribute(SDL_GL_DEPTH_SIZE, 24);
24+#endif
25 }
26
27 /* TODO: platform independant design MGEvent, MGPoll */
28@@ -44,7 +50,11 @@ i32 platform_poll(MGContext* ctx)
29
30 void platform_present(MGContext* ctx)
31 {
32+#ifdef MG_RENDERER_SMALL3DLIB
33+ SDL_RenderPresent(ctx->vbe.renderer);
34+#else
35 SDL_GL_SwapWindow(ctx->win);
36+#endif
37 }
38
39 void platform_shutdown(MGContext* ctx)
40@@ -52,10 +62,19 @@ void platform_shutdown(MGContext* ctx)
41 if (!ctx)
42 return;
43
44+#ifdef MG_RENDERER_SMALL3DLIB
45+ if (ctx->vbe.renderer) {
46+ SDL_DestroyRenderer(ctx->vbe.renderer);
47+ ctx->vbe.renderer = NULL;
48+ }
49+#endif
50+
51+#ifdef MG_RENDERER_SOKOL
52 if (ctx->vbe.gl) {
53 SDL_GL_DeleteContext(ctx->vbe.gl);
54 ctx->vbe.gl = NULL;
55 }
56+#endif
57
58 if (ctx->win) {
59 SDL_DestroyWindow(ctx->win);
60@@ -67,14 +86,28 @@ void platform_shutdown(MGContext* ctx)
61
62 void platform_window_create(MGContext* ctx)
63 {
64+ u32 flags = SDL_WINDOW_RESIZABLE|SDL_WINDOW_ALLOW_HIGHDPI;
65+#ifdef MG_RENDERER_SOKOL
66+ flags |= SDL_WINDOW_OPENGL;
67+#endif
68+
69 ctx->win = SDL_CreateWindow(
70 ctx->title, ctx->x, ctx->y, ctx->width, ctx->height,
71- SDL_WINDOW_RESIZABLE|SDL_WINDOW_ALLOW_HIGHDPI|SDL_WINDOW_OPENGL
72+ flags
73 );
74
75 if (!ctx->win)
76 mg_die(MG_ERR_SDL_CREATE_WIN, "Failed to create window: %s", SDL_GetError());
77
78+#ifdef MG_RENDERER_SMALL3DLIB
79+ ctx->vbe.renderer = SDL_CreateRenderer(ctx->win, -1, SDL_RENDERER_ACCELERATED);
80+ if (!ctx->vbe.renderer)
81+ ctx->vbe.renderer = SDL_CreateRenderer(ctx->win, -1, SDL_RENDERER_SOFTWARE);
82+ if (!ctx->vbe.renderer)
83+ mg_die(MG_ERR_REN_CREATE, "Failed to create SDL renderer: %s", SDL_GetError());
84+#endif
85+
86+#ifdef MG_RENDERER_SOKOL
87 ctx->vbe.gl = SDL_GL_CreateContext(ctx->win);
88 if (!ctx->vbe.gl)
89 mg_die(MG_ERR_GL_CTX_CREATE, "Failed to initialise GL Context");
90@@ -83,5 +116,5 @@ void platform_window_create(MGContext* ctx)
91 mg_die(MG_ERR_GL_CTX_ACTIVATE, "Failed to activate GL Context: %s", SDL_GetError());
92
93 SDL_GL_SetSwapInterval(1);
94+#endif
95 }
96-
+12,
-399
1@@ -1,434 +1,47 @@
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 (512) /* TODO; random value ig */
11-#define MAX_TEXTURES (512) /* TODO; random value ig */
12-
13-typedef struct {
14- sg_buffer vb; /* vertex buffer */
15- sg_buffer ib; /* index buffer */
16- u32 ic; /* index count */
17- i8 used;
18-} RendererMesh;
19-
20-typedef struct {
21- sg_image image;
22- sg_view view;
23- i8 used;
24-} RendererTexture;
25-
26-typedef 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 */
40-static void create_pipelines(void);
41-
42-/* create texture sampler */
43-static void create_sampler(void);
44-
45-/* logging function for passing to sokol */
46-static void sokol_log(const char* tag, u32 level, u32 item, const char* msg, u32 ln, const char* file, void* user);
47-
48-static RendererState ren;
49-/* TODO: textures */
50-static 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";
67-static 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-
81-static 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-
130-static 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-
145-static 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+#include "renderer_backend.h"
162
163 void renderer_create(MGContext* ctx)
164 {
165- sg_desc desc = {0};
166-
167- ren.ctx = ctx;
168- ren.pass = 0;
169-
170- desc.environment.defaults.color_format = SG_PIXELFORMAT_RGBA8;
171- desc.environment.defaults.depth_format = SG_PIXELFORMAT_DEPTH;
172- desc.environment.defaults.sample_count = 1;
173- desc.logger.func = sokol_log;
174-
175- sg_setup(&desc);
176-
177- if (!sg_isvalid())
178- mg_die(MG_ERR_REN_CREATE, "Failed to initialise renderer");
179-
180- create_pipelines();
181- create_sampler();
182+ renderer_backend_create(ctx);
183 }
184
185 void renderer_begin(void)
186 {
187- sg_pass pass = {0};
188- i32 width;
189- i32 height;
190-
191- ren.pass = 0;
192-
193- platform_get_drawable_size(ren.ctx, &width, &height);
194- /* (minimized window?) no need to draw */
195- if (width <= 0 || height <= 0)
196- return;
197-
198- pass.action.colors[0].load_action = SG_LOADACTION_CLEAR;
199- pass.action.colors[0].clear_value.r = 0.90f;
200- pass.action.colors[0].clear_value.g = 0.80f;
201- pass.action.colors[0].clear_value.b = 0.70f;
202- pass.action.colors[0].clear_value.a = 1.00f;
203-
204- pass.action.depth.load_action = SG_LOADACTION_CLEAR;
205- pass.action.depth.clear_value = 1.0f;
206-
207- /* swapchain descriptor */
208- pass.swapchain.width = width;
209- pass.swapchain.height = height;
210- pass.swapchain.sample_count = 1;
211- pass.swapchain.color_format = SG_PIXELFORMAT_RGBA8;
212- pass.swapchain.depth_format = SG_PIXELFORMAT_DEPTH;
213-
214- pass.swapchain.gl.framebuffer = 0;
215-
216- sg_begin_pass(&pass);
217- ren.pass = 1;
218+ renderer_backend_begin();
219 }
220
221 void renderer_end(void)
222 {
223- if (!ren.pass)
224- return;
225-
226- sg_end_pass();
227- sg_commit();
228-
229- ren.pass = 0;
230+ renderer_backend_end();
231 }
232
233 void renderer_destroy(void)
234 {
235- u32 id;
236-
237- /* the values could be different in future so
238- probably should keep them separate */
239- for (id = 1; id < MAX_MESHES; id++)
240- renderer_mesh_destroy(id);
241- for (id = 1; id < MAX_TEXTURES; id++)
242- renderer_texture_destroy(id);
243-
244- sg_destroy_sampler(ren.sampler);
245- sg_destroy_pipeline(ren.pipeline);
246- sg_destroy_shader(ren.shader);
247-
248- if (sg_isvalid())
249- sg_shutdown();
250-
251- ren.ctx = NULL;
252- ren.pass = 0;
253+ renderer_backend_destroy();
254 }
255
256 MGMesh renderer_mesh_create(const MGMeshDesc* desc)
257 {
258- sg_buffer_desc buffer_desc = {0};
259- RendererMesh* mesh;
260- u32 id;
261-
262- if (!desc || !desc->vtc || !desc->idc || desc->vtcc == 0 || desc->idcc == 0)
263- return MGMESH_INVALID;
264-
265- /* find free mesh slot */
266- for (id = 1; id < MAX_MESHES; id++) {
267- if (!ren.meshes[id].used)
268- break;
269- }
270-
271- if (id == MAX_MESHES) {
272- mg_log(LOG_ERR, "Maximum mesh limit reached");
273- return MGMESH_INVALID;
274- }
275-
276- /* vertex buffer */
277- mesh = &ren.meshes[id];
278- buffer_desc.data.ptr = desc->vtc;
279- buffer_desc.data.size = desc->vtcc * sizeof(MGVertex);
280- mesh->vb = sg_make_buffer(&buffer_desc);
281-
282- /* index buffer */
283- buffer_desc = (sg_buffer_desc){0};
284- buffer_desc.usage.vertex_buffer = false;
285- buffer_desc.usage.index_buffer = true;
286- buffer_desc.usage.immutable = true;
287- buffer_desc.data.ptr = desc->idc;
288- buffer_desc.data.size = desc->idcc * sizeof(u16);
289- mesh->ib = sg_make_buffer(&buffer_desc);
290-
291- i8 valid = sg_query_buffer_state(mesh->vb) == SG_RESOURCESTATE_VALID &&
292- sg_query_buffer_state(mesh->ib) == SG_RESOURCESTATE_VALID;
293- if (!valid) {
294- if (sg_query_buffer_state(mesh->vb) == SG_RESOURCESTATE_VALID)
295- sg_destroy_buffer(mesh->vb);
296- if (sg_query_buffer_state(mesh->ib) == SG_RESOURCESTATE_VALID)
297- sg_destroy_buffer(mesh->ib);
298-
299- *mesh = (RendererMesh){0};
300- mg_log(LOG_ERR, "Failed to create mesh buffers");
301- return MGMESH_INVALID;
302- }
303-
304- mesh->ic = desc->idcc;
305- mesh->used = 1;
306- return id;
307+ return renderer_backend_mesh_create(desc);
308 }
309
310 void renderer_mesh_draw(MGMesh mesh, MGTexture texture, const MGMat4* mvp)
311 {
312- RendererMesh* rmesh;
313- RendererTexture* rtexture;
314- sg_bindings bindings = {0};
315- sg_range uniforms;
316-
317- if (!ren.pass || !mvp)
318- return;
319-
320- if (mesh == MGMESH_INVALID || mesh >= MAX_MESHES)
321- return;
322-
323- if (texture == MGTEXTURE_INVALID || texture >= MAX_TEXTURES)
324- return;
325-
326- rmesh = &ren.meshes[mesh];
327- rtexture = &ren.textures[texture];
328- if (!rmesh->used || !rtexture->used)
329- return;
330-
331- bindings.vertex_buffers[0] = rmesh->vb;
332- bindings.index_buffer = rmesh->ib;
333- bindings.views[0] = rtexture->view;
334- bindings.samplers[0] = ren.sampler;
335-
336- uniforms.ptr = &mvp->Elements[0][0];
337- uniforms.size = sizeof(float) * 16;
338-
339- sg_apply_pipeline(ren.pipeline);
340- sg_apply_bindings(&bindings);
341- sg_apply_uniforms(0, &uniforms);
342-
343- sg_draw(0, rmesh->ic, 1);
344+ renderer_backend_mesh_draw(mesh, texture, mvp);
345 }
346
347-void renderer_mesh_destroy(MGMesh id)
348+void renderer_mesh_destroy(MGMesh mesh)
349 {
350- RendererMesh* mesh;
351-
352- if (id == MGMESH_INVALID || id >= MAX_MESHES)
353- return;
354-
355- mesh = &ren.meshes[id];
356- if (!mesh->used)
357- return;
358-
359- sg_destroy_buffer(mesh->ib);
360- sg_destroy_buffer(mesh->vb);
361-
362- *mesh = (RendererMesh){0};
363+ renderer_backend_mesh_destroy(mesh);
364 }
365
366 MGTexture renderer_texture_load(const char* path)
367 {
368- sg_image_desc image_desc = {0};
369- sg_view_desc view_desc = {0};
370- RendererTexture* texture;
371- stbi_uc* pixels;
372- int width;
373- int height;
374- int channels;
375- u32 id;
376-
377- if (!path)
378- return MGTEXTURE_INVALID;
379-
380- /* find free texture slot */
381- for (id = 1; id < MAX_TEXTURES; id++) {
382- if (!ren.textures[id].used)
383- break;
384- }
385-
386- if (id == MAX_TEXTURES) {
387- mg_log(LOG_ERR, "Maximum texture limit reached");
388- return MGTEXTURE_INVALID;
389- }
390-
391- texture = &ren.textures[id];
392- pixels = stbi_load(path, &width, &height, &channels, 4);
393- if (!pixels) {
394- mg_log(LOG_ERR, "Failed to load texture: %s", path);
395- return MGTEXTURE_INVALID;
396- }
397-
398- image_desc.width = width;
399- image_desc.height = height;
400- image_desc.pixel_format = SG_PIXELFORMAT_RGBA8;
401- image_desc.data.mip_levels[0].ptr = pixels;
402- image_desc.data.mip_levels[0].size =width * height * 4;
403-
404- texture->image = sg_make_image(&image_desc);
405- stbi_image_free(pixels);
406- if (sg_query_image_state(texture->image) != SG_RESOURCESTATE_VALID) {
407- *texture = (RendererTexture){0};
408- mg_log(LOG_ERR, "Failed to create texture: %s", path);
409- return MGTEXTURE_INVALID;
410- }
411-
412- view_desc.texture.image = texture->image;
413- texture->view = sg_make_view(&view_desc);
414-
415- if (sg_query_view_state(texture->view) != SG_RESOURCESTATE_VALID) {
416- sg_destroy_image(texture->image);
417- *texture = (RendererTexture){0};
418- mg_log(LOG_ERR, "Failed to create texture view: %s", path);
419- return MGTEXTURE_INVALID;
420- }
421-
422- texture->used = 1;
423-
424- return id;
425+ return renderer_backend_texture_load(path);
426 }
427
428-void renderer_texture_destroy(MGTexture id)
429+void renderer_texture_destroy(MGTexture texture)
430 {
431- RendererTexture* texture;
432-
433- if (id == MGTEXTURE_INVALID || id >= MAX_TEXTURES)
434- return;
435-
436- texture = &ren.textures[id];
437-
438- if (!texture->used)
439- return;
440-
441- sg_destroy_view(texture->view);
442- sg_destroy_image(texture->image);
443-
444- *texture = (RendererTexture){0};
445+ renderer_backend_texture_destroy(texture);
446 }
447-
+18,
-0
1@@ -0,0 +1,18 @@
2+#ifndef RENDERER_BACKEND_H
3+#define RENDERER_BACKEND_H
4+
5+#include "renderer/renderer.h"
6+
7+void renderer_backend_create(MGContext* ctx);
8+void renderer_backend_begin(void);
9+void renderer_backend_end(void);
10+void renderer_backend_destroy(void);
11+
12+MGMesh renderer_backend_mesh_create(const MGMeshDesc* desc);
13+void renderer_backend_mesh_draw(MGMesh mesh, MGTexture texture, const MGMat4* mvp);
14+void renderer_backend_mesh_destroy(MGMesh mesh);
15+
16+MGTexture renderer_backend_texture_load(const char* path);
17+void renderer_backend_texture_destroy(MGTexture texture);
18+
19+#endif /* RENDERER_BACKEND_H */
+473,
-0
1@@ -0,0 +1,473 @@
2+#include <SDL2/SDL.h>
3+#include <stdlib.h>
4+#include <string.h>
5+#include <stb_image_c89.h>
6+
7+#include "util.h"
8+#include "types.h"
9+#include "platform/platform.h"
10+#include "renderer/renderer.h"
11+#include "renderer_backend.h"
12+
13+#define MAX_MESHES (512)
14+#define MAX_TEXTURES (512)
15+#define S3L_MAX_WIDTH (4096)
16+#define S3L_MAX_HEIGHT (4096)
17+
18+#define S3L_MAX_PIXELS (S3L_MAX_WIDTH * S3L_MAX_HEIGHT)
19+#define S3L_Z_BUFFER 1
20+#define S3L_PIXEL_FUNCTION small3dlib_pixel
21+#include <small3dlib.h>
22+
23+typedef struct {
24+ MGVertex* vtc; /* vertices */
25+ u16* idc; /* indices */
26+ u32 ic; /* index count */
27+ i8 used;
28+} RendererMesh;
29+
30+typedef struct {
31+ u8* pixels;
32+ i32 width;
33+ i32 height;
34+ i8 used;
35+} RendererTexture;
36+
37+typedef struct {
38+ MGContext* ctx;
39+ i8 pass; /* basically a bool; in
40+ render pass or not */
41+
42+ SDL_Texture* screen_texture;
43+ u32* framebuffer;
44+ i32 width;
45+ i32 height;
46+
47+ RendererMesh meshes[MAX_MESHES];
48+ RendererTexture textures[MAX_TEXTURES];
49+
50+ RendererMesh* active_mesh;
51+ RendererTexture* active_texture;
52+} RendererState;
53+
54+typedef struct {
55+ float x;
56+ float y;
57+ float z;
58+ float w;
59+} ClipVertex;
60+
61+static RendererState ren;
62+
63+/* create or resize the SDL texture used to present the software framebuffer */
64+static void create_screen_texture(i32 width, i32 height);
65+
66+/* destroy the SDL texture and software framebuffer */
67+static void destroy_screen_texture(void);
68+
69+static ClipVertex transform_vertex(const MGMat4* mvp, const MGVertex* v);
70+static i8 triangle_outside(const ClipVertex* a, const ClipVertex* b, const ClipVertex* c);
71+static i8 triangle_front_facing(const ClipVertex* a, const ClipVertex* b, const ClipVertex* c);
72+static S3L_Vec4 clip_to_screen(const ClipVertex* v);
73+static u32 sample_texture(const RendererMesh* mesh, const RendererTexture* texture, const S3L_PixelInfo* pixel);
74+static u32 rgba_to_sdl(u32 rgba);
75+
76+static inline void small3dlib_pixel(S3L_PixelInfo* pixel)
77+{
78+ u32 index;
79+
80+ if (!ren.active_mesh || !ren.active_texture || !ren.framebuffer)
81+ return;
82+
83+ index = (u32)pixel->y * (u32)ren.width + (u32)pixel->x;
84+ ren.framebuffer[index] = rgba_to_sdl(sample_texture(ren.active_mesh, ren.active_texture, pixel));
85+}
86+
87+static void create_screen_texture(i32 width, i32 height)
88+{
89+ u64 pixel_count;
90+ u32* framebuffer;
91+
92+ if (width == ren.width && height == ren.height && ren.framebuffer && ren.screen_texture)
93+ return;
94+
95+ if (width <= 0 || height <= 0)
96+ return;
97+
98+ if (width > S3L_MAX_WIDTH || height > S3L_MAX_HEIGHT)
99+ mg_die(MG_ERR_REN_CREATE, "small3dlib framebuffer exceeds %dx%d", S3L_MAX_WIDTH, S3L_MAX_HEIGHT);
100+
101+ pixel_count = (u64)width * (u64)height;
102+ framebuffer = malloc(pixel_count * sizeof(*framebuffer));
103+ if (!framebuffer)
104+ mg_die(MG_ERR_REN_CREATE, "Failed to allocate small3dlib framebuffer");
105+
106+ if (ren.screen_texture)
107+ SDL_DestroyTexture(ren.screen_texture);
108+
109+ ren.screen_texture = SDL_CreateTexture(
110+ ren.ctx->vbe.renderer,
111+ SDL_PIXELFORMAT_RGBA8888,
112+ SDL_TEXTUREACCESS_STREAMING,
113+ width,
114+ height
115+ );
116+
117+ if (!ren.screen_texture)
118+ mg_die(MG_ERR_REN_CREATE, "Failed to create small3dlib texture: %s", SDL_GetError());
119+
120+ free(ren.framebuffer);
121+ ren.framebuffer = framebuffer;
122+ ren.width = width;
123+ ren.height = height;
124+
125+ S3L_resolutionX = (uint16_t)width;
126+ S3L_resolutionY = (uint16_t)height;
127+}
128+
129+static void destroy_screen_texture(void)
130+{
131+ if (ren.screen_texture) {
132+ SDL_DestroyTexture(ren.screen_texture);
133+ ren.screen_texture = NULL;
134+ }
135+
136+ free(ren.framebuffer);
137+ ren.framebuffer = NULL;
138+ ren.width = 0;
139+ ren.height = 0;
140+}
141+
142+static ClipVertex transform_vertex(const MGMat4* mvp, const MGVertex* v)
143+{
144+ ClipVertex r;
145+
146+ r.x = mvp->Elements[0][0] * v->x + mvp->Elements[1][0] * v->y +
147+ mvp->Elements[2][0] * v->z + mvp->Elements[3][0];
148+ r.y = mvp->Elements[0][1] * v->x + mvp->Elements[1][1] * v->y +
149+ mvp->Elements[2][1] * v->z + mvp->Elements[3][1];
150+ r.z = mvp->Elements[0][2] * v->x + mvp->Elements[1][2] * v->y +
151+ mvp->Elements[2][2] * v->z + mvp->Elements[3][2];
152+ r.w = mvp->Elements[0][3] * v->x + mvp->Elements[1][3] * v->y +
153+ mvp->Elements[2][3] * v->z + mvp->Elements[3][3];
154+
155+ return r;
156+}
157+
158+static i8 triangle_outside(const ClipVertex* a, const ClipVertex* b, const ClipVertex* c)
159+{
160+ if (a->w <= 0.0001f || b->w <= 0.0001f || c->w <= 0.0001f)
161+ return 1;
162+
163+ if (a->x < -a->w && b->x < -b->w && c->x < -c->w)
164+ return 1;
165+ if (a->x > a->w && b->x > b->w && c->x > c->w)
166+ return 1;
167+ if (a->y < -a->w && b->y < -b->w && c->y < -c->w)
168+ return 1;
169+ if (a->y > a->w && b->y > b->w && c->y > c->w)
170+ return 1;
171+ if (a->z < -a->w || b->z < -b->w || c->z < -c->w)
172+ return 1;
173+ if (a->z > a->w && b->z > b->w && c->z > c->w)
174+ return 1;
175+
176+ return 0;
177+}
178+
179+static i8 triangle_front_facing(const ClipVertex* a, const ClipVertex* b, const ClipVertex* c)
180+{
181+ float ax = a->x / a->w;
182+ float ay = a->y / a->w;
183+ float bx = b->x / b->w;
184+ float by = b->y / b->w;
185+ float cx = c->x / c->w;
186+ float cy = c->y / c->w;
187+ float area = (bx - ax) * (cy - ay) - (by - ay) * (cx - ax);
188+
189+ return area > 0.0f;
190+}
191+
192+static S3L_Vec4 clip_to_screen(const ClipVertex* v)
193+{
194+ S3L_Vec4 r;
195+ float inv_w = 1.0f / v->w;
196+ float ndc_x = v->x * inv_w;
197+ float ndc_y = v->y * inv_w;
198+ float ndc_z = v->z * inv_w;
199+
200+ r.x = (S3L_Unit)((ndc_x * 0.5f + 0.5f) * (float)(ren.width - 1));
201+ r.y = (S3L_Unit)((0.5f - ndc_y * 0.5f) * (float)(ren.height - 1));
202+ r.z = (S3L_Unit)((ndc_z * 0.5f + 0.5f) * 1048576.0f) + 1;
203+ r.w = S3L_F;
204+
205+ return r;
206+}
207+
208+static u32 sample_texture(const RendererMesh* mesh, const RendererTexture* texture, const S3L_PixelInfo* pixel)
209+{
210+ u32 tri = (u32)pixel->triangleIndex * 3;
211+ const MGVertex* a = &mesh->vtc[mesh->idc[tri + 0]];
212+ const MGVertex* b = &mesh->vtc[mesh->idc[tri + 1]];
213+ const MGVertex* c = &mesh->vtc[mesh->idc[tri + 2]];
214+ float ba = (float)pixel->barycentric[0] / (float)S3L_F;
215+ float bb = (float)pixel->barycentric[1] / (float)S3L_F;
216+ float bc = (float)pixel->barycentric[2] / (float)S3L_F;
217+ float u = a->u * ba + b->u * bb + c->u * bc;
218+ float v = a->v * ba + b->v * bb + c->v * bc;
219+ i32 x;
220+ i32 y;
221+ u8* texel;
222+
223+ if (!texture || !texture->pixels)
224+ return 0xffffffff;
225+
226+ while (u < 0.0f)
227+ u += 1.0f;
228+ while (v < 0.0f)
229+ v += 1.0f;
230+ while (u >= 1.0f)
231+ u -= 1.0f;
232+ while (v >= 1.0f)
233+ v -= 1.0f;
234+
235+ x = (i32)(u * (float)texture->width);
236+ y = (i32)(v * (float)texture->height);
237+
238+ texel = texture->pixels + ((y * texture->width + x) * 4);
239+ return ((u32)texel[0]) |
240+ ((u32)texel[1] << 8) |
241+ ((u32)texel[2] << 16) |
242+ ((u32)texel[3] << 24);
243+}
244+
245+static u32 rgba_to_sdl(u32 rgba)
246+{
247+ u32 r = (rgba >> 0) & 0xff;
248+ u32 g = (rgba >> 8) & 0xff;
249+ u32 b = (rgba >> 16) & 0xff;
250+ u32 a = (rgba >> 24) & 0xff;
251+
252+ return (r << 24) | (g << 16) | (b << 8) | a;
253+}
254+
255+void renderer_backend_create(MGContext* ctx)
256+{
257+ ren.ctx = ctx;
258+ ren.pass = 0;
259+ ren.active_mesh = NULL;
260+ ren.active_texture = NULL;
261+}
262+
263+void renderer_backend_begin(void)
264+{
265+ i32 width;
266+ i32 height;
267+ u64 i;
268+ u64 pixel_count;
269+ u32 clear_colour = rgba_to_sdl(0xffb3cce6);
270+
271+ ren.pass = 0;
272+ platform_get_drawable_size(ren.ctx, &width, &height);
273+ if (width <= 0 || height <= 0)
274+ return;
275+
276+ create_screen_texture(width, height);
277+
278+ pixel_count = (u64)ren.width * (u64)ren.height;
279+ for (i = 0; i < pixel_count; i++)
280+ ren.framebuffer[i] = clear_colour;
281+
282+ S3L_newFrame();
283+ ren.pass = 1;
284+}
285+
286+void renderer_backend_end(void)
287+{
288+ if (!ren.pass)
289+ return;
290+
291+ SDL_UpdateTexture(ren.screen_texture, NULL, ren.framebuffer, ren.width * (i32)sizeof(*ren.framebuffer));
292+ SDL_RenderClear(ren.ctx->vbe.renderer);
293+ SDL_RenderCopy(ren.ctx->vbe.renderer, ren.screen_texture, NULL, NULL);
294+ ren.pass = 0;
295+}
296+
297+void renderer_backend_destroy(void)
298+{
299+ u32 id;
300+
301+ /* the values could be different in future so
302+ probably should keep them separate */
303+ for (id = 1; id < MAX_MESHES; id++)
304+ renderer_backend_mesh_destroy(id);
305+ for (id = 1; id < MAX_TEXTURES; id++)
306+ renderer_backend_texture_destroy(id);
307+
308+ destroy_screen_texture();
309+
310+ ren.ctx = NULL;
311+ ren.active_mesh = NULL;
312+ ren.active_texture = NULL;
313+ ren.pass = 0;
314+}
315+
316+MGMesh renderer_backend_mesh_create(const MGMeshDesc* desc)
317+{
318+ RendererMesh* mesh;
319+ u32 id;
320+
321+ if (!desc || !desc->vtc || !desc->idc || desc->vtcc == 0 || desc->idcc == 0)
322+ return MGMESH_INVALID;
323+
324+ if (desc->idcc % 3 != 0)
325+ return MGMESH_INVALID;
326+
327+ /* find free mesh slot */
328+ for (id = 1; id < MAX_MESHES; id++) {
329+ if (!ren.meshes[id].used)
330+ break;
331+ }
332+
333+ if (id == MAX_MESHES) {
334+ mg_log(LOG_ERR, "Maximum mesh limit reached");
335+ return MGMESH_INVALID;
336+ }
337+
338+ /* copy vertex and index data */
339+ mesh = &ren.meshes[id];
340+ mesh->vtc = malloc(desc->vtcc * sizeof(*mesh->vtc));
341+ mesh->idc = malloc(desc->idcc * sizeof(*mesh->idc));
342+ if (!mesh->vtc || !mesh->idc) {
343+ free(mesh->vtc);
344+ free(mesh->idc);
345+ *mesh = (RendererMesh){0};
346+ mg_log(LOG_ERR, "Failed to allocate mesh data");
347+ return MGMESH_INVALID;
348+ }
349+
350+ memcpy(mesh->vtc, desc->vtc, desc->vtcc * sizeof(*mesh->vtc));
351+ memcpy(mesh->idc, desc->idc, desc->idcc * sizeof(*mesh->idc));
352+ mesh->ic = desc->idcc;
353+ mesh->used = 1;
354+
355+ return id;
356+}
357+
358+void renderer_backend_mesh_draw(MGMesh mesh, MGTexture texture, const MGMat4* mvp)
359+{
360+ RendererMesh* rmesh;
361+ RendererTexture* rtexture;
362+ u32 tri;
363+
364+ if (!ren.pass || !mvp)
365+ return;
366+
367+ if (mesh == MGMESH_INVALID || mesh >= MAX_MESHES)
368+ return;
369+
370+ if (texture == MGTEXTURE_INVALID || texture >= MAX_TEXTURES)
371+ return;
372+
373+ rmesh = &ren.meshes[mesh];
374+ rtexture = &ren.textures[texture];
375+ if (!rmesh->used || !rtexture->used)
376+ return;
377+
378+ ren.active_mesh = rmesh;
379+ ren.active_texture = rtexture;
380+
381+ for (tri = 0; tri < rmesh->ic; tri += 3) {
382+ ClipVertex ca;
383+ ClipVertex cb;
384+ ClipVertex cc;
385+ S3L_Vec4 sa;
386+ S3L_Vec4 sb;
387+ S3L_Vec4 sc;
388+
389+ ca = transform_vertex(mvp, &rmesh->vtc[rmesh->idc[tri + 0]]);
390+ cb = transform_vertex(mvp, &rmesh->vtc[rmesh->idc[tri + 1]]);
391+ cc = transform_vertex(mvp, &rmesh->vtc[rmesh->idc[tri + 2]]);
392+
393+ if (triangle_outside(&ca, &cb, &cc))
394+ continue;
395+ if (!triangle_front_facing(&ca, &cb, &cc))
396+ continue;
397+
398+ sa = clip_to_screen(&ca);
399+ sb = clip_to_screen(&cb);
400+ sc = clip_to_screen(&cc);
401+
402+ S3L_drawTriangle(sa, sb, sc, 0, (S3L_Index)(tri / 3));
403+ }
404+
405+ ren.active_mesh = NULL;
406+ ren.active_texture = NULL;
407+}
408+
409+void renderer_backend_mesh_destroy(MGMesh id)
410+{
411+ RendererMesh* mesh;
412+
413+ if (id == MGMESH_INVALID || id >= MAX_MESHES)
414+ return;
415+
416+ mesh = &ren.meshes[id];
417+ if (!mesh->used)
418+ return;
419+
420+ free(mesh->idc);
421+ free(mesh->vtc);
422+ *mesh = (RendererMesh){0};
423+}
424+
425+MGTexture renderer_backend_texture_load(const char* path)
426+{
427+ RendererTexture* texture;
428+ int width;
429+ int height;
430+ int channels;
431+ u32 id;
432+
433+ if (!path)
434+ return MGTEXTURE_INVALID;
435+
436+ /* find free texture slot */
437+ for (id = 1; id < MAX_TEXTURES; id++) {
438+ if (!ren.textures[id].used)
439+ break;
440+ }
441+
442+ if (id == MAX_TEXTURES) {
443+ mg_log(LOG_ERR, "Maximum texture limit reached");
444+ return MGTEXTURE_INVALID;
445+ }
446+
447+ texture = &ren.textures[id];
448+ texture->pixels = stbi_load(path, &width, &height, &channels, 4);
449+ if (!texture->pixels) {
450+ mg_log(LOG_ERR, "Failed to load texture: %s", path);
451+ return MGTEXTURE_INVALID;
452+ }
453+
454+ texture->width = width;
455+ texture->height = height;
456+ texture->used = 1;
457+
458+ return id;
459+}
460+
461+void renderer_backend_texture_destroy(MGTexture id)
462+{
463+ RendererTexture* texture;
464+
465+ if (id == MGTEXTURE_INVALID || id >= MAX_TEXTURES)
466+ return;
467+
468+ texture = &ren.textures[id];
469+ if (!texture->used)
470+ return;
471+
472+ stbi_image_free(texture->pixels);
473+ *texture = (RendererTexture){0};
474+}
+434,
-0
1@@ -0,0 +1,434 @@
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+#include "renderer_backend.h"
10+
11+#define MAX_MESHES (512) /* TODO; random value ig */
12+#define MAX_TEXTURES (512) /* TODO; random value ig */
13+
14+typedef struct {
15+ sg_buffer vb; /* vertex buffer */
16+ sg_buffer ib; /* index buffer */
17+ u32 ic; /* index count */
18+ i8 used;
19+} RendererMesh;
20+
21+typedef struct {
22+ sg_image image;
23+ sg_view view;
24+ i8 used;
25+} RendererTexture;
26+
27+typedef struct {
28+ MGContext* ctx;
29+ i8 pass; /* basically a bool; in
30+ render pass or not */
31+
32+ sg_shader shader;
33+ sg_pipeline pipeline;
34+ sg_sampler sampler;
35+
36+ RendererMesh meshes[MAX_MESHES];
37+ RendererTexture textures[MAX_MESHES];
38+} RendererState;
39+
40+/* create the shader and render pipeline */
41+static void create_pipelines(void);
42+
43+/* create texture sampler */
44+static void create_sampler(void);
45+
46+/* logging function for passing to sokol */
47+static void sokol_log(const char* tag, u32 level, u32 item, const char* msg, u32 ln, const char* file, void* user);
48+
49+static RendererState ren;
50+/* TODO: textures */
51+static const char* vsdr =
52+"#version 410\n"
53+"layout(location=0) in vec3 position;\n"
54+"layout(location=1) in vec2 texcoord0;\n"
55+"layout(location=2) in vec4 color0;\n"
56+"\n"
57+"uniform mat4 mvp;\n"
58+"\n"
59+"out vec2 uv;\n"
60+"out vec4 colour;\n"
61+"\n"
62+"void main(void)\n"
63+"{\n"
64+" gl_Position = mvp * vec4(position, 1.0);\n"
65+" uv = texcoord0;\n"
66+" colour = color0;\n"
67+"}\n";
68+static const char* fsdr =
69+"#version 410\n"
70+"in vec2 uv;\n"
71+"in vec4 colour;\n"
72+"\n"
73+"uniform sampler2D tex;\n"
74+"\n"
75+"out vec4 frag_colour;\n"
76+"\n"
77+"void main(void)\n"
78+"{\n"
79+" frag_colour = texture(tex, uv);\n"
80+"}\n";
81+
82+static void create_pipelines(void)
83+{
84+ sg_shader_desc shader_desc = {0};
85+ sg_pipeline_desc pipeline_desc = {0};
86+
87+ /* shader descriptor */
88+ shader_desc.vertex_func.source = vsdr;
89+ shader_desc.fragment_func.source = fsdr;
90+ shader_desc.attrs[0].glsl_name = "position";
91+ shader_desc.attrs[1].glsl_name = "texcoord0";
92+ shader_desc.attrs[2].glsl_name = "color0";
93+ shader_desc.uniform_blocks[0].stage = SG_SHADERSTAGE_VERTEX;
94+ shader_desc.uniform_blocks[0].size = sizeof(float) * 16; /* size of mvp matrix */
95+ shader_desc.uniform_blocks[0].layout = SG_UNIFORMLAYOUT_STD140;
96+ shader_desc.uniform_blocks[0] .glsl_uniforms[0].glsl_name = "mvp";
97+ shader_desc.uniform_blocks[0] .glsl_uniforms[0].type = SG_UNIFORMTYPE_MAT4;
98+
99+ /* textures */
100+ shader_desc.views[0].texture.stage = SG_SHADERSTAGE_FRAGMENT;
101+ shader_desc.views[0].texture.image_type = SG_IMAGETYPE_2D;
102+ shader_desc.views[0].texture.sample_type = SG_IMAGESAMPLETYPE_FLOAT;
103+ shader_desc.samplers[0].stage = SG_SHADERSTAGE_FRAGMENT;
104+ shader_desc.samplers[0].sampler_type = SG_SAMPLERTYPE_FILTERING;
105+ shader_desc.texture_sampler_pairs[0].stage = SG_SHADERSTAGE_FRAGMENT;
106+ shader_desc.texture_sampler_pairs[0].view_slot = 0;
107+ shader_desc.texture_sampler_pairs[0].sampler_slot = 0;
108+ shader_desc.texture_sampler_pairs[0].glsl_name = "tex";
109+
110+ ren.shader = sg_make_shader(&shader_desc);
111+ if (sg_query_shader_state(ren.shader) != SG_RESOURCESTATE_VALID)
112+ mg_die(MG_ERR_SHADER_CREATE, "Failed to create shader");
113+
114+ /* pipeline descriptor */
115+ pipeline_desc.shader = ren.shader;
116+ pipeline_desc.layout.buffers[0].stride = sizeof(MGVertex);
117+ pipeline_desc.layout.attrs[0].format = SG_VERTEXFORMAT_FLOAT3;
118+ pipeline_desc.layout.attrs[1].format = SG_VERTEXFORMAT_FLOAT2;
119+ pipeline_desc.layout.attrs[2].format = SG_VERTEXFORMAT_UBYTE4N;
120+ pipeline_desc.index_type = SG_INDEXTYPE_UINT16;
121+ pipeline_desc.depth.compare = SG_COMPAREFUNC_LESS_EQUAL;
122+ pipeline_desc.depth.write_enabled = true;
123+ pipeline_desc.cull_mode = SG_CULLMODE_BACK;
124+ pipeline_desc.face_winding = SG_FACEWINDING_CCW;
125+
126+ ren.pipeline = sg_make_pipeline(&pipeline_desc);
127+ if (sg_query_pipeline_state(ren.pipeline) != SG_RESOURCESTATE_VALID)
128+ mg_die(MG_ERR_PIPELINE_CREATE, "Failed to create pipeline");
129+}
130+
131+static void create_sampler(void)
132+{
133+ sg_sampler_desc desc = {0};
134+
135+ desc.min_filter = SG_FILTER_NEAREST;
136+ desc.mag_filter = SG_FILTER_NEAREST;
137+ desc.wrap_u = SG_WRAP_REPEAT;
138+ desc.wrap_v = SG_WRAP_REPEAT;
139+
140+ ren.sampler = sg_make_sampler(&desc);
141+
142+ if (sg_query_sampler_state(ren.sampler) != SG_RESOURCESTATE_VALID)
143+ mg_die(MG_ERR_SAMPLER_CREATE, "Failed to create sampler");
144+}
145+
146+static void sokol_log(const char* tag, u32 level, u32 item, const char* msg, u32 ln, const char* file, void* user)
147+{
148+ LogType type;
149+ (void)item;
150+ (void)user;
151+
152+ type = level <= 1 ? LOG_ERR : LOG_DBG;
153+
154+ mg_log(
155+ type, "%s: %s [%s:%u]",
156+ tag ? tag : "Sokol",
157+ msg ? msg : "No message",
158+ file ? file : "Unknown",
159+ ln
160+ );
161+}
162+
163+void renderer_backend_create(MGContext* ctx)
164+{
165+ sg_desc desc = {0};
166+
167+ ren.ctx = ctx;
168+ ren.pass = 0;
169+
170+ desc.environment.defaults.color_format = SG_PIXELFORMAT_RGBA8;
171+ desc.environment.defaults.depth_format = SG_PIXELFORMAT_DEPTH;
172+ desc.environment.defaults.sample_count = 1;
173+ desc.logger.func = sokol_log;
174+
175+ sg_setup(&desc);
176+
177+ if (!sg_isvalid())
178+ mg_die(MG_ERR_REN_CREATE, "Failed to initialise renderer");
179+
180+ create_pipelines();
181+ create_sampler();
182+}
183+
184+void renderer_backend_begin(void)
185+{
186+ sg_pass pass = {0};
187+ i32 width;
188+ i32 height;
189+
190+ ren.pass = 0;
191+
192+ platform_get_drawable_size(ren.ctx, &width, &height);
193+ /* (minimized window?) no need to draw */
194+ if (width <= 0 || height <= 0)
195+ return;
196+
197+ pass.action.colors[0].load_action = SG_LOADACTION_CLEAR;
198+ pass.action.colors[0].clear_value.r = 0.90f;
199+ pass.action.colors[0].clear_value.g = 0.80f;
200+ pass.action.colors[0].clear_value.b = 0.70f;
201+ pass.action.colors[0].clear_value.a = 1.00f;
202+
203+ pass.action.depth.load_action = SG_LOADACTION_CLEAR;
204+ pass.action.depth.clear_value = 1.0f;
205+
206+ /* swapchain descriptor */
207+ pass.swapchain.width = width;
208+ pass.swapchain.height = height;
209+ pass.swapchain.sample_count = 1;
210+ pass.swapchain.color_format = SG_PIXELFORMAT_RGBA8;
211+ pass.swapchain.depth_format = SG_PIXELFORMAT_DEPTH;
212+
213+ pass.swapchain.gl.framebuffer = 0;
214+
215+ sg_begin_pass(&pass);
216+ ren.pass = 1;
217+}
218+
219+void renderer_backend_end(void)
220+{
221+ if (!ren.pass)
222+ return;
223+
224+ sg_end_pass();
225+ sg_commit();
226+
227+ ren.pass = 0;
228+}
229+
230+void renderer_backend_destroy(void)
231+{
232+ u32 id;
233+
234+ /* the values could be different in future so
235+ probably should keep them separate */
236+ for (id = 1; id < MAX_MESHES; id++)
237+ renderer_backend_mesh_destroy(id);
238+ for (id = 1; id < MAX_TEXTURES; id++)
239+ renderer_backend_texture_destroy(id);
240+
241+ sg_destroy_sampler(ren.sampler);
242+ sg_destroy_pipeline(ren.pipeline);
243+ sg_destroy_shader(ren.shader);
244+
245+ if (sg_isvalid())
246+ sg_shutdown();
247+
248+ ren.ctx = NULL;
249+ ren.pass = 0;
250+}
251+
252+MGMesh renderer_backend_mesh_create(const MGMeshDesc* desc)
253+{
254+ sg_buffer_desc buffer_desc = {0};
255+ RendererMesh* mesh;
256+ u32 id;
257+
258+ if (!desc || !desc->vtc || !desc->idc || desc->vtcc == 0 || desc->idcc == 0)
259+ return MGMESH_INVALID;
260+
261+ /* find free mesh slot */
262+ for (id = 1; id < MAX_MESHES; id++) {
263+ if (!ren.meshes[id].used)
264+ break;
265+ }
266+
267+ if (id == MAX_MESHES) {
268+ mg_log(LOG_ERR, "Maximum mesh limit reached");
269+ return MGMESH_INVALID;
270+ }
271+
272+ /* vertex buffer */
273+ mesh = &ren.meshes[id];
274+ buffer_desc.data.ptr = desc->vtc;
275+ buffer_desc.data.size = desc->vtcc * sizeof(MGVertex);
276+ mesh->vb = sg_make_buffer(&buffer_desc);
277+
278+ /* index buffer */
279+ buffer_desc = (sg_buffer_desc){0};
280+ buffer_desc.usage.vertex_buffer = false;
281+ buffer_desc.usage.index_buffer = true;
282+ buffer_desc.usage.immutable = true;
283+ buffer_desc.data.ptr = desc->idc;
284+ buffer_desc.data.size = desc->idcc * sizeof(u16);
285+ mesh->ib = sg_make_buffer(&buffer_desc);
286+
287+ i8 valid = sg_query_buffer_state(mesh->vb) == SG_RESOURCESTATE_VALID &&
288+ sg_query_buffer_state(mesh->ib) == SG_RESOURCESTATE_VALID;
289+ if (!valid) {
290+ if (sg_query_buffer_state(mesh->vb) == SG_RESOURCESTATE_VALID)
291+ sg_destroy_buffer(mesh->vb);
292+ if (sg_query_buffer_state(mesh->ib) == SG_RESOURCESTATE_VALID)
293+ sg_destroy_buffer(mesh->ib);
294+
295+ *mesh = (RendererMesh){0};
296+ mg_log(LOG_ERR, "Failed to create mesh buffers");
297+ return MGMESH_INVALID;
298+ }
299+
300+ mesh->ic = desc->idcc;
301+ mesh->used = 1;
302+ return id;
303+}
304+
305+void renderer_backend_mesh_draw(MGMesh mesh, MGTexture texture, const MGMat4* mvp)
306+{
307+ RendererMesh* rmesh;
308+ RendererTexture* rtexture;
309+ sg_bindings bindings = {0};
310+ sg_range uniforms;
311+
312+ if (!ren.pass || !mvp)
313+ return;
314+
315+ if (mesh == MGMESH_INVALID || mesh >= MAX_MESHES)
316+ return;
317+
318+ if (texture == MGTEXTURE_INVALID || texture >= MAX_TEXTURES)
319+ return;
320+
321+ rmesh = &ren.meshes[mesh];
322+ rtexture = &ren.textures[texture];
323+ if (!rmesh->used || !rtexture->used)
324+ return;
325+
326+ bindings.vertex_buffers[0] = rmesh->vb;
327+ bindings.index_buffer = rmesh->ib;
328+ bindings.views[0] = rtexture->view;
329+ bindings.samplers[0] = ren.sampler;
330+
331+ uniforms.ptr = &mvp->Elements[0][0];
332+ uniforms.size = sizeof(float) * 16;
333+
334+ sg_apply_pipeline(ren.pipeline);
335+ sg_apply_bindings(&bindings);
336+ sg_apply_uniforms(0, &uniforms);
337+
338+ sg_draw(0, rmesh->ic, 1);
339+}
340+
341+void renderer_backend_mesh_destroy(MGMesh id)
342+{
343+ RendererMesh* mesh;
344+
345+ if (id == MGMESH_INVALID || id >= MAX_MESHES)
346+ return;
347+
348+ mesh = &ren.meshes[id];
349+ if (!mesh->used)
350+ return;
351+
352+ sg_destroy_buffer(mesh->ib);
353+ sg_destroy_buffer(mesh->vb);
354+
355+ *mesh = (RendererMesh){0};
356+}
357+
358+MGTexture renderer_backend_texture_load(const char* path)
359+{
360+ sg_image_desc image_desc = {0};
361+ sg_view_desc view_desc = {0};
362+ RendererTexture* texture;
363+ stbi_uc* pixels;
364+ int width;
365+ int height;
366+ int channels;
367+ u32 id;
368+
369+ if (!path)
370+ return MGTEXTURE_INVALID;
371+
372+ /* find free texture slot */
373+ for (id = 1; id < MAX_TEXTURES; id++) {
374+ if (!ren.textures[id].used)
375+ break;
376+ }
377+
378+ if (id == MAX_TEXTURES) {
379+ mg_log(LOG_ERR, "Maximum texture limit reached");
380+ return MGTEXTURE_INVALID;
381+ }
382+
383+ texture = &ren.textures[id];
384+ pixels = stbi_load(path, &width, &height, &channels, 4);
385+ if (!pixels) {
386+ mg_log(LOG_ERR, "Failed to load texture: %s", path);
387+ return MGTEXTURE_INVALID;
388+ }
389+
390+ image_desc.width = width;
391+ image_desc.height = height;
392+ image_desc.pixel_format = SG_PIXELFORMAT_RGBA8;
393+ image_desc.data.mip_levels[0].ptr = pixels;
394+ image_desc.data.mip_levels[0].size =width * height * 4;
395+
396+ texture->image = sg_make_image(&image_desc);
397+ stbi_image_free(pixels);
398+ if (sg_query_image_state(texture->image) != SG_RESOURCESTATE_VALID) {
399+ *texture = (RendererTexture){0};
400+ mg_log(LOG_ERR, "Failed to create texture: %s", path);
401+ return MGTEXTURE_INVALID;
402+ }
403+
404+ view_desc.texture.image = texture->image;
405+ texture->view = sg_make_view(&view_desc);
406+
407+ if (sg_query_view_state(texture->view) != SG_RESOURCESTATE_VALID) {
408+ sg_destroy_image(texture->image);
409+ *texture = (RendererTexture){0};
410+ mg_log(LOG_ERR, "Failed to create texture view: %s", path);
411+ return MGTEXTURE_INVALID;
412+ }
413+
414+ texture->used = 1;
415+
416+ return id;
417+}
418+
419+void renderer_backend_texture_destroy(MGTexture id)
420+{
421+ RendererTexture* texture;
422+
423+ if (id == MGTEXTURE_INVALID || id >= MAX_TEXTURES)
424+ return;
425+
426+ texture = &ren.textures[id];
427+
428+ if (!texture->used)
429+ return;
430+
431+ sg_destroy_view(texture->view);
432+ sg_destroy_image(texture->image);
433+
434+ *texture = (RendererTexture){0};
435+}
+2998,
-0
1@@ -0,0 +1,2998 @@
2+#ifndef SMALL3DLIB_H
3+#define SMALL3DLIB_H
4+
5+/*
6+ Simple realtime 3D software rasterization renderer. It is fast, focused on
7+ resource-limited computers, located in a single C header file, with no
8+ dependencies, using only 32bit integer arithmetics.
9+
10+ author: Miloslav Ciz
11+ license: CC0 1.0 (public domain)
12+ found at https://creativecommons.org/publicdomain/zero/1.0/
13+ + additional waiver of all IP
14+ version: 0.905d
15+
16+ Before including the library, define S3L_PIXEL_FUNCTION to the name of the
17+ function you'll be using to draw single pixels (this function will be called
18+ by the library to render the frames). Also either init S3L_resolutionX and
19+ S3L_resolutionY or define S3L_RESOLUTION_X and S3L_RESOLUTION_Y.
20+
21+ You'll also need to decide what rendering strategy and other settings you
22+ want to use, depending on your specific usecase. You may want to use a
23+ z-buffer (full or reduced, S3L_Z_BUFFER), sorted-drawing (S3L_SORT), or even
24+ none of these. See the description of the options in this file.
25+
26+ The rendering itself is done with S3L_drawScene, usually preceded by
27+ S3L_newFrame (for clearing zBuffer etc.).
28+
29+ The library is meant to be used in not so huge programs that use single
30+ translation unit and so includes both declarations and implementation at once.
31+ If you for some reason use multiple translation units (which include the
32+ library), you'll have to handle this yourself (e.g. create a wrapper, manually
33+ split the library into .c and .h etc.).
34+
35+ --------------------
36+
37+ This work's goal is to never be encumbered by any exclusive intellectual
38+ property rights. The work is therefore provided under CC0 1.0 + additional
39+ WAIVER OF ALL INTELLECTUAL PROPERTY RIGHTS that waives the rest of
40+ intellectual property rights not already waived by CC0 1.0. The WAIVER OF ALL
41+ INTELLECTUAL PROPERTY RGHTS is as follows:
42+
43+ Each contributor to this work agrees that they waive any exclusive rights,
44+ including but not limited to copyright, patents, trademark, trade dress,
45+ industrial design, plant varieties and trade secrets, to any and all ideas,
46+ concepts, processes, discoveries, improvements and inventions conceived,
47+ discovered, made, designed, researched or developed by the contributor either
48+ solely or jointly with others, which relate to this work or result from this
49+ work. Should any waiver of such right be judged legally invalid or
50+ ineffective under applicable law, the contributor hereby grants to each
51+ affected person a royalty-free, non transferable, non sublicensable, non
52+ exclusive, irrevocable and unconditional license to this right.
53+
54+ --------------------
55+
56+ CONVENTIONS:
57+
58+ This library should never draw pixels outside the specified screen
59+ boundaries, so you don't have to check this (that would cost CPU time)!
60+
61+ You can safely assume that triangles are rasterized one by one and from top
62+ down, left to right (so you can utilize e.g. various caches), and if sorting
63+ is disabled the order of rasterization will be that specified in the scene
64+ structure and model arrays (of course, some triangles and models may be
65+ skipped due to culling etc.).
66+
67+ Angles are in S3L_Units, a full angle (2 pi) is S3L_FRACTIONS_PER_UNITs.
68+
69+ We use row vectors.
70+
71+ In 3D space, a left-handed coord. system is used. One spatial unit is split
72+ into S3L_FRACTIONS_PER_UNITs fractions (fixed point arithmetic).
73+
74+ y ^
75+ | _
76+ | /| z
77+ | /
78+ | /
79+ [0,0,0]-------> x
80+
81+ Untransformed camera is placed at [0,0,0], looking forward along +z axis. The
82+ projection plane is centered at [0,0,0], stretrinch from
83+ -S3L_FRACTIONS_PER_UNIT to S3L_FRACTIONS_PER_UNIT horizontally (x),
84+ vertical size (y) depends on the aspect ratio (S3L_RESOLUTION_X and
85+ S3L_RESOLUTION_Y). Camera FOV is defined by focal length in S3L_Units.
86+
87+ Rotations use Euler angles and are generally in the extrinsic Euler angles in
88+ ZXY order (by Z, then by X, then by Y). Positive rotation about an axis
89+ rotates CW (clock-wise) when looking in the direction of the axis.
90+
91+ Coordinates of pixels on the screen start at the top left, from [0,0].
92+
93+ There is NO subpixel accuracy (screen coordinates are only integer).
94+
95+ Triangle rasterization rules are these (mostly same as OpenGL, D3D etc.):
96+
97+ - Let's define:
98+ - left side:
99+ - not exactly horizontal, and on the left side of triangle
100+ - exactly horizontal and above the topmost
101+ (in other words: its normal points at least a little to the left or
102+ completely up)
103+ - right side: not left side
104+ - Pixel centers are at integer coordinates and triangle for drawing are
105+ specified with integer coordinates of pixel centers.
106+ - A pixel is rasterized:
107+ - if its center is inside the triangle OR
108+ - if its center is exactly on the triangle side which is left and at the
109+ same time is not on the side that's right (case of a triangle that's on
110+ a single line) OR
111+ - if its center is exactly on the triangle corner of sides neither of which
112+ is right.
113+
114+ These rules imply among others:
115+
116+ - Adjacent triangles don't have any overlapping pixels, nor gaps between.
117+ - Triangles of points that lie on a single line are NOT rasterized.
118+ - A single "long" triangle CAN be rasterized as isolated islands of pixels.
119+ - Transforming (e.g. mirroring, rotating by 90 degrees etc.) a result of
120+ rasterizing triangle A is NOT generally equal to applying the same
121+ transformation to triangle A first and then rasterizing it. Even the number
122+ of rasterized pixels is usually different.
123+ - If specifying a triangle with integer coordinates (which we are), then:
124+ - The bottom-most corner (or side) of a triangle is never rasterized
125+ (because it is connected to a right side).
126+ - The top-most corner can only be rasterized on completely horizontal side
127+ (otherwise it is connected to a right side).
128+ - Vertically middle corner is rasterized if and only if it is on the left
129+ of the triangle and at the same time is also not the bottom-most corner.
130+*/
131+
132+#include <stdint.h>
133+
134+#ifdef S3L_RESOLUTION_X
135+ #ifdef S3L_RESOLUTION_Y
136+ #define S3L_MAX_PIXELS (S3L_RESOLUTION_X * S3L_RESOLUTION_Y)
137+ #endif
138+#endif
139+
140+#ifndef S3L_RESOLUTION_X
141+ #ifndef S3L_MAX_PIXELS
142+ #error Dynamic resolution set (S3L_RESOLUTION_X not defined), but\
143+ S3L_MAX_PIXELS not defined!
144+ #endif
145+
146+ uint16_t S3L_resolutionX = 512; /**< If a static resolution is not set with
147+ S3L_RESOLUTION_X, this variable can be
148+ used to change X resolution at runtime,
149+ in which case S3L_MAX_PIXELS has to be
150+ defined (to allocate zBuffer etc.)! */
151+ #define S3L_RESOLUTION_X S3L_resolutionX
152+#endif
153+
154+#ifndef S3L_RESOLUTION_Y
155+ #ifndef S3L_MAX_PIXELS
156+ #error Dynamic resolution set (S3L_RESOLUTION_Y not defined), but\
157+ S3L_MAX_PIXELS not defined!
158+ #endif
159+
160+ uint16_t S3L_resolutionY = 512; /**< Same as S3L_resolutionX, but for Y
161+ resolution. */
162+ #define S3L_RESOLUTION_Y S3L_resolutionY
163+#endif
164+
165+#ifndef S3L_USE_WIDER_TYPES
166+ /** If true, the library will use wider data types which will largely supress
167+ many rendering bugs and imprecisions happening due to overflows, but this will
168+ also consumer more RAM and may potentially be slower on computers with smaller
169+ native integer. */
170+ #define S3L_USE_WIDER_TYPES 0
171+#endif
172+
173+#ifndef S3L_SIN_METHOD
174+ /** Says which method should be used for computing sin/cos functions, possible
175+ values: 0 (lookup table, takes more program memory), 1 (Bhaskara's
176+ approximation, slower). This may cause the trigonometric functions give
177+ slightly different results. */
178+ #define S3L_SIN_METHOD 0
179+#endif
180+
181+/** Units of measurement in 3D space. There is S3L_FRACTIONS_PER_UNIT in one
182+spatial unit. By dividing the unit into fractions we effectively achieve a
183+fixed point arithmetic. The number of fractions is a constant that serves as
184+1.0 in floating point arithmetic (normalization etc.). */
185+
186+typedef
187+#if S3L_USE_WIDER_TYPES
188+ int64_t
189+#else
190+ int32_t
191+#endif
192+ S3L_Unit;
193+
194+/** How many fractions a spatial unit is split into, i.e. this is the fixed
195+point scaling. This is NOT SUPPOSED TO BE REDEFINED, so rather don't do it
196+(otherwise things may overflow etc.). */
197+#define S3L_FRACTIONS_PER_UNIT 512
198+#define S3L_F S3L_FRACTIONS_PER_UNIT
199+
200+typedef
201+#if S3L_USE_WIDER_TYPES
202+ int32_t
203+#else
204+ int16_t
205+#endif
206+ S3L_ScreenCoord;
207+
208+typedef
209+#if S3L_USE_WIDER_TYPES
210+ uint32_t
211+#else
212+ uint16_t
213+#endif
214+ S3L_Index;
215+
216+#ifndef S3L_NEAR_CROSS_STRATEGY
217+ /** Specifies how the library will handle triangles that partially cross the
218+ near plane. These are problematic and require special handling. Possible
219+ values:
220+
221+ 0: Strictly cull any triangle crossing the near plane. This will make such
222+ triangles disappear. This is good for performance or models viewed only
223+ from at least small distance.
224+ 1: Forcefully push the vertices crossing near plane in front of it. This is
225+ a cheap technique that can be good enough for displaying simple
226+ environments on slow devices, but texturing and geometric artifacts/warps
227+ will appear.
228+ 2: Geometrically correct the triangles crossing the near plane. This may
229+ result in some triangles being subdivided into two and is a little more
230+ expensive, but the results will be geometrically correct, even though
231+ barycentric correction is not performed so texturing artifacts will
232+ appear. Can be ideal with S3L_FLAT.
233+ 3: Perform both geometrical and barycentric correction of triangle crossing
234+ the near plane. This is significantly more expensive but results in
235+ correct rendering. */
236+ #define S3L_NEAR_CROSS_STRATEGY 0
237+#endif
238+
239+#ifndef S3L_FLAT
240+ /** If on, disables computation of per-pixel values such as barycentric
241+ coordinates and depth -- these will still be available but will be the same
242+ for the whole triangle. This can be used to create flat-shaded renders and
243+ will be a lot faster. With this option on you will probably want to use
244+ sorting instead of z-buffer. */
245+ #define S3L_FLAT 0
246+#endif
247+
248+#if S3L_FLAT
249+ #define S3L_COMPUTE_DEPTH 0
250+ #define S3L_PERSPECTIVE_CORRECTION 0
251+ // don't disable z-buffer, it makes sense to use it with no sorting
252+#endif
253+
254+#ifndef S3L_PERSPECTIVE_CORRECTION
255+ /** Specifies what type of perspective correction (PC) to use. Remember this
256+ is an expensive operation! Possible values:
257+
258+ 0: No perspective correction. Fastest, inaccurate from most angles.
259+ 1: Per-pixel perspective correction, accurate but very expensive.
260+ 2: Approximation (computing only at every S3L_PC_APPROX_LENGTHth pixel).
261+ Quake-style approximation is used, which only computes the PC after
262+ S3L_PC_APPROX_LENGTH pixels. This is reasonably accurate and fast. */
263+ #define S3L_PERSPECTIVE_CORRECTION 0
264+#endif
265+
266+#ifndef S3L_PC_APPROX_LENGTH
267+ /** For S3L_PERSPECTIVE_CORRECTION == 2, this specifies after how many pixels
268+ PC is recomputed. Should be a power of two to keep up the performance.
269+ Smaller is nicer but slower. */
270+ #define S3L_PC_APPROX_LENGTH 32
271+#endif
272+
273+#if S3L_PERSPECTIVE_CORRECTION
274+ #define S3L_COMPUTE_DEPTH 1 // PC inevitably computes depth, so enable it
275+#endif
276+
277+#ifndef S3L_COMPUTE_DEPTH
278+ /** Whether to compute depth for each pixel (fragment). Some other options
279+ may turn this on automatically. If you don't need depth information, turning
280+ this off can save performance. Depth will still be accessible in
281+ S3L_PixelInfo, but will be constant -- equal to center point depth -- over
282+ the whole triangle. */
283+ #define S3L_COMPUTE_DEPTH 1
284+#endif
285+
286+#ifndef S3L_Z_BUFFER
287+ /** What type of z-buffer (depth buffer) to use for visibility determination.
288+ Possible values:
289+
290+ 0: Don't use z-buffer. This saves a lot of memory, but visibility checking
291+ won't be pixel-accurate and has to mostly be done by other means (typically
292+ sorting).
293+ 1: Use full z-buffer (of S3L_Units) for visibiltiy determination. This is the
294+ most accurate option (and also a fast one), but requires a big amount of
295+ memory.
296+ 2: Use reduced-size z-buffer (of bytes). This is fast and somewhat accurate,
297+ but inaccuracies can occur and a considerable amount of memory is
298+ needed. */
299+ #define S3L_Z_BUFFER 0
300+#endif
301+
302+#ifndef S3L_REDUCED_Z_BUFFER_GRANULARITY
303+ /** For S3L_Z_BUFFER == 2 this sets the reduced z-buffer granularity. */
304+ #define S3L_REDUCED_Z_BUFFER_GRANULARITY 5
305+#endif
306+
307+#ifndef S3L_STENCIL_BUFFER
308+ /** Whether to use stencil buffer for drawing -- with this a pixel that would
309+ be resterized over an already rasterized pixel (within a frame) will be
310+ discarded. This is mostly for front-to-back sorted drawing. */
311+ #define S3L_STENCIL_BUFFER 0
312+#endif
313+
314+#ifndef S3L_SORT
315+ /** Defines how to sort triangles before drawing a frame. This can be used to
316+ solve visibility in case z-buffer is not used, to prevent overwriting already
317+ rasterized pixels, implement transparency etc. Note that for simplicity and
318+ performance a relatively simple sorting is used which doesn't work completely
319+ correctly, so mistakes can occur (even the best sorting wouldn't be able to
320+ solve e.g. intersecting triangles). Note that sorting requires a bit of extra
321+ memory -- an array of the triangles to sort -- the size of this array limits
322+ the maximum number of triangles that can be drawn in a single frame
323+ (S3L_MAX_TRIANGES_DRAWN). Possible values:
324+
325+ 0: Don't sort triangles. This is fastest and doesn't use extra memory.
326+ 1: Sort triangles from back to front. This can in most cases solve visibility
327+ without requiring almost any extra memory compared to z-buffer.
328+ 2: Sort triangles from front to back. This can be faster than back to front
329+ because we prevent computing pixels that will be overwritten by nearer
330+ ones, but we need a 1b stencil buffer for this (enable S3L_STENCIL_BUFFER),
331+ so a bit more memory is needed. */
332+ #define S3L_SORT 0
333+#endif
334+
335+#ifndef S3L_MAX_TRIANGES_DRAWN
336+ /** Maximum number of triangles that can be drawn in sorted modes. This
337+ affects the size of the cache used for triangle sorting. */
338+ #define S3L_MAX_TRIANGES_DRAWN 128
339+#endif
340+
341+#ifndef S3L_NEAR
342+ /** Distance of the near clipping plane. Points in front or EXATLY ON this
343+ plane are considered outside the frustum. This must be >= 0. */
344+ #define S3L_NEAR (S3L_F / 4)
345+#endif
346+
347+#if S3L_NEAR <= 0
348+#define S3L_NEAR 1 // Can't be <= 0.
349+#endif
350+
351+#ifndef S3L_NORMAL_COMPUTE_MAXIMUM_AVERAGE
352+ /** Affects the S3L_computeModelNormals function. See its description for
353+ details. */
354+ #define S3L_NORMAL_COMPUTE_MAXIMUM_AVERAGE 6
355+#endif
356+
357+#ifndef S3L_FAST_LERP_QUALITY
358+ /** Quality (scaling) of SOME (stepped) linear interpolations. 0 will most
359+ likely be a tiny bit faster, but artifacts can occur for bigger tris, while
360+ higher values can fix this -- in theory all higher values will have the same
361+ speed (it is a shift value), but it mustn't be too high to prevent
362+ overflow. */
363+ #define S3L_FAST_LERP_QUALITY 11
364+#endif
365+
366+/** Vector that consists of four scalars and can represent homogenous
367+ coordinates, but is generally also used as Vec3 and Vec2 for various
368+ purposes. */
369+typedef struct
370+{
371+ S3L_Unit x;
372+ S3L_Unit y;
373+ S3L_Unit z;
374+ S3L_Unit w;
375+} S3L_Vec4;
376+
377+#define S3L_logVec4(v)\
378+ printf("Vec4: %d %d %d %d\n",((v).x),((v).y),((v).z),((v).w))
379+
380+static inline void S3L_vec4Init(S3L_Vec4 *v);
381+static inline void S3L_vec4Set(S3L_Vec4 *v, S3L_Unit x, S3L_Unit y,
382+ S3L_Unit z, S3L_Unit w);
383+static inline void S3L_vec3Add(S3L_Vec4 *result, S3L_Vec4 added);
384+static inline void S3L_vec3Sub(S3L_Vec4 *result, S3L_Vec4 substracted);
385+S3L_Unit S3L_vec3Length(S3L_Vec4 v);
386+
387+/** Normalizes Vec3. Note that this function tries to normalize correctly
388+ rather than quickly! If you need to normalize quickly, do it yourself in a
389+ way that best fits your case. */
390+void S3L_vec3Normalize(S3L_Vec4 *v);
391+
392+/** Like S3L_vec3Normalize, but doesn't perform any checks on the input vector,
393+ which is faster, but can be very innacurate or overflowing. You are supposed
394+ to provide a "nice" vector (not too big or small). */
395+static inline void S3L_vec3NormalizeFast(S3L_Vec4 *v);
396+
397+S3L_Unit S3L_vec2Length(S3L_Vec4 v);
398+void S3L_vec3Cross(S3L_Vec4 a, S3L_Vec4 b, S3L_Vec4 *result);
399+static inline S3L_Unit S3L_vec3Dot(S3L_Vec4 a, S3L_Vec4 b);
400+
401+/** Computes a reflection direction (typically used e.g. for specular component
402+ in Phong illumination). The input vectors must be normalized. The result will
403+ be normalized as well. */
404+void S3L_reflect(S3L_Vec4 toLight, S3L_Vec4 normal, S3L_Vec4 *result);
405+
406+/** Determines the winding of a triangle, returns 1 (CW, clockwise), -1 (CCW,
407+ counterclockwise) or 0 (points lie on a single line). */
408+static inline int8_t S3L_triangleWinding(
409+ S3L_ScreenCoord x0,
410+ S3L_ScreenCoord y0,
411+ S3L_ScreenCoord x1,
412+ S3L_ScreenCoord y1,
413+ S3L_ScreenCoord x2,
414+ S3L_ScreenCoord y2);
415+
416+typedef struct
417+{
418+ S3L_Vec4 translation;
419+ S3L_Vec4 rotation; /**< Euler angles. Rortation is applied in this order:
420+ 1. z = by z (roll) CW looking along z+
421+ 2. x = by x (pitch) CW looking along x+
422+ 3. y = by y (yaw) CW looking along y+ */
423+ S3L_Vec4 scale;
424+} S3L_Transform3D;
425+
426+#define S3L_logTransform3D(t)\
427+ printf("Transform3D: T = [%d %d %d], R = [%d %d %d], S = [%d %d %d]\n",\
428+ (t).translation.x,(t).translation.y,(t).translation.z,\
429+ (t).rotation.x,(t).rotation.y,(t).rotation.z,\
430+ (t).scale.x,(t).scale.y,(t).scale.z)
431+
432+static inline void S3L_transform3DInit(S3L_Transform3D *t);
433+
434+void S3L_lookAt(S3L_Vec4 pointTo, S3L_Transform3D *t);
435+
436+void S3L_transform3DSet(
437+ S3L_Unit tx,
438+ S3L_Unit ty,
439+ S3L_Unit tz,
440+ S3L_Unit rx,
441+ S3L_Unit ry,
442+ S3L_Unit rz,
443+ S3L_Unit sx,
444+ S3L_Unit sy,
445+ S3L_Unit sz,
446+ S3L_Transform3D *t);
447+
448+/** Converts rotation transformation to three direction vectors of given length
449+ (any one can be NULL, in which case it won't be computed). */
450+void S3L_rotationToDirections(
451+ S3L_Vec4 rotation,
452+ S3L_Unit length,
453+ S3L_Vec4 *forw,
454+ S3L_Vec4 *right,
455+ S3L_Vec4 *up);
456+
457+/** 4x4 matrix, used mostly for 3D transforms. The indexing is this:
458+ matrix[column][row]. */
459+typedef S3L_Unit S3L_Mat4[4][4];
460+
461+#define S3L_logMat4(m)\
462+ printf("Mat4:\n %d %d %d %d\n %d %d %d %d\n %d %d %d %d\n %d %d %d %d\n"\
463+ ,(m)[0][0],(m)[1][0],(m)[2][0],(m)[3][0],\
464+ (m)[0][1],(m)[1][1],(m)[2][1],(m)[3][1],\
465+ (m)[0][2],(m)[1][2],(m)[2][2],(m)[3][2],\
466+ (m)[0][3],(m)[1][3],(m)[2][3],(m)[3][3])
467+
468+/** Initializes a 4x4 matrix to identity. */
469+static inline void S3L_mat4Init(S3L_Mat4 m);
470+
471+void S3L_mat4Copy(S3L_Mat4 src, S3L_Mat4 dst);
472+
473+void S3L_mat4Transpose(S3L_Mat4 m);
474+
475+void S3L_makeTranslationMat(
476+ S3L_Unit offsetX,
477+ S3L_Unit offsetY,
478+ S3L_Unit offsetZ,
479+ S3L_Mat4 m);
480+
481+/** Makes a scaling matrix. DON'T FORGET: scale of 1.0 is set with
482+ S3L_FRACTIONS_PER_UNIT! */
483+void S3L_makeScaleMatrix(
484+ S3L_Unit scaleX,
485+ S3L_Unit scaleY,
486+ S3L_Unit scaleZ,
487+ S3L_Mat4 m);
488+
489+/** Makes a matrix for rotation in the ZXY order. */
490+void S3L_makeRotationMatrixZXY(
491+ S3L_Unit byX,
492+ S3L_Unit byY,
493+ S3L_Unit byZ,
494+ S3L_Mat4 m);
495+
496+void S3L_makeWorldMatrix(S3L_Transform3D worldTransform, S3L_Mat4 m);
497+void S3L_makeCameraMatrix(S3L_Transform3D cameraTransform, S3L_Mat4 m);
498+
499+/** Multiplies a vector by a matrix with normalization by
500+ S3L_FRACTIONS_PER_UNIT. Result is stored in the input vector. */
501+void S3L_vec4Xmat4(S3L_Vec4 *v, S3L_Mat4 m);
502+
503+/** Same as S3L_vec4Xmat4 but faster, because this version doesn't compute the
504+ W component of the result, which is usually not needed. */
505+void S3L_vec3Xmat4(S3L_Vec4 *v, S3L_Mat4 m);
506+
507+/** Multiplies two matrices with normalization by S3L_FRACTIONS_PER_UNIT.
508+ Result is stored in the first matrix. The result represents a transformation
509+ that has the same effect as applying the transformation represented by m1 and
510+ then m2 (in that order). */
511+void S3L_mat4Xmat4(S3L_Mat4 m1, S3L_Mat4 m2);
512+
513+typedef struct
514+{
515+ S3L_Unit focalLength; /**< Defines the field of view (FOV). 0 sets an
516+ orthographics projection (scale is controlled
517+ with camera's scale in its transform). */
518+ S3L_Transform3D transform;
519+} S3L_Camera;
520+
521+void S3L_cameraInit(S3L_Camera *camera);
522+
523+typedef struct
524+{
525+ uint8_t backfaceCulling; /**< What backface culling to use. Possible
526+ values:
527+ - 0 none
528+ - 1 clock-wise
529+ - 2 counter clock-wise */
530+ int8_t visible; /**< Can be used to easily hide the model. */
531+} S3L_DrawConfig;
532+
533+void S3L_drawConfigInit(S3L_DrawConfig *config);
534+
535+typedef struct
536+{
537+ const S3L_Unit *vertices;
538+ S3L_Index vertexCount;
539+ const S3L_Index *triangles;
540+ S3L_Index triangleCount;
541+ S3L_Transform3D transform;
542+ S3L_Mat4 *customTransformMatrix; /**< This can be used to override the
543+ transform (if != 0) with a custom
544+ transform matrix, which is more
545+ general. */
546+ S3L_DrawConfig config;
547+} S3L_Model3D; ///< Represents a 3D model.
548+
549+void S3L_model3DInit(
550+ const S3L_Unit *vertices,
551+ S3L_Index vertexCount,
552+ const S3L_Index *triangles,
553+ S3L_Index triangleCount,
554+ S3L_Model3D *model);
555+
556+typedef struct
557+{
558+ S3L_Model3D *models;
559+ S3L_Index modelCount;
560+ S3L_Camera camera;
561+} S3L_Scene; ///< Represent the 3D scene to be rendered.
562+
563+void S3L_sceneInit(
564+ S3L_Model3D *models,
565+ S3L_Index modelCount,
566+ S3L_Scene *scene);
567+
568+typedef struct
569+{
570+ S3L_ScreenCoord x; ///< Screen X coordinate.
571+ S3L_ScreenCoord y; ///< Screen Y coordinate.
572+
573+ S3L_Unit barycentric[3]; /**< Barycentric coords correspond to the three
574+ vertices. These serve to locate the pixel on a
575+ triangle and interpolate values between its
576+ three points. Each one goes from 0 to
577+ S3L_FRACTIONS_PER_UNIT (including), but due to
578+ rounding error may fall outside this range (you
579+ can use S3L_correctBarycentricCoords to fix this
580+ for the price of some performance). The sum of
581+ the three coordinates will always be exactly
582+ S3L_FRACTIONS_PER_UNIT. */
583+ S3L_Index modelIndex; ///< Model index within the scene.
584+ S3L_Index triangleIndex; ///< Triangle index within the model.
585+ uint32_t triangleID; /**< Unique ID of the triangle withing the whole
586+ scene. This can be used e.g. by a cache to
587+ quickly find out if a triangle has changed. */
588+ S3L_Unit depth; ///< Depth (only if depth is turned on).
589+ S3L_Unit previousZ; /**< Z-buffer value (not necessarily world depth in
590+ S3L_Units!) that was in the z-buffer on the
591+ pixels position before this pixel was
592+ rasterized. This can be used to set the value
593+ back, e.g. for transparency. */
594+ S3L_ScreenCoord triangleSize[2]; /**< Rasterized triangle width and height,
595+ can be used e.g. for MIP mapping. */
596+} S3L_PixelInfo; /**< Used to pass the info about a rasterized pixel
597+ (fragment) to the user-defined drawing func. */
598+
599+static inline void S3L_pixelInfoInit(S3L_PixelInfo *p);
600+
601+/** Corrects barycentric coordinates so that they exactly meet the defined
602+ conditions (each fall into <0,S3L_FRACTIONS_PER_UNIT>, sum =
603+ S3L_FRACTIONS_PER_UNIT). Note that doing this per-pixel can slow the program
604+ down significantly. */
605+static inline void S3L_correctBarycentricCoords(S3L_Unit barycentric[3]);
606+
607+// general helper functions
608+static inline S3L_Unit S3L_abs(S3L_Unit value);
609+static inline S3L_Unit S3L_min(S3L_Unit v1, S3L_Unit v2);
610+static inline S3L_Unit S3L_max(S3L_Unit v1, S3L_Unit v2);
611+static inline S3L_Unit S3L_clamp(S3L_Unit v, S3L_Unit v1, S3L_Unit v2);
612+static inline S3L_Unit S3L_wrap(S3L_Unit value, S3L_Unit mod);
613+static inline S3L_Unit S3L_nonZero(S3L_Unit value);
614+static inline S3L_Unit S3L_zeroClamp(S3L_Unit value);
615+
616+S3L_Unit S3L_sin(S3L_Unit x);
617+S3L_Unit S3L_asin(S3L_Unit x);
618+static inline S3L_Unit S3L_cos(S3L_Unit x);
619+
620+S3L_Unit S3L_vec3Length(S3L_Vec4 v);
621+S3L_Unit S3L_sqrt(S3L_Unit value);
622+
623+/** Projects a single point from 3D space to the screen space (pixels), which
624+ can be useful e.g. for drawing sprites. The w component of input and result
625+ holds the point size. If this size is 0 in the result, the sprite is outside
626+ the view. */
627+void S3L_project3DPointToScreen(
628+ S3L_Vec4 point,
629+ S3L_Camera camera,
630+ S3L_Vec4 *result);
631+
632+/** Computes a normalized normal of given triangle. */
633+void S3L_triangleNormal(S3L_Vec4 t0, S3L_Vec4 t1, S3L_Vec4 t2,
634+ S3L_Vec4 *n);
635+
636+/** Helper function for retrieving per-vertex indexed values from an array,
637+ e.g. texturing (UV) coordinates. The 'indices' array contains three indices
638+ for each triangle, each index pointing into 'values' array, which contains
639+ the values, each one consisting of 'numComponents' components (e.g. 2 for
640+ UV coordinates). The three values are retrieved into 'v0', 'v1' and 'v2'
641+ vectors (into x, y, z and w, depending on 'numComponents'). This function is
642+ meant to be used per-triangle (typically from a cache), NOT per-pixel, as it
643+ is not as fast as possible! */
644+void S3L_getIndexedTriangleValues(
645+ S3L_Index triangleIndex,
646+ const S3L_Index *indices,
647+ const S3L_Unit *values,
648+ uint8_t numComponents,
649+ S3L_Vec4 *v0,
650+ S3L_Vec4 *v1,
651+ S3L_Vec4 *v2);
652+
653+/** Computes a normalized normal for every vertex of given model (this is
654+ relatively slow and SHOUDN'T be done each frame). The dst array must have a
655+ sufficient size preallocated! The size is: number of model vertices * 3 *
656+ sizeof(S3L_Unit). Note that for advanced allowing sharp edges it is not
657+ sufficient to have per-vertex normals, but must be per-triangle. This
658+ function doesn't support this.
659+
660+ The function computes a normal for each vertex by averaging normals of
661+ the triangles containing the vertex. The maximum number of these triangle
662+ normals that will be averaged is set with
663+ S3L_NORMAL_COMPUTE_MAXIMUM_AVERAGE. */
664+void S3L_computeModelNormals(S3L_Model3D model, S3L_Unit *dst,
665+ int8_t transformNormals);
666+
667+/** Interpolated between two values, v1 and v2, in the same ratio as t is to
668+ tMax. Does NOT prevent zero division. */
669+static inline S3L_Unit S3L_interpolate(
670+ S3L_Unit v1,
671+ S3L_Unit v2,
672+ S3L_Unit t,
673+ S3L_Unit tMax);
674+
675+/** Same as S3L_interpolate but with v1 == 0. Should be faster. */
676+static inline S3L_Unit S3L_interpolateFrom0(
677+ S3L_Unit v2,
678+ S3L_Unit t,
679+ S3L_Unit tMax);
680+
681+/** Like S3L_interpolate, but uses a parameter that goes from 0 to
682+ S3L_FRACTIONS_PER_UNIT - 1, which can be faster. */
683+static inline S3L_Unit S3L_interpolateByUnit(
684+ S3L_Unit v1,
685+ S3L_Unit v2,
686+ S3L_Unit t);
687+
688+/** Same as S3L_interpolateByUnit but with v1 == 0. Should be faster. */
689+static inline S3L_Unit S3L_interpolateByUnitFrom0(
690+ S3L_Unit v2,
691+ S3L_Unit t);
692+
693+static inline S3L_Unit S3L_distanceManhattan(S3L_Vec4 a, S3L_Vec4 b);
694+
695+/** Returns a value interpolated between the three triangle vertices based on
696+ barycentric coordinates. */
697+static inline S3L_Unit S3L_interpolateBarycentric(
698+ S3L_Unit value0,
699+ S3L_Unit value1,
700+ S3L_Unit value2,
701+ S3L_Unit barycentric[3]);
702+
703+static inline void S3L_mapProjectionPlaneToScreen(
704+ S3L_Vec4 point,
705+ S3L_ScreenCoord *screenX,
706+ S3L_ScreenCoord *screenY);
707+
708+/** Draws a triangle according to given config. The vertices are specified in
709+ Screen Space space (pixels). If perspective correction is enabled, each
710+ vertex has to have a depth (Z position in camera space) specified in the Z
711+ component. */
712+void S3L_drawTriangle(
713+ S3L_Vec4 point0,
714+ S3L_Vec4 point1,
715+ S3L_Vec4 point2,
716+ S3L_Index modelIndex,
717+ S3L_Index triangleIndex);
718+
719+/** This should be called before rendering each frame. The function clears
720+ buffers and does potentially other things needed for the frame. */
721+void S3L_newFrame(void);
722+
723+void S3L_zBufferClear(void);
724+void S3L_stencilBufferClear(void);
725+
726+/** Writes a value (not necessarily depth! depends on the format of z-buffer)
727+ to z-buffer (if enabled). Does NOT check boundaries! */
728+void S3L_zBufferWrite(S3L_ScreenCoord x, S3L_ScreenCoord y, S3L_Unit value);
729+
730+/** Reads a value (not necessarily depth! depends on the format of z-buffer)
731+ from z-buffer (if enabled). Does NOT check boundaries! */
732+S3L_Unit S3L_zBufferRead(S3L_ScreenCoord x, S3L_ScreenCoord y);
733+
734+static inline void S3L_rotate2DPoint(S3L_Unit *x, S3L_Unit *y, S3L_Unit angle);
735+
736+/** Predefined vertices of a cube to simply insert in an array. These come with
737+ S3L_CUBE_TRIANGLES and S3L_CUBE_TEXCOORDS. */
738+#define S3L_CUBE_VERTICES(m)\
739+ /* 0 front, bottom, right */\
740+ m/2, -m/2, -m/2,\
741+ /* 1 front, bottom, left */\
742+-m/2, -m/2, -m/2,\
743+ /* 2 front, top, right */\
744+ m/2, m/2, -m/2,\
745+ /* 3 front, top, left */\
746+-m/2, m/2, -m/2,\
747+ /* 4 back, bottom, right */\
748+ m/2, -m/2, m/2,\
749+ /* 5 back, bottom, left */\
750+-m/2, -m/2, m/2,\
751+ /* 6 back, top, right */\
752+ m/2, m/2, m/2,\
753+ /* 7 back, top, left */\
754+-m/2, m/2, m/2
755+
756+#define S3L_CUBE_VERTEX_COUNT 8
757+
758+/** Predefined triangle indices of a cube, to be used with S3L_CUBE_VERTICES
759+ and S3L_CUBE_TEXCOORDS. */
760+#define S3L_CUBE_TRIANGLES\
761+ 3, 0, 2, /* front */\
762+ 1, 0, 3,\
763+ 0, 4, 2, /* right */\
764+ 2, 4, 6,\
765+ 4, 5, 6, /* back */\
766+ 7, 6, 5,\
767+ 3, 7, 1, /* left */\
768+ 1, 7, 5,\
769+ 6, 3, 2, /* top */\
770+ 7, 3, 6,\
771+ 1, 4, 0, /* bottom */\
772+ 5, 4, 1
773+
774+#define S3L_CUBE_TRIANGLE_COUNT 12
775+
776+/** Predefined texture coordinates of a cube, corresponding to triangles (NOT
777+ vertices), to be used with S3L_CUBE_VERTICES and S3L_CUBE_TRIANGLES. */
778+#define S3L_CUBE_TEXCOORDS(m)\
779+ 0,0, m,m, m,0,\
780+ 0,m, m,m, 0,0,\
781+ m,m, m,0, 0,m,\
782+ 0,m, m,0, 0,0,\
783+ m,0, 0,0, m,m,\
784+ 0,m, m,m, 0,0,\
785+ 0,0, 0,m, m,0,\
786+ m,0, 0,m, m,m,\
787+ 0,0, m,m, m,0,\
788+ 0,m, m,m, 0,0,\
789+ m,0, 0,m, m,m,\
790+ 0,0, 0,m, m,0
791+
792+//=============================================================================
793+// privates
794+
795+#define S3L_UNUSED(what) (void)(what) ///< helper macro for unused vars
796+
797+#define S3L_HALF_RESOLUTION_X (S3L_RESOLUTION_X >> 1)
798+#define S3L_HALF_RESOLUTION_Y (S3L_RESOLUTION_Y >> 1)
799+
800+#define S3L_PROJECTION_PLANE_HEIGHT\
801+ ((S3L_RESOLUTION_Y * S3L_F * 2) / S3L_RESOLUTION_X)
802+
803+#if S3L_Z_BUFFER == 1
804+ #define S3L_MAX_DEPTH 2147483647
805+ S3L_Unit S3L_zBuffer[S3L_MAX_PIXELS];
806+ #define S3L_zBufferFormat(depth) (depth)
807+#elif S3L_Z_BUFFER == 2
808+ #define S3L_MAX_DEPTH 255
809+ uint8_t S3L_zBuffer[S3L_MAX_PIXELS];
810+ #define S3L_zBufferFormat(depth)\
811+ S3L_min(255,(depth) >> S3L_REDUCED_Z_BUFFER_GRANULARITY)
812+#endif
813+
814+#if S3L_Z_BUFFER
815+static inline int8_t S3L_zTest(
816+ S3L_ScreenCoord x,
817+ S3L_ScreenCoord y,
818+ S3L_Unit depth)
819+{
820+ uint32_t index = y * S3L_RESOLUTION_X + x;
821+
822+ depth = S3L_zBufferFormat(depth);
823+
824+#if S3L_Z_BUFFER == 2
825+ #define cmp <= /* For reduced z-buffer we need equality test, because
826+ otherwise pixels at the maximum depth (255) would never be
827+ drawn over the background (which also has the depth of
828+ 255). */
829+#else
830+ #define cmp < /* For normal z-buffer we leave out equality test to not waste
831+ time by drawing over already drawn pixls. */
832+#endif
833+
834+ if (depth cmp S3L_zBuffer[index])
835+ {
836+ S3L_zBuffer[index] = depth;
837+ return 1;
838+ }
839+
840+#undef cmp
841+
842+ return 0;
843+}
844+#endif
845+
846+S3L_Unit S3L_zBufferRead(S3L_ScreenCoord x, S3L_ScreenCoord y)
847+{
848+#if S3L_Z_BUFFER
849+ return S3L_zBuffer[y * S3L_RESOLUTION_X + x];
850+#else
851+ S3L_UNUSED(x);
852+ S3L_UNUSED(y);
853+
854+ return 0;
855+#endif
856+}
857+
858+void S3L_zBufferWrite(S3L_ScreenCoord x, S3L_ScreenCoord y, S3L_Unit value)
859+{
860+#if S3L_Z_BUFFER
861+ S3L_zBuffer[y * S3L_RESOLUTION_X + x] = value;
862+#else
863+ S3L_UNUSED(x);
864+ S3L_UNUSED(y);
865+ S3L_UNUSED(value);
866+#endif
867+}
868+
869+#if S3L_STENCIL_BUFFER
870+ #define S3L_STENCIL_BUFFER_SIZE\
871+ ((S3L_RESOLUTION_X * S3L_RESOLUTION_Y - 1) / 8 + 1)
872+
873+uint8_t S3L_stencilBuffer[S3L_STENCIL_BUFFER_SIZE];
874+
875+static inline int8_t S3L_stencilTest(
876+ S3L_ScreenCoord x,
877+ S3L_ScreenCoord y)
878+{
879+ uint32_t index = y * S3L_RESOLUTION_X + x;
880+ uint32_t bit = (index & 0x00000007);
881+ index = index >> 3;
882+
883+ uint8_t val = S3L_stencilBuffer[index];
884+
885+ if ((val >> bit) & 0x1)
886+ return 0;
887+
888+ S3L_stencilBuffer[index] = val | (0x1 << bit);
889+
890+ return 1;
891+}
892+#endif
893+
894+#define S3L_COMPUTE_LERP_DEPTH\
895+ (S3L_COMPUTE_DEPTH && (S3L_PERSPECTIVE_CORRECTION == 0))
896+
897+#define S3L_SIN_TABLE_LENGTH 128
898+
899+#if S3L_SIN_METHOD == 0
900+static const S3L_Unit S3L_sinTable[S3L_SIN_TABLE_LENGTH] =
901+{
902+ /* 511 was chosen here as a highest number that doesn't overflow during
903+ compilation for S3L_F == 1024 */
904+
905+ (0*S3L_F)/511, (6*S3L_F)/511,
906+ (12*S3L_F)/511, (18*S3L_F)/511,
907+ (25*S3L_F)/511, (31*S3L_F)/511,
908+ (37*S3L_F)/511, (43*S3L_F)/511,
909+ (50*S3L_F)/511, (56*S3L_F)/511,
910+ (62*S3L_F)/511, (68*S3L_F)/511,
911+ (74*S3L_F)/511, (81*S3L_F)/511,
912+ (87*S3L_F)/511, (93*S3L_F)/511,
913+ (99*S3L_F)/511, (105*S3L_F)/511,
914+ (111*S3L_F)/511, (118*S3L_F)/511,
915+ (124*S3L_F)/511, (130*S3L_F)/511,
916+ (136*S3L_F)/511, (142*S3L_F)/511,
917+ (148*S3L_F)/511, (154*S3L_F)/511,
918+ (160*S3L_F)/511, (166*S3L_F)/511,
919+ (172*S3L_F)/511, (178*S3L_F)/511,
920+ (183*S3L_F)/511, (189*S3L_F)/511,
921+ (195*S3L_F)/511, (201*S3L_F)/511,
922+ (207*S3L_F)/511, (212*S3L_F)/511,
923+ (218*S3L_F)/511, (224*S3L_F)/511,
924+ (229*S3L_F)/511, (235*S3L_F)/511,
925+ (240*S3L_F)/511, (246*S3L_F)/511,
926+ (251*S3L_F)/511, (257*S3L_F)/511,
927+ (262*S3L_F)/511, (268*S3L_F)/511,
928+ (273*S3L_F)/511, (278*S3L_F)/511,
929+ (283*S3L_F)/511, (289*S3L_F)/511,
930+ (294*S3L_F)/511, (299*S3L_F)/511,
931+ (304*S3L_F)/511, (309*S3L_F)/511,
932+ (314*S3L_F)/511, (319*S3L_F)/511,
933+ (324*S3L_F)/511, (328*S3L_F)/511,
934+ (333*S3L_F)/511, (338*S3L_F)/511,
935+ (343*S3L_F)/511, (347*S3L_F)/511,
936+ (352*S3L_F)/511, (356*S3L_F)/511,
937+ (361*S3L_F)/511, (365*S3L_F)/511,
938+ (370*S3L_F)/511, (374*S3L_F)/511,
939+ (378*S3L_F)/511, (382*S3L_F)/511,
940+ (386*S3L_F)/511, (391*S3L_F)/511,
941+ (395*S3L_F)/511, (398*S3L_F)/511,
942+ (402*S3L_F)/511, (406*S3L_F)/511,
943+ (410*S3L_F)/511, (414*S3L_F)/511,
944+ (417*S3L_F)/511, (421*S3L_F)/511,
945+ (424*S3L_F)/511, (428*S3L_F)/511,
946+ (431*S3L_F)/511, (435*S3L_F)/511,
947+ (438*S3L_F)/511, (441*S3L_F)/511,
948+ (444*S3L_F)/511, (447*S3L_F)/511,
949+ (450*S3L_F)/511, (453*S3L_F)/511,
950+ (456*S3L_F)/511, (459*S3L_F)/511,
951+ (461*S3L_F)/511, (464*S3L_F)/511,
952+ (467*S3L_F)/511, (469*S3L_F)/511,
953+ (472*S3L_F)/511, (474*S3L_F)/511,
954+ (476*S3L_F)/511, (478*S3L_F)/511,
955+ (481*S3L_F)/511, (483*S3L_F)/511,
956+ (485*S3L_F)/511, (487*S3L_F)/511,
957+ (488*S3L_F)/511, (490*S3L_F)/511,
958+ (492*S3L_F)/511, (494*S3L_F)/511,
959+ (495*S3L_F)/511, (497*S3L_F)/511,
960+ (498*S3L_F)/511, (499*S3L_F)/511,
961+ (501*S3L_F)/511, (502*S3L_F)/511,
962+ (503*S3L_F)/511, (504*S3L_F)/511,
963+ (505*S3L_F)/511, (506*S3L_F)/511,
964+ (507*S3L_F)/511, (507*S3L_F)/511,
965+ (508*S3L_F)/511, (509*S3L_F)/511,
966+ (509*S3L_F)/511, (510*S3L_F)/511,
967+ (510*S3L_F)/511, (510*S3L_F)/511,
968+ (510*S3L_F)/511, (510*S3L_F)/511
969+};
970+#endif
971+
972+#define S3L_SIN_TABLE_UNIT_STEP\
973+ (S3L_F / (S3L_SIN_TABLE_LENGTH * 4))
974+
975+void S3L_vec4Init(S3L_Vec4 *v)
976+{
977+ v->x = 0; v->y = 0; v->z = 0; v->w = S3L_F;
978+}
979+
980+void S3L_vec4Set(S3L_Vec4 *v, S3L_Unit x, S3L_Unit y, S3L_Unit z, S3L_Unit w)
981+{
982+ v->x = x;
983+ v->y = y;
984+ v->z = z;
985+ v->w = w;
986+}
987+
988+void S3L_vec3Add(S3L_Vec4 *result, S3L_Vec4 added)
989+{
990+ result->x += added.x;
991+ result->y += added.y;
992+ result->z += added.z;
993+}
994+
995+void S3L_vec3Sub(S3L_Vec4 *result, S3L_Vec4 substracted)
996+{
997+ result->x -= substracted.x;
998+ result->y -= substracted.y;
999+ result->z -= substracted.z;
1000+}
1001+
1002+void S3L_mat4Init(S3L_Mat4 m)
1003+{
1004+ #define M(x,y) m[x][y]
1005+ #define S S3L_F
1006+
1007+ M(0,0) = S; M(1,0) = 0; M(2,0) = 0; M(3,0) = 0;
1008+ M(0,1) = 0; M(1,1) = S; M(2,1) = 0; M(3,1) = 0;
1009+ M(0,2) = 0; M(1,2) = 0; M(2,2) = S; M(3,2) = 0;
1010+ M(0,3) = 0; M(1,3) = 0; M(2,3) = 0; M(3,3) = S;
1011+
1012+ #undef M
1013+ #undef S
1014+}
1015+
1016+void S3L_mat4Copy(S3L_Mat4 src, S3L_Mat4 dst)
1017+{
1018+ for (uint8_t j = 0; j < 4; ++j)
1019+ for (uint8_t i = 0; i < 4; ++i)
1020+ dst[i][j] = src[i][j];
1021+}
1022+
1023+S3L_Unit S3L_vec3Dot(S3L_Vec4 a, S3L_Vec4 b)
1024+{
1025+ return (a.x * b.x + a.y * b.y + a.z * b.z) / S3L_F;
1026+}
1027+
1028+void S3L_reflect(S3L_Vec4 toLight, S3L_Vec4 normal, S3L_Vec4 *result)
1029+{
1030+ S3L_Unit d = 2 * S3L_vec3Dot(toLight,normal);
1031+
1032+ result->x = (normal.x * d) / S3L_F - toLight.x;
1033+ result->y = (normal.y * d) / S3L_F - toLight.y;
1034+ result->z = (normal.z * d) / S3L_F - toLight.z;
1035+}
1036+
1037+void S3L_vec3Cross(S3L_Vec4 a, S3L_Vec4 b, S3L_Vec4 *result)
1038+{
1039+ result->x = a.y * b.z - a.z * b.y;
1040+ result->y = a.z * b.x - a.x * b.z;
1041+ result->z = a.x * b.y - a.y * b.x;
1042+}
1043+
1044+void S3L_triangleNormal(S3L_Vec4 t0, S3L_Vec4 t1, S3L_Vec4 t2, S3L_Vec4 *n)
1045+{
1046+ #define ANTI_OVERFLOW 32
1047+
1048+ t1.x = (t1.x - t0.x) / ANTI_OVERFLOW;
1049+ t1.y = (t1.y - t0.y) / ANTI_OVERFLOW;
1050+ t1.z = (t1.z - t0.z) / ANTI_OVERFLOW;
1051+
1052+ t2.x = (t2.x - t0.x) / ANTI_OVERFLOW;
1053+ t2.y = (t2.y - t0.y) / ANTI_OVERFLOW;
1054+ t2.z = (t2.z - t0.z) / ANTI_OVERFLOW;
1055+
1056+ #undef ANTI_OVERFLOW
1057+
1058+ S3L_vec3Cross(t1,t2,n);
1059+
1060+ S3L_vec3Normalize(n);
1061+}
1062+
1063+void S3L_getIndexedTriangleValues(
1064+ S3L_Index triangleIndex,
1065+ const S3L_Index *indices,
1066+ const S3L_Unit *values,
1067+ uint8_t numComponents,
1068+ S3L_Vec4 *v0,
1069+ S3L_Vec4 *v1,
1070+ S3L_Vec4 *v2)
1071+{
1072+ uint32_t i0, i1;
1073+ S3L_Unit *value;
1074+
1075+ i0 = triangleIndex * 3;
1076+ i1 = indices[i0] * numComponents;
1077+ value = (S3L_Unit *) v0;
1078+
1079+ if (numComponents > 4)
1080+ numComponents = 4;
1081+
1082+ for (uint8_t j = 0; j < numComponents; ++j)
1083+ {
1084+ *value = values[i1];
1085+ i1++;
1086+ value++;
1087+ }
1088+
1089+ i0++;
1090+ i1 = indices[i0] * numComponents;
1091+ value = (S3L_Unit *) v1;
1092+
1093+ for (uint8_t j = 0; j < numComponents; ++j)
1094+ {
1095+ *value = values[i1];
1096+ i1++;
1097+ value++;
1098+ }
1099+
1100+ i0++;
1101+ i1 = indices[i0] * numComponents;
1102+ value = (S3L_Unit *) v2;
1103+
1104+ for (uint8_t j = 0; j < numComponents; ++j)
1105+ {
1106+ *value = values[i1];
1107+ i1++;
1108+ value++;
1109+ }
1110+}
1111+
1112+void S3L_computeModelNormals(S3L_Model3D model, S3L_Unit *dst,
1113+ int8_t transformNormals)
1114+{
1115+ S3L_Index vPos = 0;
1116+
1117+ S3L_Vec4 n;
1118+
1119+ n.w = 0;
1120+
1121+ S3L_Vec4 ns[S3L_NORMAL_COMPUTE_MAXIMUM_AVERAGE];
1122+ S3L_Index normalCount;
1123+
1124+ for (uint32_t i = 0; i < model.vertexCount; ++i)
1125+ {
1126+ normalCount = 0;
1127+
1128+ for (uint32_t j = 0; j < model.triangleCount * 3; j += 3)
1129+ {
1130+ if (
1131+ (model.triangles[j] == i) ||
1132+ (model.triangles[j + 1] == i) ||
1133+ (model.triangles[j + 2] == i))
1134+ {
1135+ S3L_Vec4 t0, t1, t2;
1136+ uint32_t vIndex;
1137+
1138+ #define getVertex(n)\
1139+ vIndex = model.triangles[j + n] * 3;\
1140+ t##n.x = model.vertices[vIndex];\
1141+ vIndex++;\
1142+ t##n.y = model.vertices[vIndex];\
1143+ vIndex++;\
1144+ t##n.z = model.vertices[vIndex];
1145+
1146+ getVertex(0)
1147+ getVertex(1)
1148+ getVertex(2)
1149+
1150+ #undef getVertex
1151+
1152+ S3L_triangleNormal(t0,t1,t2,&(ns[normalCount]));
1153+
1154+ normalCount++;
1155+
1156+ if (normalCount >= S3L_NORMAL_COMPUTE_MAXIMUM_AVERAGE)
1157+ break;
1158+ }
1159+ }
1160+
1161+ n.x = S3L_F;
1162+ n.y = 0;
1163+ n.z = 0;
1164+
1165+ if (normalCount != 0)
1166+ {
1167+ // compute average
1168+
1169+ n.x = 0;
1170+
1171+ for (uint8_t i = 0; i < normalCount; ++i)
1172+ {
1173+ n.x += ns[i].x;
1174+ n.y += ns[i].y;
1175+ n.z += ns[i].z;
1176+ }
1177+
1178+ n.x /= normalCount;
1179+ n.y /= normalCount;
1180+ n.z /= normalCount;
1181+
1182+ S3L_vec3Normalize(&n);
1183+ }
1184+
1185+ dst[vPos] = n.x;
1186+ vPos++;
1187+
1188+ dst[vPos] = n.y;
1189+ vPos++;
1190+
1191+ dst[vPos] = n.z;
1192+ vPos++;
1193+ }
1194+
1195+ S3L_Mat4 m;
1196+
1197+ S3L_makeWorldMatrix(model.transform,m);
1198+
1199+ if (transformNormals)
1200+ for (S3L_Index i = 0; i < model.vertexCount * 3; i += 3)
1201+ {
1202+ n.x = dst[i];
1203+ n.y = dst[i + 1];
1204+ n.z = dst[i + 2];
1205+
1206+ S3L_vec4Xmat4(&n,m);
1207+
1208+ dst[i] = n.x;
1209+ dst[i + 1] = n.y;
1210+ dst[i + 2] = n.z;
1211+ }
1212+}
1213+
1214+void S3L_vec4Xmat4(S3L_Vec4 *v, S3L_Mat4 m)
1215+{
1216+ S3L_Vec4 vBackup;
1217+
1218+ vBackup.x = v->x;
1219+ vBackup.y = v->y;
1220+ vBackup.z = v->z;
1221+ vBackup.w = v->w;
1222+
1223+ #define dotCol(col)\
1224+ ((vBackup.x * m[col][0]) +\
1225+ (vBackup.y * m[col][1]) +\
1226+ (vBackup.z * m[col][2]) +\
1227+ (vBackup.w * m[col][3])) / S3L_F
1228+
1229+ v->x = dotCol(0);
1230+ v->y = dotCol(1);
1231+ v->z = dotCol(2);
1232+ v->w = dotCol(3);
1233+}
1234+
1235+void S3L_vec3Xmat4(S3L_Vec4 *v, S3L_Mat4 m)
1236+{
1237+ S3L_Vec4 vBackup;
1238+
1239+ #undef dotCol
1240+ #define dotCol(col)\
1241+ (vBackup.x * m[col][0]) / S3L_F +\
1242+ (vBackup.y * m[col][1]) / S3L_F +\
1243+ (vBackup.z * m[col][2]) / S3L_F +\
1244+ m[col][3]
1245+
1246+ vBackup.x = v->x;
1247+ vBackup.y = v->y;
1248+ vBackup.z = v->z;
1249+ vBackup.w = v->w;
1250+
1251+ v->x = dotCol(0);
1252+ v->y = dotCol(1);
1253+ v->z = dotCol(2);
1254+ v->w = S3L_F;
1255+}
1256+
1257+#undef dotCol
1258+
1259+S3L_Unit S3L_abs(S3L_Unit value)
1260+{
1261+ return value * (((value >= 0) << 1) - 1);
1262+}
1263+
1264+S3L_Unit S3L_min(S3L_Unit v1, S3L_Unit v2)
1265+{
1266+ return v1 >= v2 ? v2 : v1;
1267+}
1268+
1269+S3L_Unit S3L_max(S3L_Unit v1, S3L_Unit v2)
1270+{
1271+ return v1 >= v2 ? v1 : v2;
1272+}
1273+
1274+S3L_Unit S3L_clamp(S3L_Unit v, S3L_Unit v1, S3L_Unit v2)
1275+{
1276+ return v >= v1 ? (v <= v2 ? v : v2) : v1;
1277+}
1278+
1279+S3L_Unit S3L_zeroClamp(S3L_Unit value)
1280+{
1281+ return (value * (value >= 0));
1282+}
1283+
1284+S3L_Unit S3L_wrap(S3L_Unit value, S3L_Unit mod)
1285+{
1286+ return value >= 0 ? (value % mod) : (mod + (value % mod) - 1);
1287+}
1288+
1289+S3L_Unit S3L_nonZero(S3L_Unit value)
1290+{
1291+ return (value + (value == 0));
1292+}
1293+
1294+S3L_Unit S3L_interpolate(S3L_Unit v1, S3L_Unit v2, S3L_Unit t, S3L_Unit tMax)
1295+{
1296+ return v1 + ((v2 - v1) * t) / tMax;
1297+}
1298+
1299+S3L_Unit S3L_interpolateByUnit(S3L_Unit v1, S3L_Unit v2, S3L_Unit t)
1300+{
1301+ return v1 + ((v2 - v1) * t) / S3L_F;
1302+}
1303+
1304+S3L_Unit S3L_interpolateByUnitFrom0(S3L_Unit v2, S3L_Unit t)
1305+{
1306+ return (v2 * t) / S3L_F;
1307+}
1308+
1309+S3L_Unit S3L_interpolateFrom0(S3L_Unit v2, S3L_Unit t, S3L_Unit tMax)
1310+{
1311+ return (v2 * t) / tMax;
1312+}
1313+
1314+S3L_Unit S3L_distanceManhattan(S3L_Vec4 a, S3L_Vec4 b)
1315+{
1316+ return
1317+ S3L_abs(a.x - b.x) +
1318+ S3L_abs(a.y - b.y) +
1319+ S3L_abs(a.z - b.z);
1320+}
1321+
1322+void S3L_mat4Xmat4(S3L_Mat4 m1, S3L_Mat4 m2)
1323+{
1324+ S3L_Mat4 mat1;
1325+
1326+ for (uint16_t row = 0; row < 4; ++row)
1327+ for (uint16_t col = 0; col < 4; ++col)
1328+ mat1[col][row] = m1[col][row];
1329+
1330+ for (uint16_t row = 0; row < 4; ++row)
1331+ for (uint16_t col = 0; col < 4; ++col)
1332+ {
1333+ m1[col][row] = 0;
1334+
1335+ for (uint16_t i = 0; i < 4; ++i)
1336+ m1[col][row] +=
1337+ (mat1[i][row] * m2[col][i]) / S3L_F;
1338+ }
1339+}
1340+
1341+S3L_Unit S3L_sin(S3L_Unit x)
1342+{
1343+#if S3L_SIN_METHOD == 0
1344+ x = S3L_wrap(x / S3L_SIN_TABLE_UNIT_STEP,S3L_SIN_TABLE_LENGTH * 4);
1345+ int8_t positive = 1;
1346+
1347+ if (x < S3L_SIN_TABLE_LENGTH)
1348+ {
1349+ }
1350+ else if (x < S3L_SIN_TABLE_LENGTH * 2)
1351+ {
1352+ x = S3L_SIN_TABLE_LENGTH * 2 - x - 1;
1353+ }
1354+ else if (x < S3L_SIN_TABLE_LENGTH * 3)
1355+ {
1356+ x = x - S3L_SIN_TABLE_LENGTH * 2;
1357+ positive = 0;
1358+ }
1359+ else
1360+ {
1361+ x = S3L_SIN_TABLE_LENGTH - (x - S3L_SIN_TABLE_LENGTH * 3) - 1;
1362+ positive = 0;
1363+ }
1364+
1365+ return positive ? S3L_sinTable[x] : -1 * S3L_sinTable[x];
1366+#else
1367+ int8_t sign = 1;
1368+
1369+ if (x < 0) // odd function
1370+ {
1371+ x *= -1;
1372+ sign = -1;
1373+ }
1374+
1375+ x %= S3L_F;
1376+
1377+ if (x > S3L_F / 2)
1378+ {
1379+ x -= S3L_F / 2;
1380+ sign *= -1;
1381+ }
1382+
1383+ S3L_Unit tmp = S3L_F - 2 * x;
1384+
1385+ #define _PI2 ((S3L_Unit) (9.8696044 * S3L_F))
1386+ return sign * // Bhaskara's approximation
1387+ (((32 * x * _PI2) / S3L_F) * tmp) /
1388+ ((_PI2 * (5 * S3L_F - (8 * x * tmp) /
1389+ S3L_F)) / S3L_F);
1390+ #undef _PI2
1391+#endif
1392+}
1393+
1394+S3L_Unit S3L_asin(S3L_Unit x)
1395+{
1396+#if S3L_SIN_METHOD == 0
1397+ x = S3L_clamp(x,-S3L_F,S3L_F);
1398+
1399+ int8_t sign = 1;
1400+
1401+ if (x < 0)
1402+ {
1403+ sign = -1;
1404+ x *= -1;
1405+ }
1406+
1407+ int16_t low = 0, high = S3L_SIN_TABLE_LENGTH -1, middle = 0;
1408+
1409+ while (low <= high) // binary search
1410+ {
1411+ middle = (low + high) / 2;
1412+
1413+ S3L_Unit v = S3L_sinTable[middle];
1414+
1415+ if (v > x)
1416+ high = middle - 1;
1417+ else if (v < x)
1418+ low = middle + 1;
1419+ else
1420+ break;
1421+ }
1422+
1423+ middle *= S3L_SIN_TABLE_UNIT_STEP;
1424+
1425+ return sign * middle;
1426+#else
1427+ S3L_Unit low = -1 * S3L_F / 4,
1428+ high = S3L_F / 4,
1429+ middle;
1430+
1431+ while (low <= high) // binary search
1432+ {
1433+ middle = (low + high) / 2;
1434+
1435+ S3L_Unit v = S3L_sin(middle);
1436+
1437+ if (v > x)
1438+ high = middle - 1;
1439+ else if (v < x)
1440+ low = middle + 1;
1441+ else
1442+ break;
1443+ }
1444+
1445+ return middle;
1446+#endif
1447+}
1448+
1449+S3L_Unit S3L_cos(S3L_Unit x)
1450+{
1451+ return S3L_sin(x + S3L_F / 4);
1452+}
1453+
1454+void S3L_correctBarycentricCoords(S3L_Unit barycentric[3])
1455+{
1456+ barycentric[0] = S3L_clamp(barycentric[0],0,S3L_F);
1457+ barycentric[1] = S3L_clamp(barycentric[1],0,S3L_F);
1458+
1459+ S3L_Unit d = S3L_F - barycentric[0] - barycentric[1];
1460+
1461+ if (d < 0)
1462+ {
1463+ barycentric[0] += d;
1464+ barycentric[2] = 0;
1465+ }
1466+ else
1467+ barycentric[2] = d;
1468+}
1469+
1470+void S3L_makeTranslationMat(
1471+ S3L_Unit offsetX,
1472+ S3L_Unit offsetY,
1473+ S3L_Unit offsetZ,
1474+ S3L_Mat4 m)
1475+{
1476+ #define M(x,y) m[x][y]
1477+ #define S S3L_F
1478+
1479+ M(0,0) = S; M(1,0) = 0; M(2,0) = 0; M(3,0) = 0;
1480+ M(0,1) = 0; M(1,1) = S; M(2,1) = 0; M(3,1) = 0;
1481+ M(0,2) = 0; M(1,2) = 0; M(2,2) = S; M(3,2) = 0;
1482+ M(0,3) = offsetX; M(1,3) = offsetY; M(2,3) = offsetZ; M(3,3) = S;
1483+
1484+ #undef M
1485+ #undef S
1486+}
1487+
1488+void S3L_makeScaleMatrix(
1489+ S3L_Unit scaleX,
1490+ S3L_Unit scaleY,
1491+ S3L_Unit scaleZ,
1492+ S3L_Mat4 m)
1493+{
1494+ #define M(x,y) m[x][y]
1495+
1496+ M(0,0) = scaleX; M(1,0) = 0; M(2,0) = 0; M(3,0) = 0;
1497+ M(0,1) = 0; M(1,1) = scaleY; M(2,1) = 0; M(3,1) = 0;
1498+ M(0,2) = 0; M(1,2) = 0; M(2,2) = scaleZ; M(3,2) = 0;
1499+ M(0,3) = 0; M(1,3) = 0; M(2,3) = 0; M(3,3) = S3L_F;
1500+
1501+ #undef M
1502+}
1503+
1504+void S3L_makeRotationMatrixZXY(
1505+ S3L_Unit byX,
1506+ S3L_Unit byY,
1507+ S3L_Unit byZ,
1508+ S3L_Mat4 m)
1509+{
1510+ byX *= -1;
1511+ byY *= -1;
1512+ byZ *= -1;
1513+
1514+ S3L_Unit sx = S3L_sin(byX);
1515+ S3L_Unit sy = S3L_sin(byY);
1516+ S3L_Unit sz = S3L_sin(byZ);
1517+
1518+ S3L_Unit cx = S3L_cos(byX);
1519+ S3L_Unit cy = S3L_cos(byY);
1520+ S3L_Unit cz = S3L_cos(byZ);
1521+
1522+ #define M(x,y) m[x][y]
1523+ #define S S3L_F
1524+
1525+ M(0,0) = (cy * cz) / S + (sy * sx * sz) / (S * S);
1526+ M(1,0) = (cx * sz) / S;
1527+ M(2,0) = (cy * sx * sz) / (S * S) - (cz * sy) / S;
1528+ M(3,0) = 0;
1529+
1530+ M(0,1) = (cz * sy * sx) / (S * S) - (cy * sz) / S;
1531+ M(1,1) = (cx * cz) / S;
1532+ M(2,1) = (cy * cz * sx) / (S * S) + (sy * sz) / S;
1533+ M(3,1) = 0;
1534+
1535+ M(0,2) = (cx * sy) / S;
1536+ M(1,2) = -1 * sx;
1537+ M(2,2) = (cy * cx) / S;
1538+ M(3,2) = 0;
1539+
1540+ M(0,3) = 0;
1541+ M(1,3) = 0;
1542+ M(2,3) = 0;
1543+ M(3,3) = S3L_F;
1544+
1545+ #undef M
1546+ #undef S
1547+}
1548+
1549+S3L_Unit S3L_sqrt(S3L_Unit value)
1550+{
1551+ int8_t sign = 1;
1552+
1553+ if (value < 0)
1554+ {
1555+ sign = -1;
1556+ value *= -1;
1557+ }
1558+
1559+ uint32_t result = 0;
1560+ uint32_t a = value;
1561+ uint32_t b = 1u << 30;
1562+
1563+ while (b > a)
1564+ b >>= 2;
1565+
1566+ while (b != 0)
1567+ {
1568+ if (a >= result + b)
1569+ {
1570+ a -= result + b;
1571+ result = result + 2 * b;
1572+ }
1573+
1574+ b >>= 2;
1575+ result >>= 1;
1576+ }
1577+
1578+ return result * sign;
1579+}
1580+
1581+S3L_Unit S3L_vec3Length(S3L_Vec4 v)
1582+{
1583+ return S3L_sqrt(v.x * v.x + v.y * v.y + v.z * v.z);
1584+}
1585+
1586+S3L_Unit S3L_vec2Length(S3L_Vec4 v)
1587+{
1588+ return S3L_sqrt(v.x * v.x + v.y * v.y);
1589+}
1590+
1591+void S3L_vec3Normalize(S3L_Vec4 *v)
1592+{
1593+ #define SCALE 16
1594+ #define BOTTOM_LIMIT 16
1595+ #define UPPER_LIMIT 900
1596+
1597+ /* Here we try to decide if the vector is too small and would cause
1598+ inaccurate result due to very its inaccurate length. If so, we scale
1599+ it up. We can't scale up everything as big vectors overflow in length
1600+ calculations. */
1601+
1602+ if (
1603+ S3L_abs(v->x) <= BOTTOM_LIMIT &&
1604+ S3L_abs(v->y) <= BOTTOM_LIMIT &&
1605+ S3L_abs(v->z) <= BOTTOM_LIMIT)
1606+ {
1607+ v->x *= SCALE;
1608+ v->y *= SCALE;
1609+ v->z *= SCALE;
1610+ }
1611+ else if (
1612+ S3L_abs(v->x) > UPPER_LIMIT ||
1613+ S3L_abs(v->y) > UPPER_LIMIT ||
1614+ S3L_abs(v->z) > UPPER_LIMIT)
1615+ {
1616+ v->x /= SCALE;
1617+ v->y /= SCALE;
1618+ v->z /= SCALE;
1619+ }
1620+
1621+ #undef SCALE
1622+ #undef BOTTOM_LIMIT
1623+ #undef UPPER_LIMIT
1624+
1625+ S3L_Unit l = S3L_vec3Length(*v);
1626+
1627+ if (l == 0)
1628+ return;
1629+
1630+ v->x = (v->x * S3L_F) / l;
1631+ v->y = (v->y * S3L_F) / l;
1632+ v->z = (v->z * S3L_F) / l;
1633+}
1634+
1635+void S3L_vec3NormalizeFast(S3L_Vec4 *v)
1636+{
1637+ S3L_Unit l = S3L_vec3Length(*v);
1638+
1639+ if (l == 0)
1640+ return;
1641+
1642+ v->x = (v->x * S3L_F) / l;
1643+ v->y = (v->y * S3L_F) / l;
1644+ v->z = (v->z * S3L_F) / l;
1645+}
1646+
1647+void S3L_transform3DInit(S3L_Transform3D *t)
1648+{
1649+ S3L_vec4Init(&(t->translation));
1650+ S3L_vec4Init(&(t->rotation));
1651+ t->scale.x = S3L_F;
1652+ t->scale.y = S3L_F;
1653+ t->scale.z = S3L_F;
1654+ t->scale.w = 0;
1655+}
1656+
1657+/** Performs perspecive division (z-divide). Does NOT check for division by
1658+ zero. */
1659+static inline void S3L_perspectiveDivide(S3L_Vec4 *vector,
1660+ S3L_Unit focalLength)
1661+{
1662+ if (focalLength == 0)
1663+ return;
1664+
1665+ vector->x = (vector->x * focalLength) / vector->z;
1666+ vector->y = (vector->y * focalLength) / vector->z;
1667+}
1668+
1669+void S3L_project3DPointToScreen(
1670+ S3L_Vec4 point,
1671+ S3L_Camera camera,
1672+ S3L_Vec4 *result)
1673+{
1674+ // TODO: hotfix to prevent a mapping bug probably to overlfows
1675+ S3L_Vec4 toPoint = point, camForw;
1676+
1677+ S3L_vec3Sub(&toPoint,camera.transform.translation);
1678+
1679+ S3L_vec3Normalize(&toPoint);
1680+
1681+ S3L_rotationToDirections(camera.transform.rotation,S3L_FRACTIONS_PER_UNIT,
1682+ &camForw,0,0);
1683+
1684+ if (S3L_vec3Dot(toPoint,camForw) < S3L_FRACTIONS_PER_UNIT / 6)
1685+ {
1686+ result->z = -1;
1687+ result->w = 0;
1688+ return;
1689+ }
1690+ // end of hotfix
1691+ S3L_Mat4 m;
1692+ S3L_makeCameraMatrix(camera.transform,m);
1693+
1694+ S3L_Unit s = point.w;
1695+
1696+ point.w = S3L_F;
1697+
1698+ S3L_vec3Xmat4(&point,m);
1699+
1700+ point.z = S3L_nonZero(point.z);
1701+
1702+ S3L_perspectiveDivide(&point,camera.focalLength);
1703+
1704+ S3L_ScreenCoord x, y;
1705+
1706+ S3L_mapProjectionPlaneToScreen(point,&x,&y);
1707+
1708+ result->x = x;
1709+ result->y = y;
1710+ result->z = point.z;
1711+
1712+ result->w =
1713+ (point.z <= 0) ? 0 :
1714+ (
1715+ camera.focalLength > 0 ?(
1716+ (s * camera.focalLength * S3L_RESOLUTION_X) /
1717+ (point.z * S3L_F)) :
1718+ ((camera.transform.scale.x * S3L_RESOLUTION_X) / S3L_F)
1719+ );
1720+}
1721+
1722+void S3L_lookAt(S3L_Vec4 pointTo, S3L_Transform3D *t)
1723+{
1724+ S3L_Vec4 v;
1725+
1726+ v.x = pointTo.x - t->translation.x;
1727+ v.y = pointTo.z - t->translation.z;
1728+
1729+ S3L_Unit dx = v.x;
1730+ S3L_Unit l = S3L_vec2Length(v);
1731+
1732+ dx = (v.x * S3L_F) / S3L_nonZero(l); // normalize
1733+
1734+ t->rotation.y = -1 * S3L_asin(dx);
1735+
1736+ if (v.y < 0)
1737+ t->rotation.y = S3L_F / 2 - t->rotation.y;
1738+
1739+ v.x = pointTo.y - t->translation.y;
1740+ v.y = l;
1741+
1742+ l = S3L_vec2Length(v);
1743+
1744+ dx = (v.x * S3L_F) / S3L_nonZero(l);
1745+
1746+ t->rotation.x = S3L_asin(dx);
1747+}
1748+
1749+void S3L_transform3DSet(
1750+ S3L_Unit tx,
1751+ S3L_Unit ty,
1752+ S3L_Unit tz,
1753+ S3L_Unit rx,
1754+ S3L_Unit ry,
1755+ S3L_Unit rz,
1756+ S3L_Unit sx,
1757+ S3L_Unit sy,
1758+ S3L_Unit sz,
1759+ S3L_Transform3D *t)
1760+{
1761+ t->translation.x = tx;
1762+ t->translation.y = ty;
1763+ t->translation.z = tz;
1764+
1765+ t->rotation.x = rx;
1766+ t->rotation.y = ry;
1767+ t->rotation.z = rz;
1768+
1769+ t->scale.x = sx;
1770+ t->scale.y = sy;
1771+ t->scale.z = sz;
1772+}
1773+
1774+void S3L_cameraInit(S3L_Camera *camera)
1775+{
1776+ camera->focalLength = S3L_F;
1777+ S3L_transform3DInit(&(camera->transform));
1778+}
1779+
1780+void S3L_rotationToDirections(
1781+ S3L_Vec4 rotation,
1782+ S3L_Unit length,
1783+ S3L_Vec4 *forw,
1784+ S3L_Vec4 *right,
1785+ S3L_Vec4 *up)
1786+{
1787+ S3L_Mat4 m;
1788+
1789+ S3L_makeRotationMatrixZXY(rotation.x,rotation.y,rotation.z,m);
1790+
1791+ if (forw != 0)
1792+ {
1793+ forw->x = 0;
1794+ forw->y = 0;
1795+ forw->z = length;
1796+ S3L_vec3Xmat4(forw,m);
1797+ }
1798+
1799+ if (right != 0)
1800+ {
1801+ right->x = length;
1802+ right->y = 0;
1803+ right->z = 0;
1804+ S3L_vec3Xmat4(right,m);
1805+ }
1806+
1807+ if (up != 0)
1808+ {
1809+ up->x = 0;
1810+ up->y = length;
1811+ up->z = 0;
1812+ S3L_vec3Xmat4(up,m);
1813+ }
1814+}
1815+
1816+void S3L_pixelInfoInit(S3L_PixelInfo *p)
1817+{
1818+ p->x = 0;
1819+ p->y = 0;
1820+ p->barycentric[0] = S3L_F;
1821+ p->barycentric[1] = 0;
1822+ p->barycentric[2] = 0;
1823+ p->modelIndex = 0;
1824+ p->triangleIndex = 0;
1825+ p->triangleID = 0;
1826+ p->depth = 0;
1827+ p->previousZ = 0;
1828+}
1829+
1830+void S3L_model3DInit(
1831+ const S3L_Unit *vertices,
1832+ S3L_Index vertexCount,
1833+ const S3L_Index *triangles,
1834+ S3L_Index triangleCount,
1835+ S3L_Model3D *model)
1836+{
1837+ model->vertices = vertices;
1838+ model->vertexCount = vertexCount;
1839+ model->triangles = triangles;
1840+ model->triangleCount = triangleCount;
1841+ model->customTransformMatrix = 0;
1842+
1843+ S3L_transform3DInit(&(model->transform));
1844+ S3L_drawConfigInit(&(model->config));
1845+}
1846+
1847+void S3L_sceneInit(
1848+ S3L_Model3D *models,
1849+ S3L_Index modelCount,
1850+ S3L_Scene *scene)
1851+{
1852+ scene->models = models;
1853+ scene->modelCount = modelCount;
1854+ S3L_cameraInit(&(scene->camera));
1855+}
1856+
1857+void S3L_drawConfigInit(S3L_DrawConfig *config)
1858+{
1859+ config->backfaceCulling = 2;
1860+ config->visible = 1;
1861+}
1862+
1863+#ifndef S3L_PIXEL_FUNCTION
1864+ #error Pixel rendering function (S3L_PIXEL_FUNCTION) not specified!
1865+#endif
1866+
1867+static inline void S3L_PIXEL_FUNCTION(S3L_PixelInfo *pixel); // forward decl
1868+
1869+/** Serves to accelerate linear interpolation for performance-critical
1870+ code. Functions such as S3L_interpolate require division to compute each
1871+ interpolated value, while S3L_FastLerpState only requires a division for
1872+ the initiation and a shift for retrieving each interpolated value.
1873+
1874+ S3L_FastLerpState stores a value and a step, both scaled (shifted by
1875+ S3L_FAST_LERP_QUALITY) to increase precision. The step is being added to the
1876+ value, which achieves the interpolation. This will only be useful for
1877+ interpolations in which we need to get the interpolated value in every step.
1878+
1879+ BEWARE! Shifting a negative value is undefined, so handling shifting of
1880+ negative values has to be done cleverly. */
1881+typedef struct
1882+{
1883+ S3L_Unit valueScaled;
1884+ S3L_Unit stepScaled;
1885+} S3L_FastLerpState;
1886+
1887+#define S3L_getFastLerpValue(state)\
1888+ (state.valueScaled >> S3L_FAST_LERP_QUALITY)
1889+
1890+#define S3L_stepFastLerp(state)\
1891+ state.valueScaled += state.stepScaled
1892+
1893+static inline S3L_Unit S3L_interpolateBarycentric(
1894+ S3L_Unit value0,
1895+ S3L_Unit value1,
1896+ S3L_Unit value2,
1897+ S3L_Unit barycentric[3])
1898+{
1899+ return
1900+ (
1901+ (value0 * barycentric[0]) +
1902+ (value1 * barycentric[1]) +
1903+ (value2 * barycentric[2])
1904+ ) / S3L_F;
1905+}
1906+
1907+void S3L_mapProjectionPlaneToScreen(
1908+ S3L_Vec4 point,
1909+ S3L_ScreenCoord *screenX,
1910+ S3L_ScreenCoord *screenY)
1911+{
1912+ *screenX =
1913+ S3L_HALF_RESOLUTION_X +
1914+ (point.x * S3L_HALF_RESOLUTION_X) / S3L_F;
1915+
1916+ *screenY =
1917+ S3L_HALF_RESOLUTION_Y -
1918+ (point.y * S3L_HALF_RESOLUTION_X) / S3L_F;
1919+}
1920+
1921+void S3L_zBufferClear(void)
1922+{
1923+#if S3L_Z_BUFFER
1924+ for (uint32_t i = 0; i < S3L_RESOLUTION_X * S3L_RESOLUTION_Y; ++i)
1925+ S3L_zBuffer[i] = S3L_MAX_DEPTH;
1926+#endif
1927+}
1928+
1929+void S3L_stencilBufferClear(void)
1930+{
1931+#if S3L_STENCIL_BUFFER
1932+ for (uint32_t i = 0; i < S3L_STENCIL_BUFFER_SIZE; ++i)
1933+ S3L_stencilBuffer[i] = 0;
1934+#endif
1935+}
1936+
1937+void S3L_newFrame(void)
1938+{
1939+ S3L_zBufferClear();
1940+ S3L_stencilBufferClear();
1941+}
1942+
1943+/* the following serves to communicate info about if the triangle has been split
1944+ and how the barycentrics should be remapped. */
1945+uint8_t _S3L_projectedTriangleState = 0; // 0 = normal, 1 = cut, 2 = split
1946+
1947+#if S3L_NEAR_CROSS_STRATEGY == 3
1948+S3L_Vec4 _S3L_triangleRemapBarycentrics[6];
1949+#endif
1950+
1951+void S3L_drawTriangle(
1952+ S3L_Vec4 point0,
1953+ S3L_Vec4 point1,
1954+ S3L_Vec4 point2,
1955+ S3L_Index modelIndex,
1956+ S3L_Index triangleIndex)
1957+{
1958+ S3L_PixelInfo p;
1959+ S3L_pixelInfoInit(&p);
1960+ p.modelIndex = modelIndex;
1961+ p.triangleIndex = triangleIndex;
1962+ p.triangleID = (modelIndex << 16) | triangleIndex;
1963+
1964+ S3L_Vec4 *tPointSS, *lPointSS, *rPointSS; /* points in Screen Space (in
1965+ S3L_Units, normalized by
1966+ S3L_F) */
1967+
1968+ S3L_Unit *barycentric0; // bar. coord that gets higher from L to R
1969+ S3L_Unit *barycentric1; // bar. coord that gets higher from R to L
1970+ S3L_Unit *barycentric2; // bar. coord that gets higher from bottom up
1971+
1972+ // sort the vertices:
1973+
1974+ #define assignPoints(t,a,b)\
1975+ {\
1976+ tPointSS = &point##t;\
1977+ barycentric2 = &(p.barycentric[t]);\
1978+ if (S3L_triangleWinding(point##t.x,point##t.y,point##a.x,point##a.y,\
1979+ point##b.x,point##b.y) >= 0)\
1980+ {\
1981+ lPointSS = &point##a; rPointSS = &point##b;\
1982+ barycentric0 = &(p.barycentric[b]);\
1983+ barycentric1 = &(p.barycentric[a]);\
1984+ }\
1985+ else\
1986+ {\
1987+ lPointSS = &point##b; rPointSS = &point##a;\
1988+ barycentric0 = &(p.barycentric[a]);\
1989+ barycentric1 = &(p.barycentric[b]);\
1990+ }\
1991+ }
1992+
1993+ if (point0.y <= point1.y)
1994+ {
1995+ if (point0.y <= point2.y)
1996+ assignPoints(0,1,2)
1997+ else
1998+ assignPoints(2,0,1)
1999+ }
2000+ else
2001+ {
2002+ if (point1.y <= point2.y)
2003+ assignPoints(1,0,2)
2004+ else
2005+ assignPoints(2,0,1)
2006+ }
2007+
2008+ #undef assignPoints
2009+
2010+#if S3L_FLAT
2011+ *barycentric0 = S3L_F / 3;
2012+ *barycentric1 = S3L_F / 3;
2013+ *barycentric2 = S3L_F - 2 * (S3L_F / 3);
2014+#endif
2015+
2016+ p.triangleSize[0] = rPointSS->x - lPointSS->x;
2017+ p.triangleSize[1] =
2018+ (rPointSS->y > lPointSS->y ? rPointSS->y : lPointSS->y) - tPointSS->y;
2019+
2020+ // now draw the triangle line by line:
2021+
2022+ S3L_ScreenCoord splitY; // Y of the vertically middle point of the triangle
2023+ S3L_ScreenCoord endY; // bottom Y of the whole triangle
2024+ int splitOnLeft; /* whether splitY is the y coord. of left or right
2025+ point */
2026+
2027+ if (rPointSS->y <= lPointSS->y)
2028+ {
2029+ splitY = rPointSS->y;
2030+ splitOnLeft = 0;
2031+ endY = lPointSS->y;
2032+ }
2033+ else
2034+ {
2035+ splitY = lPointSS->y;
2036+ splitOnLeft = 1;
2037+ endY = rPointSS->y;
2038+ }
2039+
2040+ S3L_ScreenCoord currentY = tPointSS->y;
2041+
2042+ /* We'll be using an algorithm similar to Bresenham line algorithm. The
2043+ specifics of this algorithm are among others:
2044+
2045+ - drawing possibly NON-CONTINUOUS line
2046+ - NOT tracing the line exactly, but rather rasterizing one the right
2047+ side of it, according to the pixel CENTERS, INCLUDING the pixel
2048+ centers
2049+
2050+ The principle is this:
2051+
2052+ - Move vertically by pixels and accumulate the error (abs(dx/dy)).
2053+ - If the error is greater than one (crossed the next pixel center), keep
2054+ moving horizontally and substracting 1 from the error until it is less
2055+ than 1 again.
2056+ - To make this INTEGER ONLY, scale the case so that distance between
2057+ pixels is equal to dy (instead of 1). This way the error becomes
2058+ dx/dy * dy == dx, and we're comparing the error to (and potentially
2059+ substracting) 1 * dy == dy. */
2060+
2061+ int16_t
2062+ /* triangle side:
2063+ left right */
2064+ lX, rX, // current x position on the screen
2065+ lDx, rDx, // dx (end point - start point)
2066+ lDy, rDy, // dy (end point - start point)
2067+ lInc, rInc, // direction in which to increment (1 or -1)
2068+ lErr, rErr, // current error (Bresenham)
2069+ lErrCmp, rErrCmp, // helper for deciding comparison (> vs >=)
2070+ lErrAdd, rErrAdd, // error value to add in each Bresenham cycle
2071+ lErrSub, rErrSub; // error value to substract when moving in x direction
2072+
2073+ S3L_FastLerpState lSideFLS, rSideFLS;
2074+
2075+#if S3L_COMPUTE_LERP_DEPTH
2076+ S3L_FastLerpState lDepthFLS, rDepthFLS;
2077+
2078+ #define initDepthFLS(s,p1,p2)\
2079+ s##DepthFLS.valueScaled = p1##PointSS->z << S3L_FAST_LERP_QUALITY;\
2080+ s##DepthFLS.stepScaled = ((p2##PointSS->z << S3L_FAST_LERP_QUALITY) -\
2081+ s##DepthFLS.valueScaled) / (s##Dy != 0 ? s##Dy : 1);
2082+#else
2083+ #define initDepthFLS(s,p1,p2) ;
2084+#endif
2085+
2086+ /* init side for the algorithm, params:
2087+ s - which side (l or r)
2088+ p1 - point from (t, l or r)
2089+ p2 - point to (t, l or r)
2090+ down - whether the side coordinate goes top-down or vice versa */
2091+ #define initSide(s,p1,p2,down)\
2092+ s##X = p1##PointSS->x;\
2093+ s##Dx = p2##PointSS->x - p1##PointSS->x;\
2094+ s##Dy = p2##PointSS->y - p1##PointSS->y;\
2095+ initDepthFLS(s,p1,p2)\
2096+ s##SideFLS.stepScaled = (S3L_F << S3L_FAST_LERP_QUALITY)\
2097+ / (s##Dy != 0 ? s##Dy : 1);\
2098+ s##SideFLS.valueScaled = 0;\
2099+ if (!down)\
2100+ {\
2101+ s##SideFLS.valueScaled =\
2102+ S3L_F << S3L_FAST_LERP_QUALITY;\
2103+ s##SideFLS.stepScaled *= -1;\
2104+ }\
2105+ s##Inc = s##Dx >= 0 ? 1 : -1;\
2106+ if (s##Dx < 0)\
2107+ {s##Err = 0; s##ErrCmp = 0;}\
2108+ else\
2109+ {s##Err = s##Dy; s##ErrCmp = 1;}\
2110+ s##ErrAdd = S3L_abs(s##Dx);\
2111+ s##ErrSub = s##Dy != 0 ? s##Dy : 1; /* don't allow 0, could lead to an
2112+ infinite substracting loop */
2113+
2114+ #define stepSide(s)\
2115+ while (s##Err - s##Dy >= s##ErrCmp)\
2116+ {\
2117+ s##X += s##Inc;\
2118+ s##Err -= s##ErrSub;\
2119+ }\
2120+ s##Err += s##ErrAdd;
2121+
2122+ initSide(r,t,r,1)
2123+ initSide(l,t,l,1)
2124+
2125+#if S3L_PERSPECTIVE_CORRECTION
2126+ /* PC is done by linearly interpolating reciprocals from which the corrected
2127+ velues can be computed. See
2128+ http://www.lysator.liu.se/~mikaelk/doc/perspectivetexture/ */
2129+
2130+ #if S3L_PERSPECTIVE_CORRECTION == 1
2131+ #define Z_RECIP_NUMERATOR\
2132+ (S3L_F * S3L_F * S3L_F)
2133+ #elif S3L_PERSPECTIVE_CORRECTION == 2
2134+ #define Z_RECIP_NUMERATOR\
2135+ (S3L_F * S3L_F)
2136+ #endif
2137+ /* ^ This numerator is a number by which we divide values for the
2138+ reciprocals. For PC == 2 it has to be lower because linear interpolation
2139+ scaling would make it overflow -- this results in lower depth precision
2140+ in bigger distance for PC == 2. */
2141+
2142+ S3L_Unit
2143+ tPointRecipZ, lPointRecipZ, rPointRecipZ, /* Reciprocals of the depth of
2144+ each triangle point. */
2145+ lRecip0, lRecip1, rRecip0, rRecip1; /* Helper variables for swapping
2146+ the above after split. */
2147+
2148+ tPointRecipZ = Z_RECIP_NUMERATOR / S3L_nonZero(tPointSS->z);
2149+ lPointRecipZ = Z_RECIP_NUMERATOR / S3L_nonZero(lPointSS->z);
2150+ rPointRecipZ = Z_RECIP_NUMERATOR / S3L_nonZero(rPointSS->z);
2151+
2152+ lRecip0 = tPointRecipZ;
2153+ lRecip1 = lPointRecipZ;
2154+ rRecip0 = tPointRecipZ;
2155+ rRecip1 = rPointRecipZ;
2156+
2157+ #define manageSplitPerspective(b0,b1)\
2158+ b1##Recip0 = b0##PointRecipZ;\
2159+ b1##Recip1 = b1##PointRecipZ;\
2160+ b0##Recip0 = b0##PointRecipZ;\
2161+ b0##Recip1 = tPointRecipZ;
2162+#else
2163+ #define manageSplitPerspective(b0,b1) ;
2164+#endif
2165+
2166+ // clip to the screen in y dimension:
2167+
2168+ endY = S3L_min(endY,S3L_RESOLUTION_Y);
2169+
2170+ /* Clipping above the screen (y < 0) can't be easily done here, will be
2171+ handled inside the loop. */
2172+
2173+ while (currentY < endY) /* draw the triangle from top to bottom -- the
2174+ bottom-most row is left out because, following
2175+ from the rasterization rules (see start of the
2176+ file), it is to never be rasterized. */
2177+ {
2178+ if (currentY == splitY) // reached a vertical split of the triangle?
2179+ {
2180+ #define manageSplit(b0,b1,s0,s1)\
2181+ S3L_Unit *tmp = barycentric##b0;\
2182+ barycentric##b0 = barycentric##b1;\
2183+ barycentric##b1 = tmp;\
2184+ s0##SideFLS.valueScaled = (S3L_F\
2185+ << S3L_FAST_LERP_QUALITY) - s0##SideFLS.valueScaled;\
2186+ s0##SideFLS.stepScaled *= -1;\
2187+ manageSplitPerspective(s0,s1)
2188+
2189+ if (splitOnLeft)
2190+ {
2191+ initSide(l,l,r,0);
2192+ manageSplit(0,2,r,l)
2193+ }
2194+ else
2195+ {
2196+ initSide(r,r,l,0);
2197+ manageSplit(1,2,l,r)
2198+ }
2199+ }
2200+
2201+ stepSide(r)
2202+ stepSide(l)
2203+
2204+ if (currentY >= 0) /* clipping of pixels whose y < 0 (can't be easily done
2205+ outside the loop because of the Bresenham-like
2206+ algorithm steps) */
2207+ {
2208+ p.y = currentY;
2209+
2210+ // draw the horizontal line
2211+
2212+#if !S3L_FLAT
2213+ S3L_Unit rowLength = S3L_nonZero(rX - lX - 1); // prevent zero div
2214+
2215+ #if S3L_PERSPECTIVE_CORRECTION
2216+ S3L_Unit lOverZ, lRecipZ, rOverZ, rRecipZ, lT, rT;
2217+
2218+ lT = S3L_getFastLerpValue(lSideFLS);
2219+ rT = S3L_getFastLerpValue(rSideFLS);
2220+
2221+ lOverZ = S3L_interpolateByUnitFrom0(lRecip1,lT);
2222+ lRecipZ = S3L_interpolateByUnit(lRecip0,lRecip1,lT);
2223+
2224+ rOverZ = S3L_interpolateByUnitFrom0(rRecip1,rT);
2225+ rRecipZ = S3L_interpolateByUnit(rRecip0,rRecip1,rT);
2226+ #else
2227+ S3L_FastLerpState b0FLS, b1FLS;
2228+
2229+ #if S3L_COMPUTE_LERP_DEPTH
2230+ S3L_FastLerpState depthFLS;
2231+
2232+ depthFLS.valueScaled = lDepthFLS.valueScaled;
2233+ depthFLS.stepScaled =
2234+ (rDepthFLS.valueScaled - lDepthFLS.valueScaled) / rowLength;
2235+ #endif
2236+
2237+ b0FLS.valueScaled = 0;
2238+ b1FLS.valueScaled = lSideFLS.valueScaled;
2239+
2240+ b0FLS.stepScaled = rSideFLS.valueScaled / rowLength;
2241+ b1FLS.stepScaled = -1 * lSideFLS.valueScaled / rowLength;
2242+ #endif
2243+#endif
2244+
2245+ // clip to the screen in x dimension:
2246+
2247+ S3L_ScreenCoord rXClipped = S3L_min(rX,S3L_RESOLUTION_X),
2248+ lXClipped = lX;
2249+
2250+ if (lXClipped < 0)
2251+ {
2252+ lXClipped = 0;
2253+
2254+#if !S3L_PERSPECTIVE_CORRECTION && !S3L_FLAT
2255+ b0FLS.valueScaled -= lX * b0FLS.stepScaled;
2256+ b1FLS.valueScaled -= lX * b1FLS.stepScaled;
2257+
2258+ #if S3L_COMPUTE_LERP_DEPTH
2259+ depthFLS.valueScaled -= lX * depthFLS.stepScaled;
2260+ #endif
2261+#endif
2262+ }
2263+
2264+#if S3L_PERSPECTIVE_CORRECTION
2265+ S3L_ScreenCoord i = lXClipped - lX; /* helper var to save one
2266+ substraction in the inner
2267+ loop */
2268+#endif
2269+
2270+#if S3L_PERSPECTIVE_CORRECTION == 2
2271+ S3L_FastLerpState
2272+ depthPC, // interpolates depth between row segments
2273+ b0PC, // interpolates barycentric0 between row segments
2274+ b1PC; // interpolates barycentric1 between row segments
2275+
2276+ /* ^ These interpolate values between row segments (lines of pixels
2277+ of S3L_PC_APPROX_LENGTH length). After each row segment perspective
2278+ correction is recomputed. */
2279+
2280+ depthPC.valueScaled =
2281+ (Z_RECIP_NUMERATOR /
2282+ S3L_nonZero(S3L_interpolate(lRecipZ,rRecipZ,i,rowLength)))
2283+ << S3L_FAST_LERP_QUALITY;
2284+
2285+ b0PC.valueScaled =
2286+ (
2287+ S3L_interpolateFrom0(rOverZ,i,rowLength)
2288+ * depthPC.valueScaled
2289+ ) / (Z_RECIP_NUMERATOR / S3L_F);
2290+
2291+ b1PC.valueScaled =
2292+ (
2293+ (lOverZ - S3L_interpolateFrom0(lOverZ,i,rowLength))
2294+ * depthPC.valueScaled
2295+ ) / (Z_RECIP_NUMERATOR / S3L_F);
2296+
2297+ int8_t rowCount = S3L_PC_APPROX_LENGTH;
2298+#endif
2299+
2300+#if S3L_Z_BUFFER
2301+ uint32_t zBufferIndex = p.y * S3L_RESOLUTION_X + lXClipped;
2302+#endif
2303+
2304+ // draw the row -- inner loop:
2305+ for (S3L_ScreenCoord x = lXClipped; x < rXClipped; ++x)
2306+ {
2307+ int8_t testsPassed = 1;
2308+
2309+#if S3L_STENCIL_BUFFER
2310+ if (!S3L_stencilTest(x,p.y))
2311+ testsPassed = 0;
2312+#endif
2313+ p.x = x;
2314+
2315+#if S3L_COMPUTE_DEPTH
2316+ #if S3L_PERSPECTIVE_CORRECTION == 1
2317+ p.depth = Z_RECIP_NUMERATOR /
2318+ S3L_nonZero(S3L_interpolate(lRecipZ,rRecipZ,i,rowLength));
2319+ #elif S3L_PERSPECTIVE_CORRECTION == 2
2320+ if (rowCount >= S3L_PC_APPROX_LENGTH)
2321+ {
2322+ // init the linear interpolation to the next PC correct value
2323+
2324+ rowCount = 0;
2325+
2326+ S3L_Unit nextI = i + S3L_PC_APPROX_LENGTH;
2327+
2328+ if (nextI < rowLength)
2329+ {
2330+ S3L_Unit nextDepthScaled =
2331+ (
2332+ Z_RECIP_NUMERATOR /
2333+ S3L_nonZero(S3L_interpolate(lRecipZ,rRecipZ,nextI,rowLength))
2334+ ) << S3L_FAST_LERP_QUALITY;
2335+
2336+ depthPC.stepScaled =
2337+ (nextDepthScaled - depthPC.valueScaled) / S3L_PC_APPROX_LENGTH;
2338+
2339+ S3L_Unit nextValue =
2340+ (
2341+ S3L_interpolateFrom0(rOverZ,nextI,rowLength)
2342+ * nextDepthScaled
2343+ ) / (Z_RECIP_NUMERATOR / S3L_F);
2344+
2345+ b0PC.stepScaled =
2346+ (nextValue - b0PC.valueScaled) / S3L_PC_APPROX_LENGTH;
2347+
2348+ nextValue =
2349+ (
2350+ (lOverZ - S3L_interpolateFrom0(lOverZ,nextI,rowLength))
2351+ * nextDepthScaled
2352+ ) / (Z_RECIP_NUMERATOR / S3L_F);
2353+
2354+ b1PC.stepScaled =
2355+ (nextValue - b1PC.valueScaled) / S3L_PC_APPROX_LENGTH;
2356+ }
2357+ else
2358+ {
2359+ /* A special case where we'd be interpolating outside the triangle.
2360+ It seems like a valid approach at first, but it creates a bug
2361+ in a case when the rasaterized triangle is near screen 0 and can
2362+ actually never reach the extrapolated screen position. So we
2363+ have to clamp to the actual end of the triangle here. */
2364+
2365+ S3L_Unit maxI = S3L_nonZero(rowLength - i);
2366+
2367+ S3L_Unit nextDepthScaled =
2368+ (
2369+ Z_RECIP_NUMERATOR /
2370+ S3L_nonZero(rRecipZ)
2371+ ) << S3L_FAST_LERP_QUALITY;
2372+
2373+ depthPC.stepScaled =
2374+ (nextDepthScaled - depthPC.valueScaled) / maxI;
2375+
2376+ S3L_Unit nextValue =
2377+ (
2378+ rOverZ
2379+ * nextDepthScaled
2380+ ) / (Z_RECIP_NUMERATOR / S3L_F);
2381+
2382+ b0PC.stepScaled =
2383+ (nextValue - b0PC.valueScaled) / maxI;
2384+
2385+ b1PC.stepScaled =
2386+ -1 * b1PC.valueScaled / maxI;
2387+ }
2388+ }
2389+
2390+ p.depth = S3L_getFastLerpValue(depthPC);
2391+ #else
2392+ p.depth = S3L_getFastLerpValue(depthFLS);
2393+ S3L_stepFastLerp(depthFLS);
2394+ #endif
2395+#else // !S3L_COMPUTE_DEPTH
2396+ p.depth = (tPointSS->z + lPointSS->z + rPointSS->z) / 3;
2397+#endif
2398+
2399+#if S3L_Z_BUFFER
2400+ p.previousZ = S3L_zBuffer[zBufferIndex];
2401+
2402+ zBufferIndex++;
2403+
2404+ if (!S3L_zTest(p.x,p.y,p.depth))
2405+ testsPassed = 0;
2406+#endif
2407+
2408+ if (testsPassed)
2409+ {
2410+#if !S3L_FLAT
2411+ #if S3L_PERSPECTIVE_CORRECTION == 0
2412+ *barycentric0 = S3L_getFastLerpValue(b0FLS);
2413+ *barycentric1 = S3L_getFastLerpValue(b1FLS);
2414+ #elif S3L_PERSPECTIVE_CORRECTION == 1
2415+ *barycentric0 =
2416+ (
2417+ S3L_interpolateFrom0(rOverZ,i,rowLength)
2418+ * p.depth
2419+ ) / (Z_RECIP_NUMERATOR / S3L_F);
2420+
2421+ *barycentric1 =
2422+ (
2423+ (lOverZ - S3L_interpolateFrom0(lOverZ,i,rowLength))
2424+ * p.depth
2425+ ) / (Z_RECIP_NUMERATOR / S3L_F);
2426+ #elif S3L_PERSPECTIVE_CORRECTION == 2
2427+ *barycentric0 = S3L_getFastLerpValue(b0PC);
2428+ *barycentric1 = S3L_getFastLerpValue(b1PC);
2429+ #endif
2430+
2431+ *barycentric2 =
2432+ S3L_F - *barycentric0 - *barycentric1;
2433+#endif
2434+
2435+#if S3L_NEAR_CROSS_STRATEGY == 3
2436+ if (_S3L_projectedTriangleState != 0)
2437+ {
2438+ S3L_Unit newBarycentric[3];
2439+
2440+ newBarycentric[0] = S3L_interpolateBarycentric(
2441+ _S3L_triangleRemapBarycentrics[0].x,
2442+ _S3L_triangleRemapBarycentrics[1].x,
2443+ _S3L_triangleRemapBarycentrics[2].x,
2444+ p.barycentric);
2445+
2446+ newBarycentric[1] = S3L_interpolateBarycentric(
2447+ _S3L_triangleRemapBarycentrics[0].y,
2448+ _S3L_triangleRemapBarycentrics[1].y,
2449+ _S3L_triangleRemapBarycentrics[2].y,
2450+ p.barycentric);
2451+
2452+ newBarycentric[2] = S3L_interpolateBarycentric(
2453+ _S3L_triangleRemapBarycentrics[0].z,
2454+ _S3L_triangleRemapBarycentrics[1].z,
2455+ _S3L_triangleRemapBarycentrics[2].z,
2456+ p.barycentric);
2457+
2458+ p.barycentric[0] = newBarycentric[0];
2459+ p.barycentric[1] = newBarycentric[1];
2460+ p.barycentric[2] = newBarycentric[2];
2461+ }
2462+#endif
2463+ S3L_PIXEL_FUNCTION(&p);
2464+ } // tests passed
2465+
2466+#if !S3L_FLAT
2467+ #if S3L_PERSPECTIVE_CORRECTION
2468+ i++;
2469+ #if S3L_PERSPECTIVE_CORRECTION == 2
2470+ rowCount++;
2471+
2472+ S3L_stepFastLerp(depthPC);
2473+ S3L_stepFastLerp(b0PC);
2474+ S3L_stepFastLerp(b1PC);
2475+ #endif
2476+ #else
2477+ S3L_stepFastLerp(b0FLS);
2478+ S3L_stepFastLerp(b1FLS);
2479+ #endif
2480+#endif
2481+ } // inner loop
2482+ } // y clipping
2483+
2484+#if !S3L_FLAT
2485+ S3L_stepFastLerp(lSideFLS);
2486+ S3L_stepFastLerp(rSideFLS);
2487+
2488+ #if S3L_COMPUTE_LERP_DEPTH
2489+ S3L_stepFastLerp(lDepthFLS);
2490+ S3L_stepFastLerp(rDepthFLS);
2491+ #endif
2492+#endif
2493+
2494+ ++currentY;
2495+ } // row drawing
2496+
2497+ #undef manageSplit
2498+ #undef initPC
2499+ #undef initSide
2500+ #undef stepSide
2501+ #undef Z_RECIP_NUMERATOR
2502+}
2503+
2504+void S3L_rotate2DPoint(S3L_Unit *x, S3L_Unit *y, S3L_Unit angle)
2505+{
2506+ if (angle < S3L_SIN_TABLE_UNIT_STEP)
2507+ return; // no visible rotation
2508+
2509+ S3L_Unit angleSin = S3L_sin(angle);
2510+ S3L_Unit angleCos = S3L_cos(angle);
2511+
2512+ S3L_Unit xBackup = *x;
2513+
2514+ *x =
2515+ (angleCos * (*x)) / S3L_F -
2516+ (angleSin * (*y)) / S3L_F;
2517+
2518+ *y =
2519+ (angleSin * xBackup) / S3L_F +
2520+ (angleCos * (*y)) / S3L_F;
2521+}
2522+
2523+void S3L_makeWorldMatrix(S3L_Transform3D worldTransform, S3L_Mat4 m)
2524+{
2525+ S3L_makeScaleMatrix(
2526+ worldTransform.scale.x,
2527+ worldTransform.scale.y,
2528+ worldTransform.scale.z,
2529+ m);
2530+
2531+ S3L_Mat4 t;
2532+
2533+ S3L_makeRotationMatrixZXY(
2534+ worldTransform.rotation.x,
2535+ worldTransform.rotation.y,
2536+ worldTransform.rotation.z,
2537+ t);
2538+
2539+ S3L_mat4Xmat4(m,t);
2540+
2541+ S3L_makeTranslationMat(
2542+ worldTransform.translation.x,
2543+ worldTransform.translation.y,
2544+ worldTransform.translation.z,
2545+ t);
2546+
2547+ S3L_mat4Xmat4(m,t);
2548+}
2549+
2550+void S3L_mat4Transpose(S3L_Mat4 m)
2551+{
2552+ S3L_Unit tmp;
2553+
2554+ for (uint8_t y = 0; y < 3; ++y)
2555+ for (uint8_t x = 1 + y; x < 4; ++x)
2556+ {
2557+ tmp = m[x][y];
2558+ m[x][y] = m[y][x];
2559+ m[y][x] = tmp;
2560+ }
2561+}
2562+
2563+void S3L_makeCameraMatrix(S3L_Transform3D cameraTransform, S3L_Mat4 m)
2564+{
2565+ S3L_makeTranslationMat(
2566+ -1 * cameraTransform.translation.x,
2567+ -1 * cameraTransform.translation.y,
2568+ -1 * cameraTransform.translation.z,
2569+ m);
2570+
2571+ S3L_Mat4 r;
2572+
2573+ S3L_makeRotationMatrixZXY(
2574+ cameraTransform.rotation.x,
2575+ cameraTransform.rotation.y,
2576+ cameraTransform.rotation.z,
2577+ r);
2578+
2579+ S3L_mat4Transpose(r); // transposing creates an inverse transform
2580+
2581+ S3L_Mat4 s;
2582+
2583+ S3L_makeScaleMatrix(
2584+ cameraTransform.scale.x,
2585+ cameraTransform.scale.y,
2586+ cameraTransform.scale.z,s);
2587+
2588+ S3L_mat4Xmat4(m,r);
2589+ S3L_mat4Xmat4(m,s);
2590+}
2591+
2592+int8_t S3L_triangleWinding(
2593+ S3L_ScreenCoord x0,
2594+ S3L_ScreenCoord y0,
2595+ S3L_ScreenCoord x1,
2596+ S3L_ScreenCoord y1,
2597+ S3L_ScreenCoord x2,
2598+ S3L_ScreenCoord y2)
2599+{
2600+ int32_t winding =
2601+ (y1 - y0) * (x2 - x1) - (x1 - x0) * (y2 - y1);
2602+ // ^ cross product for points with z == 0
2603+
2604+ return winding > 0 ? 1 : (winding < 0 ? -1 : 0);
2605+}
2606+
2607+/** Checks if given triangle (in Screen Space) is at least partially visible,
2608+ i.e. returns false if the triangle is either completely outside the frustum
2609+ (left, right, top, bottom, near) or is invisible due to backface culling. */
2610+static inline int8_t S3L_triangleIsVisible(
2611+ S3L_Vec4 p0,
2612+ S3L_Vec4 p1,
2613+ S3L_Vec4 p2,
2614+ uint8_t backfaceCulling)
2615+{
2616+ #define clipTest(c,cmp,v)\
2617+ (p0.c cmp (v) && p1.c cmp (v) && p2.c cmp (v))
2618+
2619+ if ( // outside frustum?
2620+#if S3L_NEAR_CROSS_STRATEGY == 0
2621+ p0.z <= S3L_NEAR || p1.z <= S3L_NEAR || p2.z <= S3L_NEAR ||
2622+ // ^ partially in front of NEAR?
2623+#else
2624+ clipTest(z,<=,S3L_NEAR) || // completely in front of NEAR?
2625+#endif
2626+ clipTest(x,<,0) ||
2627+ clipTest(x,>=,S3L_RESOLUTION_X) ||
2628+ clipTest(y,<,0) ||
2629+ clipTest(y,>,S3L_RESOLUTION_Y)
2630+ )
2631+ return 0;
2632+
2633+ #undef clipTest
2634+
2635+ if (backfaceCulling != 0)
2636+ {
2637+ int8_t winding =
2638+ S3L_triangleWinding(p0.x,p0.y,p1.x,p1.y,p2.x,p2.y);
2639+
2640+ if ((backfaceCulling == 1 && winding > 0) ||
2641+ (backfaceCulling == 2 && winding < 0))
2642+ return 0;
2643+ }
2644+
2645+ return 1;
2646+}
2647+
2648+#if S3L_SORT != 0
2649+typedef struct
2650+{
2651+ uint8_t modelIndex;
2652+ S3L_Index triangleIndex;
2653+ uint16_t sortValue;
2654+} _S3L_TriangleToSort;
2655+
2656+_S3L_TriangleToSort S3L_sortArray[S3L_MAX_TRIANGES_DRAWN];
2657+uint16_t S3L_sortArrayLength;
2658+#endif
2659+
2660+void _S3L_projectVertex(const S3L_Model3D *model, S3L_Index triangleIndex,
2661+ uint8_t vertex, S3L_Mat4 projectionMatrix, S3L_Vec4 *result)
2662+{
2663+ uint32_t vertexIndex = model->triangles[triangleIndex * 3 + vertex] * 3;
2664+
2665+ result->x = model->vertices[vertexIndex];
2666+ result->y = model->vertices[vertexIndex + 1];
2667+ result->z = model->vertices[vertexIndex + 2];
2668+ result->w = S3L_F; // needed for translation
2669+
2670+ S3L_vec3Xmat4(result,projectionMatrix);
2671+
2672+ result->w = result->z;
2673+ /* We'll keep the non-clamped z in w for sorting. */
2674+}
2675+
2676+void _S3L_mapProjectedVertexToScreen(S3L_Vec4 *vertex, S3L_Unit focalLength)
2677+{
2678+ vertex->z = vertex->z >= S3L_NEAR ? vertex->z : S3L_NEAR;
2679+ /* ^ This firstly prevents zero division in the follwoing z-divide and
2680+ secondly "pushes" vertices that are in front of near a little bit forward,
2681+ which makes them behave a bit better. If all three vertices end up exactly
2682+ on NEAR, the triangle will be culled. */
2683+
2684+ S3L_perspectiveDivide(vertex,focalLength);
2685+
2686+ S3L_ScreenCoord sX, sY;
2687+
2688+ S3L_mapProjectionPlaneToScreen(*vertex,&sX,&sY);
2689+
2690+ vertex->x = sX;
2691+ vertex->y = sY;
2692+}
2693+
2694+/** Projects a triangle to the screen. If enabled, a triangle can be potentially
2695+ subdivided into two if it crosses the near plane, in which case two projected
2696+ triangles are returned (the info about splitting or cutting the triangle is
2697+ passed in global variables, see above). */
2698+void _S3L_projectTriangle(
2699+ const S3L_Model3D *model,
2700+ S3L_Index triangleIndex,
2701+ S3L_Mat4 matrix,
2702+ uint32_t focalLength,
2703+ S3L_Vec4 transformed[6])
2704+{
2705+ _S3L_projectVertex(model,triangleIndex,0,matrix,&(transformed[0]));
2706+ _S3L_projectVertex(model,triangleIndex,1,matrix,&(transformed[1]));
2707+ _S3L_projectVertex(model,triangleIndex,2,matrix,&(transformed[2]));
2708+ _S3L_projectedTriangleState = 0;
2709+
2710+#if S3L_NEAR_CROSS_STRATEGY == 2 || S3L_NEAR_CROSS_STRATEGY == 3
2711+ uint8_t infront = 0;
2712+ uint8_t behind = 0;
2713+ uint8_t infrontI[3];
2714+ uint8_t behindI[3];
2715+
2716+ for (uint8_t i = 0; i < 3; ++i)
2717+ if (transformed[i].z < S3L_NEAR)
2718+ {
2719+ infrontI[infront] = i;
2720+ infront++;
2721+ }
2722+ else
2723+ {
2724+ behindI[behind] = i;
2725+ behind++;
2726+ }
2727+
2728+#if S3L_NEAR_CROSS_STRATEGY == 3
2729+ for (int i = 0; i < 3; ++i)
2730+ S3L_vec4Init(&(_S3L_triangleRemapBarycentrics[i]));
2731+
2732+ _S3L_triangleRemapBarycentrics[0].x = S3L_F;
2733+ _S3L_triangleRemapBarycentrics[1].y = S3L_F;
2734+ _S3L_triangleRemapBarycentrics[2].z = S3L_F;
2735+#endif
2736+
2737+#define interpolateVertex \
2738+ S3L_Unit ratio =\
2739+ ((transformed[be].z - S3L_NEAR) * S3L_F) /\
2740+ (transformed[be].z - transformed[in].z);\
2741+ transformed[in].x = transformed[be].x - \
2742+ ((transformed[be].x - transformed[in].x) * ratio) /\
2743+ S3L_F;\
2744+ transformed[in].y = transformed[be].y -\
2745+ ((transformed[be].y - transformed[in].y) * ratio) /\
2746+ S3L_F;\
2747+ transformed[in].z = S3L_NEAR;\
2748+ if (beI != 0) {\
2749+ beI->x = (beI->x * ratio) / S3L_F;\
2750+ beI->y = (beI->y * ratio) / S3L_F;\
2751+ beI->z = (beI->z * ratio) / S3L_F;\
2752+ ratio = S3L_F - ratio;\
2753+ beI->x += (beB->x * ratio) / S3L_F;\
2754+ beI->y += (beB->y * ratio) / S3L_F;\
2755+ beI->z += (beB->z * ratio) / S3L_F; }
2756+
2757+ if (infront == 2)
2758+ {
2759+ // shift the two vertices forward along the edge
2760+ for (uint8_t i = 0; i < 2; ++i)
2761+ {
2762+ uint8_t be = behindI[0], in = infrontI[i];
2763+
2764+#if S3L_NEAR_CROSS_STRATEGY == 3
2765+ S3L_Vec4 *beI = &(_S3L_triangleRemapBarycentrics[in]),
2766+ *beB = &(_S3L_triangleRemapBarycentrics[be]);
2767+#else
2768+ S3L_Vec4 *beI = 0, *beB = 0;
2769+#endif
2770+
2771+ interpolateVertex
2772+
2773+ _S3L_projectedTriangleState = 1;
2774+ }
2775+ }
2776+ else if (infront == 1)
2777+ {
2778+ // create another triangle and do the shifts
2779+ transformed[3] = transformed[behindI[1]];
2780+ transformed[4] = transformed[infrontI[0]];
2781+ transformed[5] = transformed[infrontI[0]];
2782+
2783+#if S3L_NEAR_CROSS_STRATEGY == 3
2784+ _S3L_triangleRemapBarycentrics[3] =
2785+ _S3L_triangleRemapBarycentrics[behindI[1]];
2786+ _S3L_triangleRemapBarycentrics[4] =
2787+ _S3L_triangleRemapBarycentrics[infrontI[0]];
2788+ _S3L_triangleRemapBarycentrics[5] =
2789+ _S3L_triangleRemapBarycentrics[infrontI[0]];
2790+#endif
2791+
2792+ for (uint8_t i = 0; i < 2; ++i)
2793+ {
2794+ uint8_t be = behindI[i], in = i + 4;
2795+
2796+#if S3L_NEAR_CROSS_STRATEGY == 3
2797+ S3L_Vec4 *beI = &(_S3L_triangleRemapBarycentrics[in]),
2798+ *beB = &(_S3L_triangleRemapBarycentrics[be]);
2799+#else
2800+ S3L_Vec4 *beI = 0, *beB = 0;
2801+#endif
2802+
2803+ interpolateVertex
2804+ }
2805+
2806+#if S3L_NEAR_CROSS_STRATEGY == 3
2807+ _S3L_triangleRemapBarycentrics[infrontI[0]] =
2808+ _S3L_triangleRemapBarycentrics[4];
2809+#endif
2810+
2811+ transformed[infrontI[0]] = transformed[4];
2812+
2813+ _S3L_mapProjectedVertexToScreen(&transformed[3],focalLength);
2814+ _S3L_mapProjectedVertexToScreen(&transformed[4],focalLength);
2815+ _S3L_mapProjectedVertexToScreen(&transformed[5],focalLength);
2816+
2817+ _S3L_projectedTriangleState = 2;
2818+ }
2819+
2820+#undef interpolateVertex
2821+#endif // S3L_NEAR_CROSS_STRATEGY == 2
2822+
2823+ _S3L_mapProjectedVertexToScreen(&transformed[0],focalLength);
2824+ _S3L_mapProjectedVertexToScreen(&transformed[1],focalLength);
2825+ _S3L_mapProjectedVertexToScreen(&transformed[2],focalLength);
2826+}
2827+
2828+void S3L_drawScene(S3L_Scene scene)
2829+{
2830+ S3L_Mat4 matFinal, matCamera;
2831+ S3L_Vec4 transformed[6]; // transformed triangle coords, for 2 triangles
2832+
2833+ const S3L_Model3D *model;
2834+ S3L_Index modelIndex, triangleIndex;
2835+
2836+ S3L_makeCameraMatrix(scene.camera.transform,matCamera);
2837+
2838+#if S3L_SORT != 0
2839+ uint16_t previousModel = 0;
2840+ S3L_sortArrayLength = 0;
2841+#endif
2842+
2843+ for (modelIndex = 0; modelIndex < scene.modelCount; ++modelIndex)
2844+ {
2845+ if (!scene.models[modelIndex].config.visible)
2846+ continue;
2847+
2848+#if S3L_SORT != 0
2849+ if (S3L_sortArrayLength >= S3L_MAX_TRIANGES_DRAWN)
2850+ break;
2851+
2852+ previousModel = modelIndex;
2853+#endif
2854+
2855+ if (scene.models[modelIndex].customTransformMatrix == 0)
2856+ S3L_makeWorldMatrix(scene.models[modelIndex].transform,matFinal);
2857+ else
2858+ {
2859+ S3L_Mat4 *m = scene.models[modelIndex].customTransformMatrix;
2860+
2861+ for (int8_t j = 0; j < 4; ++j)
2862+ for (int8_t i = 0; i < 4; ++i)
2863+ matFinal[i][j] = (*m)[i][j];
2864+ }
2865+
2866+ S3L_mat4Xmat4(matFinal,matCamera);
2867+
2868+ S3L_Index triangleCount = scene.models[modelIndex].triangleCount;
2869+
2870+ triangleIndex = 0;
2871+
2872+ model = &(scene.models[modelIndex]);
2873+
2874+ while (triangleIndex < triangleCount)
2875+ {
2876+ /* Some kind of cache could be used in theory to not project perviously
2877+ already projected vertices, but after some testing this was abandoned,
2878+ no gain was seen. */
2879+
2880+ _S3L_projectTriangle(model,triangleIndex,matFinal,
2881+ scene.camera.focalLength,transformed);
2882+
2883+ if (S3L_triangleIsVisible(transformed[0],transformed[1],transformed[2],
2884+ model->config.backfaceCulling))
2885+ {
2886+#if S3L_SORT == 0
2887+ // without sorting draw right away
2888+ S3L_drawTriangle(transformed[0],transformed[1],transformed[2],modelIndex,
2889+ triangleIndex);
2890+
2891+ if (_S3L_projectedTriangleState == 2) // draw potential subtriangle
2892+ {
2893+#if S3L_NEAR_CROSS_STRATEGY == 3
2894+ _S3L_triangleRemapBarycentrics[0] = _S3L_triangleRemapBarycentrics[3];
2895+ _S3L_triangleRemapBarycentrics[1] = _S3L_triangleRemapBarycentrics[4];
2896+ _S3L_triangleRemapBarycentrics[2] = _S3L_triangleRemapBarycentrics[5];
2897+#endif
2898+
2899+ S3L_drawTriangle(transformed[3],transformed[4],transformed[5],
2900+ modelIndex, triangleIndex);
2901+ }
2902+#else
2903+
2904+ if (S3L_sortArrayLength >= S3L_MAX_TRIANGES_DRAWN)
2905+ break;
2906+
2907+ // with sorting add to a sort list
2908+ S3L_sortArray[S3L_sortArrayLength].modelIndex = modelIndex;
2909+ S3L_sortArray[S3L_sortArrayLength].triangleIndex = triangleIndex;
2910+ S3L_sortArray[S3L_sortArrayLength].sortValue = S3L_zeroClamp(
2911+ transformed[0].w + transformed[1].w + transformed[2].w) >> 2;
2912+ /* ^
2913+ The w component here stores non-clamped z.
2914+
2915+ As a simple approximation we sort by the triangle center point,
2916+ which is a mean coordinate -- we don't actually have to divide by 3
2917+ (or anything), that is unnecessary for sorting! We shift by 2 just
2918+ as a fast operation to prevent overflow of the sum over uint_16t. */
2919+
2920+ S3L_sortArrayLength++;
2921+#endif
2922+ }
2923+
2924+ triangleIndex++;
2925+ }
2926+ }
2927+
2928+#if S3L_SORT != 0
2929+
2930+ #if S3L_SORT == 1
2931+ #define cmp <
2932+ #else
2933+ #define cmp >
2934+ #endif
2935+
2936+ /* Sort the triangles. We use insertion sort, because it has many advantages,
2937+ especially for smaller arrays (better than bubble sort, in-place, stable,
2938+ simple, ...). */
2939+
2940+ for (int16_t i = 1; i < S3L_sortArrayLength; ++i)
2941+ {
2942+ _S3L_TriangleToSort tmp = S3L_sortArray[i];
2943+
2944+ int16_t j = i - 1;
2945+
2946+ while (j >= 0 && S3L_sortArray[j].sortValue cmp tmp.sortValue)
2947+ {
2948+ S3L_sortArray[j + 1] = S3L_sortArray[j];
2949+ j--;
2950+ }
2951+
2952+ S3L_sortArray[j + 1] = tmp;
2953+ }
2954+
2955+ #undef cmp
2956+
2957+ for (S3L_Index i = 0; i < S3L_sortArrayLength; ++i) // draw sorted triangles
2958+ {
2959+ modelIndex = S3L_sortArray[i].modelIndex;
2960+ triangleIndex = S3L_sortArray[i].triangleIndex;
2961+
2962+ model = &(scene.models[modelIndex]);
2963+
2964+ if (modelIndex != previousModel)
2965+ {
2966+ // only recompute the matrix when the model has changed
2967+ S3L_makeWorldMatrix(model->transform,matFinal);
2968+ S3L_mat4Xmat4(matFinal,matCamera);
2969+ previousModel = modelIndex;
2970+ }
2971+
2972+ /* Here we project the points again, which is redundant and slow as they've
2973+ already been projected above, but saving the projected points would
2974+ require a lot of memory, which for small resolutions could be even
2975+ worse than z-bufer. So this seems to be the best way memory-wise. */
2976+
2977+ _S3L_projectTriangle(model,triangleIndex,matFinal,scene.camera.focalLength,
2978+ transformed);
2979+
2980+ S3L_drawTriangle(transformed[0],transformed[1],transformed[2],modelIndex,
2981+ triangleIndex);
2982+
2983+ if (_S3L_projectedTriangleState == 2)
2984+ {
2985+#if S3L_NEAR_CROSS_STRATEGY == 3
2986+ _S3L_triangleRemapBarycentrics[0] = _S3L_triangleRemapBarycentrics[3];
2987+ _S3L_triangleRemapBarycentrics[1] = _S3L_triangleRemapBarycentrics[4];
2988+ _S3L_triangleRemapBarycentrics[2] = _S3L_triangleRemapBarycentrics[5];
2989+#endif
2990+
2991+ S3L_drawTriangle(transformed[3],transformed[4],transformed[5],
2992+ modelIndex, triangleIndex);
2993+ }
2994+ }
2995+#endif
2996+}
2997+
2998+#endif // guard
2999+