commit 7485052cf5412b55b91f2fde562b46cad727f7a3
parent 4c129482be98fe54516eff50bb117872ef96a033
Author: root <root@lunarcry>
Date: Mon, 21 Oct 2024 19:17:18 +0000
SR:
Moved it to main src/ folder and integrate it into the gpu module.
Still a little hard to understand how to correctly draw the triangles and
if it's possible with this implementation of the software rasterizer.. we'll see
Diffstat:
10 files changed, 488 insertions(+), 144 deletions(-)
diff --git a/build.sh b/build.sh
@@ -1 +1,2 @@
-cc -Wall -Wpedantic -Wall -pedantic -g src/*.c -o bin/ultimecia -I/usr/local/include -L/usr/local/lib -lSDL2
+#cc -Wall -Wpedantic -Wall -pedantic -g src/*.c -o bin/ultimecia -I/usr/local/include -L/usr/local/lib -lSDL2
+cc -g src/*.c -o bin/ultimecia -I/usr/local/include -L/usr/local/lib -lSDL2
diff --git a/src/gpu.c b/src/gpu.c
@@ -4,7 +4,7 @@
#include "gpu.h"
#include "types.h"
#include "defs.h"
-#include "renderer.h"
+#include "sr.h"
u8 HOR_RES_from_fields(u8 hr1, u8 hr2) { return (hr2 & 1) | ((hr1 & 3) << 1); }
u32 HOR_RES_into_status(u8 hr) {return ((u32)hr) << 16; }
@@ -17,7 +17,7 @@ GPU_new(void)
gpu = (GPU*)malloc(sizeof(GPU));
memset(gpu, 0, sizeof(GPU));
gpu->display_disabled = 1;
- gpu->ren = RENDERER_new();
+ gpu->ren = REN_new();
return gpu;
}
@@ -107,7 +107,8 @@ GPU_gp1(GPU* gpu, u32 val)
void
GPU_gp1_reset(GPU* gpu, u32 val)
{
- memset(gpu, 0, sizeof(GPU));
+ REN* tmp = gpu->ren;
+ memset(gpu, 0, sizeof(GPU));
/* Fill out the rest of the fields with no$cash spec reset values*/
gpu->display_disabled = 1;
gpu->interlaced = 1;
@@ -115,6 +116,7 @@ GPU_gp1_reset(GPU* gpu, u32 val)
gpu->display_horiz_end = 0xc00;
gpu->display_line_start = 0x10;
gpu->display_line_end = 0x100;
+ gpu->ren = tmp;
}
void
@@ -349,8 +351,8 @@ GPU_gp0_quad_mono_opaque(GPU* gpu)
void
GPU_gp0_triangle_shaded_opaque(GPU* gpu)
{
- R_Position positions[3];
- R_Color colors[3];
+ ivec2 positions[3];
+ C colors[3];
positions[0] = POSITION_from_gp0(gpu->gp0_command.buffer[1]);
positions[1] = POSITION_from_gp0(gpu->gp0_command.buffer[3]);
@@ -360,7 +362,7 @@ GPU_gp0_triangle_shaded_opaque(GPU* gpu)
colors[1] = COLOR_from_gp0(gpu->gp0_command.buffer[2]);
colors[2] = COLOR_from_gp0(gpu->gp0_command.buffer[4]);
- // gpu->ren->push_triangle(positions, colors);
+ REN_push_triangle(gpu->ren, positions, colors);
fprintf(stdout, "Draw triangle shaded\n");
}
@@ -415,4 +417,4 @@ GPU_CMD_BUFFER_push_word(GPU_CMD_BUFFER* cmd_buffer, u32 word)
cmd_buffer->buffer[cmd_buffer->len] = word;
cmd_buffer->len++;
-}
-\ No newline at end of file
+}
diff --git a/src/gpu.h b/src/gpu.h
@@ -1,7 +1,7 @@
#pragma once
#include "types.h"
-#include "renderer.h"
+#include "sr.h"
typedef enum _GP0_MODE {
GP0_MODE_COMMAND,
@@ -88,7 +88,7 @@ typedef struct _GPU {
u32 gp0_words_remaining;
void (*gp0_command_method) (struct _GPU*);
- RENDERER* ren;
+ REN* ren;
} GPU;
/* GPU */
diff --git a/src/main.c b/src/main.c
@@ -10,30 +10,37 @@
#include "mem.h"
#include "defs.h"
#include "gpu.h"
-#include "renderer.h"
+#include "sr.h"
+
+//FB fb;
+//SDL_Window* win;
+//SDL_Renderer* ren;
+//vec3f **vertices;
+//i32 **faces;
+SDL_Event ev;
int
main(int argc, char **argv)
{
- CPU* cpu;
- Interconnect* inter;
-
- inter = new_interconnect();
- cpu = new_cpu(inter);
+ REN *ren;
+ CPU *cpu;
+ Interconnect *inter;
- while(1) {
- CPU_run_next_instruction(cpu);
- }
+ inter = new_interconnect();
+ cpu = new_cpu(inter);
+ while(1) {
+ CPU_run_next_instruction(cpu);
+ }
- free(inter->bios->data);
- free(inter->bios);
- free(inter->ram->data);
- free(inter->ram);
- free(inter->dma);
- free(inter);
- free(cpu);
+ free(inter->bios->data);
+ free(inter->bios);
+ free(inter->ram->data);
+ free(inter->ram);
+ free(inter->dma);
+ free(inter);
+ free(cpu);
- SDL_Quit();
+ SDL_Quit();
- return 0;
+ return 0;
}
diff --git a/src/renderer.c b/src/renderer.c
@@ -1,66 +0,0 @@
-/* This is the renderer */
-
-#include <SDL2/SDL.h>
-#include <stdint.h>
-#include "types.h"
-#include "renderer.h"
-
-RENDERER*
-RENDERER_new()
-{
- RENDERER* ren;
-
- SDL_Init(SDL_INIT_VIDEO);
-
- ren = malloc(sizeof(RENDERER));
- ren->window = SDL_CreateWindow("Ultimecia", 0, 0, 1024, 512, SDL_WINDOW_RESIZABLE);
- ren->renderer = SDL_CreateRenderer(ren->window, -1, SDL_RENDERER_SOFTWARE);
- ren->framebuffer = malloc(sizeof(u32) * 320 * 240);
- memset(ren->framebuffer, 0, sizeof(u32)* 320 * 240);
- ren->nvertices = 0;
-
- return ren;
-}
-
-R_Position
-POSITION_from_gp0(u32 val)
-{
- R_Position pos;
- pos.x = (i16)val;
- pos.y = (i16)(val >> 16);
-
- return pos;
-}
-
-R_Color
-COLOR_from_gp0(u32 val)
-{
- R_Color c;
- c.r = (u8)val;
- c.g = (u8)(val >> 8);
- c.b = (u8)(val >> 16);
-
- return c;
-}
-
-void
-RENDERER_draw()
-{
- return;
-}
-
-void
-RENDERER_push_triangle(RENDERER* ren, R_Position positions[3], R_Color colors[3])
-{
- u8 i;
-
- if (ren->nvertices + 3 > 1) {
- printf("Vertex attribute buffer full, forcing draw...\n");
- RENDERER_draw();
- }
-
- for (i = 0; i < 3; i++) {
- ren->nvertices++;
- }
-
-}
-\ No newline at end of file
diff --git a/src/renderer.h b/src/renderer.h
@@ -1,33 +0,0 @@
-#pragma once
-
-#include <SDL2/SDL.h>
-#include "types.h"
-
-typedef struct _R_Position {
- i16 x;
- i16 y;
-} R_Position;
-
-typedef struct _R_Color {
- u8 r;
- u8 g;
- u8 b;
-} R_Color;
-
-typedef struct _RENDERER {
- SDL_Window* window;
- SDL_Surface* surface;
- SDL_Renderer* renderer;
- R_Position* positions;
- R_Color* colors;
- u32* framebuffer;
- u32 nvertices; /* Current number or vertices in the buffers */
-} RENDERER;
-
-RENDERER* RENDERER_new();
-R_Position POSITION_from_gp0(u32 val);
-R_Color COLOR_from_gp0(u32 val);
-void RENDERER_push_triangle(RENDERER*, R_Position[3], R_Color[3]);
-void RENDERER_draw();
-
-
diff --git a/src/sr.c b/src/sr.c
@@ -0,0 +1,379 @@
+/* Based software renderer in one file */
+
+#include <stdint.h>
+#include <stdio.h>
+#include <string.h>
+#include <stdlib.h>
+#include <math.h>
+#include <SDL2/SDL.h>
+
+#include "types.h"
+#include "sr.h"
+
+FB fb;
+SDL_Window* win;
+SDL_Renderer* ren;
+vec3f **vertices;
+i32 **faces;
+//SDL_Event ev;
+
+
+ivec2
+POSITION_from_gp0(u32 val)
+{
+ ivec2 pos;
+ pos.x = (i16)val;
+ pos.y = (i16)(val >> 16);
+
+ return pos;
+}
+
+C
+COLOR_from_gp0(u32 val)
+{
+ C c;
+ c.r = (u8)val;
+ c.g = (u8)(val >> 8);
+ c.b = (u8)(val >> 16);
+ c.a = (u8)((val >> 24) & 0xff);
+ return c;
+}
+
+void
+RENDERER_push_triangle(REN* ren, ivec2 positions[3], C colors[3])
+{
+ u8 i;
+
+ if (ren->nvertices + 3 > 1) {
+ printf("Vertex attribute buffer full, forcing draw...\n");
+ //RENDERER_draw();
+ }
+
+ for (i = 0; i < 3; i++) {
+ ren->nvertices++;
+ }
+
+}
+
+void
+FB_flip_vert(u32 *data)
+{
+ u64 bytes_per_line; u32 *line; i32 half, j;
+
+ bytes_per_line = W;
+ line = (u32 *)malloc(bytes_per_line * sizeof(u32));
+ half = H>>1;
+
+ for (i32 j=0; j<half; j++) {
+ u64 l1 = j*bytes_per_line;
+ u64 l2 = (H-1-j)*bytes_per_line;
+ memmove((void *)line, (void *)(data+l1), bytes_per_line* sizeof(u32));
+ memmove((void *)(data+l1), (void *)(data+l2), bytes_per_line* sizeof(u32));
+ memmove((void *)(data+l2), (void *)line, bytes_per_line* sizeof(u32));
+ }
+ free(line);
+}
+
+ivec2
+IVEC2_op(ivec2 a, ivec2 b, enum mop mop)
+{
+ ivec2 c;
+ switch(mop) {
+ case ADD:
+ c.x = a.x + b.x;
+ c.y = a.y + b.y;
+ break;
+ case SUB:
+ c.x = a.x - b.x;
+ c.y = a.y - b.y;
+ break;
+ case MUL:
+ c.x = a.x * b.x;
+ c.y = a.y * b.y;
+ break;
+ default:
+ break;
+ }
+ return c;
+}
+
+ivec2
+IVEC2_ops(ivec2 a, i32 b, enum mop mop)
+{
+ ivec2 c;
+ switch(mop) {
+ case ADD:
+ c.x = a.x + b;
+ c.y = a.y + b;
+ break;
+ case SUB:
+ c.x = a.x - b;
+ c.y = a.y - b;
+ break;
+ case MUL:
+ c.x = a.x * b;
+ c.y = a.y * b;
+ break;
+ default:
+ break;
+ }
+ return c;
+}
+
+
+C
+C_new(u32 b)
+{
+ C c;
+ c.r = (u8)(b & 0xff);
+ c.g = (u8)((b >> 8) & 0xff);
+ c.b = (u8)((b >> 16) & 0xff);
+ c.a = (u8)((b >> 24) & 0xff);
+ return c;
+};
+
+v new_fb() { fb.data = malloc(W*H*sizeof(u32));}
+void FB_set(i32 x, i32 y, C c) { (!fb.data || x<0 || y<0 || x>=W || y>=H) ? 0 : memcpy(fb.data + ((x + y * W)), &c, 4); }
+void REN_FB_set(REN* ren, i32 x, i32 y, C c) { (!fb.data || x<0 || y<0 || x>=W || y>=H) ? 0 : memcpy(ren->fb + ((x + y * W)), &c, 4); }
+
+//C*
+//FB_get(i32 x, i32 y)
+//{
+// void* c = (!fb.data || x<0 || y<0 || x>=W || y>=H) ? (void*)0 : (void*)(C_new(fb.data[(x+y*W)]));
+// return (C*)c;
+//}
+
+void
+line(ivec2 v, ivec2 v1, C color)
+{
+
+ i32 x,y,x0,x1,y0,y1,dx,dy,sx,sy,e,e2,p;
+ x0 = v.x;
+ y0 = v.y;
+ x1 = v1.x;
+ y1 = v1.y;
+
+ dx = abs(x1-x0);
+ sx = x0 < x1 ? 1 : -1;
+ dy = -abs(y1-y0);
+ sy = y0 < y1 ? 1 : -1;
+ e = dx + dy;
+
+ while (1) {
+ FB_set(x0, y0, color);
+ if (x0 == x1 && y0 == y1) break;
+ e2 = 2 * e;
+ if (e2 >= dy) {
+ e += dy;
+ x0 += sx;
+ }
+ if (e2 <= dx) {
+ e += dx;
+ y0 += sy;
+ }
+ }
+}
+
+REN*
+REN_new()
+{
+ REN* ren;
+ SDL_Init(SDL_INIT_VIDEO);
+ ren = (REN*)malloc(sizeof(REN));
+ ren->window = SDL_CreateWindow("Ultimecia", 400, 400, WIN_W, WIN_H, SDL_WINDOW_RESIZABLE);
+ ren->renderer = SDL_CreateRenderer(ren->window, -1, SDL_RENDERER_SOFTWARE);
+ ren->tex = SDL_CreateTexture(ren->renderer, SDL_PIXELFORMAT_RGBA8888, SDL_TEXTUREACCESS_STREAMING, W, H);
+ ren->verts = NULL;
+ ren->colors = NULL;
+ ren->fb = malloc(W*H*sizeof(u32));
+ memset(ren->fb, 0, (u32)(W*H)*sizeof(u32));
+ ren->nvertices = 0;
+ return ren;
+}
+
+i32
+parse_obj(i32 *fcount, i32 *ecount)
+{
+ FILE *f;
+ char *line;
+ char *tokens[128], *p;
+ size_t len;
+ ssize_t read;
+ long pos;
+ i32 edge_count, face_count;
+
+ f = fopen("african.obj", "rb");
+
+ fseek(f, 0, SEEK_END);
+ pos = ftell(f);
+ fseek(f, 0, SEEK_SET);
+
+ line = NULL;
+ edge_count = 0;
+ face_count = 0;
+
+ while ((read = getline(&line, &len, f)) != -1) {
+ i32 i;
+ i = 0;
+ if (strstr(line, "v ") != NULL) {
+ for ((p = strtok(line, " ")); p; (p = strtok(NULL, " ")))
+ tokens[i++] = p;
+
+ vertices = realloc(vertices, (edge_count + 1) * sizeof(vec3f*));
+ vertices[edge_count] = malloc(sizeof(vec3f));
+
+ vertices[edge_count]->x = atof(tokens[1]);
+ vertices[edge_count]->y = atof(tokens[2]);
+ vertices[edge_count++]->z = atof(tokens[3]);
+ }
+
+ if (strstr(line, "f ") != NULL) {
+ for ((p = strtok(line, " ")); p; (p = strtok(NULL, " ")))
+ tokens[i++] = p;
+
+ faces = realloc(faces, (face_count + 1) * sizeof(i32*));
+ faces[face_count] = malloc(sizeof(i32)*3);
+
+ faces[face_count][0] = atoi(tokens[1]) - 1;
+ faces[face_count][1] = atoi(tokens[2]) - 1;
+ faces[face_count++][2] = atoi(tokens[3]) - 1;
+ }
+ }
+
+ fclose(f);
+ if (line)
+ free(line);
+
+ *fcount = face_count;
+ *ecount = edge_count;
+
+ return 1;
+
+}
+
+void
+draw_obj(i32 fcount)
+{
+ for (i32 i=0; i<fcount-1; i++) {
+ i32 *face = faces[i];
+
+ for (i32 j=0; j<3; j++) {
+ vec3f *v0 = vertices[face[j]];
+ vec3f *v1 = vertices[face[(j+1)%3]];
+ i32 x0 = (v0->x+1.)*W/2.;
+ i32 y0 = W - ((v0->y+1.)*W/2.);
+ i32 x1 = (v1->x+1.)*W/2.;
+ i32 y1 = W - ((v1->y+1.)*W/2.);
+ line((ivec2){x0, y0}, (ivec2){x1, y1}, C_new(0x3333ffff));
+ }
+ }
+}
+
+void
+triangle(REN* ren, ivec2 t0, ivec2 t1, ivec2 t2, C color) {
+ // sort the vertices, t0, t1, t2 lower−to−upper (bubblesort yay!)
+ ivec2 temp;
+
+ if (t0.y>t1.y) VEC2I_SWAP(t0, t1);
+ if (t0.y>t2.y) VEC2I_SWAP(t0, t2);
+ if (t1.y>t2.y) VEC2I_SWAP(t1, t2);
+
+ i32 total_height = t2.y - t0.y;
+
+ for (i32 i = 0; i < total_height; i++) {
+ i32 second_half = i > (t1.y - t0.y) || t1.y == t0.y;
+ i32 segment_height = second_half ? t2.y - t1.y : t1.y - t0.y;
+ float alpha = (float)i / total_height;
+ float beta = (float)(i - (second_half ? t1.y - t0.y : 0)) / segment_height; // how far down are we
+
+ ivec2 A = {
+ t0.x + (t2.x - t0.x) * alpha,
+ t0.y + i
+ };
+
+ ivec2 B = second_half ?
+ (ivec2){
+ t1.x + (t2.x - t1.x) * beta,
+ t1.y + (i - (t1.y - t0.y))
+ } :
+ (ivec2){
+ t0.x + (t1.x - t0.x) * beta,
+ t0.y + i
+ };
+
+ if (A.x > B.x) VEC2I_SWAP(A, B);
+ // Draw horizontal span between A and B
+ for (i32 x = A.x; x <= B.x; x++) {
+ REN_FB_set(ren, x, A.y, color);
+ }
+ }
+}
+
+void
+REN_push_triangle(REN* ren, ivec2 verts[3], C colors[3])
+{
+ u8 i;
+ long VERTEX_BUFFER_LEN = 64*1024;
+
+ if (ren->nvertices + 3 > 100) {
+ printf("Vertex attribute buffer full, forcing draw...\n");
+ for(int i = 0; i < ren->nvertices-2; i++)
+ triangle(ren, ren->verts[i], ren->verts[i+1], ren->verts[i+2], ren->colors[i]);
+ }
+
+ ren->verts = realloc(ren->verts, sizeof(ivec2) * (ren->nvertices + 3));
+ ren->colors = realloc(ren->colors, sizeof(C) * (ren->nvertices + 3));
+ for (i = 0; i < 3; i++) {
+ ren->verts[ren->nvertices] = verts[i];
+ ren->colors[ren->nvertices] = colors[i];
+ ren->nvertices++;
+ }
+}
+
+i32
+haha_main()
+{
+ //init_sdl();
+
+ //SDL_Event ev;
+
+ SDL_RenderClear(ren);
+
+ //i32 fcount, ecount;
+ //i32 ho = parse_obj(&fcount, &ecount);
+
+ //draw_obj(fcount);
+
+ //ivec2 t0[3] = {{10, 70}, {50, 160}, {70, 80}};
+ //ivec2 t1[3] = {{180, 50}, {150, 1}, {70, 180}};
+ //ivec2 t2[3] = {{180, 150}, {120, 160}, {130, 180}};
+ //triangle(t0[0], t0[1], t0[2],C_new(0xff000000));
+ //triangle(t1[0], t1[1], t1[2],C_new(0xffff0000));
+ //triangle(t2[0], t2[1], t2[2],C_new(0x000ff000));
+ //ivec2 v0 = {0, 0};
+ //ivec2 v1 = {W, 0};
+ //ivec2 v2 = {W/2, H};
+ //triangle(v0, v1, v2,C_new(0xff000000));
+
+ //FB_flip_vert(fb.data);
+
+ //SDL_UpdateTexture(texture, NULL, fb.data, W * sizeof(u32));
+ //SDL_RenderCopy(ren->ren, texture, NULL, NULL);
+ //SDL_RenderPresent(ren);
+
+ //while(1) {
+ // while(SDL_PollEvent(&ev) != 0) {
+ // switch(ev.type) {
+ // case SDL_QUIT:
+ // SDL_Quit();
+ // exit(1);
+ // }
+ // switch(ev.key.keysym.sym) {
+ // case SDLK_q:
+ // SDL_Quit();
+ // exit(1);
+ // }
+ // }
+ //}
+ return 0;
+}
diff --git a/src/sr.h b/src/sr.h
@@ -0,0 +1,43 @@
+#pragma once
+
+#include <SDL2/SDL.h>
+
+#define W 1024
+#define H 1024
+#define WIN_W 1024
+#define WIN_H 1024
+
+#define VEC2I_SWAP(x, y) { ivec2 temp = x; x = y; y = temp; }
+#define I_SWAP(x, y) { int temp = x; x = y; y = temp; }
+
+typedef void v; typedef uint8_t u8;typedef uint16_t u16;typedef uint32_t u32;typedef uint64_t u64;typedef int8_t i8;typedef int16_t i16;typedef int32_t i32;typedef int64_t i64;typedef uint8_t Bool;
+typedef struct { u32 *data; } FB;
+typedef struct {u8 b;u8 g;u8 r; u8 a;} C;
+typedef struct { double x, y, z; } point;
+typedef struct { int x, y; } ivec2;
+typedef struct { double x, y, z; } vec3f;
+enum mop {ADD, SUB, MUL, DIV};
+
+typedef struct _RENDERER {
+ SDL_Window* window;
+ SDL_Texture* tex;
+ SDL_Renderer* renderer;
+ ivec2* verts;
+ C* colors;
+ u32* fb;
+ u32 nvertices; /* Current number or vertices in the buffers */
+} REN;
+
+void FB_flip_vert(u32*);
+ivec2 IVEC2_op(ivec2, ivec2, enum mop);
+ivec2 IVEC2_ops(ivec2, i32, enum mop);
+C C_new(u32);
+
+v new_fb(); //{ fb.data = malloc(W*H*sizeof(u32));}
+void FB_set(i32, i32, C);
+void triangle(REN*, ivec2, ivec2, ivec2, C color);
+REN* REN_new();
+ivec2 POSITION_from_gp0(u32 val);
+C COLOR_from_gp0(u32 val);
+void REN_push_triangle(REN*, ivec2[3], C[3]);
+void RENDERER_draw();
diff --git a/src/sr/sr.c b/src/sr/sr.c
@@ -7,21 +7,8 @@
#include <math.h>
#include <SDL2/SDL.h>
-#define W 1024
-#define H 1024
-#define WIN_W 1024
-#define WIN_H 1024
-
-#define VEC2I_SWAP(x, y) { ivec2 temp = x; x = y; y = temp; }
-#define I_SWAP(x, y) { int temp = x; x = y; y = temp; }
-
-typedef void v; typedef uint8_t u8;typedef uint16_t u16;typedef uint32_t u32;typedef uint64_t u64;typedef int8_t i8;typedef int16_t i16;typedef int32_t i32;typedef int64_t i64;typedef uint8_t Bool;
-typedef struct { u32 *data; } FB;
-typedef struct {u8 b;u8 g;u8 r; u8 a;} C;
-typedef struct { double x, y, z; } point;
-typedef struct { int x, y; } ivec2;
-typedef struct { double x, y, z; } vec3f;
-enum mop {ADD, SUB, MUL, DIV};
+#include "../types.h"
+#include "sr.h"
FB fb;
SDL_Window* win;
diff --git a/src/sr/sr.h b/src/sr/sr.h
@@ -0,0 +1,26 @@
+#pragma once
+
+#define W 1024
+#define H 1024
+#define WIN_W 1024
+#define WIN_H 1024
+
+#define VEC2I_SWAP(x, y) { ivec2 temp = x; x = y; y = temp; }
+#define I_SWAP(x, y) { int temp = x; x = y; y = temp; }
+
+typedef void v; typedef uint8_t u8;typedef uint16_t u16;typedef uint32_t u32;typedef uint64_t u64;typedef int8_t i8;typedef int16_t i16;typedef int32_t i32;typedef int64_t i64;typedef uint8_t Bool;
+typedef struct { u32 *data; } FB;
+typedef struct {u8 b;u8 g;u8 r; u8 a;} C;
+typedef struct { double x, y, z; } point;
+typedef struct { int x, y; } ivec2;
+typedef struct { double x, y, z; } vec3f;
+enum mop {ADD, SUB, MUL, DIV};
+
+void FB_flip_vert(u32*);
+ivec2 IVEC2_op(ivec2, ivec2, enum mop);
+ivec2 IVEC2_ops(ivec2, i32, enum mop);
+C C_new(u32);
+
+v new_fb(); //{ fb.data = malloc(W*H*sizeof(u32));}
+void FB_set(i32, i32, C);
+void triangle(ivec2, ivec2, ivec2, C color);