diff options
Diffstat (limited to 'src/core')
-rw-r--r-- | src/core/buffer.c | 85 | ||||
-rw-r--r-- | src/core/buffer.h | 26 | ||||
-rw-r--r-- | src/core/constants.h | 9 | ||||
-rw-r--r-- | src/core/core.c | 429 | ||||
-rw-r--r-- | src/core/core_impl.h | 68 | ||||
-rw-r--r-- | src/core/framebuffer.c | 151 | ||||
-rw-r--r-- | src/core/framebuffer.h | 15 | ||||
-rw-r--r-- | src/core/geometry.c | 326 | ||||
-rw-r--r-- | src/core/geometry.h | 28 | ||||
-rw-r--r-- | src/core/gl_util.h | 45 | ||||
-rw-r--r-- | src/core/renderbuffer.c | 35 | ||||
-rw-r--r-- | src/core/renderbuffer.h | 15 | ||||
-rw-r--r-- | src/core/shader.c | 92 | ||||
-rw-r--r-- | src/core/shader.h | 17 | ||||
-rw-r--r-- | src/core/shader_program.c | 291 | ||||
-rw-r--r-- | src/core/shader_program.h | 24 | ||||
-rw-r--r-- | src/core/texture.c | 218 | ||||
-rw-r--r-- | src/core/texture.h | 35 |
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 | |||
10 | static 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 | |||
15 | static 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 | |||
26 | size_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 | |||
47 | bool 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 | |||
64 | void gfx_del_buffer(Buffer* buffer) { | ||
65 | assert(buffer); | ||
66 | if (buffer->vbo) { | ||
67 | glDeleteBuffers(1, &buffer->vbo); | ||
68 | buffer->vbo = 0; | ||
69 | } | ||
70 | } | ||
71 | |||
72 | void 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 | |||
12 | typedef 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. | ||
20 | size_t gfx_get_buffer_type_size_bytes(BufferType); | ||
21 | |||
22 | /// Create a buffer from raw data. | ||
23 | bool gfx_init_buffer(Buffer*, const BufferDesc*); | ||
24 | |||
25 | /// Destroy the buffer. | ||
26 | void 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 | |||
9 | void 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. | ||
36 | void 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 | |||
64 | void 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 | |||
76 | void gfx_end_frame(GfxCore* gfxcore) { | ||
77 | assert(gfxcore); | ||
78 | ASSERT_GL; | ||
79 | } | ||
80 | |||
81 | void 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 | |||
87 | void 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 | |||
101 | void 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 | |||
108 | void 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 | |||
118 | void gfx_set_depth_mask(GfxCore* gfxcore, bool enable) { | ||
119 | assert(gfxcore); | ||
120 | glDepthMask(enable ? GL_TRUE : GL_FALSE); | ||
121 | } | ||
122 | |||
123 | void 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 | |||
132 | void 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 | |||
142 | void 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 | |||
152 | Buffer* 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 | |||
164 | void 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 | |||
177 | Geometry* 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 | |||
189 | void 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 | |||
203 | Texture* 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 | |||
215 | void 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 | |||
230 | RenderBuffer* 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 | |||
242 | void 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 | |||
257 | FrameBuffer* 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 | |||
270 | void 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 | |||
285 | static 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 | |||
298 | static 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 | |||
304 | static 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 | |||
314 | static 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 | |||
324 | static 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 | |||
336 | static 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 | |||
348 | Shader* 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 | |||
375 | void 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 | |||
391 | ShaderProgram* 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 | |||
416 | void 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. | ||
21 | typedef struct ShaderCacheEntry { | ||
22 | uint64_t hash; | ||
23 | Shader* shader; | ||
24 | } ShaderCacheEntry; | ||
25 | |||
26 | typedef struct ShaderProgramCacheEntry { | ||
27 | uint64_t hash; | ||
28 | ShaderProgram* program; | ||
29 | } ShaderProgramCacheEntry; | ||
30 | |||
31 | DEF_MEMPOOL(buffer_pool, Buffer, GFX_MAX_NUM_BUFFERS) | ||
32 | DEF_MEMPOOL(framebuffer_pool, FrameBuffer, GFX_MAX_NUM_FRAMEBUFFERS) | ||
33 | DEF_MEMPOOL(geometry_pool, Geometry, GFX_MAX_NUM_GEOMETRIES) | ||
34 | DEF_MEMPOOL(renderbuffer_pool, RenderBuffer, GFX_MAX_NUM_RENDERBUFFERS) | ||
35 | DEF_MEMPOOL(shader_pool, Shader, GFX_MAX_NUM_SHADERS) | ||
36 | DEF_MEMPOOL(shader_program_pool, ShaderProgram, GFX_MAX_NUM_SHADER_PROGRAMS) | ||
37 | DEF_MEMPOOL(texture_pool, Texture, GFX_MAX_NUM_TEXTURES) | ||
38 | |||
39 | DEF_MEMPOOL(ShaderCache, ShaderCacheEntry, GFX_MAX_NUM_SHADERS) | ||
40 | DEF_MEMPOOL(ProgramCache, ShaderProgramCacheEntry, GFX_MAX_NUM_SHADER_PROGRAMS) | ||
41 | |||
42 | typedef struct { | ||
43 | int x; | ||
44 | int y; | ||
45 | int width; | ||
46 | int height; | ||
47 | } Viewport; | ||
48 | |||
49 | typedef 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. | ||
65 | void gfx_init_gfxcore(GfxCore*); | ||
66 | |||
67 | /// Destroy the render backend. | ||
68 | void 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 | |||
10 | static 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 | |||
39 | static 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 | |||
67 | bool 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 | |||
99 | bool 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 | |||
114 | bool 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 | |||
129 | void gfx_del_framebuffer(FrameBuffer* framebuffer) { | ||
130 | assert(framebuffer); | ||
131 | if (framebuffer->id) { | ||
132 | glDeleteFramebuffers(1, &framebuffer->id); | ||
133 | framebuffer->id = 0; | ||
134 | } | ||
135 | } | ||
136 | |||
137 | void gfx_activate_framebuffer(const FrameBuffer* framebuffer) { | ||
138 | assert(framebuffer); | ||
139 | glBindFramebuffer(GL_FRAMEBUFFER, framebuffer->id); | ||
140 | } | ||
141 | |||
142 | void gfx_deactivate_framebuffer(const FrameBuffer* framebuffer) { | ||
143 | assert(framebuffer); | ||
144 | glBindFramebuffer(GL_FRAMEBUFFER, 0); | ||
145 | } | ||
146 | |||
147 | void 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 | |||
7 | typedef struct FrameBuffer { | ||
8 | GLuint id; | ||
9 | } FrameBuffer; | ||
10 | |||
11 | /// Create a new framebuffer. | ||
12 | bool gfx_init_framebuffer(FrameBuffer*, const FrameBufferDesc*); | ||
13 | |||
14 | /// Destroy the framebuffer. | ||
15 | void 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 | |||
17 | static 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. | ||
32 | void 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. | ||
49 | static 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 | |||
81 | static 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 | |||
190 | static 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 | |||
218 | bool 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 | |||
250 | cleanup: | ||
251 | gfx_del_geometry(geometry); | ||
252 | return 0; | ||
253 | } | ||
254 | |||
255 | void gfx_del_geometry(Geometry* geometry) { | ||
256 | assert(geometry); | ||
257 | if (geometry->vao) { | ||
258 | glDeleteVertexArrays(1, &geometry->vao); | ||
259 | geometry->vao = 0; | ||
260 | } | ||
261 | } | ||
262 | |||
263 | void 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 | |||
299 | void 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 | |||
323 | aabb3 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. | ||
15 | typedef 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. | ||
25 | bool gfx_init_geometry(Geometry*, GfxCore*, const GeometryDesc*); | ||
26 | |||
27 | /// Destroy the geometry. | ||
28 | void 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 | |||
7 | bool 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 | |||
28 | void 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 | |||
7 | typedef struct RenderBuffer { | ||
8 | GLuint id; | ||
9 | } RenderBuffer; | ||
10 | |||
11 | /// Create a new renderbuffer. | ||
12 | bool gfx_init_renderbuffer(RenderBuffer*, const RenderBufferDesc*); | ||
13 | |||
14 | /// Destroy the renderbuffer. | ||
15 | void 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 | |||
12 | static 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 | |||
23 | static 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. | ||
38 | static 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 | |||
79 | bool gfx_compile_shader(Shader* shader, const ShaderDesc* desc) { | ||
80 | shader->id = create_shader(desc); | ||
81 | return shader->id != 0; | ||
82 | } | ||
83 | |||
84 | void 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 | |||
9 | typedef struct Shader { | ||
10 | GLuint id; | ||
11 | } Shader; | ||
12 | |||
13 | /// Compile a new shader. | ||
14 | bool gfx_compile_shader(Shader*, const ShaderDesc*); | ||
15 | |||
16 | /// Destroy the shader. | ||
17 | void 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. | ||
15 | static 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 | |||
44 | bool 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 | |||
53 | void 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 | |||
63 | void gfx_activate_shader_program(const ShaderProgram* prog) { | ||
64 | assert(prog); | ||
65 | glUseProgram(prog->id); | ||
66 | ASSERT_GL; | ||
67 | } | ||
68 | |||
69 | void gfx_deactivate_shader_program(const ShaderProgram* prog) { | ||
70 | assert(prog); | ||
71 | glUseProgram(0); | ||
72 | ASSERT_GL; | ||
73 | } | ||
74 | |||
75 | static 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 | |||
89 | static 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 | |||
101 | static 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 | |||
111 | static 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 | |||
121 | static 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 | |||
131 | void 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. | ||
167 | static 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 | |||
194 | void 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 | |||
211 | void 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 | |||
228 | void 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 | |||
243 | void 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 | |||
258 | void 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 | |||
275 | void 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 | |||
12 | typedef struct Texture Texture; | ||
13 | |||
14 | typedef 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. | ||
21 | bool gfx_build_shader_program(ShaderProgram*, const ShaderProgramDesc*); | ||
22 | |||
23 | /// Destroy the shader program. | ||
24 | void 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 | |||
10 | bool 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 | |||
82 | void 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 | |||
91 | void 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 | |||
126 | GLenum 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 | |||
138 | GLenum 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 | |||
162 | GLenum 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 | |||
182 | GLenum 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 | |||
200 | GLenum 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 | |||
7 | typedef 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. | ||
17 | bool gfx_init_texture(Texture*, const TextureDesc*); | ||
18 | |||
19 | /// Destroy the texture. | ||
20 | void gfx_del_texture(Texture*); | ||
21 | |||
22 | /// Converts a TextureDimension into the OpenGL enum equivalent. | ||
23 | GLenum to_GL_dimension(TextureDimension dim); | ||
24 | |||
25 | /// Converts a texture format into an OpenGL internal format. | ||
26 | GLenum to_GL_internal_format(TextureFormat format); | ||
27 | |||
28 | /// Converts a texture format into an OpenGL format. | ||
29 | GLenum to_GL_format(TextureFormat format); | ||
30 | |||
31 | /// Converts a texture format into an OpenGL type. | ||
32 | GLenum to_GL_type(TextureFormat format); | ||
33 | |||
34 | /// Converts a cubemap face into the OpenGL enum equivalent. | ||
35 | GLenum to_GL_cubemap_face(CubemapFace face); | ||