aboutsummaryrefslogtreecommitdiff
path: root/src/core
diff options
context:
space:
mode:
Diffstat (limited to 'src/core')
-rw-r--r--src/core/buffer.c85
-rw-r--r--src/core/buffer.h26
-rw-r--r--src/core/constants.h9
-rw-r--r--src/core/core.c429
-rw-r--r--src/core/core_impl.h68
-rw-r--r--src/core/framebuffer.c151
-rw-r--r--src/core/framebuffer.h15
-rw-r--r--src/core/geometry.c326
-rw-r--r--src/core/geometry.h28
-rw-r--r--src/core/gl_util.h45
-rw-r--r--src/core/renderbuffer.c35
-rw-r--r--src/core/renderbuffer.h15
-rw-r--r--src/core/shader.c92
-rw-r--r--src/core/shader.h17
-rw-r--r--src/core/shader_program.c291
-rw-r--r--src/core/shader_program.h24
-rw-r--r--src/core/texture.c218
-rw-r--r--src/core/texture.h35
18 files changed, 1909 insertions, 0 deletions
diff --git a/src/core/buffer.c b/src/core/buffer.c
new file mode 100644
index 0000000..3b7e4bc
--- /dev/null
+++ b/src/core/buffer.c
@@ -0,0 +1,85 @@
1#include "buffer.h"
2
3#include <gfx/core.h>
4#include <gfx_assert.h>
5
6#include <math/vec2.h>
7#include <math/vec3.h>
8#include <math/vec4.h>
9
10static size_t get_buffer_size_bytes(
11 BufferType type, const BufferDataDesc* desc) {
12 return desc->count * gfx_get_buffer_type_size_bytes(type);
13}
14
15static GLenum get_buffer_usage(BufferUsage usage) {
16 switch (usage) {
17 case BufferStatic:
18 return GL_STATIC_DRAW;
19 case BufferDynamic:
20 return GL_DYNAMIC_DRAW;
21 }
22 FAIL("Unhandled buffer usage");
23 return GL_STATIC_DRAW;
24}
25
26size_t gfx_get_buffer_type_size_bytes(BufferType type) {
27 switch (type) {
28 case BufferUntyped:
29 return 1;
30 case Buffer2d:
31 return sizeof(vec2);
32 case Buffer3d:
33 return sizeof(vec3);
34 case Buffer4d:
35 return sizeof(vec4);
36 case BufferFloat:
37 return sizeof(float);
38 case BufferU8:
39 return sizeof(uint8_t);
40 case BufferU16:
41 return sizeof(uint16_t);
42 }
43 FAIL("Unhandled buffer type");
44 return 0;
45}
46
47bool gfx_init_buffer(Buffer* buffer, const BufferDesc* desc) {
48 assert(buffer);
49
50 buffer->type = desc->type;
51 buffer->usage = desc->usage;
52 buffer->size_bytes = get_buffer_size_bytes(desc->type, &desc->data);
53 const GLenum usage = get_buffer_usage(desc->usage);
54
55 glGenBuffers(1, &buffer->vbo);
56 glBindBuffer(GL_ARRAY_BUFFER, buffer->vbo);
57 glBufferData(GL_ARRAY_BUFFER, buffer->size_bytes, desc->data.data, usage);
58 glBindBuffer(GL_ARRAY_BUFFER, 0);
59 ASSERT_GL;
60
61 return true;
62}
63
64void gfx_del_buffer(Buffer* buffer) {
65 assert(buffer);
66 if (buffer->vbo) {
67 glDeleteBuffers(1, &buffer->vbo);
68 buffer->vbo = 0;
69 }
70}
71
72void gfx_update_buffer(Buffer* buffer, const BufferDataDesc* desc) {
73 assert(buffer);
74 assert(desc);
75 // OpenGL allows updating static buffers, but it is not optimal for
76 // performance, so we enforce data in static buffers remain static.
77 assert(buffer->usage == BufferDynamic);
78
79 const size_t update_size_bytes = get_buffer_size_bytes(buffer->type, desc);
80 assert(update_size_bytes <= buffer->size_bytes);
81
82 glBindBuffer(GL_ARRAY_BUFFER, buffer->vbo);
83 glBufferSubData(GL_ARRAY_BUFFER, 0, update_size_bytes, desc->data);
84 glBindBuffer(GL_ARRAY_BUFFER, 0);
85}
diff --git a/src/core/buffer.h b/src/core/buffer.h
new file mode 100644
index 0000000..b9080f0
--- /dev/null
+++ b/src/core/buffer.h
@@ -0,0 +1,26 @@
1#pragma once
2
3#include <gfx/core.h>
4
5#include "gl_util.h"
6
7#include <math/fwd.h>
8
9#include <stdbool.h>
10#include <stddef.h>
11
12typedef struct Buffer {
13 GLuint vbo;
14 BufferType type;
15 BufferUsage usage;
16 size_t size_bytes;
17} Buffer;
18
19/// Return the buffer type size in bytes.
20size_t gfx_get_buffer_type_size_bytes(BufferType);
21
22/// Create a buffer from raw data.
23bool gfx_init_buffer(Buffer*, const BufferDesc*);
24
25/// Destroy the buffer.
26void gfx_del_buffer(Buffer*);
diff --git a/src/core/constants.h b/src/core/constants.h
new file mode 100644
index 0000000..a6a3b94
--- /dev/null
+++ b/src/core/constants.h
@@ -0,0 +1,9 @@
1#pragma once
2
3// Shaders vertex attribute locations must match the channels here.
4#define GFX_POSITION_CHANNEL 0
5#define GFX_NORMAL_CHANNEL 1
6#define GFX_TANGENT_CHANNEL 2
7#define GFX_TEXCOORDS_CHANNEL 3
8#define GFX_JOINTS_CHANNEL 4
9#define GFX_WEIGHTS_CHANNEL 5
diff --git a/src/core/core.c b/src/core/core.c
new file mode 100644
index 0000000..90038c6
--- /dev/null
+++ b/src/core/core.c
@@ -0,0 +1,429 @@
1#include "core_impl.h"
2
3#include "gl_util.h"
4
5// #include <log/log.h>
6
7#include <assert.h>
8
9void gfx_init_gfxcore(GfxCore* gfxcore) {
10 assert(gfxcore);
11
12 mempool_make(&gfxcore->buffers);
13 mempool_make(&gfxcore->framebuffers);
14 mempool_make(&gfxcore->geometries);
15 mempool_make(&gfxcore->renderbuffers);
16 mempool_make(&gfxcore->shaders);
17 mempool_make(&gfxcore->shader_programs);
18 mempool_make(&gfxcore->textures);
19
20 mempool_make(&gfxcore->shader_cache);
21 mempool_make(&gfxcore->program_cache);
22
23 glEnable(GL_CULL_FACE);
24 glFrontFace(GL_CCW);
25 glCullFace(GL_BACK);
26
27 glEnable(GL_DEPTH_TEST);
28
29 // Filter cubemaps across their faces to avoid seams.
30 // https://www.khronos.org/opengl/wiki/Cubemap_Texture#Seamless_cubemap
31 glEnable(GL_TEXTURE_CUBE_MAP_SEAMLESS);
32}
33
34// Conveniently destroy any objects that have not been destroyed by the
35// application.
36void gfx_del_gfxcore(GfxCore* gfxcore) {
37 assert(gfxcore);
38
39 mempool_foreach(&gfxcore->buffers, buffer, { gfx_del_buffer(buffer); });
40
41 mempool_foreach(&gfxcore->framebuffers, framebuffer, {
42 gfx_del_framebuffer(framebuffer);
43 });
44
45 mempool_foreach(
46 &gfxcore->geometries, geometry, { gfx_del_geometry(geometry); });
47
48 mempool_foreach(&gfxcore->renderbuffers, renderbuffer, {
49 gfx_del_renderbuffer(renderbuffer);
50 });
51
52 mempool_foreach(
53 &gfxcore->shader_programs, prog, { gfx_del_shader_program(prog); });
54
55 mempool_foreach(&gfxcore->shaders, shader, { gfx_del_shader(shader); });
56
57 mempool_foreach(&gfxcore->textures, texture, { gfx_del_texture(texture); });
58}
59
60// -----------------------------------------------------------------------------
61// Render commands.
62// -----------------------------------------------------------------------------
63
64void gfx_start_frame(GfxCore* gfxcore) {
65 assert(gfxcore);
66
67 glViewport(
68 gfxcore->viewport.x, gfxcore->viewport.y, gfxcore->viewport.width,
69 gfxcore->viewport.height);
70 glClearColor(0, 0, 0, 0);
71 glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
72
73 ASSERT_GL;
74}
75
76void gfx_end_frame(GfxCore* gfxcore) {
77 assert(gfxcore);
78 ASSERT_GL;
79}
80
81void gfx_set_viewport(GfxCore* gfxcore, int x, int y, int width, int height) {
82 assert(gfxcore);
83 gfxcore->viewport =
84 (Viewport){.x = x, .y = y, .width = width, .height = height};
85}
86
87void gfx_get_viewport(
88 GfxCore* gfxcore, int* x, int* y, int* width, int* height) {
89 assert(gfxcore);
90 assert(x);
91 assert(y);
92 assert(width);
93 assert(height);
94
95 *x = gfxcore->viewport.x;
96 *y = gfxcore->viewport.y;
97 *width = gfxcore->viewport.width;
98 *height = gfxcore->viewport.height;
99}
100
101void gfx_clear(GfxCore* gfxcore, vec4 colour) {
102 assert(gfxcore);
103
104 glClearColor(colour.x, colour.y, colour.z, colour.w);
105 glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
106}
107
108void gfx_set_blending(GfxCore* gfxcore, bool enable) {
109 assert(gfxcore);
110 if (enable) {
111 glEnable(GL_BLEND);
112 glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
113 } else {
114 glDisable(GL_BLEND);
115 }
116}
117
118void gfx_set_depth_mask(GfxCore* gfxcore, bool enable) {
119 assert(gfxcore);
120 glDepthMask(enable ? GL_TRUE : GL_FALSE);
121}
122
123void gfx_set_culling(GfxCore* gfxcore, bool enable) {
124 assert(gfxcore);
125 if (enable) {
126 glEnable(GL_CULL_FACE);
127 } else {
128 glDisable(GL_CULL_FACE);
129 }
130}
131
132void gfx_set_polygon_offset(GfxCore* gfxcore, float scale, float bias) {
133 assert(gfxcore);
134 if ((scale != 0.0f) || (bias != 0.0f)) {
135 glEnable(GL_POLYGON_OFFSET_FILL);
136 } else {
137 glDisable(GL_POLYGON_OFFSET_FILL);
138 }
139 glPolygonOffset(scale, bias);
140}
141
142void gfx_reset_polygon_offset(GfxCore* gfxcore) {
143 assert(gfxcore);
144 glPolygonOffset(0, 0);
145 glDisable(GL_POLYGON_OFFSET_FILL);
146}
147
148// -----------------------------------------------------------------------------
149// Buffers.
150// -----------------------------------------------------------------------------
151
152Buffer* gfx_make_buffer(GfxCore* gfxcore, const BufferDesc* desc) {
153 assert(gfxcore);
154 assert(desc);
155
156 Buffer* buffer = mempool_alloc(&gfxcore->buffers);
157 if (!gfx_init_buffer(buffer, desc)) {
158 mempool_free(&gfxcore->buffers, &buffer);
159 return 0;
160 }
161 return buffer;
162}
163
164void gfx_destroy_buffer(GfxCore* gfxcore, Buffer** buffer) {
165 assert(gfxcore);
166 assert(buffer);
167 if (*buffer) {
168 gfx_del_buffer(*buffer);
169 mempool_free(&gfxcore->buffers, buffer);
170 }
171}
172
173// -----------------------------------------------------------------------------
174// Geometry.
175// -----------------------------------------------------------------------------
176
177Geometry* gfx_make_geometry(GfxCore* gfxcore, const GeometryDesc* desc) {
178 assert(gfxcore);
179 assert(desc);
180
181 Geometry* geometry = mempool_alloc(&gfxcore->geometries);
182 if (!gfx_init_geometry(geometry, gfxcore, desc)) {
183 mempool_free(&gfxcore->geometries, &geometry);
184 return 0;
185 }
186 return geometry;
187}
188
189void gfx_destroy_geometry(GfxCore* gfxcore, Geometry** geometry) {
190 assert(gfxcore);
191 assert(geometry);
192
193 if (*geometry) {
194 gfx_del_geometry(*geometry);
195 mempool_free(&gfxcore->geometries, geometry);
196 }
197}
198
199// -----------------------------------------------------------------------------
200// Textures.
201// -----------------------------------------------------------------------------
202
203Texture* gfx_make_texture(GfxCore* gfxcore, const TextureDesc* desc) {
204 assert(gfxcore);
205 assert(desc);
206
207 Texture* texture = mempool_alloc(&gfxcore->textures);
208 if (!gfx_init_texture(texture, desc)) {
209 mempool_free(&gfxcore->textures, &texture);
210 return 0;
211 }
212 return texture;
213}
214
215void gfx_destroy_texture(GfxCore* gfxcore, Texture** texture) {
216 assert(gfxcore);
217 assert(texture);
218 assert(*texture);
219
220 if (*texture) {
221 gfx_del_texture(*texture);
222 mempool_free(&gfxcore->textures, texture);
223 }
224}
225
226// -----------------------------------------------------------------------------
227// Renderbuffers.
228// -----------------------------------------------------------------------------
229
230RenderBuffer* gfx_make_renderbuffer(
231 GfxCore* gfxcore, const RenderBufferDesc* desc) {
232 assert(gfxcore);
233 assert(desc);
234
235 RenderBuffer* renderbuffer = mempool_alloc(&gfxcore->renderbuffers);
236 if (!gfx_init_renderbuffer(renderbuffer, desc)) {
237 mempool_free(&gfxcore->renderbuffers, &renderbuffer);
238 }
239 return renderbuffer;
240}
241
242void gfx_destroy_renderbuffer(GfxCore* gfxcore, RenderBuffer** renderbuffer) {
243 assert(gfxcore);
244 assert(renderbuffer);
245 assert(*renderbuffer);
246
247 if (*renderbuffer) {
248 gfx_del_renderbuffer(*renderbuffer);
249 mempool_free(&gfxcore->renderbuffers, renderbuffer);
250 }
251}
252
253// -----------------------------------------------------------------------------
254// Framebuffers.
255// -----------------------------------------------------------------------------
256
257FrameBuffer* gfx_make_framebuffer(
258 GfxCore* gfxcore, const FrameBufferDesc* desc) {
259 assert(gfxcore);
260 assert(desc);
261
262 FrameBuffer* framebuffer = mempool_alloc(&gfxcore->framebuffers);
263 if (!gfx_init_framebuffer(framebuffer, desc)) {
264 mempool_free(&gfxcore->framebuffers, &framebuffer);
265 return 0;
266 }
267 return framebuffer;
268}
269
270void gfx_destroy_framebuffer(GfxCore* gfxcore, FrameBuffer** framebuffer) {
271 assert(gfxcore);
272 assert(framebuffer);
273 assert(*framebuffer);
274
275 if (*framebuffer) {
276 gfx_del_framebuffer(*framebuffer);
277 mempool_free(&gfxcore->framebuffers, framebuffer);
278 }
279}
280
281// -----------------------------------------------------------------------------
282// Shaders.
283// -----------------------------------------------------------------------------
284
285static uint64_t hash_shader_desc(const ShaderDesc* desc) {
286 assert(desc);
287 // Note that defines may affect shader permutations, so we need to hash those
288 // as well.
289 uint64_t hash = 0;
290 for (size_t i = 0; i < desc->num_defines; ++i) {
291 const ShaderCompilerDefine* define = &desc->defines[i];
292 hash = (((hash << 13) + sstring_hash(define->name)) << 7) +
293 sstring_hash(define->value);
294 }
295 return (hash << 17) + cstring_hash(desc->code);
296}
297
298static uint64_t hash_program_desc(const ShaderProgramDesc* desc) {
299 assert(desc);
300 return ((uint64_t)desc->vertex_shader->id << 32) |
301 (uint64_t)desc->fragment_shader->id;
302}
303
304static Shader* find_cached_shader(ShaderCache* cache, uint64_t hash) {
305 assert(cache);
306 mempool_foreach(cache, entry, {
307 if (entry->hash == hash) {
308 return entry->shader;
309 }
310 });
311 return 0;
312}
313
314static ShaderProgram* find_cached_program(ProgramCache* cache, uint64_t hash) {
315 assert(cache);
316 mempool_foreach(cache, entry, {
317 if (entry->hash == hash) {
318 return entry->program;
319 }
320 });
321 return 0;
322}
323
324static ShaderCacheEntry* find_shader_cache_entry(
325 ShaderCache* cache, const Shader* shader) {
326 assert(cache);
327 assert(shader);
328 mempool_foreach(cache, entry, {
329 if (entry->shader == shader) {
330 return entry;
331 }
332 });
333 return 0;
334}
335
336static ShaderProgramCacheEntry* find_program_cache_entry(
337 ProgramCache* cache, const ShaderProgram* prog) {
338 assert(cache);
339 assert(prog);
340 mempool_foreach(cache, entry, {
341 if (entry->program == prog) {
342 return entry;
343 }
344 });
345 return 0;
346}
347
348Shader* gfx_make_shader(GfxCore* gfxcore, const ShaderDesc* desc) {
349 assert(gfxcore);
350 assert(desc);
351
352 // Check the shader cache first.
353 ShaderCache* cache = &gfxcore->shader_cache;
354 const uint64_t hash = hash_shader_desc(desc);
355 Shader* shader = find_cached_shader(cache, hash);
356 if (shader) {
357 // LOGD("Found cached shader with hash [%lx]", hash);
358 return shader;
359 }
360
361 shader = mempool_alloc(&gfxcore->shaders);
362 if (!shader) {
363 return 0;
364 }
365 if (!gfx_compile_shader(shader, desc)) {
366 mempool_free(&gfxcore->shaders, &shader);
367 return 0;
368 }
369 ShaderCacheEntry* entry = mempool_alloc(cache);
370 *entry = (ShaderCacheEntry){.hash = hash, .shader = shader};
371 // LOGD("Added shader with hash [%lx] to cache", hash);
372 return shader;
373}
374
375void gfx_destroy_shader(GfxCore* gfxcore, Shader** shader) {
376 assert(gfxcore);
377 assert(shader);
378
379 if (*shader) {
380 // Remove the shader from the cache.
381 ShaderCache* cache = &gfxcore->shader_cache;
382 ShaderCacheEntry* entry = find_shader_cache_entry(cache, *shader);
383 assert(entry); // Must be there, shaders can't go untracked.
384 mempool_free(cache, &entry);
385
386 gfx_del_shader(*shader);
387 mempool_free(&gfxcore->shaders, shader);
388 }
389}
390
391ShaderProgram* gfx_make_shader_program(
392 GfxCore* gfxcore, const ShaderProgramDesc* desc) {
393 assert(gfxcore);
394 assert(desc);
395
396 // Check the shader program cache first.
397 ProgramCache* cache = &gfxcore->program_cache;
398 const uint64_t hash = hash_program_desc(desc);
399 ShaderProgram* prog = find_cached_program(cache, hash);
400 if (prog) {
401 // LOGD("Found cached shader program with hash [%lx]", hash);
402 return prog;
403 }
404
405 prog = mempool_alloc(&gfxcore->shader_programs);
406 if (!gfx_build_shader_program(prog, desc)) {
407 mempool_free(&gfxcore->shader_programs, &prog);
408 return 0;
409 }
410 ShaderProgramCacheEntry* entry = mempool_alloc(cache);
411 *entry = (ShaderProgramCacheEntry){.hash = hash, .program = prog};
412 // LOGD("Added shader program with hash [%lx] to cache", hash);
413 return prog;
414}
415
416void gfx_destroy_shader_program(GfxCore* gfxcore, ShaderProgram** prog) {
417 assert(gfxcore);
418 assert(prog);
419 if (*prog) {
420 // Remove the shader program from the cache.
421 ProgramCache* cache = &gfxcore->program_cache;
422 ShaderProgramCacheEntry* entry = find_program_cache_entry(cache, *prog);
423 assert(entry); // Must be there, shaders can't go untracked.
424 mempool_free(cache, &entry);
425
426 gfx_del_shader_program(*prog);
427 mempool_free(&gfxcore->shader_programs, prog);
428 }
429}
diff --git a/src/core/core_impl.h b/src/core/core_impl.h
new file mode 100644
index 0000000..eefdfbe
--- /dev/null
+++ b/src/core/core_impl.h
@@ -0,0 +1,68 @@
1#pragma once
2
3#include <gfx/core.h>
4#include <gfx/sizes.h>
5
6#include "buffer.h"
7#include "framebuffer.h"
8#include "geometry.h"
9#include "renderbuffer.h"
10#include "shader.h"
11#include "shader_program.h"
12#include "texture.h"
13
14#include <mempool.h>
15
16#include <stdint.h>
17
18// TODO: Make a generic (hash, void*) structure and define functions over it.
19// Then define a macro that defines type-safe macros given the type of the
20// entry.
21typedef struct ShaderCacheEntry {
22 uint64_t hash;
23 Shader* shader;
24} ShaderCacheEntry;
25
26typedef struct ShaderProgramCacheEntry {
27 uint64_t hash;
28 ShaderProgram* program;
29} ShaderProgramCacheEntry;
30
31DEF_MEMPOOL(buffer_pool, Buffer, GFX_MAX_NUM_BUFFERS)
32DEF_MEMPOOL(framebuffer_pool, FrameBuffer, GFX_MAX_NUM_FRAMEBUFFERS)
33DEF_MEMPOOL(geometry_pool, Geometry, GFX_MAX_NUM_GEOMETRIES)
34DEF_MEMPOOL(renderbuffer_pool, RenderBuffer, GFX_MAX_NUM_RENDERBUFFERS)
35DEF_MEMPOOL(shader_pool, Shader, GFX_MAX_NUM_SHADERS)
36DEF_MEMPOOL(shader_program_pool, ShaderProgram, GFX_MAX_NUM_SHADER_PROGRAMS)
37DEF_MEMPOOL(texture_pool, Texture, GFX_MAX_NUM_TEXTURES)
38
39DEF_MEMPOOL(ShaderCache, ShaderCacheEntry, GFX_MAX_NUM_SHADERS)
40DEF_MEMPOOL(ProgramCache, ShaderProgramCacheEntry, GFX_MAX_NUM_SHADER_PROGRAMS)
41
42typedef struct {
43 int x;
44 int y;
45 int width;
46 int height;
47} Viewport;
48
49typedef struct GfxCore {
50 Viewport viewport;
51 // mempools for render-specific objects: textures, geometry, etc.
52 buffer_pool buffers;
53 framebuffer_pool framebuffers;
54 geometry_pool geometries;
55 renderbuffer_pool renderbuffers;
56 shader_pool shaders;
57 shader_program_pool shader_programs;
58 texture_pool textures;
59 // Caches.
60 ShaderCache shader_cache;
61 ProgramCache program_cache;
62} GfxCore;
63
64/// Create a new render backend.
65void gfx_init_gfxcore(GfxCore*);
66
67/// Destroy the render backend.
68void gfx_del_gfxcore(GfxCore*);
diff --git a/src/core/framebuffer.c b/src/core/framebuffer.c
new file mode 100644
index 0000000..76d9002
--- /dev/null
+++ b/src/core/framebuffer.c
@@ -0,0 +1,151 @@
1#include "framebuffer.h"
2
3#include "renderbuffer.h"
4#include "texture.h"
5
6#include <gfx_assert.h>
7
8#include <error.h>
9
10static void framebuffer_attach_colour(
11 FrameBuffer* framebuffer, const FrameBufferAttachment* attachment) {
12 assert(framebuffer);
13 assert(attachment);
14
15 switch (attachment->type) {
16 case FrameBufferNoAttachment:
17 break;
18 case FrameBufferTexture:
19 glFramebufferTexture2D(
20 GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D,
21 attachment->texture.texture->id, attachment->texture.mip_level);
22 break;
23 case FrameBufferCubemapTexture:
24 glFramebufferTexture2D(
25 GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0,
26 to_GL_cubemap_face(attachment->cubemap.face),
27 attachment->cubemap.texture->id, attachment->cubemap.mip_level);
28 break;
29 case FrameBufferRenderBuffer:
30 glFramebufferRenderbuffer(
31 GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER,
32 attachment->renderbuffer->id);
33 break;
34 }
35
36 ASSERT_GL;
37}
38
39static void framebuffer_attach_depth(
40 FrameBuffer* framebuffer, const FrameBufferAttachment* attachment) {
41 assert(framebuffer);
42 assert(attachment);
43
44 switch (attachment->type) {
45 case FrameBufferNoAttachment:
46 break;
47 case FrameBufferTexture:
48 glFramebufferTexture2D(
49 GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_DEPTH_COMPONENT,
50 attachment->texture.texture->id, attachment->texture.mip_level);
51 break;
52 // TODO: Could distinguish between colour and depth attachment types to make
53 // this a compile-time error.
54 case FrameBufferCubemapTexture:
55 log_error("Cannot use a cubemap texture as a depth framebuffer attachment");
56 break;
57 case FrameBufferRenderBuffer:
58 glFramebufferRenderbuffer(
59 GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_RENDERBUFFER,
60 attachment->renderbuffer->id);
61 break;
62 }
63
64 ASSERT_GL;
65}
66
67bool gfx_init_framebuffer(
68 FrameBuffer* framebuffer, const FrameBufferDesc* desc) {
69 assert(framebuffer);
70 assert(desc);
71
72 glGenFramebuffers(1, &framebuffer->id);
73 if (!framebuffer->id) {
74 log_error("glGenFramebuffers() failed");
75 return false;
76 }
77
78 // Allow incomplete framebuffers for flexibility.
79 // Attach buffers and check the framebuffer status only if buffers are given
80 // up front.
81 if (desc->colour.type != FrameBufferNoAttachment ||
82 desc->depth.type != FrameBufferNoAttachment) {
83 // TODO: Could use the "named" API to avoid having to bind the framebuffer.
84 glBindFramebuffer(GL_FRAMEBUFFER, framebuffer->id);
85 framebuffer_attach_colour(framebuffer, &desc->colour);
86 framebuffer_attach_depth(framebuffer, &desc->depth);
87 if (glCheckFramebufferStatus(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE) {
88 log_error("glCheckFramebufferStatus() failed");
89 gfx_del_framebuffer(framebuffer);
90 return false;
91 }
92 glBindFramebuffer(GL_FRAMEBUFFER, 0);
93 }
94
95 ASSERT_GL;
96 return true;
97}
98
99bool gfx_framebuffer_attach_colour(
100 FrameBuffer* framebuffer, const FrameBufferAttachment* attachment) {
101 assert(framebuffer);
102 assert(attachment);
103
104 // TODO: Could use the "named" API to avoid having to bind the framebuffer.
105 glBindFramebuffer(GL_FRAMEBUFFER, framebuffer->id);
106 framebuffer_attach_colour(framebuffer, attachment);
107 if (glCheckFramebufferStatus(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE) {
108 log_error("glCheckFramebufferStatus() failed");
109 return false;
110 }
111 return true;
112}
113
114bool gfx_framebuffer_attach_depth(
115 FrameBuffer* framebuffer, const FrameBufferAttachment* attachment) {
116 assert(framebuffer);
117 assert(attachment);
118
119 // TODO: Could use the "named" API to avoid having to bind the framebuffer.
120 glBindFramebuffer(GL_FRAMEBUFFER, framebuffer->id);
121 framebuffer_attach_depth(framebuffer, attachment);
122 if (glCheckFramebufferStatus(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE) {
123 log_error("glCheckFramebufferStatus() failed");
124 return false;
125 }
126 return true;
127}
128
129void gfx_del_framebuffer(FrameBuffer* framebuffer) {
130 assert(framebuffer);
131 if (framebuffer->id) {
132 glDeleteFramebuffers(1, &framebuffer->id);
133 framebuffer->id = 0;
134 }
135}
136
137void gfx_activate_framebuffer(const FrameBuffer* framebuffer) {
138 assert(framebuffer);
139 glBindFramebuffer(GL_FRAMEBUFFER, framebuffer->id);
140}
141
142void gfx_deactivate_framebuffer(const FrameBuffer* framebuffer) {
143 assert(framebuffer);
144 glBindFramebuffer(GL_FRAMEBUFFER, 0);
145}
146
147void gfx_framebuffer_set_viewport(
148 FrameBuffer* framebuffer, int x, int y, int width, int height) {
149 assert(framebuffer);
150 glViewport(x, y, width, height);
151}
diff --git a/src/core/framebuffer.h b/src/core/framebuffer.h
new file mode 100644
index 0000000..1a3439c
--- /dev/null
+++ b/src/core/framebuffer.h
@@ -0,0 +1,15 @@
1#pragma once
2
3#include <gfx/core.h>
4
5#include "gl_util.h"
6
7typedef struct FrameBuffer {
8 GLuint id;
9} FrameBuffer;
10
11/// Create a new framebuffer.
12bool gfx_init_framebuffer(FrameBuffer*, const FrameBufferDesc*);
13
14/// Destroy the framebuffer.
15void gfx_del_framebuffer(FrameBuffer*);
diff --git a/src/core/geometry.c b/src/core/geometry.c
new file mode 100644
index 0000000..cfc749f
--- /dev/null
+++ b/src/core/geometry.c
@@ -0,0 +1,326 @@
1#include "geometry.h"
2
3#include "buffer.h"
4#include "constants.h"
5
6#include <gfx_assert.h>
7
8#include <math/vec2.h>
9#include <math/vec3.h>
10
11/// Determines whether a view is populated.
12///
13/// Note that views are allowed to have no data, in which case a buffer of the
14/// specified size is created.
15#define view_is_populated(BUFFER_VIEW) (BUFFER_VIEW.size_bytes > 0)
16
17static GLenum primitive_type_to_gl(PrimitiveType type) {
18 switch (type) {
19 case Triangles:
20 return GL_TRIANGLES;
21 case TriangleFan:
22 return GL_TRIANGLE_FAN;
23 case TriangleStrip:
24 return GL_TRIANGLE_STRIP;
25 }
26 FAIL("primitive_type_to_gl(): missing case");
27 return GL_INVALID_ENUM;
28}
29
30/// Create a typed buffer for the buffer view if the view does not already point
31/// to a buffer.
32void init_view_buffer(
33 GfxCore* gfxcore, BufferView* view, BufferType buffer_type,
34 BufferUsage buffer_usage) {
35 if (!view->buffer) {
36 view->buffer = gfx_make_buffer(
37 gfxcore,
38 &(BufferDesc){
39 .usage = buffer_usage,
40 .type = buffer_type,
41 .data.data = view->data,
42 .data.count = view->size_bytes /
43 gfx_get_buffer_type_size_bytes(buffer_type)});
44 }
45 assert(view->size_bytes <= view->buffer->size_bytes);
46}
47
48/// Configure the buffer in teh VAO.
49static void configure_buffer(
50 GfxCore* gfxcore, const GeometryDesc* desc, BufferView* view,
51 size_t num_components, size_t component_size_bytes, GLenum component_type,
52 GLboolean normalized, GLuint channel) {
53 assert(gfxcore);
54 assert(desc);
55 assert(view);
56 assert(view->buffer);
57 assert(
58 desc->num_verts <=
59 view->size_bytes / (num_components * component_size_bytes));
60 assert(view->size_bytes <= view->buffer->size_bytes);
61
62 glBindBuffer(GL_ARRAY_BUFFER, view->buffer->vbo);
63 glEnableVertexAttribArray(channel);
64 if ((component_type == GL_FLOAT) || normalized) {
65 glVertexAttribPointer(
66 channel, num_components, component_type, normalized, view->stride_bytes,
67 (const void*)view->offset_bytes);
68 } else {
69 assert(!normalized);
70 assert(
71 (component_type == GL_BYTE) || (component_type == GL_UNSIGNED_BYTE) ||
72 (component_type == GL_SHORT) || (component_type == GL_UNSIGNED_SHORT) ||
73 (component_type == GL_INT) || component_type == GL_UNSIGNED_INT);
74 glVertexAttribIPointer(
75 channel, num_components, component_type, view->stride_bytes,
76 (const void*)view->offset_bytes);
77 }
78 glBindBuffer(GL_ARRAY_BUFFER, 0);
79}
80
81static bool configure_vertex_attributes(GfxCore* gfxcore, GeometryDesc* desc) {
82 assert(gfxcore);
83 assert(desc);
84
85 if (view_is_populated(desc->positions3d)) {
86 init_view_buffer(
87 gfxcore, (BufferView*)&desc->positions3d, Buffer3d, desc->buffer_usage);
88 if (!desc->positions3d.buffer) {
89 return false;
90 }
91 configure_buffer(
92 gfxcore, desc, (BufferView*)&desc->positions3d, 3, sizeof(float),
93 GL_FLOAT, GL_FALSE, GFX_POSITION_CHANNEL);
94 } else if (view_is_populated(desc->positions2d)) {
95 init_view_buffer(
96 gfxcore, (BufferView*)&desc->positions2d, Buffer2d, desc->buffer_usage);
97 if (!desc->positions2d.buffer) {
98 return false;
99 }
100 configure_buffer(
101 gfxcore, desc, (BufferView*)&desc->positions2d, 2, sizeof(float),
102 GL_FLOAT, GL_FALSE, GFX_POSITION_CHANNEL);
103 }
104 if (view_is_populated(desc->normals)) {
105 init_view_buffer(
106 gfxcore, (BufferView*)&desc->normals, Buffer3d, desc->buffer_usage);
107 if (!desc->normals.buffer) {
108 return false;
109 }
110 configure_buffer(
111 gfxcore, desc, (BufferView*)&desc->normals, 3, sizeof(float), GL_FLOAT,
112 GL_FALSE, GFX_NORMAL_CHANNEL);
113 }
114 if (view_is_populated(desc->tangents)) {
115 init_view_buffer(
116 gfxcore, (BufferView*)&desc->tangents, Buffer4d, desc->buffer_usage);
117 if (!desc->tangents.buffer) {
118 return false;
119 }
120 configure_buffer(
121 gfxcore, desc, (BufferView*)&desc->tangents, 4, sizeof(float), GL_FLOAT,
122 GL_FALSE, GFX_TANGENT_CHANNEL);
123 }
124 if (view_is_populated(desc->texcoords)) {
125 init_view_buffer(
126 gfxcore, (BufferView*)&desc->texcoords, Buffer2d, desc->buffer_usage);
127 if (!desc->texcoords.buffer) {
128 return false;
129 }
130 configure_buffer(
131 gfxcore, desc, (BufferView*)&desc->texcoords, 2, sizeof(float),
132 GL_FLOAT, GL_FALSE, GFX_TEXCOORDS_CHANNEL);
133 }
134 if (view_is_populated(desc->joints.u8)) {
135 init_view_buffer(
136 gfxcore, (BufferView*)&desc->joints.u8, BufferU8, desc->buffer_usage);
137 if (!desc->joints.u8.buffer) {
138 return false;
139 }
140 configure_buffer(
141 gfxcore, desc, (BufferView*)&desc->joints.u8, 4, sizeof(uint8_t),
142 GL_UNSIGNED_BYTE, GL_FALSE, GFX_JOINTS_CHANNEL);
143 } else if (view_is_populated(desc->joints.u16)) {
144 init_view_buffer(
145 gfxcore, (BufferView*)&desc->joints.u16, BufferU16, desc->buffer_usage);
146 if (!desc->joints.u16.buffer) {
147 return false;
148 }
149 configure_buffer(
150 gfxcore, desc, (BufferView*)&desc->joints.u16, 4, sizeof(uint16_t),
151 GL_UNSIGNED_SHORT, GL_FALSE, GFX_JOINTS_CHANNEL);
152 }
153
154 // If weights are given as unsigned integers, then they are normalized
155 // when read by the shader.
156 if (view_is_populated(desc->weights.u8)) {
157 init_view_buffer(
158 gfxcore, (BufferView*)&desc->weights.u8, BufferU8, desc->buffer_usage);
159 if (!desc->weights.u8.buffer) {
160 return false;
161 }
162 configure_buffer(
163 gfxcore, desc, (BufferView*)&desc->weights.u8, 4, sizeof(uint8_t),
164 GL_UNSIGNED_BYTE, GL_TRUE, GFX_WEIGHTS_CHANNEL);
165 } else if (view_is_populated(desc->weights.u16)) {
166 init_view_buffer(
167 gfxcore, (BufferView*)&desc->weights.u16, BufferU16,
168 desc->buffer_usage);
169 if (!desc->weights.u16.buffer) {
170 return false;
171 }
172 configure_buffer(
173 gfxcore, desc, (BufferView*)&desc->weights.u16, 4, sizeof(uint16_t),
174 GL_UNSIGNED_SHORT, GL_TRUE, GFX_WEIGHTS_CHANNEL);
175 } else if (view_is_populated(desc->weights.floats)) {
176 init_view_buffer(
177 gfxcore, (BufferView*)&desc->weights.floats, BufferFloat,
178 desc->buffer_usage);
179 if (!desc->weights.floats.buffer) {
180 return false;
181 }
182 configure_buffer(
183 gfxcore, desc, (BufferView*)&desc->weights.floats, 4, sizeof(float),
184 GL_FLOAT, GL_FALSE, GFX_WEIGHTS_CHANNEL);
185 }
186
187 return true;
188}
189
190static bool configure_indices(GfxCore* gfxcore, GeometryDesc* desc) {
191 assert(gfxcore);
192 assert(desc);
193
194 if (view_is_populated(desc->indices8)) {
195 assert(desc->num_indices > 0);
196 assert(
197 desc->num_indices <= desc->indices8.size_bytes / sizeof(VertexIndex8));
198 init_view_buffer(
199 gfxcore, (BufferView*)&desc->indices8, BufferU8, desc->buffer_usage);
200 if (!desc->indices8.buffer) {
201 return false;
202 }
203 } else if (view_is_populated(desc->indices16)) {
204 assert(desc->num_indices > 0);
205 assert(
206 desc->num_indices <=
207 desc->indices16.size_bytes / sizeof(VertexIndex16));
208 init_view_buffer(
209 gfxcore, (BufferView*)&desc->indices16, BufferU16, desc->buffer_usage);
210 if (!desc->indices16.buffer) {
211 return false;
212 }
213 }
214
215 return true;
216}
217
218bool gfx_init_geometry(
219 Geometry* geometry, GfxCore* gfxcore, const GeometryDesc* input_desc) {
220 assert(geometry);
221 assert(gfxcore);
222 assert(input_desc);
223 assert(
224 view_is_populated(input_desc->positions3d) ||
225 view_is_populated(input_desc->positions2d));
226 assert(input_desc->num_verts > 0);
227
228 geometry->mode = primitive_type_to_gl(input_desc->type);
229 geometry->desc = *input_desc;
230 geometry->num_verts = input_desc->num_verts;
231 geometry->gfxcore = gfxcore;
232
233 // The geometry's copy of the descriptor is manipulated below. Create a
234 // shorter name for it.
235 GeometryDesc* desc = &geometry->desc;
236
237 glGenVertexArrays(1, &geometry->vao);
238 glBindVertexArray(geometry->vao);
239 if (!configure_vertex_attributes(gfxcore, desc)) {
240 goto cleanup;
241 }
242 if (!configure_indices(gfxcore, desc)) {
243 goto cleanup;
244 }
245 glBindVertexArray(0);
246 ASSERT_GL;
247
248 return true;
249
250cleanup:
251 gfx_del_geometry(geometry);
252 return 0;
253}
254
255void gfx_del_geometry(Geometry* geometry) {
256 assert(geometry);
257 if (geometry->vao) {
258 glDeleteVertexArrays(1, &geometry->vao);
259 geometry->vao = 0;
260 }
261}
262
263void gfx_update_geometry(Geometry* geometry, const GeometryDesc* desc) {
264 assert(geometry);
265 assert(desc);
266 // New geometry size cannot exceed original size.
267 assert(desc->positions3d.size_bytes <= geometry->desc.positions3d.size_bytes);
268 assert(desc->positions2d.size_bytes <= geometry->desc.positions2d.size_bytes);
269 assert(desc->normals.size_bytes <= geometry->desc.normals.size_bytes);
270 assert(desc->tangents.size_bytes <= geometry->desc.tangents.size_bytes);
271 assert(desc->texcoords.size_bytes <= geometry->desc.texcoords.size_bytes);
272 assert(desc->joints.u8.size_bytes <= geometry->desc.joints.u8.size_bytes);
273 assert(desc->joints.u16.size_bytes <= geometry->desc.joints.u16.size_bytes);
274 assert(desc->weights.u8.size_bytes <= geometry->desc.weights.u8.size_bytes);
275 assert(desc->weights.u16.size_bytes <= geometry->desc.weights.u16.size_bytes);
276 assert(
277 desc->weights.floats.size_bytes <=
278 geometry->desc.weights.floats.size_bytes);
279
280 if (desc->positions3d.data) {
281 // The geometry must already have an underlying GPU buffer.
282 assert(geometry->desc.positions3d.buffer);
283 gfx_update_buffer(
284 geometry->desc.positions3d.buffer,
285 &(BufferDataDesc){
286 .vec3s = desc->positions3d.data,
287 .count = desc->positions3d.size_bytes / sizeof(vec3)});
288 }
289 // TODO: more
290 else {
291 FAIL("TODO: gfx_update_geometry() - handle other buffer types");
292 }
293
294 if (desc->num_verts != 0) {
295 geometry->num_verts = desc->num_verts;
296 }
297}
298
299void gfx_render_geometry(const Geometry* geometry) {
300 assert(geometry);
301 assert(geometry->vao);
302
303 const GeometryDesc* desc = &geometry->desc;
304 glBindVertexArray(geometry->vao);
305
306 if (desc->indices8.buffer) {
307 glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, desc->indices8.buffer->vbo);
308 glDrawElements(
309 geometry->mode, desc->num_indices, GL_UNSIGNED_BYTE,
310 (const void*)desc->indices8.offset_bytes);
311 } else if (desc->indices16.buffer) {
312 glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, desc->indices16.buffer->vbo);
313 glDrawElements(
314 geometry->mode, desc->num_indices, GL_UNSIGNED_SHORT,
315 (const void*)desc->indices16.offset_bytes);
316 } else {
317 glDrawArrays(geometry->mode, 0, geometry->num_verts);
318 }
319
320 glBindVertexArray(0);
321}
322
323aabb3 gfx_get_geometry_aabb(const Geometry* geometry) {
324 assert(geometry);
325 return geometry->desc.aabb;
326}
diff --git a/src/core/geometry.h b/src/core/geometry.h
new file mode 100644
index 0000000..c37a76f
--- /dev/null
+++ b/src/core/geometry.h
@@ -0,0 +1,28 @@
1#pragma once
2
3#include <gfx/core.h>
4
5#include "gl_util.h"
6
7#include <stdbool.h>
8
9/// A piece of renderable geometry.
10///
11/// The Geometry does not own its buffers, since buffers are typically shared
12/// to reduce the memory footprint and the number of draw calls. More generally,
13/// the renderer assumes ownership of all rendering resources, which simplifies
14/// their management.
15typedef struct Geometry {
16 GLuint vao;
17 GLenum mode;
18 GeometryDesc desc;
19 size_t num_verts; // May differ from the initial value in the descriptor if
20 // the geometry is updated.
21 GfxCore* gfxcore;
22} Geometry;
23
24/// Create new geometry.
25bool gfx_init_geometry(Geometry*, GfxCore*, const GeometryDesc*);
26
27/// Destroy the geometry.
28void gfx_del_geometry(Geometry*);
diff --git a/src/core/gl_util.h b/src/core/gl_util.h
new file mode 100644
index 0000000..d2d6e22
--- /dev/null
+++ b/src/core/gl_util.h
@@ -0,0 +1,45 @@
1#pragma once
2
3#include <glad/glad.h>
4#include <log/log.h>
5
6#define GFX_GL_CONTEXT_PC 1
7#define GFX_GL_CONTEXT_ES 2
8
9#ifndef GFX_GL_CONTEXT
10#define GFX_GL_CONTEXT GFX_GL_CONTEXT_PC
11#endif // GFX_GL_CONTEXT
12
13/// Log an error if an OpenGL has occurred.
14#ifndef NDEBUG
15#define ASSERT_GL \
16 { \
17 GLenum e = glGetError(); \
18 switch (e) { \
19 case GL_NO_ERROR: \
20 break; \
21 case GL_INVALID_ENUM: \
22 LOGE("GL_INVALID_ENUM"); \
23 break; \
24 case GL_INVALID_VALUE: \
25 LOGE("GL_INVALID_VALUE"); \
26 break; \
27 case GL_INVALID_OPERATION: \
28 LOGE("GL_INVALID_OPERATION"); \
29 break; \
30 case GL_INVALID_FRAMEBUFFER_OPERATION: \
31 LOGE("GL_INVALID_FRAMEBUFFER_OPERATION"); \
32 break; \
33 case GL_OUT_OF_MEMORY: \
34 LOGE("GL_OUT_OF_MEMORY"); \
35 break; \
36 /*case GL_STACK_UNDERFLOW: LOGE("GL_STACK_UNDERFLOW");*/ \
37 /*case GL_STACK_OVERFLOW: LOGE("GL_STACK_OVERFLOW");*/ \
38 default: \
39 LOGE("Unknown OpenGL error"); \
40 break; \
41 } \
42 }
43#else // Not NDEBUG.
44#define ASSERT_GL
45#endif
diff --git a/src/core/renderbuffer.c b/src/core/renderbuffer.c
new file mode 100644
index 0000000..2753f3b
--- /dev/null
+++ b/src/core/renderbuffer.c
@@ -0,0 +1,35 @@
1#include "renderbuffer.h"
2
3#include "texture.h"
4
5#include <error.h>
6
7bool gfx_init_renderbuffer(
8 RenderBuffer* renderbuffer, const RenderBufferDesc* desc) {
9 assert(renderbuffer);
10 assert(desc);
11
12 glGenRenderbuffers(1, &renderbuffer->id);
13 if (!renderbuffer->id) {
14 log_error("glGenRenderbuffers failed");
15 return false;
16 }
17
18 glBindRenderbuffer(GL_RENDERBUFFER, renderbuffer->id);
19 glRenderbufferStorage(
20 GL_RENDERBUFFER, to_GL_internal_format(desc->texture_format), desc->width,
21 desc->height);
22 glBindRenderbuffer(GL_RENDERBUFFER, 0);
23
24 ASSERT_GL;
25 return true;
26}
27
28void gfx_del_renderbuffer(RenderBuffer* renderbuffer) {
29 assert(renderbuffer);
30
31 if (renderbuffer->id) {
32 glDeleteRenderbuffers(1, &renderbuffer->id);
33 renderbuffer->id = 0;
34 }
35}
diff --git a/src/core/renderbuffer.h b/src/core/renderbuffer.h
new file mode 100644
index 0000000..ea11610
--- /dev/null
+++ b/src/core/renderbuffer.h
@@ -0,0 +1,15 @@
1#pragma once
2
3#include <gfx/core.h>
4
5#include "gl_util.h"
6
7typedef struct RenderBuffer {
8 GLuint id;
9} RenderBuffer;
10
11/// Create a new renderbuffer.
12bool gfx_init_renderbuffer(RenderBuffer*, const RenderBufferDesc*);
13
14/// Destroy the renderbuffer.
15void gfx_del_renderbuffer(RenderBuffer*);
diff --git a/src/core/shader.c b/src/core/shader.c
new file mode 100644
index 0000000..dded084
--- /dev/null
+++ b/src/core/shader.c
@@ -0,0 +1,92 @@
1#include "shader.h"
2
3#include "gl_util.h"
4#include <gfx_assert.h>
5
6#include <cstring.h>
7#include <log/log.h>
8
9#include <stdlib.h>
10#include <string.h>
11
12static GLenum shader_type_to_gl(ShaderType type) {
13 switch (type) {
14 case VertexShader:
15 return GL_VERTEX_SHADER;
16 case FragmentShader:
17 return GL_FRAGMENT_SHADER;
18 }
19 FAIL("shader_type_to_gl(): missing case");
20 return GL_INVALID_ENUM;
21}
22
23static lstring make_defines_string(const ShaderDesc* desc) {
24 lstring defines = {0};
25 for (size_t i = 0; i < desc->num_defines; ++i) {
26 const ShaderCompilerDefine* define = &desc->defines[i];
27 lstring_append_cstr(&defines, "#define ");
28 lstring_append_cstr(&defines, sstring_cstr(&define->name));
29 lstring_append_cstr(&defines, " ");
30 lstring_append_cstr(&defines, sstring_cstr(&define->value));
31 lstring_append_cstr(&defines, "\n");
32 }
33 return defines;
34}
35
36/// Creates an OpenGL shader.
37/// Returns non-zero on success, 0 on failure.
38static GLuint create_shader(const ShaderDesc* desc) {
39 const GLuint shader = glCreateShader(shader_type_to_gl(desc->type));
40 if (!shader) {
41 return 0;
42 }
43
44#if GFX_GL_CONTEXT == GFX_GL_CONTEXT_ES
45 const char* header = "#version 300 es\n\nprecision highp float;";
46#else
47 const char* header = "#version 400 core\n\n";
48#endif
49
50 lstring defines = make_defines_string(desc);
51
52 const char* source_bits[] = {header, lstring_cstr(&defines), desc->code};
53 const GLint source_lengths[] = {
54 strlen(header), lstring_length(&defines), strlen(desc->code)};
55
56 glShaderSource(shader, 3, source_bits, source_lengths);
57 glCompileShader(shader);
58 GLint result;
59 glGetShaderiv(shader, GL_COMPILE_STATUS, &result);
60 if (result == GL_FALSE) {
61 GLint log_len;
62 glGetShaderiv(shader, GL_INFO_LOG_LENGTH, &log_len);
63 if (log_len > 0) {
64 char* log = calloc(log_len, sizeof(char));
65 glGetShaderInfoLog(shader, log_len, NULL, log);
66 static const char* sep = "----------";
67 LOGE("Failed loading shader: %s\n%s\n%s\n%s", log, sep, desc->code, sep);
68 free(log);
69 } else {
70 LOGE("Failed loading shader:\n%s", desc->code);
71 }
72 glDeleteShader(shader);
73 return 0;
74 }
75 ASSERT_GL;
76 return shader;
77}
78
79bool gfx_compile_shader(Shader* shader, const ShaderDesc* desc) {
80 shader->id = create_shader(desc);
81 return shader->id != 0;
82}
83
84void gfx_del_shader(Shader* shader) {
85 assert(shader);
86
87 if (shader->id) {
88 glDeleteShader(shader->id);
89 shader->id = 0;
90 }
91 ASSERT_GL;
92}
diff --git a/src/core/shader.h b/src/core/shader.h
new file mode 100644
index 0000000..b9f5679
--- /dev/null
+++ b/src/core/shader.h
@@ -0,0 +1,17 @@
1#pragma once
2
3#include <gfx/core.h>
4
5#include "gl_util.h"
6
7#include <stdbool.h>
8
9typedef struct Shader {
10 GLuint id;
11} Shader;
12
13/// Compile a new shader.
14bool gfx_compile_shader(Shader*, const ShaderDesc*);
15
16/// Destroy the shader.
17void gfx_del_shader(Shader*);
diff --git a/src/core/shader_program.c b/src/core/shader_program.c
new file mode 100644
index 0000000..3cbe48d
--- /dev/null
+++ b/src/core/shader_program.c
@@ -0,0 +1,291 @@
1#include "shader_program.h"
2
3#include "gl_util.h"
4#include "shader.h"
5#include "texture.h"
6#include <gfx_assert.h>
7
8#include <log/log.h>
9
10#include <stdlib.h>
11#include <string.h>
12
13/// Creates an OpenGL shader program.
14/// Returns non-zero on success, 0 on failure.
15static GLuint create_program(GLuint vertex_shader, GLuint fragment_shader) {
16 const GLuint prog = glCreateProgram();
17 if (prog == 0) {
18 LOGE("Failed creating shader program");
19 return 0;
20 }
21 glAttachShader(prog, vertex_shader);
22 glAttachShader(prog, fragment_shader);
23 glLinkProgram(prog);
24 GLint result;
25 glGetProgramiv(prog, GL_LINK_STATUS, &result);
26 if (result == GL_FALSE) {
27 GLint log_len;
28 glGetProgramiv(prog, GL_INFO_LOG_LENGTH, &log_len);
29 if (log_len > 0) {
30 char* log = calloc(log_len, sizeof(char));
31 glGetProgramInfoLog(prog, log_len, NULL, log);
32 LOGE("Failed creating shader program: %s", log);
33 free(log);
34 } else {
35 LOGE("Failed creating shader program");
36 }
37 glDeleteProgram(prog);
38 return 0;
39 }
40 ASSERT_GL;
41 return prog;
42}
43
44bool gfx_build_shader_program(
45 ShaderProgram* prog, const ShaderProgramDesc* desc) {
46 assert(prog);
47 assert(desc);
48
49 prog->id = create_program(desc->vertex_shader->id, desc->fragment_shader->id);
50 return prog->id != 0;
51}
52
53void gfx_del_shader_program(ShaderProgram* prog) {
54 assert(prog);
55
56 if (prog->id) {
57 glDeleteProgram(prog->id);
58 prog->id = 0;
59 }
60 ASSERT_GL;
61}
62
63void gfx_activate_shader_program(const ShaderProgram* prog) {
64 assert(prog);
65 glUseProgram(prog->id);
66 ASSERT_GL;
67}
68
69void gfx_deactivate_shader_program(const ShaderProgram* prog) {
70 assert(prog);
71 glUseProgram(0);
72 ASSERT_GL;
73}
74
75static void set_texture_uniform(
76 GLuint prog, const char* name, int texture_unit, const Texture* texture) {
77 assert(prog != 0);
78 assert(name);
79 assert(texture);
80
81 const GLint location = glGetUniformLocation(prog, name);
82 if (location >= 0) {
83 glActiveTexture(GL_TEXTURE0 + texture_unit);
84 glBindTexture(texture->target, texture->id);
85 glUniform1i(location, texture_unit);
86 }
87}
88
89static void set_mat4_uniform(
90 GLuint prog, const char* name, const mat4* mats, size_t count) {
91 assert(prog != 0);
92 assert(name);
93 assert(mats);
94
95 const GLint location = glGetUniformLocation(prog, name);
96 if (location >= 0) {
97 glUniformMatrix4fv(location, count, GL_FALSE, (const float*)mats);
98 }
99}
100
101static void set_vec3_uniform(GLuint prog, const char* name, vec3 value) {
102 assert(prog != 0);
103 assert(name);
104
105 const GLint location = glGetUniformLocation(prog, name);
106 if (location >= 0) {
107 glUniform3f(location, value.x, value.y, value.z);
108 }
109}
110
111static void set_vec4_uniform(GLuint prog, const char* name, vec4 value) {
112 assert(prog != 0);
113 assert(name);
114
115 const GLint location = glGetUniformLocation(prog, name);
116 if (location >= 0) {
117 glUniform4f(location, value.x, value.y, value.z, value.w);
118 }
119}
120
121static void set_float_uniform(GLuint prog, const char* name, float value) {
122 assert(prog != 0);
123 assert(name);
124
125 const GLint location = glGetUniformLocation(prog, name);
126 if (location >= 0) {
127 glUniform1f(location, value);
128 }
129}
130
131void gfx_apply_uniforms(const ShaderProgram* prog) {
132 assert(prog);
133
134 int next_texture_unit = 0;
135 for (int i = 0; i < prog->num_uniforms; ++i) {
136 const ShaderUniform* uniform = &prog->uniforms[i];
137 switch (uniform->type) {
138 case UniformTexture:
139 set_texture_uniform(
140 prog->id, uniform->name.str, next_texture_unit,
141 uniform->value.texture);
142 next_texture_unit++;
143 break;
144 case UniformMat4:
145 set_mat4_uniform(prog->id, uniform->name.str, &uniform->value.mat4, 1);
146 break;
147 case UniformVec3:
148 set_vec3_uniform(prog->id, uniform->name.str, uniform->value.vec3);
149 break;
150 case UniformVec4:
151 set_vec4_uniform(prog->id, uniform->name.str, uniform->value.vec4);
152 break;
153 case UniformFloat:
154 set_float_uniform(prog->id, uniform->name.str, uniform->value.scalar);
155 break;
156 case UniformMat4Array:
157 set_mat4_uniform(
158 prog->id, uniform->name.str, uniform->value.array.values,
159 uniform->value.array.count);
160 break;
161 }
162 }
163}
164
165// Get the ShaderUniform object by name from the shader program if it already
166// exists, or allocate a new one otherwise.
167static ShaderUniform* get_or_allocate_uniform(
168 ShaderProgram* prog, const char* name) {
169 assert(prog);
170 assert(name);
171
172 // First search for the uniform in the list.
173 for (int i = 0; i < prog->num_uniforms; ++i) {
174 ShaderUniform* uniform = &prog->uniforms[i];
175 if (sstring_eq_cstr(uniform->name, name)) {
176 return uniform;
177 }
178 }
179
180 // Create the uniform if it does not exist.
181 if (prog->num_uniforms == GFX_MAX_UNIFORMS_PER_SHADER) {
182 FAIL("Exceeded the maximum number of uniforms per shader. Please increase "
183 "this value.");
184 return 0;
185 }
186 ShaderUniform* uniform = &prog->uniforms[prog->num_uniforms];
187 prog->num_uniforms++;
188 return uniform;
189}
190
191// The functions below save the value of a uniform in the shader program. If the
192// uniform does not even exist, then there is no need to store the value.
193
194void gfx_set_texture_uniform(
195 ShaderProgram* prog, const char* name, const Texture* texture) {
196 assert(prog);
197 assert(name);
198 assert(texture);
199
200 const GLint location = glGetUniformLocation(prog->id, name);
201 if (location < 0) {
202 return;
203 }
204 ShaderUniform* uniform = get_or_allocate_uniform(prog, name);
205 assert(uniform);
206 uniform->name = sstring_make(name);
207 uniform->type = UniformTexture;
208 uniform->value.texture = texture;
209}
210
211void gfx_set_mat4_uniform(
212 ShaderProgram* prog, const char* name, const mat4* mat) {
213 assert(prog);
214 assert(name);
215 assert(mat);
216
217 const GLint location = glGetUniformLocation(prog->id, name);
218 if (location < 0) {
219 return;
220 }
221 ShaderUniform* uniform = get_or_allocate_uniform(prog, name);
222 assert(uniform);
223 uniform->name = sstring_make(name);
224 uniform->type = UniformMat4;
225 uniform->value.mat4 = *mat;
226}
227
228void gfx_set_vec3_uniform(ShaderProgram* prog, const char* name, vec3 value) {
229 assert(prog);
230 assert(name);
231
232 const GLint location = glGetUniformLocation(prog->id, name);
233 if (location < 0) {
234 return;
235 }
236 ShaderUniform* uniform = get_or_allocate_uniform(prog, name);
237 assert(uniform);
238 uniform->name = sstring_make(name);
239 uniform->type = UniformVec3;
240 uniform->value.vec3 = value;
241}
242
243void gfx_set_vec4_uniform(ShaderProgram* prog, const char* name, vec4 value) {
244 assert(prog);
245 assert(name);
246
247 const GLint location = glGetUniformLocation(prog->id, name);
248 if (location < 0) {
249 return;
250 }
251 ShaderUniform* uniform = get_or_allocate_uniform(prog, name);
252 assert(uniform);
253 uniform->name = sstring_make(name);
254 uniform->type = UniformVec4;
255 uniform->value.vec4 = value;
256}
257
258void gfx_set_float_uniform(ShaderProgram* prog, const char* name, float value) {
259 assert(prog);
260 assert(name);
261
262 // No need to store the uniform on our side if it does not exist in the
263 // program.
264 const GLint location = glGetUniformLocation(prog->id, name);
265 if (location < 0) {
266 return;
267 }
268 ShaderUniform* uniform = get_or_allocate_uniform(prog, name);
269 assert(uniform);
270 uniform->name = sstring_make(name);
271 uniform->type = UniformFloat;
272 uniform->value.scalar = value;
273}
274
275void gfx_set_mat4_array_uniform(
276 ShaderProgram* prog, const char* name, const mat4* mats, size_t count) {
277 assert(prog);
278 assert(name);
279 assert(mats);
280
281 const GLint location = glGetUniformLocation(prog->id, name);
282 if (location < 0) {
283 return;
284 }
285 ShaderUniform* uniform = get_or_allocate_uniform(prog, name);
286 assert(uniform);
287 uniform->name = sstring_make(name);
288 uniform->type = UniformMat4Array;
289 uniform->value.array.count = count;
290 uniform->value.array.values = mats;
291}
diff --git a/src/core/shader_program.h b/src/core/shader_program.h
new file mode 100644
index 0000000..1443663
--- /dev/null
+++ b/src/core/shader_program.h
@@ -0,0 +1,24 @@
1#pragma once
2
3#include <gfx/core.h>
4#include <gfx/sizes.h>
5
6#include "gl_util.h"
7
8#include <math/fwd.h>
9
10#include <stdbool.h>
11
12typedef struct Texture Texture;
13
14typedef struct ShaderProgram {
15 GLuint id;
16 ShaderUniform uniforms[GFX_MAX_UNIFORMS_PER_SHADER];
17 int num_uniforms;
18} ShaderProgram;
19
20/// Create a new shader program.
21bool gfx_build_shader_program(ShaderProgram*, const ShaderProgramDesc*);
22
23/// Destroy the shader program.
24void gfx_del_shader_program(ShaderProgram*);
diff --git a/src/core/texture.c b/src/core/texture.c
new file mode 100644
index 0000000..89f7ec0
--- /dev/null
+++ b/src/core/texture.c
@@ -0,0 +1,218 @@
1#include "texture.h"
2
3#include <gfx_assert.h>
4
5#include <error.h>
6#include <math/defs.h>
7
8#include <stdbool.h>
9
10bool gfx_init_texture(Texture* texture, const TextureDesc* desc) {
11 assert(texture);
12 assert(desc);
13
14 glGenTextures(1, &texture->id);
15 if (!texture->id) {
16 log_error("glGenTextures() failed");
17 return false;
18 }
19 texture->target = to_GL_dimension(desc->dimension);
20 glBindTexture(texture->target, texture->id);
21
22 // glTexStorageXD
23 const int levels =
24 desc->mipmaps
25 ? max(max(log2(desc->width), log2(desc->height)), log2(desc->depth)) +
26 1
27 : 1;
28 const GLenum internal_format = to_GL_internal_format(desc->format);
29 switch (texture->target) {
30 case GL_TEXTURE_2D:
31 case GL_TEXTURE_CUBE_MAP:
32 glTexStorage2D(
33 texture->target, levels, internal_format, desc->width, desc->height);
34 break;
35 default:
36 FAIL("Unhandled texture dimension");
37 gfx_del_texture(texture);
38 return false;
39 }
40
41 texture->format = to_GL_format(desc->format);
42 texture->type = to_GL_type(desc->format);
43 texture->width = desc->width;
44 texture->height = desc->height;
45 gfx_update_texture(texture, &desc->data);
46
47 // gfx_update_texture() unbinds the texture at the end, so re-bind it here.
48 glBindTexture(texture->target, texture->id);
49
50 // Mipmaps.
51 if (desc->mipmaps) {
52 glGenerateMipmap(texture->target);
53 }
54
55 // Texture filtering.
56 const bool linear = desc->filtering == LinearFiltering;
57 GLenum min = desc->mipmaps ? (linear ? GL_LINEAR_MIPMAP_LINEAR
58 : GL_NEAREST_MIPMAP_NEAREST)
59 : (linear ? GL_LINEAR : GL_NEAREST);
60 GLenum mag = linear ? GL_LINEAR : GL_NEAREST;
61 glTexParameteri(texture->target, GL_TEXTURE_MIN_FILTER, min);
62 glTexParameteri(texture->target, GL_TEXTURE_MAG_FILTER, mag);
63
64 // Texture wrapping.
65 GLenum wrap = GL_INVALID_ENUM;
66 switch (desc->wrap) {
67 case Repeat:
68 wrap = GL_REPEAT;
69 break;
70 case ClampToEdge:
71 wrap = GL_CLAMP_TO_EDGE;
72 break;
73 }
74 glTexParameteri(texture->target, GL_TEXTURE_WRAP_R, wrap);
75 glTexParameteri(texture->target, GL_TEXTURE_WRAP_S, wrap);
76 glTexParameteri(texture->target, GL_TEXTURE_WRAP_T, wrap);
77
78 glBindTexture(texture->target, 0);
79 return true;
80}
81
82void gfx_del_texture(Texture* texture) {
83 assert(texture);
84
85 if (texture->id) {
86 glDeleteTextures(1, &texture->id);
87 texture->id = 0;
88 }
89}
90
91void gfx_update_texture(Texture* texture, const TextureDataDesc* desc) {
92 assert(texture);
93 assert(desc);
94
95 glBindTexture(texture->target, texture->id);
96
97 // glTexSubImageXD
98 switch (texture->target) {
99 case GL_TEXTURE_2D:
100 if (desc->pixels) {
101 glTexSubImage2D(
102 GL_TEXTURE_2D, /*level=*/0, /*xoffset=*/0,
103 /*yoffset=*/0, texture->width, texture->height, texture->format,
104 texture->type, desc->pixels);
105 }
106 break;
107 case GL_TEXTURE_CUBE_MAP:
108 for (int i = 0; i < 6; ++i) {
109 const void* pixels = *(&desc->cubemap.pixels_pos_x + i);
110 if (pixels) {
111 glTexSubImage2D(
112 GL_TEXTURE_CUBE_MAP_POSITIVE_X + i, /*level=*/0, /*xoffset=*/0,
113 /*yoffset=*/0, texture->width, texture->height, texture->format,
114 texture->type, pixels);
115 }
116 }
117 break;
118 default:
119 FAIL("Unhandled texture dimension");
120 break;
121 }
122
123 glBindTexture(texture->target, 0);
124}
125
126GLenum to_GL_dimension(TextureDimension dim) {
127 switch (dim) {
128 case Texture2D:
129 return GL_TEXTURE_2D;
130 case TextureCubeMap:
131 return GL_TEXTURE_CUBE_MAP;
132 default:
133 FAIL("Unhandled TextureDimension");
134 return GL_INVALID_ENUM;
135 }
136}
137
138GLenum to_GL_internal_format(TextureFormat format) {
139 switch (format) {
140 case TextureDepth:
141 return GL_DEPTH_COMPONENT;
142 case TextureRG16:
143 return GL_RG16;
144 case TextureRG16F:
145 return GL_RG16F;
146 case TextureRGB8:
147 return GL_RGB8;
148 case TextureR11G11B10F:
149 return GL_R11F_G11F_B10F;
150 case TextureRGBA8:
151 return GL_RGBA8;
152 case TextureSRGB8:
153 return GL_SRGB8;
154 case TextureSRGBA8:
155 return GL_SRGB8_ALPHA8;
156 default:
157 FAIL("Unhandled TextureFormat");
158 return GL_INVALID_ENUM;
159 }
160}
161
162GLenum to_GL_format(TextureFormat format) {
163 switch (format) {
164 case TextureDepth:
165 return GL_DEPTH_COMPONENT;
166 case TextureRG16:
167 case TextureRG16F:
168 return GL_RG;
169 case TextureRGB8:
170 case TextureR11G11B10F:
171 case TextureSRGB8:
172 return GL_RGB;
173 case TextureRGBA8:
174 case TextureSRGBA8:
175 return GL_RGBA;
176 default:
177 FAIL("Unhandled TextureFormat");
178 return GL_INVALID_ENUM;
179 }
180}
181
182GLenum to_GL_type(TextureFormat format) {
183 switch (format) {
184 case TextureDepth:
185 case TextureRG16F:
186 case TextureR11G11B10F:
187 return GL_FLOAT;
188 case TextureRG16:
189 case TextureRGB8:
190 case TextureRGBA8:
191 case TextureSRGB8:
192 case TextureSRGBA8:
193 return GL_UNSIGNED_BYTE;
194 default:
195 FAIL("Unhandled TextureFormat");
196 return GL_INVALID_ENUM;
197 }
198}
199
200GLenum to_GL_cubemap_face(CubemapFace face) {
201 switch (face) {
202 case CubemapFacePosX:
203 return GL_TEXTURE_CUBE_MAP_POSITIVE_X;
204 case CubemapFaceNegX:
205 return GL_TEXTURE_CUBE_MAP_NEGATIVE_X;
206 case CubemapFacePosY:
207 return GL_TEXTURE_CUBE_MAP_POSITIVE_Y;
208 case CubemapFaceNegY:
209 return GL_TEXTURE_CUBE_MAP_NEGATIVE_Y;
210 case CubemapFacePosZ:
211 return GL_TEXTURE_CUBE_MAP_POSITIVE_Z;
212 case CubemapFaceNegZ:
213 return GL_TEXTURE_CUBE_MAP_NEGATIVE_Z;
214 default:
215 FAIL("Unhandled CubemapFace");
216 return GL_INVALID_ENUM;
217 }
218}
diff --git a/src/core/texture.h b/src/core/texture.h
new file mode 100644
index 0000000..4af41e9
--- /dev/null
+++ b/src/core/texture.h
@@ -0,0 +1,35 @@
1#pragma once
2
3#include <gfx/core.h>
4
5#include "gl_util.h"
6
7typedef struct Texture {
8 GLuint id;
9 GLenum target;
10 GLenum format;
11 GLenum type;
12 int width;
13 int height;
14} Texture;
15
16/// Create a new texture.
17bool gfx_init_texture(Texture*, const TextureDesc*);
18
19/// Destroy the texture.
20void gfx_del_texture(Texture*);
21
22/// Converts a TextureDimension into the OpenGL enum equivalent.
23GLenum to_GL_dimension(TextureDimension dim);
24
25/// Converts a texture format into an OpenGL internal format.
26GLenum to_GL_internal_format(TextureFormat format);
27
28/// Converts a texture format into an OpenGL format.
29GLenum to_GL_format(TextureFormat format);
30
31/// Converts a texture format into an OpenGL type.
32GLenum to_GL_type(TextureFormat format);
33
34/// Converts a cubemap face into the OpenGL enum equivalent.
35GLenum to_GL_cubemap_face(CubemapFace face);