aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--CMakeLists.txt98
-rw-r--r--README.md (renamed from gfx/README.md)44
-rw-r--r--app/src/app.c3
-rwxr-xr-xbin/txt2c.py (renamed from gfx/bin/txt2c.py)0
-rw-r--r--cmake/shader.txt (renamed from gfx/cmake/shader.txt)0
-rw-r--r--cmake/txt2c.txt (renamed from gfx/cmake/txt2c.txt)0
-rw-r--r--contrib/cgltf-tangents/CMakeLists.txt (renamed from gfx/contrib/cgltf-tangents/CMakeLists.txt)0
-rw-r--r--contrib/cgltf-tangents/LICENSE (renamed from gfx/contrib/cgltf-tangents/LICENSE)0
-rw-r--r--contrib/cgltf-tangents/MikkTSpace/README.md (renamed from gfx/contrib/cgltf-tangents/MikkTSpace/README.md)0
-rw-r--r--contrib/cgltf-tangents/MikkTSpace/mikktspace.c (renamed from gfx/contrib/cgltf-tangents/MikkTSpace/mikktspace.c)0
-rw-r--r--contrib/cgltf-tangents/MikkTSpace/mikktspace.h (renamed from gfx/contrib/cgltf-tangents/MikkTSpace/mikktspace.h)0
-rw-r--r--contrib/cgltf-tangents/README.md (renamed from gfx/contrib/cgltf-tangents/README.md)0
-rw-r--r--contrib/cgltf-tangents/cgltf_tangents.c (renamed from gfx/contrib/cgltf-tangents/cgltf_tangents.c)0
-rw-r--r--contrib/cgltf-tangents/cgltf_tangents.h (renamed from gfx/contrib/cgltf-tangents/cgltf_tangents.h)0
-rw-r--r--contrib/cgltf-tangents/test/CMakeLists.txt (renamed from gfx/contrib/cgltf-tangents/test/CMakeLists.txt)0
-rw-r--r--contrib/cgltf-tangents/test/main.c (renamed from gfx/contrib/cgltf-tangents/test/main.c)0
-rw-r--r--contrib/cgltf/CMakeLists.txt (renamed from gfx/contrib/cgltf/CMakeLists.txt)0
-rw-r--r--contrib/cgltf/LICENSE (renamed from gfx/contrib/cgltf/LICENSE)0
-rw-r--r--contrib/cgltf/README.md (renamed from gfx/contrib/cgltf/README.md)0
-rw-r--r--contrib/cgltf/cgltf.h (renamed from gfx/contrib/cgltf/cgltf.h)0
-rw-r--r--contrib/cgltf/cgltf_write.h (renamed from gfx/contrib/cgltf/cgltf_write.h)0
-rw-r--r--contrib/stb/CMakeLists.txt (renamed from gfx/contrib/stb/CMakeLists.txt)0
-rw-r--r--contrib/stb/stb_image.h (renamed from gfx/contrib/stb/stb_image.h)0
-rw-r--r--doc/extern/2013SiggraphPresentationsNotes-26915738.pdf (renamed from gfx/doc/extern/2013SiggraphPresentationsNotes-26915738.pdf)bin2947929 -> 2947929 bytes
-rw-r--r--doc/extern/Scene Graph - CSE 167.pdf (renamed from gfx/doc/extern/Scene Graph - CSE 167.pdf)bin890801 -> 890801 bytes
-rw-r--r--doc/gfx.png (renamed from gfx/doc/gfx.png)bin6272 -> 6272 bytes
-rw-r--r--doc/gfx.txt (renamed from gfx/doc/gfx.txt)0
-rw-r--r--doc/gltfOverview-2.0.0b.png (renamed from gfx/doc/gltfOverview-2.0.0b.png)bin4757973 -> 4757973 bytes
-rw-r--r--doc/pipeline.png (renamed from gfx/doc/pipeline.png)bin10318 -> 10318 bytes
-rw-r--r--doc/pipeline.txt (renamed from gfx/doc/pipeline.txt)0
-rw-r--r--doc/renderer.png (renamed from gfx/doc/renderer.png)bin13144 -> 13144 bytes
-rw-r--r--doc/renderer.txt (renamed from gfx/doc/renderer.txt)0
-rw-r--r--doc/scene.png (renamed from gfx/doc/scene.png)bin59119 -> 59119 bytes
-rw-r--r--doc/scene.txt (renamed from gfx/doc/scene.txt)0
-rw-r--r--game/CMakeLists.txt22
-rw-r--r--game/src/game.c223
-rw-r--r--game/src/game.h21
-rw-r--r--game/src/plugins/CMakeLists.txt29
-rw-r--r--game/src/plugins/plugin.h52
-rw-r--r--game/src/plugins/pong.c237
-rw-r--r--game/src/plugins/texture_view.c144
-rw-r--r--game/src/plugins/viewer.c366
-rw-r--r--gfx-iso/CMakeLists.txt42
-rw-r--r--gfx-iso/demos/CMakeLists.txt2
-rw-r--r--gfx-iso/demos/checkerboard/CMakeLists.txt16
-rw-r--r--gfx-iso/demos/checkerboard/checkerboard.c166
-rw-r--r--gfx-iso/demos/isomap/CMakeLists.txt16
-rw-r--r--gfx-iso/demos/isomap/isomap.c105
-rw-r--r--gfx-iso/include/isogfx/backend.h28
-rw-r--r--gfx-iso/include/isogfx/isogfx.h136
-rw-r--r--gfx-iso/src/backend.c199
-rw-r--r--gfx-iso/src/isogfx.c952
-rw-r--r--gfx-iso/tools/mkasset.py324
-rw-r--r--gfx/CMakeLists.txt89
-rw-r--r--gfx/include/gfx/renderer.h104
-rw-r--r--gfx/src/renderer/imm_renderer.c260
-rw-r--r--gfx/src/renderer/renderer.c396
-rw-r--r--gfx/src/scene/material_impl.h16
-rw-r--r--include/gfx/asset.h (renamed from gfx/include/gfx/asset.h)0
-rw-r--r--include/gfx/core.h (renamed from gfx/include/gfx/core.h)4
-rw-r--r--include/gfx/gfx.h (renamed from gfx/include/gfx/gfx.h)6
-rw-r--r--include/gfx/llr/light.h (renamed from gfx/include/gfx/scene/light.h)0
-rw-r--r--include/gfx/llr/llr.h67
-rw-r--r--include/gfx/llr/material.h (renamed from gfx/include/gfx/scene/material.h)0
-rw-r--r--include/gfx/llr/mesh.h (renamed from gfx/include/gfx/scene/mesh.h)0
-rw-r--r--include/gfx/renderer.h29
-rw-r--r--include/gfx/renderer/imm_renderer.h55
-rw-r--r--include/gfx/scene.h (renamed from gfx/include/gfx/scene.h)7
-rw-r--r--include/gfx/scene/animation.h (renamed from gfx/include/gfx/scene/animation.h)4
-rw-r--r--include/gfx/scene/camera.h (renamed from gfx/include/gfx/scene/camera.h)0
-rw-r--r--include/gfx/scene/model.h (renamed from gfx/include/gfx/scene/model.h)0
-rw-r--r--include/gfx/scene/node.h (renamed from gfx/include/gfx/scene/node.h)4
-rw-r--r--include/gfx/scene/object.h (renamed from gfx/include/gfx/scene/object.h)0
-rw-r--r--include/gfx/scene/scene.h (renamed from gfx/include/gfx/scene/scene.h)0
-rw-r--r--include/gfx/sizes.h (renamed from gfx/include/gfx/sizes.h)24
-rw-r--r--include/gfx/util/geometry.h (renamed from gfx/include/gfx/util/geometry.h)0
-rw-r--r--include/gfx/util/ibl.h (renamed from gfx/include/gfx/util/ibl.h)0
-rw-r--r--include/gfx/util/shader.h (renamed from gfx/include/gfx/util/shader.h)0
-rw-r--r--include/gfx/util/skyquad.h (renamed from gfx/include/gfx/util/skyquad.h)0
-rw-r--r--shaders/brdf_integration_map.frag (renamed from gfx/shaders/brdf_integration_map.frag)0
-rw-r--r--shaders/cook_torrance.frag (renamed from gfx/shaders/cook_torrance.frag)0
-rw-r--r--shaders/cook_torrance.vert (renamed from gfx/shaders/cook_torrance.vert)0
-rw-r--r--shaders/cubemap_filtering.vert (renamed from gfx/shaders/cubemap_filtering.vert)0
-rw-r--r--shaders/debug3d.frag (renamed from gfx/shaders/debug3d.frag)0
-rw-r--r--shaders/debug3d.vert (renamed from gfx/shaders/debug3d.vert)0
-rw-r--r--shaders/immediate_mode.frag (renamed from gfx/shaders/immediate_mode.frag)0
-rw-r--r--shaders/immediate_mode.vert (renamed from gfx/shaders/immediate_mode.vert)0
-rw-r--r--shaders/irradiance_map.frag (renamed from gfx/shaders/irradiance_map.frag)0
-rw-r--r--shaders/prefiltered_environment_map.frag (renamed from gfx/shaders/prefiltered_environment_map.frag)0
-rw-r--r--shaders/quad.vert (renamed from gfx/shaders/quad.vert)0
-rw-r--r--shaders/skyquad.frag (renamed from gfx/shaders/skyquad.frag)0
-rw-r--r--shaders/skyquad.vert (renamed from gfx/shaders/skyquad.vert)0
-rw-r--r--shaders/view_normal_mapped_normals.frag (renamed from gfx/shaders/view_normal_mapped_normals.frag)0
-rw-r--r--shaders/view_normal_mapped_normals.vert (renamed from gfx/shaders/view_normal_mapped_normals.vert)0
-rw-r--r--shaders/view_normals.frag (renamed from gfx/shaders/view_normals.frag)0
-rw-r--r--shaders/view_normals.vert (renamed from gfx/shaders/view_normals.vert)0
-rw-r--r--shaders/view_tangents.frag (renamed from gfx/shaders/view_tangents.frag)0
-rw-r--r--shaders/view_tangents.vert (renamed from gfx/shaders/view_tangents.vert)0
-rw-r--r--shaders/view_texture.frag (renamed from gfx/shaders/view_texture.frag)0
-rw-r--r--shaders/view_texture.vert (renamed from gfx/shaders/view_texture.vert)0
-rw-r--r--src/asset/asset_cache.c (renamed from gfx/src/asset/asset_cache.c)2
-rw-r--r--src/asset/asset_cache.h (renamed from gfx/src/asset/asset_cache.h)0
-rw-r--r--src/asset/model.c (renamed from gfx/src/asset/model.c)116
-rw-r--r--src/asset/model.h (renamed from gfx/src/asset/model.h)0
-rw-r--r--src/asset/texture.c (renamed from gfx/src/asset/texture.c)32
-rw-r--r--src/asset/texture.h (renamed from gfx/src/asset/texture.h)0
-rw-r--r--src/core/buffer.c (renamed from gfx/src/core/buffer.c)0
-rw-r--r--src/core/buffer.h (renamed from gfx/src/core/buffer.h)0
-rw-r--r--src/core/constants.h (renamed from gfx/src/core/constants.h)0
-rw-r--r--src/core/core.c (renamed from gfx/src/core/core.c)4
-rw-r--r--src/core/core_impl.h (renamed from gfx/src/core/core_impl.h)0
-rw-r--r--src/core/framebuffer.c (renamed from gfx/src/core/framebuffer.c)0
-rw-r--r--src/core/framebuffer.h (renamed from gfx/src/core/framebuffer.h)0
-rw-r--r--src/core/geometry.c (renamed from gfx/src/core/geometry.c)0
-rw-r--r--src/core/geometry.h (renamed from gfx/src/core/geometry.h)0
-rw-r--r--src/core/gl_util.h (renamed from gfx/src/core/gl_util.h)0
-rw-r--r--src/core/renderbuffer.c (renamed from gfx/src/core/renderbuffer.c)0
-rw-r--r--src/core/renderbuffer.h (renamed from gfx/src/core/renderbuffer.h)0
-rw-r--r--src/core/shader.c (renamed from gfx/src/core/shader.c)0
-rw-r--r--src/core/shader.h (renamed from gfx/src/core/shader.h)0
-rw-r--r--src/core/shader_program.c (renamed from gfx/src/core/shader_program.c)0
-rw-r--r--src/core/shader_program.h (renamed from gfx/src/core/shader_program.h)0
-rw-r--r--src/core/texture.c (renamed from gfx/src/core/texture.c)10
-rw-r--r--src/core/texture.h (renamed from gfx/src/core/texture.h)0
-rw-r--r--src/gfx.c (renamed from gfx/src/gfx.c)24
-rw-r--r--src/gfx_assert.h (renamed from gfx/src/gfx_assert.h)0
-rw-r--r--src/llr/light.c (renamed from gfx/src/scene/light.c)4
-rw-r--r--src/llr/light_impl.h (renamed from gfx/src/scene/light_impl.h)4
-rw-r--r--src/llr/llr.c381
-rw-r--r--src/llr/llr_impl.h83
-rw-r--r--src/llr/material.c (renamed from gfx/src/scene/material.c)4
-rw-r--r--src/llr/material_impl.h15
-rw-r--r--src/llr/mesh.c (renamed from gfx/src/scene/mesh.c)2
-rw-r--r--src/llr/mesh_impl.h (renamed from gfx/src/scene/mesh_impl.h)2
-rw-r--r--src/renderer/imm_renderer.c192
-rw-r--r--src/renderer/imm_renderer_impl.h (renamed from gfx/src/renderer/imm_renderer_impl.h)29
-rw-r--r--src/renderer/renderer.c247
-rw-r--r--src/renderer/renderer_impl.h (renamed from gfx/src/renderer/renderer_impl.h)10
-rw-r--r--src/scene/animation.c (renamed from gfx/src/scene/animation.c)0
-rw-r--r--src/scene/animation_impl.h (renamed from gfx/src/scene/animation_impl.h)0
-rw-r--r--src/scene/camera.c (renamed from gfx/src/scene/camera.c)0
-rw-r--r--src/scene/camera_impl.h (renamed from gfx/src/scene/camera_impl.h)0
-rw-r--r--src/scene/model.c (renamed from gfx/src/scene/model.c)0
-rw-r--r--src/scene/model_impl.h (renamed from gfx/src/scene/model_impl.h)0
-rw-r--r--src/scene/node.c (renamed from gfx/src/scene/node.c)2
-rw-r--r--src/scene/node_impl.h (renamed from gfx/src/scene/node_impl.h)0
-rw-r--r--src/scene/object.c (renamed from gfx/src/scene/object.c)2
-rw-r--r--src/scene/object_impl.h (renamed from gfx/src/scene/object_impl.h)7
-rw-r--r--src/scene/scene.c (renamed from gfx/src/scene/scene.c)0
-rw-r--r--src/scene/scene_graph.h (renamed from gfx/src/scene/scene_graph.h)42
-rw-r--r--src/scene/scene_impl.h (renamed from gfx/src/scene/scene_impl.h)0
-rw-r--r--src/scene/scene_memory.c (renamed from gfx/src/scene/scene_memory.c)67
-rw-r--r--src/scene/scene_memory.h (renamed from gfx/src/scene/scene_memory.h)0
-rw-r--r--src/scene/types.h (renamed from gfx/src/scene/types.h)0
-rw-r--r--src/util/geometry.c (renamed from gfx/src/util/geometry.c)0
-rw-r--r--src/util/ibl.c (renamed from gfx/src/util/ibl.c)0
-rw-r--r--src/util/shader.c (renamed from gfx/src/util/shader.c)0
-rw-r--r--src/util/skyquad.c (renamed from gfx/src/util/skyquad.c)17
158 files changed, 1447 insertions, 4141 deletions
diff --git a/CMakeLists.txt b/CMakeLists.txt
index 25c7560..c480daf 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -1,8 +1,96 @@
1cmake_minimum_required(VERSION 3.0) 1cmake_minimum_required(VERSION 3.5)
2 2
3project(gfx-all) 3add_subdirectory(contrib/cgltf)
4add_subdirectory(contrib/cgltf-tangents)
5add_subdirectory(contrib/stb)
4 6
5add_subdirectory(app) 7add_subdirectory(app)
6add_subdirectory(gfx) 8
7add_subdirectory(gfx-iso) 9project(gfx)
8add_subdirectory(game) 10
11include(cmake/shader.txt)
12
13set(CMAKE_C_STANDARD 23)
14set(CMAKE_C_STANDARD_REQUIRED On)
15set(CMAKE_C_EXTENSIONS Off)
16
17add_shader_library(shaders
18 shaders/brdf_integration_map.frag
19 shaders/cook_torrance.frag
20 shaders/cook_torrance.vert
21 shaders/cubemap_filtering.vert
22 shaders/debug3d.frag
23 shaders/debug3d.vert
24 shaders/immediate_mode.frag
25 shaders/immediate_mode.vert
26 shaders/irradiance_map.frag
27 shaders/prefiltered_environment_map.frag
28 shaders/quad.vert
29 shaders/skyquad.frag
30 shaders/skyquad.vert
31 shaders/view_normal_mapped_normals.frag
32 shaders/view_normal_mapped_normals.vert
33 shaders/view_normals.frag
34 shaders/view_normals.vert
35 shaders/view_tangents.frag
36 shaders/view_tangents.vert
37 shaders/view_texture.frag
38 shaders/view_texture.vert)
39
40add_library(gfx SHARED
41 src/asset/asset_cache.c
42 src/asset/model.c
43 src/asset/texture.c
44 src/core/buffer.c
45 src/core/core.c
46 src/core/framebuffer.c
47 src/core/geometry.c
48 src/core/renderbuffer.c
49 src/core/shader_program.c
50 src/core/shader.c
51 src/core/texture.c
52 src/llr/llr.c
53 src/llr/light.c
54 src/llr/material.c
55 src/llr/mesh.c
56 src/renderer/imm_renderer.c
57 src/renderer/renderer.c
58 src/scene/animation.c
59 src/scene/camera.c
60 src/scene/model.c
61 src/scene/node.c
62 src/scene/object.c
63 src/scene/scene.c
64 src/scene/scene_memory.c
65 src/gfx.c
66 src/util/geometry.c
67 src/util/ibl.c
68 src/util/shader.c
69 src/util/skyquad.c)
70
71target_include_directories(gfx PUBLIC
72 include)
73
74target_include_directories(gfx PRIVATE
75 src)
76
77target_compile_options(gfx PRIVATE -std=gnu11 -Wall -Wextra -Wpedantic)
78
79target_link_libraries(gfx PUBLIC
80 cstring
81 math)
82
83target_link_libraries(gfx PRIVATE
84 cassert
85 cgltf
86 cgltf-tangents
87 error
88 gfx-app
89 log
90 mempool
91 shaders
92 stb
93 # System libraries.
94 GL
95 # Required to initialize GLAD.
96 -ldl)
diff --git a/gfx/README.md b/README.md
index f0b103d..491761d 100644
--- a/gfx/README.md
+++ b/README.md
@@ -20,32 +20,35 @@ educational purposes.
20### Gfx 20### Gfx
21 21
22The `Gfx` object represents the graphics subsystem and is at the center of the 22The `Gfx` object represents the graphics subsystem and is at the center of the
23library's high-level API. The `Gfx` object exposes a `Render` backend and a 23library's high-level API. The `Gfx` object exposes a render backend (`GfxCore`)
24`Renderer` and allows the caller to create `Scene`s. 24and a `Renderer`, and allows the caller to create `Scene`s.
25 25
26### Render Backend 26### Render Backend
27 27
28The `Render` backend is a thin abstraction layer over low-level graphics APIs 28The render backend (`GfxCore`) is a thin abstraction layer over low-level
29like OpenGL or Vulkan. It holds GPU resources such as geometry, textures, 29graphics APIs like OpenGL or Vulkan. It holds GPU resources such as geometry,
30shaders, etc, and exposes functions to manipulate them. 30textures, shaders, etc, and exposes functions to manipulate them.
31 31
32Currently there is only one implementation of the `Render` backend based on 32Currently, there is only one implementation of the render backend based on
33OpenGL. 33OpenGL.
34 34
35#### Ownership 35#### Ownership
36 36
37The `Render` backend owns all rendering resources: buffers, geometries, 37The render backend owns all rendering resources: buffers, geometries, textures,
38textures, shaders, etc. Even resources that point to other resources do not own 38shaders, etc. Even resources that point to other resources do not own those
39those other resources (geometries pointing to buffers). 39other resources (geometries pointing to buffers).
40 40
41There is no ownership tracking in the render backend. It is up to the client to 41There is no lifetime tracking in the render backend. It is up to the client to
42manage resource lifetime. 42manage resource lifetime.
43 43
44### Low-level Renderer
45
46`ImmRenderer` is a low-level, immediate mode renderer.
47
44### Scene 48### Scene
45 49
46A `Scene` encapsulates a scene graph. A scene graph contains the elements that 50A `Scene` graph contains the elements that make up a scene. The current scene
47make up a scene: nodes, cameras, lights, objects, etc. The current scene graph 51graph implementation includes:
48implementation includes:
49 52
50- Camera 53- Camera
51- Light 54- Light
@@ -64,7 +67,8 @@ functions to set the parent node of an object/camera/light. If we exposed the
64former, the API could create the illusion that the hierarchy can be a DAG. 67former, the API could create the illusion that the hierarchy can be a DAG.
65 68
66The strict tree hierarchy should not be that restrictive in practice. Even the 69The strict tree hierarchy should not be that restrictive in practice. Even the
67glTF 2.0 spec [enforces this](https://github.com/KhronosGroup/glTF/blob/master/specification/2.0/README.md#nodes-and-hierarchy): 70glTF 2.0
71spec [enforces this](https://github.com/KhronosGroup/glTF/blob/master/specification/2.0/README.md#nodes-and-hierarchy):
68 72
69> *For Version 2.0 conformance, the glTF node hierarchy is not a directed 73> *For Version 2.0 conformance, the glTF node hierarchy is not a directed
70> acyclic graph (DAG) or scene graph, but a disjoint union of strict trees. That 74> acyclic graph (DAG) or scene graph, but a disjoint union of strict trees. That
@@ -76,9 +80,9 @@ glTF 2.0 spec [enforces this](https://github.com/KhronosGroup/glTF/blob/master/s
76Two use cases for instancing seem to be: 80Two use cases for instancing seem to be:
77 81
781. Creating N identical clones, but each with a unique transform. (Ex: N 821. Creating N identical clones, but each with a unique transform. (Ex: N
79animated characters animated in unison but located in different locations.) 83 animated characters animated in unison but located in different locations.)
802. Creating N copies of a sub-tree, each now being their own unique tree. (Ex: 842. Creating N copies of a sub-tree, each now being their own unique tree. (Ex:
81The same N animated characters, but each of them now being animated separately.) 85 The same N animated characters, but each of them now being animated separately.)
82 86
83Some scene graphs 87Some scene graphs
84([Panda3D](https://docs.panda3d.org/1.10/python/programming/scene-graph/instancing)) 88([Panda3D](https://docs.panda3d.org/1.10/python/programming/scene-graph/instancing))
@@ -87,10 +91,10 @@ to have multiple parents. This turns the scene graph into a DAG and adds a
87number of complications for us: 91number of complications for us:
88 92
891. Shared ownership of children. We would now need some sort of ref counting or 931. Shared ownership of children. We would now need some sort of ref counting or
90deferred GC to delete nodes and their subtrees. 94 deferred GC to delete nodes and their subtrees.
912. Nodes no longer have a unique parent. 952. Nodes no longer have a unique parent.
923. Given a node, we can no longer determine its location (which parent link do 963. Given a node, we can no longer determine its location (which parent link do
93you follow?), or any attribute that is derived from its parent(s). 97 you follow?), or any attribute that is derived from its parent(s).
94 98
95In our case, we stick to strict tree hierarchies. 99In our case, we stick to strict tree hierarchies.
96 100
@@ -131,6 +135,10 @@ Code under `util/` provides functionality that need not be in the core part
131of the library (Gfx, render backend, scene or renderer). This includes functions 135of the library (Gfx, render backend, scene or renderer). This includes functions
132to compute irradiance maps, create procedural geometry, etc. 136to compute irradiance maps, create procedural geometry, etc.
133 137
138### Memory management
139
140TODO
141
134## Ideas for Future Work 142## Ideas for Future Work
135 143
136- Render graphs to allow for custom multi-pass rendering algorithms. 144- Render graphs to allow for custom multi-pass rendering algorithms.
diff --git a/app/src/app.c b/app/src/app.c
index 9b816ee..58d7b90 100644
--- a/app/src/app.c
+++ b/app/src/app.c
@@ -112,6 +112,9 @@ bool gfx_app_run(const GfxAppDesc* desc, const GfxAppCallbacks* callbacks) {
112 } 112 }
113 glfwMakeContextCurrent(g_gfx_app.window); 113 glfwMakeContextCurrent(g_gfx_app.window);
114 114
115 // Request adaptive sync if supported.
116 glfwSwapInterval(-1);
117
115 // Load GL before calling the application init clalback. 118 // Load GL before calling the application init clalback.
116 if (!gladLoadGL()) { 119 if (!gladLoadGL()) {
117 LOGE("Failed loading glad!"); 120 LOGE("Failed loading glad!");
diff --git a/gfx/bin/txt2c.py b/bin/txt2c.py
index 1d8ff70..1d8ff70 100755
--- a/gfx/bin/txt2c.py
+++ b/bin/txt2c.py
diff --git a/gfx/cmake/shader.txt b/cmake/shader.txt
index 8273f4d..8273f4d 100644
--- a/gfx/cmake/shader.txt
+++ b/cmake/shader.txt
diff --git a/gfx/cmake/txt2c.txt b/cmake/txt2c.txt
index 0cb11a9..0cb11a9 100644
--- a/gfx/cmake/txt2c.txt
+++ b/cmake/txt2c.txt
diff --git a/gfx/contrib/cgltf-tangents/CMakeLists.txt b/contrib/cgltf-tangents/CMakeLists.txt
index 2c0771e..2c0771e 100644
--- a/gfx/contrib/cgltf-tangents/CMakeLists.txt
+++ b/contrib/cgltf-tangents/CMakeLists.txt
diff --git a/gfx/contrib/cgltf-tangents/LICENSE b/contrib/cgltf-tangents/LICENSE
index 7796e37..7796e37 100644
--- a/gfx/contrib/cgltf-tangents/LICENSE
+++ b/contrib/cgltf-tangents/LICENSE
diff --git a/gfx/contrib/cgltf-tangents/MikkTSpace/README.md b/contrib/cgltf-tangents/MikkTSpace/README.md
index 9fda155..9fda155 100644
--- a/gfx/contrib/cgltf-tangents/MikkTSpace/README.md
+++ b/contrib/cgltf-tangents/MikkTSpace/README.md
diff --git a/gfx/contrib/cgltf-tangents/MikkTSpace/mikktspace.c b/contrib/cgltf-tangents/MikkTSpace/mikktspace.c
index 0342ae0..0342ae0 100644
--- a/gfx/contrib/cgltf-tangents/MikkTSpace/mikktspace.c
+++ b/contrib/cgltf-tangents/MikkTSpace/mikktspace.c
diff --git a/gfx/contrib/cgltf-tangents/MikkTSpace/mikktspace.h b/contrib/cgltf-tangents/MikkTSpace/mikktspace.h
index 52c44a7..52c44a7 100644
--- a/gfx/contrib/cgltf-tangents/MikkTSpace/mikktspace.h
+++ b/contrib/cgltf-tangents/MikkTSpace/mikktspace.h
diff --git a/gfx/contrib/cgltf-tangents/README.md b/contrib/cgltf-tangents/README.md
index 2a68b27..2a68b27 100644
--- a/gfx/contrib/cgltf-tangents/README.md
+++ b/contrib/cgltf-tangents/README.md
diff --git a/gfx/contrib/cgltf-tangents/cgltf_tangents.c b/contrib/cgltf-tangents/cgltf_tangents.c
index 80b1e56..80b1e56 100644
--- a/gfx/contrib/cgltf-tangents/cgltf_tangents.c
+++ b/contrib/cgltf-tangents/cgltf_tangents.c
diff --git a/gfx/contrib/cgltf-tangents/cgltf_tangents.h b/contrib/cgltf-tangents/cgltf_tangents.h
index 79e3502..79e3502 100644
--- a/gfx/contrib/cgltf-tangents/cgltf_tangents.h
+++ b/contrib/cgltf-tangents/cgltf_tangents.h
diff --git a/gfx/contrib/cgltf-tangents/test/CMakeLists.txt b/contrib/cgltf-tangents/test/CMakeLists.txt
index 422c950..422c950 100644
--- a/gfx/contrib/cgltf-tangents/test/CMakeLists.txt
+++ b/contrib/cgltf-tangents/test/CMakeLists.txt
diff --git a/gfx/contrib/cgltf-tangents/test/main.c b/contrib/cgltf-tangents/test/main.c
index 0d70008..0d70008 100644
--- a/gfx/contrib/cgltf-tangents/test/main.c
+++ b/contrib/cgltf-tangents/test/main.c
diff --git a/gfx/contrib/cgltf/CMakeLists.txt b/contrib/cgltf/CMakeLists.txt
index 0ac840a..0ac840a 100644
--- a/gfx/contrib/cgltf/CMakeLists.txt
+++ b/contrib/cgltf/CMakeLists.txt
diff --git a/gfx/contrib/cgltf/LICENSE b/contrib/cgltf/LICENSE
index 0afe8c7..0afe8c7 100644
--- a/gfx/contrib/cgltf/LICENSE
+++ b/contrib/cgltf/LICENSE
diff --git a/gfx/contrib/cgltf/README.md b/contrib/cgltf/README.md
index 3b49d52..3b49d52 100644
--- a/gfx/contrib/cgltf/README.md
+++ b/contrib/cgltf/README.md
diff --git a/gfx/contrib/cgltf/cgltf.h b/contrib/cgltf/cgltf.h
index 077cf36..077cf36 100644
--- a/gfx/contrib/cgltf/cgltf.h
+++ b/contrib/cgltf/cgltf.h
diff --git a/gfx/contrib/cgltf/cgltf_write.h b/contrib/cgltf/cgltf_write.h
index 2096a5b..2096a5b 100644
--- a/gfx/contrib/cgltf/cgltf_write.h
+++ b/contrib/cgltf/cgltf_write.h
diff --git a/gfx/contrib/stb/CMakeLists.txt b/contrib/stb/CMakeLists.txt
index 8cee003..8cee003 100644
--- a/gfx/contrib/stb/CMakeLists.txt
+++ b/contrib/stb/CMakeLists.txt
diff --git a/gfx/contrib/stb/stb_image.h b/contrib/stb/stb_image.h
index 97038e6..97038e6 100644
--- a/gfx/contrib/stb/stb_image.h
+++ b/contrib/stb/stb_image.h
diff --git a/gfx/doc/extern/2013SiggraphPresentationsNotes-26915738.pdf b/doc/extern/2013SiggraphPresentationsNotes-26915738.pdf
index 989658e..989658e 100644
--- a/gfx/doc/extern/2013SiggraphPresentationsNotes-26915738.pdf
+++ b/doc/extern/2013SiggraphPresentationsNotes-26915738.pdf
Binary files differ
diff --git a/gfx/doc/extern/Scene Graph - CSE 167.pdf b/doc/extern/Scene Graph - CSE 167.pdf
index 5fbbb10..5fbbb10 100644
--- a/gfx/doc/extern/Scene Graph - CSE 167.pdf
+++ b/doc/extern/Scene Graph - CSE 167.pdf
Binary files differ
diff --git a/gfx/doc/gfx.png b/doc/gfx.png
index e64f6e1..e64f6e1 100644
--- a/gfx/doc/gfx.png
+++ b/doc/gfx.png
Binary files differ
diff --git a/gfx/doc/gfx.txt b/doc/gfx.txt
index d3ce01b..d3ce01b 100644
--- a/gfx/doc/gfx.txt
+++ b/doc/gfx.txt
diff --git a/gfx/doc/gltfOverview-2.0.0b.png b/doc/gltfOverview-2.0.0b.png
index 6a5bb61..6a5bb61 100644
--- a/gfx/doc/gltfOverview-2.0.0b.png
+++ b/doc/gltfOverview-2.0.0b.png
Binary files differ
diff --git a/gfx/doc/pipeline.png b/doc/pipeline.png
index 426f39e..426f39e 100644
--- a/gfx/doc/pipeline.png
+++ b/doc/pipeline.png
Binary files differ
diff --git a/gfx/doc/pipeline.txt b/doc/pipeline.txt
index 51523d6..51523d6 100644
--- a/gfx/doc/pipeline.txt
+++ b/doc/pipeline.txt
diff --git a/gfx/doc/renderer.png b/doc/renderer.png
index d0516b0..d0516b0 100644
--- a/gfx/doc/renderer.png
+++ b/doc/renderer.png
Binary files differ
diff --git a/gfx/doc/renderer.txt b/doc/renderer.txt
index 90b18f8..90b18f8 100644
--- a/gfx/doc/renderer.txt
+++ b/doc/renderer.txt
diff --git a/gfx/doc/scene.png b/doc/scene.png
index 85d2447..85d2447 100644
--- a/gfx/doc/scene.png
+++ b/doc/scene.png
Binary files differ
diff --git a/gfx/doc/scene.txt b/doc/scene.txt
index a771488..a771488 100644
--- a/gfx/doc/scene.txt
+++ b/doc/scene.txt
diff --git a/game/CMakeLists.txt b/game/CMakeLists.txt
deleted file mode 100644
index 3a88bb7..0000000
--- a/game/CMakeLists.txt
+++ /dev/null
@@ -1,22 +0,0 @@
1cmake_minimum_required(VERSION 3.0)
2
3add_subdirectory(src/plugins)
4
5project(game)
6
7add_executable(game
8 src/game.c)
9
10target_include_directories(game PRIVATE
11 src/)
12
13target_link_libraries(game PRIVATE
14 cstring
15 error
16 gfx
17 gfx-app
18 list
19 log
20 math
21 mempool
22 plugin)
diff --git a/game/src/game.c b/game/src/game.c
deleted file mode 100644
index dc2248b..0000000
--- a/game/src/game.c
+++ /dev/null
@@ -1,223 +0,0 @@
1/*
2 * Main game module with entry point and game loop.
3 *
4 * The game module sets up the window and GL context and defers the core game
5 * logic to a plugin.
6 */
7#define _GNU_SOURCE 200112L // For readlink()
8
9#include "game.h"
10
11#include "plugins/plugin.h"
12
13#include <gfx/app.h>
14#include <gfx/core.h>
15#include <gfx/gfx.h>
16#include <gfx/renderer.h>
17#include <gfx/scene/camera.h>
18#include <gfx/scene/node.h>
19#include <gfx/scene/object.h>
20#include <gfx/scene/scene.h>
21
22#include <error.h>
23#include <log/log.h>
24#include <math/camera.h>
25#include <plugin.h>
26
27#include <assert.h>
28#include <stdbool.h>
29#include <stdio.h>
30#include <stdlib.h>
31
32#include <linux/limits.h>
33
34#include <unistd.h>
35
36#undef _GNU_SOURCE
37
38static const int WIDTH = 1350;
39static const int HEIGHT = 900;
40static const int MAX_FPS = 60;
41
42typedef struct GfxAppState {
43 Game game;
44} GfxAppState;
45
46/// Initialize the game's plugin.
47static bool init_plugin(Game* game) {
48 assert(game);
49 assert(game->plugin);
50 // Plugin state is allowed to be null, either when the plugin does not
51 // expose an init() or when init() does not initialize a state.
52 if (plugin_resolve(game->plugin, plugin_init, "init")) {
53 State* plugin_state = 0;
54 if (!plugin_call(game->plugin, plugin_init, "init", game, &plugin_state)) {
55 return false;
56 }
57 set_plugin_state(game->plugin, plugin_state);
58 }
59 return true; // Plugin does not need to expose an init().
60}
61
62/// Shutdown the game's plugin.
63/// The game's plugin is allowed to be null in the call to this function.
64static void shutdown_plugin(Game* game) {
65 assert(game);
66 if (game->plugin &&
67 (plugin_resolve(game->plugin, plugin_shutdown, "shutdown"))) {
68 void* plugin_state = get_plugin_state(game->plugin);
69 plugin_call(game->plugin, plugin_shutdown, "shutdown", game, plugin_state);
70 set_plugin_state(game->plugin, 0);
71 }
72}
73
74/// Boot the game's plugin.
75static bool boot_plugin(Game* game) {
76 assert(game);
77 assert(game->plugin);
78 if (plugin_resolve(game->plugin, plugin_boot, "boot")) {
79 void* plugin_state = get_plugin_state(game->plugin);
80 return plugin_call(game->plugin, plugin_boot, "boot", game, plugin_state);
81 }
82 return true; // Plugin does not need to expose a boot().
83}
84
85/// Update the plugin's state.
86static void update_plugin(Game* game, double t, double dt) {
87 assert(game);
88 assert(game->plugin);
89 if (plugin_resolve(game->plugin, plugin_update, "update")) {
90 void* plugin_state = get_plugin_state(game->plugin);
91 plugin_call(
92 game->plugin, plugin_update, "update", game, plugin_state, t, dt);
93 }
94}
95
96/// Plugin render.
97static void render_plugin(const Game* game) {
98 assert(game);
99 assert(game->plugin);
100 if (plugin_resolve(game->plugin, plugin_render, "render")) {
101 void* plugin_state = get_plugin_state(game->plugin);
102 plugin_call(game->plugin, plugin_render, "render", game, plugin_state);
103 }
104}
105
106/// Plugin resize.
107static void resize_plugin(Game* game, int width, int height) {
108 assert(game);
109 assert(game->plugin);
110 if (plugin_resolve(game->plugin, plugin_resize, "resize")) {
111 void* plugin_state = get_plugin_state(game->plugin);
112 plugin_call(
113 game->plugin, plugin_resize, "resize", game, plugin_state, width,
114 height);
115 }
116}
117
118static void Shutdown(Game* game);
119
120static bool Init(Game* game, int argc, const char** argv) {
121 assert(game);
122
123 if (argc <= 1) {
124 LOGE("Usage: %s <plugin> [plugin args]", argv[0]);
125 return false;
126 }
127
128 // Syntax: game <plugin> [plugin args]
129 //
130 // Here we consume the <plugin> arg so that plugins receive the remainder
131 // args starting from 0.
132 game->argc = argc - 1;
133 game->argv = argv + 1;
134
135 char exe_path_buf[NAME_MAX] = {0};
136 if (readlink("/proc/self/exe", exe_path_buf, sizeof(exe_path_buf)) == -1) {
137 LOGE("readlink(/proc/self/exe) failed");
138 goto cleanup;
139 }
140
141 // Replace the last / with a null terminator to remove the exe file from the
142 // path. This gets the file's parent directory.
143 *strrchr(exe_path_buf, '/') = 0;
144
145 const mstring exe_dir = mstring_make(exe_path_buf);
146 const mstring plugins_path = mstring_concat_cstr(exe_dir, "/src/plugins");
147
148 if (!(game->plugin_engine = new_plugin_engine(
149 &(PluginEngineDesc){.plugins_dir = mstring_cstr(&plugins_path)}))) {
150 goto cleanup;
151 }
152
153 const char* plugin = argv[1];
154 if (!(game->plugin = load_plugin(game->plugin_engine, plugin))) {
155 goto cleanup;
156 }
157
158 if (!(game->gfx = gfx_init())) {
159 goto cleanup;
160 }
161
162 if (!init_plugin(game)) {
163 goto cleanup;
164 }
165 if (!boot_plugin(game)) {
166 goto cleanup;
167 }
168
169 return true;
170
171cleanup:
172 LOGE("Gfx error: %s", get_error());
173 Shutdown(game);
174 return false;
175}
176
177static void Shutdown(Game* game) {
178 assert(game);
179 shutdown_plugin(game);
180 if (game->gfx) {
181 gfx_destroy(&game->gfx);
182 }
183 if (game->plugin) {
184 delete_plugin(&game->plugin);
185 }
186 if (game->plugin_engine) {
187 delete_plugin_engine(&game->plugin_engine);
188 }
189}
190
191static void Update(Game* game, double t, double dt) {
192 plugin_engine_update(game->plugin_engine);
193 if (plugin_reloaded(game->plugin)) {
194 shutdown_plugin(game);
195 const bool result = init_plugin(game);
196 assert(result); // TODO: handle error better.
197
198 // Trigger a resize just like the initial resize that occurs when the gfx
199 // application starts.
200 resize_plugin(game, game->width, game->height);
201 }
202
203 update_plugin(game, t, dt);
204}
205
206static void Render(const Game* game) {
207 GfxCore* gfxcore = gfx_get_core(game->gfx);
208 gfx_start_frame(gfxcore);
209 render_plugin(game);
210 gfx_end_frame(gfxcore);
211}
212
213static void Resize(Game* game, int width, int height) {
214 game->width = width;
215 game->height = height;
216
217 GfxCore* gfxcore = gfx_get_core(game->gfx);
218 gfx_set_viewport(gfxcore, 0, 0, width, height);
219
220 resize_plugin(game, width, height);
221}
222
223GFX_APP_MAIN(WIDTH, HEIGHT, MAX_FPS, "Game");
diff --git a/game/src/game.h b/game/src/game.h
deleted file mode 100644
index 579ba3c..0000000
--- a/game/src/game.h
+++ /dev/null
@@ -1,21 +0,0 @@
1/*
2 * Header file defining the game state, included by plugins.
3 */
4#pragma once
5
6typedef struct PluginEngine PluginEngine;
7typedef struct Plugin Plugin;
8typedef struct Gfx Gfx;
9typedef struct Scene Scene;
10typedef struct SceneCamera SceneCamera;
11
12/// Game state.
13typedef struct {
14 int argc;
15 const char** argv;
16 PluginEngine* plugin_engine;
17 Plugin* plugin;
18 Gfx* gfx;
19 int width;
20 int height;
21} Game;
diff --git a/game/src/plugins/CMakeLists.txt b/game/src/plugins/CMakeLists.txt
deleted file mode 100644
index 8661598..0000000
--- a/game/src/plugins/CMakeLists.txt
+++ /dev/null
@@ -1,29 +0,0 @@
1cmake_minimum_required(VERSION 3.0)
2
3project(plugins)
4
5set(LINK_LIBRARIES cstring math gfx gfx-app)
6
7# Viewer
8
9add_library(viewer SHARED
10 viewer.c)
11
12target_link_libraries(viewer PUBLIC
13 ${LINK_LIBRARIES})
14
15# Texture viewer
16
17add_library(texture_view SHARED
18 texture_view.c)
19
20target_link_libraries(texture_view PUBLIC
21 ${LINK_LIBRARIES})
22
23# Pong
24
25add_library(pong SHARED
26 pong.c)
27
28target_link_libraries(pong PUBLIC
29 ${LINK_LIBRARIES})
diff --git a/game/src/plugins/plugin.h b/game/src/plugins/plugin.h
deleted file mode 100644
index f7219c6..0000000
--- a/game/src/plugins/plugin.h
+++ /dev/null
@@ -1,52 +0,0 @@
1/*
2 * Game plugin.
3 */
4#pragma once
5
6#include "../game.h"
7
8#include <gfx/gfx.h>
9#include <gfx/scene.h>
10
11#include <stdbool.h>
12
13typedef struct State State;
14
15/// Initialize the plugin, which may optionally return a state object.
16///
17/// This function is called every time the plugin is (re)loaded.
18///
19/// It is assumed that the plugin's state is fully encapsulated in the returned
20/// state object. The plugin should not store any (mutable) state outside of the
21/// returned state object (e.g., no mutable global variables.)
22bool init(Game*, State**);
23
24/// Shut down the plugin.
25///
26/// This function is called before the plugin is unloaded.
27///
28/// The plugin should perform any destruction needed, but not free the state
29/// object; freeing the state object's memory is handled by the caller.
30void shutdown(Game*, State*);
31
32/// Function called the first time the plugin is loaded throughout the
33/// application's lifetime. This allows the plugin to do one-time initialization
34/// of the game state.
35bool boot(Game*, State*);
36
37/// Update the plugin's and the game's state.
38void update(Game*, State*, double t, double dt);
39
40/// Render hook.
41void render(const Game*, const State*);
42
43/// Called when the game's window is resized.
44void resize(Game*, State*, int width, int height);
45
46// Signatures for the plugin's exposed functions.
47typedef bool (*plugin_init)(Game*, State**);
48typedef bool (*plugin_shutdown)(Game*, State*);
49typedef bool (*plugin_boot)(Game*, State*);
50typedef void (*plugin_update)(Game*, State*, double t, double dt);
51typedef void (*plugin_render)(const Game*, const State*);
52typedef void (*plugin_resize)(Game* game, State* state, int width, int height);
diff --git a/game/src/plugins/pong.c b/game/src/plugins/pong.c
deleted file mode 100644
index c1c55be..0000000
--- a/game/src/plugins/pong.c
+++ /dev/null
@@ -1,237 +0,0 @@
1#include "plugin.h"
2
3#include <gfx/app.h>
4#include <gfx/gfx.h>
5#include <gfx/renderer.h>
6
7#include <math/mat4.h>
8#include <math/vec2.h>
9#include <math/vec4.h>
10
11#include <stdlib.h>
12
13static const vec2 PAD_SIZE = (vec2){120, 20};
14static const R PLAYER_Y_OFFSET = 50;
15static const R PLAYER_SPEED = 800;
16
17static const R ENEMY_SPEED = 2;
18
19static const R BALL_SIZE = 18;
20static const R BALL_SPEED = 360; // In each dimension.
21
22static const R EPS = (R)1e-3;
23
24typedef struct Player {
25 vec2 position;
26} Player;
27
28typedef struct Ball {
29 vec2 position;
30 vec2 velocity;
31} Ball;
32
33typedef struct State {
34 bool game_started;
35 Player human;
36 Player enemy;
37 Ball ball;
38 mat4 viewProjection;
39} State;
40
41bool init(Game* game, State** pp_state) {
42 assert(game);
43
44 State* state = calloc(1, sizeof(State));
45 if (!state) {
46 return false;
47 }
48
49 *pp_state = state;
50 return true;
51
52cleanup:
53 free(state);
54 return false;
55}
56
57void shutdown(Game* game, State* state) {
58 assert(game);
59 assert(state);
60}
61
62static void move_ball(Ball* ball, R dt, int width, int height) {
63 assert(ball);
64
65 const R offset = BALL_SIZE / 2;
66
67 ball->position = vec2_add(ball->position, vec2_scale(ball->velocity, dt));
68
69 // Right wall.
70 if (ball->position.x + offset > (R)width) {
71 ball->position.x = (R)width - offset - EPS;
72 ball->velocity.x = -ball->velocity.x;
73 }
74 // Left wall.
75 else if (ball->position.x - offset < 0) {
76 ball->position.x = offset + EPS;
77 ball->velocity.x = -ball->velocity.x;
78 }
79 // Top wall.
80 if (ball->position.y + offset > (R)height) {
81 ball->position.y = (R)height - offset - EPS;
82 ball->velocity.y = -ball->velocity.y;
83 }
84 // Bottom wall.
85 else if (ball->position.y - offset < 0) {
86 ball->position.y = offset + EPS;
87 ball->velocity.y = -ball->velocity.y;
88 }
89}
90
91void move_enemy_player(int width, Player* player, R t) {
92 const R half_width = (R)width / 2;
93 const R amplitude = half_width - (PAD_SIZE.x / 2);
94 player->position.x = half_width + amplitude * sinf(t * ENEMY_SPEED);
95}
96
97void move_human_player(Player* player, R dt) {
98 assert(player);
99
100 R speed = 0;
101 if (gfx_app_is_key_pressed('a')) {
102 speed -= PLAYER_SPEED;
103 }
104 if (gfx_app_is_key_pressed('d')) {
105 speed += PLAYER_SPEED;
106 }
107
108 player->position.x += speed * dt;
109}
110
111void clamp_player(Player* player, int width) {
112 assert(player);
113
114 const R offset = PAD_SIZE.x / 2;
115
116 // Left wall.
117 if (player->position.x + offset > (R)width) {
118 player->position.x = (R)width - offset;
119 }
120 // Right wall.
121 else if (player->position.x - offset < 0) {
122 player->position.x = offset;
123 }
124}
125
126void collide_ball(vec2 old_ball_position, const Player* player, Ball* ball) {
127 assert(player);
128 assert(ball);
129
130 // Discrete but simple collision. Checks for intersection and moves the ball
131 // back by a small epsilon.
132
133 // Player bounding box.
134 const vec2 player_pmin = vec2_make(
135 player->position.x - PAD_SIZE.x / 2, player->position.y - PAD_SIZE.y / 2);
136 const vec2 player_pmax = vec2_make(
137 player->position.x + PAD_SIZE.x / 2, player->position.y + PAD_SIZE.y / 2);
138
139 // Ball bounding box.
140 const vec2 ball_pmin = vec2_make(
141 ball->position.x - BALL_SIZE / 2, ball->position.y - BALL_SIZE / 2);
142 const vec2 ball_pmax = vec2_make(
143 ball->position.x + BALL_SIZE / 2, ball->position.y + BALL_SIZE / 2);
144
145 // Check for intersection and update ball.
146 if (!((ball_pmax.x < player_pmin.x) || (ball_pmin.x > player_pmax.x) ||
147 (ball_pmax.y < player_pmin.y) || (ball_pmin.y > player_pmax.y))) {
148 ball->position =
149 vec2_add(old_ball_position, vec2_scale(ball->velocity, -EPS));
150 ball->velocity.y = -ball->velocity.y;
151 }
152}
153
154void update(Game* game, State* state, double t, double dt) {
155 assert(game);
156 assert(state);
157
158 // TODO: Move game width/height to GfxApp query functions?
159 const vec2 old_ball_position = state->ball.position;
160 move_ball(&state->ball, (R)dt, game->width, game->height);
161 move_human_player(&state->human, (R)dt);
162 move_enemy_player(game->width, &state->enemy, (R)t);
163 clamp_player(&state->human, game->width);
164 collide_ball(old_ball_position, &state->human, &state->ball);
165 collide_ball(old_ball_position, &state->enemy, &state->ball);
166}
167
168static void draw_player(ImmRenderer* imm, const Player* player) {
169 assert(imm);
170 assert(player);
171
172 const vec2 half_box = vec2_div(PAD_SIZE, vec2_make(2, 2));
173
174 const vec2 pmin = vec2_sub(player->position, half_box);
175 const vec2 pmax = vec2_add(player->position, half_box);
176 const aabb2 box = aabb2_make(pmin, pmax);
177
178 gfx_imm_draw_aabb2(imm, box);
179}
180
181static void draw_ball(ImmRenderer* imm, const Ball* ball) {
182 assert(imm);
183 assert(ball);
184
185 const vec2 half_box = vec2_make(BALL_SIZE / 2, BALL_SIZE / 2);
186 const vec2 pmin = vec2_sub(ball->position, half_box);
187 const vec2 pmax = vec2_add(ball->position, half_box);
188 const aabb2 box = aabb2_make(pmin, pmax);
189
190 gfx_imm_draw_aabb2(imm, box);
191}
192
193void render(const Game* game, const State* state) {
194 assert(game);
195 assert(state);
196
197 ImmRenderer* imm = gfx_get_imm_renderer(game->gfx);
198 gfx_imm_start(imm);
199 gfx_imm_set_view_projection_matrix(imm, &state->viewProjection);
200 gfx_imm_load_identity(imm);
201 gfx_imm_set_colour(imm, vec4_make(1, 1, 1, 1));
202 draw_player(imm, &state->human);
203 draw_player(imm, &state->enemy);
204 draw_ball(imm, &state->ball);
205 gfx_imm_end(imm);
206}
207
208static R clamp_to_width(int width, R x, R extent) {
209 return min(x, (R)width - extent);
210}
211
212void resize(Game* game, State* state, int width, int height) {
213 assert(game);
214 assert(state);
215
216 state->viewProjection = mat4_ortho(0, (R)width, 0, (R)height, -1, 1);
217
218 state->human.position.y = PLAYER_Y_OFFSET;
219 state->enemy.position.y = (R)height - PLAYER_Y_OFFSET;
220
221 if (!state->game_started) {
222 state->human.position.x = (R)width / 2;
223 state->enemy.position.x = (R)width / 2;
224
225 state->ball.position =
226 vec2_div(vec2_make((R)width, (R)height), vec2_make(2, 2));
227
228 state->ball.velocity = vec2_make(BALL_SPEED, BALL_SPEED);
229
230 state->game_started = true;
231 } else {
232 state->human.position.x =
233 clamp_to_width(width, state->human.position.x, PAD_SIZE.x / 2);
234 state->enemy.position.x =
235 clamp_to_width(width, state->enemy.position.x, PAD_SIZE.x / 2);
236 }
237}
diff --git a/game/src/plugins/texture_view.c b/game/src/plugins/texture_view.c
deleted file mode 100644
index a8b2a94..0000000
--- a/game/src/plugins/texture_view.c
+++ /dev/null
@@ -1,144 +0,0 @@
1#include "plugin.h"
2
3#include <gfx/asset.h>
4#include <gfx/core.h>
5#include <gfx/renderer.h>
6#include <gfx/scene.h>
7#include <gfx/util/geometry.h>
8#include <gfx/util/shader.h>
9
10#include <math/camera.h>
11
12#include <assert.h>
13#include <stdlib.h>
14
15// Default texture to load if no texture is provided.
16static const char* DEFAULT_TEXTURE = "/assets/skybox/clouds1/clouds1_west.bmp";
17// static const char* DEFAULT_TEXTURE = "/assets/checkerboard.jpg";
18
19struct State {
20 Scene* scene;
21 SceneCamera* camera;
22};
23
24bool init(Game* game, State** pp_state) {
25 assert(game);
26 assert(pp_state);
27
28 State* state = calloc(1, sizeof(State));
29 if (!state) {
30 goto cleanup;
31 }
32
33 // Usage: [texture file]
34 const char* texture_file = game->argc > 1 ? game->argv[1] : DEFAULT_TEXTURE;
35
36 GfxCore* gfxcore = gfx_get_core(game->gfx);
37
38 const Texture* texture = gfx_load_texture(
39 game->gfx, &(LoadTextureCmd){
40 .origin = AssetFromFile,
41 .type = LoadTexture,
42 .filtering = LinearFiltering,
43 .mipmaps = false,
44 .data.texture.filepath = mstring_make(texture_file)});
45 if (!texture) {
46 goto cleanup;
47 }
48
49 ShaderProgram* shader = gfx_make_view_texture_shader(gfxcore);
50 if (!shader) {
51 goto cleanup;
52 }
53
54 Geometry* geometry = gfx_make_quad_11(gfxcore);
55 if (!geometry) {
56 goto cleanup;
57 }
58
59 MaterialDesc material_desc = (MaterialDesc){.num_uniforms = 1};
60 material_desc.uniforms[0] = (ShaderUniform){
61 .type = UniformTexture,
62 .value.texture = texture,
63 .name = sstring_make("Texture")};
64 Material* material = gfx_make_material(&material_desc);
65 if (!material) {
66 goto cleanup;
67 }
68
69 const MeshDesc mesh_desc =
70 (MeshDesc){.geometry = geometry, .material = material, .shader = shader};
71 Mesh* mesh = gfx_make_mesh(&mesh_desc);
72 if (!mesh) {
73 goto cleanup;
74 }
75
76 SceneObject* object =
77 gfx_make_object(&(ObjectDesc){.num_meshes = 1, .meshes = {mesh}});
78 if (!object) {
79 goto cleanup;
80 }
81
82 if (!(state->scene = gfx_make_scene())) {
83 goto cleanup;
84 }
85
86 SceneNode* node = gfx_make_object_node(object);
87 if (!node) {
88 goto cleanup;
89 }
90 SceneNode* root = gfx_get_scene_root(state->scene);
91 if (!root) {
92 goto cleanup;
93 }
94 gfx_set_node_parent(node, root);
95
96 if (!(state->camera = gfx_make_camera())) {
97 goto cleanup;
98 }
99
100 *pp_state = state;
101 return true;
102
103cleanup:
104 shutdown(game, state);
105 if (state) {
106 free(state);
107 }
108 return false;
109}
110
111void shutdown(Game* game, State* state) {
112 assert(game);
113 if (state) {
114 gfx_destroy_camera(&state->camera);
115 gfx_destroy_scene(&state->scene);
116 // State freed by plugin engine.
117 }
118}
119
120void render(const Game* game, const State* state) {
121 assert(game);
122 assert(state);
123
124 Renderer* renderer = gfx_get_renderer(game->gfx);
125 gfx_render_scene(
126 renderer, &(RenderSceneParams){
127 .mode = RenderDefault,
128 .scene = state->scene,
129 .camera = state->camera});
130}
131
132void resize(Game* game, State* state, int width, int height) {
133 assert(game);
134 assert(state);
135
136 const R fovy = 90 * TO_RAD;
137 const R aspect = (R)width / (R)height;
138 const R near = 0.1;
139 const R far = 1000;
140 const mat4 projection = mat4_perspective(fovy, aspect, near, far);
141
142 Camera* camera = gfx_get_camera_camera(state->camera);
143 camera->projection = projection;
144}
diff --git a/game/src/plugins/viewer.c b/game/src/plugins/viewer.c
deleted file mode 100644
index 5fc4be7..0000000
--- a/game/src/plugins/viewer.c
+++ /dev/null
@@ -1,366 +0,0 @@
1#include "plugin.h"
2
3#include <gfx/app.h>
4#include <gfx/asset.h>
5#include <gfx/renderer.h>
6#include <gfx/scene.h>
7#include <gfx/util/skyquad.h>
8#include <math/camera.h>
9#include <math/spatial3.h>
10
11#include <log/log.h>
12
13#include <stdlib.h>
14
15// Paths to various scene files.
16static const char* BOX = "/assets/models/box.gltf";
17static const char* SUZANNE = "/assets/models/suzanne.gltf";
18static const char* SPONZA =
19 "/assets/glTF-Sample-Models/2.0/Sponza/glTF/Sponza.gltf";
20static const char* FLIGHT_HELMET =
21 "/assets/glTF-Sample-Models/2.0/FlightHelmet/glTF/FlightHelmet.gltf";
22static const char* DAMAGED_HELMET =
23 "/assets/glTF-Sample-Models/2.0/DamagedHelmet/glTF/DamagedHelmet.gltf";
24static const char* GIRL =
25 "/home/jeanne/Nextcloud/assets/models/girl/girl-with-ground.gltf";
26static const char* BOXES =
27 "/home/jeanne/Nextcloud/assets/models/boxes/boxes.gltf";
28
29#define DEFAULT_SCENE_FILE GIRL
30
31static const bool RenderBoundingBoxes = false;
32static const R DefaultCameraSpeed = (R)6.0;
33static const R DefaultMouseSensitivity = (R)(10 * TO_RAD);
34static const vec3 DefaultCameraPosition = (vec3){0, 2, 5};
35
36typedef struct CameraCommand {
37 bool CameraMoveLeft : 1;
38 bool CameraMoveRight : 1;
39 bool CameraMoveForward : 1;
40 bool CameraMoveBackward : 1;
41} CameraCommand;
42
43typedef struct CameraController {
44 R camera_speed; // Camera movement speed.
45 R mouse_sensitivity; // Controls the degree with which mouse movements
46 // rotate the camera.
47 vec2 prev_mouse_position; // Mouse position in the previous frame.
48 bool rotating; // When true, subsequent mouse movements cause the
49 // camera to rotate.
50} CameraController;
51
52typedef struct State {
53 Scene* scene;
54 Model* model;
55 SceneCamera* camera;
56 CameraController camera_controller;
57} State;
58
59/// Load the skyquad texture.
60static const Texture* load_environment_map(Gfx* gfx) {
61 assert(gfx);
62 return gfx_load_texture(
63 gfx, &(LoadTextureCmd){
64 .origin = AssetFromFile,
65 .type = LoadCubemap,
66 .colour_space = sRGB,
67 .filtering = NearestFiltering,
68 .mipmaps = false,
69 .data.cubemap.filepaths = {
70 mstring_make("/assets/skybox/clouds1/clouds1_east.bmp"),
71 mstring_make("/assets/skybox/clouds1/clouds1_west.bmp"),
72 mstring_make("/assets/skybox/clouds1/clouds1_up.bmp"),
73 mstring_make("/assets/skybox/clouds1/clouds1_down.bmp"),
74 mstring_make("/assets/skybox/clouds1/clouds1_south.bmp"),
75 mstring_make("/assets/skybox/clouds1/clouds1_north.bmp")}
76 });
77}
78
79/// Load the skyquad and return the environment light node.
80static SceneNode* load_skyquad(Gfx* gfx, SceneNode* root) {
81 assert(gfx);
82 assert(root);
83
84 GfxCore* gfxcore = gfx_get_core(gfx);
85
86 const Texture* environment_map = load_environment_map(gfx);
87 if (!environment_map) {
88 return 0;
89 }
90
91 return gfx_setup_skyquad(gfxcore, root, environment_map);
92}
93
94/// Load the 3D scene.
95/// Return the loaded model.
96static Model* load_scene(Game* game, State* state, const char* scene_filepath) {
97 assert(game);
98 assert(game->gfx);
99 assert(state);
100 assert(state->scene);
101
102 Camera* camera = gfx_get_camera_camera(state->camera);
103 spatial3_set_position(&camera->spatial, vec3_make(0, 0, 2));
104
105 SceneNode* root = gfx_get_scene_root(state->scene);
106 SceneNode* sky_light_node = load_skyquad(game->gfx, root);
107 if (!sky_light_node) {
108 return 0; // test
109 }
110
111 Model* model = gfx_load_model(
112 game->gfx,
113 &(LoadModelCmd){
114 .origin = AssetFromFile, .filepath = mstring_make(scene_filepath)});
115 if (!model) {
116 return 0;
117 }
118 SceneNode* model_node = gfx_make_model_node(model);
119 if (!model_node) {
120 return 0;
121 }
122 gfx_set_node_parent(model_node, sky_light_node);
123
124 gfx_log_node_hierarchy(root);
125
126 return model;
127}
128
129bool init(Game* game, State** pp_state) {
130 assert(game);
131
132 // Usage: <scene file>
133 const char* scene_filepath =
134 game->argc > 1 ? game->argv[1] : DEFAULT_SCENE_FILE;
135
136 State* state = calloc(1, sizeof(State));
137 if (!state) {
138 goto cleanup;
139 }
140
141 if (!(state->scene = gfx_make_scene())) {
142 goto cleanup;
143 }
144 if (!(state->camera = gfx_make_camera())) {
145 goto cleanup;
146 }
147
148 state->model = load_scene(game, state, scene_filepath);
149 if (!state->model) {
150 goto cleanup;
151 }
152
153 Anima* anima = gfx_get_model_anima(state->model);
154 if (anima) {
155 gfx_play_animation(
156 anima, &(AnimationPlaySettings){.name = "Walk", .loop = true});
157 // TODO: Interpolate animations.
158 /*gfx_play_animation(
159 anima,
160 &(AnimationPlaySettings){.name = "Jumping-jack-lower", .loop = true});
161 gfx_play_animation(
162 anima, &(AnimationPlaySettings){
163 .name = "Jumping-jack-arms-mid", .loop = true});*/
164 }
165
166 spatial3_set_position(
167 &gfx_get_camera_camera(state->camera)->spatial, DefaultCameraPosition);
168
169 state->camera_controller.camera_speed = DefaultCameraSpeed;
170 state->camera_controller.mouse_sensitivity = DefaultMouseSensitivity;
171
172 *pp_state = state;
173 return true;
174
175cleanup:
176 shutdown(game, state);
177 if (state) {
178 free(state);
179 }
180 return false;
181}
182
183void shutdown(Game* game, State* state) {
184 assert(game);
185 if (state) {
186 gfx_destroy_camera(&state->camera);
187 gfx_destroy_scene(&state->scene);
188 // State freed by plugin engine.
189 }
190}
191
192static void update_camera(
193 CameraController* controller, R dt, vec2 mouse_position,
194 CameraCommand command, Spatial3* camera) {
195 assert(controller);
196 assert(camera);
197
198 // Translation.
199 const R move_x = (R)(command.CameraMoveLeft ? -1 : 0) +
200 (R)(command.CameraMoveRight ? 1 : 0);
201 const R move_y = (R)(command.CameraMoveForward ? 1 : 0) +
202 (R)(command.CameraMoveBackward ? -1 : 0);
203 const vec2 translation =
204 vec2_scale(vec2_make(move_x, move_y), controller->camera_speed * dt);
205 spatial3_move_right(camera, translation.x);
206 spatial3_move_forwards(camera, translation.y);
207
208 // Rotation.
209 if (controller->rotating) {
210 const vec2 mouse_delta =
211 vec2_sub(mouse_position, controller->prev_mouse_position);
212
213 const vec2 rotation =
214 vec2_scale(mouse_delta, controller->mouse_sensitivity * dt);
215
216 spatial3_global_yaw(camera, -rotation.x);
217 spatial3_pitch(camera, -rotation.y);
218 }
219
220 // Update controller state.
221 controller->prev_mouse_position = mouse_position;
222}
223
224void update(Game* game, State* state, double t, double dt) {
225 assert(game);
226 assert(state);
227 assert(state->scene);
228 assert(state->camera);
229
230 double mouse_x, mouse_y;
231 gfx_app_get_mouse_position(&mouse_x, &mouse_y);
232 const vec2 mouse_position = {(R)mouse_x, (R)mouse_y};
233
234 const CameraCommand camera_command = (CameraCommand){
235 .CameraMoveLeft = gfx_app_is_key_pressed(KeyA),
236 .CameraMoveRight = gfx_app_is_key_pressed(KeyD),
237 .CameraMoveForward = gfx_app_is_key_pressed(KeyW),
238 .CameraMoveBackward = gfx_app_is_key_pressed(KeyS),
239 };
240
241 state->camera_controller.rotating = gfx_app_is_mouse_button_pressed(LMB);
242
243 update_camera(
244 &state->camera_controller, (R)dt, mouse_position, camera_command,
245 &gfx_get_camera_camera(state->camera)->spatial);
246
247 // const vec3 orbit_point = vec3_make(0, 2, 0);
248 // Camera* camera = gfx_get_camera_camera(state->camera);
249 // spatial3_orbit(
250 // &camera->spatial, orbit_point,
251 // /*radius=*/5,
252 // /*azimuth=*/(R)(t * 0.5), /*zenith=*/0);
253 // spatial3_lookat(&camera->spatial, orbit_point);
254
255 gfx_update(state->scene, state->camera, (R)t);
256}
257
258/// Render the bounding boxes of all scene objects.
259static void render_bounding_boxes_rec(
260 ImmRenderer* imm, const Anima* anima, const mat4* parent_model_matrix,
261 const SceneNode* node) {
262 assert(imm);
263 assert(node);
264
265 const mat4 model_matrix =
266 mat4_mul(*parent_model_matrix, gfx_get_node_transform(node));
267
268 const NodeType node_type = gfx_get_node_type(node);
269
270 if (node_type == ModelNode) {
271 const Model* model = gfx_get_node_model(node);
272 const SceneNode* root = gfx_get_model_root(model);
273 render_bounding_boxes_rec(imm, anima, &model_matrix, root);
274 } else if (node_type == AnimaNode) {
275 anima = gfx_get_node_anima(node);
276 } else if (node_type == ObjectNode) {
277 gfx_imm_set_model_matrix(imm, &model_matrix);
278
279 const SceneObject* obj = gfx_get_node_object(node);
280 const Skeleton* skeleton = gfx_get_object_skeleton(obj);
281
282 if (skeleton) { // Animated model.
283 assert(anima);
284 const size_t num_joints = gfx_get_skeleton_num_joints(skeleton);
285 for (size_t i = 0; i < num_joints; ++i) {
286 if (gfx_joint_has_box(anima, skeleton, i)) {
287 const Box box = gfx_get_joint_box(anima, skeleton, i);
288 gfx_imm_draw_box3(imm, box.vertices);
289 }
290 }
291 } else { // Static model.
292 const aabb3 box = gfx_get_object_aabb(obj);
293 gfx_imm_draw_aabb3(imm, box);
294 }
295 }
296
297 // Render children's boxes.
298 const SceneNode* child = gfx_get_node_child(node);
299 while (child) {
300 render_bounding_boxes_rec(imm, anima, &model_matrix, child);
301 child = gfx_get_node_sibling(child);
302 }
303}
304
305/// Render the bounding boxes of all scene objects.
306static void render_bounding_boxes(const Game* game, const State* state) {
307 assert(game);
308 assert(state);
309
310 GfxCore* gfxcore = gfx_get_core(game->gfx);
311 ImmRenderer* imm = gfx_get_imm_renderer(game->gfx);
312 assert(gfxcore);
313 assert(imm);
314
315 const mat4 id = mat4_id();
316 Anima* anima = 0;
317
318 gfx_set_blending(gfxcore, true);
319 gfx_set_depth_mask(gfxcore, false);
320 gfx_set_polygon_offset(gfxcore, -1.5f, -1.0f);
321
322 gfx_imm_start(imm);
323 gfx_imm_set_camera(imm, gfx_get_camera_camera(state->camera));
324 gfx_imm_set_colour(imm, vec4_make(0.3, 0.3, 0.9, 0.1));
325 render_bounding_boxes_rec(imm, anima, &id, gfx_get_scene_root(state->scene));
326 gfx_imm_end(imm);
327
328 gfx_reset_polygon_offset(gfxcore);
329 gfx_set_depth_mask(gfxcore, true);
330 gfx_set_blending(gfxcore, false);
331}
332
333void render(const Game* game, const State* state) {
334 assert(state);
335 assert(game);
336 assert(game->gfx);
337 assert(state->scene);
338 assert(state->camera);
339
340 Renderer* renderer = gfx_get_renderer(game->gfx);
341 assert(renderer);
342
343 gfx_render_scene(
344 renderer, &(RenderSceneParams){
345 .mode = RenderDefault,
346 .scene = state->scene,
347 .camera = state->camera});
348
349 if (RenderBoundingBoxes) {
350 render_bounding_boxes(game, state);
351 }
352}
353
354void resize(Game* game, State* state, int width, int height) {
355 assert(game);
356 assert(state);
357
358 const R fovy = 60 * TO_RAD;
359 const R aspect = (R)width / (R)height;
360 const R near = 0.1;
361 const R far = 1000;
362 const mat4 projection = mat4_perspective(fovy, aspect, near, far);
363
364 Camera* camera = gfx_get_camera_camera(state->camera);
365 camera->projection = projection;
366}
diff --git a/gfx-iso/CMakeLists.txt b/gfx-iso/CMakeLists.txt
deleted file mode 100644
index e4a677d..0000000
--- a/gfx-iso/CMakeLists.txt
+++ /dev/null
@@ -1,42 +0,0 @@
1cmake_minimum_required(VERSION 3.0)
2
3project(isogfx)
4
5set(CMAKE_C_STANDARD 17)
6set(CMAKE_C_STANDARD_REQUIRED On)
7set(CMAKE_C_EXTENSIONS Off)
8
9# isogfx
10
11add_library(isogfx
12 src/isogfx.c)
13
14target_include_directories(isogfx PUBLIC
15 include)
16
17target_link_libraries(isogfx PUBLIC
18 filesystem
19 mem
20 mempool)
21
22target_compile_options(isogfx PRIVATE -Wall -Wextra -Wpedantic)
23
24# Backend
25
26add_library(isogfx-backend
27 src/backend.c)
28
29target_include_directories(isogfx-backend PUBLIC
30 include)
31
32target_link_libraries(isogfx-backend PUBLIC
33 isogfx)
34
35target_link_libraries(isogfx-backend PRIVATE
36 gfx)
37
38target_compile_options(isogfx-backend PRIVATE -Wall -Wextra -Wpedantic)
39
40# Demos
41
42add_subdirectory(demos)
diff --git a/gfx-iso/demos/CMakeLists.txt b/gfx-iso/demos/CMakeLists.txt
deleted file mode 100644
index c0a4101..0000000
--- a/gfx-iso/demos/CMakeLists.txt
+++ /dev/null
@@ -1,2 +0,0 @@
1add_subdirectory(checkerboard)
2add_subdirectory(isomap)
diff --git a/gfx-iso/demos/checkerboard/CMakeLists.txt b/gfx-iso/demos/checkerboard/CMakeLists.txt
deleted file mode 100644
index d1691c6..0000000
--- a/gfx-iso/demos/checkerboard/CMakeLists.txt
+++ /dev/null
@@ -1,16 +0,0 @@
1cmake_minimum_required(VERSION 3.0)
2
3project(checkerboard)
4
5set(CMAKE_C_STANDARD 17)
6set(CMAKE_C_STANDARD_REQUIRED On)
7set(CMAKE_C_EXTENSIONS Off)
8
9add_executable(checkerboard
10 checkerboard.c)
11
12target_link_libraries(checkerboard PRIVATE
13 gfx-app
14 isogfx-backend)
15
16target_compile_options(checkerboard PRIVATE -Wall -Wextra -Wpedantic)
diff --git a/gfx-iso/demos/checkerboard/checkerboard.c b/gfx-iso/demos/checkerboard/checkerboard.c
deleted file mode 100644
index dbc817c..0000000
--- a/gfx-iso/demos/checkerboard/checkerboard.c
+++ /dev/null
@@ -1,166 +0,0 @@
1#include <isogfx/backend.h>
2#include <isogfx/isogfx.h>
3
4#include <gfx/app.h>
5
6#include <assert.h>
7#include <stdbool.h>
8#include <stdio.h>
9
10static const int WINDOW_WIDTH = 1408;
11static const int WINDOW_HEIGHT = 960;
12static const int MAX_FPS = 60;
13
14// Virtual screen dimensions.
15static const int SCREEN_WIDTH = 704;
16static const int SCREEN_HEIGHT = 480;
17
18static const int TILE_WIDTH = 32;
19static const int TILE_HEIGHT = TILE_WIDTH / 2;
20static const int WORLD_WIDTH = 20;
21static const int WORLD_HEIGHT = 20;
22
23static const TileDesc tile_set[] = {
24 {.type = TileFromColour,
25 .width = TILE_WIDTH,
26 .height = TILE_HEIGHT,
27 .colour = (Pixel){.r = 0x38, .g = 0x3b, .b = 0x46, .a = 0xff}},
28 {.type = TileFromColour,
29 .width = TILE_WIDTH,
30 .height = TILE_HEIGHT,
31 .colour = (Pixel){.r = 0xA5, .g = 0xb3, .b = 0xc0, .a = 0xff}},
32 {.type = TileFromColour,
33 .width = TILE_WIDTH,
34 .height = TILE_HEIGHT,
35 .colour = (Pixel){.r = 0xdc, .g = 0x76, .b = 0x84, .a = 0xff}},
36};
37
38typedef enum Colour {
39 Black,
40 White,
41 Red,
42} Colour;
43
44typedef struct GfxAppState {
45 IsoBackend* backend;
46 IsoGfx* iso;
47 Tile red;
48 int xpick;
49 int ypick;
50} GfxAppState;
51
52static void make_checkerboard(IsoGfx* iso, Tile black, Tile white) {
53 assert(iso);
54 for (int y = 0; y < isogfx_world_height(iso); ++y) {
55 for (int x = 0; x < isogfx_world_width(iso); ++x) {
56 const int odd_col = x & 1;
57 const int odd_row = y & 1;
58 const Tile value = (odd_row ^ odd_col) == 0 ? black : white;
59 isogfx_set_tile(iso, x, y, value);
60 }
61 }
62}
63
64static bool init(GfxAppState* state, int argc, const char** argv) {
65 assert(state);
66
67 (void)argc;
68 (void)argv;
69
70 if (!(state->iso = isogfx_new(&(IsoGfxDesc){
71 .screen_width = SCREEN_WIDTH, .screen_height = SCREEN_HEIGHT}))) {
72 return false;
73 }
74 IsoGfx* iso = state->iso;
75
76 isogfx_resize(iso, SCREEN_WIDTH, SCREEN_HEIGHT);
77
78 if (!isogfx_make_world(
79 iso, &(WorldDesc){
80 .tile_width = TILE_WIDTH,
81 .tile_height = TILE_HEIGHT,
82 .world_width = WORLD_WIDTH,
83 .world_height = WORLD_HEIGHT})) {
84 return false;
85 }
86
87 const Tile black = isogfx_make_tile(iso, &tile_set[Black]);
88 const Tile white = isogfx_make_tile(iso, &tile_set[White]);
89 state->red = isogfx_make_tile(iso, &tile_set[Red]);
90 make_checkerboard(iso, black, white);
91
92 if (!(state->backend = IsoBackendInit(iso))) {
93 return false;
94 }
95
96 return true;
97}
98
99static void shutdown(GfxAppState* state) {
100 assert(state);
101
102 IsoBackendShutdown(&state->backend);
103 isogfx_del(&state->iso);
104}
105
106static void update(GfxAppState* state, double t, double dt) {
107 assert(state);
108 (void)dt;
109
110 IsoGfx* iso = state->iso;
111
112 isogfx_update(iso, t);
113
114 // Get mouse position in window coordinates.
115 double mouse_x, mouse_y;
116 gfx_app_get_mouse_position(&mouse_x, &mouse_y);
117
118 // Map from window coordinates to virtual screen coordinates.
119 IsoBackendGetMousePosition(
120 state->backend, mouse_x, mouse_y, &mouse_x, &mouse_y);
121
122 isogfx_pick_tile(iso, mouse_x, mouse_y, &state->xpick, &state->ypick);
123
124 printf("Picked tile: (%d, %d)\n", state->xpick, state->ypick);
125}
126
127static void render(GfxAppState* state) {
128 assert(state);
129
130 IsoGfx* iso = state->iso;
131
132 isogfx_render(iso);
133
134 if ((state->xpick != -1) && (state->ypick != -1)) {
135 isogfx_draw_tile(iso, state->xpick, state->ypick, state->red);
136 }
137
138 IsoBackendRender(state->backend, iso);
139}
140
141static void resize(GfxAppState* state, int width, int height) {
142 assert(state);
143
144 IsoBackendResizeWindow(state->backend, state->iso, width, height);
145}
146
147int main(int argc, const char** argv) {
148 GfxAppState state = {0};
149 gfx_app_run(
150 &(GfxAppDesc){
151 .argc = argc,
152 .argv = argv,
153 .width = WINDOW_WIDTH,
154 .height = WINDOW_HEIGHT,
155 .max_fps = MAX_FPS,
156 .update_delta_time = MAX_FPS > 0 ? 1.0 / (double)MAX_FPS : 0.0,
157 .title = "Isometric Renderer",
158 .app_state = &state},
159 &(GfxAppCallbacks){
160 .init = init,
161 .update = update,
162 .render = render,
163 .resize = resize,
164 .shutdown = shutdown});
165 return 0;
166}
diff --git a/gfx-iso/demos/isomap/CMakeLists.txt b/gfx-iso/demos/isomap/CMakeLists.txt
deleted file mode 100644
index 2dbfd32..0000000
--- a/gfx-iso/demos/isomap/CMakeLists.txt
+++ /dev/null
@@ -1,16 +0,0 @@
1cmake_minimum_required(VERSION 3.0)
2
3project(isomap)
4
5set(CMAKE_C_STANDARD 17)
6set(CMAKE_C_STANDARD_REQUIRED On)
7set(CMAKE_C_EXTENSIONS Off)
8
9add_executable(isomap
10 isomap.c)
11
12target_link_libraries(isomap PRIVATE
13 gfx-app
14 isogfx-backend)
15
16target_compile_options(isomap PRIVATE -Wall -Wextra -Wpedantic)
diff --git a/gfx-iso/demos/isomap/isomap.c b/gfx-iso/demos/isomap/isomap.c
deleted file mode 100644
index a233659..0000000
--- a/gfx-iso/demos/isomap/isomap.c
+++ /dev/null
@@ -1,105 +0,0 @@
1#include <isogfx/backend.h>
2#include <isogfx/isogfx.h>
3
4#include <gfx/app.h>
5
6#include <assert.h>
7#include <stdbool.h>
8
9static const int WINDOW_WIDTH = 1408;
10static const int WINDOW_HEIGHT = 960;
11static const int MAX_FPS = 60;
12
13// Virtual screen dimensions.
14static const int SCREEN_WIDTH = 704;
15static const int SCREEN_HEIGHT = 480;
16
17typedef struct GfxAppState {
18 IsoBackend* backend;
19 IsoGfx* iso;
20 int xpick;
21 int ypick;
22 SpriteSheet stag_sheet;
23 Sprite stag;
24} GfxAppState;
25
26static bool init(GfxAppState* state, int argc, const char** argv) {
27 assert(state);
28 (void)argc;
29 (void)argv;
30
31 if (!(state->iso = isogfx_new(&(IsoGfxDesc){
32 .screen_width = SCREEN_WIDTH, .screen_height = SCREEN_HEIGHT}))) {
33 return false;
34 }
35 IsoGfx* iso = state->iso;
36
37 isogfx_resize(iso, SCREEN_WIDTH, SCREEN_HEIGHT);
38
39 if (!isogfx_load_world(iso, "/home/jeanne/assets/tilemaps/demo1.tm")) {
40 return false;
41 }
42
43 if (!isogfx_load_sprite_sheet(
44 iso, "/home/jeanne/assets/tilesets/scrabling/critters/stag/stag.ss",
45 &state->stag_sheet)) {
46 return false;
47 }
48
49 state->stag = isogfx_make_sprite(iso, state->stag_sheet);
50 isogfx_set_sprite_position(iso, state->stag, 5, 4);
51
52 if (!(state->backend = IsoBackendInit(iso))) {
53 return false;
54 }
55
56 return true;
57}
58
59static void shutdown(GfxAppState* state) {
60 assert(state);
61 //
62}
63
64static void update(GfxAppState* state, double t, double dt) {
65 assert(state);
66 (void)dt;
67
68 IsoGfx* iso = state->iso;
69 isogfx_update(iso, t);
70}
71
72static void render(GfxAppState* state) {
73 assert(state);
74
75 IsoGfx* iso = state->iso;
76 isogfx_render(iso);
77 IsoBackendRender(state->backend, iso);
78}
79
80static void resize(GfxAppState* state, int width, int height) {
81 assert(state);
82
83 IsoBackendResizeWindow(state->backend, state->iso, width, height);
84}
85
86int main(int argc, const char** argv) {
87 GfxAppState state = {0};
88 gfx_app_run(
89 &(GfxAppDesc){
90 .argc = argc,
91 .argv = argv,
92 .width = WINDOW_WIDTH,
93 .height = WINDOW_HEIGHT,
94 .max_fps = MAX_FPS,
95 .update_delta_time = MAX_FPS > 0 ? 1.0 / (double)MAX_FPS : 0.0,
96 .title = "Isometric Renderer",
97 .app_state = &state},
98 &(GfxAppCallbacks){
99 .init = init,
100 .update = update,
101 .render = render,
102 .resize = resize,
103 .shutdown = shutdown});
104 return 0;
105}
diff --git a/gfx-iso/include/isogfx/backend.h b/gfx-iso/include/isogfx/backend.h
deleted file mode 100644
index 172991d..0000000
--- a/gfx-iso/include/isogfx/backend.h
+++ /dev/null
@@ -1,28 +0,0 @@
1#pragma once
2
3#include <stdbool.h>
4
5typedef struct Gfx Gfx;
6typedef struct IsoGfx IsoGfx;
7
8typedef struct IsoBackend IsoBackend;
9
10/// Initialize the backend.
11IsoBackend* IsoBackendInit(const IsoGfx*);
12
13/// Shut down the backend.
14void IsoBackendShutdown(IsoBackend**);
15
16/// Notify the backend of a window resize event.
17/// This allows the backend to determine how to position and scale the iso
18/// screen buffer on the graphics window.
19void IsoBackendResizeWindow(IsoBackend*, const IsoGfx*, int width, int height);
20
21/// Render the iso screen to the graphics window.
22void IsoBackendRender(const IsoBackend*, const IsoGfx*);
23
24/// Map window coordinates to iso space coordinates.
25/// This takes into account any possible resizing done by the backend in
26/// response to calls to IsoBackendResizeWindow().
27bool IsoBackendGetMousePosition(
28 const IsoBackend*, double window_x, double window_y, double* x, double* y);
diff --git a/gfx-iso/include/isogfx/isogfx.h b/gfx-iso/include/isogfx/isogfx.h
deleted file mode 100644
index 3421a7b..0000000
--- a/gfx-iso/include/isogfx/isogfx.h
+++ /dev/null
@@ -1,136 +0,0 @@
1/*
2 * Isometric rendering engine.
3 */
4#pragma once
5
6#include <stdbool.h>
7#include <stdint.h>
8
9typedef struct IsoGfx IsoGfx;
10
11/// Sprite sheet handle.
12typedef uint16_t SpriteSheet;
13
14/// Sprite handle.
15typedef uint16_t Sprite;
16
17/// Tile handle.
18typedef uint16_t Tile;
19
20/// Colour channel.
21typedef uint8_t Channel;
22
23typedef struct Pixel {
24 Channel r, g, b, a;
25} Pixel;
26
27typedef enum TileDescType {
28 TileFromColour,
29 TileFromFile,
30 TileFromMemory,
31} TileDescType;
32
33typedef struct TileDesc {
34 TileDescType type;
35 int width; /// Tile width in pixels.
36 int height; /// Tile height in pixels.
37 union {
38 Pixel colour; /// Constant colour tile.
39 struct {
40 const char* path;
41 } file;
42 struct {
43 const uint8_t* data; /// sizeof(Pixel) * width * height
44 } mem;
45 };
46} TileDesc;
47
48typedef struct WorldDesc {
49 int tile_width; /// Base tile width in pixels.
50 int tile_height; /// Base tile height in pixels.
51 int world_width; /// World width in tiles.
52 int world_height; /// World height in tiles.
53 int max_num_tiles; /// 0 for an implementation-defined default.
54} WorldDesc;
55
56typedef struct IsoGfxDesc {
57 int screen_width; /// Screen width in pixels.
58 int screen_height; /// Screen height in pixels.
59 int max_num_sprites; /// 0 for an implementation-defined default.
60 int sprite_sheet_pool_size_bytes; /// 0 for an implementation-defined default.
61} IsoGfxDesc;
62
63/// Create a new isometric graphics engine.
64IsoGfx* isogfx_new(const IsoGfxDesc*);
65
66/// Destroy the isometric graphics engine.
67void isogfx_del(IsoGfx**);
68
69/// Create an empty world.
70bool isogfx_make_world(IsoGfx*, const WorldDesc*);
71
72/// Load a world from a tile map (.TM) file.
73bool isogfx_load_world(IsoGfx*, const char* filepath);
74
75/// Return the world's width.
76int isogfx_world_width(const IsoGfx*);
77
78/// Return the world's height.
79int isogfx_world_height(const IsoGfx*);
80
81/// Create a new tile.
82Tile isogfx_make_tile(IsoGfx*, const TileDesc*);
83
84/// Set the tile at position (x,y).
85void isogfx_set_tile(IsoGfx*, int x, int y, Tile);
86
87/// Set the tiles in positions in the range (x0,y0) - (x1,y1).
88void isogfx_set_tiles(IsoGfx*, int x0, int y0, int x1, int y1, Tile);
89
90/// Load a sprite sheet (.SS) file.
91bool isogfx_load_sprite_sheet(IsoGfx*, const char* filepath, SpriteSheet*);
92
93/// Create an animated sprite.
94Sprite isogfx_make_sprite(IsoGfx*, SpriteSheet);
95
96/// Destroy the sprite.
97void isogfx_del_sprite(IsoGfx*, Sprite);
98
99/// Destroy all the sprites.
100void isogfx_del_sprites(IsoGfx*);
101
102/// Set the sprite's position.
103void isogfx_set_sprite_position(IsoGfx*, Sprite, int x, int y);
104
105/// Set the sprite's current animation.
106void isogfx_set_sprite_animation(IsoGfx*, Sprite, int animation);
107
108/// Update the renderer.
109///
110/// Currently this updates the sprite animations.
111void isogfx_update(IsoGfx*, double t);
112
113/// Render the world.
114void isogfx_render(IsoGfx*);
115
116/// Draw/overlay a tile at position (x,y).
117///
118/// This function just renders a tile at position (x,y) and should be called
119/// after isogfx_render() to obtain the correct result. To set the tile at
120/// position (x,y) instead, use isogfx_set_tile().
121void isogfx_draw_tile(IsoGfx*, int x, int y, Tile);
122
123/// Resize the virtual screen's dimensions.
124bool isogfx_resize(IsoGfx*, int screen_width, int screen_height);
125
126/// Get the virtual screen's dimensions.
127void isogfx_get_screen_size(const IsoGfx*, int* width, int* height);
128
129/// Return a pointer to the virtual screen's colour buffer.
130///
131/// Call after each call to isogfx_render() to retrieve the render output.
132const Pixel* isogfx_get_screen_buffer(const IsoGfx*);
133
134/// Translate Cartesian to isometric coordinates.
135void isogfx_pick_tile(
136 const IsoGfx*, double xcart, double ycart, int* xiso, int* yiso);
diff --git a/gfx-iso/src/backend.c b/gfx-iso/src/backend.c
deleted file mode 100644
index db91647..0000000
--- a/gfx-iso/src/backend.c
+++ /dev/null
@@ -1,199 +0,0 @@
1#include <isogfx/backend.h>
2#include <isogfx/isogfx.h>
3
4#include <gfx/core.h>
5#include <gfx/gfx.h>
6#include <gfx/renderer.h>
7#include <gfx/scene.h>
8#include <gfx/util/geometry.h>
9#include <gfx/util/shader.h>
10
11#include <assert.h>
12#include <stdlib.h>
13
14typedef struct IsoBackend {
15 Gfx* gfx;
16 Scene* scene;
17 /// The screen or "iso screen" refers to the colour buffer of the iso graphics
18 /// library. This texture is used to draw the iso screen onto the graphics
19 /// window.
20 Texture* screen_texture;
21 /// Window size.
22 int window_width;
23 int window_height;
24 /// The viewport refers to the area inside the window to which screen_texture
25 /// is drawn. It is a scaled version of the iso screen, scaled while
26 /// respecting the iso screen's aspect ratio to prevent distortion.
27 int viewport_x, viewport_y, viewport_width, viewport_height;
28 double stretch; // Stretch factor from iso screen dimensions to viewport
29 // dimensions.
30} IsoBackend;
31
32IsoBackend* IsoBackendInit(const IsoGfx* iso) {
33 assert(iso);
34
35 IsoBackend* backend = calloc(1, sizeof(IsoBackend));
36 if (!backend) {
37 return 0;
38 }
39
40 if (!(backend->gfx = gfx_init())) {
41 goto cleanup;
42 }
43 GfxCore* gfxcore = gfx_get_core(backend->gfx);
44
45 int screen_width, screen_height;
46 isogfx_get_screen_size(iso, &screen_width, &screen_height);
47
48 if (!(backend->screen_texture = gfx_make_texture(
49 gfxcore, &(TextureDesc){
50 .width = screen_width,
51 .height = screen_height,
52 .dimension = Texture2D,
53 .format = TextureSRGBA8,
54 .filtering = NearestFiltering,
55 .wrap = ClampToEdge,
56 .mipmaps = false}))) {
57 goto cleanup;
58 }
59
60 ShaderProgram* shader = gfx_make_view_texture_shader(gfxcore);
61 if (!shader) {
62 goto cleanup;
63 }
64
65 Geometry* geometry = gfx_make_quad_11(gfxcore);
66 if (!geometry) {
67 goto cleanup;
68 }
69
70 MaterialDesc material_desc = (MaterialDesc){.num_uniforms = 1};
71 material_desc.uniforms[0] = (ShaderUniform){
72 .type = UniformTexture,
73 .value.texture = backend->screen_texture,
74 .name = sstring_make("Texture")};
75 Material* material = gfx_make_material(&material_desc);
76 if (!material) {
77 return false;
78 }
79
80 const MeshDesc mesh_desc =
81 (MeshDesc){.geometry = geometry, .material = material, .shader = shader};
82 Mesh* mesh = gfx_make_mesh(&mesh_desc);
83 if (!mesh) {
84 goto cleanup;
85 }
86
87 SceneObject* object =
88 gfx_make_object(&(ObjectDesc){.num_meshes = 1, .meshes = {mesh}});
89 if (!object) {
90 goto cleanup;
91 }
92
93 backend->scene = gfx_make_scene();
94 SceneNode* node = gfx_make_object_node(object);
95 SceneNode* root = gfx_get_scene_root(backend->scene);
96 gfx_set_node_parent(node, root);
97
98 return backend;
99
100cleanup:
101 if (backend->gfx) {
102 gfx_destroy(&backend->gfx);
103 }
104 free(backend);
105 return 0;
106}
107
108void IsoBackendShutdown(IsoBackend** ppApp) {
109 assert(ppApp);
110
111 IsoBackend* app = *ppApp;
112 if (!app) {
113 return;
114 }
115
116 gfx_destroy(&app->gfx);
117}
118
119void IsoBackendResizeWindow(
120 IsoBackend* app, const IsoGfx* iso, int width, int height) {
121 assert(app);
122 assert(iso);
123
124 app->window_width = width;
125 app->window_height = height;
126
127 // Virtual screen dimensions.
128 int screen_width, screen_height;
129 isogfx_get_screen_size(iso, &screen_width, &screen_height);
130
131 // Stretch the virtual screen onto the viewport while respecting the screen's
132 // aspect ratio to prevent distortion.
133 if (width > height) { // Wide screen.
134 app->stretch = (double)height / (double)screen_height;
135 app->viewport_width = (int)((double)screen_width * app->stretch);
136 app->viewport_height = height;
137 app->viewport_x = (width - app->viewport_width) / 2;
138 app->viewport_y = 0;
139 } else { // Tall screen.
140 app->stretch = (double)width / (double)screen_width;
141 app->viewport_width = width;
142 app->viewport_height = (int)((float)screen_height * app->stretch);
143 app->viewport_x = 0;
144 app->viewport_y = (height - app->viewport_height) / 2;
145 }
146}
147
148void IsoBackendRender(const IsoBackend* app, const IsoGfx* iso) {
149 assert(app);
150 assert(iso);
151
152 const Pixel* screen = isogfx_get_screen_buffer(iso);
153 assert(screen);
154 gfx_update_texture(app->screen_texture, &(TextureDataDesc){.pixels = screen});
155
156 GfxCore* gfxcore = gfx_get_core(app->gfx);
157 Renderer* renderer = gfx_get_renderer(app->gfx);
158
159 // Clear the whole window.
160 gfx_set_viewport(gfxcore, 0, 0, app->window_width, app->window_height);
161 gfx_clear(gfxcore, vec4_make(0, 0, 0, 0));
162
163 // Draw to the subregion where the virtual screen can stretch without
164 // distortion.
165 gfx_set_viewport(
166 gfxcore, app->viewport_x, app->viewport_y, app->viewport_width,
167 app->viewport_height);
168
169 // Render the iso screen.
170 gfx_start_frame(gfxcore);
171 gfx_render_scene(
172 renderer, &(RenderSceneParams){
173 .mode = RenderDefault, .scene = app->scene, .camera = 0});
174 gfx_end_frame(gfxcore);
175}
176
177bool IsoBackendGetMousePosition(
178 const IsoBackend* app, double window_x, double window_y, double* x,
179 double* y) {
180 assert(app);
181
182 // Translate from window coordinates to the subregion where the stretched
183 // iso screen is rendered.
184 const double screen_x = window_x - app->viewport_x;
185 const double screen_y = window_y - app->viewport_y;
186
187 // Position may be out of bounds.
188 if ((0 <= screen_x) && (screen_x < app->viewport_width) && (0 <= screen_y) &&
189 (screen_y < app->viewport_height)) {
190 // Scale back from the stretched subregion to the iso screen dimensions.
191 *x = screen_x / app->stretch;
192 *y = screen_y / app->stretch;
193 return true;
194 } else {
195 *x = -1;
196 *y = -1;
197 return false;
198 }
199}
diff --git a/gfx-iso/src/isogfx.c b/gfx-iso/src/isogfx.c
deleted file mode 100644
index 52c4ae2..0000000
--- a/gfx-iso/src/isogfx.c
+++ /dev/null
@@ -1,952 +0,0 @@
1#include <isogfx/isogfx.h>
2
3#include <filesystem.h>
4#include <mem.h>
5#include <mempool.h>
6#include <path.h>
7
8#include <linux/limits.h>
9
10#include <assert.h>
11#include <stdbool.h>
12#include <stdint.h>
13#include <stdio.h>
14#include <stdlib.h>
15#include <string.h>
16
17/// Maximum number of tiles unless the user specifies a value.
18#define DEFAULT_MAX_NUM_TILES 1024
19
20/// Maximum number of sprites unless the user specifies a value.
21#define DEFAULT_MAX_NUM_SPRITES 128
22
23/// Size of sprite sheet pool in bytes unless the user specifies a value.
24#define DEFAULT_SPRITE_SHEET_POOL_SIZE_BYTES (8 * 1024 * 1024)
25
26/// Default animation speed.
27#define ANIMATION_FPS 10
28
29/// Time between animation updates.
30#define ANIMATION_UPDATE_DELTA (1.0 / ANIMATION_FPS)
31
32typedef struct ivec2 {
33 int x, y;
34} ivec2;
35
36typedef struct vec2 {
37 double x, y;
38} vec2;
39
40// -----------------------------------------------------------------------------
41// Tile set (TS) and tile map (TM) file formats.
42// -----------------------------------------------------------------------------
43
44/// Maximum length of path strings in .TS and .TM files.
45#define MAX_PATH_LENGTH 128
46
47typedef struct Ts_Tile {
48 uint16_t width; /// Tile width in pixels.
49 uint16_t height; /// Tile height in pixels.
50 Pixel pixels[1]; /// Count: width * height.
51} Ts_Tile;
52
53typedef struct Ts_TileSet {
54 uint16_t num_tiles;
55 uint16_t max_tile_width; /// Maximum tile width in pixels.
56 uint16_t max_tile_height; /// Maximum tile height in pixels.
57 Ts_Tile tiles[1]; /// Count: num_tiles.
58} Ts_TileSet;
59
60typedef struct Tm_Layer {
61 union {
62 char tileset_path[MAX_PATH_LENGTH]; // Relative to the Tm_Map file.
63 };
64 Tile tiles[1]; /// Count: world_width * world_height.
65} Tm_Layer;
66
67typedef struct Tm_Map {
68 uint16_t world_width; /// World width in number of tiles.
69 uint16_t world_height; /// World height in number of tiles.
70 uint16_t base_tile_width;
71 uint16_t base_tile_height;
72 uint16_t num_layers;
73 Tm_Layer layers[1]; // Count: num_layers.
74} Tm_Map;
75
76static inline const Tm_Layer* tm_map_get_next_layer(
77 const Tm_Map* map, const Tm_Layer* layer) {
78 assert(map);
79 assert(layer);
80 return (const Tm_Layer*)((const uint8_t*)layer + sizeof(Tm_Layer) +
81 ((map->world_width * map->world_height - 1) *
82 sizeof(Tile)));
83}
84
85static inline const Ts_Tile* ts_tileset_get_next_tile(
86 const Ts_TileSet* tileset, const Ts_Tile* tile) {
87 assert(tileset);
88 assert(tile);
89 return (const Ts_Tile*)((const uint8_t*)tile + sizeof(Ts_Tile) +
90 ((tile->width * tile->height - 1) * sizeof(Pixel)));
91}
92
93// -----------------------------------------------------------------------------
94// Sprite sheet file format.
95// -----------------------------------------------------------------------------
96
97/// A row of sprites in a sprite sheet.
98///
99/// Each row in a sprite sheet can have a different number of columns.
100///
101/// The pixels of the row follow a "sprite-major" order. It contains the
102/// 'sprite_width * sprite_height' pixels for the first column/sprite, then the
103/// second column/sprite, etc.
104///
105/// Pixels are 8-bit indices into the sprite sheet's colour palette.
106typedef struct Ss_Row {
107 uint16_t num_cols; /// Number of columns in this row.
108 uint8_t pixels[1]; /// Count: num_cols * sprite_width * sprite_height.
109} Ss_Row;
110
111typedef struct Ss_Palette {
112 uint16_t num_colours;
113 Pixel colours[1]; /// Count: num_colors.
114} Ss_Palette;
115
116/// Sprite sheet top-level data definition.
117///
118/// Sprite width and height are assumed constant throughout the sprite sheet.
119typedef struct Ss_SpriteSheet {
120 uint16_t sprite_width; /// Sprite width in pixels.
121 uint16_t sprite_height; /// Sprite height in pixels.
122 uint16_t num_rows;
123 Ss_Palette palette; /// Variable size.
124 Ss_Row rows[1]; /// Count: num_rows. Variable offset.
125} Ss_SpriteSheet;
126
127static inline const Ss_Row* get_sprite_sheet_row(
128 const Ss_SpriteSheet* sheet, int row) {
129 assert(sheet);
130 assert(row >= 0);
131 assert(row < sheet->num_rows);
132 // Skip over the palette.
133 const Ss_Row* rows =
134 (const Ss_Row*)(&sheet->palette.colours[0] + sheet->palette.num_colours);
135 return &rows[row];
136}
137
138static inline const uint8_t* get_sprite_sheet_sprite(
139 const Ss_SpriteSheet* sheet, const Ss_Row* row, int col) {
140 assert(sheet);
141 assert(row);
142 assert(col >= 0);
143 assert(col < row->num_cols);
144 const int sprite_offset = col * sheet->sprite_width * sheet->sprite_height;
145 const uint8_t* sprite = &row->pixels[sprite_offset];
146 return sprite;
147}
148
149// -----------------------------------------------------------------------------
150// Renderer state.
151// -----------------------------------------------------------------------------
152
153typedef struct TileData {
154 uint16_t width;
155 uint16_t height;
156 uint16_t pixels_handle; // Handle to the tile's pixels in the pixel pool.
157} TileData;
158
159// File format is already convenient for working in memory.
160typedef Ss_Row SpriteSheetRow;
161typedef Ss_SpriteSheet SpriteSheetData;
162
163typedef struct SpriteData {
164 SpriteSheet sheet; // Handle to the sprite's sheet.
165 ivec2 position;
166 int animation; // Current animation.
167 int frame; // Current frame of animation.
168} SpriteData;
169
170DEF_MEMPOOL_DYN(TilePool, TileData)
171DEF_MEM_DYN(PixelPool, Pixel)
172
173DEF_MEMPOOL_DYN(SpritePool, SpriteData)
174DEF_MEM_DYN(SpriteSheetPool, SpriteSheetData)
175
176typedef struct IsoGfx {
177 int screen_width;
178 int screen_height;
179 int tile_width;
180 int tile_height;
181 int world_width;
182 int world_height;
183 int max_num_sprites;
184 int sprite_sheet_pool_size_bytes;
185 double last_animation_time;
186 Tile* world;
187 Pixel* screen;
188 TilePool tiles;
189 PixelPool pixels;
190 SpritePool sprites;
191 SpriteSheetPool sheets;
192} IsoGfx;
193
194// -----------------------------------------------------------------------------
195// Math and world / tile / screen access.
196// -----------------------------------------------------------------------------
197
198static inline ivec2 ivec2_add(ivec2 a, ivec2 b) {
199 return (ivec2){.x = a.x + b.x, .y = a.y + b.y};
200}
201
202static inline ivec2 ivec2_scale(ivec2 a, int s) {
203 return (ivec2){.x = a.x * s, .y = a.y * s};
204}
205
206static inline ivec2 iso2cart(ivec2 iso, int s, int t, int w) {
207 return (ivec2){
208 .x = (iso.x - iso.y) * (s / 2) + (w / 2), .y = (iso.x + iso.y) * (t / 2)};
209}
210
211// Method 1.
212// static inline vec2 cart2iso(vec2 cart, int s, int t, int w) {
213// const double x = cart.x - (double)(w / 2);
214// const double xiso = (x * t + cart.y * s) / (double)(s * t);
215// return (vec2){
216// .x = (int)(xiso), .y = (int)((2.0 / (double)t) * cart.y - xiso)};
217//}
218
219// Method 2.
220static inline vec2 cart2iso(vec2 cart, int s, int t, int w) {
221 const double one_over_s = 1. / (double)s;
222 const double one_over_t = 1. / (double)t;
223 const double x = cart.x - (double)(w / 2);
224 return (vec2){
225 .x = (one_over_s * x + one_over_t * cart.y),
226 .y = (-one_over_s * x + one_over_t * cart.y)};
227}
228
229static const Pixel* tile_xy_const_ref(
230 const IsoGfx* iso, const TileData* tile, int x, int y) {
231 assert(iso);
232 assert(tile);
233 assert(x >= 0);
234 assert(y >= 0);
235 assert(x < tile->width);
236 assert(y < tile->height);
237 return &mem_get_chunk(&iso->pixels, tile->pixels_handle)[y * tile->width + x];
238}
239
240// static Pixel tile_xy(const IsoGfx* iso, const TileData* tile, int x, int y) {
241// return *tile_xy_const_ref(iso, tile, x, y);
242// }
243
244static Pixel* tile_xy_mut(const IsoGfx* iso, TileData* tile, int x, int y) {
245 return (Pixel*)tile_xy_const_ref(iso, tile, x, y);
246}
247
248static inline const Tile* world_xy_const_ref(const IsoGfx* iso, int x, int y) {
249 assert(iso);
250 assert(x >= 0);
251 assert(y >= 0);
252 assert(x < iso->world_width);
253 assert(y < iso->world_height);
254 return &iso->world[y * iso->world_width + x];
255}
256
257static inline Tile world_xy(const IsoGfx* iso, int x, int y) {
258 return *world_xy_const_ref(iso, x, y);
259}
260
261static inline Tile* world_xy_mut(IsoGfx* iso, int x, int y) {
262 return (Tile*)world_xy_const_ref(iso, x, y);
263}
264
265static inline const Pixel* screen_xy_const_ref(
266 const IsoGfx* iso, int x, int y) {
267 assert(iso);
268 assert(x >= 0);
269 assert(y >= 0);
270 assert(x < iso->screen_width);
271 assert(y < iso->screen_height);
272 return &iso->screen[y * iso->screen_width + x];
273}
274
275static inline Pixel screen_xy(IsoGfx* iso, int x, int y) {
276 return *screen_xy_const_ref(iso, x, y);
277}
278
279static inline Pixel* screen_xy_mut(IsoGfx* iso, int x, int y) {
280 return (Pixel*)screen_xy_const_ref(iso, x, y);
281}
282
283static int calc_num_tile_blocks(
284 int base_tile_width, int base_tile_height, int tile_width,
285 int tile_height) {
286 const int base_tile_size = base_tile_width * base_tile_height;
287 const int tile_size = tile_width * tile_height;
288 const int num_blocks = tile_size / base_tile_size;
289 return num_blocks;
290}
291
292// -----------------------------------------------------------------------------
293// Renderer, world and tile management.
294// -----------------------------------------------------------------------------
295
296IsoGfx* isogfx_new(const IsoGfxDesc* desc) {
297 assert(desc->screen_width > 0);
298 assert(desc->screen_height > 0);
299 // Part of our implementation assumes even widths and heights for precision.
300 assert((desc->screen_width & 1) == 0);
301 assert((desc->screen_height & 1) == 0);
302
303 IsoGfx* iso = calloc(1, sizeof(IsoGfx));
304 if (!iso) {
305 return 0;
306 }
307
308 iso->screen_width = desc->screen_width;
309 iso->screen_height = desc->screen_height;
310
311 iso->last_animation_time = 0.0;
312
313 iso->max_num_sprites = desc->max_num_sprites == 0 ? DEFAULT_MAX_NUM_SPRITES
314 : desc->max_num_sprites;
315 iso->sprite_sheet_pool_size_bytes = desc->sprite_sheet_pool_size_bytes == 0
316 ? DEFAULT_SPRITE_SHEET_POOL_SIZE_BYTES
317 : desc->sprite_sheet_pool_size_bytes;
318
319 const int screen_size = desc->screen_width * desc->screen_height;
320 if (!(iso->screen = calloc(screen_size, sizeof(Pixel)))) {
321 goto cleanup;
322 }
323
324 return iso;
325
326cleanup:
327 isogfx_del(&iso);
328 return 0;
329}
330
331/// Destroy the world, its tile set, and the underlying pools.
332static void destroy_world(IsoGfx* iso) {
333 assert(iso);
334 if (iso->world) {
335 free(iso->world);
336 iso->world = 0;
337 }
338 mempool_del(&iso->tiles);
339 mem_del(&iso->pixels);
340}
341
342/// Destroy all loaded sprites and the underlying pools.
343static void destroy_sprites(IsoGfx* iso) {
344 assert(iso);
345 mempool_del(&iso->sprites);
346 mem_del(&iso->sheets);
347}
348
349void isogfx_del(IsoGfx** pIso) {
350 assert(pIso);
351 IsoGfx* iso = *pIso;
352 if (iso) {
353 destroy_world(iso);
354 destroy_sprites(iso);
355 if (iso->screen) {
356 free(iso->screen);
357 iso->screen = 0;
358 }
359 free(iso);
360 *pIso = 0;
361 }
362}
363
364bool isogfx_make_world(IsoGfx* iso, const WorldDesc* desc) {
365 assert(iso);
366 assert(desc);
367 assert(desc->tile_width > 0);
368 assert(desc->tile_height > 0);
369 // Part of our implementation assumes even widths and heights for greater
370 // precision.
371 assert((desc->tile_width & 1) == 0);
372 assert((desc->tile_height & 1) == 0);
373
374 // Handle recreation by destroying the previous world.
375 destroy_world(iso);
376
377 iso->tile_width = desc->tile_width;
378 iso->tile_height = desc->tile_height;
379 iso->world_width = desc->world_width;
380 iso->world_height = desc->world_height;
381
382 const int world_size = desc->world_width * desc->world_height;
383 const int tile_size = desc->tile_width * desc->tile_height;
384 const int tile_size_bytes = tile_size * (int)sizeof(Pixel);
385 const int tile_pool_size =
386 desc->max_num_tiles > 0 ? desc->max_num_tiles : DEFAULT_MAX_NUM_TILES;
387
388 if (!(iso->world = calloc(world_size, sizeof(Tile)))) {
389 goto cleanup;
390 }
391 if (!mempool_make_dyn(&iso->tiles, world_size, sizeof(TileData))) {
392 goto cleanup;
393 }
394 if (!mem_make_dyn(&iso->pixels, tile_pool_size, tile_size_bytes)) {
395 goto cleanup;
396 }
397
398 return true;
399
400cleanup:
401 destroy_world(iso);
402 return false;
403}
404
405bool isogfx_load_world(IsoGfx* iso, const char* filepath) {
406 assert(iso);
407 assert(filepath);
408
409 bool success = false;
410
411 // Handle recreation by destroying the previous world.
412 destroy_world(iso);
413
414 // Load the map.
415 printf("Load tile map: %s\n", filepath);
416 Tm_Map* map = read_file(filepath);
417 if (!map) {
418 goto cleanup;
419 }
420
421 // Allocate memory for the map and tile sets.
422 const int world_size = map->world_width * map->world_height;
423 const int base_tile_size = map->base_tile_width * map->base_tile_height;
424 const int base_tile_size_bytes = base_tile_size * (int)sizeof(Pixel);
425 // TODO: Need to get the total number of tiles from the map.
426 const int tile_pool_size = DEFAULT_MAX_NUM_TILES;
427
428 if (!(iso->world = calloc(world_size, sizeof(Tile)))) {
429 goto cleanup;
430 }
431 if (!mempool_make_dyn(&iso->tiles, tile_pool_size, sizeof(TileData))) {
432 goto cleanup;
433 }
434 if (!mem_make_dyn(&iso->pixels, tile_pool_size, base_tile_size_bytes)) {
435 goto cleanup;
436 }
437
438 // Load the tile sets.
439 const Tm_Layer* layer = &map->layers[0];
440 // TODO: Handle num_layers layers.
441 for (int i = 0; i < 1; ++i) {
442 const char* ts_path = layer->tileset_path;
443
444 // Tile set path is relative to the tile map file. Make it relative to the
445 // current working directory before loading.
446 char ts_path_cwd[PATH_MAX] = {0};
447 if (!path_make_relative(filepath, ts_path, ts_path_cwd, PATH_MAX)) {
448 goto cleanup;
449 }
450
451 Ts_TileSet* tileset = read_file(ts_path_cwd);
452 if (!tileset) {
453 goto cleanup;
454 };
455
456 // Load tile data.
457 const Ts_Tile* tile = &tileset->tiles[0];
458 for (uint16_t j = 0; j < tileset->num_tiles; ++j) {
459 // Tile dimensions should be a multiple of the base tile size.
460 assert((tile->width % map->base_tile_width) == 0);
461 assert((tile->height % map->base_tile_height) == 0);
462
463 // Allocate N base tile size blocks for the tile.
464 const uint16_t tile_size = tile->width * tile->height;
465 const int num_blocks = tile_size / base_tile_size;
466 Pixel* pixels = mem_alloc(&iso->pixels, num_blocks);
467 assert(pixels);
468 memcpy(pixels, tile->pixels, tile_size * sizeof(Pixel));
469
470 // Allocate the tile data.
471 TileData* tile_data = mempool_alloc(&iso->tiles);
472 assert(tile_data);
473 tile_data->width = tile->width;
474 tile_data->height = tile->height;
475 tile_data->pixels_handle =
476 (uint16_t)mem_get_chunk_handle(&iso->pixels, pixels);
477
478 tile = ts_tileset_get_next_tile(tileset, tile);
479 }
480
481 printf("Loaded tile set (%u tiles): %s\n", tileset->num_tiles, ts_path_cwd);
482
483 free(tileset);
484 layer = tm_map_get_next_layer(map, layer);
485 }
486
487 // Load the map into the world.
488 layer = &map->layers[0];
489 // TODO: Handle num_layers layers.
490 for (int i = 0; i < 1; ++i) {
491 memcpy(iso->world, layer->tiles, world_size * sizeof(Tile));
492
493 // TODO: We need to handle 'firsgid' in TMX files.
494 for (int j = 0; j < world_size; ++j) {
495 iso->world[j] -= 1;
496 }
497
498 layer = tm_map_get_next_layer(map, layer);
499 }
500
501 iso->world_width = map->world_width;
502 iso->world_height = map->world_height;
503 iso->tile_width = map->base_tile_width;
504 iso->tile_height = map->base_tile_height;
505
506 success = true;
507
508cleanup:
509 if (map) {
510 free(map);
511 }
512 if (!success) {
513 destroy_world(iso);
514 }
515 return success;
516}
517
518int isogfx_world_width(const IsoGfx* iso) {
519 assert(iso);
520 return iso->world_width;
521}
522
523int isogfx_world_height(const IsoGfx* iso) {
524 assert(iso);
525 return iso->world_height;
526}
527
528/// Create a tile mask procedurally.
529static void make_tile_from_colour(
530 const IsoGfx* iso, Pixel colour, TileData* tile) {
531 assert(iso);
532 assert(tile);
533
534 const int width = tile->width;
535 const int height = tile->height;
536 const int r = width / height;
537
538 for (int y = 0; y < height / 2; ++y) {
539 const int mask_start = width / 2 - r * y - 1;
540 const int mask_end = width / 2 + r * y + 1;
541 for (int x = 0; x < width; ++x) {
542 const bool mask = (mask_start <= x) && (x <= mask_end);
543 const Pixel val = mask ? colour : (Pixel){.r = 0, .g = 0, .b = 0, .a = 0};
544
545 // Top half.
546 *tile_xy_mut(iso, tile, x, y) = val;
547
548 // Bottom half reflects the top half.
549 const int y_reflected = height - y - 1;
550 *tile_xy_mut(iso, tile, x, y_reflected) = val;
551 }
552 }
553}
554
555Tile isogfx_make_tile(IsoGfx* iso, const TileDesc* desc) {
556 assert(iso);
557 assert(desc);
558 // Client must create world before creating tiles.
559 assert(iso->tile_width > 0);
560 assert(iso->tile_height > 0);
561
562 TileData* tile = mempool_alloc(&iso->tiles);
563 assert(tile); // TODO: Make this a hard assert.
564
565 const int num_blocks = calc_num_tile_blocks(
566 iso->tile_width, iso->tile_height, desc->width, desc->height);
567
568 Pixel* pixels = mem_alloc(&iso->pixels, num_blocks);
569 assert(pixels); // TODO: Make this a hard assert.
570
571 tile->width = desc->width;
572 tile->height = desc->height;
573 tile->pixels_handle = mem_get_chunk_handle(&iso->pixels, pixels);
574
575 switch (desc->type) {
576 case TileFromColour:
577 make_tile_from_colour(iso, desc->colour, tile);
578 break;
579 case TileFromFile:
580 assert(false); // TODO
581 break;
582 case TileFromMemory:
583 assert(false); // TODO
584 break;
585 }
586
587 return (Tile)mempool_get_block_index(&iso->tiles, tile);
588}
589
590void isogfx_set_tile(IsoGfx* iso, int x, int y, Tile tile) {
591 assert(iso);
592 *world_xy_mut(iso, x, y) = tile;
593}
594
595void isogfx_set_tiles(IsoGfx* iso, int x0, int y0, int x1, int y1, Tile tile) {
596 assert(iso);
597 for (int y = y0; y < y1; ++y) {
598 for (int x = x0; x < x1; ++x) {
599 isogfx_set_tile(iso, x, y, tile);
600 }
601 }
602}
603
604bool isogfx_load_sprite_sheet(
605 IsoGfx* iso, const char* filepath, SpriteSheet* p_sheet) {
606 assert(iso);
607 assert(filepath);
608 assert(p_sheet);
609
610 bool success = false;
611
612 // Lazy initialization of sprite pools.
613 if (mempool_capacity(&iso->sprites) == 0) {
614 if (!mempool_make_dyn(
615 &iso->sprites, iso->max_num_sprites, sizeof(SpriteData))) {
616 return false;
617 }
618 }
619 if (mem_capacity(&iso->sheets) == 0) {
620 // Using a block size of 1 byte for sprite sheet data.
621 if (!mem_make_dyn(&iso->sheets, iso->sprite_sheet_pool_size_bytes, 1)) {
622 return false;
623 }
624 }
625
626 // Load sprite sheet file.
627 printf("Load sprite sheet: %s\n", filepath);
628 FILE* file = fopen(filepath, "rb");
629 if (file == NULL) {
630 goto cleanup;
631 }
632 const size_t sheet_size = get_file_size(file);
633 SpriteSheetData* ss_sheet = mem_alloc(&iso->sheets, sheet_size);
634 if (!ss_sheet) {
635 goto cleanup;
636 }
637 if (fread(ss_sheet, sheet_size, 1, file) != 1) {
638 goto cleanup;
639 }
640
641 *p_sheet = mem_get_chunk_handle(&iso->sheets, ss_sheet);
642 success = true;
643
644cleanup:
645 // Pools remain initialized since client may attempt to load other sprites.
646 if (file != NULL) {
647 fclose(file);
648 }
649 if (!success) {
650 if (ss_sheet) {
651 mem_free(&iso->sheets, &ss_sheet);
652 }
653 }
654 return success;
655}
656
657Sprite isogfx_make_sprite(IsoGfx* iso, SpriteSheet sheet) {
658 assert(iso);
659
660 SpriteData* sprite = mempool_alloc(&iso->sprites);
661 assert(sprite);
662
663 sprite->sheet = sheet;
664
665 return mempool_get_block_index(&iso->sprites, sprite);
666}
667
668#define with_sprite(SPRITE, BODY) \
669 { \
670 SpriteData* data = mempool_get_block(&iso->sprites, sprite); \
671 assert(data); \
672 BODY; \
673 }
674
675void isogfx_set_sprite_position(IsoGfx* iso, Sprite sprite, int x, int y) {
676 assert(iso);
677 with_sprite(sprite, {
678 data->position.x = x;
679 data->position.y = y;
680 });
681}
682
683void isogfx_set_sprite_animation(IsoGfx* iso, Sprite sprite, int animation) {
684 assert(iso);
685 with_sprite(sprite, { data->animation = animation; });
686}
687
688void isogfx_update(IsoGfx* iso, double t) {
689 assert(iso);
690
691 // If this is the first time update() is called after initialization, just
692 // record the starting animation time.
693 if (iso->last_animation_time == 0.0) {
694 iso->last_animation_time = t;
695 return;
696 }
697
698 if ((t - iso->last_animation_time) >= ANIMATION_UPDATE_DELTA) {
699 // TODO: Consider linking animated sprites in a list so that we only walk
700 // over those here and not also the static sprites.
701 mempool_foreach(&iso->sprites, sprite, {
702 const SpriteSheetData* sheet = mem_get_chunk(&iso->sheets, sprite->sheet);
703 assert(sheet); // TODO: Make this a hard assert inside the mem/pool.
704 const SpriteSheetRow* row =
705 get_sprite_sheet_row(sheet, sprite->animation);
706 sprite->frame = (sprite->frame + 1) % row->num_cols;
707 });
708
709 iso->last_animation_time = t;
710 }
711}
712
713// -----------------------------------------------------------------------------
714// Rendering and picking.
715// -----------------------------------------------------------------------------
716
717typedef struct CoordSystem {
718 ivec2 o; /// Origin.
719 ivec2 x;
720 ivec2 y;
721} CoordSystem;
722
723/// Create the basis for the isometric coordinate system with origin and vectors
724/// expressed in the Cartesian system.
725static CoordSystem make_iso_coord_system(const IsoGfx* iso) {
726 assert(iso);
727 const ivec2 o = {iso->screen_width / 2, 0};
728 const ivec2 x = {.x = iso->tile_width / 2, .y = iso->tile_height / 2};
729 const ivec2 y = {.x = -iso->tile_width / 2, .y = iso->tile_height / 2};
730 return (CoordSystem){o, x, y};
731}
732
733/// Get the screen position of the top diamond-corner of the tile at world
734/// (x,y).
735static ivec2 GetTileScreenOrigin(
736 const CoordSystem iso_space, int world_x, int world_y) {
737 const ivec2 vx_offset = ivec2_scale(iso_space.x, world_x);
738 const ivec2 vy_offset = ivec2_scale(iso_space.y, world_y);
739 const ivec2 screen_origin =
740 ivec2_add(iso_space.o, ivec2_add(vx_offset, vy_offset));
741
742 return screen_origin;
743}
744
745static Pixel alpha_blend(Pixel src, Pixel dst) {
746 if ((src.a == 255) || (dst.a == 0)) {
747 return src;
748 }
749 const uint16_t one_minus_alpha = 255 - src.a;
750#define blend(s, d) \
751 (Channel)( \
752 (double)((uint16_t)s * (uint16_t)src.a + \
753 (uint16_t)d * one_minus_alpha) / \
754 255.0)
755 return (Pixel){
756 .r = blend(src.r, dst.r),
757 .g = blend(src.g, dst.g),
758 .b = blend(src.b, dst.b),
759 .a = src.a};
760}
761
762/// Draw a rectangle (tile or sprite).
763///
764/// The rectangle's top-left corner is mapped to the screen space position given
765/// by 'top_left'.
766///
767/// The rectangle's pixels are assumed to be arranged in a linear, row-major
768/// fashion.
769///
770/// If indices are given, then the image is assumed to be colour-paletted, where
771/// 'pixels' is the palette and 'indices' the pixel indices. Otherwise, the
772/// image is assumed to be in plain RGBA format.
773static void draw_rect(
774 IsoGfx* iso, ivec2 top_left, int rect_width, int rect_height,
775 const Pixel* pixels, const uint8_t* indices) {
776 assert(iso);
777
778#define rect_pixel(X, Y) \
779 (indices ? pixels[indices[Y * rect_width + X]] : pixels[Y * rect_width + X])
780
781 // Rect origin can be outside screen bounds, so we must offset accordingly to
782 // draw only the visible portion.
783#define max(a, b) (a > b ? a : b)
784 const int px_offset = max(0, -top_left.x);
785 const int py_offset = max(0, -top_left.y);
786
787 // Rect can exceed screen bounds, so clip along Y and X as we draw.
788 for (int py = py_offset;
789 (py < rect_height) && (top_left.y + py < iso->screen_height); ++py) {
790 const int sy = top_left.y + py;
791 for (int px = px_offset;
792 (px < rect_width) && (top_left.x + px < iso->screen_width); ++px) {
793 const Pixel colour = rect_pixel(px, py);
794 if (colour.a > 0) {
795 const int sx = top_left.x + px;
796 const Pixel dst = screen_xy(iso, sx, sy);
797 const Pixel final = alpha_blend(colour, dst);
798 *screen_xy_mut(iso, sx, sy) = final;
799 }
800 }
801 }
802}
803
804/// Draw a tile.
805///
806/// 'screen_origin' is the screen coordinates of the top diamond-corner of the
807/// tile (the base tile for super tiles).
808/// World (0, 0) -> (screen_width / 2, 0).
809static void draw_tile(IsoGfx* iso, ivec2 screen_origin, Tile tile) {
810 assert(iso);
811
812 const TileData* tile_data = mempool_get_block(&iso->tiles, tile);
813 assert(tile_data);
814 const Pixel* pixels = tile_xy_const_ref(iso, tile_data, 0, 0);
815
816 // Move from the top diamond-corner to the top-left corner of the tile image.
817 // For regular tiles, tile height == base tile height, so the y offset is 0.
818 // For super tiles, move as high up as the height of the tile.
819 const ivec2 offset = {
820 -(iso->tile_width / 2), tile_data->height - iso->tile_height};
821 const ivec2 top_left = ivec2_add(screen_origin, offset);
822
823 draw_rect(iso, top_left, tile_data->width, tile_data->height, pixels, 0);
824}
825
826static void draw_world(IsoGfx* iso) {
827 assert(iso);
828
829 const int W = iso->screen_width;
830 const int H = iso->screen_height;
831
832 memset(iso->screen, 0, W * H * sizeof(Pixel));
833
834 const CoordSystem iso_space = make_iso_coord_system(iso);
835
836 // TODO: Culling.
837 // Ex: map the screen corners to tile space to cull.
838 // Ex: walk in screen space and fetch the tile.
839 // The tile-centric approach might be more cache-friendly since the
840 // screen-centric approach would juggle multiple tiles throughout the scan.
841 for (int wy = 0; wy < iso->world_height; ++wy) {
842 for (int wx = 0; wx < iso->world_width; ++wx) {
843 const Tile tile = world_xy(iso, wx, wy);
844 const ivec2 screen_origin = GetTileScreenOrigin(iso_space, wx, wy);
845 draw_tile(iso, screen_origin, tile);
846 }
847 }
848}
849
850static void draw_sprite(
851 IsoGfx* iso, ivec2 origin, const SpriteData* sprite,
852 const SpriteSheetData* sheet) {
853 assert(iso);
854 assert(sprite);
855 assert(sheet);
856 assert(sprite->animation >= 0);
857 assert(sprite->animation < sheet->num_rows);
858 assert(sprite->frame >= 0);
859
860 const SpriteSheetRow* row = get_sprite_sheet_row(sheet, sprite->animation);
861 const uint8_t* frame = get_sprite_sheet_sprite(sheet, row, sprite->frame);
862 draw_rect(
863 iso, origin, sheet->sprite_width, sheet->sprite_height,
864 sheet->palette.colours, frame);
865}
866
867static void draw_sprites(IsoGfx* iso) {
868 assert(iso);
869
870 const CoordSystem iso_space = make_iso_coord_system(iso);
871
872 mempool_foreach(&iso->sprites, sprite, {
873 const SpriteSheetData* sheet = mem_get_chunk(&iso->sheets, sprite->sheet);
874 assert(sheet);
875
876 const ivec2 screen_origin =
877 GetTileScreenOrigin(iso_space, sprite->position.x, sprite->position.y);
878 draw_sprite(iso, screen_origin, sprite, sheet);
879 });
880}
881
882void isogfx_render(IsoGfx* iso) {
883 assert(iso);
884 draw_world(iso);
885 draw_sprites(iso);
886}
887
888void isogfx_draw_tile(IsoGfx* iso, int x, int y, Tile tile) {
889 assert(iso);
890 assert(x >= 0);
891 assert(y >= 0);
892 assert(x < iso->world_width);
893 assert(y < iso->world_height);
894
895 const CoordSystem iso_space = make_iso_coord_system(iso);
896 const ivec2 screen_origin = GetTileScreenOrigin(iso_space, x, y);
897 draw_tile(iso, screen_origin, tile);
898}
899
900bool isogfx_resize(IsoGfx* iso, int screen_width, int screen_height) {
901 assert(iso);
902 assert(iso->screen);
903
904 const int current_size = iso->screen_width * iso->screen_height;
905 const int new_size = screen_width * screen_height;
906
907 if (new_size > current_size) {
908 Pixel* new_screen = calloc(new_size, sizeof(Pixel));
909 if (new_screen) {
910 free(iso->screen);
911 iso->screen = new_screen;
912 } else {
913 return false;
914 }
915 }
916 iso->screen_width = screen_width;
917 iso->screen_height = screen_height;
918 return true;
919}
920
921void isogfx_get_screen_size(const IsoGfx* iso, int* width, int* height) {
922 assert(iso);
923 assert(width);
924 assert(height);
925 *width = iso->screen_width;
926 *height = iso->screen_height;
927}
928
929const Pixel* isogfx_get_screen_buffer(const IsoGfx* iso) {
930 assert(iso);
931 return iso->screen;
932}
933
934void isogfx_pick_tile(
935 const IsoGfx* iso, double xcart, double ycart, int* xiso, int* yiso) {
936 assert(iso);
937 assert(xiso);
938 assert(yiso);
939
940 const vec2 xy_iso = cart2iso(
941 (vec2){.x = xcart, .y = ycart}, iso->tile_width, iso->tile_height,
942 iso->screen_width);
943
944 if ((0 <= xy_iso.x) && (xy_iso.x < iso->world_width) && (0 <= xy_iso.y) &&
945 (xy_iso.y < iso->world_height)) {
946 *xiso = (int)xy_iso.x;
947 *yiso = (int)xy_iso.y;
948 } else {
949 *xiso = -1;
950 *yiso = -1;
951 }
952}
diff --git a/gfx-iso/tools/mkasset.py b/gfx-iso/tools/mkasset.py
deleted file mode 100644
index 3ca8a1d..0000000
--- a/gfx-iso/tools/mkasset.py
+++ /dev/null
@@ -1,324 +0,0 @@
1# Converts assets to binary formats (.ts, .tm, .ss) for the engine.
2#
3# Input file formats:
4# - Tiled tile set (.tsx)
5# - Tiled tile map (.tmx)
6# - Sprite sheets (.jpg, .png, etc), 1 row per animation.
7#
8# Output file formats:
9# - Binary tile set file (.ts)
10# - Binary tile map file (.tm)
11# - Binary sprite sheet file (.ss)
12#
13import argparse
14import ctypes
15import os
16from PIL import Image
17import sys
18from xml.etree import ElementTree
19
20# Maximum length of path strings in .TS and .TM files.
21# Must match the engine's value.
22MAX_PATH_LENGTH = 128
23
24
25def drop_extension(filepath):
26 return filepath[:filepath.rfind('.')]
27
28
29def to_char_array(string, length):
30 """Convert a string to a fixed-length ASCII char array.
31
32 The length of str must be at most length-1 so that the resulting string can
33 be null-terminated.
34 """
35 assert (len(string) < length)
36 chars = string.encode("ascii")
37 nulls = ("\0" * (length - len(string))).encode("ascii")
38 return chars + nulls
39
40
41def convert_tsx(input_filepath, output_filepath):
42 """Converts a Tiled .tsx tileset file to a .TS tile set file."""
43 xml = ElementTree.parse(input_filepath)
44 root = xml.getroot()
45
46 tile_count = int(root.attrib["tilecount"])
47 max_tile_width = int(root.attrib["tilewidth"])
48 max_tile_height = int(root.attrib["tileheight"])
49
50 print(f"Tile count: {tile_count}")
51 print(f"Max width: {max_tile_width}")
52 print(f"Max height: {max_tile_height}")
53
54 with open(output_filepath, 'bw') as output:
55 output.write(ctypes.c_uint16(tile_count))
56 output.write(ctypes.c_uint16(max_tile_width))
57 output.write(ctypes.c_uint16(max_tile_height))
58
59 num_tile = 0
60 for tile in root:
61 # Skip the "grid" and other non-tile elements.
62 if not tile.tag == "tile":
63 continue
64
65 # Assuming tiles are numbered 0..N.
66 tile_id = int(tile.attrib["id"])
67 assert (tile_id == num_tile)
68 num_tile += 1
69
70 image = tile[0]
71 tile_width = int(image.attrib["width"])
72 tile_height = int(image.attrib["height"])
73 tile_path = image.attrib["source"]
74
75 output.write(ctypes.c_uint16(tile_width))
76 output.write(ctypes.c_uint16(tile_height))
77
78 with Image.open(tile_path) as im:
79 bytes = im.convert('RGBA').tobytes()
80 output.write(bytes)
81
82
83def convert_tmx(input_filepath, output_filepath):
84 """Converts a Tiled .tmx file to a .TM tile map file."""
85 xml = ElementTree.parse(input_filepath)
86 root = xml.getroot()
87
88 map_width = int(root.attrib["width"])
89 map_height = int(root.attrib["height"])
90 base_tile_width = int(root.attrib["tilewidth"])
91 base_tile_height = int(root.attrib["tileheight"])
92 num_layers = 1
93
94 print(f"Map width: {map_width}")
95 print(f"Map height: {map_height}")
96 print(f"Tile width: {base_tile_width}")
97 print(f"Tile height: {base_tile_height}")
98
99 with open(output_filepath, 'bw') as output:
100 output.write(ctypes.c_uint16(map_width))
101 output.write(ctypes.c_uint16(map_height))
102 output.write(ctypes.c_uint16(base_tile_width))
103 output.write(ctypes.c_uint16(base_tile_height))
104 output.write(ctypes.c_uint16(num_layers))
105
106 tileset_path = None
107
108 for child in root:
109 if child.tag == "tileset":
110 tileset = child
111 tileset_path = tileset.attrib["source"]
112
113 print(f"Tile set: {tileset_path}")
114
115 tileset_path = tileset_path.replace("tsx", "ts")
116 elif child.tag == "layer":
117 layer = child
118 layer_id = int(layer.attrib["id"])
119 layer_width = int(layer.attrib["width"])
120 layer_height = int(layer.attrib["height"])
121
122 print(f"Layer: {layer_id}")
123 print(f"Width: {layer_width}")
124 print(f"Height: {layer_height}")
125
126 assert (tileset_path)
127 output.write(to_char_array(tileset_path, MAX_PATH_LENGTH))
128
129 # Assume the layer's dimensions matches the map's.
130 assert (layer_width == map_width)
131 assert (layer_height == map_height)
132
133 data = layer[0]
134 # Handle other encodings later.
135 assert (data.attrib["encoding"] == "csv")
136
137 csv = data.text.strip()
138 rows = csv.split('\n')
139 for row in rows:
140 tile_ids = [x.strip() for x in row.split(',') if x]
141 for tile_id in tile_ids:
142 output.write(ctypes.c_uint16(int(tile_id)))
143
144
145def get_num_cols(image, sprite_width):
146 """Return the number of non-empty columns in the image.
147
148 Assumes no gaps in the columns.
149 """
150 assert (image.width % sprite_width == 0)
151 num_cols = image.width // sprite_width
152
153 # Start the search from right to left.
154 for col in reversed(range(1, num_cols)):
155 left = (col - 1) * sprite_width
156 right = col * sprite_width
157 rect = image.crop((left, 0, right, image.height))
158 min_max = rect.getextrema()
159 for (channel_min, channel_max) in min_max:
160 if channel_min != 0 or channel_max != 0:
161 # 'col' is the rightmost non-empty column.
162 # Assuming no gaps, col+1 is the number of non-empty columns.
163 return col + 1
164
165 return 0
166
167
168def get_sprite_sheet_rows(im, sprite_width, sprite_height):
169 """Gets the individual rows of a sprite sheet.
170
171 The input sprite sheet can have any number of rows.
172
173 Returns a list of lists [[sprite]], one inner list for the columns in each
174 row.
175 """
176 # Sprite sheet's width and height must be integer multiples of the
177 # sprite's width and height.
178 assert (im.width % sprite_width == 0)
179 assert (im.height % sprite_height == 0)
180
181 num_rows = im.height // sprite_height
182
183 rows = []
184 for row in range(num_rows):
185 # Get the number of columns.
186 upper = row * sprite_height
187 lower = (row + 1) * sprite_height
188 whole_row = im.crop((0, upper, im.width, lower))
189 num_cols = get_num_cols(whole_row, sprite_width)
190 assert (num_cols > 0)
191
192 # Crop the row into N columns.
193 cols = []
194 for i in range(num_cols):
195 left = i * sprite_width
196 right = (i + 1) * sprite_width
197 sprite = im.crop((left, upper, right, lower))
198 cols.append(sprite)
199
200 assert (len(cols) == num_cols)
201 rows.append(cols)
202
203 return rows
204
205
206def make_image_from_rows(rows, sprite_width, sprite_height):
207 """Concatenate the rows into a single RGBA image."""
208 im_width = sprite_width * max(len(row) for row in rows)
209 im_height = len(rows) * sprite_height
210 im = Image.new('RGBA', (im_width, im_height))
211 y = 0
212 for row in rows:
213 x = 0
214 for sprite in row:
215 im.paste(sprite.convert('RGBA'), (x, y))
216 x += sprite_width
217 y += sprite_height
218 return im
219
220
221def convert_sprite_sheet(input_file_paths, sprite_width, sprite_height,
222 output_filepath):
223 """Converts a set of sprite sheet images into a binary sprite sheet file
224 (.ss).
225
226 The input sprite sheets can have any number of rows, one row per animation.
227 All rows from all sprite sheets are concatenated in the output file.
228
229 The sprite's width and height is assumed constant throughout the input
230 sprite sheets.
231 """
232 rows = []
233 for input_filepath in input_file_paths:
234 with Image.open(input_filepath) as sprite_sheet:
235 rows.extend(
236 get_sprite_sheet_rows(sprite_sheet, sprite_width,
237 sprite_height))
238
239 im = make_image_from_rows(rows, sprite_width, sprite_height)
240 im = im.convert(mode="P", palette=Image.ADAPTIVE, colors=256)
241
242 # The sprite data in 'rows' is no longer needed.
243 # Keep just the number of columns per row.
244 rows = [len(row) for row in rows]
245
246 with open(output_filepath, 'bw') as output:
247 output.write(ctypes.c_uint16(sprite_width))
248 output.write(ctypes.c_uint16(sprite_height))
249 output.write(ctypes.c_uint16(len(rows)))
250
251 # Write palette.
252 # getpalette() returns 256 colors, but the palette might use less than
253 # that. getcolors() returns the number of unique colors.
254 # getpalette() also returns a flattened list, which is why we must *4.
255 num_colours = len(im.getcolors())
256 colours = im.getpalette(rawmode="RGBA")[:4 * num_colours]
257 palette = []
258 for i in range(0, 4 * num_colours, 4):
259 palette.append((colours[i], colours[i + 1], colours[i + 2],
260 colours[i + 3]))
261
262 output.write(ctypes.c_uint16(len(palette)))
263 output.write(bytearray(colours))
264
265 print(f"Sprite width: {sprite_width}")
266 print(f"Sprite height: {sprite_height}")
267 print(f"Rows: {len(rows)}")
268 print(f"Colours: {len(palette)}")
269
270 # print("Palette")
271 # for i, colour in enumerate(palette):
272 # print(f"{i}: {colour}")
273
274 for row, num_columns in enumerate(rows):
275 output.write(ctypes.c_uint16(num_columns))
276 upper = row * sprite_height
277 lower = (row + 1) * sprite_height
278 for col in range(num_columns):
279 left = col * sprite_width
280 right = (col + 1) * sprite_width
281 sprite = im.crop((left, upper, right, lower))
282 sprite_bytes = sprite.tobytes()
283
284 assert (len(sprite_bytes) == sprite_width * sprite_height)
285 output.write(sprite_bytes)
286
287 # if (row == 0) and (col == 0):
288 # print(f"Sprite: ({len(sprite_bytes)})")
289 # print(list(sprite_bytes))
290 # sprite.save("out.png")
291
292
293def main():
294 parser = argparse.ArgumentParser()
295 parser.add_argument("input",
296 nargs="+",
297 help="Input file (.tsx, .tmx) or path regex (sprite sheets)")
298 parser.add_argument("--width", type=int, help="Sprite width in pixels")
299 parser.add_argument("--height", type=int, help="Sprite height in pixels")
300 parser.add_argument("--out", help="Output file (sprite sheets)")
301 args = parser.parse_args()
302
303 if ".tsx" in args.input:
304 output_filepath_no_ext = drop_extension(args.input)
305 output_filepath = output_filepath_no_ext + ".ts"
306 convert_tsx(args.input, output_filepath)
307 elif ".tmx" in args.input:
308 output_filepath_no_ext = drop_extension(args.input)
309 output_filepath = output_filepath_no_ext + ".tm"
310 convert_tmx(args.input, output_filepath)
311 else:
312 # Sprite sheets.
313 if not args.width or not args.height:
314 print("Sprite width and height must be given")
315 return 1
316 output_filepath = args.out if args.out else "out.ss"
317 convert_sprite_sheet(args.input, args.width, args.height,
318 output_filepath)
319
320 return 0
321
322
323if __name__ == '__main__':
324 sys.exit(main())
diff --git a/gfx/CMakeLists.txt b/gfx/CMakeLists.txt
deleted file mode 100644
index 7d629dc..0000000
--- a/gfx/CMakeLists.txt
+++ /dev/null
@@ -1,89 +0,0 @@
1cmake_minimum_required(VERSION 3.0)
2
3include(cmake/shader.txt)
4
5add_subdirectory(contrib/cgltf)
6add_subdirectory(contrib/cgltf-tangents)
7add_subdirectory(contrib/stb)
8
9project(gfx)
10
11add_shader_library(shaders
12 shaders/brdf_integration_map.frag
13 shaders/cook_torrance.frag
14 shaders/cook_torrance.vert
15 shaders/cubemap_filtering.vert
16 shaders/debug3d.frag
17 shaders/debug3d.vert
18 shaders/immediate_mode.frag
19 shaders/immediate_mode.vert
20 shaders/irradiance_map.frag
21 shaders/prefiltered_environment_map.frag
22 shaders/quad.vert
23 shaders/skyquad.frag
24 shaders/skyquad.vert
25 shaders/view_normal_mapped_normals.frag
26 shaders/view_normal_mapped_normals.vert
27 shaders/view_normals.frag
28 shaders/view_normals.vert
29 shaders/view_tangents.frag
30 shaders/view_tangents.vert
31 shaders/view_texture.frag
32 shaders/view_texture.vert)
33
34add_library(gfx SHARED
35 src/asset/asset_cache.c
36 src/asset/model.c
37 src/asset/texture.c
38 src/core/buffer.c
39 src/core/core.c
40 src/core/framebuffer.c
41 src/core/geometry.c
42 src/core/renderbuffer.c
43 src/core/shader_program.c
44 src/core/shader.c
45 src/core/texture.c
46 src/renderer/imm_renderer.c
47 src/renderer/renderer.c
48 src/scene/animation.c
49 src/scene/camera.c
50 src/scene/light.c
51 src/scene/material.c
52 src/scene/mesh.c
53 src/scene/model.c
54 src/scene/node.c
55 src/scene/object.c
56 src/scene/scene.c
57 src/scene/scene_memory.c
58 src/gfx.c
59 src/util/geometry.c
60 src/util/ibl.c
61 src/util/shader.c
62 src/util/skyquad.c)
63
64target_include_directories(gfx PUBLIC
65 include)
66
67target_include_directories(gfx PRIVATE
68 src)
69
70target_compile_options(gfx PRIVATE -std=gnu11 -Wall -Wextra -Wpedantic)
71
72target_link_libraries(gfx PUBLIC
73 cstring
74 math)
75
76target_link_libraries(gfx PRIVATE
77 cassert
78 cgltf
79 cgltf-tangents
80 error
81 gfx-app
82 log
83 mempool
84 shaders
85 stb
86 # System libraries.
87 GL
88 # Required to initialize GLAD.
89 -ldl)
diff --git a/gfx/include/gfx/renderer.h b/gfx/include/gfx/renderer.h
deleted file mode 100644
index 2a4ada1..0000000
--- a/gfx/include/gfx/renderer.h
+++ /dev/null
@@ -1,104 +0,0 @@
1#pragma once
2
3#include <math/aabb2.h>
4#include <math/aabb3.h>
5#include <math/camera.h>
6#include <math/defs.h>
7#include <math/mat4.h>
8#include <math/vec3.h>
9#include <math/vec4.h>
10
11typedef struct GfxCore GfxCore;
12typedef struct Scene Scene;
13typedef struct SceneCamera SceneCamera;
14
15typedef struct ImmRenderer ImmRenderer;
16typedef struct Renderer Renderer;
17
18// -----------------------------------------------------------------------------
19// Main Renderer.
20// -----------------------------------------------------------------------------
21
22typedef enum RenderSceneMode {
23 RenderDefault,
24 RenderDebug,
25 RenderNormals,
26 RenderNormalMappedNormals,
27 RenderTangents
28} RenderSceneMode;
29
30typedef struct RenderSceneParams {
31 RenderSceneMode mode;
32 const Scene* scene;
33 const SceneCamera* camera;
34} RenderSceneParams;
35
36/// Render the scene.
37void gfx_render_scene(Renderer*, const RenderSceneParams*);
38
39/// Update the scene.
40void gfx_update(Scene*, const SceneCamera*, R t);
41
42// -----------------------------------------------------------------------------
43// Immediate Mode Renderer.
44// -----------------------------------------------------------------------------
45
46/// Prepare the graphics systems for immediate-mode rendering.
47///
48/// Call this before issuing any immediate-mode rendering draws.
49void gfx_imm_start(ImmRenderer*);
50
51/// End immediate mode rendering.
52///
53/// Call this after issuing immediate-mode rendering draws and before swapping
54/// buffers.
55void gfx_imm_end(ImmRenderer*);
56
57/// Draw a set of triangles.
58void gfx_imm_draw_triangles(ImmRenderer*, const vec3[], size_t num_triangles);
59
60/// Draw a triangle.
61void gfx_imm_draw_triangle(ImmRenderer*, const vec3[3]);
62
63/// Draw a bounding box.
64void gfx_imm_draw_aabb2(ImmRenderer*, aabb2);
65
66/// Draw a bounding box.
67void gfx_imm_draw_aabb3(ImmRenderer*, aabb3);
68
69/// Draw a box.
70///
71/// The vertices must be given in the following order:
72///
73/// 7 ----- 6
74/// / /|
75/// 3 ----- 2 |
76/// | | |
77/// | 4 ----- 5
78/// |/ |/
79/// 0 ----- 1
80void gfx_imm_draw_box3(ImmRenderer* renderer, const vec3 vertices[8]);
81
82/// Set the camera.
83void gfx_imm_set_camera(ImmRenderer*, const Camera*);
84
85/// Load an identity model matrix. Clears the matrix stack.
86void gfx_imm_load_identity(ImmRenderer* renderer);
87
88/// Push the given matrix to the matrix stack.
89void gfx_imm_push_matrix(ImmRenderer* renderer, const mat4* matrix);
90
91/// Pop the top of the matrix stack.
92void gfx_imm_pop_matrix(ImmRenderer* renderer);
93
94/// Push a translation matrix to the matrix stack.
95void gfx_imm_translate(ImmRenderer* renderer, vec3 offset);
96
97/// Set the model matrix. Clears the matrix stack.
98void gfx_imm_set_model_matrix(ImmRenderer*, const mat4*);
99
100/// Set the view-projection matrix.
101void gfx_imm_set_view_projection_matrix(ImmRenderer*, const mat4*);
102
103/// Set the render colour.
104void gfx_imm_set_colour(ImmRenderer*, vec4 colour);
diff --git a/gfx/src/renderer/imm_renderer.c b/gfx/src/renderer/imm_renderer.c
deleted file mode 100644
index 8cf3a10..0000000
--- a/gfx/src/renderer/imm_renderer.c
+++ /dev/null
@@ -1,260 +0,0 @@
1#include "imm_renderer_impl.h"
2
3#include <gfx/core.h>
4#include <gfx/util/shader.h>
5
6#include <math/aabb3.h>
7
8#include <assert.h>
9#include <string.h> // memcpy
10
11bool imm_renderer_make(ImmRenderer* renderer, GfxCore* gfxcore) {
12 assert(renderer);
13 assert(gfxcore);
14
15 const size_t num_triangle_verts = IMM_MAX_NUM_TRIANGLES * 3;
16
17 renderer->gfxcore = gfxcore;
18
19 renderer->triangles = gfx_make_geometry(
20 gfxcore, &(GeometryDesc){
21 .type = Triangles,
22 .buffer_usage = BufferDynamic,
23 .num_verts = num_triangle_verts,
24 .positions3d = (BufferView3d){
25 .size_bytes = num_triangle_verts * sizeof(vec3)}});
26 if (!renderer->triangles) {
27 goto cleanup;
28 }
29
30 renderer->shader = gfx_make_immediate_mode_shader(gfxcore);
31 if (!renderer->shader) {
32 goto cleanup;
33 }
34
35 renderer->matrix_stack[0] = mat4_id();
36 renderer->stack_pointer = 0;
37
38 gfx_imm_set_colour(renderer, vec4_make(0.0, 0.0, 0.0, 1.0));
39
40 return true;
41
42cleanup:
43 imm_renderer_destroy(renderer);
44 return false;
45}
46
47void imm_renderer_destroy(ImmRenderer* renderer) {
48 assert(renderer);
49 assert(renderer->gfxcore);
50
51 if (renderer->triangles) {
52 gfx_destroy_geometry(renderer->gfxcore, &renderer->triangles);
53 // TODO: Could also destroy the geometry's buffers here.
54 }
55
56 if (renderer->shader) {
57 gfx_destroy_shader_program(renderer->gfxcore, &renderer->shader);
58 }
59}
60
61void imm_renderer_flush(ImmRenderer* renderer) {
62 assert(renderer);
63
64 if (renderer->num_triangle_verts > 0) {
65 gfx_update_geometry(
66 renderer->triangles,
67 &(GeometryDesc){
68 .num_verts = renderer->num_triangle_verts,
69 .positions3d = (BufferView3d){
70 .data = renderer->triangle_verts,
71 .size_bytes = renderer->num_triangle_verts * sizeof(vec3)}
72 });
73
74 gfx_apply_uniforms(renderer->shader);
75 gfx_render_geometry(renderer->triangles);
76
77 renderer->num_triangle_verts = 0;
78 }
79}
80
81void gfx_imm_start(ImmRenderer* renderer) {
82 assert(renderer);
83 // Shader uniforms are applied lazily.
84 // TODO: In the event that gfx_activate_shader_program() activates uniforms
85 // automatically for convenience, call an overload here that doesn't do so.
86 ShaderProgram* shader = renderer->shader;
87 gfx_activate_shader_program(shader);
88}
89
90void gfx_imm_end(ImmRenderer* renderer) {
91 assert(renderer);
92 imm_renderer_flush(renderer);
93 gfx_deactivate_shader_program(renderer->shader);
94}
95
96void gfx_imm_draw_triangles(
97 ImmRenderer* renderer, const vec3 verts[], size_t num_triangles) {
98 assert(renderer);
99 assert(verts);
100 const size_t new_verts = num_triangles * 3;
101 assert(
102 renderer->num_triangle_verts + new_verts < (IMM_MAX_NUM_TRIANGLES * 3));
103
104 memcpy(
105 renderer->triangle_verts + renderer->num_triangle_verts, verts,
106 new_verts * sizeof(vec3));
107
108 renderer->num_triangle_verts += new_verts;
109}
110
111void gfx_imm_draw_triangle(ImmRenderer* renderer, const vec3 verts[3]) {
112 gfx_imm_draw_triangles(renderer, verts, 1);
113}
114
115void gfx_imm_draw_aabb2(ImmRenderer* renderer, aabb2 box) {
116 assert(renderer);
117
118 // clang-format off
119 const vec3 verts[4] = {
120 vec3_make(box.min.x, box.min.y, 0), // 3 ---- 2
121 vec3_make(box.max.x, box.min.y, 0), // | |
122 vec3_make(box.max.x, box.max.y, 0), // | |
123 vec3_make(box.min.x, box.max.y, 0)}; // 0 ---- 1
124 // clang-format on
125
126#define tri(i0, i1, i2) verts[i0], verts[i1], verts[i2]
127 const vec3 tris[6] = {tri(0, 1, 2), tri(0, 2, 3)};
128#undef tri
129
130 gfx_imm_draw_triangles(renderer, tris, 2);
131}
132
133void gfx_imm_draw_aabb3(ImmRenderer* renderer, aabb3 box) {
134 assert(renderer);
135
136 // clang-format off
137 const vec3 vertices[8] = {
138 vec3_make(box.min.x, box.min.y, box.max.z), // 7 ----- 6
139 vec3_make(box.max.x, box.min.y, box.max.z), // / /|
140 vec3_make(box.max.x, box.max.y, box.max.z), // 3 ----- 2 |
141 vec3_make(box.min.x, box.max.y, box.max.z), // | | |
142 vec3_make(box.min.x, box.min.y, box.min.z), // | 4 ----- 5
143 vec3_make(box.max.x, box.min.y, box.min.z), // |/ |/
144 vec3_make(box.max.x, box.max.y, box.min.z), // 0 ----- 1
145 vec3_make(box.min.x, box.max.y, box.min.z)};
146 // clang-format on
147
148 gfx_imm_draw_box3(renderer, vertices);
149}
150
151void gfx_imm_draw_box3(ImmRenderer* renderer, const vec3 vertices[8]) {
152 assert(renderer);
153 assert(vertices);
154
155 // 7 ----- 6
156 // / /|
157 // 3 ----- 2 |
158 // | | |
159 // | 4 ----- 5
160 // |/ |/
161 // 0 ----- 1
162
163#define tri(i0, i1, i2) vertices[i0], vertices[i1], vertices[i2]
164 const vec3 tris[36] = {// Front.
165 tri(0, 1, 2), tri(0, 2, 3),
166 // Right.
167 tri(1, 5, 6), tri(1, 6, 2),
168 // Back.
169 tri(5, 4, 7), tri(5, 7, 6),
170 // Left.
171 tri(4, 0, 03), tri(4, 3, 7),
172 // Top.
173 tri(3, 2, 6), tri(3, 6, 7),
174 // Bottom.
175 tri(0, 4, 5), tri(0, 5, 1)};
176
177 gfx_imm_draw_triangles(renderer, tris, 12);
178}
179
180// Load the top of the matrix stack into the shader.
181static void update_shader_model_matrix(ImmRenderer* renderer) {
182 assert(renderer);
183 imm_renderer_flush(renderer);
184 gfx_set_mat4_uniform(
185 renderer->shader, "Model",
186 &renderer->matrix_stack[renderer->stack_pointer]);
187}
188
189void gfx_imm_load_identity(ImmRenderer* renderer) {
190 assert(renderer);
191 renderer->matrix_stack[0] = mat4_id();
192 renderer->stack_pointer = 0;
193 update_shader_model_matrix(renderer);
194}
195
196void gfx_imm_push_matrix(ImmRenderer* renderer, const mat4* matrix) {
197 assert(renderer);
198 assert(matrix);
199 assert(renderer->stack_pointer >= 0);
200 assert(renderer->stack_pointer < IMM_MAX_NUM_MATRICES); // TODO: hard assert.
201
202 renderer->matrix_stack[renderer->stack_pointer + 1] =
203 mat4_mul(*matrix, renderer->matrix_stack[renderer->stack_pointer]);
204 renderer->stack_pointer += 1;
205
206 update_shader_model_matrix(renderer);
207}
208
209void gfx_imm_pop_matrix(ImmRenderer* renderer) {
210 assert(renderer);
211 assert(renderer->stack_pointer > 0); // TODO: hard assert.
212
213 // For debugging, zero out the matrix stack as matrices are popped out.
214 memset(
215 &renderer->matrix_stack[renderer->stack_pointer], 0,
216 sizeof(renderer->matrix_stack[0]));
217
218 renderer->stack_pointer -= 1;
219
220 update_shader_model_matrix(renderer);
221}
222
223void gfx_imm_translate(ImmRenderer* renderer, vec3 offset) {
224 assert(renderer);
225 const mat4 mat = mat4_translate(offset);
226 gfx_imm_push_matrix(renderer, &mat);
227}
228
229void gfx_imm_set_camera(ImmRenderer* renderer, const Camera* camera) {
230 assert(renderer);
231 assert(renderer->shader);
232 imm_renderer_flush(renderer);
233 const mat4 view = spatial3_inverse_transform(&camera->spatial);
234 const mat4 view_proj = mat4_mul(camera->projection, view);
235 gfx_imm_set_view_projection_matrix(renderer, &view_proj);
236}
237
238void gfx_imm_set_model_matrix(ImmRenderer* renderer, const mat4* model) {
239 assert(renderer);
240 assert(model);
241 imm_renderer_flush(renderer);
242 renderer->matrix_stack[0] = *model;
243 renderer->stack_pointer = 0;
244 update_shader_model_matrix(renderer);
245}
246
247void gfx_imm_set_view_projection_matrix(
248 ImmRenderer* renderer, const mat4* view_proj) {
249 assert(renderer);
250 assert(renderer->shader);
251 imm_renderer_flush(renderer);
252 gfx_set_mat4_uniform(renderer->shader, "ViewProjection", view_proj);
253}
254
255void gfx_imm_set_colour(ImmRenderer* renderer, vec4 colour) {
256 assert(renderer);
257 assert(renderer->shader);
258 imm_renderer_flush(renderer);
259 gfx_set_vec4_uniform(renderer->shader, "Colour", colour);
260}
diff --git a/gfx/src/renderer/renderer.c b/gfx/src/renderer/renderer.c
deleted file mode 100644
index c2a7dda..0000000
--- a/gfx/src/renderer/renderer.c
+++ /dev/null
@@ -1,396 +0,0 @@
1#include "renderer_impl.h"
2
3#include "scene/animation_impl.h"
4#include "scene/camera_impl.h"
5#include "scene/light_impl.h"
6#include "scene/material_impl.h"
7#include "scene/mesh_impl.h"
8#include "scene/model_impl.h"
9#include "scene/node_impl.h"
10#include "scene/object_impl.h"
11#include "scene/scene_impl.h"
12#include "scene/scene_memory.h"
13
14#include <gfx/core.h>
15#include <gfx/util/ibl.h>
16#include <gfx/util/shader.h>
17
18#include <log/log.h>
19#include <math/mat4.h>
20#include <math/spatial3.h>
21
22#include <assert.h>
23
24// TODO: Move to a header like "constants.h".
25static const int IRRADIANCE_MAP_WIDTH = 1024;
26static const int IRRADIANCE_MAP_HEIGHT = 1024;
27static const int PREFILTERED_ENVIRONMENT_MAP_WIDTH = 128;
28static const int PREFILTERED_ENVIRONMENT_MAP_HEIGHT = 128;
29static const int BRDF_INTEGRATION_MAP_WIDTH = 512;
30static const int BRDF_INTEGRATION_MAP_HEIGHT = 512;
31
32bool renderer_make(Renderer* renderer, GfxCore* gfxcore) {
33 assert(renderer);
34 assert(gfxcore);
35
36 renderer->gfxcore = gfxcore;
37
38 return true;
39}
40
41void renderer_destroy(Renderer* renderer) {
42 if (!renderer) {
43 return;
44 }
45 assert(renderer->gfxcore);
46 GfxCore* gfxcore = renderer->gfxcore;
47 if (renderer->ibl) {
48 gfx_destroy_ibl(gfxcore, &renderer->ibl);
49 }
50 if (renderer->shaders.debug) {
51 gfx_destroy_shader_program(gfxcore, &renderer->shaders.debug);
52 }
53 if (renderer->shaders.normals) {
54 gfx_destroy_shader_program(gfxcore, &renderer->shaders.normals);
55 }
56 if (renderer->shaders.normal_mapped_normals) {
57 gfx_destroy_shader_program(
58 gfxcore, &renderer->shaders.normal_mapped_normals);
59 }
60 if (renderer->shaders.tangents) {
61 gfx_destroy_shader_program(gfxcore, &renderer->shaders.tangents);
62 }
63}
64
65/// Initialize renderer state for IBL if not already initialized.
66static bool init_ibl(Renderer* renderer) {
67 assert(renderer);
68
69 if (!renderer->ibl && !(renderer->ibl = gfx_make_ibl(renderer->gfxcore))) {
70 return false;
71 }
72
73 if (!renderer->brdf_integration_map &&
74 !(renderer->brdf_integration_map = gfx_make_brdf_integration_map(
75 renderer->ibl, renderer->gfxcore, BRDF_INTEGRATION_MAP_WIDTH,
76 BRDF_INTEGRATION_MAP_HEIGHT))) {
77 return false;
78 }
79
80 return true;
81}
82
83static ShaderProgram* load_shader(Renderer* renderer, RenderSceneMode mode) {
84 assert(renderer);
85
86#define LOAD_AND_RETURN(pShader, constructor) \
87 { \
88 if (!pShader) { \
89 pShader = constructor(renderer->gfxcore); \
90 } \
91 assert(pShader); \
92 return pShader; \
93 }
94
95 switch (mode) {
96 case RenderDefault:
97 return 0;
98 case RenderDebug:
99 LOAD_AND_RETURN(renderer->shaders.debug, gfx_make_debug3d_shader);
100 case RenderNormals:
101 LOAD_AND_RETURN(renderer->shaders.normals, gfx_make_view_normals_shader);
102 case RenderNormalMappedNormals:
103 LOAD_AND_RETURN(
104 renderer->shaders.normal_mapped_normals,
105 gfx_make_view_normal_mapped_normals_shader);
106 case RenderTangents:
107 LOAD_AND_RETURN(renderer->shaders.tangents, gfx_make_view_tangents_shader);
108 }
109 assert(false);
110 return 0;
111}
112
113// static void log_matrix(const mat4* m) {
114// for (int row = 0; row < 4; ++row) {
115// LOGI("[ %5.2f, %5.2f, %5.2f, %5.2f ]", m->val[0][row], m->val[1][row],
116// m->val[2][row], m->val[3][row]);
117// }
118// }
119
120/// Computes irradiance and prefiltered environment maps for the light if they
121/// have not been already computed.
122static bool setup_environment_light(
123 Renderer* renderer, GfxCore* gfxcore, EnvironmentLight* light) {
124 assert(renderer);
125 assert(light);
126
127 if (!init_ibl(renderer)) {
128 return false;
129 }
130
131 if (light->irradiance_map) {
132 assert(light->prefiltered_environment_map);
133 return true;
134 }
135
136 Texture* irradiance_map = 0;
137 Texture* prefiltered_environment_map = 0;
138
139 if (!(irradiance_map = gfx_make_irradiance_map(
140 renderer->ibl, gfxcore, light->environment_map,
141 IRRADIANCE_MAP_WIDTH, IRRADIANCE_MAP_HEIGHT))) {
142 goto cleanup;
143 }
144
145 int max_mip_level = 0;
146 if (!(prefiltered_environment_map = gfx_make_prefiltered_environment_map(
147 renderer->ibl, gfxcore, light->environment_map,
148 PREFILTERED_ENVIRONMENT_MAP_WIDTH,
149 PREFILTERED_ENVIRONMENT_MAP_HEIGHT, &max_mip_level))) {
150 goto cleanup;
151 }
152
153 light->irradiance_map = irradiance_map;
154 light->prefiltered_environment_map = prefiltered_environment_map;
155 light->max_reflection_lod = max_mip_level;
156
157 return true;
158
159cleanup:
160 if (irradiance_map) {
161 gfx_destroy_texture(gfxcore, &irradiance_map);
162 }
163 if (prefiltered_environment_map) {
164 gfx_destroy_texture(gfxcore, &prefiltered_environment_map);
165 }
166 return false;
167}
168
169typedef struct RenderState {
170 GfxCore* gfxcore;
171 Renderer* renderer;
172 ShaderProgram* shader; // Null to use scene shaders.
173 const Scene* scene;
174 const Camera* camera;
175 const mat4* camera_rotation; // From camera to world space, rotation only.
176 const mat4* view_matrix;
177 const mat4* projection;
178 const float fovy;
179 const float aspect;
180 Light* environment_light;
181 const Anima* anima;
182 size_t num_joints;
183 mat4 joint_matrices[GFX_MAX_NUM_JOINTS];
184} RenderState;
185
186/// Load joint matrices into the render state.
187static void load_skeleton(RenderState* state, skeleton_idx skeleton_index) {
188 assert(state);
189 assert(skeleton_index.val != 0);
190
191 const Skeleton* skeleton = mem_get_skeleton(skeleton_index);
192 assert(skeleton);
193 assert(skeleton->num_joints <= GFX_MAX_NUM_JOINTS);
194
195 state->num_joints = skeleton->num_joints;
196
197 for (size_t i = 0; i < skeleton->num_joints; ++i) {
198 const joint_idx joint_index = skeleton->joints[i];
199 const Joint* joint = &state->anima->joints[joint_index];
200 state->joint_matrices[i] = joint->joint_matrix;
201 }
202}
203
204/// Draw the scene recursively.
205static void draw_recursively(
206 RenderState* state, mat4 parent_transform, const SceneNode* node) {
207 assert(state);
208 const mat4 node_transform = mat4_mul(parent_transform, node->transform);
209
210 // Anima.
211 if (node->type == AnimaNode) {
212 state->anima = gfx_get_node_anima(node);
213 }
214 // Activate light.
215 else if (node->type == LightNode) {
216 Light* light = mem_get_light(node->light);
217 assert(light);
218
219 if (light->type == EnvironmentLightType) {
220 bool result = setup_environment_light(
221 state->renderer, state->gfxcore, &light->environment);
222 // TODO: Handle the result in a better way.
223 assert(result);
224 state->environment_light = light;
225 }
226 }
227 // Model.
228 else if (node->type == ModelNode) {
229 const Model* model = gfx_get_node_model(node);
230 const SceneNode* root = mem_get_node(model->root);
231 draw_recursively(state, parent_transform, root);
232 }
233 // Render object.
234 else if (node->type == ObjectNode) {
235 const SceneObject* object = mem_get_object(node->object);
236 assert(object);
237
238 // TODO: Here we would frustum-cull the object.
239
240 // TODO: Avoid computing matrices like Modelview or MVP if the shader does
241 // not use them.
242 const mat4 model_matrix = node_transform;
243 const mat4 modelview = mat4_mul(*state->view_matrix, model_matrix);
244 const mat4 mvp = mat4_mul(*state->projection, modelview);
245
246 if (object->skeleton.val) {
247 load_skeleton(state, object->skeleton);
248 }
249
250 for (mesh_link_idx mesh_link_index = object->mesh_link;
251 mesh_link_index.val;) {
252 const MeshLink* mesh_link = mem_get_mesh_link(mesh_link_index);
253 mesh_link_index = mesh_link->next;
254
255 const Mesh* mesh = mem_get_mesh(mesh_link->mesh);
256 if (!mesh) {
257 continue;
258 }
259 assert(mesh->geometry);
260 assert(mesh->material);
261
262 // TODO: Here we would frustum-cull the mesh. The AABB would have to be
263 // transformed by the model matrix. Rotation would make the AABB
264 // relatively large, but still, the culling would be conservative.
265
266 // Apply common shader uniforms not captured by materials.
267 ShaderProgram* shader = state->shader ? state->shader : mesh->shader;
268 gfx_set_mat4_uniform(shader, "ModelMatrix", &model_matrix);
269 gfx_set_mat4_uniform(shader, "Modelview", &modelview);
270 gfx_set_mat4_uniform(shader, "View", state->view_matrix);
271 gfx_set_mat4_uniform(shader, "Projection", state->projection);
272 gfx_set_mat4_uniform(shader, "MVP", &mvp);
273 gfx_set_mat4_uniform(shader, "CameraRotation", state->camera_rotation);
274 gfx_set_float_uniform(shader, "Fovy", state->fovy);
275 gfx_set_float_uniform(shader, "Aspect", state->aspect);
276 if (state->camera) {
277 gfx_set_vec3_uniform(
278 shader, "CameraPosition", state->camera->spatial.p);
279 }
280 if (state->num_joints > 0) {
281 gfx_set_mat4_array_uniform(
282 shader, "JointMatrices", state->joint_matrices, state->num_joints);
283 }
284 // Apply lights.
285 if (state->environment_light) {
286 const EnvironmentLight* light = &state->environment_light->environment;
287 assert(light->environment_map);
288 assert(light->irradiance_map);
289 assert(light->prefiltered_environment_map);
290 assert(state->renderer->brdf_integration_map);
291 gfx_set_texture_uniform(
292 shader, "BRDFIntegrationMap",
293 state->renderer->brdf_integration_map);
294 gfx_set_texture_uniform(shader, "Sky", light->environment_map);
295 gfx_set_texture_uniform(shader, "IrradianceMap", light->irradiance_map);
296 gfx_set_texture_uniform(
297 shader, "PrefilteredEnvironmentMap",
298 light->prefiltered_environment_map);
299 gfx_set_float_uniform(
300 shader, "MaxReflectionLOD", light->max_reflection_lod);
301 }
302 material_activate(shader, mesh->material);
303 gfx_activate_shader_program(shader);
304 gfx_apply_uniforms(shader);
305 gfx_render_geometry(mesh->geometry);
306 }
307
308 // Reset state for next object.
309 state->num_joints = 0;
310 }
311
312 // Render children recursively.
313 for (node_idx child_index = node->child; child_index.val;) {
314 const SceneNode* child = mem_get_node(child_index);
315 draw_recursively(state, node_transform, child);
316 child_index = child->next;
317 }
318}
319
320void gfx_render_scene(Renderer* renderer, const RenderSceneParams* params) {
321 assert(renderer);
322 assert(params);
323 assert(params->scene);
324
325 ShaderProgram* const shader = load_shader(renderer, params->mode);
326
327 const Scene* scene = params->scene;
328 const SceneCamera* camera = params->camera;
329
330 GfxCore* gfxcore = renderer->gfxcore;
331
332 mat4 projection, camera_rotation, view_matrix;
333 if (camera) {
334 projection = camera->camera.projection;
335 camera_rotation =
336 mat4_rotation(spatial3_transform(&camera->camera.spatial));
337 view_matrix = spatial3_inverse_transform(&camera->camera.spatial);
338 } else {
339 projection = mat4_id();
340 camera_rotation = mat4_id();
341 view_matrix = mat4_id();
342 }
343
344 int x, y, width, height;
345 gfx_get_viewport(gfxcore, &x, &y, &width, &height);
346 const float aspect = (float)width / (float)height;
347
348 RenderState state = {
349 .gfxcore = gfxcore,
350 .renderer = renderer,
351 .shader = shader,
352 .scene = scene,
353 .camera = &camera->camera,
354 .camera_rotation = &camera_rotation,
355 .view_matrix = &view_matrix,
356 .projection = &projection,
357 .environment_light = 0,
358 // Assuming a perspective matrix.
359 .fovy = atan(1.0 / (mat4_at(projection, 1, 1))) * 2,
360 .aspect = aspect};
361
362 draw_recursively(&state, mat4_id(), scene->root);
363}
364
365static void update_rec(SceneNode* node, const SceneCamera* camera, R t) {
366 assert(node);
367 assert(camera);
368
369 const NodeType node_type = gfx_get_node_type(node);
370
371 // TODO: Models do not need to be animated if they are not visible to the
372 // camera.
373 if (node_type == AnimaNode) {
374 Anima* anima = gfx_get_node_anima_mut(node);
375 gfx_update_animation(anima, (R)t);
376 } else if (node_type == ModelNode) {
377 Model* model = gfx_get_node_model_mut(node);
378 SceneNode* root = gfx_get_model_root_mut(model);
379 update_rec(root, camera, t);
380 }
381
382 // Children.
383 SceneNode* child = gfx_get_node_child_mut(node);
384 while (child) {
385 update_rec(child, camera, t);
386 child = gfx_get_node_sibling_mut(child);
387 }
388}
389
390void gfx_update(Scene* scene, const SceneCamera* camera, R t) {
391 assert(scene);
392 assert(camera);
393
394 SceneNode* node = gfx_get_scene_root(scene);
395 update_rec(node, camera, t);
396}
diff --git a/gfx/src/scene/material_impl.h b/gfx/src/scene/material_impl.h
deleted file mode 100644
index a6aa95b..0000000
--- a/gfx/src/scene/material_impl.h
+++ /dev/null
@@ -1,16 +0,0 @@
1#pragma once
2
3#include <gfx/scene/material.h>
4
5typedef struct ShaderProgram ShaderProgram;
6
7typedef struct Material {
8 ShaderUniform uniforms[GFX_MAX_UNIFORMS_PER_MATERIAL];
9 int num_uniforms;
10} Material;
11
12/// Activate the material.
13///
14/// This activates the material's shader and configures the shader uniforms that
15/// are specific to the material.
16void material_activate(ShaderProgram* shader, const Material* material);
diff --git a/gfx/include/gfx/asset.h b/include/gfx/asset.h
index caf40c1..caf40c1 100644
--- a/gfx/include/gfx/asset.h
+++ b/include/gfx/asset.h
diff --git a/gfx/include/gfx/core.h b/include/gfx/core.h
index 44509c9..0cf4465 100644
--- a/gfx/include/gfx/core.h
+++ b/include/gfx/core.h
@@ -219,6 +219,7 @@ typedef enum { Texture2D, TextureCubeMap } TextureDimension;
219/// Texture data format. 219/// Texture data format.
220typedef enum { 220typedef enum {
221 TextureDepth, 221 TextureDepth,
222 TextureR8,
222 TextureRG16, 223 TextureRG16,
223 TextureRG16F, 224 TextureRG16F,
224 TextureRGB8, 225 TextureRGB8,
@@ -473,6 +474,9 @@ void gfx_deactivate_shader_program(const ShaderProgram*);
473/// 474///
474/// This function should be called after setting all of the uniform variables 475/// This function should be called after setting all of the uniform variables
475/// and prior to issuing a draw call. 476/// and prior to issuing a draw call.
477///
478/// The given program must have been activated prior to this call with
479/// gfx_activate_shader_program().
476void gfx_apply_uniforms(const ShaderProgram*); 480void gfx_apply_uniforms(const ShaderProgram*);
477 481
478/// Set the texture uniform. 482/// Set the texture uniform.
diff --git a/gfx/include/gfx/gfx.h b/include/gfx/gfx.h
index 7c670a5..d5c25b6 100644
--- a/gfx/include/gfx/gfx.h
+++ b/include/gfx/gfx.h
@@ -3,6 +3,7 @@
3typedef struct AssetCache AssetCache; 3typedef struct AssetCache AssetCache;
4typedef struct GfxCore GfxCore; 4typedef struct GfxCore GfxCore;
5typedef struct ImmRenderer ImmRenderer; 5typedef struct ImmRenderer ImmRenderer;
6typedef struct LLR LLR;
6typedef struct Renderer Renderer; 7typedef struct Renderer Renderer;
7 8
8typedef struct Gfx Gfx; 9typedef struct Gfx Gfx;
@@ -16,12 +17,15 @@ void gfx_destroy(Gfx**);
16/// Get the render backend. 17/// Get the render backend.
17GfxCore* gfx_get_core(Gfx*); 18GfxCore* gfx_get_core(Gfx*);
18 19
19/// Get the renderer. 20/// Get the scene renderer.
20Renderer* gfx_get_renderer(Gfx*); 21Renderer* gfx_get_renderer(Gfx*);
21 22
22/// Get the immediate mode renderer. 23/// Get the immediate mode renderer.
23ImmRenderer* gfx_get_imm_renderer(Gfx*); 24ImmRenderer* gfx_get_imm_renderer(Gfx*);
24 25
26/// Get the low-level renderer.
27LLR* gfx_get_llr(Gfx*);
28
25/// Get the asset cache. 29/// Get the asset cache.
26AssetCache* gfx_get_asset_cache(Gfx*); 30AssetCache* gfx_get_asset_cache(Gfx*);
27 31
diff --git a/gfx/include/gfx/scene/light.h b/include/gfx/llr/light.h
index 132e344..132e344 100644
--- a/gfx/include/gfx/scene/light.h
+++ b/include/gfx/llr/light.h
diff --git a/include/gfx/llr/llr.h b/include/gfx/llr/llr.h
new file mode 100644
index 0000000..77df33f
--- /dev/null
+++ b/include/gfx/llr/llr.h
@@ -0,0 +1,67 @@
1#pragma once
2
3#include <math/camera.h>
4#include <math/mat4.h>
5#include <math/vec3.h>
6
7typedef struct Anima Anima;
8typedef struct Geometry Geometry;
9typedef struct Light Light;
10typedef struct Mesh Mesh;
11typedef struct ShaderProgram ShaderProgram;
12typedef struct Skeleton Skeleton;
13
14typedef struct LLR LLR;
15
16/// Set the shader to be used for subsequent draw calls.
17/// The shader is not yet activated at this point.
18void gfx_llr_set_shader(LLR*, ShaderProgram*);
19
20/// Push a light into the lights stack.
21void gfx_llr_push_light(LLR*, Light*);
22
23/// Pop the last light from the lights stack.
24void gfx_llr_pop_light(LLR*);
25
26/// Load a skeleton.
27///
28/// If a skeleton is loaded, subsequent meshes are rendered with joint data
29/// passed to the shader. This has a cost, so if subsequent meshes are not
30/// animated, unload the skeleton prior to rendering them.
31void gfx_llr_set_skeleton(LLR*, const Anima*, const Skeleton*);
32
33/// Clear the loaded skeleton.
34void gfx_llr_clear_skeleton(LLR*);
35
36/// Set the camera.
37void gfx_llr_set_camera(LLR*, const Camera*);
38
39/// Set the view-projection matrix.
40// void gfx_llr_set_view_projection_matrix(LLR*, const mat4*);
41
42/// Set the aspect ratio.
43void gfx_llr_set_aspect(LLR*, float aspect);
44
45/// Render the geometry.
46void gfx_llr_render_geometry(LLR*, const Geometry*);
47
48/// Render the mesh.
49void gfx_llr_render_mesh(LLR*, const Mesh*);
50
51// -----------------------------------------------------------------------------
52// Matrix stack manipulation.
53
54/// Load an identity model matrix. Clears the matrix stack.
55void gfx_llr_load_identity(LLR* renderer);
56
57/// Push the given matrix to the matrix stack.
58void gfx_llr_push_matrix(LLR* renderer, const mat4* matrix);
59
60/// Pop the top of the matrix stack.
61void gfx_llr_pop_matrix(LLR* renderer);
62
63/// Push a translation matrix to the matrix stack.
64void gfx_llr_translate(LLR* renderer, vec3 offset);
65
66/// Set the model matrix. Clears the matrix stack.
67void gfx_llr_set_model_matrix(LLR*, const mat4*);
diff --git a/gfx/include/gfx/scene/material.h b/include/gfx/llr/material.h
index bca664e..bca664e 100644
--- a/gfx/include/gfx/scene/material.h
+++ b/include/gfx/llr/material.h
diff --git a/gfx/include/gfx/scene/mesh.h b/include/gfx/llr/mesh.h
index 0d3b4d4..0d3b4d4 100644
--- a/gfx/include/gfx/scene/mesh.h
+++ b/include/gfx/llr/mesh.h
diff --git a/include/gfx/renderer.h b/include/gfx/renderer.h
new file mode 100644
index 0000000..1da74eb
--- /dev/null
+++ b/include/gfx/renderer.h
@@ -0,0 +1,29 @@
1#pragma once
2
3#include <math/defs.h>
4
5typedef struct GfxCore GfxCore;
6typedef struct Scene Scene;
7typedef struct SceneCamera SceneCamera;
8
9typedef struct Renderer Renderer;
10
11typedef enum RenderSceneMode {
12 RenderDefault,
13 RenderDebug,
14 RenderNormals,
15 RenderNormalMappedNormals,
16 RenderTangents
17} RenderSceneMode;
18
19typedef struct RenderSceneParams {
20 RenderSceneMode mode;
21 const Scene* scene;
22 const SceneCamera* camera;
23} RenderSceneParams;
24
25/// Render the scene.
26void gfx_render_scene(Renderer*, const RenderSceneParams*);
27
28/// Update the scene.
29void gfx_update(Scene*, const SceneCamera*, R t);
diff --git a/include/gfx/renderer/imm_renderer.h b/include/gfx/renderer/imm_renderer.h
new file mode 100644
index 0000000..db4d290
--- /dev/null
+++ b/include/gfx/renderer/imm_renderer.h
@@ -0,0 +1,55 @@
1#pragma once
2
3#include <math/aabb2.h>
4#include <math/aabb3.h>
5#include <math/camera.h>
6#include <math/mat4.h>
7#include <math/vec3.h>
8#include <math/vec4.h>
9
10typedef struct ImmRenderer ImmRenderer;
11
12/// Prepare the graphics systems for immediate-mode rendering.
13///
14/// Call this before issuing any immediate-mode rendering draws.
15void gfx_imm_start(ImmRenderer*);
16
17/// End immediate mode rendering.
18///
19/// Call this after issuing immediate-mode rendering draws and before swapping
20/// buffers.
21void gfx_imm_end(ImmRenderer*);
22
23/// Flush draw commands.
24///
25/// This should be done when changing any state that may affect the rendering of
26/// primitives; for example, LLR matrix stack changes.
27void gfx_imm_flush(ImmRenderer*);
28
29/// Draw a set of triangles.
30void gfx_imm_draw_triangles(ImmRenderer*, const vec3[], size_t num_triangles);
31
32/// Draw a triangle.
33void gfx_imm_draw_triangle(ImmRenderer*, const vec3[3]);
34
35/// Draw a bounding box.
36void gfx_imm_draw_aabb2(ImmRenderer*, aabb2);
37
38/// Draw a bounding box.
39void gfx_imm_draw_aabb3(ImmRenderer*, aabb3);
40
41/// Draw a box.
42///
43/// The vertices must be given in the following order:
44///
45/// 7 ----- 6
46/// / /|
47/// 3 ----- 2 |
48/// | | |
49/// | 4 ----- 5
50/// |/ |/
51/// 0 ----- 1
52void gfx_imm_draw_box3(ImmRenderer* renderer, const vec3 vertices[8]);
53
54/// Set the render colour.
55void gfx_imm_set_colour(ImmRenderer*, vec4 colour);
diff --git a/gfx/include/gfx/scene.h b/include/gfx/scene.h
index abcaa70..37a7e0b 100644
--- a/gfx/include/gfx/scene.h
+++ b/include/gfx/scene.h
@@ -1,10 +1,11 @@
1#pragma once 1#pragma once
2 2
3// TODO: Remove references to gfx/llr once the transition is complete.
4#include <gfx/llr/light.h>
5#include <gfx/llr/material.h>
6#include <gfx/llr/mesh.h>
3#include <gfx/scene/animation.h> 7#include <gfx/scene/animation.h>
4#include <gfx/scene/camera.h> 8#include <gfx/scene/camera.h>
5#include <gfx/scene/light.h>
6#include <gfx/scene/material.h>
7#include <gfx/scene/mesh.h>
8#include <gfx/scene/model.h> 9#include <gfx/scene/model.h>
9#include <gfx/scene/node.h> 10#include <gfx/scene/node.h>
10#include <gfx/scene/object.h> 11#include <gfx/scene/object.h>
diff --git a/gfx/include/gfx/scene/animation.h b/include/gfx/scene/animation.h
index d95b895..3ef0471 100644
--- a/gfx/include/gfx/scene/animation.h
+++ b/include/gfx/scene/animation.h
@@ -1,6 +1,5 @@
1#pragma once 1#pragma once
2 2
3#include "node.h"
4#include "object.h" 3#include "object.h"
5#include <gfx/sizes.h> 4#include <gfx/sizes.h>
6 5
@@ -15,8 +14,7 @@
15#include <stddef.h> 14#include <stddef.h>
16#include <stdint.h> 15#include <stdint.h>
17 16
18typedef struct Buffer Buffer; 17typedef struct Buffer Buffer;
19typedef struct SceneNode SceneNode;
20 18
21typedef struct Anima Anima; 19typedef struct Anima Anima;
22typedef struct Joint Joint; 20typedef struct Joint Joint;
diff --git a/gfx/include/gfx/scene/camera.h b/include/gfx/scene/camera.h
index 99d83fe..99d83fe 100644
--- a/gfx/include/gfx/scene/camera.h
+++ b/include/gfx/scene/camera.h
diff --git a/gfx/include/gfx/scene/model.h b/include/gfx/scene/model.h
index 42f85d4..42f85d4 100644
--- a/gfx/include/gfx/scene/model.h
+++ b/include/gfx/scene/model.h
diff --git a/gfx/include/gfx/scene/node.h b/include/gfx/scene/node.h
index a2c2836..193eb25 100644
--- a/gfx/include/gfx/scene/node.h
+++ b/include/gfx/scene/node.h
@@ -1,12 +1,8 @@
1#pragma once 1#pragma once
2 2
3#include "animation.h"
4
5#include <math/fwd.h> 3#include <math/fwd.h>
6#include <math/mat4.h> 4#include <math/mat4.h>
7 5
8#include <stdint.h>
9
10typedef struct Anima Anima; 6typedef struct Anima Anima;
11typedef struct Light Light; 7typedef struct Light Light;
12typedef struct Model Model; 8typedef struct Model Model;
diff --git a/gfx/include/gfx/scene/object.h b/include/gfx/scene/object.h
index 7579d29..7579d29 100644
--- a/gfx/include/gfx/scene/object.h
+++ b/include/gfx/scene/object.h
diff --git a/gfx/include/gfx/scene/scene.h b/include/gfx/scene/scene.h
index 0d96210..0d96210 100644
--- a/gfx/include/gfx/scene/scene.h
+++ b/include/gfx/scene/scene.h
diff --git a/gfx/include/gfx/sizes.h b/include/gfx/sizes.h
index 076113c..3eb7481 100644
--- a/gfx/include/gfx/sizes.h
+++ b/include/gfx/sizes.h
@@ -3,7 +3,7 @@
3 3
4// Scene. 4// Scene.
5 5
6/// Maximum number of cameras per scene. 6/// Maximum number of cameras.
7#define GFX_MAX_NUM_CAMERAS 16 7#define GFX_MAX_NUM_CAMERAS 16
8 8
9/// Maximum number of lights. 9/// Maximum number of lights.
@@ -31,10 +31,10 @@
31#define GFX_MAX_NUM_CHANNELS 128 31#define GFX_MAX_NUM_CHANNELS 128
32 32
33/// Maximum number of skeletons. 33/// Maximum number of skeletons.
34#define GFX_MAX_NUM_SKELETONS 128 34#define GFX_MAX_NUM_SKELETONS 8
35 35
36/// Maximum number of animations. 36/// Maximum number of animations.
37#define GFX_MAX_NUM_ANIMATIONS 128 37#define GFX_MAX_NUM_ANIMATIONS 32
38 38
39/// Maximum number of animas. 39/// Maximum number of animas.
40#define GFX_MAX_NUM_ANIMAS 128 40#define GFX_MAX_NUM_ANIMAS 128
@@ -77,16 +77,22 @@
77/// Maximum number of compiler defines in a Shader. 77/// Maximum number of compiler defines in a Shader.
78#define GFX_MAX_SHADER_COMPILER_DEFINES 16 78#define GFX_MAX_SHADER_COMPILER_DEFINES 16
79 79
80// Renderer. 80// Low-level renderer.
81
82/// Maximum number of lights that the low-level renderer can enable per rendered
83/// mesh.
84#define GFX_LLR_MAX_NUM_LIGHTS 8
85
86/// Maximum number of matrices in the low-level renderer's matrix stack.
87#define GFX_LLR_MAX_NUM_MATRICES 32
88
89// Immediate-mode Renderer.
81 90
82/// Maximum number of triangles that the immediate-mode renderer can draw in a 91/// Maximum number of triangles that the immediate-mode renderer can draw in a
83/// frame. 92/// frame.
84#define IMM_MAX_NUM_TRIANGLES 1024 93#define GFX_IMM_MAX_NUM_TRIANGLES 1024
85
86/// Maximum number of matrices in the immediate-mode renderer's matrix stack.
87#define IMM_MAX_NUM_MATRICES 32
88 94
89// Asset Manager. 95// Asset Cache.
90 96
91#define GFX_MAX_NUM_ASSETS 1024 97#define GFX_MAX_NUM_ASSETS 1024
92 98
diff --git a/gfx/include/gfx/util/geometry.h b/include/gfx/util/geometry.h
index a962291..a962291 100644
--- a/gfx/include/gfx/util/geometry.h
+++ b/include/gfx/util/geometry.h
diff --git a/gfx/include/gfx/util/ibl.h b/include/gfx/util/ibl.h
index 6e39180..6e39180 100644
--- a/gfx/include/gfx/util/ibl.h
+++ b/include/gfx/util/ibl.h
diff --git a/gfx/include/gfx/util/shader.h b/include/gfx/util/shader.h
index bd058f4..bd058f4 100644
--- a/gfx/include/gfx/util/shader.h
+++ b/include/gfx/util/shader.h
diff --git a/gfx/include/gfx/util/skyquad.h b/include/gfx/util/skyquad.h
index 2b3fe17..2b3fe17 100644
--- a/gfx/include/gfx/util/skyquad.h
+++ b/include/gfx/util/skyquad.h
diff --git a/gfx/shaders/brdf_integration_map.frag b/shaders/brdf_integration_map.frag
index bb2cebd..bb2cebd 100644
--- a/gfx/shaders/brdf_integration_map.frag
+++ b/shaders/brdf_integration_map.frag
diff --git a/gfx/shaders/cook_torrance.frag b/shaders/cook_torrance.frag
index 1975491..1975491 100644
--- a/gfx/shaders/cook_torrance.frag
+++ b/shaders/cook_torrance.frag
diff --git a/gfx/shaders/cook_torrance.vert b/shaders/cook_torrance.vert
index 5f126c0..5f126c0 100644
--- a/gfx/shaders/cook_torrance.vert
+++ b/shaders/cook_torrance.vert
diff --git a/gfx/shaders/cubemap_filtering.vert b/shaders/cubemap_filtering.vert
index d0cf73f..d0cf73f 100644
--- a/gfx/shaders/cubemap_filtering.vert
+++ b/shaders/cubemap_filtering.vert
diff --git a/gfx/shaders/debug3d.frag b/shaders/debug3d.frag
index 54568d4..54568d4 100644
--- a/gfx/shaders/debug3d.frag
+++ b/shaders/debug3d.frag
diff --git a/gfx/shaders/debug3d.vert b/shaders/debug3d.vert
index d51684f..d51684f 100644
--- a/gfx/shaders/debug3d.vert
+++ b/shaders/debug3d.vert
diff --git a/gfx/shaders/immediate_mode.frag b/shaders/immediate_mode.frag
index ac23b5c..ac23b5c 100644
--- a/gfx/shaders/immediate_mode.frag
+++ b/shaders/immediate_mode.frag
diff --git a/gfx/shaders/immediate_mode.vert b/shaders/immediate_mode.vert
index 65070bb..65070bb 100644
--- a/gfx/shaders/immediate_mode.vert
+++ b/shaders/immediate_mode.vert
diff --git a/gfx/shaders/irradiance_map.frag b/shaders/irradiance_map.frag
index 8200e73..8200e73 100644
--- a/gfx/shaders/irradiance_map.frag
+++ b/shaders/irradiance_map.frag
diff --git a/gfx/shaders/prefiltered_environment_map.frag b/shaders/prefiltered_environment_map.frag
index 8327950..8327950 100644
--- a/gfx/shaders/prefiltered_environment_map.frag
+++ b/shaders/prefiltered_environment_map.frag
diff --git a/gfx/shaders/quad.vert b/shaders/quad.vert
index ef1834c..ef1834c 100644
--- a/gfx/shaders/quad.vert
+++ b/shaders/quad.vert
diff --git a/gfx/shaders/skyquad.frag b/shaders/skyquad.frag
index 9b44bfd..9b44bfd 100644
--- a/gfx/shaders/skyquad.frag
+++ b/shaders/skyquad.frag
diff --git a/gfx/shaders/skyquad.vert b/shaders/skyquad.vert
index c0c46e6..c0c46e6 100644
--- a/gfx/shaders/skyquad.vert
+++ b/shaders/skyquad.vert
diff --git a/gfx/shaders/view_normal_mapped_normals.frag b/shaders/view_normal_mapped_normals.frag
index a372c02..a372c02 100644
--- a/gfx/shaders/view_normal_mapped_normals.frag
+++ b/shaders/view_normal_mapped_normals.frag
diff --git a/gfx/shaders/view_normal_mapped_normals.vert b/shaders/view_normal_mapped_normals.vert
index 004ed9a..004ed9a 100644
--- a/gfx/shaders/view_normal_mapped_normals.vert
+++ b/shaders/view_normal_mapped_normals.vert
diff --git a/gfx/shaders/view_normals.frag b/shaders/view_normals.frag
index e90189c..e90189c 100644
--- a/gfx/shaders/view_normals.frag
+++ b/shaders/view_normals.frag
diff --git a/gfx/shaders/view_normals.vert b/shaders/view_normals.vert
index d51684f..d51684f 100644
--- a/gfx/shaders/view_normals.vert
+++ b/shaders/view_normals.vert
diff --git a/gfx/shaders/view_tangents.frag b/shaders/view_tangents.frag
index 11d1455..11d1455 100644
--- a/gfx/shaders/view_tangents.frag
+++ b/shaders/view_tangents.frag
diff --git a/gfx/shaders/view_tangents.vert b/shaders/view_tangents.vert
index 561ad22..561ad22 100644
--- a/gfx/shaders/view_tangents.vert
+++ b/shaders/view_tangents.vert
diff --git a/gfx/shaders/view_texture.frag b/shaders/view_texture.frag
index 12fa367..12fa367 100644
--- a/gfx/shaders/view_texture.frag
+++ b/shaders/view_texture.frag
diff --git a/gfx/shaders/view_texture.vert b/shaders/view_texture.vert
index 4e3c7d7..4e3c7d7 100644
--- a/gfx/shaders/view_texture.vert
+++ b/shaders/view_texture.vert
diff --git a/gfx/src/asset/asset_cache.c b/src/asset/asset_cache.c
index 16c4d5c..727b63f 100644
--- a/gfx/src/asset/asset_cache.c
+++ b/src/asset/asset_cache.c
@@ -179,6 +179,7 @@ static Model* clone_model(const Model* model) {
179 179
180void gfx_init_asset_cache(AssetCache* cache) { 180void gfx_init_asset_cache(AssetCache* cache) {
181 assert(cache); 181 assert(cache);
182
182 mempool_make(&cache->assets); 183 mempool_make(&cache->assets);
183 184
184 // Allocate a dummy asset at index 0 to guarantee that no assets allocated by 185 // Allocate a dummy asset at index 0 to guarantee that no assets allocated by
@@ -189,6 +190,7 @@ void gfx_init_asset_cache(AssetCache* cache) {
189 190
190void gfx_destroy_asset_cache(AssetCache* cache) { 191void gfx_destroy_asset_cache(AssetCache* cache) {
191 assert(cache); 192 assert(cache);
193 // TODO: Destroy assets here.
192 mempool_del(&cache->assets); 194 mempool_del(&cache->assets);
193} 195}
194 196
diff --git a/gfx/src/asset/asset_cache.h b/src/asset/asset_cache.h
index b2a35ed..b2a35ed 100644
--- a/gfx/src/asset/asset_cache.h
+++ b/src/asset/asset_cache.h
diff --git a/gfx/src/asset/model.c b/src/asset/model.c
index 25f2780..0c57470 100644
--- a/gfx/src/asset/model.c
+++ b/src/asset/model.c
@@ -84,13 +84,12 @@
84#include "asset/texture.h" 84#include "asset/texture.h"
85#include "gfx/core.h" 85#include "gfx/core.h"
86#include "gfx/gfx.h" 86#include "gfx/gfx.h"
87#include "gfx/llr/material.h"
88#include "gfx/llr/mesh.h"
87#include "gfx/scene/animation.h" 89#include "gfx/scene/animation.h"
88#include "gfx/scene/camera.h" 90#include "gfx/scene/camera.h"
89#include "gfx/scene/material.h"
90#include "gfx/scene/mesh.h"
91#include "gfx/scene/node.h" 91#include "gfx/scene/node.h"
92#include "gfx/scene/object.h" 92#include "gfx/scene/object.h"
93#include "gfx/scene/scene.h"
94#include "gfx/sizes.h" 93#include "gfx/sizes.h"
95#include "gfx/util/shader.h" 94#include "gfx/util/shader.h"
96 95
@@ -578,11 +577,10 @@ static bool load_buffers(
578 const cgltf_buffer* buffer = &data->buffers[i]; 577 const cgltf_buffer* buffer = &data->buffers[i];
579 assert(buffer->data); 578 assert(buffer->data);
580 buffers[i] = gfx_make_buffer( 579 buffers[i] = gfx_make_buffer(
581 gfxcore, &(BufferDesc){ 580 gfxcore, &(BufferDesc){.usage = BufferStatic,
582 .usage = BufferStatic, 581 .type = BufferUntyped,
583 .type = BufferUntyped, 582 .data.data = buffer->data,
584 .data.data = buffer->data, 583 .data.count = buffer->size});
585 .data.count = buffer->size});
586 if (!buffers[i]) { 584 if (!buffers[i]) {
587 return false; 585 return false;
588 } 586 }
@@ -604,11 +602,10 @@ static bool load_tangent_buffers(
604 const cgltfTangentBuffer* buffer = &cgltf_tangent_buffers[i]; 602 const cgltfTangentBuffer* buffer = &cgltf_tangent_buffers[i];
605 assert(buffer->data); 603 assert(buffer->data);
606 tangent_buffers[i] = gfx_make_buffer( 604 tangent_buffers[i] = gfx_make_buffer(
607 gfxcore, &(BufferDesc){ 605 gfxcore, &(BufferDesc){.usage = BufferStatic,
608 .usage = BufferStatic, 606 .type = BufferUntyped,
609 .type = BufferUntyped, 607 .data.data = buffer->data,
610 .data.data = buffer->data, 608 .data.count = buffer->size_bytes});
611 .data.count = buffer->size_bytes});
612 if (!tangent_buffers[i]) { 609 if (!tangent_buffers[i]) {
613 return false; 610 return false;
614 } 611 }
@@ -682,14 +679,13 @@ static void load_textures_lazy(
682 mstring fullpath = 679 mstring fullpath =
683 mstring_concat_path(mstring_make(directory), mstring_make(image->uri)); 680 mstring_concat_path(mstring_make(directory), mstring_make(image->uri));
684 681
685 load_texture_cmds[i] = (LoadTextureCmd){ 682 load_texture_cmds[i] = (LoadTextureCmd){.origin = AssetFromFile,
686 .origin = AssetFromFile, 683 .type = LoadTexture,
687 .type = LoadTexture, 684 .colour_space = LinearColourSpace,
688 .colour_space = sRGB, 685 .filtering = filtering,
689 .filtering = filtering, 686 .wrap = wrap,
690 .wrap = wrap, 687 .mipmaps = mipmaps,
691 .mipmaps = mipmaps, 688 .data.texture.filepath = fullpath};
692 .data.texture.filepath = fullpath};
693 } 689 }
694} 690}
695 691
@@ -717,8 +713,12 @@ static bool load_texture_and_uniform(
717 // not be used as albedo and vice versa. 713 // not be used as albedo and vice versa.
718 if (!textures[texture_index]) { 714 if (!textures[texture_index]) {
719 LoadTextureCmd* cmd = &load_texture_cmds[texture_index]; 715 LoadTextureCmd* cmd = &load_texture_cmds[texture_index];
720 // TODO: Check for colour textures and default to LinearColourSpace instead. 716 // Albedo and emissive use sRGB. Other textures use a linear colour space.
721 if (texture_type == NormalMap) { 717 // See the notes on the spec.
718 if ((texture_type == BaseColorTexture) ||
719 (texture_type == EmissiveTexture)) {
720 cmd->colour_space = sRGB;
721 } else {
722 cmd->colour_space = LinearColourSpace; 722 cmd->colour_space = LinearColourSpace;
723 } 723 }
724 724
@@ -778,22 +778,22 @@ static bool load_materials(
778 .value.vec4 = vec4_from_array(pbr->base_color_factor)}; 778 .value.vec4 = vec4_from_array(pbr->base_color_factor)};
779 779
780 assert(next_uniform < GFX_MAX_UNIFORMS_PER_MATERIAL); 780 assert(next_uniform < GFX_MAX_UNIFORMS_PER_MATERIAL);
781 desc.uniforms[next_uniform++] = (ShaderUniform){ 781 desc.uniforms[next_uniform++] =
782 .name = sstring_make(UNIFORM_METALLIC_FACTOR), 782 (ShaderUniform){.name = sstring_make(UNIFORM_METALLIC_FACTOR),
783 .type = UniformFloat, 783 .type = UniformFloat,
784 .value.scalar = pbr->metallic_factor}; 784 .value.scalar = pbr->metallic_factor};
785 785
786 assert(next_uniform < GFX_MAX_UNIFORMS_PER_MATERIAL); 786 assert(next_uniform < GFX_MAX_UNIFORMS_PER_MATERIAL);
787 desc.uniforms[next_uniform++] = (ShaderUniform){ 787 desc.uniforms[next_uniform++] =
788 .name = sstring_make(UNIFORM_ROUGHNESS_FACTOR), 788 (ShaderUniform){.name = sstring_make(UNIFORM_ROUGHNESS_FACTOR),
789 .type = UniformFloat, 789 .type = UniformFloat,
790 .value.scalar = pbr->roughness_factor}; 790 .value.scalar = pbr->roughness_factor};
791 791
792 assert(next_uniform < GFX_MAX_UNIFORMS_PER_MATERIAL); 792 assert(next_uniform < GFX_MAX_UNIFORMS_PER_MATERIAL);
793 desc.uniforms[next_uniform++] = (ShaderUniform){ 793 desc.uniforms[next_uniform++] =
794 .name = sstring_make(UNIFORM_EMISSIVE_FACTOR), 794 (ShaderUniform){.name = sstring_make(UNIFORM_EMISSIVE_FACTOR),
795 .type = UniformVec3, 795 .type = UniformVec3,
796 .value.vec3 = vec3_from_array(mat->emissive_factor)}; 796 .value.vec3 = vec3_from_array(mat->emissive_factor)};
797 797
798 if (pbr->base_color_texture.texture) { 798 if (pbr->base_color_texture.texture) {
799 if (!load_texture_and_uniform( 799 if (!load_texture_and_uniform(
@@ -854,28 +854,28 @@ static Material* make_default_material() {
854 MaterialDesc desc = (MaterialDesc){0}; 854 MaterialDesc desc = (MaterialDesc){0};
855 855
856 assert(desc.num_uniforms < GFX_MAX_UNIFORMS_PER_MATERIAL); 856 assert(desc.num_uniforms < GFX_MAX_UNIFORMS_PER_MATERIAL);
857 desc.uniforms[desc.num_uniforms++] = (ShaderUniform){ 857 desc.uniforms[desc.num_uniforms++] =
858 .name = sstring_make(UNIFORM_BASE_COLOR_FACTOR), 858 (ShaderUniform){.name = sstring_make(UNIFORM_BASE_COLOR_FACTOR),
859 .type = UniformVec4, 859 .type = UniformVec4,
860 .value.vec4 = vec4_make(1, 1, 1, 1)}; 860 .value.vec4 = vec4_make(1, 1, 1, 1)};
861 861
862 assert(desc.num_uniforms < GFX_MAX_UNIFORMS_PER_MATERIAL); 862 assert(desc.num_uniforms < GFX_MAX_UNIFORMS_PER_MATERIAL);
863 desc.uniforms[desc.num_uniforms++] = (ShaderUniform){ 863 desc.uniforms[desc.num_uniforms++] =
864 .name = sstring_make(UNIFORM_METALLIC_FACTOR), 864 (ShaderUniform){.name = sstring_make(UNIFORM_METALLIC_FACTOR),
865 .type = UniformFloat, 865 .type = UniformFloat,
866 .value.scalar = 0}; 866 .value.scalar = 0};
867 867
868 assert(desc.num_uniforms < GFX_MAX_UNIFORMS_PER_MATERIAL); 868 assert(desc.num_uniforms < GFX_MAX_UNIFORMS_PER_MATERIAL);
869 desc.uniforms[desc.num_uniforms++] = (ShaderUniform){ 869 desc.uniforms[desc.num_uniforms++] =
870 .name = sstring_make(UNIFORM_ROUGHNESS_FACTOR), 870 (ShaderUniform){.name = sstring_make(UNIFORM_ROUGHNESS_FACTOR),
871 .type = UniformFloat, 871 .type = UniformFloat,
872 .value.scalar = 1}; 872 .value.scalar = 1};
873 873
874 assert(desc.num_uniforms < GFX_MAX_UNIFORMS_PER_MATERIAL); 874 assert(desc.num_uniforms < GFX_MAX_UNIFORMS_PER_MATERIAL);
875 desc.uniforms[desc.num_uniforms++] = (ShaderUniform){ 875 desc.uniforms[desc.num_uniforms++] =
876 .name = sstring_make(UNIFORM_EMISSIVE_FACTOR), 876 (ShaderUniform){.name = sstring_make(UNIFORM_EMISSIVE_FACTOR),
877 .type = UniformVec3, 877 .type = UniformVec3,
878 .value.vec3 = vec3_make(0, 0, 0)}; 878 .value.vec3 = vec3_make(0, 0, 0)};
879 879
880 return gfx_make_material(&desc); 880 return gfx_make_material(&desc);
881} 881}
@@ -1192,10 +1192,10 @@ static bool load_meshes(
1192 shader ? shader : make_shader_permutation(gfxcore, perm); 1192 shader ? shader : make_shader_permutation(gfxcore, perm);
1193 assert(mesh_shader); 1193 assert(mesh_shader);
1194 1194
1195 meshes[next_mesh] = gfx_make_mesh(&(MeshDesc){ 1195 meshes[next_mesh] =
1196 .geometry = geometries[next_mesh], 1196 gfx_make_mesh(&(MeshDesc){.geometry = geometries[next_mesh],
1197 .material = material, 1197 .material = material,
1198 .shader = mesh_shader}); 1198 .shader = mesh_shader});
1199 1199
1200 if (!meshes[next_mesh]) { 1200 if (!meshes[next_mesh]) {
1201 return false; 1201 return false;
@@ -1432,9 +1432,9 @@ static void load_animations(
1432 const cgltf_animation* animation = &data->animations[a]; 1432 const cgltf_animation* animation = &data->animations[a];
1433 AnimationDesc* animation_desc = &anima_desc->animations[a]; 1433 AnimationDesc* animation_desc = &anima_desc->animations[a];
1434 1434
1435 *animation_desc = (AnimationDesc){ 1435 *animation_desc =
1436 .name = sstring_make(animation->name), 1436 (AnimationDesc){.name = sstring_make(animation->name),
1437 .num_channels = animation->channels_count}; 1437 .num_channels = animation->channels_count};
1438 1438
1439 assert(animation->channels_count <= GFX_MAX_NUM_CHANNELS); 1439 assert(animation->channels_count <= GFX_MAX_NUM_CHANNELS);
1440 for (cgltf_size c = 0; c < animation->channels_count; ++c) { 1440 for (cgltf_size c = 0; c < animation->channels_count; ++c) {
diff --git a/gfx/src/asset/model.h b/src/asset/model.h
index d6399b1..d6399b1 100644
--- a/gfx/src/asset/model.h
+++ b/src/asset/model.h
diff --git a/gfx/src/asset/texture.c b/src/asset/texture.c
index c790394..fb423cc 100644
--- a/gfx/src/asset/texture.c
+++ b/src/asset/texture.c
@@ -49,7 +49,7 @@ Texture* gfx_texture_load(GfxCore* gfxcore, const LoadTextureCmd* cmd) {
49 assert(cmd->origin == AssetFromFile || cmd->origin == AssetFromMemory); 49 assert(cmd->origin == AssetFromFile || cmd->origin == AssetFromMemory);
50 assert(cmd->type == LoadTexture || cmd->type == LoadCubemap); 50 assert(cmd->type == LoadTexture || cmd->type == LoadCubemap);
51 51
52 int width, height, components, old_components; 52 int width, height, components;
53 unsigned char* pixels[6] = {0}; 53 unsigned char* pixels[6] = {0};
54 54
55 switch (cmd->origin) { 55 switch (cmd->origin) {
@@ -64,7 +64,8 @@ Texture* gfx_texture_load(GfxCore* gfxcore, const LoadTextureCmd* cmd) {
64 } 64 }
65 break; 65 break;
66 } 66 }
67 case LoadCubemap: 67 case LoadCubemap: {
68 int old_components = 0;
68 for (int i = 0; i < 6; ++i) { 69 for (int i = 0; i < 6; ++i) {
69 // Flip +Y and -Y textures vertically. 70 // Flip +Y and -Y textures vertically.
70 stbi_set_flip_vertically_on_load(((i == 2) || (i == 3)) ? 1 : 0); 71 stbi_set_flip_vertically_on_load(((i == 2) || (i == 3)) ? 1 : 0);
@@ -76,9 +77,10 @@ Texture* gfx_texture_load(GfxCore* gfxcore, const LoadTextureCmd* cmd) {
76 log_error("Failed to load texture file: %s", filepath); 77 log_error("Failed to load texture file: %s", filepath);
77 break; 78 break;
78 } 79 }
79 if (i > 0 && components != old_components) { 80 if ((i > 0) && (components != old_components)) {
80 log_error("All textures in a cubemap must have the same number of " 81 log_error(
81 "components"); 82 "All textures in a cubemap must have the same number of "
83 "components");
82 break; 84 break;
83 } 85 }
84 if ((i != 2) && (i != 3)) { 86 if ((i != 2) && (i != 3)) {
@@ -89,6 +91,7 @@ Texture* gfx_texture_load(GfxCore* gfxcore, const LoadTextureCmd* cmd) {
89 } 91 }
90 break; 92 break;
91 } 93 }
94 }
92 break; 95 break;
93 case AssetFromMemory: 96 case AssetFromMemory:
94 // TODO: Load textures from memory. 97 // TODO: Load textures from memory.
@@ -122,6 +125,25 @@ Texture* gfx_texture_load(GfxCore* gfxcore, const LoadTextureCmd* cmd) {
122 } 125 }
123 126
124 switch (components) { 127 switch (components) {
128 case 1:
129 switch (cmd->colour_space) {
130 case LinearColourSpace:
131 desc.format = TextureR8;
132 break;
133 case sRGB:
134 // TODO: Gamma single-channel textures are not implemented yet.
135 // The caller should convert the single-channel to RGB and pass it down
136 // as sRGB. This is why the ChronographWatch currently appears red on the
137 // back.
138 log_error("Gamma colour space is not supported for 1-channel textures");
139 // return 0;
140 desc.format = TextureR8;
141 break;
142 default:
143 log_error("Unsupported texture colour space: %d", cmd->colour_space);
144 return 0;
145 }
146 break;
125 case 3: 147 case 3:
126 switch (cmd->colour_space) { 148 switch (cmd->colour_space) {
127 case LinearColourSpace: 149 case LinearColourSpace:
diff --git a/gfx/src/asset/texture.h b/src/asset/texture.h
index 0d38bd9..0d38bd9 100644
--- a/gfx/src/asset/texture.h
+++ b/src/asset/texture.h
diff --git a/gfx/src/core/buffer.c b/src/core/buffer.c
index 3b7e4bc..3b7e4bc 100644
--- a/gfx/src/core/buffer.c
+++ b/src/core/buffer.c
diff --git a/gfx/src/core/buffer.h b/src/core/buffer.h
index b9080f0..b9080f0 100644
--- a/gfx/src/core/buffer.h
+++ b/src/core/buffer.h
diff --git a/gfx/src/core/constants.h b/src/core/constants.h
index a6a3b94..a6a3b94 100644
--- a/gfx/src/core/constants.h
+++ b/src/core/constants.h
diff --git a/gfx/src/core/core.c b/src/core/core.c
index 90038c6..e1671ea 100644
--- a/gfx/src/core/core.c
+++ b/src/core/core.c
@@ -420,6 +420,10 @@ void gfx_destroy_shader_program(GfxCore* gfxcore, ShaderProgram** prog) {
420 // Remove the shader program from the cache. 420 // Remove the shader program from the cache.
421 ProgramCache* cache = &gfxcore->program_cache; 421 ProgramCache* cache = &gfxcore->program_cache;
422 ShaderProgramCacheEntry* entry = find_program_cache_entry(cache, *prog); 422 ShaderProgramCacheEntry* entry = find_program_cache_entry(cache, *prog);
423 // TODO: The following assertion is too restrictive. Clients can end up
424 // re-using the same shader by virtue of the cache. The assertion assumes
425 // that no two "different" clients use the same set of shaders. This can
426 // be relaxed by reference-counting the shaders in the cache.
423 assert(entry); // Must be there, shaders can't go untracked. 427 assert(entry); // Must be there, shaders can't go untracked.
424 mempool_free(cache, &entry); 428 mempool_free(cache, &entry);
425 429
diff --git a/gfx/src/core/core_impl.h b/src/core/core_impl.h
index eefdfbe..eefdfbe 100644
--- a/gfx/src/core/core_impl.h
+++ b/src/core/core_impl.h
diff --git a/gfx/src/core/framebuffer.c b/src/core/framebuffer.c
index 76d9002..76d9002 100644
--- a/gfx/src/core/framebuffer.c
+++ b/src/core/framebuffer.c
diff --git a/gfx/src/core/framebuffer.h b/src/core/framebuffer.h
index 1a3439c..1a3439c 100644
--- a/gfx/src/core/framebuffer.h
+++ b/src/core/framebuffer.h
diff --git a/gfx/src/core/geometry.c b/src/core/geometry.c
index cfc749f..cfc749f 100644
--- a/gfx/src/core/geometry.c
+++ b/src/core/geometry.c
diff --git a/gfx/src/core/geometry.h b/src/core/geometry.h
index c37a76f..c37a76f 100644
--- a/gfx/src/core/geometry.h
+++ b/src/core/geometry.h
diff --git a/gfx/src/core/gl_util.h b/src/core/gl_util.h
index d2d6e22..d2d6e22 100644
--- a/gfx/src/core/gl_util.h
+++ b/src/core/gl_util.h
diff --git a/gfx/src/core/renderbuffer.c b/src/core/renderbuffer.c
index 2753f3b..2753f3b 100644
--- a/gfx/src/core/renderbuffer.c
+++ b/src/core/renderbuffer.c
diff --git a/gfx/src/core/renderbuffer.h b/src/core/renderbuffer.h
index ea11610..ea11610 100644
--- a/gfx/src/core/renderbuffer.h
+++ b/src/core/renderbuffer.h
diff --git a/gfx/src/core/shader.c b/src/core/shader.c
index dded084..dded084 100644
--- a/gfx/src/core/shader.c
+++ b/src/core/shader.c
diff --git a/gfx/src/core/shader.h b/src/core/shader.h
index b9f5679..b9f5679 100644
--- a/gfx/src/core/shader.h
+++ b/src/core/shader.h
diff --git a/gfx/src/core/shader_program.c b/src/core/shader_program.c
index 3cbe48d..3cbe48d 100644
--- a/gfx/src/core/shader_program.c
+++ b/src/core/shader_program.c
diff --git a/gfx/src/core/shader_program.h b/src/core/shader_program.h
index 1443663..1443663 100644
--- a/gfx/src/core/shader_program.h
+++ b/src/core/shader_program.h
diff --git a/gfx/src/core/texture.c b/src/core/texture.c
index 89f7ec0..372f9e6 100644
--- a/gfx/src/core/texture.c
+++ b/src/core/texture.c
@@ -37,6 +37,7 @@ bool gfx_init_texture(Texture* texture, const TextureDesc* desc) {
37 gfx_del_texture(texture); 37 gfx_del_texture(texture);
38 return false; 38 return false;
39 } 39 }
40 ASSERT_GL;
40 41
41 texture->format = to_GL_format(desc->format); 42 texture->format = to_GL_format(desc->format);
42 texture->type = to_GL_type(desc->format); 43 texture->type = to_GL_type(desc->format);
@@ -50,6 +51,7 @@ bool gfx_init_texture(Texture* texture, const TextureDesc* desc) {
50 // Mipmaps. 51 // Mipmaps.
51 if (desc->mipmaps) { 52 if (desc->mipmaps) {
52 glGenerateMipmap(texture->target); 53 glGenerateMipmap(texture->target);
54 ASSERT_GL;
53 } 55 }
54 56
55 // Texture filtering. 57 // Texture filtering.
@@ -60,6 +62,7 @@ bool gfx_init_texture(Texture* texture, const TextureDesc* desc) {
60 GLenum mag = linear ? GL_LINEAR : GL_NEAREST; 62 GLenum mag = linear ? GL_LINEAR : GL_NEAREST;
61 glTexParameteri(texture->target, GL_TEXTURE_MIN_FILTER, min); 63 glTexParameteri(texture->target, GL_TEXTURE_MIN_FILTER, min);
62 glTexParameteri(texture->target, GL_TEXTURE_MAG_FILTER, mag); 64 glTexParameteri(texture->target, GL_TEXTURE_MAG_FILTER, mag);
65 ASSERT_GL;
63 66
64 // Texture wrapping. 67 // Texture wrapping.
65 GLenum wrap = GL_INVALID_ENUM; 68 GLenum wrap = GL_INVALID_ENUM;
@@ -74,6 +77,7 @@ bool gfx_init_texture(Texture* texture, const TextureDesc* desc) {
74 glTexParameteri(texture->target, GL_TEXTURE_WRAP_R, wrap); 77 glTexParameteri(texture->target, GL_TEXTURE_WRAP_R, wrap);
75 glTexParameteri(texture->target, GL_TEXTURE_WRAP_S, wrap); 78 glTexParameteri(texture->target, GL_TEXTURE_WRAP_S, wrap);
76 glTexParameteri(texture->target, GL_TEXTURE_WRAP_T, wrap); 79 glTexParameteri(texture->target, GL_TEXTURE_WRAP_T, wrap);
80 ASSERT_GL;
77 81
78 glBindTexture(texture->target, 0); 82 glBindTexture(texture->target, 0);
79 return true; 83 return true;
@@ -119,6 +123,7 @@ void gfx_update_texture(Texture* texture, const TextureDataDesc* desc) {
119 FAIL("Unhandled texture dimension"); 123 FAIL("Unhandled texture dimension");
120 break; 124 break;
121 } 125 }
126 ASSERT_GL;
122 127
123 glBindTexture(texture->target, 0); 128 glBindTexture(texture->target, 0);
124} 129}
@@ -139,6 +144,8 @@ GLenum to_GL_internal_format(TextureFormat format) {
139 switch (format) { 144 switch (format) {
140 case TextureDepth: 145 case TextureDepth:
141 return GL_DEPTH_COMPONENT; 146 return GL_DEPTH_COMPONENT;
147 case TextureR8:
148 return GL_R8;
142 case TextureRG16: 149 case TextureRG16:
143 return GL_RG16; 150 return GL_RG16;
144 case TextureRG16F: 151 case TextureRG16F:
@@ -163,6 +170,8 @@ GLenum to_GL_format(TextureFormat format) {
163 switch (format) { 170 switch (format) {
164 case TextureDepth: 171 case TextureDepth:
165 return GL_DEPTH_COMPONENT; 172 return GL_DEPTH_COMPONENT;
173 case TextureR8:
174 return GL_RED;
166 case TextureRG16: 175 case TextureRG16:
167 case TextureRG16F: 176 case TextureRG16F:
168 return GL_RG; 177 return GL_RG;
@@ -185,6 +194,7 @@ GLenum to_GL_type(TextureFormat format) {
185 case TextureRG16F: 194 case TextureRG16F:
186 case TextureR11G11B10F: 195 case TextureR11G11B10F:
187 return GL_FLOAT; 196 return GL_FLOAT;
197 case TextureR8:
188 case TextureRG16: 198 case TextureRG16:
189 case TextureRGB8: 199 case TextureRGB8:
190 case TextureRGBA8: 200 case TextureRGBA8:
diff --git a/gfx/src/core/texture.h b/src/core/texture.h
index 4af41e9..4af41e9 100644
--- a/gfx/src/core/texture.h
+++ b/src/core/texture.h
diff --git a/gfx/src/gfx.c b/src/gfx.c
index cd2ac90..4291ae7 100644
--- a/gfx/src/gfx.c
+++ b/src/gfx.c
@@ -2,20 +2,20 @@
2 2
3#include "asset/asset_cache.h" 3#include "asset/asset_cache.h"
4#include "core/core_impl.h" 4#include "core/core_impl.h"
5#include "llr/llr_impl.h"
5#include "renderer/imm_renderer_impl.h" 6#include "renderer/imm_renderer_impl.h"
6#include "renderer/renderer_impl.h" 7#include "renderer/renderer_impl.h"
7#include "scene/scene_memory.h" 8#include "scene/scene_memory.h"
8 9
9#include <log/log.h>
10
11#include <assert.h> 10#include <assert.h>
12#include <stdlib.h> 11#include <stdlib.h>
13 12
14typedef struct Gfx { 13typedef struct Gfx {
15 AssetCache asset_cache; 14 AssetCache asset_cache;
16 GfxCore gfxcore; 15 GfxCore gfxcore;
17 Renderer renderer; 16 LLR llr;
18 ImmRenderer imm_renderer; 17 ImmRenderer imm_renderer;
18 Renderer renderer;
19} Gfx; 19} Gfx;
20 20
21Gfx* gfx_init(void) { 21Gfx* gfx_init(void) {
@@ -24,16 +24,20 @@ Gfx* gfx_init(void) {
24 return 0; 24 return 0;
25 } 25 }
26 gfx_init_gfxcore(&gfx->gfxcore); 26 gfx_init_gfxcore(&gfx->gfxcore);
27 if (!renderer_make(&gfx->renderer, &gfx->gfxcore)) { 27 if (!gfx_llr_make(&gfx->llr, &gfx->gfxcore)) {
28 gfx_destroy(&gfx); 28 gfx_destroy(&gfx);
29 return 0; 29 return 0;
30 } 30 }
31 if (!imm_renderer_make(&gfx->imm_renderer, &gfx->gfxcore)) { 31 if (!gfx_imm_make(&gfx->imm_renderer, &gfx->gfxcore, &gfx->llr)) {
32 // TODO: Add error logs to the initialization failure cases here and inside 32 // TODO: Add error logs to the initialization failure cases here and inside
33 // the renderers. 33 // the renderers.
34 gfx_destroy(&gfx); 34 gfx_destroy(&gfx);
35 return 0; 35 return 0;
36 } 36 }
37 if (!gfx_renderer_make(&gfx->renderer, &gfx->llr, &gfx->gfxcore)) {
38 gfx_destroy(&gfx);
39 return 0;
40 }
37 gfx_init_asset_cache(&gfx->asset_cache); 41 gfx_init_asset_cache(&gfx->asset_cache);
38 scene_mem_init(); 42 scene_mem_init();
39 return gfx; 43 return gfx;
@@ -45,8 +49,9 @@ void gfx_destroy(Gfx** gfx) {
45 } 49 }
46 scene_mem_destroy(); 50 scene_mem_destroy();
47 gfx_destroy_asset_cache(&(*gfx)->asset_cache); 51 gfx_destroy_asset_cache(&(*gfx)->asset_cache);
48 renderer_destroy(&(*gfx)->renderer); 52 gfx_renderer_destroy(&(*gfx)->renderer);
49 imm_renderer_destroy(&(*gfx)->imm_renderer); 53 gfx_imm_destroy(&(*gfx)->imm_renderer);
54 gfx_llr_destroy(&(*gfx)->llr);
50 gfx_del_gfxcore(&(*gfx)->gfxcore); 55 gfx_del_gfxcore(&(*gfx)->gfxcore);
51 free(*gfx); 56 free(*gfx);
52 *gfx = 0; 57 *gfx = 0;
@@ -67,6 +72,11 @@ ImmRenderer* gfx_get_imm_renderer(Gfx* gfx) {
67 return &gfx->imm_renderer; 72 return &gfx->imm_renderer;
68} 73}
69 74
75LLR* gfx_get_llr(Gfx* gfx) {
76 assert(gfx);
77 return &gfx->llr;
78}
79
70AssetCache* gfx_get_asset_cache(Gfx* gfx) { 80AssetCache* gfx_get_asset_cache(Gfx* gfx) {
71 assert(gfx); 81 assert(gfx);
72 return &gfx->asset_cache; 82 return &gfx->asset_cache;
diff --git a/gfx/src/gfx_assert.h b/src/gfx_assert.h
index f4b3aa5..f4b3aa5 100644
--- a/gfx/src/gfx_assert.h
+++ b/src/gfx_assert.h
diff --git a/gfx/src/scene/light.c b/src/llr/light.c
index adbec8d..1d1c40d 100644
--- a/gfx/src/scene/light.c
+++ b/src/llr/light.c
@@ -1,7 +1,7 @@
1#include "light_impl.h" 1#include "light_impl.h"
2 2
3#include "node_impl.h" 3#include "../scene/node_impl.h"
4#include "scene_memory.h" 4#include "../scene/scene_memory.h"
5 5
6#include <error.h> 6#include <error.h>
7 7
diff --git a/gfx/src/scene/light_impl.h b/src/llr/light_impl.h
index 1aa0bb4..32203c4 100644
--- a/gfx/src/scene/light_impl.h
+++ b/src/llr/light_impl.h
@@ -1,8 +1,8 @@
1#pragma once 1#pragma once
2 2
3#include <gfx/scene/light.h> 3#include <../../include/gfx/llr/light.h>
4 4
5#include "types.h" 5#include "../scene/types.h"
6 6
7typedef struct Texture Texture; 7typedef struct Texture Texture;
8 8
diff --git a/src/llr/llr.c b/src/llr/llr.c
new file mode 100644
index 0000000..74cfaed
--- /dev/null
+++ b/src/llr/llr.c
@@ -0,0 +1,381 @@
1#include "light_impl.h"
2#include "llr_impl.h"
3#include "mesh_impl.h"
4
5#include "llr/material_impl.h"
6#include "scene/animation_impl.h"
7
8#include <gfx/core.h>
9#include <gfx/util/ibl.h>
10
11#include <cassert.h>
12
13static const int IRRADIANCE_MAP_WIDTH = 1024;
14static const int IRRADIANCE_MAP_HEIGHT = 1024;
15static const int PREFILTERED_ENVIRONMENT_MAP_WIDTH = 128;
16static const int PREFILTERED_ENVIRONMENT_MAP_HEIGHT = 128;
17static const int BRDF_INTEGRATION_MAP_WIDTH = 512;
18static const int BRDF_INTEGRATION_MAP_HEIGHT = 512;
19
20/// Initialize renderer state for IBL.
21static bool init_ibl(LLR* renderer) {
22 assert(renderer);
23 assert(!renderer->ibl);
24 assert(!renderer->brdf_integration_map);
25
26 if (!((renderer->ibl = gfx_make_ibl(renderer->gfxcore)))) {
27 return false;
28 }
29
30 if (!((renderer->brdf_integration_map = gfx_make_brdf_integration_map(
31 renderer->ibl, renderer->gfxcore, BRDF_INTEGRATION_MAP_WIDTH,
32 BRDF_INTEGRATION_MAP_HEIGHT)))) {
33 return false;
34 }
35
36 return true;
37}
38
39// TODO: Why is this done lazily here? Do it when the environment light is
40// created.
41//
42/// Compute irradiance and prefiltered environment maps for the light if they
43/// have not been already computed.
44static bool set_up_environment_light(LLR* renderer, EnvironmentLight* light) {
45 assert(renderer);
46 assert(light);
47 assert(renderer->ibl);
48 assert(renderer->brdf_integration_map);
49
50 if (light->irradiance_map) {
51 assert(light->prefiltered_environment_map);
52 return true;
53 }
54
55 // For convenience.
56 GfxCore* gfxcore = renderer->gfxcore;
57
58 Texture* irradiance_map = 0;
59 Texture* prefiltered_environment_map = 0;
60
61 if (!((irradiance_map = gfx_make_irradiance_map(
62 renderer->ibl, gfxcore, light->environment_map,
63 IRRADIANCE_MAP_WIDTH, IRRADIANCE_MAP_HEIGHT)))) {
64 goto cleanup;
65 }
66
67 int max_mip_level = 0;
68 if (!((prefiltered_environment_map = gfx_make_prefiltered_environment_map(
69 renderer->ibl, gfxcore, light->environment_map,
70 PREFILTERED_ENVIRONMENT_MAP_WIDTH,
71 PREFILTERED_ENVIRONMENT_MAP_HEIGHT, &max_mip_level)))) {
72 goto cleanup;
73 }
74
75 light->irradiance_map = irradiance_map;
76 light->prefiltered_environment_map = prefiltered_environment_map;
77 light->max_reflection_lod = max_mip_level;
78
79 return true;
80
81cleanup:
82 if (irradiance_map) {
83 gfx_destroy_texture(gfxcore, &irradiance_map);
84 }
85 if (prefiltered_environment_map) {
86 gfx_destroy_texture(gfxcore, &prefiltered_environment_map);
87 }
88 return false;
89}
90
91static void configure_light(LLR* renderer, Light* light) {
92 assert(renderer);
93 assert(light);
94
95 // For convenience.
96 ShaderProgram* const shader = renderer->shader;
97
98 switch (light->type) {
99 case EnvironmentLightType: {
100 EnvironmentLight* env = &light->environment;
101
102 const bool initialized = set_up_environment_light(renderer, env);
103 ASSERT(initialized);
104 assert(env->environment_map);
105 assert(env->irradiance_map);
106 assert(env->prefiltered_environment_map);
107 assert(renderer->brdf_integration_map);
108
109 gfx_set_texture_uniform(
110 shader, "BRDFIntegrationMap", renderer->brdf_integration_map);
111 gfx_set_texture_uniform(shader, "Sky", env->environment_map);
112 gfx_set_texture_uniform(shader, "IrradianceMap", env->irradiance_map);
113 gfx_set_texture_uniform(
114 shader, "PrefilteredEnvironmentMap", env->prefiltered_environment_map);
115 gfx_set_float_uniform(
116 shader, "MaxReflectionLOD", (float)env->max_reflection_lod);
117
118 break;
119 }
120 default:
121 assert(false); // TODO: Implement other light types.
122 break;
123 }
124}
125
126static void configure_state(LLR* renderer) {
127 assert(renderer);
128
129 // Check if anything changed first so that we don't call gfx_apply_uniforms()
130 // unnecessarily.
131 const bool nothing_changed = (renderer->changed_flags == 0);
132 if (nothing_changed) {
133 return;
134 }
135 // Setting a null shader is also allowed, in which case there is nothing to
136 // configure.
137 if (renderer->shader == 0) {
138 renderer->shader_changed = false;
139 return;
140 }
141
142 // For convenience.
143 ShaderProgram* const shader = renderer->shader;
144 const mat4* const model = &renderer->matrix_stack[renderer->stack_pointer];
145
146 // TODO: Check to see which ones the shader actually uses and avoid
147 // computing the unnecessary matrices.
148
149 if (renderer->matrix_changed || renderer->shader_changed) {
150 renderer->matrix_changed = false;
151
152 gfx_set_mat4_uniform(shader, "Model", model);
153 gfx_set_mat4_uniform(shader, "ModelMatrix", model);
154 }
155
156 // TODO: camera_changed is not set anywhere. Need to think how imm primitive
157 // rendering and imm mesh rendering work together. We could treat imm
158 // primitive calls like setting a new shader.
159 if (renderer->camera_changed || renderer->shader_changed) {
160 renderer->camera_changed = false;
161
162 // Set all supported camera-related uniforms. Shaders can choose which ones
163 // to use.
164 const mat4 modelview = mat4_mul(renderer->view, *model);
165 const mat4 view_proj = mat4_mul(renderer->projection, renderer->view);
166 const mat4 mvp = mat4_mul(renderer->projection, modelview);
167
168 gfx_set_mat4_uniform(shader, "Modelview", &modelview);
169 gfx_set_mat4_uniform(shader, "View", &renderer->view);
170 gfx_set_mat4_uniform(shader, "Projection", &renderer->projection);
171 gfx_set_mat4_uniform(shader, "ViewProjection", &view_proj);
172 gfx_set_mat4_uniform(shader, "MVP", &mvp);
173 gfx_set_vec3_uniform(shader, "CameraPosition", renderer->camera_position);
174 gfx_set_mat4_uniform(shader, "CameraRotation", &renderer->camera_rotation);
175 gfx_set_float_uniform(shader, "Fovy", renderer->fovy);
176 gfx_set_float_uniform(shader, "Aspect", renderer->aspect);
177 }
178
179 if (renderer->lights_changed || renderer->shader_changed) {
180 renderer->lights_changed = false;
181
182 // TODO: Could do better by only setting the lights that have actually
183 // changed.
184 // TODO: Will also need to pass the number of lights to the shader once the
185 // other light types are implemented.
186 for (int i = 0; i < renderer->num_lights; ++i) {
187 configure_light(renderer, renderer->lights[i]);
188 }
189 }
190
191 if (renderer->skeleton_changed || renderer->shader_changed) {
192 renderer->skeleton_changed = false;
193
194 if (renderer->num_joints > 0) {
195 gfx_set_mat4_array_uniform(
196 shader, "JointMatrices", renderer->joint_matrices,
197 renderer->num_joints);
198 }
199 }
200
201 if (renderer->shader_changed) {
202 renderer->shader_changed = false;
203 gfx_activate_shader_program(renderer->shader);
204 }
205
206 // Must be called after activating the program.
207 gfx_apply_uniforms(renderer->shader);
208}
209
210bool gfx_llr_make(LLR* renderer, GfxCore* gfxcore) {
211 assert(renderer);
212 assert(gfxcore);
213
214 renderer->gfxcore = gfxcore;
215 if (!init_ibl(renderer)) {
216 goto cleanup;
217 }
218 gfx_llr_load_identity(renderer);
219 renderer->view = mat4_id();
220 renderer->projection = mat4_id();
221 renderer->camera_rotation = mat4_id();
222 return true;
223
224cleanup:
225 gfx_llr_destroy(renderer);
226 return false;
227}
228
229void gfx_llr_destroy(LLR* renderer) {
230 assert(renderer);
231 assert(renderer->gfxcore);
232
233 if (renderer->brdf_integration_map) {
234 gfx_destroy_texture(renderer->gfxcore, &renderer->brdf_integration_map);
235 }
236
237 // TODO: Do this once the IBL from the scene renderer is gone.
238 if (renderer->ibl) {
239 // gfx_destroy_ibl(renderer->gfxcore, &renderer->ibl);
240 }
241}
242
243void gfx_llr_set_shader(LLR* renderer, ShaderProgram* shader) {
244 assert(renderer);
245 // null shader is allowed, so do not assert it.
246
247 // It's important to not set shader_changed unnecessarily, since that would
248 // re-trigger the setting of uniforms.
249 if (renderer->shader != shader) {
250 renderer->shader = shader;
251 renderer->shader_changed = true;
252 }
253}
254
255void gfx_llr_push_light(LLR* renderer, Light* light) {
256 assert(renderer);
257 assert(light);
258 assert(renderer->num_lights >= 0);
259 ASSERT(renderer->num_lights < GFX_LLR_MAX_NUM_LIGHTS);
260
261 renderer->lights[renderer->num_lights++] = light;
262 renderer->lights_changed = true;
263}
264
265void gfx_llr_pop_light(LLR* renderer) {
266 assert(renderer);
267 ASSERT(renderer->num_lights > 0);
268
269 renderer->lights[--renderer->num_lights] = 0;
270 renderer->lights_changed = true;
271}
272
273void gfx_llr_set_skeleton(
274 LLR* renderer, const Anima* anima, const Skeleton* skeleton) {
275 assert(renderer);
276 assert(anima);
277 assert(skeleton);
278 assert(skeleton->num_joints <= GFX_MAX_NUM_JOINTS);
279
280 for (size_t i = 0; i < skeleton->num_joints; ++i) {
281 const joint_idx joint_index = skeleton->joints[i];
282 const Joint* joint = &anima->joints[joint_index];
283 renderer->joint_matrices[i] = joint->joint_matrix;
284 }
285 renderer->num_joints = skeleton->num_joints;
286 renderer->skeleton_changed = true;
287}
288
289void gfx_llr_clear_skeleton(LLR* renderer) {
290 assert(renderer);
291
292 renderer->num_joints = 0;
293 renderer->skeleton_changed = true;
294}
295
296void gfx_llr_set_camera(LLR* renderer, const Camera* camera) {
297 assert(renderer);
298
299 renderer->camera_position = camera->spatial.p;
300 renderer->camera_rotation =
301 mat4_rotation(spatial3_transform(&camera->spatial));
302 renderer->view = spatial3_inverse_transform(&camera->spatial);
303 renderer->projection = camera->projection;
304 // Assuming a perspective matrix.
305 renderer->fovy = (R)atan(1.0 / (mat4_at(camera->projection, 1, 1))) * 2;
306 renderer->camera_changed = true;
307}
308
309void gfx_llr_set_aspect(LLR* renderer, float aspect) {
310 assert(renderer);
311
312 renderer->aspect = aspect;
313 renderer->camera_changed = true;
314}
315
316void gfx_llr_render_geometry(LLR* renderer, const Geometry* geometry) {
317 assert(renderer);
318 assert(geometry);
319
320 configure_state(renderer);
321 gfx_render_geometry(geometry);
322}
323
324void gfx_llr_render_mesh(LLR* renderer, const Mesh* mesh) {
325 assert(renderer);
326 assert(renderer->shader);
327 assert(mesh);
328 assert(mesh->geometry);
329 assert(mesh->material);
330
331 gfx_material_activate(renderer->shader, mesh->material);
332 gfx_llr_render_geometry(renderer, mesh->geometry);
333}
334
335void gfx_llr_load_identity(LLR* renderer) {
336 assert(renderer);
337
338 renderer->matrix_stack[0] = mat4_id();
339 renderer->stack_pointer = 0;
340 renderer->matrix_changed = true;
341}
342
343void gfx_llr_push_matrix(LLR* renderer, const mat4* matrix) {
344 assert(renderer);
345 assert(matrix);
346 assert(renderer->stack_pointer >= 0);
347 ASSERT(renderer->stack_pointer < GFX_LLR_MAX_NUM_MATRICES);
348
349 renderer->stack_pointer += 1;
350 renderer->matrix_stack[renderer->stack_pointer] =
351 mat4_mul(*matrix, renderer->matrix_stack[renderer->stack_pointer - 1]);
352 renderer->matrix_changed = true;
353}
354
355void gfx_llr_pop_matrix(LLR* renderer) {
356 assert(renderer);
357 ASSERT(renderer->stack_pointer > 0);
358
359 // For debugging, zero out the matrix stack as matrices are popped out.
360 memset(
361 &renderer->matrix_stack[renderer->stack_pointer], 0,
362 sizeof(renderer->matrix_stack[0]));
363 renderer->stack_pointer -= 1;
364 renderer->matrix_changed = true;
365}
366
367void gfx_llr_translate(LLR* renderer, vec3 offset) {
368 assert(renderer);
369
370 const mat4 mat = mat4_translate(offset);
371 gfx_llr_push_matrix(renderer, &mat);
372}
373
374void gfx_llr_set_model_matrix(LLR* renderer, const mat4* model) {
375 assert(renderer);
376 assert(model);
377
378 renderer->matrix_stack[0] = *model;
379 renderer->stack_pointer = 0;
380 renderer->matrix_changed = true;
381}
diff --git a/src/llr/llr_impl.h b/src/llr/llr_impl.h
new file mode 100644
index 0000000..ada2d79
--- /dev/null
+++ b/src/llr/llr_impl.h
@@ -0,0 +1,83 @@
1#pragma once
2
3#include <gfx/llr/llr.h>
4#include <gfx/sizes.h>
5
6#include <math/mat4.h>
7#include <math/vec3.h>
8
9#include <stdbool.h>
10#include <stddef.h>
11#include <stdint.h>
12
13typedef struct Geometry Geometry;
14typedef struct GfxCore GfxCore;
15typedef struct IBL IBL;
16typedef struct Material Material;
17typedef struct ShaderProgram ShaderProgram;
18typedef struct Texture Texture;
19
20/// Immediate mode renderer.
21///
22/// The renderer caches state changes in memory and only programs the underlying
23/// shader program when a draw call is issued and if anything has changed. This
24/// keeps the number of graphics API calls to a minimum, but requires tracking
25/// state changes. The 'changed' booleans below fulfill this purpose, and
26/// indicate whether a given state has changed since the last draw call.
27///
28/// The renderer must combine state changes accordingly. For example, if only
29/// the lights have changed, then it is sufficient to update light uniforms in
30/// the current shader program. On the other hand, if the shader program has
31/// changed, then the renderer must reconfigure it from scratch and set light
32/// uniforms, camera uniforms, etc.
33///
34/// Note that the shader program API has its own level of caching as well, so
35/// reconfiguration at the level of the renderer does not result in the
36/// worst-case set of graphics API calls.
37typedef struct LLR {
38 GfxCore* gfxcore;
39
40 union {
41 struct {
42 bool shader_changed : 1; // Whether the shader has changed.
43 bool camera_changed : 1; // Whether the camera parameters have changed.
44 bool lights_changed : 1; // Whether the lights have changed.
45 bool skeleton_changed : 1; // Whether the skeleton has changed.
46 bool matrix_changed : 1; // Whether the matrix stack has changed.
47 };
48 uint8_t changed_flags;
49 };
50
51 IBL* ibl;
52 Texture* brdf_integration_map;
53
54 ShaderProgram* shader; // Active shader. Not owned.
55
56 vec3 camera_position;
57 mat4 camera_rotation;
58 mat4 view; // Camera view matrix.
59 mat4 projection; // Camera projection matrix.
60 R fovy; // Camera vertical field of view.
61 R aspect; // Aspect ratio.
62
63 // Lights are not const because environment lights store lazily-computed
64 // irradiance maps.
65 Light* lights[GFX_LLR_MAX_NUM_LIGHTS]; // Lights stack.
66 int num_lights; // Number of lights enabled at a given point in time. It
67 // points to one past the top of the stack.
68
69 size_t num_joints;
70 mat4 joint_matrices[GFX_MAX_NUM_JOINTS];
71
72 // The matrix stack contains pre-multiplied matrices.
73 // It is also never empty. The top of the stack is an identity matrix when the
74 // stack is "empty" from the user's perspective.
75 mat4 matrix_stack[GFX_LLR_MAX_NUM_MATRICES];
76 int stack_pointer; // Points to the top of the stack.
77} LLR;
78
79/// Create a new immediate mode renderer.
80bool gfx_llr_make(LLR*, GfxCore*);
81
82/// Destroy the immediate mode renderer.
83void gfx_llr_destroy(LLR*);
diff --git a/gfx/src/scene/material.c b/src/llr/material.c
index 3248243..4014482 100644
--- a/gfx/src/scene/material.c
+++ b/src/llr/material.c
@@ -1,6 +1,6 @@
1#include "material_impl.h" 1#include "material_impl.h"
2 2
3#include "scene_memory.h" 3#include "../scene/scene_memory.h"
4 4
5#include <gfx/core.h> 5#include <gfx/core.h>
6 6
@@ -48,7 +48,7 @@ static void set_uniform(ShaderProgram* prog, const ShaderUniform* uniform) {
48 } 48 }
49} 49}
50 50
51void material_activate(ShaderProgram* shader, const Material* material) { 51void gfx_material_activate(ShaderProgram* shader, const Material* material) {
52 assert(material); 52 assert(material);
53 for (int i = 0; i < material->num_uniforms; ++i) { 53 for (int i = 0; i < material->num_uniforms; ++i) {
54 const ShaderUniform* uniform = &material->uniforms[i]; 54 const ShaderUniform* uniform = &material->uniforms[i];
diff --git a/src/llr/material_impl.h b/src/llr/material_impl.h
new file mode 100644
index 0000000..138497f
--- /dev/null
+++ b/src/llr/material_impl.h
@@ -0,0 +1,15 @@
1#pragma once
2
3#include <../../include/gfx/llr/material.h>
4
5typedef struct ShaderProgram ShaderProgram;
6
7typedef struct Material {
8 ShaderUniform uniforms[GFX_MAX_UNIFORMS_PER_MATERIAL];
9 int num_uniforms;
10} Material;
11
12/// Activate the material.
13///
14/// This configures the shader uniforms that are specific to the material.
15void gfx_material_activate(ShaderProgram* shader, const Material* material);
diff --git a/gfx/src/scene/mesh.c b/src/llr/mesh.c
index 1a93bed..3aebb04 100644
--- a/gfx/src/scene/mesh.c
+++ b/src/llr/mesh.c
@@ -1,6 +1,6 @@
1#include "mesh_impl.h" 1#include "mesh_impl.h"
2 2
3#include "scene_memory.h" 3#include "../scene/scene_memory.h"
4 4
5#include <assert.h> 5#include <assert.h>
6 6
diff --git a/gfx/src/scene/mesh_impl.h b/src/llr/mesh_impl.h
index 560b77e..47ff525 100644
--- a/gfx/src/scene/mesh_impl.h
+++ b/src/llr/mesh_impl.h
@@ -1,6 +1,6 @@
1#pragma once 1#pragma once
2 2
3#include <gfx/scene/mesh.h> 3#include <../../include/gfx/llr/mesh.h>
4 4
5typedef struct Mesh { 5typedef struct Mesh {
6 const Geometry* geometry; 6 const Geometry* geometry;
diff --git a/src/renderer/imm_renderer.c b/src/renderer/imm_renderer.c
new file mode 100644
index 0000000..01cc5bb
--- /dev/null
+++ b/src/renderer/imm_renderer.c
@@ -0,0 +1,192 @@
1#include "imm_renderer_impl.h"
2
3#include <gfx/core.h>
4#include <gfx/llr/llr.h>
5#include <gfx/renderer/imm_renderer.h>
6#include <gfx/util/shader.h>
7
8#include <math/aabb3.h>
9
10#include <assert.h>
11#include <string.h> // memcpy
12
13bool gfx_imm_make(ImmRenderer* renderer, GfxCore* gfxcore, LLR* llr) {
14 assert(renderer);
15 assert(gfxcore);
16 assert(llr);
17
18 const size_t num_triangle_verts = GFX_IMM_MAX_NUM_TRIANGLES * 3;
19
20 renderer->gfxcore = gfxcore;
21 renderer->llr = llr;
22
23 renderer->triangles = gfx_make_geometry(
24 gfxcore,
25 &(GeometryDesc){.type = Triangles,
26 .buffer_usage = BufferDynamic,
27 .num_verts = num_triangle_verts,
28 .positions3d = (BufferView3d){
29 .size_bytes = num_triangle_verts * sizeof(vec3)}});
30 if (!renderer->triangles) {
31 goto cleanup;
32 }
33
34 renderer->shader = gfx_make_immediate_mode_shader(gfxcore);
35 if (!renderer->shader) {
36 goto cleanup;
37 }
38
39 gfx_imm_set_colour(renderer, vec4_make(0.0f, 0.0f, 0.0f, 1.0f));
40
41 return true;
42
43cleanup:
44 gfx_imm_destroy(renderer);
45 return false;
46}
47
48void gfx_imm_destroy(ImmRenderer* renderer) {
49 assert(renderer);
50 assert(renderer->gfxcore);
51
52 if (renderer->triangles) {
53 gfx_destroy_geometry(renderer->gfxcore, &renderer->triangles);
54 // TODO: Could also destroy the geometry's buffers here.
55 }
56
57 if (renderer->shader) {
58 gfx_destroy_shader_program(renderer->gfxcore, &renderer->shader);
59 }
60}
61
62void gfx_imm_flush(ImmRenderer* renderer) {
63 assert(renderer);
64
65 if (renderer->num_triangle_verts > 0) {
66 gfx_update_geometry(
67 renderer->triangles,
68 &(GeometryDesc){
69 .num_verts = renderer->num_triangle_verts,
70 .positions3d = (BufferView3d){
71 .data = renderer->triangle_verts,
72 .size_bytes = renderer->num_triangle_verts * sizeof(vec3)}
73 });
74
75 gfx_llr_render_geometry(renderer->llr, renderer->triangles);
76
77 renderer->num_triangle_verts = 0;
78 }
79}
80
81void gfx_imm_start(ImmRenderer* renderer) {
82 assert(renderer);
83
84 // Shader uniforms are applied lazily.
85 // TODO: In the event that gfx_activate_shader_program() activates uniforms
86 // automatically for convenience, call an overload here that doesn't do so.
87 // gfx_activate_shader_program(renderer->shader);
88 gfx_llr_set_shader(renderer->llr, renderer->shader);
89}
90
91void gfx_imm_end(ImmRenderer* renderer) {
92 assert(renderer);
93
94 gfx_imm_flush(renderer);
95 // gfx_deactivate_shader_program(renderer->shader);
96 gfx_llr_set_shader(renderer->llr, 0);
97}
98
99void gfx_imm_draw_triangles(
100 ImmRenderer* renderer, const vec3 verts[], size_t num_triangles) {
101 assert(renderer);
102 assert(verts);
103 const size_t new_verts = num_triangles * 3;
104 assert(
105 renderer->num_triangle_verts + new_verts <
106 (GFX_IMM_MAX_NUM_TRIANGLES * 3));
107
108 memcpy(
109 renderer->triangle_verts + renderer->num_triangle_verts, verts,
110 new_verts * sizeof(vec3));
111
112 renderer->num_triangle_verts += new_verts;
113}
114
115void gfx_imm_draw_triangle(ImmRenderer* renderer, const vec3 verts[3]) {
116 gfx_imm_draw_triangles(renderer, verts, 1);
117}
118
119void gfx_imm_draw_aabb2(ImmRenderer* renderer, aabb2 box) {
120 assert(renderer);
121
122 // clang-format off
123 const vec3 verts[4] = {
124 vec3_make(box.min.x, box.min.y, 0), // 3 ---- 2
125 vec3_make(box.max.x, box.min.y, 0), // | |
126 vec3_make(box.max.x, box.max.y, 0), // | |
127 vec3_make(box.min.x, box.max.y, 0)}; // 0 ---- 1
128 // clang-format on
129
130#define tri(i0, i1, i2) verts[i0], verts[i1], verts[i2]
131 const vec3 tris[6] = {tri(0, 1, 2), tri(0, 2, 3)};
132#undef tri
133
134 gfx_imm_draw_triangles(renderer, tris, 2);
135}
136
137void gfx_imm_draw_aabb3(ImmRenderer* renderer, aabb3 box) {
138 assert(renderer);
139
140 // clang-format off
141 const vec3 vertices[8] = {
142 vec3_make(box.min.x, box.min.y, box.max.z), // 7 ----- 6
143 vec3_make(box.max.x, box.min.y, box.max.z), // / /|
144 vec3_make(box.max.x, box.max.y, box.max.z), // 3 ----- 2 |
145 vec3_make(box.min.x, box.max.y, box.max.z), // | | |
146 vec3_make(box.min.x, box.min.y, box.min.z), // | 4 ----- 5
147 vec3_make(box.max.x, box.min.y, box.min.z), // |/ |/
148 vec3_make(box.max.x, box.max.y, box.min.z), // 0 ----- 1
149 vec3_make(box.min.x, box.max.y, box.min.z)};
150 // clang-format on
151
152 gfx_imm_draw_box3(renderer, vertices);
153}
154
155void gfx_imm_draw_box3(ImmRenderer* renderer, const vec3 vertices[8]) {
156 assert(renderer);
157 assert(vertices);
158
159 // 7 ----- 6
160 // / /|
161 // 3 ----- 2 |
162 // | | |
163 // | 4 ----- 5
164 // |/ |/
165 // 0 ----- 1
166
167#define tri(i0, i1, i2) vertices[i0], vertices[i1], vertices[i2]
168 const vec3 tris[36] = {
169 // Front.
170 tri(0, 1, 2), tri(0, 2, 3),
171 // Right.
172 tri(1, 5, 6), tri(1, 6, 2),
173 // Back.
174 tri(5, 4, 7), tri(5, 7, 6),
175 // Left.
176 tri(4, 0, 03), tri(4, 3, 7),
177 // Top.
178 tri(3, 2, 6), tri(3, 6, 7),
179 // Bottom.
180 tri(0, 4, 5), tri(0, 5, 1)};
181
182 gfx_imm_draw_triangles(renderer, tris, 12);
183}
184
185void gfx_imm_set_colour(ImmRenderer* renderer, vec4 colour) {
186 assert(renderer);
187 assert(renderer->shader);
188
189 gfx_imm_flush(renderer);
190
191 gfx_set_vec4_uniform(renderer->shader, "Colour", colour);
192}
diff --git a/gfx/src/renderer/imm_renderer_impl.h b/src/renderer/imm_renderer_impl.h
index 5ece354..61b49a7 100644
--- a/gfx/src/renderer/imm_renderer_impl.h
+++ b/src/renderer/imm_renderer_impl.h
@@ -1,44 +1,43 @@
1#pragma once 1#pragma once
2 2
3#include <gfx/renderer.h>
4#include <gfx/sizes.h> 3#include <gfx/sizes.h>
5 4
6#include <math/mat4.h>
7#include <math/vec3.h> 5#include <math/vec3.h>
8 6
9#include <stdbool.h> 7#include <stdbool.h>
10#include <stddef.h> 8#include <stddef.h>
11 9
12typedef struct Geometry Geometry; 10typedef struct Geometry Geometry;
11typedef struct GfxCore GfxCore;
12typedef struct IBL IBL;
13typedef struct LLR LLR;
14typedef struct Material Material;
13typedef struct ShaderProgram ShaderProgram; 15typedef struct ShaderProgram ShaderProgram;
16typedef struct Texture Texture;
14 17
15/// Immediate mode renderer. 18/// Immediate mode renderer.
16/// 19///
17/// Currently, the immediate mode renderer can only draw up to a maximum number 20/// Currently, the immediate mode renderer can only draw up to a maximum number
18/// of primitives per frame. It does not adjust this number dynamically. Keeps 21/// of primitives per frame. It does not adjust this number dynamically. Keeps
19/// things simple while the extra complexity is not needed. 22/// things simple while the extra complexity is not needed.
23/// TODO: Flush the buffer when it reaches its maximum size to remove this
24/// constraint.
20typedef struct ImmRenderer { 25typedef struct ImmRenderer {
21 GfxCore* gfxcore; 26 GfxCore* gfxcore;
22 ShaderProgram* shader; 27 LLR* llr;
28
29 ShaderProgram* shader; // Immediate-mode shader program for primitives.
23 Geometry* triangles; 30 Geometry* triangles;
24 size_t num_triangle_verts; // Number of triangle verts this frame. 31 size_t num_triangle_verts; // Number of triangle verts this frame.
25 // TODO: wireframe rendering. 32 // TODO: wireframe rendering.
26 struct { 33 struct {
27 bool wireframe : 1; 34 bool wireframe : 1;
28 } flags; 35 } flags;
29 vec3 triangle_verts[IMM_MAX_NUM_TRIANGLES * 3]; 36 vec3 triangle_verts[GFX_IMM_MAX_NUM_TRIANGLES * 3];
30 // Matrix stack contains pre-multiplied matrices.
31 // It is also never empty. The top of the stack is an identity matrix when the
32 // stack is "empty" from the user's perspective.
33 mat4 matrix_stack[IMM_MAX_NUM_MATRICES];
34 int stack_pointer;
35} ImmRenderer; 37} ImmRenderer;
36 38
37/// Create a new immediate mode renderer. 39/// Create a new immediate mode renderer.
38bool imm_renderer_make(ImmRenderer*, GfxCore*); 40bool gfx_imm_make(ImmRenderer*, GfxCore*, LLR*);
39 41
40/// Destroy the immediate mode renderer. 42/// Destroy the immediate mode renderer.
41void imm_renderer_destroy(ImmRenderer*); 43void gfx_imm_destroy(ImmRenderer*);
42
43/// Flush draw commands.
44void imm_renderer_flush(ImmRenderer*);
diff --git a/src/renderer/renderer.c b/src/renderer/renderer.c
new file mode 100644
index 0000000..29a1813
--- /dev/null
+++ b/src/renderer/renderer.c
@@ -0,0 +1,247 @@
1#include "renderer_impl.h"
2
3#include "llr/light_impl.h"
4#include "llr/mesh_impl.h"
5#include "scene/animation_impl.h"
6#include "scene/camera_impl.h"
7#include "scene/model_impl.h"
8#include "scene/node_impl.h"
9#include "scene/object_impl.h"
10#include "scene/scene_impl.h"
11#include "scene/scene_memory.h"
12
13#include <gfx/core.h>
14#include <gfx/llr/llr.h>
15#include <gfx/util/shader.h>
16
17#include <math/mat4.h>
18
19#include <assert.h>
20
21bool gfx_renderer_make(Renderer* renderer, LLR* llr, GfxCore* gfxcore) {
22 assert(renderer);
23 assert(llr);
24 assert(gfxcore);
25
26 renderer->gfxcore = gfxcore;
27 renderer->llr = llr;
28
29 return true;
30}
31
32void gfx_renderer_destroy(Renderer* renderer) {
33 if (!renderer) {
34 return;
35 }
36 assert(renderer->gfxcore);
37 GfxCore* gfxcore = renderer->gfxcore;
38 if (renderer->shaders.debug) {
39 gfx_destroy_shader_program(gfxcore, &renderer->shaders.debug);
40 }
41 if (renderer->shaders.normals) {
42 gfx_destroy_shader_program(gfxcore, &renderer->shaders.normals);
43 }
44 if (renderer->shaders.normal_mapped_normals) {
45 gfx_destroy_shader_program(
46 gfxcore, &renderer->shaders.normal_mapped_normals);
47 }
48 if (renderer->shaders.tangents) {
49 gfx_destroy_shader_program(gfxcore, &renderer->shaders.tangents);
50 }
51}
52
53static ShaderProgram* load_shader(Renderer* renderer, RenderSceneMode mode) {
54 assert(renderer);
55
56#define LOAD_AND_RETURN(pShader, constructor) \
57 { \
58 if (!pShader) { \
59 pShader = constructor(renderer->gfxcore); \
60 } \
61 assert(pShader); \
62 return pShader; \
63 }
64
65 switch (mode) {
66 case RenderDefault:
67 return 0;
68 case RenderDebug:
69 LOAD_AND_RETURN(renderer->shaders.debug, gfx_make_debug3d_shader);
70 case RenderNormals:
71 LOAD_AND_RETURN(renderer->shaders.normals, gfx_make_view_normals_shader);
72 case RenderNormalMappedNormals:
73 LOAD_AND_RETURN(
74 renderer->shaders.normal_mapped_normals,
75 gfx_make_view_normal_mapped_normals_shader);
76 case RenderTangents:
77 LOAD_AND_RETURN(renderer->shaders.tangents, gfx_make_view_tangents_shader);
78 }
79 assert(false);
80 return 0;
81}
82
83// static void log_matrix(const mat4* m) {
84// for (int row = 0; row < 4; ++row) {
85// LOGI("[ %5.2f, %5.2f, %5.2f, %5.2f ]", m->val[0][row], m->val[1][row],
86// m->val[2][row], m->val[3][row]);
87// }
88// }
89
90typedef struct RenderState {
91 GfxCore* gfxcore;
92 LLR* llr;
93 Renderer* renderer;
94 ShaderProgram* shader; // Null to use scene shaders.
95 const Scene* scene;
96 const Anima* anima;
97} RenderState;
98
99static void draw_children(
100 RenderState* state, const mat4* node_transform, const SceneNode* node);
101
102/// Draw the scene recursively.
103static void draw_recursively(
104 RenderState* state, mat4 parent_transform, const SceneNode* node) {
105 assert(state);
106 const mat4 node_transform = mat4_mul(parent_transform, node->transform);
107
108 // Anima.
109 if (node->type == AnimaNode) {
110 // Save the anima so that we can animate objects.
111 state->anima = gfx_get_node_anima(node);
112
113 draw_children(state, &node_transform, node);
114 }
115 // Activate light.
116 else if (node->type == LightNode) {
117 Light* light = mem_get_light(node->light);
118 assert(light);
119 gfx_llr_push_light(state->llr, light);
120 {
121 draw_children(state, &node_transform, node);
122 }
123 gfx_llr_pop_light(state->llr);
124 }
125 // Model.
126 else if (node->type == ModelNode) {
127 const Model* model = gfx_get_node_model(node);
128 const SceneNode* root = mem_get_node(model->root);
129 draw_recursively(state, parent_transform, root);
130 draw_children(state, &node_transform, node);
131 }
132 // Render object.
133 else if (node->type == ObjectNode) {
134 const SceneObject* object = mem_get_object(node->object);
135 assert(object);
136
137 // TODO: Here we would frustum-cull the object.
138
139 // A model/anima can have many skeletons. We need to animate the given
140 // object using its skeleton, not just any skeleton of the anima.
141 if (object->skeleton.val) {
142 const Skeleton* skeleton = mem_get_skeleton(object->skeleton);
143 gfx_llr_set_skeleton(state->llr, state->anima, skeleton);
144 }
145
146 const mat4 model_matrix = node_transform;
147
148 for (mesh_link_idx mesh_link_index = object->mesh_link;
149 mesh_link_index.val;) {
150 const MeshLink* mesh_link = mem_get_mesh_link(mesh_link_index);
151 mesh_link_index = mesh_link->next;
152
153 const Mesh* mesh = mem_get_mesh(mesh_link->mesh);
154 if (!mesh) {
155 continue;
156 }
157
158 // TODO: Here we would frustum-cull the mesh. The AABB would have to be
159 // transformed by the model matrix. Rotation would make the AABB
160 // relatively large, but still, the culling would be conservative.
161
162 ShaderProgram* shader = state->shader ? state->shader : mesh->shader;
163 gfx_llr_set_shader(state->llr, shader);
164 gfx_llr_set_model_matrix(state->llr, &model_matrix);
165 gfx_llr_render_mesh(state->llr, mesh);
166 }
167
168 if (object->skeleton.val) {
169 gfx_llr_clear_skeleton(state->llr);
170 }
171
172 draw_children(state, &node_transform, node);
173 } else {
174 draw_children(state, &node_transform, node);
175 }
176}
177
178/// Draw the node's children.
179static void draw_children(
180 RenderState* state, const mat4* node_transform, const SceneNode* node) {
181 // Render children recursively.
182 for (node_idx child_index = node->child; child_index.val;) {
183 const SceneNode* child = mem_get_node(child_index);
184 draw_recursively(state, *node_transform, child);
185 child_index = child->next;
186 }
187}
188
189void gfx_render_scene(Renderer* renderer, const RenderSceneParams* params) {
190 assert(renderer);
191 assert(params);
192 assert(params->scene);
193
194 ShaderProgram* const shader = load_shader(renderer, params->mode);
195
196 const Scene* scene = params->scene;
197 const SceneCamera* camera = params->camera;
198 GfxCore* const gfxcore = renderer->gfxcore;
199
200 int x, y, width, height;
201 gfx_get_viewport(gfxcore, &x, &y, &width, &height);
202 const R aspect = (R)width / (R)height;
203
204 RenderState state = {
205 .gfxcore = gfxcore,
206 .llr = renderer->llr,
207 .renderer = renderer,
208 .shader = shader,
209 .scene = scene};
210
211 gfx_llr_set_camera(renderer->llr, &camera->camera);
212 gfx_llr_set_aspect(renderer->llr, aspect);
213 draw_recursively(&state, mat4_id(), scene->root);
214}
215
216static void update_rec(SceneNode* node, const SceneCamera* camera, R t) {
217 assert(node);
218 assert(camera);
219
220 const NodeType node_type = gfx_get_node_type(node);
221
222 // TODO: Models do not need to be animated if they are not visible to the
223 // camera.
224 if (node_type == AnimaNode) {
225 Anima* anima = gfx_get_node_anima_mut(node);
226 gfx_update_animation(anima, (R)t);
227 } else if (node_type == ModelNode) {
228 Model* model = gfx_get_node_model_mut(node);
229 SceneNode* root = gfx_get_model_root_mut(model);
230 update_rec(root, camera, t);
231 }
232
233 // Children.
234 SceneNode* child = gfx_get_node_child_mut(node);
235 while (child) {
236 update_rec(child, camera, t);
237 child = gfx_get_node_sibling_mut(child);
238 }
239}
240
241void gfx_update(Scene* scene, const SceneCamera* camera, R t) {
242 assert(scene);
243 assert(camera);
244
245 SceneNode* node = gfx_get_scene_root(scene);
246 update_rec(node, camera, t);
247}
diff --git a/gfx/src/renderer/renderer_impl.h b/src/renderer/renderer_impl.h
index fc14dcb..7395915 100644
--- a/gfx/src/renderer/renderer_impl.h
+++ b/src/renderer/renderer_impl.h
@@ -4,14 +4,12 @@
4 4
5#include <stdbool.h> 5#include <stdbool.h>
6 6
7typedef struct IBL IBL; 7typedef struct LLR LLR;
8typedef struct ShaderProgram ShaderProgram; 8typedef struct ShaderProgram ShaderProgram;
9typedef struct Texture Texture;
10 9
11typedef struct Renderer { 10typedef struct Renderer {
12 GfxCore* gfxcore; 11 GfxCore* gfxcore;
13 IBL* ibl; 12 LLR* llr;
14 Texture* brdf_integration_map;
15 struct { 13 struct {
16 ShaderProgram* debug; 14 ShaderProgram* debug;
17 ShaderProgram* normals; 15 ShaderProgram* normals;
@@ -21,7 +19,7 @@ typedef struct Renderer {
21} Renderer; 19} Renderer;
22 20
23/// Create a new renderer. 21/// Create a new renderer.
24bool renderer_make(Renderer*, GfxCore*); 22bool gfx_renderer_make(Renderer*, LLR*, GfxCore*);
25 23
26/// Destroy the renderer. 24/// Destroy the renderer.
27void renderer_destroy(Renderer*); 25void gfx_renderer_destroy(Renderer*);
diff --git a/gfx/src/scene/animation.c b/src/scene/animation.c
index 08d02ce..08d02ce 100644
--- a/gfx/src/scene/animation.c
+++ b/src/scene/animation.c
diff --git a/gfx/src/scene/animation_impl.h b/src/scene/animation_impl.h
index 4408158..4408158 100644
--- a/gfx/src/scene/animation_impl.h
+++ b/src/scene/animation_impl.h
diff --git a/gfx/src/scene/camera.c b/src/scene/camera.c
index be7d806..be7d806 100644
--- a/gfx/src/scene/camera.c
+++ b/src/scene/camera.c
diff --git a/gfx/src/scene/camera_impl.h b/src/scene/camera_impl.h
index 20c3890..20c3890 100644
--- a/gfx/src/scene/camera_impl.h
+++ b/src/scene/camera_impl.h
diff --git a/gfx/src/scene/model.c b/src/scene/model.c
index cc41a9a..cc41a9a 100644
--- a/gfx/src/scene/model.c
+++ b/src/scene/model.c
diff --git a/gfx/src/scene/model_impl.h b/src/scene/model_impl.h
index a99d32c..a99d32c 100644
--- a/gfx/src/scene/model_impl.h
+++ b/src/scene/model_impl.h
diff --git a/gfx/src/scene/node.c b/src/scene/node.c
index 67ce93c..e359f73 100644
--- a/gfx/src/scene/node.c
+++ b/src/scene/node.c
@@ -2,7 +2,7 @@
2 2
3#include "animation_impl.h" 3#include "animation_impl.h"
4#include "camera_impl.h" 4#include "camera_impl.h"
5#include "light_impl.h" 5#include "llr/light_impl.h"
6#include "model_impl.h" 6#include "model_impl.h"
7#include "object_impl.h" 7#include "object_impl.h"
8#include "scene_graph.h" 8#include "scene_graph.h"
diff --git a/gfx/src/scene/node_impl.h b/src/scene/node_impl.h
index c79f252..c79f252 100644
--- a/gfx/src/scene/node_impl.h
+++ b/src/scene/node_impl.h
diff --git a/gfx/src/scene/object.c b/src/scene/object.c
index e8e3ee6..27ff5db 100644
--- a/gfx/src/scene/object.c
+++ b/src/scene/object.c
@@ -2,7 +2,7 @@
2 2
3#include <gfx/core.h> 3#include <gfx/core.h>
4 4
5#include "mesh_impl.h" 5#include "llr/mesh_impl.h"
6#include "node_impl.h" 6#include "node_impl.h"
7#include "scene_memory.h" 7#include "scene_memory.h"
8 8
diff --git a/gfx/src/scene/object_impl.h b/src/scene/object_impl.h
index 88f8e31..e864e53 100644
--- a/gfx/src/scene/object_impl.h
+++ b/src/scene/object_impl.h
@@ -4,8 +4,6 @@
4 4
5#include "types.h" 5#include "types.h"
6 6
7#include <math/mat4.h>
8
9typedef struct MeshLink { 7typedef struct MeshLink {
10 mesh_idx mesh; 8 mesh_idx mesh;
11 mesh_link_idx next; // Next MeshLink in the list. 9 mesh_link_idx next; // Next MeshLink in the list.
@@ -21,6 +19,7 @@ typedef struct MeshLink {
21typedef struct SceneObject { 19typedef struct SceneObject {
22 mesh_link_idx mesh_link; /// First MeshLink in the list. 20 mesh_link_idx mesh_link; /// First MeshLink in the list.
23 skeleton_idx skeleton; /// 0 for static objects. 21 skeleton_idx skeleton; /// 0 for static objects.
24 node_idx parent; /// Parent SceneNode. 22 // TODO: Can we just store node indices in nodes, not the contained object?
25 aabb3 box; 23 node_idx parent; /// Parent SceneNode.
24 aabb3 box;
26} SceneObject; 25} SceneObject;
diff --git a/gfx/src/scene/scene.c b/src/scene/scene.c
index 54452dd..54452dd 100644
--- a/gfx/src/scene/scene.c
+++ b/src/scene/scene.c
diff --git a/gfx/src/scene/scene_graph.h b/src/scene/scene_graph.h
index a26f828..0b1f7d0 100644
--- a/gfx/src/scene/scene_graph.h
+++ b/src/scene/scene_graph.h
@@ -6,35 +6,41 @@
6// NOTE: SceneMemory guarantees that index 0 can be regarded as an invalid 6// NOTE: SceneMemory guarantees that index 0 can be regarded as an invalid
7// index. 7// index.
8 8
9#define MEM_GET(INDEX) \ 9#define MEM_GET(INDEX) \
10 _Generic((INDEX), camera_idx \ 10 _Generic( \
11 : mem_get_camera, material_idx \ 11 (INDEX), \
12 : mem_get_material, mesh_idx \ 12 camera_idx: mem_get_camera, \
13 : mem_get_mesh, mesh_link_idx \ 13 material_idx: mem_get_material, \
14 : mem_get_mesh_link, node_idx \ 14 mesh_idx: mem_get_mesh, \
15 : mem_get_node, object_idx \ 15 mesh_link_idx: mem_get_mesh_link, \
16 : mem_get_object, scene_idx \ 16 node_idx: mem_get_node, \
17 : mem_get_scene)(INDEX) 17 object_idx: mem_get_object, \
18 scene_idx: mem_get_scene)(INDEX)
18 19
19#define MEM_GET_INDEX(ITEM) \ 20#define MEM_GET_INDEX(ITEM) \
20 _Generic((ITEM), SceneCamera * \ 21 _Generic( \
21 : mem_get_camera_index, Material * \ 22 (ITEM), \
22 : mem_get_material_index, Mesh * \ 23 SceneCamera *: mem_get_camera_index, \
23 : mem_get_mesh_index, MeshLink * \ 24 Material *: mem_get_material_index, \
24 : mem_get_mesh_link_index, SceneNode * \ 25 Mesh *: mem_get_mesh_index, \
25 : mem_get_node_index, SceneObject * \ 26 MeshLink *: mem_get_mesh_link_index, \
26 : mem_get_object_index, Scene * \ 27 SceneNode *: mem_get_node_index, \
27 : mem_get_scene_index)(ITEM) 28 SceneObject *: mem_get_object_index, \
29 Scene *: mem_get_scene_index)(ITEM)
28 30
29/// Assert the list node invariant. 31/// Assert the list node invariant.
30/// 32///
31/// - A node does not point to itself. 33/// - A node does not point to itself.
34#if NDEBUG
35#define ASSERT_LIST_NODE_INVARIANT(ITEM)
36#else
32#define ASSERT_LIST_NODE_INVARIANT(ITEM) \ 37#define ASSERT_LIST_NODE_INVARIANT(ITEM) \
33 { \ 38 { \
34 const gfx_idx item_idx = MEM_GET_INDEX(ITEM).val; \ 39 const gfx_idx item_idx = MEM_GET_INDEX(ITEM).val; \
35 assert((ITEM)->prev.val != item_idx); \ 40 assert((ITEM)->prev.val != item_idx); \
36 assert((ITEM)->next.val != item_idx); \ 41 assert((ITEM)->next.val != item_idx); \
37 } 42 }
43#endif
38 44
39/// Assert the tree node invariant. 45/// Assert the tree node invariant.
40/// 46///
diff --git a/gfx/src/scene/scene_impl.h b/src/scene/scene_impl.h
index 992f620..992f620 100644
--- a/gfx/src/scene/scene_impl.h
+++ b/src/scene/scene_impl.h
diff --git a/gfx/src/scene/scene_memory.c b/src/scene/scene_memory.c
index 85c27e7..3a01325 100644
--- a/gfx/src/scene/scene_memory.c
+++ b/src/scene/scene_memory.c
@@ -4,14 +4,15 @@
4 4
5#include "animation_impl.h" 5#include "animation_impl.h"
6#include "camera_impl.h" 6#include "camera_impl.h"
7#include "light_impl.h" 7#include "llr/light_impl.h"
8#include "material_impl.h" 8#include "llr/material_impl.h"
9#include "mesh_impl.h" 9#include "llr/mesh_impl.h"
10#include "model_impl.h" 10#include "model_impl.h"
11#include "node_impl.h" 11#include "node_impl.h"
12#include "object_impl.h" 12#include "object_impl.h"
13#include "scene_impl.h" 13#include "scene_impl.h"
14 14
15#include <log/log.h>
15#include <mempool.h> 16#include <mempool.h>
16 17
17DEF_MEMPOOL(anima_pool, Anima, GFX_MAX_NUM_ANIMAS) 18DEF_MEMPOOL(anima_pool, Anima, GFX_MAX_NUM_ANIMAS)
@@ -47,10 +48,11 @@ typedef struct SceneMemory {
47 48
48static SceneMemory mem; 49static SceneMemory mem;
49 50
50#define ALLOC_DUMMY(POOL) \ 51#define ALLOC_DUMMY(POOL) \
51 { \ 52 { \
52 const void* object = mempool_alloc(POOL); \ 53 const void* object = mempool_alloc(POOL); \
53 assert(mempool_get_block_index(POOL, object) == 0); \ 54 (void)object; /* Silence warning in release builds. */ \
55 assert(mempool_get_block_index(POOL, object) == 0); \
54 } 56 }
55 57
56#define PLURAL(name) name##s 58#define PLURAL(name) name##s
@@ -71,7 +73,7 @@ void scene_mem_init() {
71 mempool_make(&mem.skeletons); 73 mempool_make(&mem.skeletons);
72 74
73 // Allocate dummy objects at index 0 to guarantee that no objects allocated by 75 // Allocate dummy objects at index 0 to guarantee that no objects allocated by
74 // the caller map to index 0. 76 // the caller map to index 0. This allows 0 to be used as a sentinel.
75 ALLOC_DUMMY(&mem.animas); 77 ALLOC_DUMMY(&mem.animas);
76 ALLOC_DUMMY(&mem.animations); 78 ALLOC_DUMMY(&mem.animations);
77 ALLOC_DUMMY(&mem.cameras); 79 ALLOC_DUMMY(&mem.cameras);
@@ -90,13 +92,40 @@ void scene_mem_destroy() {
90 // NOTE: the dummy objects are not constructed, so the destruction code below 92 // NOTE: the dummy objects are not constructed, so the destruction code below
91 // always skips index 0. (I don't really like the conditional inside the loop, 93 // always skips index 0. (I don't really like the conditional inside the loop,
92 // but this gets the job done without having to specialize the loop macro.) 94 // but this gets the job done without having to specialize the loop macro.)
93#define DESTROY(name) \ 95#define DESTROY(NAME) \
94 mempool_foreach(&MEM_FIELD(name), obj, { \ 96 mempool_foreach(&MEM_FIELD(NAME), obj, { \
95 if (i > 0) { \ 97 if (i > 0) { \
96 gfx_destroy_##name(&obj); \ 98 gfx_destroy_##NAME(&obj); \
97 } \ 99 } \
98 }) 100 })
99 101
102 // Print memory diagnostics.
103#define PRINT_POOL(POOL_NAME, POOL) \
104 { \
105 const size_t capacity = mempool_capacity(POOL); \
106 const size_t size = mempool_size(POOL); \
107 const size_t block_size_bytes = mempool_block_size_bytes(POOL); \
108 const size_t size_bytes = size * block_size_bytes; \
109 const size_t capacity_bytes = capacity * block_size_bytes; \
110 LOGI( \
111 "%s pool: %lu/%lu (%lu/%lu bytes)", POOL_NAME, size, capacity, \
112 size_bytes, capacity_bytes); \
113 }
114
115 LOGI("Pool diagnostics:");
116 PRINT_POOL("Animas", &mem.animas);
117 PRINT_POOL("Animations", &mem.animations);
118 PRINT_POOL("Cameras", &mem.cameras);
119 PRINT_POOL("Lights", &mem.lights);
120 PRINT_POOL("Materials", &mem.materials);
121 PRINT_POOL("Meshes", &mem.meshs);
122 PRINT_POOL("Mesh links", &mem.mesh_links);
123 PRINT_POOL("Models", &mem.models);
124 PRINT_POOL("Nodes", &mem.nodes);
125 PRINT_POOL("Objects", &mem.objects);
126 PRINT_POOL("Scenes", &mem.scenes);
127 PRINT_POOL("Skeletons", &mem.skeletons);
128
100 // Models contain scene elements. Destruction is handled by the remainder of 129 // Models contain scene elements. Destruction is handled by the remainder of
101 // scene destructionb elow. 130 // scene destructionb elow.
102 // 131 //
@@ -119,20 +148,20 @@ void scene_mem_destroy() {
119 // Skeletons are owned by animas and do not have a destructor. 148 // Skeletons are owned by animas and do not have a destructor.
120} 149}
121 150
122#define DEF_MEMORY(name, type) \ 151#define DEF_MEMORY(NAME, TYPE) \
123 /* xyz* mem_alloc_xyz(); */ \ 152 /* xyz* mem_alloc_xyz(); */ \
124 type* mem_alloc_##name() { return mempool_alloc(&MEM_FIELD(name)); } \ 153 TYPE* mem_alloc_##NAME() { return mempool_alloc(&MEM_FIELD(NAME)); } \
125 /* void mem_free_xyz(xyz**); */ \ 154 /* void mem_free_xyz(xyz**); */ \
126 void mem_free_##name(type** obj) { mempool_free(&MEM_FIELD(name), obj); } \ 155 void mem_free_##NAME(TYPE** obj) { mempool_free(&MEM_FIELD(NAME), obj); } \
127 /* xyz* mem_get_xyz(xyz_idx); */ \ 156 /* xyz* mem_get_xyz(xyz_idx); */ \
128 type* mem_get_##name(NAMED_INDEX(name) index) { \ 157 TYPE* mem_get_##NAME(NAMED_INDEX(NAME) index) { \
129 assert(index.val != 0); /* 0 is the dummy allocation. */ \ 158 assert(index.val != 0); /* 0 is the dummy allocation. */ \
130 return mempool_get_block(&MEM_FIELD(name), index.val); \ 159 return mempool_get_block(&MEM_FIELD(NAME), index.val); \
131 } \ 160 } \
132 /* xyz_idx mem_get_xyz_index(const xyz*); */ \ 161 /* xyz_idx mem_get_xyz_index(const xyz*); */ \
133 NAMED_INDEX(name) mem_get_##name##_index(const type* obj) { \ 162 NAMED_INDEX(NAME) mem_get_##NAME##_index(const TYPE* obj) { \
134 return (NAMED_INDEX(name)){ \ 163 return (NAMED_INDEX(NAME)){ \
135 .val = mempool_get_block_index(&MEM_FIELD(name), obj)}; \ 164 .val = mempool_get_block_index(&MEM_FIELD(NAME), obj)}; \
136 } 165 }
137 166
138DEF_MEMORY(anima, Anima) 167DEF_MEMORY(anima, Anima)
diff --git a/gfx/src/scene/scene_memory.h b/src/scene/scene_memory.h
index d175cba..d175cba 100644
--- a/gfx/src/scene/scene_memory.h
+++ b/src/scene/scene_memory.h
diff --git a/gfx/src/scene/types.h b/src/scene/types.h
index d0ffc41..d0ffc41 100644
--- a/gfx/src/scene/types.h
+++ b/src/scene/types.h
diff --git a/gfx/src/util/geometry.c b/src/util/geometry.c
index afe0109..afe0109 100644
--- a/gfx/src/util/geometry.c
+++ b/src/util/geometry.c
diff --git a/gfx/src/util/ibl.c b/src/util/ibl.c
index 5a79990..5a79990 100644
--- a/gfx/src/util/ibl.c
+++ b/src/util/ibl.c
diff --git a/gfx/src/util/shader.c b/src/util/shader.c
index f5c22cc..f5c22cc 100644
--- a/gfx/src/util/shader.c
+++ b/src/util/shader.c
diff --git a/gfx/src/util/skyquad.c b/src/util/skyquad.c
index 08fa044..094de67 100644
--- a/gfx/src/util/skyquad.c
+++ b/src/util/skyquad.c
@@ -1,18 +1,14 @@
1#include <gfx/util/skyquad.h> 1#include <gfx/util/skyquad.h>
2 2
3#include <gfx/core.h> 3#include <gfx/core.h>
4#include <gfx/gfx.h> 4#include <gfx/llr/light.h>
5#include <gfx/scene/light.h> 5#include <gfx/llr/material.h>
6#include <gfx/scene/material.h> 6#include <gfx/llr/mesh.h>
7#include <gfx/scene/mesh.h>
8#include <gfx/scene/node.h> 7#include <gfx/scene/node.h>
9#include <gfx/scene/object.h> 8#include <gfx/scene/object.h>
10#include <gfx/scene/scene.h>
11#include <gfx/util/geometry.h> 9#include <gfx/util/geometry.h>
12#include <gfx/util/shader.h> 10#include <gfx/util/shader.h>
13 11
14#include <math/vec4.h>
15
16#include <assert.h> 12#include <assert.h>
17 13
18SceneObject* gfx_make_skyquad(GfxCore* gfxcore, const Texture* texture) { 14SceneObject* gfx_make_skyquad(GfxCore* gfxcore, const Texture* texture) {
@@ -36,10 +32,9 @@ SceneObject* gfx_make_skyquad(GfxCore* gfxcore, const Texture* texture) {
36 } 32 }
37 33
38 MaterialDesc material_desc = (MaterialDesc){0}; 34 MaterialDesc material_desc = (MaterialDesc){0};
39 material_desc.uniforms[0] = (ShaderUniform){ 35 material_desc.uniforms[0] = (ShaderUniform){.type = UniformTexture,
40 .type = UniformTexture, 36 .value.texture = texture,
41 .value.texture = texture, 37 .name = sstring_make("Skyquad")};
42 .name = sstring_make("Skyquad")};
43 material_desc.num_uniforms = 1; 38 material_desc.num_uniforms = 1;
44 material = gfx_make_material(&material_desc); 39 material = gfx_make_material(&material_desc);
45 if (!material) { 40 if (!material) {