diff options
| -rw-r--r-- | .clang-format | 7 | ||||
| -rw-r--r-- | .gitignore | 1 | ||||
| -rw-r--r-- | include/rgl.h | 148 | ||||
| -rw-r--r-- | meson.build | 23 | ||||
| -rw-r--r-- | robsonGL.h | 323 | ||||
| -rw-r--r-- | src/command.c | 41 | ||||
| -rw-r--r-- | src/memory.c | 20 | ||||
| -rw-r--r-- | src/renderer_software.c | 100 | ||||
| -rw-r--r-- | test.c | 26 | ||||
| -rw-r--r-- | tests/common_linux.h | 70 | ||||
| -rw-r--r-- | tests/meson.build | 8 | ||||
| -rw-r--r-- | tests/test_stroke.c | 33 |
12 files changed, 451 insertions, 349 deletions
diff --git a/.clang-format b/.clang-format new file mode 100644 index 0000000..1071109 --- /dev/null +++ b/.clang-format @@ -0,0 +1,7 @@ +BasedOnStyle: LLVM # ou Google, Mozilla, etc. +AllowShortFunctionsOnASingleLine: None # nunca coloca funções em uma linha +AllowShortIfStatementsOnASingleLine: false +AllowShortLoopsOnASingleLine: false +AllowShortBlocksOnASingleLine: false +BreakBeforeBraces: Attach # abre a chave em linha separada +ColumnLimit: 80 # ajuste conforme sua preferência diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..ceddaa3 --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +.cache/ diff --git a/include/rgl.h b/include/rgl.h new file mode 100644 index 0000000..ac69ed0 --- /dev/null +++ b/include/rgl.h @@ -0,0 +1,148 @@ +#ifndef RGL_H +#define RGL_H + +#ifdef RGL_DEBUG + +static char *ERROR_MESSAGE; + +#define RGL_CRASH(text) \ + do { \ + ERROR_MESSAGE = text; \ + __builtin_trap(); \ + } while (0) + +#define RGL_ASSERT(cond, text) \ + if (!(cond)) { \ + RGL_CRASH(text); \ + } + +#else + +#define RGL_CRASH(text) +#define RGL_ASSERT(cond, text) + +#endif + +static int RGL_ABS(int n) { + return n < 0 ? -n : n; +} + +/* Memory */ + +typedef struct { + unsigned int size; + unsigned int capacity; + + unsigned char *data; +} RGL_Arena; + +void *RGL_ArenaAlloc(RGL_Arena *arena, unsigned int size); + +unsigned int RGL_ArenaCheckpoint(RGL_Arena *arena); + +void RGL_ArenaRollback(RGL_Arena *arena, unsigned int checkpoint); + +void RGL_ArenaReset(RGL_Arena *arena); + +/* Math */ + +typedef unsigned int RGL_Fixed; + +#define RGL_SUBPIXEL_SHIFT 8 +#define RGL_SUBPIXEL_SCALE 256 +#define RGL_SUBPIXEL_MASK 255 + +static RGL_Fixed RGL_GetFixedI(int x) { + return (RGL_Fixed)(x << RGL_SUBPIXEL_SHIFT); +} + +static RGL_Fixed RGL_GetFixedF(float x) { + return (RGL_Fixed)(x * RGL_SUBPIXEL_SCALE); +} + +static RGL_Fixed RGL_GetFixedD(double x) { + return (RGL_Fixed)(x * RGL_SUBPIXEL_SCALE); +} + +static int RGL_FixedToInt(RGL_Fixed x) { + return x >> RGL_SUBPIXEL_SHIFT; +} + +static RGL_Fixed RGL_FixedMul(RGL_Fixed a, RGL_Fixed b) { + return (RGL_Fixed)(((long int)a * b) >> RGL_SUBPIXEL_SHIFT); +} + +/* Graphics */ + +typedef unsigned int RGL_Color; + +#define RGL_COLOR(a, r, g, b) \ + ((RGL_Color)((a << 24) | (r << 16) | (g << 8) | b)) + +typedef struct { + unsigned int width, height; + unsigned int *data; +} RGL_Buffer; + +typedef struct { + RGL_Color color; + unsigned int x, y, width, height; +} RGL_Rectangle; + +typedef struct { + RGL_Fixed x, y; +} RGL_Point; + +typedef enum { PathStroke, PathFill } RGL_PathVerb; + +typedef struct { + RGL_PathVerb verb; + unsigned int size; + RGL_Color color; +} RGL_PathHeader; + +typedef enum { + Rect, + Path, +} RGL_CommandType; + +typedef struct { + RGL_CommandType type; + union { + RGL_Rectangle rect; + RGL_PathHeader path; + } data; +} RGL_Command; + +typedef struct { + RGL_Arena arena; + unsigned int size; + + RGL_Command *commands; +} RGL_CommandQueue; + +typedef struct { + RGL_Color color; + RGL_CommandQueue queue; + + RGL_Command *current; +} RGL_Context; + +void RGL_Rect(RGL_Context *ctx, unsigned int x, unsigned int y, + unsigned int width, unsigned int height); + +void RGL_BeginPath(RGL_Context *ctx, RGL_PathVerb verb); + +void RGL_PathMoveTo(RGL_Context *ctx, int x, int y); + +void RGL_EndPath(RGL_Context *ctx); + +void RGL_DrawRect(RGL_Buffer *buf, RGL_Command *cmd); + +void RGL_DrawStroke(RGL_Buffer *buf, RGL_PathHeader *path, RGL_Point *points); + +void RGL_DrawPath(RGL_Buffer *buf, RGL_PathHeader *path, RGL_Point *points); + +void RGL_Draw(RGL_Context *ctx, RGL_Buffer *buf); + +#endif diff --git a/meson.build b/meson.build new file mode 100644 index 0000000..d5f2a26 --- /dev/null +++ b/meson.build @@ -0,0 +1,23 @@ +project( + 'robsonGL', + 'c', + default_options: [ + 'c_std=c89', + 'warning_level=everything', + 'prefer_static=true', + 'debug=true', + ], +) + +add_project_arguments('-DRGL_DEBUG', language: 'c') + +src = files( + 'src/command.c', + 'src/memory.c', + 'src/renderer_software.c', +) +incdir = include_directories('include') + +lib_rgl = library('RGL', src, include_directories: incdir) + +subdir('tests') diff --git a/robsonGL.h b/robsonGL.h deleted file mode 100644 index 59e0d6a..0000000 --- a/robsonGL.h +++ /dev/null @@ -1,323 +0,0 @@ -#include <stdint.h> -#include <stdio.h> -#include <stdlib.h> -#include <string.h> - -typedef int32_t rglFixed; - -#define RGL_SUBPIXEL_SHIFT 8 -#define RGL_SUBPIXEL_SCALE 256 -#define RGL_SUBPIXEL_MASK 255 - -#define crash(text) \ - do { \ - printf(text); \ - *((int *)0) = -1; \ - } while (0) - -static inline rglFixed rglGetFixedI(int x) { - return (rglFixed)(x << RGL_SUBPIXEL_SHIFT); -} - -static inline rglFixed rglGetFixedF(float x) { - return (rglFixed)(x * RGL_SUBPIXEL_SCALE); -} - -static inline rglFixed rglGetFixedD(double x) { - return (rglFixed)(x * RGL_SUBPIXEL_SCALE); -} - -static inline int rglFixedToInt(rglFixed x) { return x >> RGL_SUBPIXEL_SHIFT; } - -static inline rglFixed rglFixedMul(rglFixed a, rglFixed b) { - return (rglFixed)(((int64_t)a * b) >> RGL_SUBPIXEL_SHIFT); -} - -typedef struct { - uint32_t width, height; - uint32_t *data; -} rglBuffer; - -typedef struct { - uint32_t x, y, width, height; -} rglRectangle; - -typedef uint32_t rglColor; - -typedef struct { - rglFixed x, y; -} rglPoint; - -typedef enum { PathStroke, PathFill } rglPathVerb; - -typedef struct { - uint32_t listSize; - uint32_t listCapacity; - rglPathVerb verb; - - rglFixed *xList; - rglFixed *yList; -} rglPath; - -typedef enum { Rect, Path } rglCommandType; - -typedef struct { - rglCommandType type; - rglColor color; - union { - rglRectangle *rect; - rglPath *path; - }; -} rglCommand; - -typedef struct { - uint32_t size; - rglCommand *commands; -} rglCommandQueue; - -typedef struct { - rglColor color; - rglBuffer buf; - rglCommandQueue queue; - - // TODO: make this use rglCommand in the future - void *current; -} rglContext; - -void rglQueueCommand(rglCommandQueue *queue, rglCommand *cmd) { - // FIXME: append correctly - queue->size += 1; - queue->commands = cmd; -} - -// TODO: Use memory arena implementation from ruim -void rglBeginPath(rglContext *ctx, rglPathVerb verb) { - rglPath *path = malloc(sizeof(rglPath)); - - path->listSize = 0; - path->listCapacity = 16; - path->verb = verb; - path->xList = malloc(path->listCapacity * sizeof(uint32_t)); - path->yList = malloc(path->listCapacity * sizeof(uint32_t)); - - ctx->current = (void *)path; -} - -void rglPathMoveTo(rglContext *ctx, int32_t x, int32_t y) { - rglPath *path = (rglPath *)ctx->current; - - uint32_t p = path->listSize; - path->xList[p] = rglGetFixedI(x); - path->yList[p] = rglGetFixedI(y); - - path->listSize += 1; -} - -void rglEndPath(rglContext *ctx) { - rglCommand *command = malloc(sizeof(rglCommand)); - - command->type = Path; - command->path = (rglPath *)ctx->current; - command->color = ctx->color; - - rglQueueCommand(&ctx->queue, command); - ctx->current = 0; -} - -#define COLOR(a, r, g, b) ((a << 24) | (r << 16) | (g << 8) | b) - -// TODO: implement shared memory mechanism -int rglBufferAlloc(rglBuffer *buf) { - uint32_t size = buf->width * buf->height * 4; - buf->data = malloc(size); // ARGB pixel format - - if (buf->data == NULL) { - return -1; - } - - memset(buf->data, 0, size); - - return 0; -} - -void rglDrawRect(rglBuffer *buf, rglCommand *cmd) { - if (cmd->type != Rect) - crash("expected rectangle type"); - - for (int y = 0; y < buf->height; ++y) { - for (int x = 0; x < buf->width; ++x) { - int insideX = x >= cmd->rect->x && x <= (cmd->rect->x + cmd->rect->width); - int insideY = - y >= cmd->rect->y && y <= (cmd->rect->y + cmd->rect->height); - - if (insideX && insideY) { - buf->data[x + (y * buf->width)] = cmd->color; - } - } - } -} - -void rglDrawStroke(rglBuffer *buf, rglPath *path, rglColor color) { - for (uint32_t i = 0; i < path->listSize; ++i) { - rglPoint p1, p2; - - p1.x = path->xList[i]; - p1.y = path->yList[i]; - - p2.x = path->xList[(i + 1) % path->listSize]; - p2.y = path->yList[(i + 1) % path->listSize]; - - int x0 = rglFixedToInt(p1.x), y0 = rglFixedToInt(p1.y); - int x1 = rglFixedToInt(p2.x), y1 = rglFixedToInt(p2.y); - - int dx = abs(x1 - x0); - int dy = -abs(y1 - y0); - - int sx = x0 < x1 ? 1 : -1; - int sy = y0 < y1 ? 1 : -1; - - int err = dx + dy, e2; - for (;;) { - int idx = x0 + (y0 * buf->width); - if (idx < buf->width * buf->height) { - buf->data[idx] = color; - } - - if (x0 == x1 && y0 == y1) - break; - e2 = err * 2; - if (e2 >= dy) { - err += dy; - x0 += sx; - } - - if (e2 <= dx) { - err += dx; - y0 += sy; - } - } - } -} - -// TODO: maybe construct edge table on command creation -void rglDrawFill(rglBuffer *buf, rglPath *path, rglColor col) { - struct edge_node { - int active; - rglFixed yMax; - rglFixed x; - // FIXME: make this fixed point later - float slope; - struct edge_node *next; - }; - - unsigned int x0, y0, x1, y1, x, y, xMin, yMin, lineTest; - unsigned int i, yMinIdx, yMaxIdx; - int j; - struct edge_node *table; - struct edge_node *node; - - table = malloc(sizeof(struct edge_node) * buf->height); - memset(table, 0, sizeof(struct edge_node) * buf->height); - - for (i = 0; i < path->listSize; ++i) { - for (j = i; j < path->listSize; ++j) { - if (path->yList[j] < path->yList[yMinIdx]) { - yMinIdx = j; - } - } - - x0 = rglFixedToInt(path->xList[yMinIdx]); - y0 = rglFixedToInt(path->yList[yMinIdx]); - - node = &(table[y0]); - for (j = -1; j < 2; j += 2) { - yMaxIdx = (i + j * 1) % path->listSize; - if (y0 == path->yList[yMaxIdx]) { - continue; - } else if (y0 <= path->yList[yMaxIdx]) { - yMaxIdx = yMinIdx; - } - - x1 = rglFixedToInt(path->xList[yMaxIdx]); - y1 = rglFixedToInt(path->yList[yMaxIdx]); - - node->active = 1; - node->x = x1; - node->yMax = y1; - node->slope = (float)(x1 - x0) / (float)(y1 - y0); - - if (j != -1) { - node->next = malloc(sizeof(struct edge_node)); - node = node->next; - } - } - } - - for (y = 0; y < buf->height; ++y) { - yMin = buf->height - 1; - while (table[yMin].active) { - yMin--; - } - - // Draw polygon - node = &table[yMin]; - xMin = node->x; - - for (x = 0; x < buf->width; ++x) { - i = x + (y * buf->width); - lineTest = (x * node->slope) - - } - } -} - -void rglDrawPath(rglBuffer *buf, rglPath *path, rglColor col) { - switch (path->verb) { - case PathStroke: { - rglDrawStroke(buf, path, col); - } break; - case PathFill: { - rglDrawFill(buf, path, col); - } break; - } -} - -void rglSetColor(rglContext *ctx, int a, int r, int g, int b) { - ctx->color = COLOR(a, r, g, b); -} - -void rglDraw(rglContext *ctx) { - for (int i = 0; i < ctx->queue.size; ++i) { - rglCommand cmd = ctx->queue.commands[i]; - switch (cmd.type) { - case Rect: { - rglDrawRect(&ctx->buf, &cmd); - } break; - case Path: { - rglDrawPath(&ctx->buf, cmd.path, cmd.color); - } break; - } - } -} - -int rglWritePPM(rglContext *ctx, const char *filename) { - FILE *f = fopen(filename, "wb"); - if (!f) - return -1; - - fprintf(f, "P6\n%d %d\n255\n", ctx->buf.width, ctx->buf.height); - - for (int i = 0; i < ctx->buf.width * ctx->buf.height; ++i) { - uint32_t pixel = ctx->buf.data[i]; - - uint8_t r = (pixel >> 16) & 0xFF; - uint8_t g = (pixel >> 8) & 0xFF; - uint8_t b = pixel & 0xFF; - - fputc(r, f); - fputc(g, f); - fputc(b, f); - } - - fclose(f); - return 0; -} diff --git a/src/command.c b/src/command.c new file mode 100644 index 0000000..735036e --- /dev/null +++ b/src/command.c @@ -0,0 +1,41 @@ +#include "rgl.h" + +void RGL_Rect(RGL_Context *ctx, unsigned int x, unsigned int y, + unsigned int width, unsigned int height) { + RGL_Command *rectCommand = + (RGL_Command *)RGL_ArenaAlloc(&ctx->queue.arena, sizeof(RGL_Command)); + + rectCommand->type = Rect; + rectCommand->data.rect.x = x; + rectCommand->data.rect.y = y; + rectCommand->data.rect.width = width; + rectCommand->data.rect.height = height; + rectCommand->data.rect.color = ctx->color; + + ctx->queue.size += 1; +} + +void RGL_BeginPath(RGL_Context *ctx, RGL_PathVerb verb) { + ctx->current = + (RGL_Command *)RGL_ArenaAlloc(&ctx->queue.arena, sizeof(RGL_Command)); + + ctx->current->type = Path; + ctx->current->data.path.color = ctx->color; + ctx->current->data.path.size = 0; + ctx->current->data.path.verb = verb; +} + +void RGL_PathMoveTo(RGL_Context *ctx, int x, int y) { + RGL_Point *p = + (RGL_Point *)RGL_ArenaAlloc(&ctx->queue.arena, sizeof(RGL_Point)); + p->x = RGL_GetFixedI(x); + p->y = RGL_GetFixedI(y); + + ctx->current->data.path.size += 1; +} + +void RGL_EndPath(RGL_Context *ctx) { + ctx->current->data.path.color = ctx->color; + ctx->current = 0; + ctx->queue.size += 1; +} diff --git a/src/memory.c b/src/memory.c new file mode 100644 index 0000000..48a7cca --- /dev/null +++ b/src/memory.c @@ -0,0 +1,20 @@ +#include "rgl.h" + +void *RGL_ArenaAlloc(RGL_Arena *arena, unsigned int size) { + void *data = (void *)&(arena->data[arena->size]); + arena->size += size; + + return data; +} + +unsigned int RGL_ArenaCheckpoint(RGL_Arena *arena) { + return arena->size; +} + +void RGL_ArenaRollback(RGL_Arena *arena, unsigned int checkpoint) { + arena->size = checkpoint; +} + +void RGL_ArenaReset(RGL_Arena *arena) { + arena->size = 0; +} diff --git a/src/renderer_software.c b/src/renderer_software.c new file mode 100644 index 0000000..3cdd614 --- /dev/null +++ b/src/renderer_software.c @@ -0,0 +1,100 @@ +#include "rgl.h" + +void RGL_DrawRect(RGL_Buffer *buf, RGL_Command *cmd) { + unsigned int x, y, insideX, insideY; + + RGL_ASSERT(cmd->type != Rect, "expected rectangle type"); + for (y = 0; y < buf->height; ++y) { + for (x = 0; x < buf->width; ++x) { + insideX = x >= cmd->data.rect.x && + x <= (cmd->data.rect.x + cmd->data.rect.width); + insideY = y >= cmd->data.rect.y && + y <= (cmd->data.rect.y + cmd->data.rect.height); + + if (insideX && insideY) { + buf->data[x + (y * buf->width)] = cmd->data.rect.color; + } + } + } +} + +void RGL_DrawStroke(RGL_Buffer *buf, RGL_PathHeader *path, RGL_Point *points) { + RGL_Point p1, p2; + unsigned int i; + int x0, x1, y0, y1, dx, dy, sx, sy, idx, err, e2; + + for (i = 0; i < path->size; ++i) { + p1 = points[i], p2 = points[(i + 1) % path->size]; + + x0 = RGL_FixedToInt(p1.x), y0 = RGL_FixedToInt(p1.y); + x1 = RGL_FixedToInt(p2.x), y1 = RGL_FixedToInt(p2.y); + + dx = RGL_ABS(x1 - x0); + sx = x0 < x1 ? 1 : -1; + + dy = -RGL_ABS(y1 - y0); + sy = y0 < y1 ? 1 : -1; + + err = dx + dy; + while (1) { + idx = x0 + (y0 * buf->width); + if (idx < buf->width * buf->height) { + buf->data[idx] = path->color; + } + + if (x0 == x1 && y0 == y1) + break; + + e2 = err * 2; + if (e2 >= dy) { + err += dy; + x0 += sx; + } + + if (e2 <= dx) { + err += dx; + y0 += sy; + } + } + } +} + +void RGL_DrawPath(RGL_Buffer *buf, RGL_PathHeader *path, RGL_Point *points) { + switch (path->verb) { + case PathStroke: { + RGL_DrawStroke(buf, path, points); + } break; + case PathFill: { + /* RGL_DrawFill(buf, path, points, col); */ + RGL_CRASH("`RGL_DrawFill` not implemented"); + } break; + default: { + RGL_CRASH("tried to handle unknown `path->verb`"); + } break; + } +} + +void RGL_Draw(RGL_Context *ctx, RGL_Buffer *buf) { + RGL_Command cmd; + int i; + for (i = 0; i < ctx->queue.size; ++i) { + cmd = ctx->queue.commands[i]; + switch (cmd.type) { + case Rect: { + RGL_DrawRect(buf, &cmd); + } break; + case Path: { + RGL_Point *points = (RGL_Point *)&(ctx->queue.commands[i + 1]); + RGL_DrawPath(buf, &cmd.data.path, points); + + /* Skip point list, so it points to a valid command */ + ctx->queue.commands = + (RGL_Command *)((char *)ctx->queue.commands + + cmd.data.path.size * sizeof(RGL_Point)); + } break; + default: { + RGL_CRASH("unknown `cmd.type`"); + } break; + } + } +} @@ -1,26 +0,0 @@ -#include "robsonGL.h" - -int main(int argc, char *argv[]) { - rglContext ctx; - ctx.buf.width = 800; - ctx.buf.height = 600; - - if (rglBufferAlloc(&ctx.buf)) - return 1; - - rglSetColor(&ctx, 255, 255, 255, 255); - - rglBeginPath(&ctx, PathFill); - rglPathMoveTo(&ctx, 0, 0); - rglPathMoveTo(&ctx, 799, 599); - rglPathMoveTo(&ctx, 799, 0); - rglPathMoveTo(&ctx, 0, 599); - rglEndPath(&ctx); - - rglDraw(&ctx); - - if (rglWritePPM(&ctx, "output.ppm")) - return 1; - - return 0; -} diff --git a/tests/common_linux.h b/tests/common_linux.h new file mode 100644 index 0000000..167b483 --- /dev/null +++ b/tests/common_linux.h @@ -0,0 +1,70 @@ +#include <rgl.h> + +#include <errno.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <sys/mman.h> + +#define crashErrno() RGL_CRASH(strerror(errno)) +#define assertErrno(cond) RGL_ASSERT(cond, strerror(errno)) + +void BufferAlloc(RGL_Buffer *buf) { + unsigned int size = buf->width * buf->height * 4; + + buf->data = mmap(NULL, size, PROT_READ | PROT_WRITE, + MAP_PRIVATE | MAP_ANONYMOUS, 0, 0); + assertErrno(buf->data != MAP_FAILED); +} + +void BufferDestroy(RGL_Buffer *buf) { + unsigned int size = buf->width * buf->height * 4; + if (munmap(buf->data, size) < 0) { + crashErrno(); + } +} + +void ArenaInit(RGL_Arena *arena, unsigned int capacity) { + if (capacity) { + arena->capacity = capacity; + } else { + arena->capacity = 4096; + } + + arena->data = mmap(0, arena->capacity, PROT_READ | PROT_WRITE, + MAP_PRIVATE | MAP_ANONYMOUS, 0, 0); + if (arena->data == MAP_FAILED) { + crashErrno(); + } + + arena->size = 0; +} + +void ArenaDestroy(RGL_Arena *arena) { + if (munmap(arena->data, arena->capacity) < 0) { + crashErrno(); + } +} + +int WritePPM(RGL_Buffer buf, const char *filename) { + int i; + FILE *f = fopen(filename, "wb"); + RGL_ASSERT(f, "failed to open file"); + + fprintf(f, "P6\n%d %d\n255\n", buf.width, buf.height); + + for (i = 0; i < buf.width * buf.height; ++i) { + unsigned int pixel = buf.data[i]; + + unsigned char r = (pixel >> 16) & 0xFF; + unsigned char g = (pixel >> 8) & 0xFF; + unsigned char b = pixel & 0xFF; + + fputc(r, f); + fputc(g, f); + fputc(b, f); + } + + fclose(f); + return 0; +} diff --git a/tests/meson.build b/tests/meson.build new file mode 100644 index 0000000..f4f46e7 --- /dev/null +++ b/tests/meson.build @@ -0,0 +1,8 @@ +exe_stroke = executable( + 'testStroke', + 'test_stroke.c', + link_with: [lib_rgl], + include_directories: incdir, +) + +test('stroke', exe_stroke) diff --git a/tests/test_stroke.c b/tests/test_stroke.c new file mode 100644 index 0000000..1bcafd1 --- /dev/null +++ b/tests/test_stroke.c @@ -0,0 +1,33 @@ +#include "common_linux.h" + +int main(int argc, char *argv[]) { + RGL_Context ctx; + RGL_Buffer buf; + + buf.width = 800; + buf.height = 600; + + ArenaInit(&ctx.queue.arena, 0); + ctx.queue.commands = (RGL_Command *)ctx.queue.arena.data; + + BufferAlloc(&buf); + + ctx.color = RGL_COLOR(255, 255, 255, 255); + + RGL_BeginPath(&ctx, PathStroke); + RGL_PathMoveTo(&ctx, 0, 0); + RGL_PathMoveTo(&ctx, 799, 599); + RGL_PathMoveTo(&ctx, 799, 0); + RGL_PathMoveTo(&ctx, 0, 599); + RGL_EndPath(&ctx); + + RGL_Draw(&ctx, &buf); + + if (WritePPM(buf, "output.ppm")) + return 1; + + ArenaDestroy(&ctx.queue.arena); + BufferDestroy(&buf); + + return 0; +} |