From 88ab79e436aec6ede0fca4949570f534ffb1b853 Mon Sep 17 00:00:00 2001
From: 3gg <3gg@shellblade.net>
Date: Sat, 9 Mar 2024 09:29:52 -0800
Subject: Rename RenderBackend -> GfxCore.

---
 game/src/game.c                      |  12 +-
 game/src/plugins/texture_view.c      |   8 +-
 game/src/plugins/viewer.c            |  22 +-
 gfx-iso/src/app.c                    |  36 +--
 gfx/CMakeLists.txt                   |   2 +-
 gfx/include/gfx/asset.h              |   2 +-
 gfx/include/gfx/core.h               | 498 ++++++++++++++++++++++++++++++++++
 gfx/include/gfx/gfx.h                |  10 +-
 gfx/include/gfx/render_backend.h     | 499 -----------------------------------
 gfx/include/gfx/renderer.h           |   6 +-
 gfx/include/gfx/scene/material.h     |   2 +-
 gfx/include/gfx/util/geometry.h      |   6 +-
 gfx/include/gfx/util/ibl.h           |  21 +-
 gfx/include/gfx/util/shader.h        |  26 +-
 gfx/include/gfx/util/skyquad.h       |  14 +-
 gfx/src/asset/asset_cache.c          |   4 +-
 gfx/src/asset/model.c                |  69 +++--
 gfx/src/asset/texture.c              |   9 +-
 gfx/src/asset/texture.h              |   2 +-
 gfx/src/gfx.c                        |  22 +-
 gfx/src/render/buffer.c              |   4 +-
 gfx/src/render/buffer.h              |   2 +-
 gfx/src/render/core.c                | 414 +++++++++++++++++++++++++++++
 gfx/src/render/core_impl.h           |  66 +++++
 gfx/src/render/framebuffer.h         |   2 +-
 gfx/src/render/geometry.c            | 101 +++----
 gfx/src/render/geometry.h            |   6 +-
 gfx/src/render/render_backend.c      | 425 -----------------------------
 gfx/src/render/render_backend_impl.h |  66 -----
 gfx/src/render/renderbuffer.h        |   2 +-
 gfx/src/render/shader.h              |   2 +-
 gfx/src/render/shader_program.h      |   6 +-
 gfx/src/render/texture.h             |   2 +-
 gfx/src/renderer/imm_renderer.c      |  29 +-
 gfx/src/renderer/imm_renderer_impl.h |   4 +-
 gfx/src/renderer/renderer.c          |  62 +++--
 gfx/src/renderer/renderer_impl.h     |   8 +-
 gfx/src/scene/material.c             |   2 +-
 gfx/src/scene/object.c               |   2 +-
 gfx/src/util/geometry.c              |  12 +-
 gfx/src/util/ibl.c                   | 106 ++++----
 gfx/src/util/shader.c                |  76 +++---
 gfx/src/util/skyquad.c               |  22 +-
 43 files changed, 1323 insertions(+), 1368 deletions(-)
 create mode 100644 gfx/include/gfx/core.h
 delete mode 100644 gfx/include/gfx/render_backend.h
 create mode 100644 gfx/src/render/core.c
 create mode 100644 gfx/src/render/core_impl.h
 delete mode 100644 gfx/src/render/render_backend.c
 delete mode 100644 gfx/src/render/render_backend_impl.h

diff --git a/game/src/game.c b/game/src/game.c
index bc85691..425119f 100644
--- a/game/src/game.c
+++ b/game/src/game.c
@@ -11,8 +11,8 @@
 #include "plugins/plugin.h"
 
 #include <gfx/app.h>
+#include <gfx/core.h>
 #include <gfx/gfx.h>
-#include <gfx/render_backend.h>
 #include <gfx/renderer.h>
 #include <gfx/scene/camera.h>
 #include <gfx/scene/node.h>
@@ -204,18 +204,18 @@ void app_update(Game* game, double t, double dt) {
 }
 
 void app_render(const Game* game) {
-  RenderBackend* render_backend = gfx_get_render_backend(game->gfx);
-  gfx_start_frame(render_backend);
+  GfxCore* gfxcore = gfx_get_core(game->gfx);
+  gfx_start_frame(gfxcore);
   render_plugin(game);
-  gfx_end_frame(render_backend);
+  gfx_end_frame(gfxcore);
 }
 
 void app_resize(Game* game, int width, int height) {
   game->width  = width;
   game->height = height;
 
-  RenderBackend* render_backend = gfx_get_render_backend(game->gfx);
-  gfx_set_viewport(render_backend, width, height);
+  GfxCore* gfxcore = gfx_get_core(game->gfx);
+  gfx_set_viewport(gfxcore, width, height);
 
   resize_plugin(game, width, height);
 }
diff --git a/game/src/plugins/texture_view.c b/game/src/plugins/texture_view.c
index 52dff57..a8b2a94 100644
--- a/game/src/plugins/texture_view.c
+++ b/game/src/plugins/texture_view.c
@@ -1,7 +1,7 @@
 #include "plugin.h"
 
 #include <gfx/asset.h>
-#include <gfx/render_backend.h>
+#include <gfx/core.h>
 #include <gfx/renderer.h>
 #include <gfx/scene.h>
 #include <gfx/util/geometry.h>
@@ -33,7 +33,7 @@ bool init(Game* game, State** pp_state) {
   // Usage: [texture file]
   const char* texture_file = game->argc > 1 ? game->argv[1] : DEFAULT_TEXTURE;
 
-  RenderBackend* render_backend = gfx_get_render_backend(game->gfx);
+  GfxCore* gfxcore = gfx_get_core(game->gfx);
 
   const Texture* texture = gfx_load_texture(
       game->gfx, &(LoadTextureCmd){
@@ -46,12 +46,12 @@ bool init(Game* game, State** pp_state) {
     goto cleanup;
   }
 
-  ShaderProgram* shader = gfx_make_view_texture_shader(render_backend);
+  ShaderProgram* shader = gfx_make_view_texture_shader(gfxcore);
   if (!shader) {
     goto cleanup;
   }
 
-  Geometry* geometry = gfx_make_quad_11(render_backend);
+  Geometry* geometry = gfx_make_quad_11(gfxcore);
   if (!geometry) {
     goto cleanup;
   }
diff --git a/game/src/plugins/viewer.c b/game/src/plugins/viewer.c
index 5e8d7d3..f621b00 100644
--- a/game/src/plugins/viewer.c
+++ b/game/src/plugins/viewer.c
@@ -58,14 +58,14 @@ static SceneNode* load_skyquad(Gfx* gfx, SceneNode* root) {
   assert(gfx);
   assert(root);
 
-  RenderBackend* render_backend = gfx_get_render_backend(gfx);
+  GfxCore* gfxcore = gfx_get_core(gfx);
 
   const Texture* environment_map = load_environment_map(gfx);
   if (!environment_map) {
     return 0;
   }
 
-  return gfx_setup_skyquad(render_backend, root, environment_map);
+  return gfx_setup_skyquad(gfxcore, root, environment_map);
 }
 
 /// Load the 3D scene.
@@ -222,17 +222,17 @@ static void render_bounding_boxes(const Game* game, const State* state) {
   assert(game);
   assert(state);
 
-  RenderBackend* render_backend = gfx_get_render_backend(game->gfx);
-  ImmRenderer*   imm            = gfx_get_imm_renderer(game->gfx);
-  assert(render_backend);
+  GfxCore*     gfxcore = gfx_get_core(game->gfx);
+  ImmRenderer* imm     = gfx_get_imm_renderer(game->gfx);
+  assert(gfxcore);
   assert(imm);
 
   const mat4 id    = mat4_id();
   Anima*     anima = 0;
 
-  gfx_set_blending(render_backend, true);
-  gfx_set_depth_mask(render_backend, false);
-  gfx_set_polygon_offset(render_backend, -1.5f, -1.0f);
+  gfx_set_blending(gfxcore, true);
+  gfx_set_depth_mask(gfxcore, false);
+  gfx_set_polygon_offset(gfxcore, -1.5f, -1.0f);
 
   gfx_imm_start(imm);
   gfx_imm_set_camera(imm, gfx_get_camera_camera(state->camera));
@@ -240,9 +240,9 @@ static void render_bounding_boxes(const Game* game, const State* state) {
   render_bounding_boxes_rec(imm, anima, &id, gfx_get_scene_root(state->scene));
   gfx_imm_end(imm);
 
-  gfx_reset_polygon_offset(render_backend);
-  gfx_set_depth_mask(render_backend, true);
-  gfx_set_blending(render_backend, false);
+  gfx_reset_polygon_offset(gfxcore);
+  gfx_set_depth_mask(gfxcore, true);
+  gfx_set_blending(gfxcore, false);
 }
 
 void render(const Game* game, const State* state) {
diff --git a/gfx-iso/src/app.c b/gfx-iso/src/app.c
index 8e0a45a..e07f318 100644
--- a/gfx-iso/src/app.c
+++ b/gfx-iso/src/app.c
@@ -2,8 +2,8 @@
 #include <isogfx/isogfx.h>
 
 #include <gfx/app.h>
+#include <gfx/core.h>
 #include <gfx/gfx.h>
-#include <gfx/render_backend.h>
 #include <gfx/renderer.h>
 #include <gfx/scene.h>
 #include <gfx/util/geometry.h>
@@ -58,26 +58,26 @@ static bool init(GfxAppState* gfx_app_state, int argc, const char** argv) {
   if (!(state->gfx = gfx_init())) {
     goto cleanup;
   }
-  RenderBackend* render_backend = gfx_get_render_backend(state->gfx);
+  GfxCore* gfxcore = gfx_get_core(state->gfx);
 
   if (!(state->screen_texture = gfx_make_texture(
-            render_backend, &(TextureDesc){
-                                .width     = texture_width,
-                                .height    = texture_height,
-                                .dimension = Texture2D,
-                                .format    = TextureSRGBA8,
-                                .filtering = NearestFiltering,
-                                .wrap      = ClampToEdge,
-                                .mipmaps   = false}))) {
+            gfxcore, &(TextureDesc){
+                         .width     = texture_width,
+                         .height    = texture_height,
+                         .dimension = Texture2D,
+                         .format    = TextureSRGBA8,
+                         .filtering = NearestFiltering,
+                         .wrap      = ClampToEdge,
+                         .mipmaps   = false}))) {
     goto cleanup;
   }
 
-  ShaderProgram* shader = gfx_make_view_texture_shader(render_backend);
+  ShaderProgram* shader = gfx_make_view_texture_shader(gfxcore);
   if (!shader) {
     goto cleanup;
   }
 
-  Geometry* geometry = gfx_make_quad_11(render_backend);
+  Geometry* geometry = gfx_make_quad_11(gfxcore);
   if (!geometry) {
     goto cleanup;
   }
@@ -155,22 +155,22 @@ static void render(GfxAppState* gfx_app_state) {
   gfx_update_texture(
       state->screen_texture, &(TextureDataDesc){.pixels = screen});
 
-  RenderBackend* render_backend = gfx_get_render_backend(state->gfx);
-  Renderer*      renderer       = gfx_get_renderer(state->gfx);
+  GfxCore*  gfxcore  = gfx_get_core(state->gfx);
+  Renderer* renderer = gfx_get_renderer(state->gfx);
 
-  gfx_start_frame(render_backend);
+  gfx_start_frame(gfxcore);
   gfx_render_scene(
       renderer, &(RenderSceneParams){
                     .mode = RenderDefault, .scene = state->scene, .camera = 0});
-  gfx_end_frame(render_backend);
+  gfx_end_frame(gfxcore);
 }
 
 static void resize(GfxAppState* gfx_app_state, int width, int height) {
   assert(gfx_app_state);
   AppState* state = &gfx_app_state->state;
 
-  RenderBackend* render_backend = gfx_get_render_backend(state->gfx);
-  gfx_set_viewport(render_backend, width, height);
+  GfxCore* gfxcore = gfx_get_core(state->gfx);
+  gfx_set_viewport(gfxcore, width, height);
 }
 
 void iso_run(int argc, const char** argv, IsoGfxApp* app) {
diff --git a/gfx/CMakeLists.txt b/gfx/CMakeLists.txt
index 6c8640c..c835bd9 100644
--- a/gfx/CMakeLists.txt
+++ b/gfx/CMakeLists.txt
@@ -37,9 +37,9 @@ add_library(gfx SHARED
   src/asset/model.c
   src/asset/texture.c
   src/render/buffer.c
+  src/render/core.c
   src/render/framebuffer.c
   src/render/geometry.c
-  src/render/render_backend.c
   src/render/renderbuffer.c
   src/render/shader_program.c
   src/render/shader.c
diff --git a/gfx/include/gfx/asset.h b/gfx/include/gfx/asset.h
index 1a4e551..caf40c1 100644
--- a/gfx/include/gfx/asset.h
+++ b/gfx/include/gfx/asset.h
@@ -1,7 +1,7 @@
 /* Asset Management */
 #pragma once
 
-#include <gfx/render_backend.h>
+#include <gfx/core.h>
 
 #include <stddef.h>
 
diff --git a/gfx/include/gfx/core.h b/gfx/include/gfx/core.h
new file mode 100644
index 0000000..7d31cca
--- /dev/null
+++ b/gfx/include/gfx/core.h
@@ -0,0 +1,498 @@
+/// Render Backend.
+///
+/// The Render Backend creates and owns graphics objects and performs low-level
+/// rendering operations.
+#pragma once
+
+#include "sizes.h"
+
+#include <math/aabb3.h>
+#include <math/fwd.h>
+#include <math/mat4.h>
+#include <math/vec4.h>
+
+#include <cstring.h>
+
+#include <stddef.h>
+#include <stdint.h>
+
+// Implementation objects.
+typedef struct Buffer        Buffer;
+typedef struct FrameBuffer   FrameBuffer;
+typedef struct Geometry      Geometry;
+typedef struct GfxCore       GfxCore;
+typedef struct RenderBuffer  RenderBuffer;
+typedef struct Shader        Shader;
+typedef struct ShaderProgram ShaderProgram;
+typedef struct Texture       Texture;
+
+/// Data type for vertex indices.
+/// Might need U32 for bigger models.
+typedef uint8_t  VertexIndex8;
+typedef uint16_t VertexIndex16;
+typedef uint16_t VertexCount;
+
+/// Geometry drawing modes.
+typedef enum PrimitiveType {
+  Triangles,
+  TriangleFan,
+  TriangleStrip
+} PrimitiveType;
+
+/// Buffer usage.
+typedef enum BufferUsage { BufferStatic, BufferDynamic } BufferUsage;
+
+/// Buffer type.
+typedef enum BufferType {
+  BufferUntyped,
+  Buffer2d,
+  Buffer3d,
+  Buffer4d,
+  BufferFloat,
+  BufferU8,
+  BufferU16
+} BufferType;
+
+/// Buffer data descriptor.
+typedef struct BufferDataDesc {
+  union {
+    const void*     data;
+    const vec2*     vec2s;
+    const vec3*     vec3s;
+    const float*    floats;
+    const uint8_t*  u8s;
+    const uint16_t* u16s;
+  };
+  size_t count;
+} BufferDataDesc;
+
+/// Buffer descriptor.
+///
+/// 'count' is the number of elements in the array. For untyped buffers, this is
+/// the size in bytes of the 'data' array. For other types, it is the number of
+/// vec2s, vec3s, etc. in the corresponding array.
+///
+/// The data pointers can also be null. In such a case, a buffer of the given
+/// size is created with its contents uninitialized.
+///
+/// TODO: Think about typed buffers (Buffer, Buffer2d, Buffer3d, BufferU8, etc).
+/// Typed buffers don't work well with interleaved vertex attributes. Not sure
+/// this is really worth it.
+typedef struct BufferDesc {
+  BufferUsage    usage;
+  BufferType     type;
+  BufferDataDesc data;
+} BufferDesc;
+
+/// A buffer view for vertex data (attributes or indices).
+/// Either 'data' or 'buffer' must be set.
+#define MAKE_BUFFER_VIEW(NAME, TYPE) \
+  typedef struct NAME {              \
+    const TYPE* data;                \
+    Buffer*     buffer;              \
+    size_t      offset_bytes;        \
+    size_t      size_bytes;          \
+    size_t      stride_bytes;        \
+  } NAME;
+
+/// A buffer view for untyped data.
+MAKE_BUFFER_VIEW(BufferView, void)
+
+/// A buffer view for 2D vectors.
+MAKE_BUFFER_VIEW(BufferView2d, vec2)
+
+/// A buffer view for 3D vectors.
+MAKE_BUFFER_VIEW(BufferView3d, vec3)
+
+/// A buffer view for 4D vectors.
+MAKE_BUFFER_VIEW(BufferView4d, vec4)
+
+/// A buffer view for floats.
+MAKE_BUFFER_VIEW(BufferViewFloat, float)
+
+/// A buffer view for 8-bit unsigned integers.
+MAKE_BUFFER_VIEW(BufferViewU8, uint8_t)
+
+/// A buffer view for 16-bit unsigned integers.
+MAKE_BUFFER_VIEW(BufferViewU16, uint16_t)
+
+/// A buffer view for 8-bit vertex indices.
+MAKE_BUFFER_VIEW(BufferViewIdx8, uint16_t)
+
+/// A buffer view for 16-bit vertex indices.
+MAKE_BUFFER_VIEW(BufferViewIdx16, uint16_t)
+
+/// Describes a piece of geometry.
+///
+/// Buffer views may point to either already-existing GPU buffers or to data in
+/// host memory.
+///
+/// If the buffer views do not already point to GPU buffers, GPU buffers are
+/// created for the geometry. The 'buffer_usage' field specifies the usage for
+/// the created buffers. Use BufferStatic for static geometry and BufferDynamic
+/// for dynamic geometry.
+///
+/// Currently we support only up to 16-bit vertex indices. Might have to change
+/// this to support a larger variety of 3D models.
+typedef struct GeometryDesc {
+  BufferView2d positions2d;
+  BufferView3d positions3d;
+  BufferView3d normals;
+  BufferView4d tangents;
+  BufferView2d texcoords;
+  struct {
+    BufferViewU8  u8;
+    BufferViewU16 u16;
+  } joints; // uvec4.
+  struct {
+    BufferViewFloat floats;
+    BufferViewU8    u8;
+    BufferViewU16   u16;
+  } weights; // vec4 or uvec4.
+  BufferViewIdx8  indices8;
+  BufferViewIdx16 indices16;
+  VertexCount     num_verts;
+  size_t          num_indices;
+  PrimitiveType   type;
+  BufferUsage     buffer_usage;
+  aabb3           aabb;
+} GeometryDesc;
+
+/// Shader compiler define.
+typedef struct ShaderCompilerDefine {
+  sstring name;
+  sstring value;
+} ShaderCompilerDefine;
+
+/// Shader types.
+typedef enum { VertexShader, FragmentShader } ShaderType;
+
+/// Describes a shader.
+typedef struct ShaderDesc {
+  ShaderType           type;
+  const char*          code;
+  ShaderCompilerDefine defines[GFX_MAX_SHADER_COMPILER_DEFINES];
+  size_t               num_defines;
+} ShaderDesc;
+
+/// Describes a shader program.
+typedef struct ShaderProgramDesc {
+  const Shader* vertex_shader;
+  const Shader* fragment_shader;
+} ShaderProgramDesc;
+
+/// Shader uniform type.
+typedef enum {
+  UniformFloat,
+  UniformMat4,
+  UniformTexture,
+  UniformVec3,
+  UniformVec4,
+  UniformMat4Array
+} UniformType;
+
+/// Shader uniform.
+///
+/// For uniform arrays, the client must ensure that the array is still valid by
+/// the time the uniform data is passed to the GPU.
+typedef struct ShaderUniform {
+  sstring     name;
+  UniformType type;
+  union {
+    const Texture* texture;
+    mat4           mat4;
+    vec3           vec3;
+    vec4           vec4;
+    float          scalar;
+    struct {
+      size_t count;
+      union {
+        const mat4* values;
+      };
+    } array;
+  } value;
+} ShaderUniform;
+
+/// Texture dimension.
+typedef enum { Texture2D, TextureCubeMap } TextureDimension;
+
+/// Texture data format.
+typedef enum {
+  TextureDepth,
+  TextureRG16,
+  TextureRG16F,
+  TextureRGB8,
+  TextureR11G11B10F,
+  TextureRGBA8,
+  TextureSRGB8,
+  TextureSRGBA8
+} TextureFormat;
+
+/// Texture filtering.
+typedef enum { NearestFiltering, LinearFiltering } TextureFiltering;
+
+/// Texture wrap mode.
+typedef enum { Repeat, ClampToEdge } TextureWrapping;
+
+/// Cubemap faces.
+typedef enum {
+  CubemapFacePosX,
+  CubemapFaceNegX,
+  CubemapFacePosY,
+  CubemapFaceNegY,
+  CubemapFacePosZ,
+  CubemapFaceNegZ
+} CubemapFace;
+
+/// Texture data descriptor.
+typedef struct TextureDataDesc {
+  union {
+    const void* pixels;
+    struct {
+      const void* pixels_pos_x;
+      const void* pixels_neg_x;
+      const void* pixels_pos_y;
+      const void* pixels_neg_y;
+      const void* pixels_pos_z;
+      const void* pixels_neg_z;
+    } cubemap;
+  };
+} TextureDataDesc;
+
+/// Describes a texture.
+typedef struct TextureDesc {
+  int              width;
+  int              height;
+  int              depth; // Not used until 3D textures are exposed.
+  TextureDimension dimension;
+  TextureFormat    format;
+  TextureFiltering filtering;
+  TextureWrapping  wrap;
+  bool             mipmaps;
+  TextureDataDesc  data;
+} TextureDesc;
+
+/// Describes a renderbuffer.
+typedef struct RenderBufferDesc {
+  int           width;
+  int           height;
+  TextureFormat texture_format;
+} RenderBufferDesc;
+
+/// Framebuffer attachment type.
+typedef enum FrameBufferAttachmentType {
+  FrameBufferNoAttachment,
+  FrameBufferTexture,
+  FrameBufferCubemapTexture,
+  FrameBufferRenderBuffer
+} FrameBufferAttachmentType;
+
+/// Describes a framebuffer attachment.
+typedef struct FrameBufferAttachment {
+  FrameBufferAttachmentType type;
+  union {
+    struct {
+      Texture* texture;
+      int      mip_level;
+    } texture;
+    struct {
+      Texture*    texture;
+      int         mip_level;
+      CubemapFace face;
+    } cubemap;
+    RenderBuffer* renderbuffer;
+  };
+} FrameBufferAttachment;
+
+/// Describes a framebuffer.
+typedef struct FrameBufferDesc {
+  FrameBufferAttachment colour;
+  FrameBufferAttachment depth;
+} FrameBufferDesc;
+
+// -----------------------------------------------------------------------------
+// Render commands.
+// -----------------------------------------------------------------------------
+
+/// Start a new frame.
+void gfx_start_frame(GfxCore*);
+
+/// End a frame.
+void gfx_end_frame(GfxCore*);
+
+/// Set the render backend's viewport dimensions.
+void gfx_set_viewport(GfxCore*, int width, int height);
+
+/// Get the render backend's viewport dimensions.
+void gfx_get_viewport(GfxCore*, int* width, int* height);
+
+/// Set blending state.
+void gfx_set_blending(GfxCore*, bool enable);
+
+/// Set depth mask.
+void gfx_set_depth_mask(GfxCore*, bool enable);
+
+/// Set cull mode.
+void gfx_set_culling(GfxCore*, bool enable);
+
+/// Set polygon offset.
+void gfx_set_polygon_offset(GfxCore*, float scale, float bias);
+
+/// Reset the polygon offset.
+void gfx_reset_polygon_offset(GfxCore*);
+
+// -----------------------------------------------------------------------------
+// Buffers.
+// -----------------------------------------------------------------------------
+
+/// Create a buffer from raw data.
+Buffer* gfx_make_buffer(GfxCore*, const BufferDesc*);
+
+/// Destroy the buffer.
+void gfx_destroy_buffer(GfxCore*, Buffer**);
+
+/// Update the buffer's data.
+void gfx_update_buffer(Buffer*, const BufferDataDesc*);
+
+// -----------------------------------------------------------------------------
+// Geometry.
+// -----------------------------------------------------------------------------
+
+/// Create geometry.
+Geometry* gfx_make_geometry(GfxCore*, const GeometryDesc*);
+
+/// Destroy the geometry.
+void gfx_destroy_geometry(GfxCore*, Geometry**);
+
+/// Upload new vertex data for the geometry.
+///
+/// This is similar to gfx_make_geometry(), but the geometry need not be
+/// entirely specified.
+///
+/// Only the vertex attributes, vertex count, and index count set in the
+/// descriptor are updated. Index data, primitive type, and other properties of
+/// the geometry are not updated.
+///
+/// New data must be given as arrays in host memory. That is, the buffer views
+/// in the descriptor must point to CPU arrays, not GPU buffers.
+///
+/// Note that the descriptor cannot specify a larger vertex or index count than
+/// what the geometry was created with. If the geometry size or any other
+/// attribute not handled by this update function needs to be changed, then a
+/// new geometry must be created.
+void gfx_update_geometry(Geometry*, const GeometryDesc*);
+
+/// Render the geometry.
+void gfx_render_geometry(const Geometry*);
+
+/// Return the geometry's bounding box.
+aabb3 gfx_get_geometry_aabb(const Geometry*);
+
+// -----------------------------------------------------------------------------
+// Textures.
+// -----------------------------------------------------------------------------
+
+/// Create a texture.
+Texture* gfx_make_texture(GfxCore*, const TextureDesc*);
+
+/// Destroy the texture.
+void gfx_destroy_texture(GfxCore*, Texture**);
+
+/// Update the texture.
+void gfx_update_texture(Texture*, const TextureDataDesc*);
+
+// -----------------------------------------------------------------------------
+// Renderbuffers.
+// -----------------------------------------------------------------------------
+
+/// Create a renderbuffer.
+RenderBuffer* gfx_make_renderbuffer(GfxCore*, const RenderBufferDesc*);
+
+/// Destroy the renderbuffer.
+void gfx_destroy_renderbuffer(GfxCore*, RenderBuffer**);
+
+// -----------------------------------------------------------------------------
+// Framebuffers.
+// -----------------------------------------------------------------------------
+
+/// Create a framebuffer.
+FrameBuffer* gfx_make_framebuffer(GfxCore*, const FrameBufferDesc*);
+
+/// Destroy the framebuffer.
+void gfx_destroy_framebuffer(GfxCore*, FrameBuffer**);
+
+/// Attach a colour buffer to the framebuffer.
+bool gfx_framebuffer_attach_colour(FrameBuffer*, const FrameBufferAttachment*);
+
+/// Attach a depth buffer to the framebuffer.
+bool gfx_framebuffer_attach_depth(FrameBuffer*, const FrameBufferAttachment*);
+
+/// Activate the framebuffer.
+/// Subsequent draw calls write to this framebuffer.
+void gfx_activate_framebuffer(const FrameBuffer*);
+
+/// Deactivate the framebuffer.
+/// Subsequent draw calls write to the default framebuffer.
+void gfx_deactivate_framebuffer(const FrameBuffer*);
+
+/// Set the framebuffer's viewport.
+/// This function should be called every time the framebuffer is activated.
+void gfx_framebuffer_set_viewport(
+    FrameBuffer*, int x, int y, int width, int height);
+
+// -----------------------------------------------------------------------------
+// Shaders.
+// -----------------------------------------------------------------------------
+
+/// Create a shader.
+Shader* gfx_make_shader(GfxCore*, const ShaderDesc*);
+
+/// Destroy the shader.
+void gfx_destroy_shader(GfxCore*, Shader**);
+
+/// Create a shader program.
+ShaderProgram* gfx_make_shader_program(GfxCore*, const ShaderProgramDesc*);
+
+/// Destroy the shader program.
+void gfx_destroy_shader_program(GfxCore*, ShaderProgram**);
+
+/// Activate the shader program.
+void gfx_activate_shader_program(const ShaderProgram*);
+
+/// Deactivate the shader program.
+void gfx_deactivate_shader_program(const ShaderProgram*);
+
+/// Apply the shader program's uniform variables.
+///
+/// Calls to gfx_set_XYZ_uniform save the values of the uniform variables in the
+/// graphics library. By calling this function, those values are passed on to
+/// the graphics driver for rendering.
+///
+/// This function should be called after setting all of the uniform variables
+/// and prior to issuing a draw call.
+void gfx_apply_uniforms(const ShaderProgram*);
+
+/// Set the texture uniform.
+/// Has no effect if the shader does not contain the given uniform.
+void gfx_set_texture_uniform(ShaderProgram*, const char* name, const Texture*);
+
+/// Set the matrix uniform.
+/// Has no effect if the shader does not contain the given uniform.
+void gfx_set_mat4_uniform(ShaderProgram*, const char* name, const mat4*);
+
+/// Set the vec3 uniform.
+/// Has no effect if the shader does not contain the given uniform.
+void gfx_set_vec3_uniform(ShaderProgram*, const char* name, vec3);
+
+/// Set the vec4 uniform.
+/// Has no effect if the shader does not contain the given uniform.
+void gfx_set_vec4_uniform(ShaderProgram*, const char* name, vec4);
+
+/// Set the float uniform.
+/// Has no effect if the shader does not contain the given uniform.
+void gfx_set_float_uniform(ShaderProgram*, const char* name, float value);
+
+/// Set the matrix array uniform.
+/// Has no effect if the shader does not contain the given uniform.
+void gfx_set_mat4_array_uniform(
+    ShaderProgram*, const char* name, const mat4*, size_t count);
diff --git a/gfx/include/gfx/gfx.h b/gfx/include/gfx/gfx.h
index bfc457f..7c670a5 100644
--- a/gfx/include/gfx/gfx.h
+++ b/gfx/include/gfx/gfx.h
@@ -1,9 +1,9 @@
 #pragma once
 
-typedef struct AssetCache    AssetCache;
-typedef struct ImmRenderer   ImmRenderer;
-typedef struct RenderBackend RenderBackend;
-typedef struct Renderer      Renderer;
+typedef struct AssetCache  AssetCache;
+typedef struct GfxCore     GfxCore;
+typedef struct ImmRenderer ImmRenderer;
+typedef struct Renderer    Renderer;
 
 typedef struct Gfx Gfx;
 
@@ -14,7 +14,7 @@ Gfx* gfx_init(void);
 void gfx_destroy(Gfx**);
 
 /// Get the render backend.
-RenderBackend* gfx_get_render_backend(Gfx*);
+GfxCore* gfx_get_core(Gfx*);
 
 /// Get the renderer.
 Renderer* gfx_get_renderer(Gfx*);
diff --git a/gfx/include/gfx/render_backend.h b/gfx/include/gfx/render_backend.h
deleted file mode 100644
index 8d3c42b..0000000
--- a/gfx/include/gfx/render_backend.h
+++ /dev/null
@@ -1,499 +0,0 @@
-/// Render Backend.
-///
-/// The Render Backend creates and owns graphics objects and performs low-level
-/// rendering operations.
-#pragma once
-
-#include "sizes.h"
-
-#include <math/aabb3.h>
-#include <math/fwd.h>
-#include <math/mat4.h>
-#include <math/vec4.h>
-
-#include <cstring.h>
-
-#include <stddef.h>
-#include <stdint.h>
-
-// Implementation objects.
-typedef struct Buffer        Buffer;
-typedef struct FrameBuffer   FrameBuffer;
-typedef struct Geometry      Geometry;
-typedef struct RenderBuffer  RenderBuffer;
-typedef struct Shader        Shader;
-typedef struct ShaderProgram ShaderProgram;
-typedef struct Texture       Texture;
-typedef struct RenderBackend RenderBackend;
-
-/// Data type for vertex indices.
-/// Might need U32 for bigger models.
-typedef uint8_t  VertexIndex8;
-typedef uint16_t VertexIndex16;
-typedef uint16_t VertexCount;
-
-/// Geometry drawing modes.
-typedef enum PrimitiveType {
-  Triangles,
-  TriangleFan,
-  TriangleStrip
-} PrimitiveType;
-
-/// Buffer usage.
-typedef enum BufferUsage { BufferStatic, BufferDynamic } BufferUsage;
-
-/// Buffer type.
-typedef enum BufferType {
-  BufferUntyped,
-  Buffer2d,
-  Buffer3d,
-  Buffer4d,
-  BufferFloat,
-  BufferU8,
-  BufferU16
-} BufferType;
-
-/// Buffer data descriptor.
-typedef struct BufferDataDesc {
-  union {
-    const void*     data;
-    const vec2*     vec2s;
-    const vec3*     vec3s;
-    const float*    floats;
-    const uint8_t*  u8s;
-    const uint16_t* u16s;
-  };
-  size_t count;
-} BufferDataDesc;
-
-/// Buffer descriptor.
-///
-/// 'count' is the number of elements in the array. For untyped buffers, this is
-/// the size in bytes of the 'data' array. For other types, it is the number of
-/// vec2s, vec3s, etc. in the corresponding array.
-///
-/// The data pointers can also be null. In such a case, a buffer of the given
-/// size is created with its contents uninitialized.
-///
-/// TODO: Think about typed buffers (Buffer, Buffer2d, Buffer3d, BufferU8, etc).
-/// Typed buffers don't work well with interleaved vertex attributes. Not sure
-/// this is really worth it.
-typedef struct BufferDesc {
-  BufferUsage    usage;
-  BufferType     type;
-  BufferDataDesc data;
-} BufferDesc;
-
-/// A buffer view for vertex data (attributes or indices).
-/// Either 'data' or 'buffer' must be set.
-#define MAKE_BUFFER_VIEW(NAME, TYPE) \
-  typedef struct NAME {              \
-    const TYPE* data;                \
-    Buffer*     buffer;              \
-    size_t      offset_bytes;        \
-    size_t      size_bytes;          \
-    size_t      stride_bytes;        \
-  } NAME;
-
-/// A buffer view for untyped data.
-MAKE_BUFFER_VIEW(BufferView, void)
-
-/// A buffer view for 2D vectors.
-MAKE_BUFFER_VIEW(BufferView2d, vec2)
-
-/// A buffer view for 3D vectors.
-MAKE_BUFFER_VIEW(BufferView3d, vec3)
-
-/// A buffer view for 4D vectors.
-MAKE_BUFFER_VIEW(BufferView4d, vec4)
-
-/// A buffer view for floats.
-MAKE_BUFFER_VIEW(BufferViewFloat, float)
-
-/// A buffer view for 8-bit unsigned integers.
-MAKE_BUFFER_VIEW(BufferViewU8, uint8_t)
-
-/// A buffer view for 16-bit unsigned integers.
-MAKE_BUFFER_VIEW(BufferViewU16, uint16_t)
-
-/// A buffer view for 8-bit vertex indices.
-MAKE_BUFFER_VIEW(BufferViewIdx8, uint16_t)
-
-/// A buffer view for 16-bit vertex indices.
-MAKE_BUFFER_VIEW(BufferViewIdx16, uint16_t)
-
-/// Describes a piece of geometry.
-///
-/// Buffer views may point to either already-existing GPU buffers or to data in
-/// host memory.
-///
-/// If the buffer views do not already point to GPU buffers, GPU buffers are
-/// created for the geometry. The 'buffer_usage' field specifies the usage for
-/// the created buffers. Use BufferStatic for static geometry and BufferDynamic
-/// for dynamic geometry.
-///
-/// Currently we support only up to 16-bit vertex indices. Might have to change
-/// this to support a larger variety of 3D models.
-typedef struct GeometryDesc {
-  BufferView2d positions2d;
-  BufferView3d positions3d;
-  BufferView3d normals;
-  BufferView4d tangents;
-  BufferView2d texcoords;
-  struct {
-    BufferViewU8  u8;
-    BufferViewU16 u16;
-  } joints; // uvec4.
-  struct {
-    BufferViewFloat floats;
-    BufferViewU8    u8;
-    BufferViewU16   u16;
-  } weights; // vec4 or uvec4.
-  BufferViewIdx8  indices8;
-  BufferViewIdx16 indices16;
-  VertexCount     num_verts;
-  size_t          num_indices;
-  PrimitiveType   type;
-  BufferUsage     buffer_usage;
-  aabb3           aabb;
-} GeometryDesc;
-
-/// Shader compiler define.
-typedef struct ShaderCompilerDefine {
-  sstring name;
-  sstring value;
-} ShaderCompilerDefine;
-
-/// Shader types.
-typedef enum { VertexShader, FragmentShader } ShaderType;
-
-/// Describes a shader.
-typedef struct ShaderDesc {
-  ShaderType           type;
-  const char*          code;
-  ShaderCompilerDefine defines[GFX_MAX_SHADER_COMPILER_DEFINES];
-  size_t               num_defines;
-} ShaderDesc;
-
-/// Describes a shader program.
-typedef struct ShaderProgramDesc {
-  const Shader* vertex_shader;
-  const Shader* fragment_shader;
-} ShaderProgramDesc;
-
-/// Shader uniform type.
-typedef enum {
-  UniformFloat,
-  UniformMat4,
-  UniformTexture,
-  UniformVec3,
-  UniformVec4,
-  UniformMat4Array
-} UniformType;
-
-/// Shader uniform.
-///
-/// For uniform arrays, the client must ensure that the array is still valid by
-/// the time the uniform data is passed to the GPU.
-typedef struct ShaderUniform {
-  sstring     name;
-  UniformType type;
-  union {
-    const Texture* texture;
-    mat4           mat4;
-    vec3           vec3;
-    vec4           vec4;
-    float          scalar;
-    struct {
-      size_t count;
-      union {
-        const mat4* values;
-      };
-    } array;
-  } value;
-} ShaderUniform;
-
-/// Texture dimension.
-typedef enum { Texture2D, TextureCubeMap } TextureDimension;
-
-/// Texture data format.
-typedef enum {
-  TextureDepth,
-  TextureRG16,
-  TextureRG16F,
-  TextureRGB8,
-  TextureR11G11B10F,
-  TextureRGBA8,
-  TextureSRGB8,
-  TextureSRGBA8
-} TextureFormat;
-
-/// Texture filtering.
-typedef enum { NearestFiltering, LinearFiltering } TextureFiltering;
-
-/// Texture wrap mode.
-typedef enum { Repeat, ClampToEdge } TextureWrapping;
-
-/// Cubemap faces.
-typedef enum {
-  CubemapFacePosX,
-  CubemapFaceNegX,
-  CubemapFacePosY,
-  CubemapFaceNegY,
-  CubemapFacePosZ,
-  CubemapFaceNegZ
-} CubemapFace;
-
-/// Texture data descriptor.
-typedef struct TextureDataDesc {
-  union {
-    const void* pixels;
-    struct {
-      const void* pixels_pos_x;
-      const void* pixels_neg_x;
-      const void* pixels_pos_y;
-      const void* pixels_neg_y;
-      const void* pixels_pos_z;
-      const void* pixels_neg_z;
-    } cubemap;
-  };
-} TextureDataDesc;
-
-/// Describes a texture.
-typedef struct TextureDesc {
-  int              width;
-  int              height;
-  int              depth; // Not used until 3D textures are exposed.
-  TextureDimension dimension;
-  TextureFormat    format;
-  TextureFiltering filtering;
-  TextureWrapping  wrap;
-  bool             mipmaps;
-  TextureDataDesc  data;
-} TextureDesc;
-
-/// Describes a renderbuffer.
-typedef struct RenderBufferDesc {
-  int           width;
-  int           height;
-  TextureFormat texture_format;
-} RenderBufferDesc;
-
-/// Framebuffer attachment type.
-typedef enum FrameBufferAttachmentType {
-  FrameBufferNoAttachment,
-  FrameBufferTexture,
-  FrameBufferCubemapTexture,
-  FrameBufferRenderBuffer
-} FrameBufferAttachmentType;
-
-/// Describes a framebuffer attachment.
-typedef struct FrameBufferAttachment {
-  FrameBufferAttachmentType type;
-  union {
-    struct {
-      Texture* texture;
-      int      mip_level;
-    } texture;
-    struct {
-      Texture*    texture;
-      int         mip_level;
-      CubemapFace face;
-    } cubemap;
-    RenderBuffer* renderbuffer;
-  };
-} FrameBufferAttachment;
-
-/// Describes a framebuffer.
-typedef struct FrameBufferDesc {
-  FrameBufferAttachment colour;
-  FrameBufferAttachment depth;
-} FrameBufferDesc;
-
-// -----------------------------------------------------------------------------
-// Render commands.
-// -----------------------------------------------------------------------------
-
-/// Start a new frame.
-void gfx_start_frame(RenderBackend*);
-
-/// End a frame.
-void gfx_end_frame(RenderBackend*);
-
-/// Set the render backend's viewport dimensions.
-void gfx_set_viewport(RenderBackend*, int width, int height);
-
-/// Get the render backend's viewport dimensions.
-void gfx_get_viewport(RenderBackend*, int* width, int* height);
-
-/// Set blending state.
-void gfx_set_blending(RenderBackend*, bool enable);
-
-/// Set depth mask.
-void gfx_set_depth_mask(RenderBackend*, bool enable);
-
-/// Set cull mode.
-void gfx_set_culling(RenderBackend*, bool enable);
-
-/// Set polygon offset.
-void gfx_set_polygon_offset(RenderBackend*, float scale, float bias);
-
-/// Reset the polygon offset.
-void gfx_reset_polygon_offset(RenderBackend*);
-
-// -----------------------------------------------------------------------------
-// Buffers.
-// -----------------------------------------------------------------------------
-
-/// Create a buffer from raw data.
-Buffer* gfx_make_buffer(RenderBackend*, const BufferDesc*);
-
-/// Destroy the buffer.
-void gfx_destroy_buffer(RenderBackend*, Buffer**);
-
-/// Update the buffer's data.
-void gfx_update_buffer(Buffer*, const BufferDataDesc*);
-
-// -----------------------------------------------------------------------------
-// Geometry.
-// -----------------------------------------------------------------------------
-
-/// Create geometry.
-Geometry* gfx_make_geometry(RenderBackend*, const GeometryDesc*);
-
-/// Destroy the geometry.
-void gfx_destroy_geometry(RenderBackend*, Geometry**);
-
-/// Upload new vertex data for the geometry.
-///
-/// This is similar to gfx_make_geometry(), but the geometry need not be
-/// entirely specified.
-///
-/// Only the vertex attributes, vertex count, and index count set in the
-/// descriptor are updated. Index data, primitive type, and other properties of
-/// the geometry are not updated.
-///
-/// New data must be given as arrays in host memory. That is, the buffer views
-/// in the descriptor must point to CPU arrays, not GPU buffers.
-///
-/// Note that the descriptor cannot specify a larger vertex or index count than
-/// what the geometry was created with. If the geometry size or any other
-/// attribute not handled by this update function needs to be changed, then a
-/// new geometry must be created.
-void gfx_update_geometry(Geometry*, const GeometryDesc*);
-
-/// Render the geometry.
-void gfx_render_geometry(const Geometry*);
-
-/// Return the geometry's bounding box.
-aabb3 gfx_get_geometry_aabb(const Geometry*);
-
-// -----------------------------------------------------------------------------
-// Textures.
-// -----------------------------------------------------------------------------
-
-/// Create a texture.
-Texture* gfx_make_texture(RenderBackend*, const TextureDesc*);
-
-/// Destroy the texture.
-void gfx_destroy_texture(RenderBackend*, Texture**);
-
-/// Update the texture.
-void gfx_update_texture(Texture*, const TextureDataDesc*);
-
-// -----------------------------------------------------------------------------
-// Renderbuffers.
-// -----------------------------------------------------------------------------
-
-/// Create a renderbuffer.
-RenderBuffer* gfx_make_renderbuffer(RenderBackend*, const RenderBufferDesc*);
-
-/// Destroy the renderbuffer.
-void gfx_destroy_renderbuffer(RenderBackend*, RenderBuffer**);
-
-// -----------------------------------------------------------------------------
-// Framebuffers.
-// -----------------------------------------------------------------------------
-
-/// Create a framebuffer.
-FrameBuffer* gfx_make_framebuffer(RenderBackend*, const FrameBufferDesc*);
-
-/// Destroy the framebuffer.
-void gfx_destroy_framebuffer(RenderBackend*, FrameBuffer**);
-
-/// Attach a colour buffer to the framebuffer.
-bool gfx_framebuffer_attach_colour(FrameBuffer*, const FrameBufferAttachment*);
-
-/// Attach a depth buffer to the framebuffer.
-bool gfx_framebuffer_attach_depth(FrameBuffer*, const FrameBufferAttachment*);
-
-/// Activate the framebuffer.
-/// Subsequent draw calls write to this framebuffer.
-void gfx_activate_framebuffer(const FrameBuffer*);
-
-/// Deactivate the framebuffer.
-/// Subsequent draw calls write to the default framebuffer.
-void gfx_deactivate_framebuffer(const FrameBuffer*);
-
-/// Set the framebuffer's viewport.
-/// This function should be called every time the framebuffer is activated.
-void gfx_framebuffer_set_viewport(
-    FrameBuffer*, int x, int y, int width, int height);
-
-// -----------------------------------------------------------------------------
-// Shaders.
-// -----------------------------------------------------------------------------
-
-/// Create a shader.
-Shader* gfx_make_shader(RenderBackend*, const ShaderDesc*);
-
-/// Destroy the shader.
-void gfx_destroy_shader(RenderBackend*, Shader**);
-
-/// Create a shader program.
-ShaderProgram* gfx_make_shader_program(
-    RenderBackend*, const ShaderProgramDesc*);
-
-/// Destroy the shader program.
-void gfx_destroy_shader_program(RenderBackend*, ShaderProgram**);
-
-/// Activate the shader program.
-void gfx_activate_shader_program(const ShaderProgram*);
-
-/// Deactivate the shader program.
-void gfx_deactivate_shader_program(const ShaderProgram*);
-
-/// Apply the shader program's uniform variables.
-///
-/// Calls to gfx_set_XYZ_uniform save the values of the uniform variables in the
-/// graphics library. By calling this function, those values are passed on to
-/// the graphics driver for rendering.
-///
-/// This function should be called after setting all of the uniform variables
-/// and prior to issuing a draw call.
-void gfx_apply_uniforms(const ShaderProgram*);
-
-/// Set the texture uniform.
-/// Has no effect if the shader does not contain the given uniform.
-void gfx_set_texture_uniform(ShaderProgram*, const char* name, const Texture*);
-
-/// Set the matrix uniform.
-/// Has no effect if the shader does not contain the given uniform.
-void gfx_set_mat4_uniform(ShaderProgram*, const char* name, const mat4*);
-
-/// Set the vec3 uniform.
-/// Has no effect if the shader does not contain the given uniform.
-void gfx_set_vec3_uniform(ShaderProgram*, const char* name, vec3);
-
-/// Set the vec4 uniform.
-/// Has no effect if the shader does not contain the given uniform.
-void gfx_set_vec4_uniform(ShaderProgram*, const char* name, vec4);
-
-/// Set the float uniform.
-/// Has no effect if the shader does not contain the given uniform.
-void gfx_set_float_uniform(ShaderProgram*, const char* name, float value);
-
-/// Set the matrix array uniform.
-/// Has no effect if the shader does not contain the given uniform.
-void gfx_set_mat4_array_uniform(
-    ShaderProgram*, const char* name, const mat4*, size_t count);
diff --git a/gfx/include/gfx/renderer.h b/gfx/include/gfx/renderer.h
index 9236e3f..2a4ada1 100644
--- a/gfx/include/gfx/renderer.h
+++ b/gfx/include/gfx/renderer.h
@@ -8,9 +8,9 @@
 #include <math/vec3.h>
 #include <math/vec4.h>
 
-typedef struct RenderBackend RenderBackend;
-typedef struct Scene         Scene;
-typedef struct SceneCamera   SceneCamera;
+typedef struct GfxCore     GfxCore;
+typedef struct Scene       Scene;
+typedef struct SceneCamera SceneCamera;
 
 typedef struct ImmRenderer ImmRenderer;
 typedef struct Renderer    Renderer;
diff --git a/gfx/include/gfx/scene/material.h b/gfx/include/gfx/scene/material.h
index 07e31d4..bca664e 100644
--- a/gfx/include/gfx/scene/material.h
+++ b/gfx/include/gfx/scene/material.h
@@ -1,6 +1,6 @@
 #pragma once
 
-#include <gfx/render_backend.h>
+#include <gfx/core.h>
 #include <gfx/sizes.h>
 
 typedef struct Material Material;
diff --git a/gfx/include/gfx/util/geometry.h b/gfx/include/gfx/util/geometry.h
index c62022b..a962291 100644
--- a/gfx/include/gfx/util/geometry.h
+++ b/gfx/include/gfx/util/geometry.h
@@ -1,13 +1,13 @@
 /// Functions to construct geometry procedurally.
 #pragma once
 
-#include <gfx/render_backend.h>
+#include <gfx/core.h>
 
 #include <math/vec2.h>
 #include <math/vec3.h>
 
 /// Construct a quad with positions in the range [-1, 1]^2.
-Geometry* gfx_make_quad_11(RenderBackend*);
+Geometry* gfx_make_quad_11(GfxCore*);
 
 /// Construct a quad with positions in the range [0, 1]^2.
-Geometry* gfx_make_quad_01(RenderBackend*);
+Geometry* gfx_make_quad_01(GfxCore*);
diff --git a/gfx/include/gfx/util/ibl.h b/gfx/include/gfx/util/ibl.h
index 5d76e54..6e39180 100644
--- a/gfx/include/gfx/util/ibl.h
+++ b/gfx/include/gfx/util/ibl.h
@@ -3,26 +3,23 @@
 
 typedef struct IBL IBL;
 
-typedef struct RenderBackend RenderBackend;
+typedef struct GfxCore GfxCore;
 typedef struct Texture Texture;
 
 /// Create an environment map filterer for IBL.
-IBL* gfx_make_ibl(RenderBackend*);
+IBL* gfx_make_ibl(GfxCore*);
 
 /// Destroy the environment map filterer.
-void gfx_destroy_ibl(RenderBackend*, IBL**);
+void gfx_destroy_ibl(GfxCore*, IBL**);
 
 /// Create a BRDF integration map for IBL.
-Texture* gfx_make_brdf_integration_map(IBL*, RenderBackend*, int width,
-                                       int height);
+Texture* gfx_make_brdf_integration_map(IBL*, GfxCore*, int width, int height);
 
 /// Create an irradiance map (cubemap) from an environment map for IBL.
-Texture* gfx_make_irradiance_map(IBL*, RenderBackend*,
-                                 const Texture* environment_map, int width,
-                                 int height);
+Texture* gfx_make_irradiance_map(
+    IBL*, GfxCore*, const Texture* environment_map, int width, int height);
 
 /// Create a prefiltered environment map (cubemap) for IBL.
-Texture* gfx_make_prefiltered_environment_map(IBL*, RenderBackend*,
-                                              const Texture* environment_map,
-                                              int width, int height,
-                                              int* max_mip_level);
+Texture* gfx_make_prefiltered_environment_map(
+    IBL*, GfxCore*, const Texture* environment_map, int width, int height,
+    int* max_mip_level);
diff --git a/gfx/include/gfx/util/shader.h b/gfx/include/gfx/util/shader.h
index 9bde8cf..bd058f4 100644
--- a/gfx/include/gfx/util/shader.h
+++ b/gfx/include/gfx/util/shader.h
@@ -3,44 +3,44 @@
 
 #include <stddef.h>
 
-typedef struct RenderBackend        RenderBackend;
+typedef struct GfxCore              GfxCore;
 typedef struct ShaderCompilerDefine ShaderCompilerDefine;
 typedef struct ShaderProgram        ShaderProgram;
 
 /// Create a BRDF integration map shader.
-ShaderProgram* gfx_make_brdf_integration_map_shader(RenderBackend*);
+ShaderProgram* gfx_make_brdf_integration_map_shader(GfxCore*);
 
 /// Create a Cook-Torrance shader.
-ShaderProgram* gfx_make_cook_torrance_shader(RenderBackend*);
+ShaderProgram* gfx_make_cook_torrance_shader(GfxCore*);
 
 /// Create a Cook-Torrance shader with additional shader compiler defines.
 /// This function can be used to create shader permutations.
 ShaderProgram* gfx_make_cook_torrance_shader_perm(
-    RenderBackend*, const ShaderCompilerDefine*, size_t num_defines);
+    GfxCore*, const ShaderCompilerDefine*, size_t num_defines);
 
 /// Create a 3D debugging shader.
-ShaderProgram* gfx_make_debug3d_shader(RenderBackend*);
+ShaderProgram* gfx_make_debug3d_shader(GfxCore*);
 
 /// Create a shader for drawing in immediate mode.
-ShaderProgram* gfx_make_immediate_mode_shader(RenderBackend*);
+ShaderProgram* gfx_make_immediate_mode_shader(GfxCore*);
 
 /// Create a shader for computing irradiance maps from cube maps.
-ShaderProgram* gfx_make_irradiance_map_shader(RenderBackend*);
+ShaderProgram* gfx_make_irradiance_map_shader(GfxCore*);
 
 /// Create a shader for computing prefiltered environment maps from cube maps.
-ShaderProgram* gfx_make_prefiltered_environment_map_shader(RenderBackend*);
+ShaderProgram* gfx_make_prefiltered_environment_map_shader(GfxCore*);
 
 /// Create a skyquad shader.
-ShaderProgram* gfx_make_skyquad_shader(RenderBackend*);
+ShaderProgram* gfx_make_skyquad_shader(GfxCore*);
 
 /// Create a shader to view normal-mapped normals.
-ShaderProgram* gfx_make_view_normal_mapped_normals_shader(RenderBackend*);
+ShaderProgram* gfx_make_view_normal_mapped_normals_shader(GfxCore*);
 
 /// Create a shader to view vertex normals.
-ShaderProgram* gfx_make_view_normals_shader(RenderBackend*);
+ShaderProgram* gfx_make_view_normals_shader(GfxCore*);
 
 /// Create a shader to view vertex tangents.
-ShaderProgram* gfx_make_view_tangents_shader(RenderBackend*);
+ShaderProgram* gfx_make_view_tangents_shader(GfxCore*);
 
 /// Create a shader to view textures.
-ShaderProgram* gfx_make_view_texture_shader(RenderBackend*);
+ShaderProgram* gfx_make_view_texture_shader(GfxCore*);
diff --git a/gfx/include/gfx/util/skyquad.h b/gfx/include/gfx/util/skyquad.h
index 923c6e5..2b3fe17 100644
--- a/gfx/include/gfx/util/skyquad.h
+++ b/gfx/include/gfx/util/skyquad.h
@@ -1,14 +1,14 @@
 /// A skyquad is like a skybox but with a single quad.
 #pragma once
 
-typedef struct RenderBackend RenderBackend;
-typedef struct Scene         Scene;
-typedef struct SceneNode     SceneNode;
-typedef struct SceneObject   SceneObject;
-typedef struct Texture       Texture;
+typedef struct GfxCore     GfxCore;
+typedef struct Scene       Scene;
+typedef struct SceneNode   SceneNode;
+typedef struct SceneObject SceneObject;
+typedef struct Texture     Texture;
 
 /// Create a skyquad.
-SceneObject* gfx_make_skyquad(RenderBackend*, const Texture*);
+SceneObject* gfx_make_skyquad(GfxCore*, const Texture*);
 
 /// Set up a skyquad in the scene.
 ///
@@ -19,4 +19,4 @@ SceneObject* gfx_make_skyquad(RenderBackend*, const Texture*);
 /// Return the light node under which objects affected by the light can be
 /// rooted.
 SceneNode* gfx_setup_skyquad(
-    RenderBackend*, SceneNode* root, const Texture* environment_map);
+    GfxCore*, SceneNode* root, const Texture* environment_map);
diff --git a/gfx/src/asset/asset_cache.c b/gfx/src/asset/asset_cache.c
index d077421..16c4d5c 100644
--- a/gfx/src/asset/asset_cache.c
+++ b/gfx/src/asset/asset_cache.c
@@ -237,8 +237,8 @@ const Texture* gfx_load_texture(Gfx* gfx, const LoadTextureCmd* cmd) {
 
   // Asset not found in the cache.
   // Load it, insert it into the cache, and return it.
-  RenderBackend* render_backend = gfx_get_render_backend(gfx);
-  const Texture* texture        = gfx_texture_load(render_backend, cmd);
+  GfxCore*       gfxcore = gfx_get_core(gfx);
+  const Texture* texture = gfx_texture_load(gfxcore, cmd);
   if (texture) {
     *(Asset*)mempool_alloc(&cache->assets) = (Asset){
         .type    = TextureAsset,
diff --git a/gfx/src/asset/model.c b/gfx/src/asset/model.c
index 2053dc4..75f922f 100644
--- a/gfx/src/asset/model.c
+++ b/gfx/src/asset/model.c
@@ -82,8 +82,8 @@
 #include "asset/model.h"
 
 #include "asset/texture.h"
+#include "gfx/core.h"
 #include "gfx/gfx.h"
-#include "gfx/render_backend.h"
 #include "gfx/scene/animation.h"
 #include "gfx/scene/camera.h"
 #include "gfx/scene/material.h"
@@ -208,7 +208,7 @@ static size_t make_defines(
 
 /// Compile a shader permutation.
 static ShaderProgram* make_shader_permutation(
-    RenderBackend* render_backend, MeshPermutation perm) {
+    GfxCore* gfxcore, MeshPermutation perm) {
   LOGD(
       "Compiling Cook-Torrance shader permutation: texcoords: %d, normals: "
       "%d, tangents: %d, joints: %d, weights: %d, albedo map: %d, "
@@ -221,8 +221,7 @@ static ShaderProgram* make_shader_permutation(
 
   ShaderCompilerDefine defines[GFX_MAX_SHADER_COMPILER_DEFINES];
   const size_t         num_defines = make_defines(perm, defines);
-  return gfx_make_cook_torrance_shader_perm(
-      render_backend, defines, num_defines);
+  return gfx_make_cook_torrance_shader_perm(gfxcore, defines, num_defines);
 }
 
 /// Map a texture type to the name of the shader uniform used to access the
@@ -570,20 +569,20 @@ static size_t get_total_primitives(const cgltf_data* data) {
 /// TODO: There is no need to load the inverse bind matrices buffer into the
 /// GPU. Might need to lazily load buffers.
 static bool load_buffers(
-    const cgltf_data* data, RenderBackend* render_backend, Buffer** buffers) {
+    const cgltf_data* data, GfxCore* gfxcore, Buffer** buffers) {
   assert(data);
-  assert(render_backend);
+  assert(gfxcore);
   assert(buffers);
 
   for (cgltf_size i = 0; i < data->buffers_count; ++i) {
     const cgltf_buffer* buffer = &data->buffers[i];
     assert(buffer->data);
     buffers[i] = gfx_make_buffer(
-        render_backend, &(BufferDesc){
-                            .usage      = BufferStatic,
-                            .type       = BufferUntyped,
-                            .data.data  = buffer->data,
-                            .data.count = buffer->size});
+        gfxcore, &(BufferDesc){
+                     .usage      = BufferStatic,
+                     .type       = BufferUntyped,
+                     .data.data  = buffer->data,
+                     .data.count = buffer->size});
     if (!buffers[i]) {
       return false;
     }
@@ -595,21 +594,21 @@ static bool load_buffers(
 /// Load tangent buffers.
 static bool load_tangent_buffers(
     const cgltfTangentBuffer* cgltf_tangent_buffers,
-    cgltf_size num_tangent_buffers, RenderBackend* render_backend,
+    cgltf_size num_tangent_buffers, GfxCore* gfxcore,
     Buffer** tangent_buffers) {
   assert(cgltf_tangent_buffers);
-  assert(render_backend);
+  assert(gfxcore);
   assert(tangent_buffers);
 
   for (cgltf_size i = 0; i < num_tangent_buffers; ++i) {
     const cgltfTangentBuffer* buffer = &cgltf_tangent_buffers[i];
     assert(buffer->data);
     tangent_buffers[i] = gfx_make_buffer(
-        render_backend, &(BufferDesc){
-                            .usage      = BufferStatic,
-                            .type       = BufferUntyped,
-                            .data.data  = buffer->data,
-                            .data.count = buffer->size_bytes});
+        gfxcore, &(BufferDesc){
+                     .usage      = BufferStatic,
+                     .type       = BufferUntyped,
+                     .data.data  = buffer->data,
+                     .data.count = buffer->size_bytes});
     if (!tangent_buffers[i]) {
       return false;
     }
@@ -631,10 +630,10 @@ static bool load_tangent_buffers(
 /// Return an array of LoadTextureCmds such that the index of each cmd matches
 /// the index of each glTF texture in the scene.
 static void load_textures_lazy(
-    const cgltf_data* data, RenderBackend* render_backend,
-    const char* directory, LoadTextureCmd* load_texture_cmds) {
+    const cgltf_data* data, GfxCore* gfxcore, const char* directory,
+    LoadTextureCmd* load_texture_cmds) {
   assert(data);
-  assert(render_backend);
+  assert(gfxcore);
   assert(load_texture_cmds);
 
   for (cgltf_size i = 0; i < data->textures_count; ++i) {
@@ -908,7 +907,7 @@ aabb3 compute_aabb(const cgltf_accessor* accessor) {
 
 /// Load all meshes from the glTF scene.
 static bool load_meshes(
-    const cgltf_data* data, RenderBackend* render_backend, Buffer** buffers,
+    const cgltf_data* data, GfxCore* gfxcore, Buffer** buffers,
     Buffer** tangent_buffers, const cgltfTangentBuffer* cgltf_tangent_buffers,
     cgltf_size num_tangent_buffers, Material** materials,
     ShaderProgram* const shader, size_t primitive_count, Geometry** geometries,
@@ -924,7 +923,7 @@ static bool load_meshes(
   // Accessor + buffer view    BufferView
   // Buffer                    Buffer
   assert(data);
-  assert(render_backend);
+  assert(gfxcore);
   assert(buffers);
   assert(materials);
   assert(geometries);
@@ -1175,7 +1174,7 @@ static bool load_meshes(
       }
       assert(material);
 
-      geometries[next_mesh] = gfx_make_geometry(render_backend, &geometry_desc);
+      geometries[next_mesh] = gfx_make_geometry(gfxcore, &geometry_desc);
       if (!geometries[next_mesh]) {
         return false;
       }
@@ -1190,7 +1189,7 @@ static bool load_meshes(
       // values. Also, changing uniforms is much faster than swapping shaders,
       // so shader caching is the most important thing here.
       ShaderProgram* mesh_shader =
-          shader ? shader : make_shader_permutation(render_backend, perm);
+          shader ? shader : make_shader_permutation(gfxcore, perm);
       assert(mesh_shader);
 
       meshes[next_mesh] = gfx_make_mesh(&(MeshDesc){
@@ -1277,7 +1276,7 @@ static void compute_joint_bounding_boxes(
               for (int i = 0; i < 4; ++i) {
                 const size_t joint_index = j[i];
                 assert((size_t)joint_index < num_joints);
-                
+
                 joint_descs[joint_index].box =
                     aabb3_add(joint_descs[joint_index].box, p);
               }
@@ -1699,8 +1698,8 @@ static Model* load_scene(
 
   bool success = false;
 
-  RenderBackend* render_backend  = gfx_get_render_backend(gfx);
-  const size_t   primitive_count = get_total_primitives(data);
+  GfxCore*     gfxcore         = gfx_get_core(gfx);
+  const size_t primitive_count = get_total_primitives(data);
 
   const mstring directory = mstring_dirname(*filepath);
   LOGD("Filepath: %s", mstring_cstr(filepath));
@@ -1745,18 +1744,18 @@ static Model* load_scene(
 
   if ((num_tangent_buffers > 0) &&
       !load_tangent_buffers(
-          cgltf_tangent_buffers, num_tangent_buffers, render_backend,
+          cgltf_tangent_buffers, num_tangent_buffers, gfxcore,
           tangent_buffers)) {
     goto cleanup;
   }
 
-  if (!load_buffers(data, render_backend, buffers)) {
+  if (!load_buffers(data, gfxcore, buffers)) {
     goto cleanup;
   }
 
   if (data->textures_count > 0) {
     load_textures_lazy(
-        data, render_backend, mstring_cstr(&directory), load_texture_cmds);
+        data, gfxcore, mstring_cstr(&directory), load_texture_cmds);
   }
 
   if (!load_materials(data, gfx, load_texture_cmds, textures, materials)) {
@@ -1764,7 +1763,7 @@ static Model* load_scene(
   }
 
   if (!load_meshes(
-          data, render_backend, buffers, tangent_buffers, cgltf_tangent_buffers,
+          data, gfxcore, buffers, tangent_buffers, cgltf_tangent_buffers,
           num_tangent_buffers, materials, shader, primitive_count, geometries,
           meshes, scene_objects)) {
     goto cleanup;
@@ -1823,7 +1822,7 @@ cleanup:
     if (!success) {
       for (cgltf_size i = 0; i < num_tangent_buffers; ++i) {
         if (tangent_buffers[i]) {
-          gfx_destroy_buffer(render_backend, &tangent_buffers[i]);
+          gfx_destroy_buffer(gfxcore, &tangent_buffers[i]);
         }
       }
     }
@@ -1833,7 +1832,7 @@ cleanup:
     if (!success) {
       for (cgltf_size i = 0; i < data->buffers_count; ++i) {
         if (buffers[i]) {
-          gfx_destroy_buffer(render_backend, &buffers[i]);
+          gfx_destroy_buffer(gfxcore, &buffers[i]);
         }
       }
     }
@@ -1859,7 +1858,7 @@ cleanup:
     if (!success) {
       for (size_t i = 0; i < primitive_count; ++i) {
         if (geometries[i]) {
-          gfx_destroy_geometry(render_backend, &geometries[i]);
+          gfx_destroy_geometry(gfxcore, &geometries[i]);
         }
       }
     }
diff --git a/gfx/src/asset/texture.c b/gfx/src/asset/texture.c
index 3a82788..c790394 100644
--- a/gfx/src/asset/texture.c
+++ b/gfx/src/asset/texture.c
@@ -1,6 +1,6 @@
 #include "texture.h"
 
-#include "gfx/render_backend.h"
+#include "gfx/core.h"
 
 #include "error.h"
 
@@ -43,9 +43,8 @@ static void flip_horizontally(
 // For this reason, we do X and Y flips when doing cubemap textures so that we
 // can sample cubemaps as if they were given in the usual OpenGL coordinate
 // system.
-Texture* gfx_texture_load(
-    RenderBackend* render_backend, const LoadTextureCmd* cmd) {
-  assert(render_backend);
+Texture* gfx_texture_load(GfxCore* gfxcore, const LoadTextureCmd* cmd) {
+  assert(gfxcore);
   assert(cmd);
   assert(cmd->origin == AssetFromFile || cmd->origin == AssetFromMemory);
   assert(cmd->type == LoadTexture || cmd->type == LoadCubemap);
@@ -168,7 +167,7 @@ Texture* gfx_texture_load(
     break;
   }
 
-  Texture* texture = gfx_make_texture(render_backend, &desc);
+  Texture* texture = gfx_make_texture(gfxcore, &desc);
   for (int i = 0; i < 6; ++i) {
     if (pixels[i]) {
       stbi_image_free(pixels[i]);
diff --git a/gfx/src/asset/texture.h b/gfx/src/asset/texture.h
index a3239fe..0d38bd9 100644
--- a/gfx/src/asset/texture.h
+++ b/gfx/src/asset/texture.h
@@ -4,4 +4,4 @@
 #include <gfx/asset.h>
 
 /// Load a texture.
-Texture* gfx_texture_load(RenderBackend*, const LoadTextureCmd*);
+Texture* gfx_texture_load(GfxCore*, const LoadTextureCmd*);
diff --git a/gfx/src/gfx.c b/gfx/src/gfx.c
index 7095ea1..8c513a1 100644
--- a/gfx/src/gfx.c
+++ b/gfx/src/gfx.c
@@ -1,7 +1,7 @@
 #include <gfx/gfx.h>
 
 #include "asset/asset_cache.h"
-#include "render/render_backend_impl.h"
+#include "render/core_impl.h"
 #include "renderer/imm_renderer_impl.h"
 #include "renderer/renderer_impl.h"
 #include "scene/scene_graph.h"
@@ -15,10 +15,10 @@
 #include <stdlib.h>
 
 typedef struct Gfx {
-  AssetCache    asset_cache;
-  RenderBackend render_backend;
-  Renderer      renderer;
-  ImmRenderer   imm_renderer;
+  AssetCache  asset_cache;
+  GfxCore     gfxcore;
+  Renderer    renderer;
+  ImmRenderer imm_renderer;
 } Gfx;
 
 Gfx* gfx_init(void) {
@@ -31,12 +31,12 @@ Gfx* gfx_init(void) {
   if (!gfx) {
     return 0;
   }
-  gfx_init_render_backend(&gfx->render_backend);
-  if (!renderer_make(&gfx->renderer, &gfx->render_backend)) {
+  gfx_init_gfxcore(&gfx->gfxcore);
+  if (!renderer_make(&gfx->renderer, &gfx->gfxcore)) {
     gfx_destroy(&gfx);
     return 0;
   }
-  if (!imm_renderer_make(&gfx->imm_renderer, &gfx->render_backend)) {
+  if (!imm_renderer_make(&gfx->imm_renderer, &gfx->gfxcore)) {
     // TODO: Add error logs to the initialization failure cases here and inside
     // the renderers.
     gfx_destroy(&gfx);
@@ -55,14 +55,14 @@ void gfx_destroy(Gfx** gfx) {
   gfx_destroy_asset_cache(&(*gfx)->asset_cache);
   renderer_destroy(&(*gfx)->renderer);
   imm_renderer_destroy(&(*gfx)->imm_renderer);
-  gfx_del_render_backend(&(*gfx)->render_backend);
+  gfx_del_gfxcore(&(*gfx)->gfxcore);
   free(*gfx);
   *gfx = 0;
 }
 
-RenderBackend* gfx_get_render_backend(Gfx* gfx) {
+GfxCore* gfx_get_core(Gfx* gfx) {
   assert(gfx);
-  return &gfx->render_backend;
+  return &gfx->gfxcore;
 }
 
 Renderer* gfx_get_renderer(Gfx* gfx) {
diff --git a/gfx/src/render/buffer.c b/gfx/src/render/buffer.c
index 28877bb..3b7e4bc 100644
--- a/gfx/src/render/buffer.c
+++ b/gfx/src/render/buffer.c
@@ -1,6 +1,6 @@
 #include "buffer.h"
 
-#include <gfx/render_backend.h>
+#include <gfx/core.h>
 #include <gfx_assert.h>
 
 #include <math/vec2.h>
@@ -57,7 +57,7 @@ bool gfx_init_buffer(Buffer* buffer, const BufferDesc* desc) {
   glBufferData(GL_ARRAY_BUFFER, buffer->size_bytes, desc->data.data, usage);
   glBindBuffer(GL_ARRAY_BUFFER, 0);
   ASSERT_GL;
-  
+
   return true;
 }
 
diff --git a/gfx/src/render/buffer.h b/gfx/src/render/buffer.h
index 3adfd8e..b9080f0 100644
--- a/gfx/src/render/buffer.h
+++ b/gfx/src/render/buffer.h
@@ -1,6 +1,6 @@
 #pragma once
 
-#include <gfx/render_backend.h>
+#include <gfx/core.h>
 
 #include "gl_util.h"
 
diff --git a/gfx/src/render/core.c b/gfx/src/render/core.c
new file mode 100644
index 0000000..7a6d9cc
--- /dev/null
+++ b/gfx/src/render/core.c
@@ -0,0 +1,414 @@
+#include "core_impl.h"
+
+#include "gl_util.h"
+
+// #include <log/log.h>
+
+#include <assert.h>
+
+void gfx_init_gfxcore(GfxCore* gfxcore) {
+  assert(gfxcore);
+
+  mempool_make(&gfxcore->buffers);
+  mempool_make(&gfxcore->framebuffers);
+  mempool_make(&gfxcore->geometries);
+  mempool_make(&gfxcore->renderbuffers);
+  mempool_make(&gfxcore->shaders);
+  mempool_make(&gfxcore->shader_programs);
+  mempool_make(&gfxcore->textures);
+
+  mempool_make(&gfxcore->shader_cache);
+  mempool_make(&gfxcore->program_cache);
+
+  glEnable(GL_CULL_FACE);
+  glFrontFace(GL_CCW);
+  glCullFace(GL_BACK);
+
+  glEnable(GL_DEPTH_TEST);
+
+  // Filter cubemaps across their faces to avoid seams.
+  // https://www.khronos.org/opengl/wiki/Cubemap_Texture#Seamless_cubemap
+  glEnable(GL_TEXTURE_CUBE_MAP_SEAMLESS);
+}
+
+// Conveniently destroy any objects that have not been destroyed by the
+// application.
+void gfx_del_gfxcore(GfxCore* gfxcore) {
+  assert(gfxcore);
+
+  mempool_foreach(&gfxcore->buffers, buffer, { gfx_del_buffer(buffer); });
+
+  mempool_foreach(&gfxcore->framebuffers, framebuffer, {
+    gfx_del_framebuffer(framebuffer);
+  });
+
+  mempool_foreach(
+      &gfxcore->geometries, geometry, { gfx_del_geometry(geometry); });
+
+  mempool_foreach(&gfxcore->renderbuffers, renderbuffer, {
+    gfx_del_renderbuffer(renderbuffer);
+  });
+
+  mempool_foreach(
+      &gfxcore->shader_programs, prog, { gfx_del_shader_program(prog); });
+
+  mempool_foreach(&gfxcore->shaders, shader, { gfx_del_shader(shader); });
+
+  mempool_foreach(&gfxcore->textures, texture, { gfx_del_texture(texture); });
+}
+
+// -----------------------------------------------------------------------------
+// Render commands.
+// -----------------------------------------------------------------------------
+
+void gfx_start_frame(GfxCore* gfxcore) {
+  assert(gfxcore);
+
+  glViewport(0, 0, gfxcore->viewport.width, gfxcore->viewport.height);
+  glClearColor(0, 0, 0, 0);
+  glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
+
+  ASSERT_GL;
+}
+
+void gfx_end_frame(GfxCore* gfxcore) {
+  assert(gfxcore);
+  ASSERT_GL;
+}
+
+void gfx_set_viewport(GfxCore* gfxcore, int width, int height) {
+  assert(gfxcore);
+  gfxcore->viewport.width  = width;
+  gfxcore->viewport.height = height;
+}
+
+void gfx_get_viewport(GfxCore* gfxcore, int* width, int* height) {
+  assert(gfxcore);
+  assert(width);
+  assert(height);
+  *width  = gfxcore->viewport.width;
+  *height = gfxcore->viewport.height;
+}
+
+void gfx_set_blending(GfxCore* gfxcore, bool enable) {
+  assert(gfxcore);
+  if (enable) {
+    glEnable(GL_BLEND);
+    glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
+  } else {
+    glDisable(GL_BLEND);
+  }
+}
+
+void gfx_set_depth_mask(GfxCore* gfxcore, bool enable) {
+  assert(gfxcore);
+  glDepthMask(enable ? GL_TRUE : GL_FALSE);
+}
+
+void gfx_set_culling(GfxCore* gfxcore, bool enable) {
+  assert(gfxcore);
+  if (enable) {
+    glEnable(GL_CULL_FACE);
+  } else {
+    glDisable(GL_CULL_FACE);
+  }
+}
+
+void gfx_set_polygon_offset(GfxCore* gfxcore, float scale, float bias) {
+  assert(gfxcore);
+  if ((scale != 0.0f) || (bias != 0.0f)) {
+    glEnable(GL_POLYGON_OFFSET_FILL);
+  } else {
+    glDisable(GL_POLYGON_OFFSET_FILL);
+  }
+  glPolygonOffset(scale, bias);
+}
+
+void gfx_reset_polygon_offset(GfxCore* gfxcore) {
+  assert(gfxcore);
+  glPolygonOffset(0, 0);
+  glDisable(GL_POLYGON_OFFSET_FILL);
+}
+
+// -----------------------------------------------------------------------------
+// Buffers.
+// -----------------------------------------------------------------------------
+
+Buffer* gfx_make_buffer(GfxCore* gfxcore, const BufferDesc* desc) {
+  assert(gfxcore);
+  assert(desc);
+
+  Buffer* buffer = mempool_alloc(&gfxcore->buffers);
+  if (!gfx_init_buffer(buffer, desc)) {
+    mempool_free(&gfxcore->buffers, &buffer);
+    return 0;
+  }
+  return buffer;
+}
+
+void gfx_destroy_buffer(GfxCore* gfxcore, Buffer** buffer) {
+  assert(gfxcore);
+  assert(buffer);
+  if (*buffer) {
+    gfx_del_buffer(*buffer);
+    mempool_free(&gfxcore->buffers, buffer);
+  }
+}
+
+// -----------------------------------------------------------------------------
+// Geometry.
+// -----------------------------------------------------------------------------
+
+Geometry* gfx_make_geometry(GfxCore* gfxcore, const GeometryDesc* desc) {
+  assert(gfxcore);
+  assert(desc);
+
+  Geometry* geometry = mempool_alloc(&gfxcore->geometries);
+  if (!gfx_init_geometry(geometry, gfxcore, desc)) {
+    mempool_free(&gfxcore->geometries, &geometry);
+    return 0;
+  }
+  return geometry;
+}
+
+void gfx_destroy_geometry(GfxCore* gfxcore, Geometry** geometry) {
+  assert(gfxcore);
+  assert(geometry);
+
+  if (*geometry) {
+    gfx_del_geometry(*geometry);
+    mempool_free(&gfxcore->geometries, geometry);
+  }
+}
+
+// -----------------------------------------------------------------------------
+// Textures.
+// -----------------------------------------------------------------------------
+
+Texture* gfx_make_texture(GfxCore* gfxcore, const TextureDesc* desc) {
+  assert(gfxcore);
+  assert(desc);
+
+  Texture* texture = mempool_alloc(&gfxcore->textures);
+  if (!gfx_init_texture(texture, desc)) {
+    mempool_free(&gfxcore->textures, &texture);
+    return 0;
+  }
+  return texture;
+}
+
+void gfx_destroy_texture(GfxCore* gfxcore, Texture** texture) {
+  assert(gfxcore);
+  assert(texture);
+  assert(*texture);
+
+  if (*texture) {
+    gfx_del_texture(*texture);
+    mempool_free(&gfxcore->textures, texture);
+  }
+}
+
+// -----------------------------------------------------------------------------
+// Renderbuffers.
+// -----------------------------------------------------------------------------
+
+RenderBuffer* gfx_make_renderbuffer(
+    GfxCore* gfxcore, const RenderBufferDesc* desc) {
+  assert(gfxcore);
+  assert(desc);
+
+  RenderBuffer* renderbuffer = mempool_alloc(&gfxcore->renderbuffers);
+  if (!gfx_init_renderbuffer(renderbuffer, desc)) {
+    mempool_free(&gfxcore->renderbuffers, &renderbuffer);
+  }
+  return renderbuffer;
+}
+
+void gfx_destroy_renderbuffer(GfxCore* gfxcore, RenderBuffer** renderbuffer) {
+  assert(gfxcore);
+  assert(renderbuffer);
+  assert(*renderbuffer);
+
+  if (*renderbuffer) {
+    gfx_del_renderbuffer(*renderbuffer);
+    mempool_free(&gfxcore->renderbuffers, renderbuffer);
+  }
+}
+
+// -----------------------------------------------------------------------------
+// Framebuffers.
+// -----------------------------------------------------------------------------
+
+FrameBuffer* gfx_make_framebuffer(
+    GfxCore* gfxcore, const FrameBufferDesc* desc) {
+  assert(gfxcore);
+  assert(desc);
+
+  FrameBuffer* framebuffer = mempool_alloc(&gfxcore->framebuffers);
+  if (!gfx_init_framebuffer(framebuffer, desc)) {
+    mempool_free(&gfxcore->framebuffers, &framebuffer);
+    return 0;
+  }
+  return framebuffer;
+}
+
+void gfx_destroy_framebuffer(GfxCore* gfxcore, FrameBuffer** framebuffer) {
+  assert(gfxcore);
+  assert(framebuffer);
+  assert(*framebuffer);
+
+  if (*framebuffer) {
+    gfx_del_framebuffer(*framebuffer);
+    mempool_free(&gfxcore->framebuffers, framebuffer);
+  }
+}
+
+// -----------------------------------------------------------------------------
+// Shaders.
+// -----------------------------------------------------------------------------
+
+static uint64_t hash_shader_desc(const ShaderDesc* desc) {
+  assert(desc);
+  // Note that defines may affect shader permutations, so we need to hash those
+  // as well.
+  uint64_t hash = 0;
+  for (size_t i = 0; i < desc->num_defines; ++i) {
+    const ShaderCompilerDefine* define = &desc->defines[i];
+    hash = (((hash << 13) + sstring_hash(define->name)) << 7) +
+           sstring_hash(define->value);
+  }
+  return (hash << 17) + cstring_hash(desc->code);
+}
+
+static uint64_t hash_program_desc(const ShaderProgramDesc* desc) {
+  assert(desc);
+  return ((uint64_t)desc->vertex_shader->id << 32) |
+         (uint64_t)desc->fragment_shader->id;
+}
+
+static Shader* find_cached_shader(ShaderCache* cache, uint64_t hash) {
+  assert(cache);
+  mempool_foreach(cache, entry, {
+    if (entry->hash == hash) {
+      return entry->shader;
+    }
+  });
+  return 0;
+}
+
+static ShaderProgram* find_cached_program(ProgramCache* cache, uint64_t hash) {
+  assert(cache);
+  mempool_foreach(cache, entry, {
+    if (entry->hash == hash) {
+      return entry->program;
+    }
+  });
+  return 0;
+}
+
+static ShaderCacheEntry* find_shader_cache_entry(
+    ShaderCache* cache, const Shader* shader) {
+  assert(cache);
+  assert(shader);
+  mempool_foreach(cache, entry, {
+    if (entry->shader == shader) {
+      return entry;
+    }
+  });
+  return 0;
+}
+
+static ShaderProgramCacheEntry* find_program_cache_entry(
+    ProgramCache* cache, const ShaderProgram* prog) {
+  assert(cache);
+  assert(prog);
+  mempool_foreach(cache, entry, {
+    if (entry->program == prog) {
+      return entry;
+    }
+  });
+  return 0;
+}
+
+Shader* gfx_make_shader(GfxCore* gfxcore, const ShaderDesc* desc) {
+  assert(gfxcore);
+  assert(desc);
+
+  // Check the shader cache first.
+  ShaderCache*   cache  = &gfxcore->shader_cache;
+  const uint64_t hash   = hash_shader_desc(desc);
+  Shader*        shader = find_cached_shader(cache, hash);
+  if (shader) {
+    // LOGD("Found cached shader with hash [%lx]", hash);
+    return shader;
+  }
+
+  shader = mempool_alloc(&gfxcore->shaders);
+  if (!shader) {
+    return 0;
+  }
+  if (!gfx_compile_shader(shader, desc)) {
+    mempool_free(&gfxcore->shaders, &shader);
+    return 0;
+  }
+  ShaderCacheEntry* entry = mempool_alloc(cache);
+  *entry                  = (ShaderCacheEntry){.hash = hash, .shader = shader};
+  // LOGD("Added shader with hash [%lx] to cache", hash);
+  return shader;
+}
+
+void gfx_destroy_shader(GfxCore* gfxcore, Shader** shader) {
+  assert(gfxcore);
+  assert(shader);
+
+  if (*shader) {
+    // Remove the shader from the cache.
+    ShaderCache*      cache = &gfxcore->shader_cache;
+    ShaderCacheEntry* entry = find_shader_cache_entry(cache, *shader);
+    assert(entry); // Must be there, shaders can't go untracked.
+    mempool_free(cache, &entry);
+
+    gfx_del_shader(*shader);
+    mempool_free(&gfxcore->shaders, shader);
+  }
+}
+
+ShaderProgram* gfx_make_shader_program(
+    GfxCore* gfxcore, const ShaderProgramDesc* desc) {
+  assert(gfxcore);
+  assert(desc);
+
+  // Check the shader program cache first.
+  ProgramCache*  cache = &gfxcore->program_cache;
+  const uint64_t hash  = hash_program_desc(desc);
+  ShaderProgram* prog  = find_cached_program(cache, hash);
+  if (prog) {
+    // LOGD("Found cached shader program with hash [%lx]", hash);
+    return prog;
+  }
+
+  prog = mempool_alloc(&gfxcore->shader_programs);
+  if (!gfx_build_shader_program(prog, desc)) {
+    mempool_free(&gfxcore->shader_programs, &prog);
+    return 0;
+  }
+  ShaderProgramCacheEntry* entry = mempool_alloc(cache);
+  *entry = (ShaderProgramCacheEntry){.hash = hash, .program = prog};
+  // LOGD("Added shader program with hash [%lx] to cache", hash);
+  return prog;
+}
+
+void gfx_destroy_shader_program(GfxCore* gfxcore, ShaderProgram** prog) {
+  assert(gfxcore);
+  assert(prog);
+  if (*prog) {
+    // Remove the shader program from the cache.
+    ProgramCache*            cache = &gfxcore->program_cache;
+    ShaderProgramCacheEntry* entry = find_program_cache_entry(cache, *prog);
+    assert(entry); // Must be there, shaders can't go untracked.
+    mempool_free(cache, &entry);
+
+    gfx_del_shader_program(*prog);
+    mempool_free(&gfxcore->shader_programs, prog);
+  }
+}
diff --git a/gfx/src/render/core_impl.h b/gfx/src/render/core_impl.h
new file mode 100644
index 0000000..e27c0f2
--- /dev/null
+++ b/gfx/src/render/core_impl.h
@@ -0,0 +1,66 @@
+#pragma once
+
+#include <gfx/core.h>
+#include <gfx/sizes.h>
+
+#include "buffer.h"
+#include "framebuffer.h"
+#include "geometry.h"
+#include "renderbuffer.h"
+#include "shader.h"
+#include "shader_program.h"
+#include "texture.h"
+
+#include <mempool.h>
+
+#include <stdint.h>
+
+// TODO: Make a generic (hash, void*) structure and define functions over it.
+// Then define a macro that defines type-safe macros given the type of the
+// entry.
+typedef struct ShaderCacheEntry {
+  uint64_t hash;
+  Shader*  shader;
+} ShaderCacheEntry;
+
+typedef struct ShaderProgramCacheEntry {
+  uint64_t       hash;
+  ShaderProgram* program;
+} ShaderProgramCacheEntry;
+
+DEF_MEMPOOL(buffer_pool, Buffer, GFX_MAX_NUM_BUFFERS)
+DEF_MEMPOOL(framebuffer_pool, FrameBuffer, GFX_MAX_NUM_FRAMEBUFFERS)
+DEF_MEMPOOL(geometry_pool, Geometry, GFX_MAX_NUM_GEOMETRIES)
+DEF_MEMPOOL(renderbuffer_pool, RenderBuffer, GFX_MAX_NUM_RENDERBUFFERS)
+DEF_MEMPOOL(shader_pool, Shader, GFX_MAX_NUM_SHADERS)
+DEF_MEMPOOL(shader_program_pool, ShaderProgram, GFX_MAX_NUM_SHADER_PROGRAMS)
+DEF_MEMPOOL(texture_pool, Texture, GFX_MAX_NUM_TEXTURES)
+
+DEF_MEMPOOL(ShaderCache, ShaderCacheEntry, GFX_MAX_NUM_SHADERS)
+DEF_MEMPOOL(ProgramCache, ShaderProgramCacheEntry, GFX_MAX_NUM_SHADER_PROGRAMS)
+
+typedef struct {
+  int width;
+  int height;
+} Viewport;
+
+typedef struct GfxCore {
+  Viewport viewport;
+  // mempools for render-specific objects: textures, geometry, etc.
+  buffer_pool         buffers;
+  framebuffer_pool    framebuffers;
+  geometry_pool       geometries;
+  renderbuffer_pool   renderbuffers;
+  shader_pool         shaders;
+  shader_program_pool shader_programs;
+  texture_pool        textures;
+  // Caches.
+  ShaderCache  shader_cache;
+  ProgramCache program_cache;
+} GfxCore;
+
+/// Create a new render backend.
+void gfx_init_gfxcore(GfxCore*);
+
+/// Destroy the render backend.
+void gfx_del_gfxcore(GfxCore*);
diff --git a/gfx/src/render/framebuffer.h b/gfx/src/render/framebuffer.h
index 7153b30..1a3439c 100644
--- a/gfx/src/render/framebuffer.h
+++ b/gfx/src/render/framebuffer.h
@@ -1,6 +1,6 @@
 #pragma once
 
-#include <gfx/render_backend.h>
+#include <gfx/core.h>
 
 #include "gl_util.h"
 
diff --git a/gfx/src/render/geometry.c b/gfx/src/render/geometry.c
index 8974387..cfc749f 100644
--- a/gfx/src/render/geometry.c
+++ b/gfx/src/render/geometry.c
@@ -30,11 +30,11 @@ static GLenum primitive_type_to_gl(PrimitiveType type) {
 /// Create a typed buffer for the buffer view if the view does not already point
 /// to a buffer.
 void init_view_buffer(
-    RenderBackend* render_backend, BufferView* view, BufferType buffer_type,
+    GfxCore* gfxcore, BufferView* view, BufferType buffer_type,
     BufferUsage buffer_usage) {
   if (!view->buffer) {
     view->buffer = gfx_make_buffer(
-        render_backend,
+        gfxcore,
         &(BufferDesc){
             .usage      = buffer_usage,
             .type       = buffer_type,
@@ -47,10 +47,10 @@ void init_view_buffer(
 
 /// Configure the buffer in teh VAO.
 static void configure_buffer(
-    RenderBackend* render_backend, const GeometryDesc* desc, BufferView* view,
+    GfxCore* gfxcore, const GeometryDesc* desc, BufferView* view,
     size_t num_components, size_t component_size_bytes, GLenum component_type,
     GLboolean normalized, GLuint channel) {
-  assert(render_backend);
+  assert(gfxcore);
   assert(desc);
   assert(view);
   assert(view->buffer);
@@ -78,127 +78,117 @@ static void configure_buffer(
   glBindBuffer(GL_ARRAY_BUFFER, 0);
 }
 
-static bool configure_vertex_attributes(
-    RenderBackend* render_backend, GeometryDesc* desc) {
-  assert(render_backend);
+static bool configure_vertex_attributes(GfxCore* gfxcore, GeometryDesc* desc) {
+  assert(gfxcore);
   assert(desc);
 
   if (view_is_populated(desc->positions3d)) {
     init_view_buffer(
-        render_backend, (BufferView*)&desc->positions3d, Buffer3d,
-        desc->buffer_usage);
+        gfxcore, (BufferView*)&desc->positions3d, Buffer3d, desc->buffer_usage);
     if (!desc->positions3d.buffer) {
       return false;
     }
     configure_buffer(
-        render_backend, desc, (BufferView*)&desc->positions3d, 3, sizeof(float),
+        gfxcore, desc, (BufferView*)&desc->positions3d, 3, sizeof(float),
         GL_FLOAT, GL_FALSE, GFX_POSITION_CHANNEL);
   } else if (view_is_populated(desc->positions2d)) {
     init_view_buffer(
-        render_backend, (BufferView*)&desc->positions2d, Buffer2d,
-        desc->buffer_usage);
+        gfxcore, (BufferView*)&desc->positions2d, Buffer2d, desc->buffer_usage);
     if (!desc->positions2d.buffer) {
       return false;
     }
     configure_buffer(
-        render_backend, desc, (BufferView*)&desc->positions2d, 2, sizeof(float),
+        gfxcore, desc, (BufferView*)&desc->positions2d, 2, sizeof(float),
         GL_FLOAT, GL_FALSE, GFX_POSITION_CHANNEL);
   }
   if (view_is_populated(desc->normals)) {
     init_view_buffer(
-        render_backend, (BufferView*)&desc->normals, Buffer3d,
-        desc->buffer_usage);
+        gfxcore, (BufferView*)&desc->normals, Buffer3d, desc->buffer_usage);
     if (!desc->normals.buffer) {
       return false;
     }
     configure_buffer(
-        render_backend, desc, (BufferView*)&desc->normals, 3, sizeof(float),
-        GL_FLOAT, GL_FALSE, GFX_NORMAL_CHANNEL);
+        gfxcore, desc, (BufferView*)&desc->normals, 3, sizeof(float), GL_FLOAT,
+        GL_FALSE, GFX_NORMAL_CHANNEL);
   }
   if (view_is_populated(desc->tangents)) {
     init_view_buffer(
-        render_backend, (BufferView*)&desc->tangents, Buffer4d,
-        desc->buffer_usage);
+        gfxcore, (BufferView*)&desc->tangents, Buffer4d, desc->buffer_usage);
     if (!desc->tangents.buffer) {
       return false;
     }
     configure_buffer(
-        render_backend, desc, (BufferView*)&desc->tangents, 4, sizeof(float),
-        GL_FLOAT, GL_FALSE, GFX_TANGENT_CHANNEL);
+        gfxcore, desc, (BufferView*)&desc->tangents, 4, sizeof(float), GL_FLOAT,
+        GL_FALSE, GFX_TANGENT_CHANNEL);
   }
   if (view_is_populated(desc->texcoords)) {
     init_view_buffer(
-        render_backend, (BufferView*)&desc->texcoords, Buffer2d,
-        desc->buffer_usage);
+        gfxcore, (BufferView*)&desc->texcoords, Buffer2d, desc->buffer_usage);
     if (!desc->texcoords.buffer) {
       return false;
     }
     configure_buffer(
-        render_backend, desc, (BufferView*)&desc->texcoords, 2, sizeof(float),
+        gfxcore, desc, (BufferView*)&desc->texcoords, 2, sizeof(float),
         GL_FLOAT, GL_FALSE, GFX_TEXCOORDS_CHANNEL);
   }
   if (view_is_populated(desc->joints.u8)) {
     init_view_buffer(
-        render_backend, (BufferView*)&desc->joints.u8, BufferU8,
-        desc->buffer_usage);
+        gfxcore, (BufferView*)&desc->joints.u8, BufferU8, desc->buffer_usage);
     if (!desc->joints.u8.buffer) {
       return false;
     }
     configure_buffer(
-        render_backend, desc, (BufferView*)&desc->joints.u8, 4, sizeof(uint8_t),
+        gfxcore, desc, (BufferView*)&desc->joints.u8, 4, sizeof(uint8_t),
         GL_UNSIGNED_BYTE, GL_FALSE, GFX_JOINTS_CHANNEL);
   } else if (view_is_populated(desc->joints.u16)) {
     init_view_buffer(
-        render_backend, (BufferView*)&desc->joints.u16, BufferU16,
-        desc->buffer_usage);
+        gfxcore, (BufferView*)&desc->joints.u16, BufferU16, desc->buffer_usage);
     if (!desc->joints.u16.buffer) {
       return false;
     }
     configure_buffer(
-        render_backend, desc, (BufferView*)&desc->joints.u16, 4,
-        sizeof(uint16_t), GL_UNSIGNED_SHORT, GL_FALSE, GFX_JOINTS_CHANNEL);
+        gfxcore, desc, (BufferView*)&desc->joints.u16, 4, sizeof(uint16_t),
+        GL_UNSIGNED_SHORT, GL_FALSE, GFX_JOINTS_CHANNEL);
   }
 
   // If weights are given as unsigned integers, then they are normalized
   // when read by the shader.
   if (view_is_populated(desc->weights.u8)) {
     init_view_buffer(
-        render_backend, (BufferView*)&desc->weights.u8, BufferU8,
-        desc->buffer_usage);
+        gfxcore, (BufferView*)&desc->weights.u8, BufferU8, desc->buffer_usage);
     if (!desc->weights.u8.buffer) {
       return false;
     }
     configure_buffer(
-        render_backend, desc, (BufferView*)&desc->weights.u8, 4,
-        sizeof(uint8_t), GL_UNSIGNED_BYTE, GL_TRUE, GFX_WEIGHTS_CHANNEL);
+        gfxcore, desc, (BufferView*)&desc->weights.u8, 4, sizeof(uint8_t),
+        GL_UNSIGNED_BYTE, GL_TRUE, GFX_WEIGHTS_CHANNEL);
   } else if (view_is_populated(desc->weights.u16)) {
     init_view_buffer(
-        render_backend, (BufferView*)&desc->weights.u16, BufferU16,
+        gfxcore, (BufferView*)&desc->weights.u16, BufferU16,
         desc->buffer_usage);
     if (!desc->weights.u16.buffer) {
       return false;
     }
     configure_buffer(
-        render_backend, desc, (BufferView*)&desc->weights.u16, 4,
-        sizeof(uint16_t), GL_UNSIGNED_SHORT, GL_TRUE, GFX_WEIGHTS_CHANNEL);
+        gfxcore, desc, (BufferView*)&desc->weights.u16, 4, sizeof(uint16_t),
+        GL_UNSIGNED_SHORT, GL_TRUE, GFX_WEIGHTS_CHANNEL);
   } else if (view_is_populated(desc->weights.floats)) {
     init_view_buffer(
-        render_backend, (BufferView*)&desc->weights.floats, BufferFloat,
+        gfxcore, (BufferView*)&desc->weights.floats, BufferFloat,
         desc->buffer_usage);
     if (!desc->weights.floats.buffer) {
       return false;
     }
     configure_buffer(
-        render_backend, desc, (BufferView*)&desc->weights.floats, 4,
-        sizeof(float), GL_FLOAT, GL_FALSE, GFX_WEIGHTS_CHANNEL);
+        gfxcore, desc, (BufferView*)&desc->weights.floats, 4, sizeof(float),
+        GL_FLOAT, GL_FALSE, GFX_WEIGHTS_CHANNEL);
   }
 
   return true;
 }
 
-static bool configure_indices(
-    RenderBackend* render_backend, GeometryDesc* desc) {
-  assert(render_backend);
+static bool configure_indices(GfxCore* gfxcore, GeometryDesc* desc) {
+  assert(gfxcore);
   assert(desc);
 
   if (view_is_populated(desc->indices8)) {
@@ -206,8 +196,7 @@ static bool configure_indices(
     assert(
         desc->num_indices <= desc->indices8.size_bytes / sizeof(VertexIndex8));
     init_view_buffer(
-        render_backend, (BufferView*)&desc->indices8, BufferU8,
-        desc->buffer_usage);
+        gfxcore, (BufferView*)&desc->indices8, BufferU8, desc->buffer_usage);
     if (!desc->indices8.buffer) {
       return false;
     }
@@ -217,8 +206,7 @@ static bool configure_indices(
         desc->num_indices <=
         desc->indices16.size_bytes / sizeof(VertexIndex16));
     init_view_buffer(
-        render_backend, (BufferView*)&desc->indices16, BufferU16,
-        desc->buffer_usage);
+        gfxcore, (BufferView*)&desc->indices16, BufferU16, desc->buffer_usage);
     if (!desc->indices16.buffer) {
       return false;
     }
@@ -228,20 +216,19 @@ static bool configure_indices(
 }
 
 bool gfx_init_geometry(
-    Geometry* geometry, RenderBackend* render_backend,
-    const GeometryDesc* input_desc) {
+    Geometry* geometry, GfxCore* gfxcore, const GeometryDesc* input_desc) {
   assert(geometry);
-  assert(render_backend);
+  assert(gfxcore);
   assert(input_desc);
   assert(
       view_is_populated(input_desc->positions3d) ||
       view_is_populated(input_desc->positions2d));
   assert(input_desc->num_verts > 0);
 
-  geometry->mode           = primitive_type_to_gl(input_desc->type);
-  geometry->desc           = *input_desc;
-  geometry->num_verts      = input_desc->num_verts;
-  geometry->render_backend = render_backend;
+  geometry->mode      = primitive_type_to_gl(input_desc->type);
+  geometry->desc      = *input_desc;
+  geometry->num_verts = input_desc->num_verts;
+  geometry->gfxcore   = gfxcore;
 
   // The geometry's copy of the descriptor is manipulated below. Create a
   // shorter name for it.
@@ -249,10 +236,10 @@ bool gfx_init_geometry(
 
   glGenVertexArrays(1, &geometry->vao);
   glBindVertexArray(geometry->vao);
-  if (!configure_vertex_attributes(render_backend, desc)) {
+  if (!configure_vertex_attributes(gfxcore, desc)) {
     goto cleanup;
   }
-  if (!configure_indices(render_backend, desc)) {
+  if (!configure_indices(gfxcore, desc)) {
     goto cleanup;
   }
   glBindVertexArray(0);
diff --git a/gfx/src/render/geometry.h b/gfx/src/render/geometry.h
index 484934d..c37a76f 100644
--- a/gfx/src/render/geometry.h
+++ b/gfx/src/render/geometry.h
@@ -1,6 +1,6 @@
 #pragma once
 
-#include <gfx/render_backend.h>
+#include <gfx/core.h>
 
 #include "gl_util.h"
 
@@ -18,11 +18,11 @@ typedef struct Geometry {
   GeometryDesc desc;
   size_t num_verts; // May differ from the initial value in the descriptor if
                     // the geometry is updated.
-  RenderBackend* render_backend;
+  GfxCore* gfxcore;
 } Geometry;
 
 /// Create new geometry.
-bool gfx_init_geometry(Geometry*, RenderBackend*, const GeometryDesc*);
+bool gfx_init_geometry(Geometry*, GfxCore*, const GeometryDesc*);
 
 /// Destroy the geometry.
 void gfx_del_geometry(Geometry*);
diff --git a/gfx/src/render/render_backend.c b/gfx/src/render/render_backend.c
deleted file mode 100644
index 8e88feb..0000000
--- a/gfx/src/render/render_backend.c
+++ /dev/null
@@ -1,425 +0,0 @@
-#include "render_backend_impl.h"
-
-#include "gl_util.h"
-
-// #include <log/log.h>
-
-#include <assert.h>
-
-void gfx_init_render_backend(RenderBackend* render_backend) {
-  assert(render_backend);
-
-  mempool_make(&render_backend->buffers);
-  mempool_make(&render_backend->framebuffers);
-  mempool_make(&render_backend->geometries);
-  mempool_make(&render_backend->renderbuffers);
-  mempool_make(&render_backend->shaders);
-  mempool_make(&render_backend->shader_programs);
-  mempool_make(&render_backend->textures);
-
-  mempool_make(&render_backend->shader_cache);
-  mempool_make(&render_backend->program_cache);
-
-  glEnable(GL_CULL_FACE);
-  glFrontFace(GL_CCW);
-  glCullFace(GL_BACK);
-
-  glEnable(GL_DEPTH_TEST);
-
-  // Filter cubemaps across their faces to avoid seams.
-  // https://www.khronos.org/opengl/wiki/Cubemap_Texture#Seamless_cubemap
-  glEnable(GL_TEXTURE_CUBE_MAP_SEAMLESS);
-}
-
-// Conveniently destroy any objects that have not been destroyed by the
-// application.
-void gfx_del_render_backend(RenderBackend* render_backend) {
-  assert(render_backend);
-
-  mempool_foreach(
-      &render_backend->buffers, buffer, { gfx_del_buffer(buffer); });
-
-  mempool_foreach(&render_backend->framebuffers, framebuffer, {
-    gfx_del_framebuffer(framebuffer);
-  });
-
-  mempool_foreach(
-      &render_backend->geometries, geometry, { gfx_del_geometry(geometry); });
-
-  mempool_foreach(&render_backend->renderbuffers, renderbuffer, {
-    gfx_del_renderbuffer(renderbuffer);
-  });
-
-  mempool_foreach(&render_backend->shader_programs, prog, {
-    gfx_del_shader_program(prog);
-  });
-
-  mempool_foreach(
-      &render_backend->shaders, shader, { gfx_del_shader(shader); });
-
-  mempool_foreach(
-      &render_backend->textures, texture, { gfx_del_texture(texture); });
-}
-
-// -----------------------------------------------------------------------------
-// Render commands.
-// -----------------------------------------------------------------------------
-
-void gfx_start_frame(RenderBackend* render_backend) {
-  assert(render_backend);
-
-  glViewport(
-      0, 0, render_backend->viewport.width, render_backend->viewport.height);
-  glClearColor(0, 0, 0, 0);
-  glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
-
-  ASSERT_GL;
-}
-
-void gfx_end_frame(RenderBackend* render_backend) {
-  assert(render_backend);
-  ASSERT_GL;
-}
-
-void gfx_set_viewport(RenderBackend* render_backend, int width, int height) {
-  assert(render_backend);
-  render_backend->viewport.width  = width;
-  render_backend->viewport.height = height;
-}
-
-void gfx_get_viewport(RenderBackend* render_backend, int* width, int* height) {
-  assert(render_backend);
-  assert(width);
-  assert(height);
-  *width  = render_backend->viewport.width;
-  *height = render_backend->viewport.height;
-}
-
-void gfx_set_blending(RenderBackend* render_backend, bool enable) {
-  assert(render_backend);
-  if (enable) {
-    glEnable(GL_BLEND);
-    glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
-  } else {
-    glDisable(GL_BLEND);
-  }
-}
-
-void gfx_set_depth_mask(RenderBackend* render_backend, bool enable) {
-  assert(render_backend);
-  glDepthMask(enable ? GL_TRUE : GL_FALSE);
-}
-
-void gfx_set_culling(RenderBackend* render_backend, bool enable) {
-  assert(render_backend);
-  if (enable) {
-    glEnable(GL_CULL_FACE);
-  } else {
-    glDisable(GL_CULL_FACE);
-  }
-}
-
-void gfx_set_polygon_offset(
-    RenderBackend* render_backend, float scale, float bias) {
-  assert(render_backend);
-  if ((scale != 0.0f) || (bias != 0.0f)) {
-    glEnable(GL_POLYGON_OFFSET_FILL);
-  } else {
-    glDisable(GL_POLYGON_OFFSET_FILL);
-  }
-  glPolygonOffset(scale, bias);
-}
-
-void gfx_reset_polygon_offset(RenderBackend* render_backend) {
-  assert(render_backend);
-  glPolygonOffset(0, 0);
-  glDisable(GL_POLYGON_OFFSET_FILL);
-}
-
-// -----------------------------------------------------------------------------
-// Buffers.
-// -----------------------------------------------------------------------------
-
-Buffer* gfx_make_buffer(RenderBackend* render_backend, const BufferDesc* desc) {
-  assert(render_backend);
-  assert(desc);
-
-  Buffer* buffer = mempool_alloc(&render_backend->buffers);
-  if (!gfx_init_buffer(buffer, desc)) {
-    mempool_free(&render_backend->buffers, &buffer);
-    return 0;
-  }
-  return buffer;
-}
-
-void gfx_destroy_buffer(RenderBackend* render_backend, Buffer** buffer) {
-  assert(render_backend);
-  assert(buffer);
-  if (*buffer) {
-    gfx_del_buffer(*buffer);
-    mempool_free(&render_backend->buffers, buffer);
-  }
-}
-
-// -----------------------------------------------------------------------------
-// Geometry.
-// -----------------------------------------------------------------------------
-
-Geometry* gfx_make_geometry(
-    RenderBackend* render_backend, const GeometryDesc* desc) {
-  assert(render_backend);
-  assert(desc);
-
-  Geometry* geometry = mempool_alloc(&render_backend->geometries);
-  if (!gfx_init_geometry(geometry, render_backend, desc)) {
-    mempool_free(&render_backend->geometries, &geometry);
-    return 0;
-  }
-  return geometry;
-}
-
-void gfx_destroy_geometry(RenderBackend* render_backend, Geometry** geometry) {
-  assert(render_backend);
-  assert(geometry);
-
-  if (*geometry) {
-    gfx_del_geometry(*geometry);
-    mempool_free(&render_backend->geometries, geometry);
-  }
-}
-
-// -----------------------------------------------------------------------------
-// Textures.
-// -----------------------------------------------------------------------------
-
-Texture* gfx_make_texture(
-    RenderBackend* render_backend, const TextureDesc* desc) {
-  assert(render_backend);
-  assert(desc);
-
-  Texture* texture = mempool_alloc(&render_backend->textures);
-  if (!gfx_init_texture(texture, desc)) {
-    mempool_free(&render_backend->textures, &texture);
-    return 0;
-  }
-  return texture;
-}
-
-void gfx_destroy_texture(RenderBackend* render_backend, Texture** texture) {
-  assert(render_backend);
-  assert(texture);
-  assert(*texture);
-
-  if (*texture) {
-    gfx_del_texture(*texture);
-    mempool_free(&render_backend->textures, texture);
-  }
-}
-
-// -----------------------------------------------------------------------------
-// Renderbuffers.
-// -----------------------------------------------------------------------------
-
-RenderBuffer* gfx_make_renderbuffer(
-    RenderBackend* render_backend, const RenderBufferDesc* desc) {
-  assert(render_backend);
-  assert(desc);
-
-  RenderBuffer* renderbuffer = mempool_alloc(&render_backend->renderbuffers);
-  if (!gfx_init_renderbuffer(renderbuffer, desc)) {
-    mempool_free(&render_backend->renderbuffers, &renderbuffer);
-  }
-  return renderbuffer;
-}
-
-void gfx_destroy_renderbuffer(
-    RenderBackend* render_backend, RenderBuffer** renderbuffer) {
-  assert(render_backend);
-  assert(renderbuffer);
-  assert(*renderbuffer);
-
-  if (*renderbuffer) {
-    gfx_del_renderbuffer(*renderbuffer);
-    mempool_free(&render_backend->renderbuffers, renderbuffer);
-  }
-}
-
-// -----------------------------------------------------------------------------
-// Framebuffers.
-// -----------------------------------------------------------------------------
-
-FrameBuffer* gfx_make_framebuffer(
-    RenderBackend* render_backend, const FrameBufferDesc* desc) {
-  assert(render_backend);
-  assert(desc);
-
-  FrameBuffer* framebuffer = mempool_alloc(&render_backend->framebuffers);
-  if (!gfx_init_framebuffer(framebuffer, desc)) {
-    mempool_free(&render_backend->framebuffers, &framebuffer);
-    return 0;
-  }
-  return framebuffer;
-}
-
-void gfx_destroy_framebuffer(
-    RenderBackend* render_backend, FrameBuffer** framebuffer) {
-  assert(render_backend);
-  assert(framebuffer);
-  assert(*framebuffer);
-
-  if (*framebuffer) {
-    gfx_del_framebuffer(*framebuffer);
-    mempool_free(&render_backend->framebuffers, framebuffer);
-  }
-}
-
-// -----------------------------------------------------------------------------
-// Shaders.
-// -----------------------------------------------------------------------------
-
-static uint64_t hash_shader_desc(const ShaderDesc* desc) {
-  assert(desc);
-  // Note that defines may affect shader permutations, so we need to hash those
-  // as well.
-  uint64_t hash = 0;
-  for (size_t i = 0; i < desc->num_defines; ++i) {
-    const ShaderCompilerDefine* define = &desc->defines[i];
-    hash = (((hash << 13) + sstring_hash(define->name)) << 7) +
-           sstring_hash(define->value);
-  }
-  return (hash << 17) + cstring_hash(desc->code);
-}
-
-static uint64_t hash_program_desc(const ShaderProgramDesc* desc) {
-  assert(desc);
-  return ((uint64_t)desc->vertex_shader->id << 32) |
-         (uint64_t)desc->fragment_shader->id;
-}
-
-static Shader* find_cached_shader(ShaderCache* cache, uint64_t hash) {
-  assert(cache);
-  mempool_foreach(cache, entry, {
-    if (entry->hash == hash) {
-      return entry->shader;
-    }
-  });
-  return 0;
-}
-
-static ShaderProgram* find_cached_program(ProgramCache* cache, uint64_t hash) {
-  assert(cache);
-  mempool_foreach(cache, entry, {
-    if (entry->hash == hash) {
-      return entry->program;
-    }
-  });
-  return 0;
-}
-
-static ShaderCacheEntry* find_shader_cache_entry(
-    ShaderCache* cache, const Shader* shader) {
-  assert(cache);
-  assert(shader);
-  mempool_foreach(cache, entry, {
-    if (entry->shader == shader) {
-      return entry;
-    }
-  });
-  return 0;
-}
-
-static ShaderProgramCacheEntry* find_program_cache_entry(
-    ProgramCache* cache, const ShaderProgram* prog) {
-  assert(cache);
-  assert(prog);
-  mempool_foreach(cache, entry, {
-    if (entry->program == prog) {
-      return entry;
-    }
-  });
-  return 0;
-}
-
-Shader* gfx_make_shader(RenderBackend* render_backend, const ShaderDesc* desc) {
-  assert(render_backend);
-  assert(desc);
-
-  // Check the shader cache first.
-  ShaderCache*   cache  = &render_backend->shader_cache;
-  const uint64_t hash   = hash_shader_desc(desc);
-  Shader*        shader = find_cached_shader(cache, hash);
-  if (shader) {
-    // LOGD("Found cached shader with hash [%lx]", hash);
-    return shader;
-  }
-
-  shader = mempool_alloc(&render_backend->shaders);
-  if (!shader) {
-    return 0;
-  }
-  if (!gfx_compile_shader(shader, desc)) {
-    mempool_free(&render_backend->shaders, &shader);
-    return 0;
-  }
-  ShaderCacheEntry* entry = mempool_alloc(cache);
-  *entry                  = (ShaderCacheEntry){.hash = hash, .shader = shader};
-  // LOGD("Added shader with hash [%lx] to cache", hash);
-  return shader;
-}
-
-void gfx_destroy_shader(RenderBackend* render_backend, Shader** shader) {
-  assert(render_backend);
-  assert(shader);
-
-  if (*shader) {
-    // Remove the shader from the cache.
-    ShaderCache*      cache = &render_backend->shader_cache;
-    ShaderCacheEntry* entry = find_shader_cache_entry(cache, *shader);
-    assert(entry); // Must be there, shaders can't go untracked.
-    mempool_free(cache, &entry);
-
-    gfx_del_shader(*shader);
-    mempool_free(&render_backend->shaders, shader);
-  }
-}
-
-ShaderProgram* gfx_make_shader_program(
-    RenderBackend* render_backend, const ShaderProgramDesc* desc) {
-  assert(render_backend);
-  assert(desc);
-
-  // Check the shader program cache first.
-  ProgramCache*  cache = &render_backend->program_cache;
-  const uint64_t hash  = hash_program_desc(desc);
-  ShaderProgram* prog  = find_cached_program(cache, hash);
-  if (prog) {
-    // LOGD("Found cached shader program with hash [%lx]", hash);
-    return prog;
-  }
-
-  prog = mempool_alloc(&render_backend->shader_programs);
-  if (!gfx_build_shader_program(prog, desc)) {
-    mempool_free(&render_backend->shader_programs, &prog);
-    return 0;
-  }
-  ShaderProgramCacheEntry* entry = mempool_alloc(cache);
-  *entry = (ShaderProgramCacheEntry){.hash = hash, .program = prog};
-  // LOGD("Added shader program with hash [%lx] to cache", hash);
-  return prog;
-}
-
-void gfx_destroy_shader_program(
-    RenderBackend* render_backend, ShaderProgram** prog) {
-  assert(render_backend);
-  assert(prog);
-  if (*prog) {
-    // Remove the shader program from the cache.
-    ProgramCache*            cache = &render_backend->program_cache;
-    ShaderProgramCacheEntry* entry = find_program_cache_entry(cache, *prog);
-    assert(entry); // Must be there, shaders can't go untracked.
-    mempool_free(cache, &entry);
-
-    gfx_del_shader_program(*prog);
-    mempool_free(&render_backend->shader_programs, prog);
-  }
-}
diff --git a/gfx/src/render/render_backend_impl.h b/gfx/src/render/render_backend_impl.h
deleted file mode 100644
index e149e98..0000000
--- a/gfx/src/render/render_backend_impl.h
+++ /dev/null
@@ -1,66 +0,0 @@
-#pragma once
-
-#include <gfx/render_backend.h>
-#include <gfx/sizes.h>
-
-#include "buffer.h"
-#include "framebuffer.h"
-#include "geometry.h"
-#include "renderbuffer.h"
-#include "shader.h"
-#include "shader_program.h"
-#include "texture.h"
-
-#include <mempool.h>
-
-#include <stdint.h>
-
-// TODO: Make a generic (hash, void*) structure and define functions over it.
-// Then define a macro that defines type-safe macros given the type of the
-// entry.
-typedef struct ShaderCacheEntry {
-  uint64_t hash;
-  Shader*  shader;
-} ShaderCacheEntry;
-
-typedef struct ShaderProgramCacheEntry {
-  uint64_t       hash;
-  ShaderProgram* program;
-} ShaderProgramCacheEntry;
-
-DEF_MEMPOOL(buffer_pool, Buffer, GFX_MAX_NUM_BUFFERS)
-DEF_MEMPOOL(framebuffer_pool, FrameBuffer, GFX_MAX_NUM_FRAMEBUFFERS)
-DEF_MEMPOOL(geometry_pool, Geometry, GFX_MAX_NUM_GEOMETRIES)
-DEF_MEMPOOL(renderbuffer_pool, RenderBuffer, GFX_MAX_NUM_RENDERBUFFERS)
-DEF_MEMPOOL(shader_pool, Shader, GFX_MAX_NUM_SHADERS)
-DEF_MEMPOOL(shader_program_pool, ShaderProgram, GFX_MAX_NUM_SHADER_PROGRAMS)
-DEF_MEMPOOL(texture_pool, Texture, GFX_MAX_NUM_TEXTURES)
-
-DEF_MEMPOOL(ShaderCache, ShaderCacheEntry, GFX_MAX_NUM_SHADERS)
-DEF_MEMPOOL(ProgramCache, ShaderProgramCacheEntry, GFX_MAX_NUM_SHADER_PROGRAMS)
-
-typedef struct {
-  int width;
-  int height;
-} Viewport;
-
-typedef struct RenderBackend {
-  Viewport viewport;
-  // mempools for render-specific objects: textures, geometry, etc.
-  buffer_pool         buffers;
-  framebuffer_pool    framebuffers;
-  geometry_pool       geometries;
-  renderbuffer_pool   renderbuffers;
-  shader_pool         shaders;
-  shader_program_pool shader_programs;
-  texture_pool        textures;
-  // Caches.
-  ShaderCache  shader_cache;
-  ProgramCache program_cache;
-} RenderBackend;
-
-/// Create a new render backend.
-void gfx_init_render_backend(RenderBackend*);
-
-/// Destroy the render backend.
-void gfx_del_render_backend(RenderBackend*);
diff --git a/gfx/src/render/renderbuffer.h b/gfx/src/render/renderbuffer.h
index 458dc83..ea11610 100644
--- a/gfx/src/render/renderbuffer.h
+++ b/gfx/src/render/renderbuffer.h
@@ -1,6 +1,6 @@
 #pragma once
 
-#include <gfx/render_backend.h>
+#include <gfx/core.h>
 
 #include "gl_util.h"
 
diff --git a/gfx/src/render/shader.h b/gfx/src/render/shader.h
index a1aaec2..b9f5679 100644
--- a/gfx/src/render/shader.h
+++ b/gfx/src/render/shader.h
@@ -1,6 +1,6 @@
 #pragma once
 
-#include <gfx/render_backend.h>
+#include <gfx/core.h>
 
 #include "gl_util.h"
 
diff --git a/gfx/src/render/shader_program.h b/gfx/src/render/shader_program.h
index c269190..1443663 100644
--- a/gfx/src/render/shader_program.h
+++ b/gfx/src/render/shader_program.h
@@ -1,6 +1,6 @@
 #pragma once
 
-#include <gfx/render_backend.h>
+#include <gfx/core.h>
 #include <gfx/sizes.h>
 
 #include "gl_util.h"
@@ -12,9 +12,9 @@
 typedef struct Texture Texture;
 
 typedef struct ShaderProgram {
-  GLuint id;
+  GLuint        id;
   ShaderUniform uniforms[GFX_MAX_UNIFORMS_PER_SHADER];
-  int num_uniforms;
+  int           num_uniforms;
 } ShaderProgram;
 
 /// Create a new shader program.
diff --git a/gfx/src/render/texture.h b/gfx/src/render/texture.h
index f667752..4af41e9 100644
--- a/gfx/src/render/texture.h
+++ b/gfx/src/render/texture.h
@@ -1,6 +1,6 @@
 #pragma once
 
-#include <gfx/render_backend.h>
+#include <gfx/core.h>
 
 #include "gl_util.h"
 
diff --git a/gfx/src/renderer/imm_renderer.c b/gfx/src/renderer/imm_renderer.c
index dd5e2cb..8cf3a10 100644
--- a/gfx/src/renderer/imm_renderer.c
+++ b/gfx/src/renderer/imm_renderer.c
@@ -1,6 +1,6 @@
 #include "imm_renderer_impl.h"
 
-#include <gfx/render_backend.h>
+#include <gfx/core.h>
 #include <gfx/util/shader.h>
 
 #include <math/aabb3.h>
@@ -8,27 +8,26 @@
 #include <assert.h>
 #include <string.h> // memcpy
 
-bool imm_renderer_make(ImmRenderer* renderer, RenderBackend* render_backend) {
+bool imm_renderer_make(ImmRenderer* renderer, GfxCore* gfxcore) {
   assert(renderer);
-  assert(render_backend);
+  assert(gfxcore);
 
   const size_t num_triangle_verts = IMM_MAX_NUM_TRIANGLES * 3;
 
-  renderer->render_backend = render_backend;
+  renderer->gfxcore = gfxcore;
 
   renderer->triangles = gfx_make_geometry(
-      render_backend,
-      &(GeometryDesc){
-          .type         = Triangles,
-          .buffer_usage = BufferDynamic,
-          .num_verts    = num_triangle_verts,
-          .positions3d =
-              (BufferView3d){.size_bytes = num_triangle_verts * sizeof(vec3)}});
+      gfxcore, &(GeometryDesc){
+                   .type         = Triangles,
+                   .buffer_usage = BufferDynamic,
+                   .num_verts    = num_triangle_verts,
+                   .positions3d  = (BufferView3d){
+                        .size_bytes = num_triangle_verts * sizeof(vec3)}});
   if (!renderer->triangles) {
     goto cleanup;
   }
 
-  renderer->shader = gfx_make_immediate_mode_shader(render_backend);
+  renderer->shader = gfx_make_immediate_mode_shader(gfxcore);
   if (!renderer->shader) {
     goto cleanup;
   }
@@ -47,15 +46,15 @@ cleanup:
 
 void imm_renderer_destroy(ImmRenderer* renderer) {
   assert(renderer);
-  assert(renderer->render_backend);
+  assert(renderer->gfxcore);
 
   if (renderer->triangles) {
-    gfx_destroy_geometry(renderer->render_backend, &renderer->triangles);
+    gfx_destroy_geometry(renderer->gfxcore, &renderer->triangles);
     // TODO: Could also destroy the geometry's buffers here.
   }
 
   if (renderer->shader) {
-    gfx_destroy_shader_program(renderer->render_backend, &renderer->shader);
+    gfx_destroy_shader_program(renderer->gfxcore, &renderer->shader);
   }
 }
 
diff --git a/gfx/src/renderer/imm_renderer_impl.h b/gfx/src/renderer/imm_renderer_impl.h
index 7769855..5ece354 100644
--- a/gfx/src/renderer/imm_renderer_impl.h
+++ b/gfx/src/renderer/imm_renderer_impl.h
@@ -18,7 +18,7 @@ typedef struct ShaderProgram ShaderProgram;
 /// of primitives per frame. It does not adjust this number dynamically. Keeps
 /// things simple while the extra complexity is not needed.
 typedef struct ImmRenderer {
-  RenderBackend* render_backend;
+  GfxCore*       gfxcore;
   ShaderProgram* shader;
   Geometry*      triangles;
   size_t         num_triangle_verts; // Number of triangle verts this frame.
@@ -35,7 +35,7 @@ typedef struct ImmRenderer {
 } ImmRenderer;
 
 /// Create a new immediate mode renderer.
-bool imm_renderer_make(ImmRenderer*, RenderBackend*);
+bool imm_renderer_make(ImmRenderer*, GfxCore*);
 
 /// Destroy the immediate mode renderer.
 void imm_renderer_destroy(ImmRenderer*);
diff --git a/gfx/src/renderer/renderer.c b/gfx/src/renderer/renderer.c
index 2244733..d615918 100644
--- a/gfx/src/renderer/renderer.c
+++ b/gfx/src/renderer/renderer.c
@@ -11,7 +11,7 @@
 #include "scene/scene_impl.h"
 #include "scene/scene_memory.h"
 
-#include <gfx/render_backend.h>
+#include <gfx/core.h>
 #include <gfx/util/ibl.h>
 #include <gfx/util/shader.h>
 
@@ -29,11 +29,11 @@ static const int PREFILTERED_ENVIRONMENT_MAP_HEIGHT = 128;
 static const int BRDF_INTEGRATION_MAP_WIDTH         = 512;
 static const int BRDF_INTEGRATION_MAP_HEIGHT        = 512;
 
-bool renderer_make(Renderer* renderer, RenderBackend* render_backend) {
+bool renderer_make(Renderer* renderer, GfxCore* gfxcore) {
   assert(renderer);
-  assert(render_backend);
+  assert(gfxcore);
 
-  renderer->render_backend = render_backend;
+  renderer->gfxcore = gfxcore;
 
   return true;
 }
@@ -42,23 +42,23 @@ void renderer_destroy(Renderer* renderer) {
   if (!renderer) {
     return;
   }
-  assert(renderer->render_backend);
-  RenderBackend* render_backend = renderer->render_backend;
+  assert(renderer->gfxcore);
+  GfxCore* gfxcore = renderer->gfxcore;
   if (renderer->ibl) {
-    gfx_destroy_ibl(render_backend, &renderer->ibl);
+    gfx_destroy_ibl(gfxcore, &renderer->ibl);
   }
   if (renderer->shaders.debug) {
-    gfx_destroy_shader_program(render_backend, &renderer->shaders.debug);
+    gfx_destroy_shader_program(gfxcore, &renderer->shaders.debug);
   }
   if (renderer->shaders.normals) {
-    gfx_destroy_shader_program(render_backend, &renderer->shaders.normals);
+    gfx_destroy_shader_program(gfxcore, &renderer->shaders.normals);
   }
   if (renderer->shaders.normal_mapped_normals) {
     gfx_destroy_shader_program(
-        render_backend, &renderer->shaders.normal_mapped_normals);
+        gfxcore, &renderer->shaders.normal_mapped_normals);
   }
   if (renderer->shaders.tangents) {
-    gfx_destroy_shader_program(render_backend, &renderer->shaders.tangents);
+    gfx_destroy_shader_program(gfxcore, &renderer->shaders.tangents);
   }
 }
 
@@ -66,14 +66,13 @@ void renderer_destroy(Renderer* renderer) {
 static bool init_ibl(Renderer* renderer) {
   assert(renderer);
 
-  if (!renderer->ibl &&
-      !(renderer->ibl = gfx_make_ibl(renderer->render_backend))) {
+  if (!renderer->ibl && !(renderer->ibl = gfx_make_ibl(renderer->gfxcore))) {
     return false;
   }
 
   if (!renderer->brdf_integration_map &&
       !(renderer->brdf_integration_map = gfx_make_brdf_integration_map(
-            renderer->ibl, renderer->render_backend, BRDF_INTEGRATION_MAP_WIDTH,
+            renderer->ibl, renderer->gfxcore, BRDF_INTEGRATION_MAP_WIDTH,
             BRDF_INTEGRATION_MAP_HEIGHT))) {
     return false;
   }
@@ -84,13 +83,13 @@ static bool init_ibl(Renderer* renderer) {
 static ShaderProgram* load_shader(Renderer* renderer, RenderSceneMode mode) {
   assert(renderer);
 
-#define LOAD_AND_RETURN(pShader, constructor)          \
-  {                                                    \
-    if (!pShader) {                                    \
-      pShader = constructor(renderer->render_backend); \
-    }                                                  \
-    assert(pShader);                                   \
-    return pShader;                                    \
+#define LOAD_AND_RETURN(pShader, constructor)   \
+  {                                             \
+    if (!pShader) {                             \
+      pShader = constructor(renderer->gfxcore); \
+    }                                           \
+    assert(pShader);                            \
+    return pShader;                             \
   }
 
   switch (mode) {
@@ -121,8 +120,7 @@ static ShaderProgram* load_shader(Renderer* renderer, RenderSceneMode mode) {
 /// Computes irradiance and prefiltered environment maps for the light if they
 /// have not been already computed.
 static bool setup_environment_light(
-    Renderer* renderer, RenderBackend* render_backend,
-    EnvironmentLight* light) {
+    Renderer* renderer, GfxCore* gfxcore, EnvironmentLight* light) {
   assert(renderer);
   assert(light);
 
@@ -139,14 +137,14 @@ static bool setup_environment_light(
   Texture* prefiltered_environment_map = 0;
 
   if (!(irradiance_map = gfx_make_irradiance_map(
-            renderer->ibl, render_backend, light->environment_map,
+            renderer->ibl, gfxcore, light->environment_map,
             IRRADIANCE_MAP_WIDTH, IRRADIANCE_MAP_HEIGHT))) {
     goto cleanup;
   }
 
   int max_mip_level = 0;
   if (!(prefiltered_environment_map = gfx_make_prefiltered_environment_map(
-            renderer->ibl, render_backend, light->environment_map,
+            renderer->ibl, gfxcore, light->environment_map,
             PREFILTERED_ENVIRONMENT_MAP_WIDTH,
             PREFILTERED_ENVIRONMENT_MAP_HEIGHT, &max_mip_level))) {
     goto cleanup;
@@ -160,16 +158,16 @@ static bool setup_environment_light(
 
 cleanup:
   if (irradiance_map) {
-    gfx_destroy_texture(render_backend, &irradiance_map);
+    gfx_destroy_texture(gfxcore, &irradiance_map);
   }
   if (prefiltered_environment_map) {
-    gfx_destroy_texture(render_backend, &prefiltered_environment_map);
+    gfx_destroy_texture(gfxcore, &prefiltered_environment_map);
   }
   return false;
 }
 
 typedef struct RenderState {
-  RenderBackend* render_backend;
+  GfxCore*       gfxcore;
   Renderer*      renderer;
   ShaderProgram* shader; // Null to use scene shaders.
   const Scene*   scene;
@@ -220,7 +218,7 @@ static void draw_recursively(
 
     if (light->type == EnvironmentLightType) {
       bool result = setup_environment_light(
-          state->renderer, state->render_backend, &light->environment);
+          state->renderer, state->gfxcore, &light->environment);
       // TODO: Handle the result in a better way.
       assert(result);
       state->environment_light = light;
@@ -329,7 +327,7 @@ void gfx_render_scene(Renderer* renderer, const RenderSceneParams* params) {
   const Scene*       scene  = params->scene;
   const SceneCamera* camera = params->camera;
 
-  RenderBackend* render_backend = renderer->render_backend;
+  GfxCore* gfxcore = renderer->gfxcore;
 
   mat4 projection, camera_rotation, view_matrix;
   if (camera) {
@@ -344,11 +342,11 @@ void gfx_render_scene(Renderer* renderer, const RenderSceneParams* params) {
   }
 
   int width, height;
-  gfx_get_viewport(render_backend, &width, &height);
+  gfx_get_viewport(gfxcore, &width, &height);
   const float aspect = (float)width / (float)height;
 
   RenderState state = {
-      .render_backend    = render_backend,
+      .gfxcore           = gfxcore,
       .renderer          = renderer,
       .shader            = shader,
       .scene             = scene,
diff --git a/gfx/src/renderer/renderer_impl.h b/gfx/src/renderer/renderer_impl.h
index 1e28eb5..fc14dcb 100644
--- a/gfx/src/renderer/renderer_impl.h
+++ b/gfx/src/renderer/renderer_impl.h
@@ -9,9 +9,9 @@ typedef struct ShaderProgram ShaderProgram;
 typedef struct Texture       Texture;
 
 typedef struct Renderer {
-  RenderBackend* render_backend;
-  IBL*           ibl;
-  Texture*       brdf_integration_map;
+  GfxCore* gfxcore;
+  IBL*     ibl;
+  Texture* brdf_integration_map;
   struct {
     ShaderProgram* debug;
     ShaderProgram* normals;
@@ -21,7 +21,7 @@ typedef struct Renderer {
 } Renderer;
 
 /// Create a new renderer.
-bool renderer_make(Renderer*, RenderBackend*);
+bool renderer_make(Renderer*, GfxCore*);
 
 /// Destroy the renderer.
 void renderer_destroy(Renderer*);
diff --git a/gfx/src/scene/material.c b/gfx/src/scene/material.c
index b32d791..3248243 100644
--- a/gfx/src/scene/material.c
+++ b/gfx/src/scene/material.c
@@ -2,7 +2,7 @@
 
 #include "scene_memory.h"
 
-#include <gfx/render_backend.h>
+#include <gfx/core.h>
 
 static void material_make(Material* material, const MaterialDesc* desc) {
   assert(material);
diff --git a/gfx/src/scene/object.c b/gfx/src/scene/object.c
index 406c81f..e8e3ee6 100644
--- a/gfx/src/scene/object.c
+++ b/gfx/src/scene/object.c
@@ -1,6 +1,6 @@
 #include "object_impl.h"
 
-#include <gfx/render_backend.h>
+#include <gfx/core.h>
 
 #include "mesh_impl.h"
 #include "node_impl.h"
diff --git a/gfx/src/util/geometry.c b/gfx/src/util/geometry.c
index 84435ce..afe0109 100644
--- a/gfx/src/util/geometry.c
+++ b/gfx/src/util/geometry.c
@@ -25,20 +25,20 @@ static GeometryDesc make_quad_desc(vec2 positions[4]) {
   return desc;
 }
 
-Geometry* gfx_make_quad_11(RenderBackend* render_backend) {
-  assert(render_backend);
+Geometry* gfx_make_quad_11(GfxCore* gfxcore) {
+  assert(gfxcore);
 
   vec2 positions[4];
   make_quad_11_positions(positions);
   const GeometryDesc geometry_desc = make_quad_desc(positions);
-  return gfx_make_geometry(render_backend, &geometry_desc);
+  return gfx_make_geometry(gfxcore, &geometry_desc);
 }
 
-Geometry* gfx_make_quad_01(RenderBackend* render_backend) {
-  assert(render_backend);
+Geometry* gfx_make_quad_01(GfxCore* gfxcore) {
+  assert(gfxcore);
 
   vec2 positions[4];
   make_quad_01_positions(positions);
   const GeometryDesc geometry_desc = make_quad_desc(positions);
-  return gfx_make_geometry(render_backend, &geometry_desc);
+  return gfx_make_geometry(gfxcore, &geometry_desc);
 }
diff --git a/gfx/src/util/ibl.c b/gfx/src/util/ibl.c
index 6b7465c..5a79990 100644
--- a/gfx/src/util/ibl.c
+++ b/gfx/src/util/ibl.c
@@ -1,6 +1,6 @@
 #include <gfx/util/ibl.h>
 
-#include <gfx/render_backend.h>
+#include <gfx/core.h>
 #include <gfx/util/geometry.h>
 #include <gfx/util/shader.h>
 #include <math/mat4.h>
@@ -36,15 +36,15 @@ static const float flips[6] = {
     -1.0f, // Front.
 };
 
-IBL* gfx_make_ibl(RenderBackend* render_backend) {
-  assert(render_backend);
+IBL* gfx_make_ibl(GfxCore* gfxcore) {
+  assert(gfxcore);
 
   IBL* ibl = calloc(1, sizeof(IBL));
   if (!ibl) {
     return 0;
   }
 
-  if (!(ibl->quad = gfx_make_quad_11(render_backend))) {
+  if (!(ibl->quad = gfx_make_quad_11(gfxcore))) {
     goto cleanup;
   }
 
@@ -53,24 +53,23 @@ IBL* gfx_make_ibl(RenderBackend* render_backend) {
   // shader is fully compiled up front anyway, since the driver will typically
   // defer full compilation to the first draw call.
   if (!(ibl->brdf_integration_map_shader =
-            gfx_make_brdf_integration_map_shader(render_backend))) {
+            gfx_make_brdf_integration_map_shader(gfxcore))) {
     goto cleanup;
   }
 
-  if (!(ibl->irradiance_map_shader =
-            gfx_make_irradiance_map_shader(render_backend))) {
+  if (!(ibl->irradiance_map_shader = gfx_make_irradiance_map_shader(gfxcore))) {
     goto cleanup;
   }
 
   if (!(ibl->prefiltered_environment_map_shader =
-            gfx_make_prefiltered_environment_map_shader(render_backend))) {
+            gfx_make_prefiltered_environment_map_shader(gfxcore))) {
     goto cleanup;
   }
 
   // Create an empty framebuffer for now. Will attach the colour buffer later
   // as we render the faces of the cube.
   if (!(ibl->framebuffer = gfx_make_framebuffer(
-            render_backend,
+            gfxcore,
             &(FrameBufferDesc){
                 .colour =
                     (FrameBufferAttachment){.type = FrameBufferNoAttachment},
@@ -116,42 +115,41 @@ IBL* gfx_make_ibl(RenderBackend* render_backend) {
   return ibl;
 
 cleanup:
-  gfx_destroy_ibl(render_backend, &ibl);
+  gfx_destroy_ibl(gfxcore, &ibl);
   return 0;
 }
 
-void gfx_destroy_ibl(RenderBackend* render_backend, IBL** ibl) {
+void gfx_destroy_ibl(GfxCore* gfxcore, IBL** ibl) {
   if (!ibl) {
     return;
   }
   if ((*ibl)->quad) {
-    gfx_destroy_geometry(render_backend, &(*ibl)->quad);
+    gfx_destroy_geometry(gfxcore, &(*ibl)->quad);
   }
   if ((*ibl)->brdf_integration_map_shader) {
-    gfx_destroy_shader_program(
-        render_backend, &(*ibl)->brdf_integration_map_shader);
+    gfx_destroy_shader_program(gfxcore, &(*ibl)->brdf_integration_map_shader);
   }
   if ((*ibl)->irradiance_map_shader) {
-    gfx_destroy_shader_program(render_backend, &(*ibl)->irradiance_map_shader);
+    gfx_destroy_shader_program(gfxcore, &(*ibl)->irradiance_map_shader);
   }
   if ((*ibl)->prefiltered_environment_map_shader) {
     gfx_destroy_shader_program(
-        render_backend, &(*ibl)->prefiltered_environment_map_shader);
+        gfxcore, &(*ibl)->prefiltered_environment_map_shader);
   }
   if ((*ibl)->brdf_integration_map) {
-    gfx_destroy_texture(render_backend, &(*ibl)->brdf_integration_map);
+    gfx_destroy_texture(gfxcore, &(*ibl)->brdf_integration_map);
   }
   if ((*ibl)->framebuffer) {
-    gfx_destroy_framebuffer(render_backend, &(*ibl)->framebuffer);
+    gfx_destroy_framebuffer(gfxcore, &(*ibl)->framebuffer);
   }
   free(*ibl);
   *ibl = 0;
 }
 
 Texture* gfx_make_brdf_integration_map(
-    IBL* ibl, RenderBackend* render_backend, int width, int height) {
+    IBL* ibl, GfxCore* gfxcore, int width, int height) {
   assert(ibl);
-  assert(render_backend);
+  assert(gfxcore);
 
   if (ibl->brdf_integration_map) {
     return ibl->brdf_integration_map;
@@ -160,15 +158,15 @@ Texture* gfx_make_brdf_integration_map(
   bool success = false;
 
   if (!(ibl->brdf_integration_map = gfx_make_texture(
-            render_backend, &(TextureDesc){
-                                .width     = width,
-                                .height    = height,
-                                .depth     = 1,
-                                .dimension = Texture2D,
-                                .format    = TextureRG16F,
-                                .filtering = LinearFiltering,
-                                .wrap      = ClampToEdge,
-                                .mipmaps   = false}))) {
+            gfxcore, &(TextureDesc){
+                         .width     = width,
+                         .height    = height,
+                         .depth     = 1,
+                         .dimension = Texture2D,
+                         .format    = TextureRG16F,
+                         .filtering = LinearFiltering,
+                         .wrap      = ClampToEdge,
+                         .mipmaps   = false}))) {
     goto cleanup;
   }
 
@@ -190,7 +188,7 @@ cleanup:
   gfx_deactivate_shader_program(ibl->brdf_integration_map_shader);
   gfx_deactivate_framebuffer(ibl->framebuffer);
   if (!success && ibl->brdf_integration_map) {
-    gfx_destroy_texture(render_backend, &ibl->brdf_integration_map);
+    gfx_destroy_texture(gfxcore, &ibl->brdf_integration_map);
     return 0;
   } else {
     return ibl->brdf_integration_map;
@@ -198,10 +196,10 @@ cleanup:
 }
 
 Texture* gfx_make_irradiance_map(
-    IBL* ibl, RenderBackend* render_backend, const Texture* environment_map,
-    int width, int height) {
+    IBL* ibl, GfxCore* gfxcore, const Texture* environment_map, int width,
+    int height) {
   assert(ibl);
-  assert(render_backend);
+  assert(gfxcore);
   assert(environment_map);
 
   bool success = false;
@@ -215,14 +213,14 @@ Texture* gfx_make_irradiance_map(
   // Make sure to use a float colour format to avoid [0,1] clamping when the
   // irradiance values are computed!
   if (!(irradiance_map = gfx_make_texture(
-            render_backend, &(TextureDesc){
-                                .width     = width,
-                                .height    = height,
-                                .depth     = 1,
-                                .dimension = TextureCubeMap,
-                                .format    = TextureR11G11B10F,
-                                .filtering = LinearFiltering,
-                                .mipmaps   = false}))) {
+            gfxcore, &(TextureDesc){
+                         .width     = width,
+                         .height    = height,
+                         .depth     = 1,
+                         .dimension = TextureCubeMap,
+                         .format    = TextureR11G11B10F,
+                         .filtering = LinearFiltering,
+                         .mipmaps   = false}))) {
     goto cleanup;
   }
 
@@ -251,7 +249,7 @@ cleanup:
   gfx_deactivate_shader_program(ibl->irradiance_map_shader);
   gfx_deactivate_framebuffer(ibl->framebuffer);
   if (!success && irradiance_map) {
-    gfx_destroy_texture(render_backend, &irradiance_map);
+    gfx_destroy_texture(gfxcore, &irradiance_map);
     return 0;
   } else {
     return irradiance_map;
@@ -259,10 +257,10 @@ cleanup:
 }
 
 Texture* gfx_make_prefiltered_environment_map(
-    IBL* ibl, RenderBackend* render_backend, const Texture* environment_map,
-    int width, int height, int* max_mip_level) {
+    IBL* ibl, GfxCore* gfxcore, const Texture* environment_map, int width,
+    int height, int* max_mip_level) {
   assert(ibl);
-  assert(render_backend);
+  assert(gfxcore);
   assert(environment_map);
   assert(max_mip_level);
 
@@ -271,14 +269,14 @@ Texture* gfx_make_prefiltered_environment_map(
   Texture* prefiltered_env_map = 0;
 
   if (!(prefiltered_env_map = gfx_make_texture(
-            render_backend, &(TextureDesc){
-                                .width     = width,
-                                .height    = height,
-                                .depth     = 1,
-                                .dimension = TextureCubeMap,
-                                .format    = TextureR11G11B10F,
-                                .filtering = LinearFiltering,
-                                .mipmaps   = true}))) {
+            gfxcore, &(TextureDesc){
+                         .width     = width,
+                         .height    = height,
+                         .depth     = 1,
+                         .dimension = TextureCubeMap,
+                         .format    = TextureR11G11B10F,
+                         .filtering = LinearFiltering,
+                         .mipmaps   = true}))) {
     goto cleanup;
   }
 
@@ -322,7 +320,7 @@ cleanup:
   gfx_deactivate_shader_program(ibl->prefiltered_environment_map_shader);
   gfx_deactivate_framebuffer(ibl->framebuffer);
   if (!success && prefiltered_env_map) {
-    gfx_destroy_texture(render_backend, &prefiltered_env_map);
+    gfx_destroy_texture(gfxcore, &prefiltered_env_map);
     return 0;
   } else {
     return prefiltered_env_map;
diff --git a/gfx/src/util/shader.c b/gfx/src/util/shader.c
index ed81f79..f5c22cc 100644
--- a/gfx/src/util/shader.c
+++ b/gfx/src/util/shader.c
@@ -1,6 +1,6 @@
 #include <gfx/util/shader.h>
 
-#include <gfx/render_backend.h>
+#include <gfx/core.h>
 #include <shaders/brdf_integration_map.frag.h>
 #include <shaders/cook_torrance.frag.h>
 #include <shaders/cook_torrance.vert.h>
@@ -27,10 +27,9 @@
 #include <string.h>
 
 static ShaderProgram* make_shader_program(
-    RenderBackend* render_backend, const char* vert_source,
-    const char* frag_source, const ShaderCompilerDefine* defines,
-    size_t num_defines) {
-  assert(render_backend);
+    GfxCore* gfxcore, const char* vert_source, const char* frag_source,
+    const ShaderCompilerDefine* defines, size_t num_defines) {
+  assert(gfxcore);
   assert(vert_source);
   assert(frag_source);
 
@@ -49,19 +48,18 @@ static ShaderProgram* make_shader_program(
         fragment_shader_desc.defines, defines,
         num_defines * sizeof(ShaderCompilerDefine));
   }
-  vert = gfx_make_shader(render_backend, &vertex_shader_desc);
+  vert = gfx_make_shader(gfxcore, &vertex_shader_desc);
   if (!vert) {
     goto cleanup;
   }
-  frag = gfx_make_shader(render_backend, &fragment_shader_desc);
+  frag = gfx_make_shader(gfxcore, &fragment_shader_desc);
   if (!frag) {
     goto cleanup;
   }
 
   ShaderProgramDesc shader_program_desc = {
       .vertex_shader = vert, .fragment_shader = frag};
-  ShaderProgram* prog =
-      gfx_make_shader_program(render_backend, &shader_program_desc);
+  ShaderProgram* prog = gfx_make_shader_program(gfxcore, &shader_program_desc);
   if (!prog) {
     goto cleanup;
   }
@@ -69,76 +67,70 @@ static ShaderProgram* make_shader_program(
 
 cleanup:
   if (vert) {
-    gfx_destroy_shader(render_backend, &vert);
+    gfx_destroy_shader(gfxcore, &vert);
   }
   if (frag) {
-    gfx_destroy_shader(render_backend, &frag);
+    gfx_destroy_shader(gfxcore, &frag);
   }
   return 0;
 }
 
-ShaderProgram* gfx_make_brdf_integration_map_shader(
-    RenderBackend* render_backend) {
+ShaderProgram* gfx_make_brdf_integration_map_shader(GfxCore* gfxcore) {
   return make_shader_program(
-      render_backend, quad_vert, brdf_integration_map_frag, 0, 0);
+      gfxcore, quad_vert, brdf_integration_map_frag, 0, 0);
 }
 
-ShaderProgram* gfx_make_cook_torrance_shader(RenderBackend* render_backend) {
+ShaderProgram* gfx_make_cook_torrance_shader(GfxCore* gfxcore) {
   return make_shader_program(
-      render_backend, cook_torrance_vert, cook_torrance_frag, 0, 0);
+      gfxcore, cook_torrance_vert, cook_torrance_frag, 0, 0);
 }
 
 ShaderProgram* gfx_make_cook_torrance_shader_perm(
-    RenderBackend* render_backend, const ShaderCompilerDefine* defines,
-    size_t num_defines) {
+    GfxCore* gfxcore, const ShaderCompilerDefine* defines, size_t num_defines) {
   return make_shader_program(
-      render_backend, cook_torrance_vert, cook_torrance_frag, defines,
-      num_defines);
+      gfxcore, cook_torrance_vert, cook_torrance_frag, defines, num_defines);
 }
 
-ShaderProgram* gfx_make_immediate_mode_shader(RenderBackend* render_backend) {
+ShaderProgram* gfx_make_immediate_mode_shader(GfxCore* gfxcore) {
   return make_shader_program(
-      render_backend, immediate_mode_vert, immediate_mode_frag, 0, 0);
+      gfxcore, immediate_mode_vert, immediate_mode_frag, 0, 0);
 }
 
-ShaderProgram* gfx_make_irradiance_map_shader(RenderBackend* render_backend) {
+ShaderProgram* gfx_make_irradiance_map_shader(GfxCore* gfxcore) {
   return make_shader_program(
-      render_backend, cubemap_filtering_vert, irradiance_map_frag, 0, 0);
+      gfxcore, cubemap_filtering_vert, irradiance_map_frag, 0, 0);
 }
 
-ShaderProgram* gfx_make_prefiltered_environment_map_shader(
-    RenderBackend* render_backend) {
+ShaderProgram* gfx_make_prefiltered_environment_map_shader(GfxCore* gfxcore) {
   return make_shader_program(
-      render_backend, cubemap_filtering_vert, prefiltered_environment_map_frag,
-      0, 0);
+      gfxcore, cubemap_filtering_vert, prefiltered_environment_map_frag, 0, 0);
 }
 
-ShaderProgram* gfx_make_debug3d_shader(RenderBackend* render_backend) {
-  return make_shader_program(render_backend, debug3d_vert, debug3d_frag, 0, 0);
+ShaderProgram* gfx_make_debug3d_shader(GfxCore* gfxcore) {
+  return make_shader_program(gfxcore, debug3d_vert, debug3d_frag, 0, 0);
 }
 
-ShaderProgram* gfx_make_skyquad_shader(RenderBackend* render_backend) {
-  return make_shader_program(render_backend, skyquad_vert, skyquad_frag, 0, 0);
+ShaderProgram* gfx_make_skyquad_shader(GfxCore* gfxcore) {
+  return make_shader_program(gfxcore, skyquad_vert, skyquad_frag, 0, 0);
 }
 
-ShaderProgram* gfx_make_view_normal_mapped_normals_shader(
-    RenderBackend* render_backend) {
+ShaderProgram* gfx_make_view_normal_mapped_normals_shader(GfxCore* gfxcore) {
   return make_shader_program(
-      render_backend, view_normal_mapped_normals_vert,
-      view_normal_mapped_normals_frag, 0, 0);
+      gfxcore, view_normal_mapped_normals_vert, view_normal_mapped_normals_frag,
+      0, 0);
 }
 
-ShaderProgram* gfx_make_view_normals_shader(RenderBackend* render_backend) {
+ShaderProgram* gfx_make_view_normals_shader(GfxCore* gfxcore) {
   return make_shader_program(
-      render_backend, view_normals_vert, view_normals_frag, 0, 0);
+      gfxcore, view_normals_vert, view_normals_frag, 0, 0);
 }
 
-ShaderProgram* gfx_make_view_tangents_shader(RenderBackend* render_backend) {
+ShaderProgram* gfx_make_view_tangents_shader(GfxCore* gfxcore) {
   return make_shader_program(
-      render_backend, view_tangents_vert, view_tangents_frag, 0, 0);
+      gfxcore, view_tangents_vert, view_tangents_frag, 0, 0);
 }
 
-ShaderProgram* gfx_make_view_texture_shader(RenderBackend* render_backend) {
+ShaderProgram* gfx_make_view_texture_shader(GfxCore* gfxcore) {
   return make_shader_program(
-      render_backend, view_texture_vert, view_texture_frag, 0, 0);
+      gfxcore, view_texture_vert, view_texture_frag, 0, 0);
 }
diff --git a/gfx/src/util/skyquad.c b/gfx/src/util/skyquad.c
index 5027705..ff8f73f 100644
--- a/gfx/src/util/skyquad.c
+++ b/gfx/src/util/skyquad.c
@@ -1,7 +1,7 @@
 #include <gfx/util/skyquad.h>
 
+#include <gfx/core.h>
 #include <gfx/gfx.h>
-#include <gfx/render_backend.h>
 #include <gfx/scene/light.h>
 #include <gfx/scene/material.h>
 #include <gfx/scene/mesh.h>
@@ -15,9 +15,8 @@
 
 #include <assert.h>
 
-SceneObject* gfx_make_skyquad(
-    RenderBackend* render_backend, const Texture* texture) {
-  assert(render_backend);
+SceneObject* gfx_make_skyquad(GfxCore* gfxcore, const Texture* texture) {
+  assert(gfxcore);
   assert(texture);
 
   ShaderProgram* shader   = 0;
@@ -26,12 +25,12 @@ SceneObject* gfx_make_skyquad(
   Mesh*          mesh     = 0;
   SceneObject*   object   = 0;
 
-  shader = gfx_make_skyquad_shader(render_backend);
+  shader = gfx_make_skyquad_shader(gfxcore);
   if (!shader) {
     goto cleanup;
   }
 
-  geometry = gfx_make_quad_11(render_backend);
+  geometry = gfx_make_quad_11(gfxcore);
   if (!geometry) {
     goto cleanup;
   }
@@ -65,10 +64,10 @@ SceneObject* gfx_make_skyquad(
 
 cleanup:
   if (shader) {
-    gfx_destroy_shader_program(render_backend, &shader);
+    gfx_destroy_shader_program(gfxcore, &shader);
   }
   if (geometry) {
-    gfx_destroy_geometry(render_backend, &geometry);
+    gfx_destroy_geometry(gfxcore, &geometry);
   }
   if (material) {
     gfx_destroy_material(&material);
@@ -116,9 +115,8 @@ cleanup:
 }
 
 SceneNode* gfx_setup_skyquad(
-    RenderBackend* render_backend, SceneNode* root,
-    const Texture* environment_map) {
-  assert(render_backend);
+    GfxCore* gfxcore, SceneNode* root, const Texture* environment_map) {
+  assert(gfxcore);
   assert(root);
   assert(environment_map);
 
@@ -127,7 +125,7 @@ SceneNode* gfx_setup_skyquad(
   SceneNode*   light_node     = 0;
 
   // Create the skyquad object.
-  skyquad_object = gfx_make_skyquad(render_backend, environment_map);
+  skyquad_object = gfx_make_skyquad(gfxcore, environment_map);
   if (!skyquad_object) {
     goto cleanup;
   }
-- 
cgit v1.2.3