summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
author3gg <3gg@shellblade.net>2023-06-24 18:46:33 -0700
committer3gg <3gg@shellblade.net>2023-06-24 18:46:33 -0700
commit0831d5bce79008bfa6404f8e8116ae8290442fde (patch)
treee488c719e16b34b60126837a90a44d1c3dd552ee
parentcf886f4fa406ddd48f30c00ad3c77f9dc134af3a (diff)
Isometric Renderer initial commit.
-rw-r--r--CMakeLists.txt1
-rw-r--r--gfx-iso/CMakeLists.txt30
-rw-r--r--gfx-iso/demo/isogfx-demo.c213
-rw-r--r--gfx-iso/include/isogfx/isogfx.h66
-rw-r--r--gfx-iso/src/isogfx.c361
-rw-r--r--gfx/CMakeLists.txt4
6 files changed, 673 insertions, 2 deletions
diff --git a/CMakeLists.txt b/CMakeLists.txt
index 2aac1ab..3a0cd5a 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -2,4 +2,5 @@ cmake_minimum_required(VERSION 3.0)
2 2
3add_subdirectory(gfx) 3add_subdirectory(gfx)
4add_subdirectory(gfx-app) 4add_subdirectory(gfx-app)
5add_subdirectory(gfx-iso)
5add_subdirectory(gltfview) 6add_subdirectory(gltfview)
diff --git a/gfx-iso/CMakeLists.txt b/gfx-iso/CMakeLists.txt
new file mode 100644
index 0000000..8f95f7f
--- /dev/null
+++ b/gfx-iso/CMakeLists.txt
@@ -0,0 +1,30 @@
1cmake_minimum_required(VERSION 3.0)
2
3project(isogfx)
4
5set(CMAKE_C_STANDARD 11)
6set(CMAKE_C_STANDARD_REQUIRED On)
7set(CMAKE_C_EXTENSIONS Off)
8
9add_library(isogfx
10 src/isogfx.c)
11
12target_include_directories(isogfx PUBLIC
13 include)
14
15target_link_libraries(isogfx PRIVATE
16 mempool)
17
18target_compile_options(isogfx PRIVATE -Wall -Wextra -Wpedantic)
19
20# Demo
21
22project(isogfx-demo)
23
24add_executable(isogfx-demo
25 demo/isogfx-demo.c)
26
27target_link_libraries(isogfx-demo PRIVATE
28 gfx
29 gfx-app
30 isogfx)
diff --git a/gfx-iso/demo/isogfx-demo.c b/gfx-iso/demo/isogfx-demo.c
new file mode 100644
index 0000000..d6c1ab0
--- /dev/null
+++ b/gfx-iso/demo/isogfx-demo.c
@@ -0,0 +1,213 @@
1#include <isogfx/isogfx.h>
2
3#include <gfx/gfx.h>
4#include <gfx/gfx_app.h>
5#include <gfx/render_backend.h>
6#include <gfx/renderer.h>
7#include <gfx/scene.h>
8#include <gfx/util/geometry.h>
9#include <gfx/util/shader.h>
10
11#include <assert.h>
12#include <stdbool.h>
13#include <stdio.h>
14#include <stdlib.h>
15
16static const int SCREEN_WIDTH = 1408;
17static const int SCREEN_HEIGHT = 960;
18static const int TILE_WIDTH = 64;
19static const int TILE_HEIGHT = TILE_WIDTH / 2;
20static const int WORLD_WIDTH = 20;
21static const int WORLD_HEIGHT = 20;
22
23static const Pixel BLACK = (Pixel){.r = 0x38, .g = 0x3b, .b = 0x46};
24static const Pixel WHITE = (Pixel){.r = 0xA5, .g = 0xb3, .b = 0xc0};
25static const Pixel RED = (Pixel){.r = 0xdc, .g = 0x76, .b = 0x84};
26
27typedef struct State {
28 Gfx* gfx;
29 IsoGfx* iso;
30 Tile red;
31 int xpick;
32 int ypick;
33 Texture* screen_texture;
34 Scene* scene;
35} State;
36
37static void make_checkerboard(IsoGfx* iso, Tile black, Tile white) {
38 assert(iso);
39 for (int y = 0; y < isogfx_world_height(iso); ++y) {
40 for (int x = 0; x < isogfx_world_width(iso); ++x) {
41 const int odd_col = x & 1;
42 const int odd_row = y & 1;
43 const Tile value = (odd_row ^ odd_col) == 0 ? black : white;
44 isogfx_set_tile(iso, x, y, value);
45 }
46 }
47}
48
49static bool init(const GfxAppDesc* desc, void** app_state) {
50 State* state = calloc(1, sizeof(State));
51 if (!state) {
52 return false;
53 }
54
55 if (!(state->iso = isogfx_new(&(IsoGfxDesc){
56 .screen_width = SCREEN_WIDTH,
57 .screen_height = SCREEN_HEIGHT,
58 .tile_width = TILE_WIDTH,
59 .tile_height = TILE_HEIGHT,
60 .world_width = WORLD_WIDTH,
61 .world_height = WORLD_HEIGHT}))) {
62 goto cleanup;
63 }
64 if (!(state->gfx = gfx_init())) {
65 goto cleanup;
66 }
67 RenderBackend* render_backend = gfx_get_render_backend(state->gfx);
68
69 if (!(state->screen_texture = gfx_make_texture(
70 render_backend, &(TextureDesc){
71 .width = SCREEN_WIDTH,
72 .height = SCREEN_HEIGHT,
73 .dimension = Texture2D,
74 .format = TextureRGB8,
75 .filtering = NearestFiltering,
76 .wrap = ClampToEdge,
77 .mipmaps = false}))) {
78 goto cleanup;
79 }
80
81 ShaderProgram* shader = gfx_make_view_texture_shader(render_backend);
82 if (!shader) {
83 goto cleanup;
84 }
85
86 Geometry* geometry = gfx_make_quad_11(render_backend);
87 if (!geometry) {
88 goto cleanup;
89 }
90
91 MaterialDesc material_desc = (MaterialDesc){.num_uniforms = 1};
92 material_desc.uniforms[0] = (ShaderUniform){
93 .type = UniformTexture,
94 .value.texture = state->screen_texture,
95 .name = sstring_make("Texture")};
96 Material* material = gfx_make_material(&material_desc);
97 if (!material) {
98 return false;
99 }
100
101 const MeshDesc mesh_desc =
102 (MeshDesc){.geometry = geometry, .material = material, .shader = shader};
103 Mesh* mesh = gfx_make_mesh(&mesh_desc);
104 if (!mesh) {
105 goto cleanup;
106 }
107
108 SceneObject* object = gfx_make_object();
109 if (!object) {
110 goto cleanup;
111 }
112 gfx_add_object_mesh(object, mesh);
113
114 state->scene = gfx_make_scene();
115 SceneNode* node = gfx_make_object_node(object);
116 SceneNode* root = gfx_get_scene_root(state->scene);
117 gfx_set_node_parent(node, root);
118
119 const Tile black = isogfx_make_tile(
120 state->iso, &(TileDesc){.type = TileFromColour, .colour = BLACK});
121 const Tile white = isogfx_make_tile(
122 state->iso, &(TileDesc){.type = TileFromColour, .colour = WHITE});
123 state->red = isogfx_make_tile(
124 state->iso, &(TileDesc){.type = TileFromColour, .colour = RED});
125 make_checkerboard(state->iso, black, white);
126 isogfx_render(state->iso);
127
128 *app_state = state;
129 return true;
130
131cleanup:
132 if (state->gfx) {
133 gfx_destroy(&state->gfx);
134 }
135 free(state);
136 return false;
137}
138
139static void shutdown(void* app_state) {
140 assert(app_state);
141 State* state = (State*)(app_state);
142 isogfx_del(&state->iso);
143 gfx_destroy(&state->gfx);
144 free(app_state);
145}
146
147static void update(void* app_state, double t, double dt) {
148 assert(app_state);
149 State* state = (State*)(app_state);
150
151 double mouse_x, mouse_y;
152 gfx_app_get_mouse_position(&mouse_x, &mouse_y);
153
154 isogfx_pick_tile(state->iso, mouse_x, mouse_y, &state->xpick, &state->ypick);
155
156 printf("Picked tile: (%d, %d)\n", state->xpick, state->ypick);
157}
158
159static void render(void* app_state) {
160 assert(app_state);
161 State* state = (State*)(app_state);
162
163 isogfx_render(state->iso);
164 if ((state->xpick != -1) && (state->ypick != -1)) {
165 isogfx_draw_tile(state->iso, state->xpick, state->ypick, state->red);
166 }
167
168 const Pixel* screen = isogfx_get_screen_buffer(state->iso);
169 assert(screen);
170 gfx_update_texture(
171 state->screen_texture, &(TextureDataDesc){.pixels = screen});
172
173 RenderBackend* render_backend = gfx_get_render_backend(state->gfx);
174 Renderer* renderer = gfx_get_renderer(state->gfx);
175
176 gfx_start_frame(render_backend);
177 gfx_render_scene(
178 renderer, &(RenderSceneParams){
179 .mode = RenderDefault, .scene = state->scene, .camera = 0});
180 gfx_end_frame(render_backend);
181}
182
183static void resize(void* app_state, int width, int height) {
184 assert(app_state);
185 State* state = (State*)(app_state);
186
187 RenderBackend* render_backend = gfx_get_render_backend(state->gfx);
188 gfx_set_viewport(render_backend, width, height);
189}
190
191int main(int argc, const char** argv) {
192 const int initial_width = SCREEN_WIDTH;
193 const int initial_height = SCREEN_HEIGHT;
194 const int max_fps = 60;
195
196 gfx_app_run(
197 &(GfxAppDesc){
198 .argc = argc,
199 .argv = argv,
200 .width = initial_width,
201 .height = initial_height,
202 .max_fps = max_fps,
203 .update_delta_time = max_fps > 0 ? 1.0 / (double)max_fps : 0.0,
204 .title = "Isometric Renderer"},
205 &(GfxAppCallbacks){
206 .init = init,
207 .update = update,
208 .render = render,
209 .resize = resize,
210 .shutdown = shutdown});
211
212 return 0;
213}
diff --git a/gfx-iso/include/isogfx/isogfx.h b/gfx-iso/include/isogfx/isogfx.h
new file mode 100644
index 0000000..a5f7770
--- /dev/null
+++ b/gfx-iso/include/isogfx/isogfx.h
@@ -0,0 +1,66 @@
1/*
2 * Isometric rendering engine.
3 */
4#pragma once
5
6#include <stdint.h>
7
8typedef struct IsoGfx IsoGfx;
9
10typedef uint8_t Tile;
11typedef uint8_t Channel;
12
13typedef struct Pixel {
14 Channel r, g, b;
15} Pixel;
16
17typedef enum TileDescType {
18 TileFromColour,
19 TileFromFile,
20 TileFromMemory
21} TileDescType;
22
23typedef struct TileDesc {
24 TileDescType type;
25 union {
26 Pixel colour;
27 struct {
28 const char* path;
29 } file;
30 struct {
31 const void* data;
32 } mem;
33 };
34} TileDesc;
35
36typedef struct IsoGfxDesc {
37 int screen_width;
38 int screen_height;
39 int tile_width;
40 int tile_height;
41 int world_width;
42 int world_height;
43 int max_num_tiles; // 0 for an implementation-defined default.
44} IsoGfxDesc;
45
46IsoGfx* isogfx_new(const IsoGfxDesc*);
47
48void isogfx_del(IsoGfx**);
49
50Tile isogfx_make_tile(IsoGfx*, const TileDesc*);
51
52void isogfx_set_tile(IsoGfx*, int x, int y, Tile);
53
54void isogfx_set_tiles(IsoGfx*, int x0, int y0, int x1, int y1, Tile);
55
56void isogfx_pick_tile(
57 const IsoGfx*, double xcart, double ycart, int* xiso, int* yiso);
58
59void isogfx_render(IsoGfx*);
60
61void isogfx_draw_tile(IsoGfx*, int x, int y, Tile);
62
63const Pixel* isogfx_get_screen_buffer(const IsoGfx*);
64
65int isogfx_world_width(const IsoGfx*);
66int isogfx_world_height(const IsoGfx*);
diff --git a/gfx-iso/src/isogfx.c b/gfx-iso/src/isogfx.c
new file mode 100644
index 0000000..27981f9
--- /dev/null
+++ b/gfx-iso/src/isogfx.c
@@ -0,0 +1,361 @@
1#include <isogfx/isogfx.h>
2
3#include <mempool.h>
4
5#include <assert.h>
6#include <stdbool.h>
7#include <stdint.h>
8#include <stdlib.h>
9#include <string.h>
10
11/// Maximum number of tiles unless the user chooses a non-zero value.
12#define DEFAULT_MAX_NUM_TILES 1024
13
14typedef struct TileData {
15 Pixel pixels[1]; // Dynamically allocated.
16} TileData;
17
18DEF_MEMPOOL_DYN(TilePool, TileData)
19
20typedef struct IsoGfx {
21 Tile* world;
22 Pixel* screen;
23 uint8_t* tile_mask;
24 TilePool tiles;
25 int screen_width;
26 int screen_height;
27 int tile_width;
28 int tile_height;
29 int world_width;
30 int world_height;
31 int max_num_tiles;
32} IsoGfx;
33
34typedef struct ivec2 {
35 int x, y;
36} ivec2;
37
38typedef struct vec2 {
39 double x, y;
40} vec2;
41
42static inline ivec2 ivec2_add(ivec2 a, ivec2 b) {
43 return (ivec2){.x = a.x + b.x, .y = a.y + b.y};
44}
45
46static inline ivec2 ivec2_scale(ivec2 a, int s) {
47 return (ivec2){.x = a.x * s, .y = a.y * s};
48}
49
50static inline ivec2 iso2cart(ivec2 iso, int s, int t, int w) {
51 return (ivec2){
52 .x = (iso.x - iso.y) * (s / 2) + (w / 2), .y = (iso.x + iso.y) * (t / 2)};
53}
54
55static inline vec2 cart2iso(vec2 cart, int s, int t, int w) {
56 const double one_over_s = 1. / (double)s;
57 const double one_over_t = 1. / (double)t;
58 const double x = cart.x - (double)(w / 2);
59
60 return (vec2){
61 .x = (int)(one_over_s * x + one_over_t * cart.y),
62 .y = (int)(-one_over_s * x + one_over_t * cart.y)};
63}
64
65Pixel* tile_xy_mut(const IsoGfx* iso, TileData* tile, int x, int y) {
66 assert(iso);
67 assert(tile);
68 assert(tile->pixels);
69 assert(x >= 0);
70 assert(y >= 0);
71 assert(x < iso->tile_width);
72 assert(y < iso->tile_height);
73 return &tile->pixels[y * iso->tile_width + x];
74}
75
76Pixel tile_xy(const IsoGfx* iso, const TileData* tile, int x, int y) {
77 assert(iso);
78 assert(tile);
79 assert(tile->pixels);
80 assert(x >= 0);
81 assert(y >= 0);
82 assert(x < iso->tile_width);
83 assert(y < iso->tile_height);
84 return tile->pixels[y * iso->tile_width + x];
85}
86
87static inline Tile world_xy(IsoGfx* iso, int x, int y) {
88 assert(iso);
89 assert(x >= 0);
90 assert(y >= 0);
91 assert(x < iso->world_width);
92 assert(y < iso->world_height);
93 return iso->world[y * iso->world_width + x];
94}
95
96static inline Tile* world_xy_mut(IsoGfx* iso, int x, int y) {
97 assert(iso);
98 assert(x >= 0);
99 assert(y >= 0);
100 assert(x < iso->world_width);
101 assert(y < iso->world_height);
102 return &iso->world[y * iso->world_width + x];
103}
104
105static inline Pixel screen_xy(IsoGfx* iso, int x, int y) {
106 assert(iso);
107 assert(x >= 0);
108 assert(y >= 0);
109 assert(x < iso->screen_width);
110 assert(y < iso->screen_height);
111 return iso->screen[y * iso->screen_width + x];
112}
113
114static inline Pixel* screen_xy_mut(IsoGfx* iso, int x, int y) {
115 assert(iso);
116 assert(x >= 0);
117 assert(y >= 0);
118 assert(x < iso->screen_width);
119 assert(y < iso->screen_height);
120 return &iso->screen[y * iso->screen_width + x];
121}
122
123static void draw_tile(IsoGfx* iso, ivec2 so, Tile tile) {
124 assert(iso);
125
126 const TileData* data = mempool_get_block(&iso->tiles, tile);
127 assert(data);
128
129 for (int py = 0; py < iso->tile_height; ++py) {
130 for (int px = 0; px < iso->tile_width; ++px) {
131 const Pixel colour = tile_xy(iso, data, px, py);
132 const int sx = so.x + px;
133 const int sy = so.y + py;
134 if ((sx >= 0) && (sy >= 0) && (sx < iso->screen_width) &&
135 (sy < iso->screen_height)) {
136 const uint8_t mask = iso->tile_mask[py * iso->tile_width + px];
137 if (mask == 1) {
138 *screen_xy_mut(iso, sx, sy) = colour;
139 }
140 }
141 }
142 }
143}
144
145static void draw(IsoGfx* iso) {
146 assert(iso);
147
148 const int W = iso->screen_width;
149 const int H = iso->screen_height;
150
151 memset(iso->screen, 0, W * H * sizeof(Pixel));
152
153 const ivec2 o = {(iso->screen_width / 2) - (iso->tile_width / 2), 0};
154 const ivec2 x = {.x = iso->tile_width / 2, .y = iso->tile_height / 2};
155 const ivec2 y = {.x = -iso->tile_width / 2, .y = iso->tile_height / 2};
156
157 // TODO: Since the world will generally be larger than the screen, it
158 // would be best to walk in screen space and fetch the tile.
159 // The tile-centric approach might be more cache-friendly, however, since the
160 // screen-centric approach would juggle multiple tiles throughout the scan.
161 for (int ty = 0; ty < iso->world_height; ++ty) {
162 for (int tx = 0; tx < iso->world_width; ++tx) {
163 const Tile tile = world_xy(iso, tx, ty);
164 const ivec2 so =
165 ivec2_add(o, ivec2_add(ivec2_scale(x, tx), ivec2_scale(y, ty)));
166 draw_tile(iso, so, tile);
167 }
168 }
169}
170
171/// Creates a tile mask procedurally.
172static void make_tile_mask(IsoGfx* iso) {
173 assert(iso);
174 assert(iso->tile_mask);
175
176 for (int y = 0; y < iso->tile_height / 2; ++y) {
177 const int mask_start = iso->tile_width / 2 - 2 * y - 1;
178 const int mask_end = iso->tile_width / 2 + 2 * y + 1;
179 for (int x = 0; x < iso->tile_width; ++x) {
180 const bool masked = (mask_start <= x) && (x <= mask_end);
181 const uint8_t val = masked ? 1 : 0;
182
183 // Top half.
184 iso->tile_mask[y * iso->tile_width + x] = val;
185
186 // Bottom half reflects the top half.
187 const int y_reflected = iso->tile_height - y - 1;
188 iso->tile_mask[y_reflected * iso->tile_width + x] = val;
189 }
190 }
191}
192
193/// Creates a tile with a constant colour.
194static void make_tile_from_colour(
195 const IsoGfx* iso, Pixel colour, TileData* tile) {
196 assert(iso);
197 assert(tile);
198
199 for (int y = 0; y < iso->tile_height; ++y) {
200 for (int x = 0; x < iso->tile_width; ++x) {
201 *tile_xy_mut(iso, tile, x, y) = colour;
202 }
203 }
204}
205
206IsoGfx* isogfx_new(const IsoGfxDesc* desc) {
207 assert(desc->screen_width > 0);
208 assert(desc->screen_height > 0);
209 assert(desc->tile_width > 0);
210 assert(desc->tile_height > 0);
211 // Part of our implementation assumes even widths and heights for greater
212 // precision.
213 assert((desc->screen_width & 1) == 0);
214 assert((desc->screen_height & 1) == 0);
215 assert((desc->tile_width & 1) == 0);
216 assert((desc->tile_height & 1) == 0);
217
218 IsoGfx* iso = calloc(1, sizeof(IsoGfx));
219 if (!iso) {
220 return 0;
221 }
222
223 iso->screen_width = desc->screen_width;
224 iso->screen_height = desc->screen_height;
225 iso->tile_width = desc->tile_width;
226 iso->tile_height = desc->tile_height;
227 iso->world_width = desc->world_width;
228 iso->world_height = desc->world_height;
229 iso->max_num_tiles =
230 desc->max_num_tiles > 0 ? desc->max_num_tiles : DEFAULT_MAX_NUM_TILES;
231
232 const int world_size = desc->world_width * desc->world_height;
233 const int screen_size = desc->screen_width * desc->screen_height;
234 const int tile_size = desc->tile_width * desc->tile_height;
235
236 const int tile_size_bytes = tile_size * (int)sizeof(Pixel);
237
238 if (!(iso->world = calloc(world_size, sizeof(Tile)))) {
239 goto cleanup;
240 }
241 if (!(iso->screen = calloc(screen_size, sizeof(Pixel)))) {
242 goto cleanup;
243 }
244 if (!(iso->tile_mask = calloc(tile_size, sizeof(uint8_t)))) {
245 goto cleanup;
246 }
247 if (!mempool_make_dyn(&iso->tiles, iso->max_num_tiles, tile_size_bytes)) {
248 goto cleanup;
249 }
250
251 make_tile_mask(iso);
252
253 return iso;
254
255cleanup:
256 isogfx_del(&iso);
257 return 0;
258}
259
260void isogfx_del(IsoGfx** pIso) {
261 assert(pIso);
262 IsoGfx* iso = *pIso;
263 if (iso) {
264 if (iso->world) {
265 free(iso->world);
266 }
267 if (iso->screen) {
268 free(iso->screen);
269 }
270 if (iso->tile_mask) {
271 free(iso->tile_mask);
272 }
273 mempool_del(&iso->tiles);
274 free(iso);
275 }
276}
277
278Tile isogfx_make_tile(IsoGfx* iso, const TileDesc* desc) {
279 assert(iso);
280 assert(desc);
281
282 TileData* tile = mempool_alloc(&iso->tiles);
283 assert(tile); // TODO: Make this a hard assert.
284
285 switch (desc->type) {
286 case TileFromColour:
287 make_tile_from_colour(iso, desc->colour, tile);
288 break;
289 case TileFromFile:
290 assert(false); // TODO
291 break;
292 case TileFromMemory:
293 assert(false); // TODO
294 break;
295 }
296
297 return (Tile)mempool_get_block_index(&iso->tiles, tile);
298}
299
300void isogfx_set_tile(IsoGfx* iso, int x, int y, Tile tile) {
301 assert(iso);
302 *world_xy_mut(iso, x, y) = tile;
303}
304
305void isogfx_pick_tile(
306 const IsoGfx* iso, double xcart, double ycart, int* xiso, int* yiso) {
307 assert(iso);
308 assert(xiso);
309 assert(yiso);
310
311 const vec2 xy_iso = cart2iso(
312 (vec2){.x = xcart, .y = ycart}, iso->tile_width, iso->tile_height,
313 iso->screen_width);
314
315 const int x = (int)xy_iso.x;
316 const int y = (int)xy_iso.y;
317
318 if ((0 <= x) && (x < iso->world_width) && (0 <= y) &&
319 (y < iso->world_height)) {
320 *xiso = x;
321 *yiso = y;
322 } else {
323 *xiso = -1;
324 }
325}
326
327void isogfx_render(IsoGfx* iso) {
328 assert(iso);
329 draw(iso);
330}
331
332void isogfx_draw_tile(IsoGfx* iso, int x, int y, Tile tile) {
333 assert(iso);
334 assert(x >= 0);
335 assert(y >= 0);
336 assert(x < iso->world_width);
337 assert(y < iso->world_height);
338
339 const ivec2 o = {(iso->screen_width / 2) - (iso->tile_width / 2), 0};
340 const ivec2 vx = {.x = iso->tile_width / 2, .y = iso->tile_height / 2};
341 const ivec2 vy = {.x = -iso->tile_width / 2, .y = iso->tile_height / 2};
342 const ivec2 so =
343 ivec2_add(o, ivec2_add(ivec2_scale(vx, x), ivec2_scale(vy, y)));
344
345 draw_tile(iso, so, tile);
346}
347
348const Pixel* isogfx_get_screen_buffer(const IsoGfx* iso) {
349 assert(iso);
350 return iso->screen;
351}
352
353int isogfx_world_width(const IsoGfx* iso) {
354 assert(iso);
355 return iso->world_width;
356}
357
358int isogfx_world_height(const IsoGfx* iso) {
359 assert(iso);
360 return iso->world_height;
361}
diff --git a/gfx/CMakeLists.txt b/gfx/CMakeLists.txt
index f5ef44c..182e4e5 100644
--- a/gfx/CMakeLists.txt
+++ b/gfx/CMakeLists.txt
@@ -69,7 +69,8 @@ target_include_directories(gfx PRIVATE
69target_compile_options(gfx PRIVATE -std=gnu11 -Wall -Wextra -Wpedantic) 69target_compile_options(gfx PRIVATE -std=gnu11 -Wall -Wextra -Wpedantic)
70 70
71target_link_libraries(gfx PUBLIC 71target_link_libraries(gfx PUBLIC
72 cstring) 72 cstring
73 math)
73 74
74target_link_libraries(gfx PRIVATE 75target_link_libraries(gfx PRIVATE
75 cgltf 76 cgltf
@@ -78,7 +79,6 @@ target_link_libraries(gfx PRIVATE
78 glad 79 glad
79 listpool 80 listpool
80 log 81 log
81 math
82 mempool 82 mempool
83 shaders 83 shaders
84 stb 84 stb