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); | ||
