aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--CMakeLists.txt99
-rw-r--r--README.md (renamed from gfx/README.md)56
-rw-r--r--app/CMakeLists.txt2
-rw-r--r--app/demo/CMakeLists.txt2
-rw-r--r--app/include/gfx/app.h31
-rw-r--r--app/src/app.c46
-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)2
-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/include/gfx/scene.h11
-rw-r--r--gfx/include/gfx/scene/camera.h22
-rw-r--r--gfx/include/gfx/scene/light.h30
-rw-r--r--gfx/include/gfx/scene/material.h25
-rw-r--r--gfx/include/gfx/scene/mesh.h23
-rw-r--r--gfx/include/gfx/scene/model.h12
-rw-r--r--gfx/include/gfx/scene/node.h156
-rw-r--r--gfx/include/gfx/scene/object.h39
-rw-r--r--gfx/include/gfx/scene/scene.h21
-rw-r--r--gfx/src/renderer/imm_renderer.c260
-rw-r--r--gfx/src/renderer/renderer.c396
-rw-r--r--gfx/src/scene/camera.c37
-rw-r--r--gfx/src/scene/camera_impl.h12
-rw-r--r--gfx/src/scene/material.c57
-rw-r--r--gfx/src/scene/material_impl.h16
-rw-r--r--gfx/src/scene/mesh_impl.h12
-rw-r--r--gfx/src/scene/scene.c25
-rw-r--r--gfx/src/scene/scene_impl.h13
-rw-r--r--include/gfx/animation.h (renamed from gfx/include/gfx/scene/animation.h)5
-rw-r--r--include/gfx/asset.h (renamed from gfx/include/gfx/asset.h)4
-rw-r--r--include/gfx/core.h (renamed from gfx/include/gfx/core.h)34
-rw-r--r--include/gfx/gfx.h (renamed from gfx/include/gfx/gfx.h)10
-rw-r--r--include/gfx/render/imm.h52
-rw-r--r--include/gfx/render/llr.h81
-rw-r--r--include/gfx/render/renderer.h36
-rw-r--r--include/gfx/scene.h296
-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)1
-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)123
-rw-r--r--shaders/cook_torrance.vert (renamed from gfx/shaders/cook_torrance.vert)24
-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/animation.c (renamed from gfx/src/scene/animation.c)29
-rw-r--r--src/animation_impl.h (renamed from gfx/src/scene/animation_impl.h)32
-rw-r--r--src/asset/asset_cache.c (renamed from gfx/src/asset/asset_cache.c)9
-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)552
-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)2
-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)40
-rw-r--r--src/core/core_impl.h (renamed from gfx/src/core/core_impl.h)8
-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)73
-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)148
-rw-r--r--src/core/shader_program.h (renamed from gfx/src/core/shader_program.h)2
-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)40
-rw-r--r--src/gfx_assert.h (renamed from gfx/src/gfx_assert.h)0
-rw-r--r--src/memory.c (renamed from gfx/src/scene/scene_memory.c)87
-rw-r--r--src/memory.h (renamed from gfx/src/scene/scene_memory.h)10
-rw-r--r--src/render/imm.c194
-rw-r--r--src/render/imm_impl.h (renamed from gfx/src/renderer/imm_renderer_impl.h)33
-rw-r--r--src/render/llr.c441
-rw-r--r--src/render/llr_impl.h85
-rw-r--r--src/render/renderer.c282
-rw-r--r--src/render/renderer_impl.h (renamed from gfx/src/renderer/renderer_impl.h)12
-rw-r--r--src/scene/camera.c18
-rw-r--r--src/scene/light.c (renamed from gfx/src/scene/light.c)7
-rw-r--r--src/scene/light_impl.h (renamed from gfx/src/scene/light_impl.h)9
-rw-r--r--src/scene/material.c24
-rw-r--r--src/scene/material_impl.h13
-rw-r--r--src/scene/mesh.c (renamed from gfx/src/scene/mesh.c)6
-rw-r--r--src/scene/mesh_impl.h11
-rw-r--r--src/scene/model.c (renamed from gfx/src/scene/model.c)4
-rw-r--r--src/scene/model_impl.h (renamed from gfx/src/scene/model_impl.h)5
-rw-r--r--src/scene/node.c (renamed from gfx/src/scene/node.c)83
-rw-r--r--src/scene/node_impl.h (renamed from gfx/src/scene/node_impl.h)6
-rw-r--r--src/scene/object.c (renamed from gfx/src/scene/object.c)11
-rw-r--r--src/scene/object_impl.h (renamed from gfx/src/scene/object_impl.h)7
-rw-r--r--src/scene/scene.c31
-rw-r--r--src/scene/scene_graph.h (renamed from gfx/src/scene/scene_graph.h)44
-rw-r--r--src/scene/scene_impl.h9
-rw-r--r--src/types.h (renamed from gfx/src/scene/types.h)0
-rw-r--r--src/util/geometry.c (renamed from gfx/src/util/geometry.c)13
-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)18
167 files changed, 2552 insertions, 5256 deletions
diff --git a/CMakeLists.txt b/CMakeLists.txt
index 25c7560..7d3767d 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -1,8 +1,97 @@
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/animation.c
42 src/asset/asset_cache.c
43 src/asset/model.c
44 src/asset/texture.c
45 src/core/buffer.c
46 src/core/core.c
47 src/core/framebuffer.c
48 src/core/geometry.c
49 src/core/renderbuffer.c
50 src/core/shader_program.c
51 src/core/shader.c
52 src/core/texture.c
53 src/memory.c
54 src/render/imm.c
55 src/render/llr.c
56 src/render/renderer.c
57 src/scene/camera.c
58 src/scene/light.c
59 src/scene/material.c
60 src/scene/mesh.c
61 src/scene/model.c
62 src/scene/node.c
63 src/scene/object.c
64 src/scene/scene.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 hash
82 math)
83
84target_link_libraries(gfx PRIVATE
85 cassert
86 cgltf
87 cgltf-tangents
88 error
89 gfx-app
90 log
91 mempool
92 shaders
93 stb
94 # System libraries.
95 GL
96 # Required to initialize GLAD.
97 -ldl)
diff --git a/gfx/README.md b/README.md
index f0b103d..3a30ee4 100644
--- a/gfx/README.md
+++ b/README.md
@@ -20,32 +20,45 @@ 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 ("Core")
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
46The low-level renderer (`LLR`) provides a low-level, immediate-mode interface
47on top of the render backend. It understands higher-level concepts to make
48rendering more convenient (object, mesh, material, light, camera, etc), but is
49still relatively low-level and does not understand any particular scene data
50structure.
51
52### Immediate-mode Renderer
53
54The immediate-mode renderer (`Imm`) provides an API to define and render
55geometry procedurally and on the spot. It should be reserved for debug or
56not-performance-intensive rendering.
57
44### Scene 58### Scene
45 59
46A `Scene` encapsulates a scene graph. A scene graph contains the elements that 60A `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 61graph implementation includes:
48implementation includes:
49 62
50- Camera 63- Camera
51- Light 64- Light
@@ -64,7 +77,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. 77former, the API could create the illusion that the hierarchy can be a DAG.
65 78
66The strict tree hierarchy should not be that restrictive in practice. Even the 79The 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): 80glTF 2.0
81spec [enforces this](https://github.com/KhronosGroup/glTF/blob/master/specification/2.0/README.md#nodes-and-hierarchy):
68 82
69> *For Version 2.0 conformance, the glTF node hierarchy is not a directed 83> *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 84> acyclic graph (DAG) or scene graph, but a disjoint union of strict trees. That
@@ -76,9 +90,9 @@ glTF 2.0 spec [enforces this](https://github.com/KhronosGroup/glTF/blob/master/s
76Two use cases for instancing seem to be: 90Two use cases for instancing seem to be:
77 91
781. Creating N identical clones, but each with a unique transform. (Ex: N 921. Creating N identical clones, but each with a unique transform. (Ex: N
79animated characters animated in unison but located in different locations.) 93 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: 942. 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.) 95 The same N animated characters, but each of them now being animated separately.)
82 96
83Some scene graphs 97Some scene graphs
84([Panda3D](https://docs.panda3d.org/1.10/python/programming/scene-graph/instancing)) 98([Panda3D](https://docs.panda3d.org/1.10/python/programming/scene-graph/instancing))
@@ -87,10 +101,10 @@ to have multiple parents. This turns the scene graph into a DAG and adds a
87number of complications for us: 101number of complications for us:
88 102
891. Shared ownership of children. We would now need some sort of ref counting or 1031. Shared ownership of children. We would now need some sort of ref counting or
90deferred GC to delete nodes and their subtrees. 104 deferred GC to delete nodes and their subtrees.
912. Nodes no longer have a unique parent. 1052. Nodes no longer have a unique parent.
923. Given a node, we can no longer determine its location (which parent link do 1063. 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). 107 you follow?), or any attribute that is derived from its parent(s).
94 108
95In our case, we stick to strict tree hierarchies. 109In our case, we stick to strict tree hierarchies.
96 110
@@ -131,6 +145,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 145of the library (Gfx, render backend, scene or renderer). This includes functions
132to compute irradiance maps, create procedural geometry, etc. 146to compute irradiance maps, create procedural geometry, etc.
133 147
148### Memory management
149
150TODO
151
134## Ideas for Future Work 152## Ideas for Future Work
135 153
136- Render graphs to allow for custom multi-pass rendering algorithms. 154- Render graphs to allow for custom multi-pass rendering algorithms.
diff --git a/app/CMakeLists.txt b/app/CMakeLists.txt
index 7703ccb..588ee87 100644
--- a/app/CMakeLists.txt
+++ b/app/CMakeLists.txt
@@ -1,4 +1,4 @@
1cmake_minimum_required(VERSION 3.0) 1cmake_minimum_required(VERSION 3.5)
2 2
3add_subdirectory(contrib/glad) 3add_subdirectory(contrib/glad)
4 4
diff --git a/app/demo/CMakeLists.txt b/app/demo/CMakeLists.txt
index bbb3aae..58d77cc 100644
--- a/app/demo/CMakeLists.txt
+++ b/app/demo/CMakeLists.txt
@@ -1,4 +1,4 @@
1cmake_minimum_required(VERSION 3.0) 1cmake_minimum_required(VERSION 3.5)
2 2
3project(gfxappdemo) 3project(gfxappdemo)
4 4
diff --git a/app/include/gfx/app.h b/app/include/gfx/app.h
index 77b6ad2..3017707 100644
--- a/app/include/gfx/app.h
+++ b/app/include/gfx/app.h
@@ -1,7 +1,6 @@
1#pragma once 1#pragma once
2 2
3#include <stdbool.h> 3typedef struct GfxApp GfxApp;
4
5typedef struct GfxAppState GfxAppState; 4typedef struct GfxAppState GfxAppState;
6 5
7/// Application settings. 6/// Application settings.
@@ -16,11 +15,11 @@ typedef struct GfxAppDesc {
16 GfxAppState* app_state; 15 GfxAppState* app_state;
17} GfxAppDesc; 16} GfxAppDesc;
18 17
19typedef bool (*GfxAppInit)(GfxAppState*, int argc, const char** argv); 18typedef bool (*GfxAppInit)(GfxApp*, GfxAppState*, int argc, const char** argv);
20typedef void (*GfxAppShutdown)(GfxAppState*); 19typedef void (*GfxAppShutdown)(GfxApp*, GfxAppState*);
21typedef void (*GfxAppUpdate)(GfxAppState*, double t, double dt); 20typedef void (*GfxAppUpdate)(GfxApp*, GfxAppState*, double t, double dt);
22typedef void (*GfxAppRender)(GfxAppState*); 21typedef void (*GfxAppRender)(const GfxApp*, GfxAppState*);
23typedef void (*GfxAppResize)(GfxAppState*, int width, int height); 22typedef void (*GfxAppResize)(GfxApp*, GfxAppState*, int width, int height);
24 23
25/// Application callback functions. 24/// Application callback functions.
26typedef struct GfxAppCallbacks { 25typedef struct GfxAppCallbacks {
@@ -64,6 +63,7 @@ typedef enum Key {
64 KeyX, 63 KeyX,
65 KeyY, 64 KeyY,
66 KeyZ, 65 KeyZ,
66 KeyLShift,
67} Key; 67} Key;
68 68
69#ifdef __cplusplus 69#ifdef __cplusplus
@@ -74,13 +74,13 @@ extern "C" {
74bool gfx_app_run(const GfxAppDesc*, const GfxAppCallbacks*); 74bool gfx_app_run(const GfxAppDesc*, const GfxAppCallbacks*);
75 75
76/// Get the mouse coordinates relative to the app's window. 76/// Get the mouse coordinates relative to the app's window.
77void gfx_app_get_mouse_position(double* x, double* y); 77void gfx_app_get_mouse_position(GfxApp*, double* x, double* y);
78 78
79/// Return if the given mouse button is pressed. 79/// Return if the given mouse button is pressed.
80bool gfx_app_is_mouse_button_pressed(MouseButton); 80bool gfx_app_is_mouse_button_pressed(GfxApp*, MouseButton);
81 81
82/// Return true if the given key is pressed. 82/// Return true if the given key is pressed.
83bool gfx_app_is_key_pressed(Key); 83bool gfx_app_is_key_pressed(GfxApp*, Key);
84 84
85#ifdef __cplusplus 85#ifdef __cplusplus
86} // extern "C" 86} // extern "C"
@@ -102,11 +102,10 @@ bool gfx_app_is_key_pressed(Key);
102 .title = TITLE, \ 102 .title = TITLE, \
103 .app_state = &app_state, \ 103 .app_state = &app_state, \
104 }, \ 104 }, \
105 &(GfxAppCallbacks){ \ 105 &(GfxAppCallbacks){.init = Init, \
106 .init = (GfxAppInit)Init, \ 106 .shutdown = Shutdown, \
107 .shutdown = (GfxAppShutdown)Shutdown, \ 107 .update = Update, \
108 .update = (GfxAppUpdate)Update, \ 108 .render = Render, \
109 .render = (GfxAppRender)Render, \ 109 .resize = Resize}); \
110 .resize = (GfxAppResize)Resize}); \
111 return 0; \ 110 return 0; \
112 } 111 }
diff --git a/app/src/app.c b/app/src/app.c
index 9b816ee..6eaa3d6 100644
--- a/app/src/app.c
+++ b/app/src/app.c
@@ -24,7 +24,7 @@ static GfxApp g_gfx_app;
24 24
25/// Called by GLFW when the window is resized. 25/// Called by GLFW when the window is resized.
26static void on_resize(GLFWwindow* window, int width, int height) { 26static void on_resize(GLFWwindow* window, int width, int height) {
27 (*g_gfx_app.callbacks.resize)(g_gfx_app.app_state, width, height); 27 (*g_gfx_app.callbacks.resize)(&g_gfx_app, g_gfx_app.app_state, width, height);
28} 28}
29 29
30/// Run the application's main loop. 30/// Run the application's main loop.
@@ -41,12 +41,13 @@ static void loop(GfxApp* app) {
41 41
42 // Warm up the update to initialize the application's state. 42 // Warm up the update to initialize the application's state.
43 (*app->callbacks.update)( 43 (*app->callbacks.update)(
44 app->app_state, time_delta_to_sec(time), time_delta_to_sec(update_dt)); 44 app, app->app_state, time_delta_to_sec(time),
45 time_delta_to_sec(update_dt));
45 46
46 // Warm up the rendering before entering the main loop. A renderer can 47 // Warm up the rendering before entering the main loop. A renderer can
47 // compile shaders and do other initialization the first time it renders a 48 // compile shaders and do other initialization the first time it renders a
48 // scene. 49 // scene.
49 (*app->callbacks.render)(app->app_state); 50 (*app->callbacks.render)(app, app->app_state);
50 glfwSwapBuffers(app->window); 51 glfwSwapBuffers(app->window);
51 52
52 timer_start(&timer); 53 timer_start(&timer);
@@ -56,14 +57,14 @@ static void loop(GfxApp* app) {
56 57
57 while (time_budget >= update_dt) { 58 while (time_budget >= update_dt) {
58 (*app->callbacks.update)( 59 (*app->callbacks.update)(
59 app->app_state, time_delta_to_sec(time), 60 app, app->app_state, time_delta_to_sec(time),
60 time_delta_to_sec(update_dt)); 61 time_delta_to_sec(update_dt));
61 62
62 time += update_dt; 63 time += update_dt;
63 time_budget -= update_dt; 64 time_budget -= update_dt;
64 } 65 }
65 66
66 (*app->callbacks.render)(app->app_state); 67 (*app->callbacks.render)(app, app->app_state);
67 glfwSwapBuffers(app->window); 68 glfwSwapBuffers(app->window);
68 glfwPollEvents(); 69 glfwPollEvents();
69 70
@@ -85,7 +86,7 @@ bool gfx_app_run(const GfxAppDesc* desc, const GfxAppCallbacks* callbacks) {
85 g_gfx_app.callbacks = *callbacks; 86 g_gfx_app.callbacks = *callbacks;
86 g_gfx_app.max_fps = desc->max_fps; 87 g_gfx_app.max_fps = desc->max_fps;
87 g_gfx_app.update_delta_time = desc->update_delta_time; 88 g_gfx_app.update_delta_time = desc->update_delta_time;
88 g_gfx_app.window = 0; 89 g_gfx_app.window = nullptr;
89 90
90 if (!glfwInit()) { 91 if (!glfwInit()) {
91 LOGE("glfwInit() failed"); 92 LOGE("glfwInit() failed");
@@ -112,28 +113,33 @@ bool gfx_app_run(const GfxAppDesc* desc, const GfxAppCallbacks* callbacks) {
112 } 113 }
113 glfwMakeContextCurrent(g_gfx_app.window); 114 glfwMakeContextCurrent(g_gfx_app.window);
114 115
115 // Load GL before calling the application init clalback. 116 // Request adaptive sync if supported.
116 if (!gladLoadGL()) { 117 glfwSwapInterval(-1);
117 LOGE("Failed loading glad!"); 118
118 return 0; 119 // Load GL before calling the application init callback.
120 // Use the GLFW loader. See: https://github.com/apitrace/apitrace/issues/954
121 if (!gladLoadGLLoader((GLADloadproc)glfwGetProcAddress)) {
122 LOGE("Failed loading glad");
123 goto cleanup;
119 } 124 }
120 125
121 // Initialize the application's state before setting any callbacks. 126 // Initialize the application's state before setting any callbacks.
122 if (!(*g_gfx_app.callbacks.init)( 127 if (!(*g_gfx_app.callbacks.init)(
123 g_gfx_app.app_state, desc->argc, desc->argv)) { 128 &g_gfx_app, g_gfx_app.app_state, desc->argc, desc->argv)) {
124 LOGE("Failed to initialize application"); 129 LOGE("Failed to initialize application");
125 goto cleanup; 130 goto cleanup;
126 } 131 }
127 132
128 // Trigger an initial resize for convenience. 133 // Trigger an initial resize for convenience.
129 (*g_gfx_app.callbacks.resize)(g_gfx_app.app_state, desc->width, desc->height); 134 (*g_gfx_app.callbacks.resize)(
135 &g_gfx_app, g_gfx_app.app_state, desc->width, desc->height);
130 136
131 // Set GLFW callbacks now that the application has been initialized. 137 // Set GLFW callbacks now that the application has been initialized.
132 glfwSetWindowSizeCallback(g_gfx_app.window, on_resize); 138 glfwSetWindowSizeCallback(g_gfx_app.window, on_resize);
133 139
134 loop(&g_gfx_app); 140 loop(&g_gfx_app);
135 141
136 (*g_gfx_app.callbacks.shutdown)(g_gfx_app.app_state); 142 (*g_gfx_app.callbacks.shutdown)(&g_gfx_app, g_gfx_app.app_state);
137 143
138 success = true; 144 success = true;
139 145
@@ -145,21 +151,21 @@ cleanup:
145 return success; 151 return success;
146} 152}
147 153
148void gfx_app_get_mouse_position(double* x, double* y) { 154void gfx_app_get_mouse_position(GfxApp* app, double* x, double* y) {
149 glfwGetCursorPos(g_gfx_app.window, x, y); 155 glfwGetCursorPos(app->window, x, y);
150} 156}
151 157
152static int to_glfw_mouse_button(MouseButton button); 158static int to_glfw_mouse_button(MouseButton button);
153 159
154bool gfx_app_is_mouse_button_pressed(MouseButton button) { 160bool gfx_app_is_mouse_button_pressed(GfxApp* app, MouseButton button) {
155 return glfwGetMouseButton(g_gfx_app.window, to_glfw_mouse_button(button)) == 161 return glfwGetMouseButton(app->window, to_glfw_mouse_button(button)) ==
156 GLFW_PRESS; 162 GLFW_PRESS;
157} 163}
158 164
159static int to_glfw_key(Key key); 165static int to_glfw_key(Key key);
160 166
161bool gfx_app_is_key_pressed(Key key) { 167bool gfx_app_is_key_pressed(GfxApp* app, Key key) {
162 return glfwGetKey(g_gfx_app.window, to_glfw_key(key)) == GLFW_PRESS; 168 return glfwGetKey(app->window, to_glfw_key(key)) == GLFW_PRESS;
163} 169}
164 170
165static int to_glfw_mouse_button(MouseButton button) { 171static int to_glfw_mouse_button(MouseButton button) {
@@ -227,5 +233,7 @@ static int to_glfw_key(Key key) {
227 return GLFW_KEY_Y; 233 return GLFW_KEY_Y;
228 case KeyZ: 234 case KeyZ:
229 return GLFW_KEY_Z; 235 return GLFW_KEY_Z;
236 case KeyLShift:
237 return GLFW_KEY_LEFT_SHIFT;
230 } 238 }
231} 239}
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..3f86fc2 100644
--- a/gfx/contrib/cgltf-tangents/CMakeLists.txt
+++ b/contrib/cgltf-tangents/CMakeLists.txt
@@ -1,4 +1,4 @@
1cmake_minimum_required(VERSION 3.0) 1cmake_minimum_required(VERSION 3.5)
2 2
3project(cgltf-tangents) 3project(cgltf-tangents)
4 4
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/include/gfx/scene.h b/gfx/include/gfx/scene.h
deleted file mode 100644
index abcaa70..0000000
--- a/gfx/include/gfx/scene.h
+++ /dev/null
@@ -1,11 +0,0 @@
1#pragma once
2
3#include <gfx/scene/animation.h>
4#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/node.h>
10#include <gfx/scene/object.h>
11#include <gfx/scene/scene.h>
diff --git a/gfx/include/gfx/scene/camera.h b/gfx/include/gfx/scene/camera.h
deleted file mode 100644
index 99d83fe..0000000
--- a/gfx/include/gfx/scene/camera.h
+++ /dev/null
@@ -1,22 +0,0 @@
1#pragma once
2
3#include <math/fwd.h>
4
5typedef struct SceneNode SceneNode;
6
7typedef struct SceneCamera SceneCamera;
8
9/// Create a new camera.
10SceneCamera* gfx_make_camera();
11
12/// Destroy the camera.
13///
14/// The camera is conveniently removed from the scene graph and its parent scene
15/// node is destroyed.
16void gfx_destroy_camera(SceneCamera**);
17
18/// Set the scene camera's math camera.
19void gfx_set_camera_camera(SceneCamera* scene_camera, Camera* camera);
20
21/// Get the scene camera's math camera.
22Camera* gfx_get_camera_camera(SceneCamera*);
diff --git a/gfx/include/gfx/scene/light.h b/gfx/include/gfx/scene/light.h
deleted file mode 100644
index 132e344..0000000
--- a/gfx/include/gfx/scene/light.h
+++ /dev/null
@@ -1,30 +0,0 @@
1#pragma once
2
3typedef struct Texture Texture;
4
5typedef struct Light Light;
6
7/// Light type.
8typedef enum LightType { EnvironmentLightType } LightType;
9
10/// Describes an environment light.
11typedef struct EnvironmentLightDesc {
12 const Texture* environment_map;
13} EnvironmentLightDesc;
14
15/// Describes a light.
16typedef struct LightDesc {
17 LightType type;
18 union {
19 EnvironmentLightDesc environment;
20 } light;
21} LightDesc;
22
23/// Create a light.
24Light* gfx_make_light(const LightDesc*);
25
26/// Destroy the light.
27///
28/// The light is conveniently removed from the scene graph and its parent scene
29/// node is destroyed.
30void gfx_destroy_light(Light**);
diff --git a/gfx/include/gfx/scene/material.h b/gfx/include/gfx/scene/material.h
deleted file mode 100644
index bca664e..0000000
--- a/gfx/include/gfx/scene/material.h
+++ /dev/null
@@ -1,25 +0,0 @@
1#pragma once
2
3#include <gfx/core.h>
4#include <gfx/sizes.h>
5
6typedef struct Material Material;
7
8/// Describes a material.
9///
10/// A material holds a shader program and a set of shader-specific uniform
11/// variables. Two materials can share the same shader, but shader parameters
12/// generally give two materials a different appearance.
13typedef struct MaterialDesc {
14 ShaderUniform uniforms[GFX_MAX_UNIFORMS_PER_MATERIAL];
15 int num_uniforms;
16} MaterialDesc;
17
18/// Create a material.
19Material* gfx_make_material(const MaterialDesc*);
20
21/// Destroy the material.
22///
23/// The caller must make sure that no Mesh points to the given Material.
24/// For a safe purge of unused resources, see scene_purge().
25void gfx_destroy_material(Material**);
diff --git a/gfx/include/gfx/scene/mesh.h b/gfx/include/gfx/scene/mesh.h
deleted file mode 100644
index 0d3b4d4..0000000
--- a/gfx/include/gfx/scene/mesh.h
+++ /dev/null
@@ -1,23 +0,0 @@
1#pragma once
2
3typedef struct Geometry Geometry;
4typedef struct Material Material;
5typedef struct ShaderProgram ShaderProgram;
6
7typedef struct Mesh Mesh;
8
9/// Describes a mesh.
10typedef struct MeshDesc {
11 const Geometry* geometry;
12 const Material* material;
13 ShaderProgram* shader;
14} MeshDesc;
15
16/// Create a mesh.
17Mesh* gfx_make_mesh(const MeshDesc*);
18
19/// Destroy the mesh.
20///
21/// The caller must make sure that no SceneObject points to the given Mesh.
22/// For a safe purge of unused resources, see scene_purge().
23void gfx_destroy_mesh(Mesh**);
diff --git a/gfx/include/gfx/scene/model.h b/gfx/include/gfx/scene/model.h
deleted file mode 100644
index 42f85d4..0000000
--- a/gfx/include/gfx/scene/model.h
+++ /dev/null
@@ -1,12 +0,0 @@
1#pragma once
2
3typedef struct Anima Anima;
4typedef struct Model Model;
5typedef struct SceneNode SceneNode;
6
7/// Return the model's anima, or null if the model is not animated.
8Anima* gfx_get_model_anima(Model*);
9
10/// Return the model's root node.
11const SceneNode* gfx_get_model_root(const Model*);
12SceneNode* gfx_get_model_root_mut(Model*);
diff --git a/gfx/include/gfx/scene/node.h b/gfx/include/gfx/scene/node.h
deleted file mode 100644
index a2c2836..0000000
--- a/gfx/include/gfx/scene/node.h
+++ /dev/null
@@ -1,156 +0,0 @@
1#pragma once
2
3#include "animation.h"
4
5#include <math/fwd.h>
6#include <math/mat4.h>
7
8#include <stdint.h>
9
10typedef struct Anima Anima;
11typedef struct Light Light;
12typedef struct Model Model;
13typedef struct SceneCamera SceneCamera;
14typedef struct SceneObject SceneObject;
15
16/// Scene node type.
17typedef enum NodeType {
18 LogicalNode,
19 AnimaNode,
20 CameraNode,
21 LightNode,
22 ModelNode,
23 ObjectNode,
24} NodeType;
25
26/// A node in the scene graph.
27///
28/// Scene nodes take ownership of the object they are associated with (Camera,
29/// Light, SceneObject, etc), as well as of child nodes.
30typedef struct SceneNode SceneNode;
31
32// -----------------------------------------------------------------------------
33// Constructors and destructor.
34// -----------------------------------------------------------------------------
35
36/// Create a new scene node.
37///
38/// This node does not contain any camera, light, object, etc. and exists simply
39/// as a logical and spatial construct.
40SceneNode* gfx_make_node();
41
42/// Create an anima node.
43SceneNode* gfx_make_anima_node(Anima*);
44
45/// Create a new camera node.
46SceneNode* gfx_make_camera_node(SceneCamera*);
47
48/// Create a new light node.
49SceneNode* gfx_make_light_node(Light*);
50
51/// Create a new model node.
52SceneNode* gfx_make_model_node(Model*);
53
54/// Create a new object node.
55SceneNode* gfx_make_object_node(SceneObject*);
56
57/// Make the node an anima node.
58void gfx_construct_anima_node(SceneNode*, Anima*);
59
60/// Make the node a camera node.
61void gfx_construct_camera_node(SceneNode*, SceneCamera*);
62
63/// Make the node a light node.
64void gfx_construct_light_node(SceneNode*, Light*);
65
66/// Make the node a model node.
67void gfx_construct_model_node(SceneNode*, Model*);
68
69/// Make the node an object node.
70void gfx_construct_object_node(SceneNode*, SceneObject*);
71
72/// Recursively destroy the scene node and its children.
73///
74/// The scene node and its children are removed from the scene graph.
75///
76/// Node resources -- cameras, lights, objects, etc. -- are also destroyed.
77void gfx_destroy_node(SceneNode**);
78
79// -----------------------------------------------------------------------------
80// Getters.
81// -----------------------------------------------------------------------------
82
83/// Get the node's type.
84NodeType gfx_get_node_type(const SceneNode*);
85
86/// Get the node's anima.
87///
88/// The node must be of type AnimaNode.
89const Anima* gfx_get_node_anima(const SceneNode*);
90Anima* gfx_get_node_anima_mut(SceneNode*);
91
92/// Get the node's camera.
93///
94/// The node must be of type CameraNode.
95const SceneCamera* gfx_get_node_camera(const SceneNode* node);
96SceneCamera* gfx_get_node_camera_mut(SceneNode* node);
97
98/// Get the node's light.
99///
100/// The node must be of type LightNode.
101const Light* gfx_get_node_light(const SceneNode*);
102Light* gfx_get_node_light_mut(SceneNode*);
103
104/// Get the node's model.
105///
106/// The node must be of type ModelNode.
107const Model* gfx_get_node_model(const SceneNode*);
108Model* gfx_get_node_model_mut(SceneNode*);
109
110/// Get the node's scene object.
111///
112/// The node must be of type ObjectNode.
113const SceneObject* gfx_get_node_object(const SceneNode*);
114SceneObject* gfx_get_node_object_mut(SceneNode*);
115
116/// Get the node's parent.
117const SceneNode* gfx_get_node_parent(const SceneNode*);
118SceneNode* gfx_get_node_parent_mut(SceneNode*);
119
120/// Get the node's first child.
121const SceneNode* gfx_get_node_child(const SceneNode*);
122SceneNode* gfx_get_node_child_mut(SceneNode*);
123
124/// Get the node's immediate sibling.
125const SceneNode* gfx_get_node_sibling(const SceneNode*);
126SceneNode* gfx_get_node_sibling_mut(SceneNode*);
127
128/// Get the node's (local) transform.
129mat4 gfx_get_node_transform(const SceneNode*);
130
131/// Get the node's global transform.
132mat4 gfx_get_node_global_transform(const SceneNode*);
133
134// -----------------------------------------------------------------------------
135// Setters.
136// -----------------------------------------------------------------------------
137
138/// Set the node's parent.
139///
140/// Pass in null to unwire from the existing parent, if one exists.
141void gfx_set_node_parent(SceneNode*, SceneNode* parent_node);
142
143/// Set the node's (local) transform.
144void gfx_set_node_transform(SceneNode*, const mat4* transform);
145
146/// Set the node's position.
147void gfx_set_node_position(SceneNode*, const vec3* position);
148
149/// Set the node's rotation.
150void gfx_set_node_rotation(SceneNode*, const quat* rotation);
151
152/// Set the node's rotation.
153void gfx_set_node_rotation_mat(SceneNode*, const mat4* rotation);
154
155/// Log the node's hierarchy.
156void gfx_log_node_hierarchy(const SceneNode*);
diff --git a/gfx/include/gfx/scene/object.h b/gfx/include/gfx/scene/object.h
deleted file mode 100644
index 7579d29..0000000
--- a/gfx/include/gfx/scene/object.h
+++ /dev/null
@@ -1,39 +0,0 @@
1#pragma once
2
3#include <gfx/sizes.h>
4
5#include <math/fwd.h>
6
7#include <math/aabb3.h>
8
9typedef struct Mesh Mesh;
10typedef struct SceneNode SceneNode;
11typedef struct Skeleton Skeleton;
12
13typedef struct SceneObject SceneObject;
14
15typedef struct ObjectDesc {
16 size_t num_meshes;
17 Mesh* meshes[GFX_MAX_NUM_MESHES];
18} ObjectDesc;
19
20/// Create a new object.
21SceneObject* gfx_make_object(const ObjectDesc*);
22
23/// Destroy the object.
24///
25/// The object is conveniently removed from the scene graph and its parent scene
26/// node is destroyed.
27void gfx_destroy_object(SceneObject**);
28
29/// Set the object's skeleton.
30void gfx_set_object_skeleton(SceneObject*, const Skeleton*);
31
32/// Get the object's skeleton.
33/// Return null if the object has no skeleton.
34const Skeleton* gfx_get_object_skeleton(const SceneObject*);
35
36/// Gets the object's bounding box.
37///
38/// The object's bounding box is the bounding box of its mesh geometries.
39aabb3 gfx_get_object_aabb(const SceneObject*);
diff --git a/gfx/include/gfx/scene/scene.h b/gfx/include/gfx/scene/scene.h
deleted file mode 100644
index 0d96210..0000000
--- a/gfx/include/gfx/scene/scene.h
+++ /dev/null
@@ -1,21 +0,0 @@
1#pragma once
2
3#include <math/defs.h>
4#include <math/fwd.h>
5
6typedef struct SceneNode SceneNode;
7
8typedef struct Scene Scene;
9
10/// Create a new scene.
11Scene* gfx_make_scene(void);
12
13/// Destroy the scene.
14///
15/// This function destroys the scene and all objects that it owns (scene
16/// objects, cameras, lights, etc), but not objects that could be shared with
17/// other scenes (meshes, materials, etc).
18void gfx_destroy_scene(Scene**);
19
20/// Get the scene's root node.
21SceneNode* gfx_get_scene_root(Scene*);
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/camera.c b/gfx/src/scene/camera.c
deleted file mode 100644
index be7d806..0000000
--- a/gfx/src/scene/camera.c
+++ /dev/null
@@ -1,37 +0,0 @@
1#include "camera_impl.h"
2
3#include "node_impl.h"
4#include "scene_memory.h"
5
6#include <assert.h>
7
8SceneCamera* gfx_make_camera() {
9 SceneCamera* camera = mem_alloc_camera();
10
11 camera->camera = camera_perspective(
12 /*fovy=*/90.0 * TO_RAD, /*aspect=*/16.0 / 9.0,
13 /*near=*/0.1, /*far=*/1000);
14
15 return camera;
16}
17
18void gfx_destroy_camera(SceneCamera** camera) {
19 assert(camera);
20 if (*camera) {
21 if ((*camera)->parent.val) {
22 gfx_del_node((*camera)->parent);
23 }
24 mem_free_camera(camera);
25 }
26}
27
28void gfx_set_camera_camera(SceneCamera* scene_camera, Camera* camera) {
29 assert(scene_camera);
30 assert(camera);
31 scene_camera->camera = *camera;
32}
33
34Camera* gfx_get_camera_camera(SceneCamera* camera) {
35 assert(camera);
36 return &camera->camera;
37}
diff --git a/gfx/src/scene/camera_impl.h b/gfx/src/scene/camera_impl.h
deleted file mode 100644
index 20c3890..0000000
--- a/gfx/src/scene/camera_impl.h
+++ /dev/null
@@ -1,12 +0,0 @@
1#pragma once
2
3#include <gfx/scene/camera.h>
4
5#include "types.h"
6
7#include <math/camera.h>
8
9typedef struct SceneCamera {
10 Camera camera;
11 node_idx parent; // Parent SceneNode.
12} SceneCamera;
diff --git a/gfx/src/scene/material.c b/gfx/src/scene/material.c
deleted file mode 100644
index 3248243..0000000
--- a/gfx/src/scene/material.c
+++ /dev/null
@@ -1,57 +0,0 @@
1#include "material_impl.h"
2
3#include "scene_memory.h"
4
5#include <gfx/core.h>
6
7static void material_make(Material* material, const MaterialDesc* desc) {
8 assert(material);
9 assert(desc);
10 assert(desc->num_uniforms < GFX_MAX_UNIFORMS_PER_MATERIAL);
11 material->num_uniforms = desc->num_uniforms;
12 for (int i = 0; i < desc->num_uniforms; ++i) {
13 material->uniforms[i] = desc->uniforms[i];
14 }
15}
16
17Material* gfx_make_material(const MaterialDesc* desc) {
18 assert(desc);
19 Material* material = mem_alloc_material();
20 material_make(material, desc);
21 return material;
22}
23
24void gfx_destroy_material(Material** material) { mem_free_material(material); }
25
26static void set_uniform(ShaderProgram* prog, const ShaderUniform* uniform) {
27 switch (uniform->type) {
28 case UniformTexture:
29 gfx_set_texture_uniform(prog, uniform->name.str, uniform->value.texture);
30 break;
31 case UniformMat4:
32 gfx_set_mat4_uniform(prog, uniform->name.str, &uniform->value.mat4);
33 break;
34 case UniformVec3:
35 gfx_set_vec3_uniform(prog, uniform->name.str, uniform->value.vec3);
36 break;
37 case UniformVec4:
38 gfx_set_vec4_uniform(prog, uniform->name.str, uniform->value.vec4);
39 break;
40 case UniformFloat:
41 gfx_set_float_uniform(prog, uniform->name.str, uniform->value.scalar);
42 break;
43 case UniformMat4Array:
44 gfx_set_mat4_array_uniform(
45 prog, uniform->name.str, uniform->value.array.values,
46 uniform->value.array.count);
47 break;
48 }
49}
50
51void material_activate(ShaderProgram* shader, const Material* material) {
52 assert(material);
53 for (int i = 0; i < material->num_uniforms; ++i) {
54 const ShaderUniform* uniform = &material->uniforms[i];
55 set_uniform(shader, uniform);
56 }
57}
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/src/scene/mesh_impl.h b/gfx/src/scene/mesh_impl.h
deleted file mode 100644
index 560b77e..0000000
--- a/gfx/src/scene/mesh_impl.h
+++ /dev/null
@@ -1,12 +0,0 @@
1#pragma once
2
3#include <gfx/scene/mesh.h>
4
5typedef struct Mesh {
6 const Geometry* geometry;
7 const Material* material;
8 ShaderProgram* shader;
9} Mesh;
10
11// TODO: a mesh_render() that takes a transform, applies the material and the
12// transform, and then renders the geometry.
diff --git a/gfx/src/scene/scene.c b/gfx/src/scene/scene.c
deleted file mode 100644
index 54452dd..0000000
--- a/gfx/src/scene/scene.c
+++ /dev/null
@@ -1,25 +0,0 @@
1#include "scene_impl.h"
2
3#include "node_impl.h"
4#include "scene_memory.h"
5
6#include <assert.h>
7
8Scene* gfx_make_scene(void) {
9 Scene* scene = mem_alloc_scene();
10 scene->root = gfx_make_node();
11 return scene;
12}
13
14void gfx_destroy_scene(Scene** scene) {
15 assert(scene);
16 if (*scene) {
17 gfx_destroy_node(&(*scene)->root);
18 mem_free_scene(scene);
19 }
20}
21
22SceneNode* gfx_get_scene_root(Scene* scene) {
23 assert(scene);
24 return scene->root;
25}
diff --git a/gfx/src/scene/scene_impl.h b/gfx/src/scene/scene_impl.h
deleted file mode 100644
index 992f620..0000000
--- a/gfx/src/scene/scene_impl.h
+++ /dev/null
@@ -1,13 +0,0 @@
1#pragma once
2
3#include <gfx/scene/scene.h>
4
5#include "types.h"
6
7typedef struct SceneNode SceneNode;
8
9typedef struct Scene {
10 SceneNode* root;
11 scene_idx next;
12 scene_idx prev;
13} Scene;
diff --git a/gfx/include/gfx/scene/animation.h b/include/gfx/animation.h
index d95b895..b55e575 100644
--- a/gfx/include/gfx/scene/animation.h
+++ b/include/gfx/animation.h
@@ -1,7 +1,5 @@
1#pragma once 1#pragma once
2 2
3#include "node.h"
4#include "object.h"
5#include <gfx/sizes.h> 3#include <gfx/sizes.h>
6 4
7#include <cstring.h> 5#include <cstring.h>
@@ -15,8 +13,7 @@
15#include <stddef.h> 13#include <stddef.h>
16#include <stdint.h> 14#include <stdint.h>
17 15
18typedef struct Buffer Buffer; 16typedef struct Buffer Buffer;
19typedef struct SceneNode SceneNode;
20 17
21typedef struct Anima Anima; 18typedef struct Anima Anima;
22typedef struct Joint Joint; 19typedef struct Joint Joint;
diff --git a/gfx/include/gfx/asset.h b/include/gfx/asset.h
index caf40c1..3018cfd 100644
--- a/gfx/include/gfx/asset.h
+++ b/include/gfx/asset.h
@@ -18,8 +18,8 @@ typedef enum AssetOrigin {
18 18
19/// Describes a texture's colour space. 19/// Describes a texture's colour space.
20typedef enum TextureColourSpace { 20typedef enum TextureColourSpace {
21 sRGB, // The most likely default. 21 LinearColourSpace, // The most likely default.
22 LinearColourSpace, 22 sRGB,
23} TextureColourSpace; 23} TextureColourSpace;
24 24
25/// Describes a command to load a texture. 25/// Describes a command to load a texture.
diff --git a/gfx/include/gfx/core.h b/include/gfx/core.h
index 44509c9..8f01081 100644
--- a/gfx/include/gfx/core.h
+++ b/include/gfx/core.h
@@ -4,7 +4,7 @@
4/// rendering operations. 4/// rendering operations.
5#pragma once 5#pragma once
6 6
7#include "sizes.h" 7#include <gfx/sizes.h>
8 8
9#include <math/aabb3.h> 9#include <math/aabb3.h>
10#include <math/fwd.h> 10#include <math/fwd.h>
@@ -93,6 +93,7 @@ typedef struct BufferDesc {
93 size_t offset_bytes; \ 93 size_t offset_bytes; \
94 size_t size_bytes; \ 94 size_t size_bytes; \
95 size_t stride_bytes; \ 95 size_t stride_bytes; \
96 size_t count; \
96 } NAME; 97 } NAME;
97 98
98/// A buffer view for untyped data. 99/// A buffer view for untyped data.
@@ -183,11 +184,12 @@ typedef struct ShaderProgramDesc {
183 184
184/// Shader uniform type. 185/// Shader uniform type.
185typedef enum { 186typedef enum {
187 UniformInt,
186 UniformFloat, 188 UniformFloat,
187 UniformMat4, 189 UniformMat4,
188 UniformTexture,
189 UniformVec3, 190 UniformVec3,
190 UniformVec4, 191 UniformVec4,
192 UniformTexture,
191 UniformMat4Array 193 UniformMat4Array
192} UniformType; 194} UniformType;
193 195
@@ -200,10 +202,11 @@ typedef struct ShaderUniform {
200 UniformType type; 202 UniformType type;
201 union { 203 union {
202 const Texture* texture; 204 const Texture* texture;
203 mat4 mat4; 205 int uniform_int;
204 vec3 vec3; 206 float uniform_float;
205 vec4 vec4; 207 mat4 uniform_mat4;
206 float scalar; 208 vec3 uniform_vec3;
209 vec4 uniform_vec4;
207 struct { 210 struct {
208 size_t count; 211 size_t count;
209 union { 212 union {
@@ -219,6 +222,7 @@ typedef enum { Texture2D, TextureCubeMap } TextureDimension;
219/// Texture data format. 222/// Texture data format.
220typedef enum { 223typedef enum {
221 TextureDepth, 224 TextureDepth,
225 TextureR8,
222 TextureRG16, 226 TextureRG16,
223 TextureRG16F, 227 TextureRG16F,
224 TextureRGB8, 228 TextureRGB8,
@@ -473,11 +477,21 @@ void gfx_deactivate_shader_program(const ShaderProgram*);
473/// 477///
474/// This function should be called after setting all of the uniform variables 478/// This function should be called after setting all of the uniform variables
475/// and prior to issuing a draw call. 479/// and prior to issuing a draw call.
480///
481/// The given program must have been activated prior to this call with
482/// gfx_activate_shader_program().
476void gfx_apply_uniforms(const ShaderProgram*); 483void gfx_apply_uniforms(const ShaderProgram*);
477 484
478/// Set the texture uniform. 485/// Set the uniform.
486void gfx_set_uniform(ShaderProgram* prog, const ShaderUniform* uniform);
487
488/// Set the int uniform.
479/// Has no effect if the shader does not contain the given uniform. 489/// Has no effect if the shader does not contain the given uniform.
480void gfx_set_texture_uniform(ShaderProgram*, const char* name, const Texture*); 490void gfx_set_int_uniform(ShaderProgram*, const char* name, int value);
491
492/// Set the float uniform.
493/// Has no effect if the shader does not contain the given uniform.
494void gfx_set_float_uniform(ShaderProgram*, const char* name, float value);
481 495
482/// Set the matrix uniform. 496/// Set the matrix uniform.
483/// Has no effect if the shader does not contain the given uniform. 497/// Has no effect if the shader does not contain the given uniform.
@@ -491,9 +505,9 @@ void gfx_set_vec3_uniform(ShaderProgram*, const char* name, vec3);
491/// Has no effect if the shader does not contain the given uniform. 505/// Has no effect if the shader does not contain the given uniform.
492void gfx_set_vec4_uniform(ShaderProgram*, const char* name, vec4); 506void gfx_set_vec4_uniform(ShaderProgram*, const char* name, vec4);
493 507
494/// Set the float uniform. 508/// Set the texture uniform.
495/// Has no effect if the shader does not contain the given uniform. 509/// Has no effect if the shader does not contain the given uniform.
496void gfx_set_float_uniform(ShaderProgram*, const char* name, float value); 510void gfx_set_texture_uniform(ShaderProgram*, const char* name, const Texture*);
497 511
498/// Set the matrix array uniform. 512/// Set the matrix array uniform.
499/// Has no effect if the shader does not contain the given uniform. 513/// Has no effect if the shader does not contain the given uniform.
diff --git a/gfx/include/gfx/gfx.h b/include/gfx/gfx.h
index 7c670a5..eb7a3cd 100644
--- a/gfx/include/gfx/gfx.h
+++ b/include/gfx/gfx.h
@@ -2,7 +2,8 @@
2 2
3typedef struct AssetCache AssetCache; 3typedef struct AssetCache AssetCache;
4typedef struct GfxCore GfxCore; 4typedef struct GfxCore GfxCore;
5typedef struct ImmRenderer ImmRenderer; 5typedef struct Imm Imm;
6typedef struct LLR LLR;
6typedef struct Renderer Renderer; 7typedef struct Renderer Renderer;
7 8
8typedef struct Gfx Gfx; 9typedef struct Gfx Gfx;
@@ -16,11 +17,14 @@ 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*); 24Imm* gfx_get_imm(Gfx*);
25
26/// Get the low-level renderer.
27LLR* gfx_get_llr(Gfx*);
24 28
25/// Get the asset cache. 29/// Get the asset cache.
26AssetCache* gfx_get_asset_cache(Gfx*); 30AssetCache* gfx_get_asset_cache(Gfx*);
diff --git a/include/gfx/render/imm.h b/include/gfx/render/imm.h
new file mode 100644
index 0000000..62c071e
--- /dev/null
+++ b/include/gfx/render/imm.h
@@ -0,0 +1,52 @@
1#pragma once
2
3#include <math/aabb2.h>
4#include <math/aabb3.h>
5#include <math/vec3.h>
6
7typedef struct Imm Imm;
8
9/// Prepare the graphics systems for immediate-mode rendering.
10///
11/// Call this before issuing any immediate-mode rendering draws.
12void gfx_imm_start(Imm*);
13
14/// End immediate mode rendering.
15///
16/// Call this after issuing immediate-mode rendering draws and before swapping
17/// buffers.
18void gfx_imm_end(Imm*);
19
20/// Flush draw commands.
21///
22/// This should be done when changing any state that may affect the rendering of
23/// primitives; for example, LLR matrix stack changes.
24void gfx_imm_flush(Imm*);
25
26/// Draw a set of triangles.
27void gfx_imm_draw_triangles(Imm*, const vec3[], size_t num_triangles);
28
29/// Draw a triangle.
30void gfx_imm_draw_triangle(Imm*, const vec3[3]);
31
32/// Draw a bounding box.
33void gfx_imm_draw_aabb2(Imm*, aabb2);
34
35/// Draw a bounding box.
36void gfx_imm_draw_aabb3(Imm*, aabb3);
37
38/// Draw a box.
39///
40/// The vertices must be given in the following order:
41///
42/// 7 ----- 6
43/// / /|
44/// 3 ----- 2 |
45/// | | |
46/// | 4 ----- 5
47/// |/ |/
48/// 0 ----- 1
49void gfx_imm_draw_box3(Imm* renderer, const vec3 vertices[8]);
50
51/// Set the render colour.
52void gfx_imm_set_colour(Imm*, vec4 colour);
diff --git a/include/gfx/render/llr.h b/include/gfx/render/llr.h
new file mode 100644
index 0000000..6f635b6
--- /dev/null
+++ b/include/gfx/render/llr.h
@@ -0,0 +1,81 @@
1#pragma once
2
3#include <math/fwd.h>
4#include <math/vec3.h>
5
6typedef struct Anima Anima;
7typedef struct Camera Camera;
8typedef struct Geometry Geometry;
9typedef struct Light Light;
10typedef struct Material Material;
11typedef struct Mesh Mesh;
12typedef struct ShaderProgram ShaderProgram;
13typedef struct Skeleton Skeleton;
14typedef struct Texture Texture;
15
16typedef struct LLR LLR;
17
18// -----------------------------------------------------------------------------
19// Low-level rendering.
20// -----------------------------------------------------------------------------
21
22/// Set the shader to be used for subsequent draw calls.
23/// The shader is not yet activated at this point.
24void gfx_llr_set_shader(LLR*, ShaderProgram*);
25
26/// Push a light into the lights stack.
27void gfx_llr_push_light(LLR*, Light*);
28
29/// Pop the last light from the lights stack.
30void gfx_llr_pop_light(LLR*);
31
32/// Load a skeleton.
33///
34/// If a skeleton is loaded, subsequent meshes are rendered with joint data
35/// passed to the shader. This has a cost, so if subsequent meshes are not
36/// animated, unload the skeleton prior to rendering them.
37void gfx_llr_set_skeleton(LLR*, const Anima*, const Skeleton*);
38
39/// Clear the loaded skeleton.
40void gfx_llr_clear_skeleton(LLR*);
41
42/// Set the material.
43///
44/// The material need not be set explicitly when rendering a mesh
45/// (gfx_llr_render_mesh). This is mostly useful when using the lower-level
46/// function to render geometry (gfx_llr_render_geometry).
47void gfx_llr_set_material(LLR*, const Material*);
48
49/// Set the camera.
50void gfx_llr_set_camera(LLR*, const Camera*);
51
52/// Set the projection matrix.
53void gfx_llr_set_projection_matrix(LLR* renderer, const mat4* projection);
54
55/// Set the aspect ratio.
56void gfx_llr_set_aspect(LLR*, float aspect);
57
58/// Render the geometry.
59void gfx_llr_render_geometry(LLR*, const Geometry*);
60
61/// Render the mesh.
62void gfx_llr_render_mesh(LLR*, const Mesh*);
63
64// -----------------------------------------------------------------------------
65// Matrix stack manipulation.
66// -----------------------------------------------------------------------------
67
68/// Load an identity model matrix. Clears the matrix stack.
69void gfx_llr_load_identity(LLR* renderer);
70
71/// Push the given matrix to the matrix stack.
72void gfx_llr_push_matrix(LLR* renderer, const mat4* matrix);
73
74/// Pop the top of the matrix stack.
75void gfx_llr_pop_matrix(LLR* renderer);
76
77/// Push a translation matrix to the matrix stack.
78void gfx_llr_translate(LLR* renderer, vec3 offset);
79
80/// Set the model matrix. Clears the matrix stack.
81void gfx_llr_set_model_matrix(LLR*, const mat4*);
diff --git a/include/gfx/render/renderer.h b/include/gfx/render/renderer.h
new file mode 100644
index 0000000..9f3231b
--- /dev/null
+++ b/include/gfx/render/renderer.h
@@ -0,0 +1,36 @@
1#pragma once
2
3#include <math/defs.h>
4#include <math/fwd.h>
5
6typedef struct GfxCore GfxCore;
7typedef struct Scene Scene;
8
9typedef struct Renderer Renderer;
10
11// TODO: Add RenderDepth.
12typedef enum RenderSceneMode {
13 RenderDefault,
14 RenderDebug,
15 RenderNormals,
16 RenderNormalMappedNormals,
17 RenderTangents
18} RenderSceneMode;
19
20typedef enum RenderSceneFilter {
21 RenderOpaqueAndAlphaMasked,
22 RenderTransparent
23} RenderSceneFilter;
24
25typedef struct RenderSceneParams {
26 RenderSceneMode mode;
27 RenderSceneFilter filter;
28 const Scene* scene;
29 const Camera* camera;
30} RenderSceneParams;
31
32/// Render the scene.
33void gfx_render_scene(Renderer*, const RenderSceneParams*);
34
35/// Update the scene.
36void gfx_update(Scene*, const Camera*, R t);
diff --git a/include/gfx/scene.h b/include/gfx/scene.h
new file mode 100644
index 0000000..3aa6e0e
--- /dev/null
+++ b/include/gfx/scene.h
@@ -0,0 +1,296 @@
1#pragma once
2
3#include <gfx/core.h>
4#include <gfx/sizes.h>
5
6#include <math/aabb3.h>
7#include <math/fwd.h>
8#include <math/mat4.h>
9
10typedef struct Anima Anima;
11typedef struct Camera Camera;
12typedef struct Light Light;
13typedef struct Material Material;
14typedef struct Mesh Mesh;
15typedef struct Model Model;
16typedef struct Scene Scene;
17/// A node in the scene graph.
18///
19/// Scene nodes take ownership of the object they are associated with (Camera,
20/// Light, SceneObject, etc), as well as of child nodes.
21typedef struct SceneNode SceneNode;
22typedef struct SceneObject SceneObject;
23typedef struct Skeleton Skeleton;
24typedef struct Texture Texture;
25
26/// Light type.
27typedef enum LightType { EnvironmentLightType } LightType;
28
29/// Describes an environment light.
30typedef struct EnvironmentLightDesc {
31 const Texture* environment_map;
32} EnvironmentLightDesc;
33
34/// Describes a light.
35typedef struct LightDesc {
36 LightType type;
37 union {
38 EnvironmentLightDesc environment;
39 } light;
40} LightDesc;
41
42/// Alpha mode.
43typedef enum AlphaMode { Opaque = 0, Mask = 1, Blend = 2 } AlphaMode;
44
45/// Describes a material.
46///
47/// TODO: It doesn't hold the shader program anymore...It's in the Mesh.
48/// A material holds a shader program and a set of shader-specific uniform
49/// variables. Two materials can share the same shader, but shader parameters
50/// generally give two materials a different appearance.
51typedef struct MaterialDesc {
52 AlphaMode alpha_mode;
53 float alpha_cutoff;
54 int num_uniforms;
55 ShaderUniform uniforms[GFX_MAX_UNIFORMS_PER_MATERIAL];
56} MaterialDesc;
57
58/// Describes a mesh.
59typedef struct MeshDesc {
60 const Geometry* geometry;
61 const Material* material;
62 ShaderProgram* shader;
63} MeshDesc;
64
65typedef struct ObjectDesc {
66 size_t num_meshes;
67 Mesh* meshes[GFX_MAX_NUM_MESHES];
68} ObjectDesc;
69
70/// Scene node type.
71typedef enum NodeType {
72 LogicalNode,
73 AnimaNode,
74 CameraNode,
75 LightNode,
76 ModelNode,
77 ObjectNode,
78} NodeType;
79
80// -----------------------------------------------------------------------------
81// Camera.
82// -----------------------------------------------------------------------------
83
84/// Create a new camera.
85Camera* gfx_make_camera(void);
86
87/// Destroy the camera.
88///
89/// The caller must make sure that no Node owns the given Camera.
90/// For a safe purge of unused resources, see scene_purge().
91void gfx_destroy_camera(Camera**);
92
93// -----------------------------------------------------------------------------
94// Light.
95// -----------------------------------------------------------------------------
96
97/// Create a light.
98Light* gfx_make_light(const LightDesc*);
99
100/// Destroy the light.
101///
102/// The caller must make sure that no Node owns the given Light.
103/// For a safe purge of unused resources, see scene_purge().
104void gfx_destroy_light(Light**);
105
106// -----------------------------------------------------------------------------
107// Material.
108// -----------------------------------------------------------------------------
109
110/// Create a material.
111Material* gfx_make_material(const MaterialDesc*);
112
113/// Destroy the material.
114///
115/// The caller must make sure that no Mesh points to the given Material.
116/// For a safe purge of unused resources, see scene_purge().
117void gfx_destroy_material(Material**);
118
119// -----------------------------------------------------------------------------
120// Mesh.
121// -----------------------------------------------------------------------------
122
123/// Create a mesh.
124Mesh* gfx_make_mesh(const MeshDesc*);
125
126/// Destroy the mesh.
127///
128/// The caller must make sure that no SceneObject points to the given Mesh.
129/// For a safe purge of unused resources, see scene_purge().
130void gfx_destroy_mesh(Mesh**);
131
132// -----------------------------------------------------------------------------
133// Model.
134// -----------------------------------------------------------------------------
135
136/// Return the model's anima, or null if the model is not animated.
137Anima* gfx_get_model_anima(Model*);
138
139/// Return the model's root node.
140const SceneNode* gfx_get_model_root(const Model*);
141SceneNode* gfx_get_model_root_mut(Model*);
142
143// -----------------------------------------------------------------------------
144// Object.
145// -----------------------------------------------------------------------------
146
147/// Create a new object.
148SceneObject* gfx_make_object(const ObjectDesc*);
149
150/// Destroy the object.
151///
152/// The caller must make sure that no Node owns the given SceneObject.
153/// For a safe purge of unused resources, see scene_purge().
154void gfx_destroy_object(SceneObject**);
155
156/// Set the object's skeleton.
157void gfx_set_object_skeleton(SceneObject*, const Skeleton*);
158
159/// Get the object's skeleton.
160/// Return null if the object has no skeleton.
161const Skeleton* gfx_get_object_skeleton(const SceneObject*);
162
163/// Gets the object's bounding box.
164///
165/// The object's bounding box is the bounding box of its mesh geometries.
166aabb3 gfx_get_object_aabb(const SceneObject*);
167
168// TODO: Remove the scene object? It only contains the root node.
169// -----------------------------------------------------------------------------
170// Scene.
171// -----------------------------------------------------------------------------
172
173/// Create a new scene.
174Scene* gfx_make_scene(void);
175
176/// Destroy the scene.
177///
178/// This function destroys the scene and all objects that it owns (scene
179/// objects, cameras, lights, etc), but not objects that could be shared with
180/// other scenes (meshes, materials, etc).
181void gfx_destroy_scene(Scene**);
182
183/// Get the scene's root node.
184const SceneNode* gfx_get_scene_root(const Scene*);
185SceneNode* gfx_get_scene_root_mut(Scene*);
186
187// -----------------------------------------------------------------------------
188// Node constructors and destructor.
189// -----------------------------------------------------------------------------
190
191/// Create a new scene node.
192///
193/// This node does not contain any camera, light, object, etc. and exists simply
194/// as a logical and spatial construct.
195SceneNode* gfx_make_node(void);
196
197/// Create an anima node.
198SceneNode* gfx_make_anima_node(Anima*);
199
200/// Create a new camera node.
201SceneNode* gfx_make_camera_node(Camera*);
202
203/// Create a new light node.
204SceneNode* gfx_make_light_node(Light*);
205
206/// Create a new model node.
207SceneNode* gfx_make_model_node(Model*);
208
209/// Create a new object node.
210SceneNode* gfx_make_object_node(SceneObject*);
211
212/// Recursively destroy the scene node and its children.
213///
214/// The scene node and its children are removed from the scene graph.
215///
216/// Node resources -- cameras, lights, objects, etc. -- are also destroyed.
217void gfx_destroy_node(SceneNode**);
218
219// -----------------------------------------------------------------------------
220// Node getters.
221// -----------------------------------------------------------------------------
222
223/// Get the node's type.
224NodeType gfx_get_node_type(const SceneNode*);
225
226/// Get the node's anima.
227///
228/// The node must be of type AnimaNode.
229const Anima* gfx_get_node_anima(const SceneNode*);
230Anima* gfx_get_node_anima_mut(SceneNode*);
231
232/// Get the node's camera.
233///
234/// The node must be of type CameraNode.
235const Camera* gfx_get_node_camera(const SceneNode* node);
236Camera* gfx_get_node_camera_mut(SceneNode* node);
237
238/// Get the node's light.
239///
240/// The node must be of type LightNode.
241const Light* gfx_get_node_light(const SceneNode*);
242Light* gfx_get_node_light_mut(SceneNode*);
243
244/// Get the node's model.
245///
246/// The node must be of type ModelNode.
247const Model* gfx_get_node_model(const SceneNode*);
248Model* gfx_get_node_model_mut(SceneNode*);
249
250/// Get the node's scene object.
251///
252/// The node must be of type ObjectNode.
253const SceneObject* gfx_get_node_object(const SceneNode*);
254SceneObject* gfx_get_node_object_mut(SceneNode*);
255
256/// Get the node's parent.
257const SceneNode* gfx_get_node_parent(const SceneNode*);
258SceneNode* gfx_get_node_parent_mut(SceneNode*);
259
260/// Get the node's first child.
261const SceneNode* gfx_get_node_child(const SceneNode*);
262SceneNode* gfx_get_node_child_mut(SceneNode*);
263
264/// Get the node's immediate sibling.
265const SceneNode* gfx_get_node_sibling(const SceneNode*);
266SceneNode* gfx_get_node_sibling_mut(SceneNode*);
267
268/// Get the node's (local) transform.
269mat4 gfx_get_node_transform(const SceneNode*);
270
271/// Get the node's global transform.
272mat4 gfx_get_node_global_transform(const SceneNode*);
273
274// -----------------------------------------------------------------------------
275// Node setters.
276// -----------------------------------------------------------------------------
277
278/// Set the node's parent.
279///
280/// Pass in null to unwire from the existing parent, if one exists.
281void gfx_set_node_parent(SceneNode*, SceneNode* parent_node);
282
283/// Set the node's (local) transform.
284void gfx_set_node_transform(SceneNode*, const mat4* transform);
285
286/// Set the node's position.
287void gfx_set_node_position(SceneNode*, const vec3* position);
288
289/// Set the node's rotation.
290void gfx_set_node_rotation(SceneNode*, const quat* rotation);
291
292/// Set the node's rotation.
293void gfx_set_node_rotation_mat(SceneNode*, const mat4* rotation);
294
295/// Log the node's hierarchy.
296void gfx_log_node_hierarchy(const SceneNode*);
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..55319e1 100644
--- a/gfx/include/gfx/util/skyquad.h
+++ b/include/gfx/util/skyquad.h
@@ -2,7 +2,6 @@
2#pragma once 2#pragma once
3 3
4typedef struct GfxCore GfxCore; 4typedef struct GfxCore GfxCore;
5typedef struct Scene Scene;
6typedef struct SceneNode SceneNode; 5typedef struct SceneNode SceneNode;
7typedef struct SceneObject SceneObject; 6typedef struct SceneObject SceneObject;
8typedef struct Texture Texture; 7typedef struct Texture Texture;
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..f6c6048 100644
--- a/gfx/shaders/cook_torrance.frag
+++ b/shaders/cook_torrance.frag
@@ -1,3 +1,8 @@
1/*
2[1] BRDF reference: https://graphicrants.blogspot.com/2013/08/specular-brdf-reference.html
3[2] glTF reference implementation: https://github.com/KhronosGroup/glTF-Sample-Renderer/blob/main/source/Renderer/shaders/brdf.glsl
4[3] Learn OpenGL reference: https://learnopengl.com/PBR/Theory (see other chapters also for IBL)
5*/
1precision highp float; 6precision highp float;
2 7
3uniform vec4 BaseColorFactor; 8uniform vec4 BaseColorFactor;
@@ -5,19 +10,26 @@ uniform float MetallicFactor;
5uniform float RoughnessFactor; 10uniform float RoughnessFactor;
6uniform vec3 EmissiveFactor; 11uniform vec3 EmissiveFactor;
7 12
8#ifdef HAS_ALBEDO_MAP 13#if HAS_TRANSPARENCY
14#define ALPHA_MODE_MASK 1
15#define ALPHA_MODE_BLEND 2
16uniform int AlphaMode;
17uniform float AlphaCutoff;
18#endif
19
20#if HAS_ALBEDO_MAP
9uniform sampler2D BaseColorTexture; 21uniform sampler2D BaseColorTexture;
10#endif 22#endif
11#ifdef HAS_METALLIC_ROUGHNESS_MAP 23#if HAS_METALLIC_ROUGHNESS_MAP
12uniform sampler2D MetallicRoughnessTexture; 24uniform sampler2D MetallicRoughnessTexture;
13#endif 25#endif
14#ifdef HAS_EMISSIVE_MAP 26#if HAS_EMISSIVE_MAP
15uniform sampler2D EmissiveTexture; 27uniform sampler2D EmissiveTexture;
16#endif 28#endif
17#ifdef HAS_OCCLUSION_MAP 29#if HAS_OCCLUSION_MAP
18uniform sampler2D AmbientOcclusionTexture; 30uniform sampler2D AmbientOcclusionTexture;
19#endif 31#endif
20#ifdef HAS_NORMAL_MAP 32#if HAS_NORMAL_MAP
21uniform sampler2D NormalMap; 33uniform sampler2D NormalMap;
22#endif 34#endif
23 35
@@ -32,13 +44,13 @@ uniform vec3 CameraPosition; // World space.
32 44
33// World-space position, normal and tangent. 45// World-space position, normal and tangent.
34in vec3 Position; 46in vec3 Position;
35#ifdef HAS_NORMALS 47#if HAS_NORMALS
36in vec3 Normal; 48in vec3 Normal;
37#endif 49#endif
38#ifdef HAS_TANGENTS 50#if HAS_TANGENTS
39in vec4 Tangent; 51in vec4 Tangent;
40#endif 52#endif
41#ifdef HAS_TEXCOORDS 53#if HAS_TEXCOORDS
42in vec2 Texcoord; 54in vec2 Texcoord;
43#endif 55#endif
44 56
@@ -56,7 +68,7 @@ layout (location = 0) out vec4 Colour;
56#if defined(HAS_NORMAL_MAP) && (defined(HAS_TANGENTS) || defined(HAS_TEXCOORDS)) 68#if defined(HAS_NORMAL_MAP) && (defined(HAS_TANGENTS) || defined(HAS_TEXCOORDS))
57vec3 get_ws_normal(vec3 normalWs, vec3 normalMapSample) { 69vec3 get_ws_normal(vec3 normalWs, vec3 normalMapSample) {
58 vec3 N = normalize(Normal); 70 vec3 N = normalize(Normal);
59#ifdef HAS_TANGENTS 71#if HAS_TANGENTS
60 //vec3 T = normalize(tangent.xyz - dot(tangent.xyz, N) * N); 72 //vec3 T = normalize(tangent.xyz - dot(tangent.xyz, N) * N);
61 vec3 T = Tangent.xyz; 73 vec3 T = Tangent.xyz;
62 vec3 B = Tangent.w * cross(N, T); 74 vec3 B = Tangent.w * cross(N, T);
@@ -86,8 +98,10 @@ vec3 get_ws_normal(vec3 normalWs, vec3 normalMapSample) {
86} 98}
87#endif // HAS_TANGENTS || HAS_TEXCOORDS 99#endif // HAS_TANGENTS || HAS_TEXCOORDS
88 100
101// Normal distribution function (NDF).
102// Eq (4) in reference [1].
89float trowbridge_reitz_GGX(float roughness, float NdotH) { 103float trowbridge_reitz_GGX(float roughness, float NdotH) {
90 float a = roughness * roughness; 104 float a = roughness * roughness; // "alpha roughness" in the reference.
91 float a2 = a * a; 105 float a2 = a * a;
92 float d = NdotH * NdotH * (a2 - 1.0) + 1.0; 106 float d = NdotH * NdotH * (a2 - 1.0) + 1.0;
93 return a2 / (PI * d * d); 107 return a2 / (PI * d * d);
@@ -97,6 +111,8 @@ float geometry_schlick_GGX(float k, float NdotV) {
97 return NdotV / (NdotV * (1.0 - k) + k); 111 return NdotV / (NdotV * (1.0 - k) + k);
98} 112}
99 113
114// Geometry function.
115// See "Smith" under "Geometric Shadowing" in reference [1].
100float geometry_smith(float roughness, float NdotL, float NdotV) { 116float geometry_smith(float roughness, float NdotL, float NdotV) {
101 float k = roughness * roughness / 2.0; // IBL 117 float k = roughness * roughness / 2.0; // IBL
102 return geometry_schlick_GGX(k, NdotV) * geometry_schlick_GGX(k, NdotL); 118 return geometry_schlick_GGX(k, NdotV) * geometry_schlick_GGX(k, NdotL);
@@ -121,7 +137,11 @@ vec3 cook_torrance(
121 vec3 F = fresnel_schlick(F0, HdotV); 137 vec3 F = fresnel_schlick(F0, HdotV);
122 float G = geometry_smith(roughness, NdotL, NdotV); 138 float G = geometry_smith(roughness, NdotL, NdotV);
123 vec3 Kd = mix(vec3(1.0) - F, vec3(0.0), metallic); 139 vec3 Kd = mix(vec3(1.0) - F, vec3(0.0), metallic);
124 vec3 diffuse = Kd*albedo*INV_PI; 140 // A non-HDR environment map essentially has the 1/pi baked in as it does not
141 // use physical units. See:
142 // https://seblagarde.wordpress.com/2012/01/08/pi-or-not-to-pi-in-game-lighting-equation/
143 // vec3 diffuse = Kd * albedo * INV_PI;
144 vec3 diffuse = Kd * albedo * INV_PI;
125 // Take a max to prevent division by 0 when either dot product is 0. 145 // Take a max to prevent division by 0 when either dot product is 0.
126 vec3 specular = (D*F*G) / max(4.0 * NdotV * NdotL, 0.0001); 146 vec3 specular = (D*F*G) / max(4.0 * NdotV * NdotL, 0.0001);
127 return diffuse + specular; 147 return diffuse + specular;
@@ -132,46 +152,33 @@ vec3 cook_torrance_IBL(
132 vec3 albedo, float metallic, float roughness, float occlusion, 152 vec3 albedo, float metallic, float roughness, float occlusion,
133 float NdotV, 153 float NdotV,
134 vec3 irradiance, vec3 prefiltered_env, vec2 BRDF_env) { 154 vec3 irradiance, vec3 prefiltered_env, vec2 BRDF_env) {
135 vec3 F0 = mix(vec3(0.04), albedo, metallic); 155 vec3 F0 = mix(vec3(0.04), albedo, metallic); // albedo = F0 for metals
136 vec3 F = fresnel_schlick_roughness(F0, NdotV, roughness); 156 vec3 F = fresnel_schlick_roughness(F0, NdotV, roughness);
137 vec3 Kd = mix(vec3(1.0) - F, vec3(0.0), metallic); 157 vec3 Kd = (vec3(1.0) - F) * (1.0 - metallic);
138 vec3 diffuse = Kd * albedo * INV_PI * irradiance; 158 // A non-HDR environment map essentially has the 1/pi baked in as it does not
159 // use physical units. See:
160 // https://seblagarde.wordpress.com/2012/01/08/pi-or-not-to-pi-in-game-lighting-equation/
161 // vec3 diffuse = Kd * albedo * INV_PI * irradiance;
162 vec3 diffuse = Kd * albedo * irradiance;
139 vec3 specular = prefiltered_env * (F * BRDF_env.x + BRDF_env.y); 163 vec3 specular = prefiltered_env * (F * BRDF_env.x + BRDF_env.y);
140 return occlusion * (diffuse + specular); 164 return occlusion * (diffuse + specular);
141} 165}
142 166
143void main() 167void main()
144{ 168{
145 // TODO: Also use the specular F0 map from the model, and emissive. Make sure
146 // to use all maps.
147 // https://sketchfab.com/models/b81008d513954189a063ff901f7abfe4
148#ifdef HAS_NORMAL_MAP
149 vec3 normalMapSample = texture(NormalMap, Texcoord).xyz * 2.0 - 1.0;
150 vec3 N = get_ws_normal(Normal, normalMapSample);
151#elif HAS_NORMALS
152 vec3 N = normalize(Normal);
153#endif
154 vec3 V = normalize(CameraPosition - Position);
155 vec3 R = reflect(-V, N);
156 // Not needed for IBL.
157 //vec3 L = N;
158 //vec3 H = normalize(L + V);
159
160 float NdotV = max(0.0, dot(N, V));
161 // Not needed for IBL.
162 //float NdotL = max(0.0, dot(N,L));
163 //float NdotH = max(0.0, dot(N,H));
164 //float HdotV = clamp(dot(H,V), 0.0, 1.0); // Clamp to prevent black spots.
165
166 // TODO: BaseColorFactor and BaseColorTexture are vec4/rgba quantities
167 // respectively. Handle the alpha channel.
168 // TODO: Other factors. 169 // TODO: Other factors.
169#ifdef HAS_ALBEDO_MAP 170#if HAS_ALBEDO_MAP
170 vec3 albedo = vec3(BaseColorFactor) * texture(BaseColorTexture, Texcoord).rgb; 171 vec4 base_colour = vec4(BaseColorFactor) * texture(BaseColorTexture, Texcoord);
171#else 172#else
172 vec3 albedo = vec3(BaseColorFactor); 173 vec4 base_colour = vec4(BaseColorFactor);
174#endif
175 vec3 albedo = base_colour.rgb;
176#if HAS_TRANSPARENCY
177 if ((AlphaMode == ALPHA_MODE_MASK) && (base_colour.a < AlphaCutoff)) {
178 discard;
179 }
173#endif 180#endif
174#ifdef HAS_METALLIC_ROUGHNESS_MAP 181#if HAS_METALLIC_ROUGHNESS_MAP
175 // Spec: "Its green channel contains roughness values and its blue channel 182 // Spec: "Its green channel contains roughness values and its blue channel
176 // contains metalness values." 183 // contains metalness values."
177 vec2 metal_roughness 184 vec2 metal_roughness
@@ -180,12 +187,12 @@ void main()
180#else 187#else
181 vec2 metal_roughness = vec2(MetallicFactor, RoughnessFactor); 188 vec2 metal_roughness = vec2(MetallicFactor, RoughnessFactor);
182#endif 189#endif
183#ifdef HAS_EMISSIVE_MAP 190#if HAS_EMISSIVE_MAP
184 vec3 emissive = EmissiveFactor * texture(EmissiveTexture, Texcoord).rgb; 191 vec3 emissive = EmissiveFactor * texture(EmissiveTexture, Texcoord).rgb;
185#else 192#else
186 vec3 emissive = EmissiveFactor; 193 vec3 emissive = EmissiveFactor;
187#endif 194#endif
188#ifdef HAS_OCCLUSION_MAP 195#if HAS_OCCLUSION_MAP
189 float occlusion = texture(AmbientOcclusionTexture, Texcoord).r; 196 float occlusion = texture(AmbientOcclusionTexture, Texcoord).r;
190#else 197#else
191 float occlusion = 1.0; 198 float occlusion = 1.0;
@@ -193,6 +200,32 @@ void main()
193 float metallic = metal_roughness.x; 200 float metallic = metal_roughness.x;
194 float roughness = metal_roughness.y; 201 float roughness = metal_roughness.y;
195 202
203 // TODO: Also use the specular F0 map from the model, and emissive. Make sure
204 // to use all maps.
205 // https://sketchfab.com/models/b81008d513954189a063ff901f7abfe4
206#if HAS_NORMAL_MAP
207 // Spec: "After dequantization, texel values MUST be mapped as follows:
208 // red [0.0 .. 1.0] to X [-1 .. 1],
209 // green [0.0 .. 1.0] to Y [-1 .. 1],
210 // blue (0.5 .. 1.0] to Z ( 0 .. 1].
211 // Normal textures SHOULD NOT contain blue values less than or equal to 0.5."
212 vec3 normalMapSample = texture(NormalMap, Texcoord).xyz * 2.0 - 1.0;
213 vec3 N = get_ws_normal(Normal, normalMapSample);
214#elif HAS_NORMALS
215 vec3 N = normalize(Normal);
216#endif
217 vec3 V = normalize(CameraPosition - Position);
218 vec3 R = reflect(-V, N);
219 // Not needed for IBL.
220 //vec3 L = N;
221 //vec3 H = normalize(L + V);
222
223 float NdotV = max(0.0, dot(N, V));
224 // Not needed for IBL.
225 //float NdotL = max(0.0, dot(N,L));
226 //float NdotH = max(0.0, dot(N,H));
227 //float HdotV = clamp(dot(H,V), 0.0, 1.0); // Clamp to prevent black spots.
228
196 // For a single light direction: 229 // For a single light direction:
197 // vec3 brdf = cook_torrance(albedo, metallic, roughness, NdotL, NdotV, NdotH, HdotV); 230 // vec3 brdf = cook_torrance(albedo, metallic, roughness, NdotL, NdotV, NdotH, HdotV);
198 // vec3 Li = texture(Sky, N).rgb; 231 // vec3 Li = texture(Sky, N).rgb;
@@ -251,5 +284,5 @@ void main()
251 // //colour = B * 0.5 + 0.5; 284 // //colour = B * 0.5 + 0.5;
252 // } 285 // }
253 286
254 Colour = vec4(colour, 1.0); 287 Colour = vec4(colour, base_colour.a);
255} 288}
diff --git a/gfx/shaders/cook_torrance.vert b/shaders/cook_torrance.vert
index 5f126c0..17fe1f7 100644
--- a/gfx/shaders/cook_torrance.vert
+++ b/shaders/cook_torrance.vert
@@ -5,7 +5,7 @@ uniform mat4 ModelMatrix;
5uniform mat4 View; 5uniform mat4 View;
6uniform mat4 Projection; 6uniform mat4 Projection;
7//uniform mat4 MVP; 7//uniform mat4 MVP;
8#ifdef HAS_JOINTS 8#if HAS_JOINTS
9// The client should pass in an appropriate value for MAX_JOINTS. 9// The client should pass in an appropriate value for MAX_JOINTS.
10// #define MAX_JOINTS 96 10// #define MAX_JOINTS 96
11// 11//
@@ -21,35 +21,35 @@ uniform mat4 JointMatrices[MAX_JOINTS]; // Use 4x4 for now to keep it simple.
21#endif 21#endif
22 22
23layout (location = 0) in vec3 vPosition; 23layout (location = 0) in vec3 vPosition;
24#ifdef HAS_NORMALS 24#if HAS_NORMALS
25layout (location = 1) in vec3 vNormal; 25layout (location = 1) in vec3 vNormal;
26#endif 26#endif
27#ifdef HAS_TANGENTS 27#if HAS_TANGENTS
28layout (location = 2) in vec4 vTangent; 28layout (location = 2) in vec4 vTangent;
29#endif 29#endif
30#ifdef HAS_TEXCOORDS 30#if HAS_TEXCOORDS
31layout (location = 3) in vec2 vTexcoord; 31layout (location = 3) in vec2 vTexcoord;
32#endif 32#endif
33#ifdef HAS_JOINTS 33#if HAS_JOINTS
34layout (location = 4) in uvec4 vJoint; 34layout (location = 4) in uvec4 vJoint;
35layout (location = 5) in vec4 vWeight; 35layout (location = 5) in vec4 vWeight;
36#endif 36#endif
37 37
38// World-space position, normal and tangent. 38// World-space position, normal and tangent.
39out vec3 Position; 39out vec3 Position;
40#ifdef HAS_NORMALS 40#if HAS_NORMALS
41out vec3 Normal; 41out vec3 Normal;
42#endif 42#endif
43#ifdef HAS_TANGENTS 43#if HAS_TANGENTS
44out vec4 Tangent; 44out vec4 Tangent;
45#endif 45#endif
46#ifdef HAS_TEXCOORDS 46#if HAS_TEXCOORDS
47out vec2 Texcoord; 47out vec2 Texcoord;
48#endif 48#endif
49 49
50void main() 50void main()
51{ 51{
52#ifdef HAS_JOINTS 52#if HAS_JOINTS
53 mat4 skinMatrix = 53 mat4 skinMatrix =
54 vWeight.x * JointMatrices[vJoint.x] + 54 vWeight.x * JointMatrices[vJoint.x] +
55 vWeight.y * JointMatrices[vJoint.y] + 55 vWeight.y * JointMatrices[vJoint.y] +
@@ -59,14 +59,14 @@ void main()
59#else 59#else
60 Position = vec3(ModelMatrix * vec4(vPosition, 1.0)); 60 Position = vec3(ModelMatrix * vec4(vPosition, 1.0));
61#endif 61#endif
62#ifdef HAS_NORMALS 62#if HAS_NORMALS
63 Normal = mat3(ModelMatrix) * vNormal; 63 Normal = mat3(ModelMatrix) * vNormal;
64 //Normal = normalize(ModelMatrix * vec4(vNormal, 0.0)).xyz; 64 //Normal = normalize(ModelMatrix * vec4(vNormal, 0.0)).xyz;
65#endif 65#endif
66#ifdef HAS_TANGENTS 66#if HAS_TANGENTS
67 Tangent = vec4(mat3(ModelMatrix) * vTangent.xyz, vTangent.w); 67 Tangent = vec4(mat3(ModelMatrix) * vTangent.xyz, vTangent.w);
68#endif 68#endif
69#ifdef HAS_TEXCOORDS 69#if HAS_TEXCOORDS
70 Texcoord = vTexcoord; 70 Texcoord = vTexcoord;
71#endif 71#endif
72 gl_Position = Projection * View * vec4(Position, 1.0); 72 gl_Position = Projection * View * vec4(Position, 1.0);
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/scene/animation.c b/src/animation.c
index 08d02ce..c52df73 100644
--- a/gfx/src/scene/animation.c
+++ b/src/animation.c
@@ -1,7 +1,6 @@
1#include "animation_impl.h" 1#include "animation_impl.h"
2 2
3#include "node_impl.h" 3#include "memory.h"
4#include "scene_memory.h"
5 4
6#include <string.h> 5#include <string.h>
7 6
@@ -204,10 +203,6 @@ void gfx_destroy_anima(Anima** anima) {
204 mem_free_animation(&animation); 203 mem_free_animation(&animation);
205 } 204 }
206 205
207 if ((*anima)->parent.val) {
208 gfx_del_node((*anima)->parent);
209 }
210
211 mem_free_anima(anima); 206 mem_free_anima(anima);
212 } 207 }
213} 208}
@@ -450,10 +445,8 @@ void gfx_update_animation(Anima* anima, R t) {
450 // which we have constructed to be the common root of all skeletons. 445 // which we have constructed to be the common root of all skeletons.
451 // 446 //
452 // This procedure touches every joint exactly once. 447 // This procedure touches every joint exactly once.
453 SceneNode* root_node = mem_get_node(anima->parent); 448 const mat4 root_global_transform = mat4_id();
454 // LOGD("Root: %u, child: %u", anima->parent.val, root->child.val); 449 const mat4 root_inv_global_transform = mat4_id();
455 const mat4 root_global_transform = gfx_get_node_global_transform(root_node);
456 const mat4 root_inv_global_transform = mat4_inverse(root_global_transform);
457 450
458 Joint* root_joint = get_anima_root_joint(anima); 451 Joint* root_joint = get_anima_root_joint(anima);
459 compute_joint_matrices_rec( 452 compute_joint_matrices_rec(
@@ -504,21 +497,21 @@ Box gfx_get_joint_box(
504 return (Box){ 497 return (Box){
505 .vertices = { 498 .vertices = {
506 mat4_mul_vec3( 499 mat4_mul_vec3(
507 joint->joint_matrix, vec3_make(pmin.x, pmin.y, pmax.z), 1), 500 joint->joint_matrix, vec3_make(pmin.x, pmin.y, pmax.z), 1),
508 mat4_mul_vec3( 501 mat4_mul_vec3(
509 joint->joint_matrix, vec3_make(pmax.x, pmin.y, pmax.z), 1), 502 joint->joint_matrix, vec3_make(pmax.x, pmin.y, pmax.z), 1),
510 mat4_mul_vec3( 503 mat4_mul_vec3(
511 joint->joint_matrix, vec3_make(pmax.x, pmax.y, pmax.z), 1), 504 joint->joint_matrix, vec3_make(pmax.x, pmax.y, pmax.z), 1),
512 mat4_mul_vec3( 505 mat4_mul_vec3(
513 joint->joint_matrix, vec3_make(pmin.x, pmax.y, pmax.z), 1), 506 joint->joint_matrix, vec3_make(pmin.x, pmax.y, pmax.z), 1),
514 mat4_mul_vec3( 507 mat4_mul_vec3(
515 joint->joint_matrix, vec3_make(pmin.x, pmin.y, pmin.z), 1), 508 joint->joint_matrix, vec3_make(pmin.x, pmin.y, pmin.z), 1),
516 mat4_mul_vec3( 509 mat4_mul_vec3(
517 joint->joint_matrix, vec3_make(pmax.x, pmin.y, pmin.z), 1), 510 joint->joint_matrix, vec3_make(pmax.x, pmin.y, pmin.z), 1),
518 mat4_mul_vec3( 511 mat4_mul_vec3(
519 joint->joint_matrix, vec3_make(pmax.x, pmax.y, pmin.z), 1), 512 joint->joint_matrix, vec3_make(pmax.x, pmax.y, pmin.z), 1),
520 mat4_mul_vec3( 513 mat4_mul_vec3(
521 joint->joint_matrix, vec3_make(pmin.x, pmax.y, pmin.z), 1), 514 joint->joint_matrix, vec3_make(pmin.x, pmax.y, pmin.z), 1),
522 } 515 }
523 }; 516 };
524} 517}
diff --git a/gfx/src/scene/animation_impl.h b/src/animation_impl.h
index 4408158..4929b97 100644
--- a/gfx/src/scene/animation_impl.h
+++ b/src/animation_impl.h
@@ -1,6 +1,6 @@
1#pragma once 1#pragma once
2 2
3#include <gfx/scene/animation.h> 3#include <gfx/animation.h>
4#include <gfx/sizes.h> 4#include <gfx/sizes.h>
5 5
6#include "types.h" 6#include "types.h"
@@ -12,7 +12,6 @@
12#include <math/vec3.h> 12#include <math/vec3.h>
13 13
14#include <stddef.h> 14#include <stddef.h>
15#include <stdint.h>
16 15
17typedef struct Buffer Buffer; 16typedef struct Buffer Buffer;
18 17
@@ -24,24 +23,24 @@ typedef struct Buffer Buffer;
24/// Joints are mutable and store the transform and joint matrices that result 23/// Joints are mutable and store the transform and joint matrices that result
25/// from animation, aside from the inverse bind matrix. 24/// from animation, aside from the inverse bind matrix.
26typedef struct Joint { 25typedef struct Joint {
27 joint_idx child; /// First child Joint; index into Anima's joints. 26 joint_idx child; // First child Joint; index into Anima's joints.
28 joint_idx next; /// Next sibling Joint; index into Anima's joints. 27 joint_idx next; // Next sibling Joint; index into Anima's joints.
29 mat4 transform; /// Local transform relative to parent. 28 mat4 transform; // Local transform relative to parent.
30 mat4 inv_bind_matrix; /// Transforms the mesh into the joint's local space. 29 mat4 inv_bind_matrix; // Transforms the mesh into the joint's local space.
31 mat4 joint_matrix; /// inv(global) * global joint transform * inv(bind). 30 mat4 joint_matrix; // inv(global) * global joint transform * inv(bind).
32 aabb3 box; /// Bounding box of vertices affected by joint. 31 aabb3 box; // Bounding box of vertices affected by joint.
33} Joint; 32} Joint;
34 33
35/// Animation skeleton. 34/// Animation skeleton.
36typedef struct Skeleton { 35typedef struct Skeleton {
37 skeleton_idx next; 36 skeleton_idx next;
38 size_t num_joints; 37 size_t num_joints;
39 joint_idx joints[GFX_MAX_NUM_JOINTS]; /// Indices into Anima's joints array. 38 joint_idx joints[GFX_MAX_NUM_JOINTS]; // Indices into Anima's joints array.
40} Skeleton; 39} Skeleton;
41 40
42/// A keyframe of animation. 41/// A keyframe of animation.
43typedef struct Keyframe { 42typedef struct Keyframe {
44 R time; /// Start time in [0, end animation time] 43 R time; // Start time in [0, end animation time]
45 union { 44 union {
46 vec3 translation; 45 vec3 translation;
47 quat rotation; 46 quat rotation;
@@ -50,7 +49,7 @@ typedef struct Keyframe {
50 49
51/// Animation channel. 50/// Animation channel.
52typedef struct Channel { 51typedef struct Channel {
53 joint_idx target; /// Index into Anima's joints array. 52 joint_idx target; // Index into Anima's joints array.
54 ChannelType type; 53 ChannelType type;
55 AnimationInterpolation interpolation; 54 AnimationInterpolation interpolation;
56 size_t num_keyframes; 55 size_t num_keyframes;
@@ -89,10 +88,9 @@ typedef struct AnimationState {
89/// have no parent (a skeleton need not have its own root and can be a set of 88/// have no parent (a skeleton need not have its own root and can be a set of
90/// disjoint node hierarchies). 89/// disjoint node hierarchies).
91typedef struct Anima { 90typedef struct Anima {
92 node_idx parent; /// Parent SceneNode. 91 skeleton_idx skeleton; // Index of first skeleton.
93 skeleton_idx skeleton; /// Index of first skeleton. 92 animation_idx animation; // Index of first animation.
94 animation_idx animation; /// Index of first animation. 93 AnimationState state; // Current animation state.
95 AnimationState state; /// Current animation state. 94 size_t num_joints; // Number of actual joints in the array.
96 size_t num_joints; /// Number of actual joints in the array. 95 Joint joints[GFX_MAX_NUM_JOINTS]; // Shared by all skeletons.
97 Joint joints[GFX_MAX_NUM_JOINTS]; /// Shared by all skeletons.
98} Anima; 96} Anima;
diff --git a/gfx/src/asset/asset_cache.c b/src/asset/asset_cache.c
index 16c4d5c..dfaf7c6 100644
--- a/gfx/src/asset/asset_cache.c
+++ b/src/asset/asset_cache.c
@@ -1,15 +1,15 @@
1#include "asset_cache.h" 1#include "asset_cache.h"
2 2
3#include "animation_impl.h"
4#include "memory.h"
3#include "model.h" 5#include "model.h"
4#include "scene/animation_impl.h"
5#include "scene/model_impl.h" 6#include "scene/model_impl.h"
6#include "scene/node_impl.h" 7#include "scene/node_impl.h"
7#include "scene/scene_memory.h"
8#include "texture.h" 8#include "texture.h"
9 9
10#include <gfx/asset.h> 10#include <gfx/asset.h>
11#include <gfx/gfx.h> 11#include <gfx/gfx.h>
12#include <gfx/scene/node.h> 12#include <gfx/scene.h>
13#include <gfx_assert.h> 13#include <gfx_assert.h>
14 14
15#include <cstring.h> 15#include <cstring.h>
@@ -167,7 +167,6 @@ static Model* clone_model(const Model* model) {
167 167
168 SceneNode* root_copy = gfx_clone_scene_shallow(root); 168 SceneNode* root_copy = gfx_clone_scene_shallow(root);
169 root_copy->anima = mem_get_anima_index(anima_copy); 169 root_copy->anima = mem_get_anima_index(anima_copy);
170 anima_copy->parent = mem_get_node_index(root_copy);
171 170
172 Model* copy = mem_alloc_model(); 171 Model* copy = mem_alloc_model();
173 copy->root = mem_get_node_index(root_copy); 172 copy->root = mem_get_node_index(root_copy);
@@ -179,6 +178,7 @@ static Model* clone_model(const Model* model) {
179 178
180void gfx_init_asset_cache(AssetCache* cache) { 179void gfx_init_asset_cache(AssetCache* cache) {
181 assert(cache); 180 assert(cache);
181
182 mempool_make(&cache->assets); 182 mempool_make(&cache->assets);
183 183
184 // Allocate a dummy asset at index 0 to guarantee that no assets allocated by 184 // Allocate a dummy asset at index 0 to guarantee that no assets allocated by
@@ -189,6 +189,7 @@ void gfx_init_asset_cache(AssetCache* cache) {
189 189
190void gfx_destroy_asset_cache(AssetCache* cache) { 190void gfx_destroy_asset_cache(AssetCache* cache) {
191 assert(cache); 191 assert(cache);
192 // TODO: Destroy assets here.
192 mempool_del(&cache->assets); 193 mempool_del(&cache->assets);
193} 194}
194 195
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..b5c6b0d 100644
--- a/gfx/src/asset/model.c
+++ b/src/asset/model.c
@@ -82,30 +82,26 @@
82#include "asset/model.h" 82#include "asset/model.h"
83 83
84#include "asset/texture.h" 84#include "asset/texture.h"
85#include "gfx/core.h"
86#include "gfx/gfx.h"
87#include "gfx/scene/animation.h"
88#include "gfx/scene/camera.h"
89#include "gfx/scene/material.h"
90#include "gfx/scene/mesh.h"
91#include "gfx/scene/node.h"
92#include "gfx/scene/object.h"
93#include "gfx/scene/scene.h"
94#include "gfx/sizes.h"
95#include "gfx/util/shader.h"
96
97#include "gfx_assert.h" 85#include "gfx_assert.h"
98#include "scene/model_impl.h" 86#include "scene/model_impl.h"
99 87
100#include "cstring.h" 88#include <gfx/animation.h>
101#include "error.h" 89#include <gfx/core.h>
102#include "log/log.h" 90#include <gfx/gfx.h>
103#include "math/camera.h" 91#include <gfx/render/llr.h>
104#include "math/defs.h" 92#include <gfx/scene.h>
105#include "math/mat4.h" 93#include <gfx/sizes.h>
106#include "math/quat.h" 94#include <gfx/util/shader.h>
107#include "math/vec2.h" 95
108#include "math/vec3.h" 96#include <cstring.h>
97#include <error.h>
98#include <log/log.h>
99#include <math/camera.h>
100#include <math/defs.h>
101#include <math/mat4.h>
102#include <math/quat.h>
103#include <math/vec2.h>
104#include <math/vec3.h>
109 105
110#include "cgltf_tangents.h" 106#include "cgltf_tangents.h"
111#define CGLTF_IMPLEMENTATION 107#define CGLTF_IMPLEMENTATION
@@ -142,6 +138,7 @@
142#define DEFINE_HAS_NORMAL_MAP "HAS_NORMAL_MAP" 138#define DEFINE_HAS_NORMAL_MAP "HAS_NORMAL_MAP"
143#define DEFINE_HAS_OCCLUSION_MAP "HAS_OCCLUSION_MAP" 139#define DEFINE_HAS_OCCLUSION_MAP "HAS_OCCLUSION_MAP"
144#define DEFINE_HAS_EMISSIVE_MAP "HAS_EMISSIVE_MAP" 140#define DEFINE_HAS_EMISSIVE_MAP "HAS_EMISSIVE_MAP"
141#define DEFINE_HAS_TRANSPARENCY "HAS_TRANSPARENCY"
145#define DEFINE_HAS_JOINTS "HAS_JOINTS" 142#define DEFINE_HAS_JOINTS "HAS_JOINTS"
146#define DEFINE_MAX_JOINTS "MAX_JOINTS" 143#define DEFINE_MAX_JOINTS "MAX_JOINTS"
147 144
@@ -170,6 +167,8 @@ typedef struct MeshPermutation {
170 bool has_normal_map : 1; 167 bool has_normal_map : 1;
171 bool has_occlusion_map : 1; 168 bool has_occlusion_map : 1;
172 bool has_emissive_map : 1; 169 bool has_emissive_map : 1;
170 // Material.
171 bool has_transparency : 1;
173 }; 172 };
174 int32_t all; 173 int32_t all;
175 }; 174 };
@@ -196,6 +195,7 @@ static size_t make_defines(
196 check(has_normal_map, DEFINE_HAS_NORMAL_MAP); 195 check(has_normal_map, DEFINE_HAS_NORMAL_MAP);
197 check(has_occlusion_map, DEFINE_HAS_OCCLUSION_MAP); 196 check(has_occlusion_map, DEFINE_HAS_OCCLUSION_MAP);
198 check(has_emissive_map, DEFINE_HAS_EMISSIVE_MAP); 197 check(has_emissive_map, DEFINE_HAS_EMISSIVE_MAP);
198 check(has_transparency, DEFINE_HAS_TRANSPARENCY);
199 199
200 if (perm.has_joints) { 200 if (perm.has_joints) {
201 defines[next].name = sstring_make(DEFINE_MAX_JOINTS); 201 defines[next].name = sstring_make(DEFINE_MAX_JOINTS);
@@ -212,12 +212,12 @@ static ShaderProgram* make_shader_permutation(
212 LOGD( 212 LOGD(
213 "Compiling Cook-Torrance shader permutation: texcoords: %d, normals: " 213 "Compiling Cook-Torrance shader permutation: texcoords: %d, normals: "
214 "%d, tangents: %d, joints: %d, weights: %d, albedo map: %d, " 214 "%d, tangents: %d, joints: %d, weights: %d, albedo map: %d, "
215 "metallic-roughness map: " 215 "metallic-roughness map: %d, normal map: %d, AO map: %d, emissive map: "
216 "%d, normal " 216 "%d, has transparency: %d",
217 "map: %d, AO map: %d, emissive map: %d",
218 perm.has_texcoords, perm.has_normals, perm.has_tangents, perm.has_joints, 217 perm.has_texcoords, perm.has_normals, perm.has_tangents, perm.has_joints,
219 perm.has_weights, perm.has_albedo_map, perm.has_metallic_roughness_map, 218 perm.has_weights, perm.has_albedo_map, perm.has_metallic_roughness_map,
220 perm.has_normal_map, perm.has_occlusion_map, perm.has_emissive_map); 219 perm.has_normal_map, perm.has_occlusion_map, perm.has_emissive_map,
220 perm.has_transparency);
221 221
222 ShaderCompilerDefine defines[GFX_MAX_SHADER_COMPILER_DEFINES]; 222 ShaderCompilerDefine defines[GFX_MAX_SHADER_COMPILER_DEFINES];
223 const size_t num_defines = make_defines(perm, defines); 223 const size_t num_defines = make_defines(perm, defines);
@@ -578,11 +578,10 @@ static bool load_buffers(
578 const cgltf_buffer* buffer = &data->buffers[i]; 578 const cgltf_buffer* buffer = &data->buffers[i];
579 assert(buffer->data); 579 assert(buffer->data);
580 buffers[i] = gfx_make_buffer( 580 buffers[i] = gfx_make_buffer(
581 gfxcore, &(BufferDesc){ 581 gfxcore, &(BufferDesc){.usage = BufferStatic,
582 .usage = BufferStatic, 582 .type = BufferUntyped,
583 .type = BufferUntyped, 583 .data.data = buffer->data,
584 .data.data = buffer->data, 584 .data.count = buffer->size});
585 .data.count = buffer->size});
586 if (!buffers[i]) { 585 if (!buffers[i]) {
587 return false; 586 return false;
588 } 587 }
@@ -604,11 +603,10 @@ static bool load_tangent_buffers(
604 const cgltfTangentBuffer* buffer = &cgltf_tangent_buffers[i]; 603 const cgltfTangentBuffer* buffer = &cgltf_tangent_buffers[i];
605 assert(buffer->data); 604 assert(buffer->data);
606 tangent_buffers[i] = gfx_make_buffer( 605 tangent_buffers[i] = gfx_make_buffer(
607 gfxcore, &(BufferDesc){ 606 gfxcore, &(BufferDesc){.usage = BufferStatic,
608 .usage = BufferStatic, 607 .type = BufferUntyped,
609 .type = BufferUntyped, 608 .data.data = buffer->data,
610 .data.data = buffer->data, 609 .data.count = buffer->size_bytes});
611 .data.count = buffer->size_bytes});
612 if (!tangent_buffers[i]) { 610 if (!tangent_buffers[i]) {
613 return false; 611 return false;
614 } 612 }
@@ -682,14 +680,13 @@ static void load_textures_lazy(
682 mstring fullpath = 680 mstring fullpath =
683 mstring_concat_path(mstring_make(directory), mstring_make(image->uri)); 681 mstring_concat_path(mstring_make(directory), mstring_make(image->uri));
684 682
685 load_texture_cmds[i] = (LoadTextureCmd){ 683 load_texture_cmds[i] = (LoadTextureCmd){.origin = AssetFromFile,
686 .origin = AssetFromFile, 684 .type = LoadTexture,
687 .type = LoadTexture, 685 .colour_space = LinearColourSpace,
688 .colour_space = sRGB, 686 .filtering = filtering,
689 .filtering = filtering, 687 .wrap = wrap,
690 .wrap = wrap, 688 .mipmaps = mipmaps,
691 .mipmaps = mipmaps, 689 .data.texture.filepath = fullpath};
692 .data.texture.filepath = fullpath};
693 } 690 }
694} 691}
695 692
@@ -717,8 +714,12 @@ static bool load_texture_and_uniform(
717 // not be used as albedo and vice versa. 714 // not be used as albedo and vice versa.
718 if (!textures[texture_index]) { 715 if (!textures[texture_index]) {
719 LoadTextureCmd* cmd = &load_texture_cmds[texture_index]; 716 LoadTextureCmd* cmd = &load_texture_cmds[texture_index];
720 // TODO: Check for colour textures and default to LinearColourSpace instead. 717 // Albedo and emissive use sRGB. Other textures use a linear colour space.
721 if (texture_type == NormalMap) { 718 // See the notes on the spec.
719 if ((texture_type == BaseColorTexture) ||
720 (texture_type == EmissiveTexture)) {
721 cmd->colour_space = sRGB;
722 } else {
722 cmd->colour_space = LinearColourSpace; 723 cmd->colour_space = LinearColourSpace;
723 } 724 }
724 725
@@ -745,6 +746,19 @@ static bool load_texture_and_uniform(
745 return true; 746 return true;
746} 747}
747 748
749static AlphaMode to_gfx_alpha_mode(cgltf_alpha_mode mode) {
750 switch (mode) {
751 case cgltf_alpha_mode_opaque:
752 return Opaque;
753 case cgltf_alpha_mode_mask:
754 return Mask;
755 case cgltf_alpha_mode_blend:
756 return Blend;
757 }
758 FAIL("unhandled alpha mode");
759 return Opaque;
760}
761
748/// Load all materials from the glTF scene. 762/// Load all materials from the glTF scene.
749/// 763///
750/// Return an array of Materials such that the index of each descriptor matches 764/// Return an array of Materials such that the index of each descriptor matches
@@ -773,27 +787,27 @@ static bool load_materials(
773 787
774 assert(next_uniform < GFX_MAX_UNIFORMS_PER_MATERIAL); 788 assert(next_uniform < GFX_MAX_UNIFORMS_PER_MATERIAL);
775 desc.uniforms[next_uniform++] = (ShaderUniform){ 789 desc.uniforms[next_uniform++] = (ShaderUniform){
776 .name = sstring_make(UNIFORM_BASE_COLOR_FACTOR), 790 .name = sstring_make(UNIFORM_BASE_COLOR_FACTOR),
777 .type = UniformVec4, 791 .type = UniformVec4,
778 .value.vec4 = vec4_from_array(pbr->base_color_factor)}; 792 .value.uniform_vec4 = vec4_from_array(pbr->base_color_factor)};
779 793
780 assert(next_uniform < GFX_MAX_UNIFORMS_PER_MATERIAL); 794 assert(next_uniform < GFX_MAX_UNIFORMS_PER_MATERIAL);
781 desc.uniforms[next_uniform++] = (ShaderUniform){ 795 desc.uniforms[next_uniform++] =
782 .name = sstring_make(UNIFORM_METALLIC_FACTOR), 796 (ShaderUniform){.name = sstring_make(UNIFORM_METALLIC_FACTOR),
783 .type = UniformFloat, 797 .type = UniformFloat,
784 .value.scalar = pbr->metallic_factor}; 798 .value.uniform_float = pbr->metallic_factor};
785 799
786 assert(next_uniform < GFX_MAX_UNIFORMS_PER_MATERIAL); 800 assert(next_uniform < GFX_MAX_UNIFORMS_PER_MATERIAL);
787 desc.uniforms[next_uniform++] = (ShaderUniform){ 801 desc.uniforms[next_uniform++] =
788 .name = sstring_make(UNIFORM_ROUGHNESS_FACTOR), 802 (ShaderUniform){.name = sstring_make(UNIFORM_ROUGHNESS_FACTOR),
789 .type = UniformFloat, 803 .type = UniformFloat,
790 .value.scalar = pbr->roughness_factor}; 804 .value.uniform_float = pbr->roughness_factor};
791 805
792 assert(next_uniform < GFX_MAX_UNIFORMS_PER_MATERIAL); 806 assert(next_uniform < GFX_MAX_UNIFORMS_PER_MATERIAL);
793 desc.uniforms[next_uniform++] = (ShaderUniform){ 807 desc.uniforms[next_uniform++] = (ShaderUniform){
794 .name = sstring_make(UNIFORM_EMISSIVE_FACTOR), 808 .name = sstring_make(UNIFORM_EMISSIVE_FACTOR),
795 .type = UniformVec3, 809 .type = UniformVec3,
796 .value.vec3 = vec3_from_array(mat->emissive_factor)}; 810 .value.uniform_vec3 = vec3_from_array(mat->emissive_factor)};
797 811
798 if (pbr->base_color_texture.texture) { 812 if (pbr->base_color_texture.texture) {
799 if (!load_texture_and_uniform( 813 if (!load_texture_and_uniform(
@@ -840,6 +854,9 @@ static bool load_materials(
840 assert(next_uniform < GFX_MAX_UNIFORMS_PER_MATERIAL); 854 assert(next_uniform < GFX_MAX_UNIFORMS_PER_MATERIAL);
841 desc.num_uniforms = next_uniform; 855 desc.num_uniforms = next_uniform;
842 856
857 desc.alpha_mode = to_gfx_alpha_mode(mat->alpha_mode);
858 desc.alpha_cutoff = mat->alpha_cutoff;
859
843 materials[i] = gfx_make_material(&desc); 860 materials[i] = gfx_make_material(&desc);
844 if (!materials[i]) { 861 if (!materials[i]) {
845 return false; 862 return false;
@@ -854,28 +871,28 @@ static Material* make_default_material() {
854 MaterialDesc desc = (MaterialDesc){0}; 871 MaterialDesc desc = (MaterialDesc){0};
855 872
856 assert(desc.num_uniforms < GFX_MAX_UNIFORMS_PER_MATERIAL); 873 assert(desc.num_uniforms < GFX_MAX_UNIFORMS_PER_MATERIAL);
857 desc.uniforms[desc.num_uniforms++] = (ShaderUniform){ 874 desc.uniforms[desc.num_uniforms++] =
858 .name = sstring_make(UNIFORM_BASE_COLOR_FACTOR), 875 (ShaderUniform){.name = sstring_make(UNIFORM_BASE_COLOR_FACTOR),
859 .type = UniformVec4, 876 .type = UniformVec4,
860 .value.vec4 = vec4_make(1, 1, 1, 1)}; 877 .value.uniform_vec4 = vec4_make(1, 1, 1, 1)};
861 878
862 assert(desc.num_uniforms < GFX_MAX_UNIFORMS_PER_MATERIAL); 879 assert(desc.num_uniforms < GFX_MAX_UNIFORMS_PER_MATERIAL);
863 desc.uniforms[desc.num_uniforms++] = (ShaderUniform){ 880 desc.uniforms[desc.num_uniforms++] =
864 .name = sstring_make(UNIFORM_METALLIC_FACTOR), 881 (ShaderUniform){.name = sstring_make(UNIFORM_METALLIC_FACTOR),
865 .type = UniformFloat, 882 .type = UniformFloat,
866 .value.scalar = 0}; 883 .value.uniform_float = 0};
867 884
868 assert(desc.num_uniforms < GFX_MAX_UNIFORMS_PER_MATERIAL); 885 assert(desc.num_uniforms < GFX_MAX_UNIFORMS_PER_MATERIAL);
869 desc.uniforms[desc.num_uniforms++] = (ShaderUniform){ 886 desc.uniforms[desc.num_uniforms++] =
870 .name = sstring_make(UNIFORM_ROUGHNESS_FACTOR), 887 (ShaderUniform){.name = sstring_make(UNIFORM_ROUGHNESS_FACTOR),
871 .type = UniformFloat, 888 .type = UniformFloat,
872 .value.scalar = 1}; 889 .value.uniform_float = 1};
873 890
874 assert(desc.num_uniforms < GFX_MAX_UNIFORMS_PER_MATERIAL); 891 assert(desc.num_uniforms < GFX_MAX_UNIFORMS_PER_MATERIAL);
875 desc.uniforms[desc.num_uniforms++] = (ShaderUniform){ 892 desc.uniforms[desc.num_uniforms++] =
876 .name = sstring_make(UNIFORM_EMISSIVE_FACTOR), 893 (ShaderUniform){.name = sstring_make(UNIFORM_EMISSIVE_FACTOR),
877 .type = UniformVec3, 894 .type = UniformVec3,
878 .value.vec3 = vec3_make(0, 0, 0)}; 895 .value.uniform_vec3 = vec3_make(0, 0, 0)};
879 896
880 return gfx_make_material(&desc); 897 return gfx_make_material(&desc);
881} 898}
@@ -953,6 +970,7 @@ static bool load_meshes(
953 perm.has_normal_map = mat->normal_texture.texture != 0; 970 perm.has_normal_map = mat->normal_texture.texture != 0;
954 perm.has_occlusion_map = mat->occlusion_texture.texture != 0; 971 perm.has_occlusion_map = mat->occlusion_texture.texture != 0;
955 perm.has_emissive_map = mat->emissive_texture.texture != 0; 972 perm.has_emissive_map = mat->emissive_texture.texture != 0;
973 perm.has_transparency = mat->alpha_mode != cgltf_alpha_mode_opaque;
956 974
957 if (mat->has_pbr_metallic_roughness) { 975 if (mat->has_pbr_metallic_roughness) {
958 const cgltf_pbr_metallic_roughness* pbr = 976 const cgltf_pbr_metallic_roughness* pbr =
@@ -1012,7 +1030,6 @@ static bool load_meshes(
1012 const cgltf_attribute* attrib = &prim->attributes[a]; 1030 const cgltf_attribute* attrib = &prim->attributes[a];
1013 const cgltf_accessor* accessor = attrib->data; 1031 const cgltf_accessor* accessor = attrib->data;
1014 const cgltf_buffer_view* view = accessor->buffer_view; 1032 const cgltf_buffer_view* view = accessor->buffer_view;
1015 const cgltf_size offset = accessor->offset + view->offset;
1016 const cgltf_size buffer_index = view->buffer - data->buffers; 1033 const cgltf_size buffer_index = view->buffer - data->buffers;
1017 1034
1018 assert(buffer_index < data->buffers_count); 1035 assert(buffer_index < data->buffers_count);
@@ -1102,12 +1119,20 @@ static bool load_meshes(
1102 break; 1119 break;
1103 } 1120 }
1104 1121
1105#define CONFIGURE_BUFFER(buf) \ 1122 // See comments here for accessor/view/buffer invariants:
1106 if (buf) { \ 1123 // https://github.com/KhronosGroup/glTF-Sample-Assets/issues/242
1107 buf->buffer = buffer; \ 1124 // Gfx only has Buffer and BufferView, not accessors. We must combine
1108 buf->offset_bytes = offset; \ 1125 // the glTF's accessor and view offsets correctly.
1109 buf->size_bytes = view->size; \ 1126 const cgltf_size offset = accessor->offset + view->offset;
1110 buf->stride_bytes = view->stride; \ 1127 const cgltf_size size_bytes = view->size - accessor->offset;
1128
1129#define CONFIGURE_BUFFER(buf) \
1130 if (buf) { \
1131 buf->buffer = buffer; \
1132 buf->offset_bytes = offset; \
1133 buf->size_bytes = size_bytes; \
1134 buf->stride_bytes = view->stride; \
1135 buf->count = accessor->count; \
1111 } 1136 }
1112 CONFIGURE_BUFFER(buffer_view_2d); 1137 CONFIGURE_BUFFER(buffer_view_2d);
1113 CONFIGURE_BUFFER(buffer_view_3d); 1138 CONFIGURE_BUFFER(buffer_view_3d);
@@ -1142,14 +1167,11 @@ static bool load_meshes(
1142 // either 2d or 3d positions but not both, here we can perform addition 1167 // either 2d or 3d positions but not both, here we can perform addition
1143 // to compute the total number of vertices. 1168 // to compute the total number of vertices.
1144 geometry_desc.num_verts = 1169 geometry_desc.num_verts =
1145 (geometry_desc.positions2d.size_bytes / sizeof(vec2)) + 1170 geometry_desc.positions2d.count + geometry_desc.positions3d.count;
1146 (geometry_desc.positions3d.size_bytes / sizeof(vec3)); 1171
1147 1172#define CHECK_COUNT(buffer_view, type, num_components) \
1148#define CHECK_COUNT(buffer_view, type, num_components) \ 1173 if (geometry_desc.buffer_view.buffer) { \
1149 if (geometry_desc.buffer_view.buffer) { \ 1174 assert(geometry_desc.buffer_view.count == geometry_desc.num_verts); \
1150 assert( \
1151 (geometry_desc.buffer_view.size_bytes / \
1152 (num_components * sizeof(type))) == geometry_desc.num_verts); \
1153 } 1175 }
1154 1176
1155 // Check that the number of vertices is consistent across all vertex 1177 // Check that the number of vertices is consistent across all vertex
@@ -1192,10 +1214,10 @@ static bool load_meshes(
1192 shader ? shader : make_shader_permutation(gfxcore, perm); 1214 shader ? shader : make_shader_permutation(gfxcore, perm);
1193 assert(mesh_shader); 1215 assert(mesh_shader);
1194 1216
1195 meshes[next_mesh] = gfx_make_mesh(&(MeshDesc){ 1217 meshes[next_mesh] =
1196 .geometry = geometries[next_mesh], 1218 gfx_make_mesh(&(MeshDesc){.geometry = geometries[next_mesh],
1197 .material = material, 1219 .material = material,
1198 .shader = mesh_shader}); 1220 .shader = mesh_shader});
1199 1221
1200 if (!meshes[next_mesh]) { 1222 if (!meshes[next_mesh]) {
1201 return false; 1223 return false;
@@ -1432,9 +1454,9 @@ static void load_animations(
1432 const cgltf_animation* animation = &data->animations[a]; 1454 const cgltf_animation* animation = &data->animations[a];
1433 AnimationDesc* animation_desc = &anima_desc->animations[a]; 1455 AnimationDesc* animation_desc = &anima_desc->animations[a];
1434 1456
1435 *animation_desc = (AnimationDesc){ 1457 *animation_desc =
1436 .name = sstring_make(animation->name), 1458 (AnimationDesc){.name = sstring_make(animation->name),
1437 .num_channels = animation->channels_count}; 1459 .num_channels = animation->channels_count};
1438 1460
1439 assert(animation->channels_count <= GFX_MAX_NUM_CHANNELS); 1461 assert(animation->channels_count <= GFX_MAX_NUM_CHANNELS);
1440 for (cgltf_size c = 0; c < animation->channels_count; ++c) { 1462 for (cgltf_size c = 0; c < animation->channels_count; ++c) {
@@ -1490,15 +1512,75 @@ static void load_animations(
1490 } 1512 }
1491} 1513}
1492 1514
1515/// Remove joint nodes from the Gfx Scene.
1516///
1517/// Joint nodes are not needed because joints are packed into the Anima.
1518static void remove_joint_nodes(
1519 const cgltf_data* data, SceneNode** scene_nodes) {
1520 assert(data);
1521 assert(scene_nodes);
1522
1523 // This works assuming the joint nodes are contiguous. Contiguity is checked
1524 // when loading skins. See load_skins().
1525 size_t min_joint_index = (size_t)-1;
1526 size_t max_joint_index = 0;
1527
1528 // First get the minimum and maximum indices of all joint nodes.
1529 for (cgltf_size s = 0; s < data->skins_count; ++s) {
1530 const cgltf_skin* skin = &data->skins[s];
1531
1532 for (cgltf_size j = 0; j < skin->joints_count; ++j) {
1533 // Joint is an index/pointer into the nodes array.
1534 const cgltf_size joint_index = skin->joints[j] - data->nodes;
1535 assert(joint_index < data->nodes_count);
1536
1537 if (joint_index < min_joint_index) {
1538 min_joint_index = joint_index;
1539 }
1540 if (joint_index > max_joint_index) {
1541 max_joint_index = joint_index;
1542 }
1543 }
1544 }
1545
1546 assert(min_joint_index < data->nodes_count);
1547 assert(max_joint_index < data->nodes_count);
1548
1549 // Now walk over the joint nodes. If a joint's parent is itself not a joint
1550 // node, then that joint is a root of a joint hierarchy (skins in glTF may
1551 // have multiple roots). In such case, delete the root joint recursively.
1552 for (cgltf_size s = 0; s < data->skins_count; ++s) {
1553 const cgltf_skin* skin = &data->skins[s];
1554
1555 for (cgltf_size j = 0; j < skin->joints_count; ++j) {
1556 // Joint is an index/pointer into the nodes array.
1557 const cgltf_size joint_index = skin->joints[j] - data->nodes;
1558 assert(joint_index < data->nodes_count);
1559
1560 const cgltf_node* joint = &data->nodes[joint_index];
1561
1562 // Parent node index.
1563 const cgltf_size parent_index = joint->parent - data->nodes;
1564 assert(parent_index < data->nodes_count);
1565
1566 // If the parent is not a joint node, recursively delete this joint node.
1567 if ((parent_index < min_joint_index) ||
1568 (parent_index > max_joint_index)) {
1569 gfx_destroy_node(&scene_nodes[joint_index]);
1570 }
1571 }
1572 }
1573}
1574
1493/// Load all nodes from the glTF scene. 1575/// Load all nodes from the glTF scene.
1494/// 1576///
1495/// This function ignores the many scenes and default scene of the glTF spec 1577/// This function ignores the many scenes and default scene of the glTF spec
1496/// and instead just loads all nodes into a single gfx Scene. 1578/// and instead just loads all nodes into a single gfx Scene.
1497static void load_nodes( 1579static void load_nodes(
1498 const cgltf_data* data, SceneNode* root_node, SceneObject** objects, 1580 const cgltf_data* data, SceneNode* root_node, SceneObject** objects,
1499 SceneCamera** cameras, const Anima* anima, SceneNode** nodes) { 1581 const Anima* anima, SceneNode** nodes) {
1500 // Note that with glTF 2.0, nodes do not form a DAG / scene graph but a 1582 // Note that with glTF 2.0, nodes do not form a DAG / scene graph but a
1501 // disjount union of strict trees: 1583 // disjoint union of strict trees:
1502 // 1584 //
1503 // "For Version 2.0 conformance, the glTF node hierarchy is not a directed 1585 // "For Version 2.0 conformance, the glTF node hierarchy is not a directed
1504 // acyclic graph (DAG) or scene graph, but a disjoint union of strict trees. 1586 // acyclic graph (DAG) or scene graph, but a disjoint union of strict trees.
@@ -1511,25 +1593,25 @@ static void load_nodes(
1511 assert(data); 1593 assert(data);
1512 assert(root_node); 1594 assert(root_node);
1513 assert(objects); 1595 assert(objects);
1514 assert(cameras);
1515 assert(nodes); 1596 assert(nodes);
1516 1597
1517 cgltf_size next_camera = 0; 1598 cgltf_size next_camera = 0;
1518 1599
1600 // First pass: create the nodes.
1519 for (cgltf_size n = 0; n < data->nodes_count; ++n) { 1601 for (cgltf_size n = 0; n < data->nodes_count; ++n) {
1520 const cgltf_node* node = &data->nodes[n]; 1602 const cgltf_node* node = &data->nodes[n];
1521 1603
1522 // Add SceneObject, SceneCamera or Lights. 1604 // Add SceneObject, Camera or Lights.
1523 // TODO: Handle lights once they are implemented in the gfx library. 1605 // TODO: Handle lights once they are implemented in the gfx library.
1524 if (node->mesh) { 1606 if (node->mesh) {
1525 const cgltf_size mesh_index = node->mesh - data->meshes; 1607 const cgltf_size mesh_index = node->mesh - data->meshes;
1526 assert(mesh_index < data->meshes_count); 1608 assert(mesh_index < data->meshes_count);
1527 SceneObject* object = objects[mesh_index]; 1609 SceneObject* object = objects[mesh_index];
1528 gfx_construct_object_node(nodes[n], object); 1610
1611 nodes[n] = gfx_make_object_node(object);
1529 1612
1530 if (node->skin) { 1613 if (node->skin) {
1531 assert(anima); 1614 assert(anima);
1532
1533 const cgltf_size skin_index = node->skin - data->skins; 1615 const cgltf_size skin_index = node->skin - data->skins;
1534 assert(skin_index < data->skins_count); 1616 assert(skin_index < data->skins_count);
1535 const Skeleton* skeleton = gfx_get_anima_skeleton(anima, skin_index); 1617 const Skeleton* skeleton = gfx_get_anima_skeleton(anima, skin_index);
@@ -1538,33 +1620,32 @@ static void load_nodes(
1538 } else if (node->camera) { 1620 } else if (node->camera) {
1539 assert(next_camera < data->cameras_count); 1621 assert(next_camera < data->cameras_count);
1540 1622
1541 Camera camera;
1542 const cgltf_camera* cam = node->camera; 1623 const cgltf_camera* cam = node->camera;
1543 1624
1544 // TODO: We could define a function load_cameras() the same way we load 1625 // TODO: We could define a function load_cameras() the same way we load
1545 // every mesh and then remove this ad-hoc loading of cameras here, as well 1626 // every mesh and then remove this ad-hoc loading of cameras here, as well
1546 // as remove 'next_camera'. 1627 // as remove 'next_camera'.
1628 Camera* camera = gfx_make_camera();
1547 switch (cam->type) { 1629 switch (cam->type) {
1548 case cgltf_camera_type_orthographic: 1630 case cgltf_camera_type_orthographic:
1549 camera = camera_orthographic( 1631 *camera = camera_orthographic(
1550 0, cam->data.orthographic.xmag, 0, cam->data.orthographic.ymag, 1632 0, cam->data.orthographic.xmag, 0, cam->data.orthographic.ymag,
1551 cam->data.orthographic.znear, cam->data.orthographic.zfar); 1633 cam->data.orthographic.znear, cam->data.orthographic.zfar);
1552 break; 1634 break;
1553 case cgltf_camera_type_perspective: 1635 case cgltf_camera_type_perspective:
1554 camera = camera_perspective( 1636 *camera = camera_perspective(
1555 cam->data.perspective.yfov, cam->data.perspective.aspect_ratio, 1637 cam->data.perspective.yfov, cam->data.perspective.aspect_ratio,
1556 cam->data.perspective.znear, cam->data.perspective.zfar); 1638 cam->data.perspective.znear, cam->data.perspective.zfar);
1557 break; 1639 break;
1558 case cgltf_camera_type_invalid: 1640 case cgltf_camera_type_invalid:
1559 break; 1641 break;
1560 } 1642 }
1561 1643 nodes[n] = gfx_make_camera_node(camera);
1562 gfx_set_camera_camera(cameras[next_camera], &camera);
1563 gfx_construct_camera_node(nodes[n], cameras[next_camera]);
1564 ++next_camera; 1644 ++next_camera;
1565 } else { 1645 } else {
1566 // TODO: implementation for missing node types. 1646 // TODO: implementation for missing node types.
1567 // These nodes currently default to logical nodes. 1647 // These nodes currently default to logical nodes.
1648 nodes[n] = gfx_make_node();
1568 } 1649 }
1569 assert(nodes[n]); 1650 assert(nodes[n]);
1570 1651
@@ -1590,6 +1671,11 @@ static void load_nodes(
1590 } 1671 }
1591 } 1672 }
1592 gfx_set_node_transform(nodes[n], &transform); 1673 gfx_set_node_transform(nodes[n], &transform);
1674 }
1675
1676 // Second pass: wire up the node hierarchy.
1677 for (cgltf_size n = 0; n < data->nodes_count; ++n) {
1678 const cgltf_node* node = &data->nodes[n];
1593 1679
1594 // If this is a top-level node in the glTF scene, set its parent to the 1680 // If this is a top-level node in the glTF scene, set its parent to the
1595 // given root node. 1681 // given root node.
@@ -1602,66 +1688,12 @@ static void load_nodes(
1602 assert(parent); 1688 assert(parent);
1603 gfx_set_node_parent(nodes[n], parent); 1689 gfx_set_node_parent(nodes[n], parent);
1604 } 1690 }
1605 } // SceneNode.
1606}
1607
1608/// Remove joint nodes from the Gfx Scene.
1609///
1610/// Joint nodes are not needed because joints are packed into the Anima.
1611static void remove_joint_nodes(
1612 const cgltf_data* data, SceneNode** scene_nodes) {
1613 assert(data);
1614 assert(scene_nodes);
1615
1616 // This works assuming the joint nodes are contiguous. Contiguity is checked
1617 // when loading skins. See load_skins().
1618 size_t min_joint_index = (size_t)-1;
1619 size_t max_joint_index = 0;
1620
1621 // First get the minimum and maximum indices of all joint nodes.
1622 for (cgltf_size s = 0; s < data->skins_count; ++s) {
1623 const cgltf_skin* skin = &data->skins[s];
1624
1625 for (cgltf_size j = 0; j < skin->joints_count; ++j) {
1626 // Joint is an index/pointer into the nodes array.
1627 const cgltf_size joint_index = skin->joints[j] - data->nodes;
1628 assert(joint_index < data->nodes_count);
1629
1630 if (joint_index < min_joint_index) {
1631 min_joint_index = joint_index;
1632 }
1633 if (joint_index > max_joint_index) {
1634 max_joint_index = joint_index;
1635 }
1636 }
1637 } 1691 }
1638 1692
1639 assert(min_joint_index < data->nodes_count); 1693 // Clean up scene nodes that correspond to joints in the glTF. These are not
1640 assert(max_joint_index < data->nodes_count); 1694 // needed anymore.
1641 1695 if (data->skins_count > 0) {
1642 // Now walk over the joint nodes. If a joint's parent is itself not a joint 1696 remove_joint_nodes(data, nodes);
1643 // node, then that joint is a root of a joint hierarchy (skins in glTF may
1644 // have multiple roots). In such case, delete the root joint recursively.
1645 for (cgltf_size s = 0; s < data->skins_count; ++s) {
1646 const cgltf_skin* skin = &data->skins[s];
1647
1648 for (cgltf_size j = 0; j < skin->joints_count; ++j) {
1649 // Joint is an index/pointer into the nodes array.
1650 const cgltf_size joint_index = skin->joints[j] - data->nodes;
1651 assert(joint_index < data->nodes_count);
1652
1653 const cgltf_node* joint = &data->nodes[joint_index];
1654
1655 // Parent node index.
1656 const cgltf_size parent_index = joint->parent - data->nodes;
1657 assert(parent_index < data->nodes_count);
1658
1659 // If the parent is not a joint node, recursively delete this joint node.
1660 if ((parent_index < min_joint_index) ||
1661 (parent_index > max_joint_index)) {
1662 gfx_destroy_node(&scene_nodes[joint_index]);
1663 }
1664 }
1665 } 1697 }
1666} 1698}
1667 1699
@@ -1705,42 +1737,21 @@ static Model* load_scene(
1705 LOGD("Filepath: %s", mstring_cstr(filepath)); 1737 LOGD("Filepath: %s", mstring_cstr(filepath));
1706 LOGD("Directory: %s", mstring_cstr(&directory)); 1738 LOGD("Directory: %s", mstring_cstr(&directory));
1707 1739
1708 Buffer** tangent_buffers = 0;
1709 Buffer** buffers = 0;
1710 LoadTextureCmd* load_texture_cmds = 0;
1711 const Texture** textures = 0; // Textures are owned by asset cache.
1712 Material** materials = 0;
1713 Geometry** geometries = 0;
1714 Mesh** meshes = 0;
1715 AnimaDesc* anima_desc = 0;
1716 SceneObject** scene_objects = 0;
1717 SceneCamera** scene_cameras = 0;
1718 SceneNode** scene_nodes = 0;
1719 Anima* anima = 0;
1720 SceneNode* root_node = 0;
1721 Model* model = 0;
1722
1723 tangent_buffers = calloc(num_tangent_buffers, sizeof(Buffer*));
1724 buffers = calloc(data->buffers_count, sizeof(Buffer*));
1725 textures = calloc(data->textures_count, sizeof(Texture*));
1726 materials = calloc(data->materials_count, sizeof(Material*));
1727 geometries = calloc(primitive_count, sizeof(Geometry*));
1728 meshes = calloc(primitive_count, sizeof(Mesh*));
1729 scene_objects = calloc(data->meshes_count, sizeof(SceneObject*));
1730 scene_cameras = calloc(data->cameras_count, sizeof(SceneCamera**));
1731 scene_nodes = calloc(data->nodes_count, sizeof(SceneNode**));
1732 // A glTF scene does not necessarily have textures. Materials can be given 1740 // A glTF scene does not necessarily have textures. Materials can be given
1733 // as constants, for example. 1741 // as constants, for example.
1734 if (data->textures_count > 0) { 1742 // gfx textures are owned by asset cache.
1735 load_texture_cmds = calloc(data->textures_count, sizeof(LoadTextureCmd)); 1743 Buffer* tangent_buffers[num_tangent_buffers];
1736 } 1744 Buffer* buffers[data->buffers_count];
1737 1745 LoadTextureCmd load_texture_cmds[data->textures_count];
1738 if (!buffers || !tangent_buffers || 1746 const Texture* textures[data->textures_count];
1739 ((data->textures_count > 0) && !load_texture_cmds) || !textures || 1747 Material* materials[data->materials_count];
1740 !materials || !geometries || !meshes || !scene_objects || 1748 Geometry* geometries[primitive_count];
1741 !scene_cameras || !scene_nodes) { 1749 Mesh* meshes[primitive_count];
1742 goto cleanup; 1750 SceneObject* scene_objects[data->meshes_count];
1743 } 1751 SceneNode* scene_nodes[data->nodes_count];
1752 Anima* anima = 0;
1753 SceneNode* root_node = 0;
1754 Model* model = 0;
1744 1755
1745 if ((num_tangent_buffers > 0) && 1756 if ((num_tangent_buffers > 0) &&
1746 !load_tangent_buffers( 1757 !load_tangent_buffers(
@@ -1769,47 +1780,30 @@ static Model* load_scene(
1769 goto cleanup; 1780 goto cleanup;
1770 } 1781 }
1771 1782
1772 // Skins refer to nodes, and nodes may refer to skins. To break this circular
1773 // dependency, glTF defines skins in terms of node indices. We could do the
1774 // same if Gfx allowed allocating nodes contiguously in memory. For now,
1775 // create the nodes up front and use the indices of the array to map to the
1776 // node_idx.
1777 for (cgltf_size i = 0; i < data->nodes_count; ++i) {
1778 scene_nodes[i] = gfx_make_node();
1779 }
1780
1781 // Create the scene's root node. 1783 // Create the scene's root node.
1782 // This is an anima node if the scene has skins; otherwise it is a logical 1784 // This is an anima node if the scene has skins; otherwise it is a logical
1783 // node. 1785 // node.
1784 root_node = gfx_make_node();
1785 if (data->skins_count > 0) { 1786 if (data->skins_count > 0) {
1786 anima_desc = calloc(1, sizeof(AnimaDesc));
1787 if (!anima_desc) {
1788 goto cleanup;
1789 }
1790
1791 const cgltf_size base = find_base_joint_index(data); 1787 const cgltf_size base = find_base_joint_index(data);
1792 1788
1793 anima_desc->num_skeletons = data->skins_count; 1789 AnimaDesc anima_desc =
1794 anima_desc->num_animations = data->animations_count; 1790 (AnimaDesc){.num_skeletons = data->skins_count,
1795 anima_desc->num_joints = load_skins(data, buffers, base, anima_desc); 1791 .num_animations = data->animations_count,
1796 load_animations(data, base, anima_desc); 1792 .num_joints = load_skins(data, buffers, base, &anima_desc)};
1793 load_animations(data, base, &anima_desc);
1797 1794
1798 compute_joint_bounding_boxes( 1795 compute_joint_bounding_boxes(
1799 data, anima_desc->num_joints, anima_desc->joints); 1796 data, anima_desc.num_joints, anima_desc.joints);
1800 1797
1801 anima = gfx_make_anima(anima_desc); 1798 anima = gfx_make_anima(&anima_desc);
1802 gfx_construct_anima_node(root_node, anima); 1799 root_node = gfx_make_anima_node(anima);
1800 } else {
1801 root_node = gfx_make_node();
1803 } 1802 }
1803 assert(root_node);
1804 1804
1805 // The root node becomes the root of all scene nodes. 1805 // The root node becomes the root of all scene nodes.
1806 load_nodes(data, root_node, scene_objects, scene_cameras, anima, scene_nodes); 1806 load_nodes(data, root_node, scene_objects, anima, scene_nodes);
1807
1808 // Clean up scene nodes that correspond to joints in the glTF. These are
1809 // not needed anymore.
1810 if (data->skins_count > 0) {
1811 remove_joint_nodes(data, scene_nodes);
1812 }
1813 1807
1814 model = gfx_make_model(root_node); 1808 model = gfx_make_model(root_node);
1815 1809
@@ -1818,96 +1812,42 @@ static Model* load_scene(
1818cleanup: 1812cleanup:
1819 // The arrays of resources are no longer needed. The resources themselves are 1813 // The arrays of resources are no longer needed. The resources themselves are
1820 // destroyed only if this function fails. 1814 // destroyed only if this function fails.
1821 if (tangent_buffers) { 1815 if (!success) {
1822 if (!success) { 1816 for (cgltf_size i = 0; i < num_tangent_buffers; ++i) {
1823 for (cgltf_size i = 0; i < num_tangent_buffers; ++i) { 1817 if (tangent_buffers[i]) {
1824 if (tangent_buffers[i]) { 1818 gfx_destroy_buffer(gfxcore, &tangent_buffers[i]);
1825 gfx_destroy_buffer(gfxcore, &tangent_buffers[i]);
1826 }
1827 }
1828 }
1829 free(tangent_buffers);
1830 }
1831 if (buffers) {
1832 if (!success) {
1833 for (cgltf_size i = 0; i < data->buffers_count; ++i) {
1834 if (buffers[i]) {
1835 gfx_destroy_buffer(gfxcore, &buffers[i]);
1836 }
1837 } 1819 }
1838 } 1820 }
1839 free(buffers); 1821 for (cgltf_size i = 0; i < data->buffers_count; ++i) {
1840 } 1822 if (buffers[i]) {
1841 if (load_texture_cmds) { 1823 gfx_destroy_buffer(gfxcore, &buffers[i]);
1842 free(load_texture_cmds);
1843 }
1844 if (textures) {
1845 free(textures);
1846 }
1847 if (materials) {
1848 if (!success) {
1849 for (cgltf_size i = 0; i < data->materials_count; ++i) {
1850 if (materials[i]) {
1851 gfx_destroy_material(&materials[i]);
1852 }
1853 } 1824 }
1854 } 1825 }
1855 free(materials); 1826 for (cgltf_size i = 0; i < data->materials_count; ++i) {
1856 } 1827 if (materials[i]) {
1857 if (geometries) { 1828 gfx_destroy_material(&materials[i]);
1858 if (!success) {
1859 for (size_t i = 0; i < primitive_count; ++i) {
1860 if (geometries[i]) {
1861 gfx_destroy_geometry(gfxcore, &geometries[i]);
1862 }
1863 } 1829 }
1864 } 1830 }
1865 free(geometries); 1831 for (size_t i = 0; i < primitive_count; ++i) {
1866 } 1832 if (geometries[i]) {
1867 if (meshes) { 1833 gfx_destroy_geometry(gfxcore, &geometries[i]);
1868 if (!success) {
1869 for (size_t i = 0; i < primitive_count; ++i) {
1870 if (meshes[i]) {
1871 gfx_destroy_mesh(&meshes[i]);
1872 }
1873 } 1834 }
1874 } 1835 }
1875 free(meshes); 1836 for (size_t i = 0; i < primitive_count; ++i) {
1876 } 1837 if (meshes[i]) {
1877 if (anima_desc) { 1838 gfx_destroy_mesh(&meshes[i]);
1878 free(anima_desc);
1879 }
1880 if (scene_objects) {
1881 if (!success) {
1882 for (cgltf_size i = 0; i < data->meshes_count; ++i) {
1883 if (scene_objects[i]) {
1884 gfx_destroy_object(&scene_objects[i]);
1885 }
1886 } 1839 }
1887 } 1840 }
1888 free(scene_objects); 1841 for (cgltf_size i = 0; i < data->meshes_count; ++i) {
1889 } 1842 if (scene_objects[i]) {
1890 if (scene_cameras) { 1843 gfx_destroy_object(&scene_objects[i]);
1891 if (!success) {
1892 for (cgltf_size i = 0; i < data->cameras_count; ++i) {
1893 if (scene_cameras[i]) {
1894 gfx_destroy_camera(&scene_cameras[i]);
1895 }
1896 } 1844 }
1897 } 1845 }
1898 free(scene_cameras); 1846 for (cgltf_size i = 0; i < data->nodes_count; ++i) {
1899 } 1847 if (scene_nodes[i]) {
1900 if (scene_nodes) { 1848 gfx_destroy_node(&scene_nodes[i]);
1901 if (!success) {
1902 for (cgltf_size i = 0; i < data->nodes_count; ++i) {
1903 if (scene_nodes[i]) {
1904 gfx_destroy_node(&scene_nodes[i]);
1905 }
1906 } 1849 }
1907 } 1850 }
1908 free(scene_nodes);
1909 }
1910 if (!success) {
1911 if (root_node) { 1851 if (root_node) {
1912 gfx_destroy_node(&root_node); // Node owns the anima. 1852 gfx_destroy_node(&root_node); // Node owns the anima.
1913 } else if (anima) { 1853 } else if (anima) {
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..1df225a 100644
--- a/gfx/src/core/buffer.h
+++ b/src/core/buffer.h
@@ -4,8 +4,6 @@
4 4
5#include "gl_util.h" 5#include "gl_util.h"
6 6
7#include <math/fwd.h>
8
9#include <stdbool.h> 7#include <stdbool.h>
10#include <stddef.h> 8#include <stddef.h>
11 9
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..9c04cc7 100644
--- a/gfx/src/core/core.c
+++ b/src/core/core.c
@@ -3,6 +3,7 @@
3#include "gl_util.h" 3#include "gl_util.h"
4 4
5// #include <log/log.h> 5// #include <log/log.h>
6#include <fnv1a.h>
6 7
7#include <assert.h> 8#include <assert.h>
8 9
@@ -282,26 +283,29 @@ void gfx_destroy_framebuffer(GfxCore* gfxcore, FrameBuffer** framebuffer) {
282// Shaders. 283// Shaders.
283// ----------------------------------------------------------------------------- 284// -----------------------------------------------------------------------------
284 285
285static uint64_t hash_shader_desc(const ShaderDesc* desc) { 286static hash_t hash_shader_desc(const ShaderDesc* desc) {
286 assert(desc); 287 assert(desc);
287 // Note that defines may affect shader permutations, so we need to hash those 288 // Defines may affect shader permutations, so we need to hash those as well.
288 // as well. 289 hash_t hash = fnv1a32_begin();
289 uint64_t hash = 0; 290 hash = fnv1a32_update(hash, desc->code, strlen(desc->code));
290 for (size_t i = 0; i < desc->num_defines; ++i) { 291 for (size_t i = 0; i < desc->num_defines; ++i) {
291 const ShaderCompilerDefine* define = &desc->defines[i]; 292 const ShaderCompilerDefine* define = &desc->defines[i];
292 hash = (((hash << 13) + sstring_hash(define->name)) << 7) + 293
293 sstring_hash(define->value); 294 hash = fnv1a32_update(
295 hash, sstring_cstr(&define->name), sstring_length(&define->name));
296 hash = fnv1a32_update(
297 hash, sstring_cstr(&define->value), sstring_length(&define->value));
294 } 298 }
295 return (hash << 17) + cstring_hash(desc->code); 299 return hash;
296} 300}
297 301
298static uint64_t hash_program_desc(const ShaderProgramDesc* desc) { 302static hash_t hash_program_desc(const ShaderProgramDesc* desc) {
299 assert(desc); 303 assert(desc);
300 return ((uint64_t)desc->vertex_shader->id << 32) | 304 return ((hash_t)desc->vertex_shader->id << 16) |
301 (uint64_t)desc->fragment_shader->id; 305 (hash_t)desc->fragment_shader->id;
302} 306}
303 307
304static Shader* find_cached_shader(ShaderCache* cache, uint64_t hash) { 308static Shader* find_cached_shader(ShaderCache* cache, hash_t hash) {
305 assert(cache); 309 assert(cache);
306 mempool_foreach(cache, entry, { 310 mempool_foreach(cache, entry, {
307 if (entry->hash == hash) { 311 if (entry->hash == hash) {
@@ -311,7 +315,7 @@ static Shader* find_cached_shader(ShaderCache* cache, uint64_t hash) {
311 return 0; 315 return 0;
312} 316}
313 317
314static ShaderProgram* find_cached_program(ProgramCache* cache, uint64_t hash) { 318static ShaderProgram* find_cached_program(ProgramCache* cache, hash_t hash) {
315 assert(cache); 319 assert(cache);
316 mempool_foreach(cache, entry, { 320 mempool_foreach(cache, entry, {
317 if (entry->hash == hash) { 321 if (entry->hash == hash) {
@@ -350,9 +354,9 @@ Shader* gfx_make_shader(GfxCore* gfxcore, const ShaderDesc* desc) {
350 assert(desc); 354 assert(desc);
351 355
352 // Check the shader cache first. 356 // Check the shader cache first.
353 ShaderCache* cache = &gfxcore->shader_cache; 357 ShaderCache* cache = &gfxcore->shader_cache;
354 const uint64_t hash = hash_shader_desc(desc); 358 const hash_t hash = hash_shader_desc(desc);
355 Shader* shader = find_cached_shader(cache, hash); 359 Shader* shader = find_cached_shader(cache, hash);
356 if (shader) { 360 if (shader) {
357 // LOGD("Found cached shader with hash [%lx]", hash); 361 // LOGD("Found cached shader with hash [%lx]", hash);
358 return shader; 362 return shader;
@@ -395,7 +399,7 @@ ShaderProgram* gfx_make_shader_program(
395 399
396 // Check the shader program cache first. 400 // Check the shader program cache first.
397 ProgramCache* cache = &gfxcore->program_cache; 401 ProgramCache* cache = &gfxcore->program_cache;
398 const uint64_t hash = hash_program_desc(desc); 402 const hash_t hash = hash_program_desc(desc);
399 ShaderProgram* prog = find_cached_program(cache, hash); 403 ShaderProgram* prog = find_cached_program(cache, hash);
400 if (prog) { 404 if (prog) {
401 // LOGD("Found cached shader program with hash [%lx]", hash); 405 // LOGD("Found cached shader program with hash [%lx]", hash);
@@ -420,6 +424,10 @@ void gfx_destroy_shader_program(GfxCore* gfxcore, ShaderProgram** prog) {
420 // Remove the shader program from the cache. 424 // Remove the shader program from the cache.
421 ProgramCache* cache = &gfxcore->program_cache; 425 ProgramCache* cache = &gfxcore->program_cache;
422 ShaderProgramCacheEntry* entry = find_program_cache_entry(cache, *prog); 426 ShaderProgramCacheEntry* entry = find_program_cache_entry(cache, *prog);
427 // TODO: The following assertion is too restrictive. Clients can end up
428 // re-using the same shader by virtue of the cache. The assertion assumes
429 // that no two "different" clients use the same set of shaders. This can
430 // be relaxed by reference-counting the shaders in the cache.
423 assert(entry); // Must be there, shaders can't go untracked. 431 assert(entry); // Must be there, shaders can't go untracked.
424 mempool_free(cache, &entry); 432 mempool_free(cache, &entry);
425 433
diff --git a/gfx/src/core/core_impl.h b/src/core/core_impl.h
index eefdfbe..320532d 100644
--- a/gfx/src/core/core_impl.h
+++ b/src/core/core_impl.h
@@ -15,16 +15,18 @@
15 15
16#include <stdint.h> 16#include <stdint.h>
17 17
18typedef uint32_t hash_t;
19
18// TODO: Make a generic (hash, void*) structure and define functions over it. 20// TODO: Make a generic (hash, void*) structure and define functions over it.
19// Then define a macro that defines type-safe macros given the type of the 21// Then define a macro that defines type-safe macros given the type of the
20// entry. 22// entry.
21typedef struct ShaderCacheEntry { 23typedef struct ShaderCacheEntry {
22 uint64_t hash; 24 hash_t hash;
23 Shader* shader; 25 Shader* shader;
24} ShaderCacheEntry; 26} ShaderCacheEntry;
25 27
26typedef struct ShaderProgramCacheEntry { 28typedef struct ShaderProgramCacheEntry {
27 uint64_t hash; 29 hash_t hash;
28 ShaderProgram* program; 30 ShaderProgram* program;
29} ShaderProgramCacheEntry; 31} ShaderProgramCacheEntry;
30 32
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..488dc23 100644
--- a/gfx/src/core/geometry.c
+++ b/src/core/geometry.c
@@ -12,7 +12,7 @@
12/// 12///
13/// Note that views are allowed to have no data, in which case a buffer of the 13/// Note that views are allowed to have no data, in which case a buffer of the
14/// specified size is created. 14/// specified size is created.
15#define view_is_populated(BUFFER_VIEW) (BUFFER_VIEW.size_bytes > 0) 15#define view_is_populated(BUFFER_VIEW) (BUFFER_VIEW.count > 0)
16 16
17static GLenum primitive_type_to_gl(PrimitiveType type) { 17static GLenum primitive_type_to_gl(PrimitiveType type) {
18 switch (type) { 18 switch (type) {
@@ -34,30 +34,25 @@ void init_view_buffer(
34 BufferUsage buffer_usage) { 34 BufferUsage buffer_usage) {
35 if (!view->buffer) { 35 if (!view->buffer) {
36 view->buffer = gfx_make_buffer( 36 view->buffer = gfx_make_buffer(
37 gfxcore, 37 gfxcore, &(BufferDesc){.usage = buffer_usage,
38 &(BufferDesc){ 38 .type = buffer_type,
39 .usage = buffer_usage, 39 .data.data = view->data,
40 .type = buffer_type, 40 .data.count = view->count});
41 .data.data = view->data, 41 assert(view->buffer);
42 .data.count = view->size_bytes /
43 gfx_get_buffer_type_size_bytes(buffer_type)});
44 } 42 }
45 assert(view->size_bytes <= view->buffer->size_bytes);
46} 43}
47 44
48/// Configure the buffer in teh VAO. 45/// Configure the buffer in the VAO.
49static void configure_buffer( 46static void configure_buffer(
50 GfxCore* gfxcore, const GeometryDesc* desc, BufferView* view, 47 GfxCore* gfxcore, const GeometryDesc* desc, BufferView* view,
51 size_t num_components, size_t component_size_bytes, GLenum component_type, 48 size_t num_components, GLenum component_type, GLboolean normalized,
52 GLboolean normalized, GLuint channel) { 49 GLuint channel) {
53 assert(gfxcore); 50 assert(gfxcore);
54 assert(desc); 51 assert(desc);
55 assert(view); 52 assert(view);
56 assert(view->buffer); 53 assert(view->buffer);
57 assert( 54 assert(view->count == desc->num_verts);
58 desc->num_verts <= 55 assert((view->offset_bytes + view->size_bytes) <= view->buffer->size_bytes);
59 view->size_bytes / (num_components * component_size_bytes));
60 assert(view->size_bytes <= view->buffer->size_bytes);
61 56
62 glBindBuffer(GL_ARRAY_BUFFER, view->buffer->vbo); 57 glBindBuffer(GL_ARRAY_BUFFER, view->buffer->vbo);
63 glEnableVertexAttribArray(channel); 58 glEnableVertexAttribArray(channel);
@@ -89,8 +84,8 @@ static bool configure_vertex_attributes(GfxCore* gfxcore, GeometryDesc* desc) {
89 return false; 84 return false;
90 } 85 }
91 configure_buffer( 86 configure_buffer(
92 gfxcore, desc, (BufferView*)&desc->positions3d, 3, sizeof(float), 87 gfxcore, desc, (BufferView*)&desc->positions3d, 3, GL_FLOAT, GL_FALSE,
93 GL_FLOAT, GL_FALSE, GFX_POSITION_CHANNEL); 88 GFX_POSITION_CHANNEL);
94 } else if (view_is_populated(desc->positions2d)) { 89 } else if (view_is_populated(desc->positions2d)) {
95 init_view_buffer( 90 init_view_buffer(
96 gfxcore, (BufferView*)&desc->positions2d, Buffer2d, desc->buffer_usage); 91 gfxcore, (BufferView*)&desc->positions2d, Buffer2d, desc->buffer_usage);
@@ -98,8 +93,8 @@ static bool configure_vertex_attributes(GfxCore* gfxcore, GeometryDesc* desc) {
98 return false; 93 return false;
99 } 94 }
100 configure_buffer( 95 configure_buffer(
101 gfxcore, desc, (BufferView*)&desc->positions2d, 2, sizeof(float), 96 gfxcore, desc, (BufferView*)&desc->positions2d, 2, GL_FLOAT, GL_FALSE,
102 GL_FLOAT, GL_FALSE, GFX_POSITION_CHANNEL); 97 GFX_POSITION_CHANNEL);
103 } 98 }
104 if (view_is_populated(desc->normals)) { 99 if (view_is_populated(desc->normals)) {
105 init_view_buffer( 100 init_view_buffer(
@@ -108,8 +103,8 @@ static bool configure_vertex_attributes(GfxCore* gfxcore, GeometryDesc* desc) {
108 return false; 103 return false;
109 } 104 }
110 configure_buffer( 105 configure_buffer(
111 gfxcore, desc, (BufferView*)&desc->normals, 3, sizeof(float), GL_FLOAT, 106 gfxcore, desc, (BufferView*)&desc->normals, 3, GL_FLOAT, GL_FALSE,
112 GL_FALSE, GFX_NORMAL_CHANNEL); 107 GFX_NORMAL_CHANNEL);
113 } 108 }
114 if (view_is_populated(desc->tangents)) { 109 if (view_is_populated(desc->tangents)) {
115 init_view_buffer( 110 init_view_buffer(
@@ -118,8 +113,8 @@ static bool configure_vertex_attributes(GfxCore* gfxcore, GeometryDesc* desc) {
118 return false; 113 return false;
119 } 114 }
120 configure_buffer( 115 configure_buffer(
121 gfxcore, desc, (BufferView*)&desc->tangents, 4, sizeof(float), GL_FLOAT, 116 gfxcore, desc, (BufferView*)&desc->tangents, 4, GL_FLOAT, GL_FALSE,
122 GL_FALSE, GFX_TANGENT_CHANNEL); 117 GFX_TANGENT_CHANNEL);
123 } 118 }
124 if (view_is_populated(desc->texcoords)) { 119 if (view_is_populated(desc->texcoords)) {
125 init_view_buffer( 120 init_view_buffer(
@@ -128,8 +123,8 @@ static bool configure_vertex_attributes(GfxCore* gfxcore, GeometryDesc* desc) {
128 return false; 123 return false;
129 } 124 }
130 configure_buffer( 125 configure_buffer(
131 gfxcore, desc, (BufferView*)&desc->texcoords, 2, sizeof(float), 126 gfxcore, desc, (BufferView*)&desc->texcoords, 2, GL_FLOAT, GL_FALSE,
132 GL_FLOAT, GL_FALSE, GFX_TEXCOORDS_CHANNEL); 127 GFX_TEXCOORDS_CHANNEL);
133 } 128 }
134 if (view_is_populated(desc->joints.u8)) { 129 if (view_is_populated(desc->joints.u8)) {
135 init_view_buffer( 130 init_view_buffer(
@@ -138,8 +133,8 @@ static bool configure_vertex_attributes(GfxCore* gfxcore, GeometryDesc* desc) {
138 return false; 133 return false;
139 } 134 }
140 configure_buffer( 135 configure_buffer(
141 gfxcore, desc, (BufferView*)&desc->joints.u8, 4, sizeof(uint8_t), 136 gfxcore, desc, (BufferView*)&desc->joints.u8, 4, GL_UNSIGNED_BYTE,
142 GL_UNSIGNED_BYTE, GL_FALSE, GFX_JOINTS_CHANNEL); 137 GL_FALSE, GFX_JOINTS_CHANNEL);
143 } else if (view_is_populated(desc->joints.u16)) { 138 } else if (view_is_populated(desc->joints.u16)) {
144 init_view_buffer( 139 init_view_buffer(
145 gfxcore, (BufferView*)&desc->joints.u16, BufferU16, desc->buffer_usage); 140 gfxcore, (BufferView*)&desc->joints.u16, BufferU16, desc->buffer_usage);
@@ -147,8 +142,8 @@ static bool configure_vertex_attributes(GfxCore* gfxcore, GeometryDesc* desc) {
147 return false; 142 return false;
148 } 143 }
149 configure_buffer( 144 configure_buffer(
150 gfxcore, desc, (BufferView*)&desc->joints.u16, 4, sizeof(uint16_t), 145 gfxcore, desc, (BufferView*)&desc->joints.u16, 4, GL_UNSIGNED_SHORT,
151 GL_UNSIGNED_SHORT, GL_FALSE, GFX_JOINTS_CHANNEL); 146 GL_FALSE, GFX_JOINTS_CHANNEL);
152 } 147 }
153 148
154 // If weights are given as unsigned integers, then they are normalized 149 // If weights are given as unsigned integers, then they are normalized
@@ -160,8 +155,8 @@ static bool configure_vertex_attributes(GfxCore* gfxcore, GeometryDesc* desc) {
160 return false; 155 return false;
161 } 156 }
162 configure_buffer( 157 configure_buffer(
163 gfxcore, desc, (BufferView*)&desc->weights.u8, 4, sizeof(uint8_t), 158 gfxcore, desc, (BufferView*)&desc->weights.u8, 4, GL_UNSIGNED_BYTE,
164 GL_UNSIGNED_BYTE, GL_TRUE, GFX_WEIGHTS_CHANNEL); 159 GL_TRUE, GFX_WEIGHTS_CHANNEL);
165 } else if (view_is_populated(desc->weights.u16)) { 160 } else if (view_is_populated(desc->weights.u16)) {
166 init_view_buffer( 161 init_view_buffer(
167 gfxcore, (BufferView*)&desc->weights.u16, BufferU16, 162 gfxcore, (BufferView*)&desc->weights.u16, BufferU16,
@@ -170,8 +165,8 @@ static bool configure_vertex_attributes(GfxCore* gfxcore, GeometryDesc* desc) {
170 return false; 165 return false;
171 } 166 }
172 configure_buffer( 167 configure_buffer(
173 gfxcore, desc, (BufferView*)&desc->weights.u16, 4, sizeof(uint16_t), 168 gfxcore, desc, (BufferView*)&desc->weights.u16, 4, GL_UNSIGNED_SHORT,
174 GL_UNSIGNED_SHORT, GL_TRUE, GFX_WEIGHTS_CHANNEL); 169 GL_TRUE, GFX_WEIGHTS_CHANNEL);
175 } else if (view_is_populated(desc->weights.floats)) { 170 } else if (view_is_populated(desc->weights.floats)) {
176 init_view_buffer( 171 init_view_buffer(
177 gfxcore, (BufferView*)&desc->weights.floats, BufferFloat, 172 gfxcore, (BufferView*)&desc->weights.floats, BufferFloat,
@@ -180,8 +175,8 @@ static bool configure_vertex_attributes(GfxCore* gfxcore, GeometryDesc* desc) {
180 return false; 175 return false;
181 } 176 }
182 configure_buffer( 177 configure_buffer(
183 gfxcore, desc, (BufferView*)&desc->weights.floats, 4, sizeof(float), 178 gfxcore, desc, (BufferView*)&desc->weights.floats, 4, GL_FLOAT,
184 GL_FLOAT, GL_FALSE, GFX_WEIGHTS_CHANNEL); 179 GL_FALSE, GFX_WEIGHTS_CHANNEL);
185 } 180 }
186 181
187 return true; 182 return true;
@@ -282,9 +277,9 @@ void gfx_update_geometry(Geometry* geometry, const GeometryDesc* desc) {
282 assert(geometry->desc.positions3d.buffer); 277 assert(geometry->desc.positions3d.buffer);
283 gfx_update_buffer( 278 gfx_update_buffer(
284 geometry->desc.positions3d.buffer, 279 geometry->desc.positions3d.buffer,
285 &(BufferDataDesc){ 280 &(BufferDataDesc){.vec3s = desc->positions3d.data,
286 .vec3s = desc->positions3d.data, 281 .count =
287 .count = desc->positions3d.size_bytes / sizeof(vec3)}); 282 desc->positions3d.size_bytes / sizeof(vec3)});
288 } 283 }
289 // TODO: more 284 // TODO: more
290 else { 285 else {
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..3840019 100644
--- a/gfx/src/core/shader_program.c
+++ b/src/core/shader_program.c
@@ -72,17 +72,23 @@ void gfx_deactivate_shader_program(const ShaderProgram* prog) {
72 ASSERT_GL; 72 ASSERT_GL;
73} 73}
74 74
75static void set_texture_uniform( 75static void set_int_uniform(GLuint prog, const char* name, int value) {
76 GLuint prog, const char* name, int texture_unit, const Texture* texture) {
77 assert(prog != 0); 76 assert(prog != 0);
78 assert(name); 77 assert(name);
79 assert(texture);
80 78
81 const GLint location = glGetUniformLocation(prog, name); 79 const GLint location = glGetUniformLocation(prog, name);
82 if (location >= 0) { 80 if (location >= 0) {
83 glActiveTexture(GL_TEXTURE0 + texture_unit); 81 glUniform1i(location, value);
84 glBindTexture(texture->target, texture->id); 82 }
85 glUniform1i(location, texture_unit); 83}
84
85static void set_float_uniform(GLuint prog, const char* name, float value) {
86 assert(prog != 0);
87 assert(name);
88
89 const GLint location = glGetUniformLocation(prog, name);
90 if (location >= 0) {
91 glUniform1f(location, value);
86 } 92 }
87} 93}
88 94
@@ -118,13 +124,17 @@ static void set_vec4_uniform(GLuint prog, const char* name, vec4 value) {
118 } 124 }
119} 125}
120 126
121static void set_float_uniform(GLuint prog, const char* name, float value) { 127static void set_texture_uniform(
128 GLuint prog, const char* name, int texture_unit, const Texture* texture) {
122 assert(prog != 0); 129 assert(prog != 0);
123 assert(name); 130 assert(name);
131 assert(texture);
124 132
125 const GLint location = glGetUniformLocation(prog, name); 133 const GLint location = glGetUniformLocation(prog, name);
126 if (location >= 0) { 134 if (location >= 0) {
127 glUniform1f(location, value); 135 glActiveTexture(GL_TEXTURE0 + texture_unit);
136 glBindTexture(texture->target, texture->id);
137 glUniform1i(location, texture_unit);
128 } 138 }
129} 139}
130 140
@@ -135,23 +145,30 @@ void gfx_apply_uniforms(const ShaderProgram* prog) {
135 for (int i = 0; i < prog->num_uniforms; ++i) { 145 for (int i = 0; i < prog->num_uniforms; ++i) {
136 const ShaderUniform* uniform = &prog->uniforms[i]; 146 const ShaderUniform* uniform = &prog->uniforms[i];
137 switch (uniform->type) { 147 switch (uniform->type) {
138 case UniformTexture: 148 case UniformInt:
139 set_texture_uniform( 149 set_int_uniform(prog->id, uniform->name.str, uniform->value.uniform_int);
140 prog->id, uniform->name.str, next_texture_unit, 150 break;
141 uniform->value.texture); 151 case UniformFloat:
142 next_texture_unit++; 152 set_float_uniform(
153 prog->id, uniform->name.str, uniform->value.uniform_float);
143 break; 154 break;
144 case UniformMat4: 155 case UniformMat4:
145 set_mat4_uniform(prog->id, uniform->name.str, &uniform->value.mat4, 1); 156 set_mat4_uniform(
157 prog->id, uniform->name.str, &uniform->value.uniform_mat4, 1);
146 break; 158 break;
147 case UniformVec3: 159 case UniformVec3:
148 set_vec3_uniform(prog->id, uniform->name.str, uniform->value.vec3); 160 set_vec3_uniform(
161 prog->id, uniform->name.str, uniform->value.uniform_vec3);
149 break; 162 break;
150 case UniformVec4: 163 case UniformVec4:
151 set_vec4_uniform(prog->id, uniform->name.str, uniform->value.vec4); 164 set_vec4_uniform(
165 prog->id, uniform->name.str, uniform->value.uniform_vec4);
152 break; 166 break;
153 case UniformFloat: 167 case UniformTexture:
154 set_float_uniform(prog->id, uniform->name.str, uniform->value.scalar); 168 set_texture_uniform(
169 prog->id, uniform->name.str, next_texture_unit,
170 uniform->value.texture);
171 next_texture_unit++;
155 break; 172 break;
156 case UniformMat4Array: 173 case UniformMat4Array:
157 set_mat4_uniform( 174 set_mat4_uniform(
@@ -179,8 +196,9 @@ static ShaderUniform* get_or_allocate_uniform(
179 196
180 // Create the uniform if it does not exist. 197 // Create the uniform if it does not exist.
181 if (prog->num_uniforms == GFX_MAX_UNIFORMS_PER_SHADER) { 198 if (prog->num_uniforms == GFX_MAX_UNIFORMS_PER_SHADER) {
182 FAIL("Exceeded the maximum number of uniforms per shader. Please increase " 199 FAIL(
183 "this value."); 200 "Exceeded the maximum number of uniforms per shader. Please increase "
201 "this value.");
184 return 0; 202 return 0;
185 } 203 }
186 ShaderUniform* uniform = &prog->uniforms[prog->num_uniforms]; 204 ShaderUniform* uniform = &prog->uniforms[prog->num_uniforms];
@@ -191,21 +209,67 @@ static ShaderUniform* get_or_allocate_uniform(
191// The functions below save the value of a uniform in the shader program. If the 209// The functions below save the value of a uniform in the shader program. If the
192// uniform does not even exist, then there is no need to store the value. 210// uniform does not even exist, then there is no need to store the value.
193 211
194void gfx_set_texture_uniform( 212void gfx_set_uniform(ShaderProgram* prog, const ShaderUniform* uniform) {
195 ShaderProgram* prog, const char* name, const Texture* texture) { 213 switch (uniform->type) {
214 case UniformInt:
215 gfx_set_int_uniform(prog, uniform->name.str, uniform->value.uniform_int);
216 break;
217 case UniformFloat:
218 gfx_set_float_uniform(
219 prog, uniform->name.str, uniform->value.uniform_float);
220 break;
221 case UniformMat4:
222 gfx_set_mat4_uniform(prog, uniform->name.str, &uniform->value.uniform_mat4);
223 break;
224 case UniformVec3:
225 gfx_set_vec3_uniform(prog, uniform->name.str, uniform->value.uniform_vec3);
226 break;
227 case UniformVec4:
228 gfx_set_vec4_uniform(prog, uniform->name.str, uniform->value.uniform_vec4);
229 break;
230 case UniformTexture:
231 gfx_set_texture_uniform(prog, uniform->name.str, uniform->value.texture);
232 break;
233 case UniformMat4Array:
234 gfx_set_mat4_array_uniform(
235 prog, uniform->name.str, uniform->value.array.values,
236 uniform->value.array.count);
237 break;
238 }
239}
240
241void gfx_set_int_uniform(ShaderProgram* prog, const char* name, int value) {
196 assert(prog); 242 assert(prog);
197 assert(name); 243 assert(name);
198 assert(texture);
199 244
245 // No need to store the uniform on our side if it does not exist in the
246 // program.
200 const GLint location = glGetUniformLocation(prog->id, name); 247 const GLint location = glGetUniformLocation(prog->id, name);
201 if (location < 0) { 248 if (location < 0) {
202 return; 249 return;
203 } 250 }
204 ShaderUniform* uniform = get_or_allocate_uniform(prog, name); 251 ShaderUniform* uniform = get_or_allocate_uniform(prog, name);
205 assert(uniform); 252 assert(uniform);
206 uniform->name = sstring_make(name); 253 uniform->name = sstring_make(name);
207 uniform->type = UniformTexture; 254 uniform->type = UniformInt;
208 uniform->value.texture = texture; 255 uniform->value.uniform_int = value;
256}
257
258void gfx_set_float_uniform(ShaderProgram* prog, const char* name, float value) {
259 assert(prog);
260 assert(name);
261
262 // No need to store the uniform on our side if it does not exist in the
263 // program.
264 const GLint location = glGetUniformLocation(prog->id, name);
265 if (location < 0) {
266 return;
267 }
268 ShaderUniform* uniform = get_or_allocate_uniform(prog, name);
269 assert(uniform);
270 uniform->name = sstring_make(name);
271 uniform->type = UniformFloat;
272 uniform->value.uniform_float = value;
209} 273}
210 274
211void gfx_set_mat4_uniform( 275void gfx_set_mat4_uniform(
@@ -220,9 +284,9 @@ void gfx_set_mat4_uniform(
220 } 284 }
221 ShaderUniform* uniform = get_or_allocate_uniform(prog, name); 285 ShaderUniform* uniform = get_or_allocate_uniform(prog, name);
222 assert(uniform); 286 assert(uniform);
223 uniform->name = sstring_make(name); 287 uniform->name = sstring_make(name);
224 uniform->type = UniformMat4; 288 uniform->type = UniformMat4;
225 uniform->value.mat4 = *mat; 289 uniform->value.uniform_mat4 = *mat;
226} 290}
227 291
228void gfx_set_vec3_uniform(ShaderProgram* prog, const char* name, vec3 value) { 292void gfx_set_vec3_uniform(ShaderProgram* prog, const char* name, vec3 value) {
@@ -235,9 +299,9 @@ void gfx_set_vec3_uniform(ShaderProgram* prog, const char* name, vec3 value) {
235 } 299 }
236 ShaderUniform* uniform = get_or_allocate_uniform(prog, name); 300 ShaderUniform* uniform = get_or_allocate_uniform(prog, name);
237 assert(uniform); 301 assert(uniform);
238 uniform->name = sstring_make(name); 302 uniform->name = sstring_make(name);
239 uniform->type = UniformVec3; 303 uniform->type = UniformVec3;
240 uniform->value.vec3 = value; 304 uniform->value.uniform_vec3 = value;
241} 305}
242 306
243void gfx_set_vec4_uniform(ShaderProgram* prog, const char* name, vec4 value) { 307void gfx_set_vec4_uniform(ShaderProgram* prog, const char* name, vec4 value) {
@@ -250,26 +314,26 @@ void gfx_set_vec4_uniform(ShaderProgram* prog, const char* name, vec4 value) {
250 } 314 }
251 ShaderUniform* uniform = get_or_allocate_uniform(prog, name); 315 ShaderUniform* uniform = get_or_allocate_uniform(prog, name);
252 assert(uniform); 316 assert(uniform);
253 uniform->name = sstring_make(name); 317 uniform->name = sstring_make(name);
254 uniform->type = UniformVec4; 318 uniform->type = UniformVec4;
255 uniform->value.vec4 = value; 319 uniform->value.uniform_vec4 = value;
256} 320}
257 321
258void gfx_set_float_uniform(ShaderProgram* prog, const char* name, float value) { 322void gfx_set_texture_uniform(
323 ShaderProgram* prog, const char* name, const Texture* texture) {
259 assert(prog); 324 assert(prog);
260 assert(name); 325 assert(name);
326 assert(texture);
261 327
262 // No need to store the uniform on our side if it does not exist in the
263 // program.
264 const GLint location = glGetUniformLocation(prog->id, name); 328 const GLint location = glGetUniformLocation(prog->id, name);
265 if (location < 0) { 329 if (location < 0) {
266 return; 330 return;
267 } 331 }
268 ShaderUniform* uniform = get_or_allocate_uniform(prog, name); 332 ShaderUniform* uniform = get_or_allocate_uniform(prog, name);
269 assert(uniform); 333 assert(uniform);
270 uniform->name = sstring_make(name); 334 uniform->name = sstring_make(name);
271 uniform->type = UniformFloat; 335 uniform->type = UniformTexture;
272 uniform->value.scalar = value; 336 uniform->value.texture = texture;
273} 337}
274 338
275void gfx_set_mat4_array_uniform( 339void gfx_set_mat4_array_uniform(
@@ -277,7 +341,7 @@ void gfx_set_mat4_array_uniform(
277 assert(prog); 341 assert(prog);
278 assert(name); 342 assert(name);
279 assert(mats); 343 assert(mats);
280 344
281 const GLint location = glGetUniformLocation(prog->id, name); 345 const GLint location = glGetUniformLocation(prog->id, name);
282 if (location < 0) { 346 if (location < 0) {
283 return; 347 return;
diff --git a/gfx/src/core/shader_program.h b/src/core/shader_program.h
index 1443663..521118d 100644
--- a/gfx/src/core/shader_program.h
+++ b/src/core/shader_program.h
@@ -5,8 +5,6 @@
5 5
6#include "gl_util.h" 6#include "gl_util.h"
7 7
8#include <math/fwd.h>
9
10#include <stdbool.h> 8#include <stdbool.h>
11 9
12typedef struct Texture Texture; 10typedef struct Texture Texture;
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..bf713ca 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 "renderer/imm_renderer_impl.h" 5#include "memory.h"
6#include "renderer/renderer_impl.h" 6#include "render/imm_impl.h"
7#include "scene/scene_memory.h" 7#include "render/llr_impl.h"
8 8#include "render/renderer_impl.h"
9#include <log/log.h>
10 9
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 Imm imm;
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, &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);
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;
@@ -62,9 +67,14 @@ Renderer* gfx_get_renderer(Gfx* gfx) {
62 return &gfx->renderer; 67 return &gfx->renderer;
63} 68}
64 69
65ImmRenderer* gfx_get_imm_renderer(Gfx* gfx) { 70Imm* gfx_get_imm(Gfx* gfx) {
71 assert(gfx);
72 return &gfx->imm;
73}
74
75LLR* gfx_get_llr(Gfx* gfx) {
66 assert(gfx); 76 assert(gfx);
67 return &gfx->imm_renderer; 77 return &gfx->llr;
68} 78}
69 79
70AssetCache* gfx_get_asset_cache(Gfx* gfx) { 80AssetCache* gfx_get_asset_cache(Gfx* gfx) {
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/scene_memory.c b/src/memory.c
index 85c27e7..754f04d 100644
--- a/gfx/src/scene/scene_memory.c
+++ b/src/memory.c
@@ -1,22 +1,23 @@
1#include "scene_memory.h" 1#include "memory.h"
2 2
3#include <gfx/scene.h>
3#include <gfx/sizes.h> 4#include <gfx/sizes.h>
4 5
5#include "animation_impl.h" 6#include "animation_impl.h"
6#include "camera_impl.h" 7#include "scene/light_impl.h"
7#include "light_impl.h" 8#include "scene/material_impl.h"
8#include "material_impl.h" 9#include "scene/mesh_impl.h"
9#include "mesh_impl.h" 10#include "scene/model_impl.h"
10#include "model_impl.h" 11#include "scene/node_impl.h"
11#include "node_impl.h" 12#include "scene/object_impl.h"
12#include "object_impl.h" 13#include "scene/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)
18DEF_MEMPOOL(animation_pool, Animation, GFX_MAX_NUM_ANIMATIONS) 19DEF_MEMPOOL(animation_pool, Animation, GFX_MAX_NUM_ANIMATIONS)
19DEF_MEMPOOL(camera_pool, SceneCamera, GFX_MAX_NUM_CAMERAS) 20DEF_MEMPOOL(camera_pool, Camera, GFX_MAX_NUM_CAMERAS)
20DEF_MEMPOOL(light_pool, Light, GFX_MAX_NUM_LIGHTS) 21DEF_MEMPOOL(light_pool, Light, GFX_MAX_NUM_LIGHTS)
21DEF_MEMPOOL(material_pool, Material, GFX_MAX_NUM_MATERIALS) 22DEF_MEMPOOL(material_pool, Material, GFX_MAX_NUM_MATERIALS)
22DEF_MEMPOOL(mesh_pool, Mesh, GFX_MAX_NUM_MESHES) 23DEF_MEMPOOL(mesh_pool, Mesh, GFX_MAX_NUM_MESHES)
@@ -47,16 +48,17 @@ 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
57#define MEM_FIELD(name) mem.PLURAL(name) 59#define MEM_FIELD(name) mem.PLURAL(name)
58 60
59void scene_mem_init() { 61void scene_mem_init(void) {
60 mempool_make(&mem.animas); 62 mempool_make(&mem.animas);
61 mempool_make(&mem.animations); 63 mempool_make(&mem.animations);
62 mempool_make(&mem.cameras); 64 mempool_make(&mem.cameras);
@@ -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);
@@ -86,17 +88,44 @@ void scene_mem_init() {
86 ALLOC_DUMMY(&mem.skeletons); 88 ALLOC_DUMMY(&mem.skeletons);
87} 89}
88 90
89void scene_mem_destroy() { 91void scene_mem_destroy(void) {
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,25 +148,25 @@ 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(void) { 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)
139DEF_MEMORY(animation, Animation) 168DEF_MEMORY(animation, Animation)
140DEF_MEMORY(camera, SceneCamera) 169DEF_MEMORY(camera, Camera)
141DEF_MEMORY(light, Light) 170DEF_MEMORY(light, Light)
142DEF_MEMORY(material, Material) 171DEF_MEMORY(material, Material)
143DEF_MEMORY(mesh, Mesh) 172DEF_MEMORY(mesh, Mesh)
diff --git a/gfx/src/scene/scene_memory.h b/src/memory.h
index d175cba..bfbee66 100644
--- a/gfx/src/scene/scene_memory.h
+++ b/src/memory.h
@@ -3,21 +3,23 @@
3 3
4#include "types.h" 4#include "types.h"
5 5
6typedef struct Camera Camera;
7
6/// Initialize scene memory. 8/// Initialize scene memory.
7/// 9///
8/// The scene memory guarantees that every object maps to an index different 10/// The scene memory guarantees that every object maps to an index different
9/// than 0. This way, 0 can be used as a special index to denote "no value". 11/// than 0. This way, 0 can be used as a special index to denote "no value".
10void scene_mem_init(); 12void scene_mem_init(void);
11 13
12/// Destroy the scene memory and all allocated objects. 14/// Destroy the scene memory and all allocated objects.
13void scene_mem_destroy(); 15void scene_mem_destroy(void);
14 16
15#define NAMED_INDEX(name) name##_idx 17#define NAMED_INDEX(name) name##_idx
16 18
17#define DECL_MEMORY(name, type) \ 19#define DECL_MEMORY(name, type) \
18 typedef struct type type; \ 20 typedef struct type type; \
19 /* xyz* mem_alloc_xyz() */ \ 21 /* xyz* mem_alloc_xyz() */ \
20 type* mem_alloc_##name(); \ 22 type* mem_alloc_##name(void); \
21 /* mem_free_xyz(xyz**) */ \ 23 /* mem_free_xyz(xyz**) */ \
22 void mem_free_##name(type**); \ 24 void mem_free_##name(type**); \
23 /* xyz* mem_get_xyz(xyz_idx); */ \ 25 /* xyz* mem_get_xyz(xyz_idx); */ \
@@ -27,7 +29,7 @@ void scene_mem_destroy();
27 29
28DECL_MEMORY(anima, Anima) 30DECL_MEMORY(anima, Anima)
29DECL_MEMORY(animation, Animation) 31DECL_MEMORY(animation, Animation)
30DECL_MEMORY(camera, SceneCamera) 32DECL_MEMORY(camera, Camera)
31DECL_MEMORY(light, Light) 33DECL_MEMORY(light, Light)
32DECL_MEMORY(material, Material) 34DECL_MEMORY(material, Material)
33DECL_MEMORY(mesh, Mesh) 35DECL_MEMORY(mesh, Mesh)
diff --git a/src/render/imm.c b/src/render/imm.c
new file mode 100644
index 0000000..7ab8d62
--- /dev/null
+++ b/src/render/imm.c
@@ -0,0 +1,194 @@
1#include "imm_impl.h"
2
3#include <gfx/core.h>
4#include <gfx/render/imm.h>
5#include <gfx/render/llr.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(Imm* 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, &(GeometryDesc){
25 .type = Triangles,
26 .buffer_usage = BufferDynamic,
27 .num_verts = num_triangle_verts,
28 .positions3d = (BufferView3d){
29 .size_bytes = num_triangle_verts * sizeof(vec3),
30 .count = num_triangle_verts}
31 });
32 if (!renderer->triangles) {
33 goto cleanup;
34 }
35
36 renderer->shader = gfx_make_immediate_mode_shader(gfxcore);
37 if (!renderer->shader) {
38 goto cleanup;
39 }
40
41 gfx_imm_set_colour(renderer, vec4_make(0.0f, 0.0f, 0.0f, 1.0f));
42
43 return true;
44
45cleanup:
46 gfx_imm_destroy(renderer);
47 return false;
48}
49
50void gfx_imm_destroy(Imm* renderer) {
51 assert(renderer);
52 assert(renderer->gfxcore);
53
54 if (renderer->triangles) {
55 gfx_destroy_geometry(renderer->gfxcore, &renderer->triangles);
56 // TODO: Could also destroy the geometry's buffers here.
57 }
58
59 if (renderer->shader) {
60 gfx_destroy_shader_program(renderer->gfxcore, &renderer->shader);
61 }
62}
63
64void gfx_imm_flush(Imm* renderer) {
65 assert(renderer);
66
67 if (renderer->num_triangle_verts > 0) {
68 gfx_update_geometry(
69 renderer->triangles,
70 &(GeometryDesc){
71 .num_verts = renderer->num_triangle_verts,
72 .positions3d = (BufferView3d){
73 .data = renderer->triangle_verts,
74 .size_bytes = renderer->num_triangle_verts * sizeof(vec3)}
75 });
76
77 gfx_llr_render_geometry(renderer->llr, renderer->triangles);
78
79 renderer->num_triangle_verts = 0;
80 }
81}
82
83void gfx_imm_start(Imm* renderer) {
84 assert(renderer);
85
86 // Shader uniforms are applied lazily.
87 // TODO: In the event that gfx_activate_shader_program() activates uniforms
88 // automatically for convenience, call an overload here that doesn't do so.
89 // gfx_activate_shader_program(renderer->shader);
90 gfx_llr_set_shader(renderer->llr, renderer->shader);
91}
92
93void gfx_imm_end(Imm* renderer) {
94 assert(renderer);
95
96 gfx_imm_flush(renderer);
97 // gfx_deactivate_shader_program(renderer->shader);
98 gfx_llr_set_shader(renderer->llr, 0);
99}
100
101void gfx_imm_draw_triangles(
102 Imm* renderer, const vec3 verts[], size_t num_triangles) {
103 assert(renderer);
104 assert(verts);
105 const size_t new_verts = num_triangles * 3;
106 assert(
107 renderer->num_triangle_verts + new_verts <
108 (GFX_IMM_MAX_NUM_TRIANGLES * 3));
109
110 memcpy(
111 renderer->triangle_verts + renderer->num_triangle_verts, verts,
112 new_verts * sizeof(vec3));
113
114 renderer->num_triangle_verts += new_verts;
115}
116
117void gfx_imm_draw_triangle(Imm* renderer, const vec3 verts[3]) {
118 gfx_imm_draw_triangles(renderer, verts, 1);
119}
120
121void gfx_imm_draw_aabb2(Imm* renderer, aabb2 box) {
122 assert(renderer);
123
124 // clang-format off
125 const vec3 verts[4] = {
126 vec3_make(box.min.x, box.min.y, 0), // 3 ---- 2
127 vec3_make(box.max.x, box.min.y, 0), // | |
128 vec3_make(box.max.x, box.max.y, 0), // | |
129 vec3_make(box.min.x, box.max.y, 0)}; // 0 ---- 1
130 // clang-format on
131
132#define tri(i0, i1, i2) verts[i0], verts[i1], verts[i2]
133 const vec3 tris[6] = {tri(0, 1, 2), tri(0, 2, 3)};
134#undef tri
135
136 gfx_imm_draw_triangles(renderer, tris, 2);
137}
138
139void gfx_imm_draw_aabb3(Imm* renderer, aabb3 box) {
140 assert(renderer);
141
142 // clang-format off
143 const vec3 vertices[8] = {
144 vec3_make(box.min.x, box.min.y, box.max.z), // 7 ----- 6
145 vec3_make(box.max.x, box.min.y, box.max.z), // / /|
146 vec3_make(box.max.x, box.max.y, box.max.z), // 3 ----- 2 |
147 vec3_make(box.min.x, box.max.y, box.max.z), // | | |
148 vec3_make(box.min.x, box.min.y, box.min.z), // | 4 ----- 5
149 vec3_make(box.max.x, box.min.y, box.min.z), // |/ |/
150 vec3_make(box.max.x, box.max.y, box.min.z), // 0 ----- 1
151 vec3_make(box.min.x, box.max.y, box.min.z)};
152 // clang-format on
153
154 gfx_imm_draw_box3(renderer, vertices);
155}
156
157void gfx_imm_draw_box3(Imm* renderer, const vec3 vertices[8]) {
158 assert(renderer);
159 assert(vertices);
160
161 // 7 ----- 6
162 // / /|
163 // 3 ----- 2 |
164 // | | |
165 // | 4 ----- 5
166 // |/ |/
167 // 0 ----- 1
168
169#define tri(i0, i1, i2) vertices[i0], vertices[i1], vertices[i2]
170 const vec3 tris[36] = {
171 // Front.
172 tri(0, 1, 2), tri(0, 2, 3),
173 // Right.
174 tri(1, 5, 6), tri(1, 6, 2),
175 // Back.
176 tri(5, 4, 7), tri(5, 7, 6),
177 // Left.
178 tri(4, 0, 03), tri(4, 3, 7),
179 // Top.
180 tri(3, 2, 6), tri(3, 6, 7),
181 // Bottom.
182 tri(0, 4, 5), tri(0, 5, 1)};
183
184 gfx_imm_draw_triangles(renderer, tris, 12);
185}
186
187void gfx_imm_set_colour(Imm* renderer, vec4 colour) {
188 assert(renderer);
189 assert(renderer->shader);
190
191 gfx_imm_flush(renderer);
192
193 gfx_set_vec4_uniform(renderer->shader, "Colour", colour);
194}
diff --git a/gfx/src/renderer/imm_renderer_impl.h b/src/render/imm_impl.h
index 5ece354..d87b910 100644
--- a/gfx/src/renderer/imm_renderer_impl.h
+++ b/src/render/imm_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.
20typedef struct ImmRenderer { 23/// TODO: Flush the buffer when it reaches its maximum size to remove this
21 GfxCore* gfxcore; 24/// constraint.
22 ShaderProgram* shader; 25typedef struct Imm {
26 GfxCore* gfxcore;
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. 37} Imm;
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;
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(Imm*, 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(Imm*);
42
43/// Flush draw commands.
44void imm_renderer_flush(ImmRenderer*);
diff --git a/src/render/llr.c b/src/render/llr.c
new file mode 100644
index 0000000..752b65b
--- /dev/null
+++ b/src/render/llr.c
@@ -0,0 +1,441 @@
1#include "llr_impl.h"
2
3#include "animation_impl.h"
4#include "scene/light_impl.h"
5#include "scene/material_impl.h"
6#include "scene/mesh_impl.h"
7#include "scene/node_impl.h"
8
9#include <gfx/core.h>
10#include <gfx/util/ibl.h>
11
12#include <cassert.h>
13
14static const int IRRADIANCE_MAP_WIDTH = 1024;
15static const int IRRADIANCE_MAP_HEIGHT = 1024;
16static const int PREFILTERED_ENVIRONMENT_MAP_WIDTH = 128;
17static const int PREFILTERED_ENVIRONMENT_MAP_HEIGHT = 128;
18static const int BRDF_INTEGRATION_MAP_WIDTH = 512;
19static const int BRDF_INTEGRATION_MAP_HEIGHT = 512;
20
21/// Activate the material.
22///
23/// This configures the shader uniforms that are specific to the material.
24static void material_activate(ShaderProgram* shader, const Material* material) {
25 assert(material);
26 for (int i = 0; i < material->num_uniforms; ++i) {
27 const ShaderUniform* uniform = &material->uniforms[i];
28 gfx_set_uniform(shader, uniform);
29 }
30 if (material->alpha_mode != Opaque) {
31 gfx_set_uniform(
32 shader, &(ShaderUniform){.name = sstring_make("AlphaMode"),
33 .type = UniformInt,
34 .value.uniform_int = material->alpha_mode});
35 }
36 if (material->alpha_mode == Mask) {
37 gfx_set_uniform(
38 shader,
39 &(ShaderUniform){.name = sstring_make("AlphaCutoff"),
40 .type = UniformFloat,
41 .value.uniform_float = material->alpha_cutoff});
42 }
43}
44
45/// Initialize renderer state for IBL.
46static bool init_ibl(LLR* renderer) {
47 assert(renderer);
48 assert(!renderer->ibl);
49 assert(!renderer->brdf_integration_map);
50
51 if (!((renderer->ibl = gfx_make_ibl(renderer->gfxcore)))) {
52 return false;
53 }
54
55 if (!((renderer->brdf_integration_map = gfx_make_brdf_integration_map(
56 renderer->ibl, renderer->gfxcore, BRDF_INTEGRATION_MAP_WIDTH,
57 BRDF_INTEGRATION_MAP_HEIGHT)))) {
58 return false;
59 }
60
61 return true;
62}
63
64/// Compute irradiance and prefiltered environment maps for the light if they
65/// have not been already computed.
66///
67/// This is done lazily here, and not when the light is created, because we
68/// need an IBL instance to do this and it is more convenient for the public
69/// API to create lights without worrying about those details. It also makes the
70/// public API cheaper, since the maps are only computed when they are actually
71/// needed.
72static bool set_up_environment_light(LLR* renderer, EnvironmentLight* light) {
73 assert(renderer);
74 assert(light);
75 assert(renderer->ibl);
76 assert(renderer->brdf_integration_map);
77
78 if (light->irradiance_map) {
79 assert(light->prefiltered_environment_map);
80 return true;
81 }
82
83 // For convenience.
84 GfxCore* gfxcore = renderer->gfxcore;
85
86 Texture* irradiance_map = 0;
87 Texture* prefiltered_environment_map = 0;
88
89 if (!((irradiance_map = gfx_make_irradiance_map(
90 renderer->ibl, gfxcore, light->environment_map,
91 IRRADIANCE_MAP_WIDTH, IRRADIANCE_MAP_HEIGHT)))) {
92 goto cleanup;
93 }
94
95 int max_mip_level = 0;
96 if (!((prefiltered_environment_map = gfx_make_prefiltered_environment_map(
97 renderer->ibl, gfxcore, light->environment_map,
98 PREFILTERED_ENVIRONMENT_MAP_WIDTH,
99 PREFILTERED_ENVIRONMENT_MAP_HEIGHT, &max_mip_level)))) {
100 goto cleanup;
101 }
102
103 light->irradiance_map = irradiance_map;
104 light->prefiltered_environment_map = prefiltered_environment_map;
105 light->max_reflection_lod = max_mip_level;
106
107 return true;
108
109cleanup:
110 if (irradiance_map) {
111 gfx_destroy_texture(gfxcore, &irradiance_map);
112 }
113 if (prefiltered_environment_map) {
114 gfx_destroy_texture(gfxcore, &prefiltered_environment_map);
115 }
116 return false;
117}
118
119static void configure_light(LLR* renderer, Light* light) {
120 assert(renderer);
121 assert(light);
122
123 // For convenience.
124 ShaderProgram* const shader = renderer->shader;
125
126 switch (light->type) {
127 case EnvironmentLightType: {
128 EnvironmentLight* env = &light->environment;
129
130 const bool initialized = set_up_environment_light(renderer, env);
131 ASSERT(initialized);
132 assert(env->environment_map);
133 assert(env->irradiance_map);
134 assert(env->prefiltered_environment_map);
135 assert(renderer->brdf_integration_map);
136
137 gfx_set_texture_uniform(
138 shader, "BRDFIntegrationMap", renderer->brdf_integration_map);
139 gfx_set_texture_uniform(shader, "Sky", env->environment_map);
140 gfx_set_texture_uniform(shader, "IrradianceMap", env->irradiance_map);
141 gfx_set_texture_uniform(
142 shader, "PrefilteredEnvironmentMap", env->prefiltered_environment_map);
143 gfx_set_float_uniform(
144 shader, "MaxReflectionLOD", (float)env->max_reflection_lod);
145
146 break;
147 }
148 default:
149 assert(false); // TODO: Implement other light types.
150 break;
151 }
152}
153
154static void configure_state(LLR* renderer) {
155 assert(renderer);
156
157 // Check if anything changed first so that we don't call gfx_apply_uniforms()
158 // unnecessarily.
159 const bool nothing_changed = (renderer->changed_flags == 0);
160 if (nothing_changed) {
161 return;
162 }
163 // Setting a null shader is also allowed, in which case there is nothing to
164 // configure.
165 if (renderer->shader == 0) {
166 renderer->shader_changed = false;
167 return;
168 }
169
170 // For convenience.
171 ShaderProgram* const shader = renderer->shader;
172 const mat4* const model = &renderer->matrix_stack[renderer->stack_pointer];
173
174 // TODO: Check to see which ones the shader actually uses and avoid
175 // computing the unnecessary matrices.
176
177 if (renderer->matrix_changed || renderer->shader_changed) {
178 renderer->matrix_changed = false;
179
180 gfx_set_mat4_uniform(shader, "Model", model);
181 gfx_set_mat4_uniform(shader, "ModelMatrix", model);
182 }
183
184 // TODO: camera_changed is not set anywhere. Need to think how imm primitive
185 // rendering and imm mesh rendering work together. We could treat imm
186 // primitive calls like setting a new shader.
187 if (renderer->camera_changed || renderer->shader_changed) {
188 renderer->camera_changed = false;
189
190 // Set all supported camera-related uniforms. Shaders can choose which ones
191 // to use.
192 const mat4 modelview = mat4_mul(renderer->view, *model);
193 const mat4 view_proj = mat4_mul(renderer->projection, renderer->view);
194 const mat4 mvp = mat4_mul(renderer->projection, modelview);
195
196 gfx_set_mat4_uniform(shader, "Modelview", &modelview);
197 gfx_set_mat4_uniform(shader, "View", &renderer->view);
198 gfx_set_mat4_uniform(shader, "Projection", &renderer->projection);
199 gfx_set_mat4_uniform(shader, "ViewProjection", &view_proj);
200 gfx_set_mat4_uniform(shader, "MVP", &mvp);
201 gfx_set_vec3_uniform(shader, "CameraPosition", renderer->camera_position);
202 gfx_set_mat4_uniform(shader, "CameraRotation", &renderer->camera_rotation);
203 gfx_set_float_uniform(shader, "Fovy", renderer->fovy);
204 gfx_set_float_uniform(shader, "Aspect", renderer->aspect);
205 }
206
207 if (renderer->lights_changed || renderer->shader_changed) {
208 renderer->lights_changed = false;
209
210 // TODO: Could do better by only setting the lights that have actually
211 // changed.
212 // TODO: Will also need to pass the number of lights to the shader once the
213 // other light types are implemented.
214 for (int i = 0; i < renderer->num_lights; ++i) {
215 configure_light(renderer, renderer->lights[i]);
216 }
217 }
218
219 if (renderer->skeleton_changed || renderer->shader_changed) {
220 renderer->skeleton_changed = false;
221
222 if (renderer->num_joints > 0) {
223 gfx_set_mat4_array_uniform(
224 shader, "JointMatrices", renderer->joint_matrices,
225 renderer->num_joints);
226 }
227 }
228
229 if (renderer->material_changed || renderer->shader_changed) {
230 renderer->material_changed = false;
231
232 // Geometry may be rendered without a material.
233 if (renderer->material) {
234 material_activate(renderer->shader, renderer->material);
235 }
236 }
237
238 if (renderer->shader_changed) {
239 renderer->shader_changed = false;
240 gfx_activate_shader_program(renderer->shader);
241 }
242
243 // TODO: At present, this results in many redundant calls to
244 // glGetUniformLocation() and glUniformXyz(). Look at the trace.
245 //
246 // TODO: Could add to qapitrace functionality to detect redundant calls and
247 // other inefficiencies. Maybe ask in the Github first if there would be
248 // interest in this.
249 //
250 // Must be called after activating the program.
251 gfx_apply_uniforms(renderer->shader);
252}
253
254bool gfx_llr_make(LLR* renderer, GfxCore* gfxcore) {
255 assert(renderer);
256 assert(gfxcore);
257
258 renderer->gfxcore = gfxcore;
259 if (!init_ibl(renderer)) {
260 goto cleanup;
261 }
262 gfx_llr_load_identity(renderer);
263 renderer->view = mat4_id();
264 renderer->projection = mat4_id();
265 renderer->camera_rotation = mat4_id();
266 return true;
267
268cleanup:
269 gfx_llr_destroy(renderer);
270 return false;
271}
272
273void gfx_llr_destroy(LLR* renderer) {
274 assert(renderer);
275 assert(renderer->gfxcore);
276
277 if (renderer->brdf_integration_map) {
278 gfx_destroy_texture(renderer->gfxcore, &renderer->brdf_integration_map);
279 }
280
281 // TODO: Do this once the IBL from the scene renderer is gone.
282 if (renderer->ibl) {
283 // gfx_destroy_ibl(renderer->gfxcore, &renderer->ibl);
284 }
285}
286
287void gfx_llr_set_shader(LLR* renderer, ShaderProgram* shader) {
288 assert(renderer);
289 // null shader is allowed, so do not assert it.
290
291 // It's important to not set shader_changed unnecessarily, since that would
292 // re-trigger the setting of uniforms.
293 if (renderer->shader != shader) {
294 renderer->shader = shader;
295 renderer->shader_changed = true;
296 }
297}
298
299void gfx_llr_push_light(LLR* renderer, Light* light) {
300 assert(renderer);
301 assert(light);
302 assert(renderer->num_lights >= 0);
303 ASSERT(renderer->num_lights < GFX_LLR_MAX_NUM_LIGHTS);
304
305 renderer->lights[renderer->num_lights++] = light;
306 renderer->lights_changed = true;
307}
308
309void gfx_llr_pop_light(LLR* renderer) {
310 assert(renderer);
311 ASSERT(renderer->num_lights > 0);
312
313 renderer->lights[--renderer->num_lights] = 0;
314 renderer->lights_changed = true;
315}
316
317void gfx_llr_set_skeleton(
318 LLR* renderer, const Anima* anima, const Skeleton* skeleton) {
319 assert(renderer);
320 assert(anima);
321 assert(skeleton);
322 assert(skeleton->num_joints <= GFX_MAX_NUM_JOINTS);
323
324 for (size_t i = 0; i < skeleton->num_joints; ++i) {
325 const joint_idx joint_index = skeleton->joints[i];
326 const Joint* joint = &anima->joints[joint_index];
327 renderer->joint_matrices[i] = joint->joint_matrix;
328 }
329 renderer->num_joints = skeleton->num_joints;
330 renderer->skeleton_changed = true;
331}
332
333void gfx_llr_clear_skeleton(LLR* renderer) {
334 assert(renderer);
335
336 renderer->num_joints = 0;
337 renderer->skeleton_changed = true;
338}
339
340void gfx_llr_set_material(LLR* renderer, const Material* material) {
341 assert(renderer);
342 assert(material);
343
344 renderer->material = material;
345 renderer->material_changed = true;
346}
347
348void gfx_llr_set_camera(LLR* renderer, const Camera* camera) {
349 assert(renderer);
350
351 renderer->camera_position = camera->spatial.p;
352 renderer->camera_rotation =
353 mat4_rotation(spatial3_transform(&camera->spatial));
354 renderer->view = spatial3_inverse_transform(&camera->spatial);
355 renderer->projection = camera->projection;
356 // Assuming a perspective matrix.
357 renderer->fovy = (R)atan(1.0 / (mat4_at(camera->projection, 1, 1))) * 2;
358 renderer->camera_changed = true;
359}
360
361void gfx_llr_set_projection_matrix(LLR* renderer, const mat4* projection) {
362 assert(renderer);
363
364 renderer->projection = *projection;
365 renderer->camera_changed = true;
366}
367
368void gfx_llr_set_aspect(LLR* renderer, float aspect) {
369 assert(renderer);
370
371 renderer->aspect = aspect;
372 renderer->camera_changed = true;
373}
374
375void gfx_llr_render_geometry(LLR* renderer, const Geometry* geometry) {
376 assert(renderer);
377 assert(geometry);
378
379 configure_state(renderer);
380 gfx_render_geometry(geometry);
381}
382
383void gfx_llr_render_mesh(LLR* renderer, const Mesh* mesh) {
384 assert(renderer);
385 assert(mesh);
386 assert(mesh->geometry);
387 assert(mesh->material);
388 assert(mesh->shader);
389
390 gfx_llr_set_material(renderer, mesh->material);
391 gfx_llr_set_shader(renderer, mesh->shader);
392 gfx_llr_render_geometry(renderer, mesh->geometry);
393}
394
395void gfx_llr_load_identity(LLR* renderer) {
396 assert(renderer);
397
398 renderer->matrix_stack[0] = mat4_id();
399 renderer->stack_pointer = 0;
400 renderer->matrix_changed = true;
401}
402
403void gfx_llr_push_matrix(LLR* renderer, const mat4* matrix) {
404 assert(renderer);
405 assert(matrix);
406 assert(renderer->stack_pointer >= 0);
407 ASSERT(renderer->stack_pointer < GFX_LLR_MAX_NUM_MATRICES);
408
409 renderer->stack_pointer += 1;
410 renderer->matrix_stack[renderer->stack_pointer] =
411 mat4_mul(*matrix, renderer->matrix_stack[renderer->stack_pointer - 1]);
412 renderer->matrix_changed = true;
413}
414
415void gfx_llr_pop_matrix(LLR* renderer) {
416 assert(renderer);
417 ASSERT(renderer->stack_pointer > 0);
418
419 // For debugging, zero out the matrix stack as matrices are popped out.
420 memset(
421 &renderer->matrix_stack[renderer->stack_pointer], 0,
422 sizeof(renderer->matrix_stack[0]));
423 renderer->stack_pointer -= 1;
424 renderer->matrix_changed = true;
425}
426
427void gfx_llr_translate(LLR* renderer, vec3 offset) {
428 assert(renderer);
429
430 const mat4 mat = mat4_translate(offset);
431 gfx_llr_push_matrix(renderer, &mat);
432}
433
434void gfx_llr_set_model_matrix(LLR* renderer, const mat4* model) {
435 assert(renderer);
436 assert(model);
437
438 renderer->matrix_stack[0] = *model;
439 renderer->stack_pointer = 0;
440 renderer->matrix_changed = true;
441}
diff --git a/src/render/llr_impl.h b/src/render/llr_impl.h
new file mode 100644
index 0000000..9d70843
--- /dev/null
+++ b/src/render/llr_impl.h
@@ -0,0 +1,85 @@
1#pragma once
2
3#include <gfx/render/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 GfxCore GfxCore;
14typedef struct IBL IBL;
15typedef struct Material Material;
16typedef struct ShaderProgram ShaderProgram;
17typedef struct Texture Texture;
18
19/// Immediate mode renderer.
20///
21/// The renderer caches state changes in memory and only programs the underlying
22/// shader program when a draw call is issued and if anything has changed. This
23/// keeps the number of graphics API calls to a minimum, but requires tracking
24/// state changes. The 'changed' booleans below fulfill this purpose, and
25/// indicate whether a given state has changed since the last draw call.
26///
27/// The renderer must combine state changes accordingly. For example, if only
28/// the lights have changed, then it is sufficient to update light uniforms in
29/// the current shader program. On the other hand, if the shader program has
30/// changed, then the renderer must reconfigure it from scratch and set light
31/// uniforms, camera uniforms, etc.
32///
33/// Note that the shader program API has its own level of caching as well, so
34/// reconfiguration at the level of the renderer does not result in the
35/// worst-case set of graphics API calls.
36typedef struct LLR {
37 GfxCore* gfxcore;
38
39 union {
40 struct {
41 bool shader_changed : 1; // Whether the shader has changed.
42 bool camera_changed : 1; // Whether the camera parameters have changed.
43 bool lights_changed : 1; // Whether the lights have changed.
44 bool skeleton_changed : 1; // Whether the skeleton has changed.
45 bool material_changed : 1; // Whether the material 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 const Material* material; // Active material. Not owned.
57
58 vec3 camera_position;
59 mat4 camera_rotation;
60 mat4 view; // Camera view matrix.
61 mat4 projection; // Camera projection matrix.
62 R fovy; // Camera vertical field of view.
63 R aspect; // Aspect ratio.
64
65 // Lights are not const because environment lights store lazily-computed
66 // irradiance maps.
67 Light* lights[GFX_LLR_MAX_NUM_LIGHTS]; // Lights stack.
68 int num_lights; // Number of lights enabled at a given point in time. It
69 // points to one past the top of the stack.
70
71 size_t num_joints;
72 mat4 joint_matrices[GFX_MAX_NUM_JOINTS];
73
74 // The matrix stack contains pre-multiplied matrices.
75 // It is also never empty. The top of the stack is an identity matrix when the
76 // stack is "empty" from the user's perspective.
77 mat4 matrix_stack[GFX_LLR_MAX_NUM_MATRICES];
78 int stack_pointer; // Points to the top of the stack.
79} LLR;
80
81/// Create a new immediate mode renderer.
82bool gfx_llr_make(LLR*, GfxCore*);
83
84/// Destroy the immediate mode renderer.
85void gfx_llr_destroy(LLR*);
diff --git a/src/render/renderer.c b/src/render/renderer.c
new file mode 100644
index 0000000..a9d9bef
--- /dev/null
+++ b/src/render/renderer.c
@@ -0,0 +1,282 @@
1#include "renderer_impl.h"
2
3#include "animation_impl.h"
4#include "llr_impl.h"
5#include "memory.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
12#include <gfx/core.h>
13#include <gfx/render/llr.h>
14#include <gfx/util/shader.h>
15
16#include <math/mat4.h>
17
18#include <assert.h>
19
20bool gfx_renderer_make(Renderer* renderer, LLR* llr, GfxCore* gfxcore) {
21 assert(renderer);
22 assert(llr);
23 assert(gfxcore);
24
25 renderer->gfxcore = gfxcore;
26 renderer->llr = llr;
27
28 return true;
29}
30
31void gfx_renderer_destroy(Renderer* renderer) {
32 if (!renderer) {
33 return;
34 }
35 assert(renderer->gfxcore);
36 GfxCore* gfxcore = renderer->gfxcore;
37 if (renderer->shaders.debug) {
38 gfx_destroy_shader_program(gfxcore, &renderer->shaders.debug);
39 }
40 if (renderer->shaders.normals) {
41 gfx_destroy_shader_program(gfxcore, &renderer->shaders.normals);
42 }
43 if (renderer->shaders.normal_mapped_normals) {
44 gfx_destroy_shader_program(
45 gfxcore, &renderer->shaders.normal_mapped_normals);
46 }
47 if (renderer->shaders.tangents) {
48 gfx_destroy_shader_program(gfxcore, &renderer->shaders.tangents);
49 }
50}
51
52static ShaderProgram* load_shader(Renderer* renderer, RenderSceneMode mode) {
53 assert(renderer);
54
55#define LOAD_AND_RETURN(pShader, constructor) \
56 { \
57 if (!pShader) { \
58 pShader = constructor(renderer->gfxcore); \
59 } \
60 assert(pShader); \
61 return pShader; \
62 }
63
64 switch (mode) {
65 case RenderDefault:
66 return 0;
67 case RenderDebug:
68 LOAD_AND_RETURN(renderer->shaders.debug, gfx_make_debug3d_shader);
69 case RenderNormals:
70 LOAD_AND_RETURN(renderer->shaders.normals, gfx_make_view_normals_shader);
71 case RenderNormalMappedNormals:
72 LOAD_AND_RETURN(
73 renderer->shaders.normal_mapped_normals,
74 gfx_make_view_normal_mapped_normals_shader);
75 case RenderTangents:
76 LOAD_AND_RETURN(renderer->shaders.tangents, gfx_make_view_tangents_shader);
77 }
78 assert(false);
79 return 0;
80}
81
82// static void log_matrix(const mat4* m) {
83// for (int row = 0; row < 4; ++row) {
84// LOGI("[ %5.2f, %5.2f, %5.2f, %5.2f ]", m->val[0][row], m->val[1][row],
85// m->val[2][row], m->val[3][row]);
86// }
87// }
88
89typedef struct RenderState {
90 GfxCore* gfxcore;
91 LLR* llr;
92 Renderer* renderer;
93 ShaderProgram* shader; // Null to use scene shaders.
94 const Scene* scene;
95 const Anima* anima;
96 RenderSceneFilter filter;
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 // Filter out by material.
159 const Material* material = mesh->material;
160 if (material) {
161 const AlphaMode mode = material->alpha_mode;
162 switch (state->filter) {
163 case RenderOpaqueAndAlphaMasked:
164 if (mode == Blend) {
165 continue;
166 }
167 break;
168 case RenderTransparent:
169 if (mode != Blend) {
170 continue;
171 }
172 break;
173 }
174 }
175
176 // TODO: Here we would frustum-cull the mesh. The AABB would have to be
177 // transformed by the model matrix. Rotation would make the AABB
178 // relatively large, but still, the culling would be conservative.
179
180 ShaderProgram* shader = state->shader ? state->shader : mesh->shader;
181 gfx_llr_set_shader(state->llr, shader);
182 gfx_llr_set_model_matrix(state->llr, &model_matrix);
183 gfx_llr_render_mesh(state->llr, mesh);
184 }
185
186 if (object->skeleton.val) {
187 gfx_llr_clear_skeleton(state->llr);
188 }
189
190 draw_children(state, &node_transform, node);
191 } else {
192 draw_children(state, &node_transform, node);
193 }
194}
195
196/// Draw the node's children.
197static void draw_children(
198 RenderState* state, const mat4* node_transform, const SceneNode* node) {
199 // Render children recursively.
200 for (node_idx child_index = node->child; child_index.val;) {
201 const SceneNode* child = mem_get_node(child_index);
202 draw_recursively(state, *node_transform, child);
203 child_index = child->next;
204 }
205}
206
207void gfx_render_scene(Renderer* renderer, const RenderSceneParams* params) {
208 assert(renderer);
209 assert(params);
210 assert(params->scene);
211
212 ShaderProgram* const shader = load_shader(renderer, params->mode);
213
214 const Scene* scene = params->scene;
215 const Camera* camera = params->camera;
216 GfxCore* const gfxcore = renderer->gfxcore;
217
218 int x, y, width, height;
219 gfx_get_viewport(gfxcore, &x, &y, &width, &height);
220 const R aspect = (R)width / (R)height;
221
222 RenderState state = {
223 .gfxcore = gfxcore,
224 .llr = renderer->llr,
225 .renderer = renderer,
226 .shader = shader,
227 .scene = scene};
228
229 gfx_llr_set_camera(renderer->llr, camera);
230 gfx_llr_set_aspect(renderer->llr, aspect);
231 // TODO: Render Opaque and Mask alpha-mode materials first, then Blend ones.
232 // TODO: I'm not sure if this belongs to the scene renderer per se, or if it
233 // is something that should be driven from the outside. Specifically, the
234 // caller could pass in a filter that determines what objects to render. The
235 // filter could include alpha mode.
236 // This caller would be some component that understands render passes and
237 // potentially renders the scene multiple times as needed. For example, a
238 // depth-prepass, followed by G-buffer, followed by some post-processing,
239 // etc. Rename this renderer to scene_renderer?
240 // TODO: When rendering transparent geometry, we need to turn off depth
241 // writes.
242 // Opaque.
243 state.filter = RenderOpaqueAndAlphaMasked;
244 draw_recursively(&state, mat4_id(), gfx_get_scene_root(scene));
245 // Transparent.
246 state.filter = RenderTransparent;
247 draw_recursively(&state, mat4_id(), gfx_get_scene_root(scene));
248}
249
250static void update_rec(SceneNode* node, const Camera* camera, R t) {
251 assert(node);
252 assert(camera);
253
254 const NodeType node_type = gfx_get_node_type(node);
255
256 // TODO: Models do not need to be animated if they are not visible to the
257 // camera.
258 if (node_type == AnimaNode) {
259 Anima* anima = gfx_get_node_anima_mut(node);
260 gfx_update_animation(anima, (R)t);
261 } else if (node_type == ModelNode) {
262 Model* model = gfx_get_node_model_mut(node);
263 SceneNode* root = gfx_get_model_root_mut(model);
264 update_rec(root, camera, t);
265 }
266
267 // Children.
268 SceneNode* child = gfx_get_node_child_mut(node);
269 while (child) {
270 update_rec(child, camera, t);
271 child = gfx_get_node_sibling_mut(child);
272 }
273}
274
275// TODO: Move this outside the renderer.
276void gfx_update(Scene* scene, const Camera* camera, R t) {
277 assert(scene);
278 assert(camera);
279
280 SceneNode* node = gfx_get_scene_root_mut(scene);
281 update_rec(node, camera, t);
282}
diff --git a/gfx/src/renderer/renderer_impl.h b/src/render/renderer_impl.h
index fc14dcb..160ff52 100644
--- a/gfx/src/renderer/renderer_impl.h
+++ b/src/render/renderer_impl.h
@@ -1,17 +1,15 @@
1#pragma once 1#pragma once
2 2
3#include <gfx/renderer.h> 3#include <gfx/render/renderer.h>
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/src/scene/camera.c b/src/scene/camera.c
new file mode 100644
index 0000000..fcfc496
--- /dev/null
+++ b/src/scene/camera.c
@@ -0,0 +1,18 @@
1#include <gfx/scene.h>
2
3#include "memory.h"
4
5#include <assert.h>
6#include <math/camera.h>
7
8Camera* gfx_make_camera(void) {
9 Camera* camera = mem_alloc_camera();
10 return camera;
11}
12
13void gfx_destroy_camera(Camera** camera) {
14 assert(camera);
15 if (*camera) {
16 mem_free_camera(camera);
17 }
18}
diff --git a/gfx/src/scene/light.c b/src/scene/light.c
index adbec8d..4233330 100644
--- a/gfx/src/scene/light.c
+++ b/src/scene/light.c
@@ -1,8 +1,8 @@
1#include "light_impl.h" 1#include "light_impl.h"
2 2
3#include "node_impl.h" 3#include "memory.h"
4#include "scene_memory.h"
5 4
5#include <cassert.h>
6#include <error.h> 6#include <error.h>
7 7
8static void make_environment_light( 8static void make_environment_light(
@@ -34,9 +34,6 @@ Light* gfx_make_light(const LightDesc* desc) {
34void gfx_destroy_light(Light** light) { 34void gfx_destroy_light(Light** light) {
35 assert(light); 35 assert(light);
36 if (*light) { 36 if (*light) {
37 if ((*light)->parent.val) {
38 gfx_del_node((*light)->parent);
39 }
40 mem_free_light(light); 37 mem_free_light(light);
41 } 38 }
42} 39}
diff --git a/gfx/src/scene/light_impl.h b/src/scene/light_impl.h
index 1aa0bb4..3191a50 100644
--- a/gfx/src/scene/light_impl.h
+++ b/src/scene/light_impl.h
@@ -1,10 +1,6 @@
1#pragma once 1#pragma once
2 2
3#include <gfx/scene/light.h> 3#include <gfx/scene.h>
4
5#include "types.h"
6
7typedef struct Texture Texture;
8 4
9/// An environment light. 5/// An environment light.
10typedef struct EnvironmentLight { 6typedef struct EnvironmentLight {
@@ -12,7 +8,7 @@ typedef struct EnvironmentLight {
12 const Texture* irradiance_map; // Renderer implementation. 8 const Texture* irradiance_map; // Renderer implementation.
13 const Texture* prefiltered_environment_map; // Renderer implementation. 9 const Texture* prefiltered_environment_map; // Renderer implementation.
14 int max_reflection_lod; // Mandatory when prefiltered_environment_map is 10 int max_reflection_lod; // Mandatory when prefiltered_environment_map is
15 // given. 11 // given.
16} EnvironmentLight; 12} EnvironmentLight;
17 13
18/// A scene light. 14/// A scene light.
@@ -21,5 +17,4 @@ typedef struct Light {
21 union { 17 union {
22 EnvironmentLight environment; 18 EnvironmentLight environment;
23 }; 19 };
24 node_idx parent; // Parent SceneNode.
25} Light; 20} Light;
diff --git a/src/scene/material.c b/src/scene/material.c
new file mode 100644
index 0000000..9fe6c1b
--- /dev/null
+++ b/src/scene/material.c
@@ -0,0 +1,24 @@
1#include "material_impl.h"
2
3#include "memory.h"
4
5static void material_make(Material* material, const MaterialDesc* desc) {
6 assert(material);
7 assert(desc);
8 assert(desc->num_uniforms < GFX_MAX_UNIFORMS_PER_MATERIAL);
9 material->alpha_mode = desc->alpha_mode;
10 material->alpha_cutoff = desc->alpha_cutoff;
11 material->num_uniforms = (int8_t)desc->num_uniforms;
12 for (int i = 0; i < desc->num_uniforms; ++i) {
13 material->uniforms[i] = desc->uniforms[i];
14 }
15}
16
17Material* gfx_make_material(const MaterialDesc* desc) {
18 assert(desc);
19 Material* material = mem_alloc_material();
20 material_make(material, desc);
21 return material;
22}
23
24void gfx_destroy_material(Material** material) { mem_free_material(material); }
diff --git a/src/scene/material_impl.h b/src/scene/material_impl.h
new file mode 100644
index 0000000..488ffc7
--- /dev/null
+++ b/src/scene/material_impl.h
@@ -0,0 +1,13 @@
1#pragma once
2
3#include <gfx/scene.h>
4#include <gfx/sizes.h>
5
6typedef struct ShaderProgram ShaderProgram;
7
8typedef struct Material {
9 AlphaMode alpha_mode;
10 float alpha_cutoff;
11 int8_t num_uniforms;
12 ShaderUniform uniforms[GFX_MAX_UNIFORMS_PER_MATERIAL];
13} Material;
diff --git a/gfx/src/scene/mesh.c b/src/scene/mesh.c
index 1a93bed..770c3fb 100644
--- a/gfx/src/scene/mesh.c
+++ b/src/scene/mesh.c
@@ -1,8 +1,10 @@
1#include "mesh_impl.h" 1#include "mesh_impl.h"
2 2
3#include "scene_memory.h" 3#include <gfx/scene.h>
4 4
5#include <assert.h> 5#include "memory.h"
6
7#include <cassert.h>
6 8
7static void mesh_make(Mesh* mesh, const MeshDesc* desc) { 9static void mesh_make(Mesh* mesh, const MeshDesc* desc) {
8 assert(mesh); 10 assert(mesh);
diff --git a/src/scene/mesh_impl.h b/src/scene/mesh_impl.h
new file mode 100644
index 0000000..c7e2211
--- /dev/null
+++ b/src/scene/mesh_impl.h
@@ -0,0 +1,11 @@
1#pragma once
2
3typedef struct Geometry Geometry;
4typedef struct Material Material;
5typedef struct ShaderProgram ShaderProgram;
6
7typedef struct Mesh {
8 const Geometry* geometry;
9 const Material* material;
10 ShaderProgram* shader; // TODO: Move this back to Material?
11} Mesh;
diff --git a/gfx/src/scene/model.c b/src/scene/model.c
index cc41a9a..1bd0112 100644
--- a/gfx/src/scene/model.c
+++ b/src/scene/model.c
@@ -1,8 +1,8 @@
1#include "model_impl.h" 1#include "model_impl.h"
2 2
3#include <gfx/scene/node.h> 3#include <gfx/scene.h>
4 4
5#include "scene_memory.h" 5#include "memory.h"
6 6
7#include <assert.h> 7#include <assert.h>
8 8
diff --git a/gfx/src/scene/model_impl.h b/src/scene/model_impl.h
index a99d32c..72cd0ab 100644
--- a/gfx/src/scene/model_impl.h
+++ b/src/scene/model_impl.h
@@ -1,13 +1,12 @@
1#pragma once 1#pragma once
2 2
3#include <gfx/scene/model.h> 3#include <gfx/scene.h>
4 4
5#include "scene_memory.h" 5#include "memory.h"
6 6
7/// Model. 7/// Model.
8typedef struct Model { 8typedef struct Model {
9 node_idx root; 9 node_idx root;
10 node_idx parent; // Parent SceneNode.
11} Model; 10} Model;
12 11
13/// Create a new model. 12/// Create a new model.
diff --git a/gfx/src/scene/node.c b/src/scene/node.c
index 67ce93c..0004d27 100644
--- a/gfx/src/scene/node.c
+++ b/src/scene/node.c
@@ -1,15 +1,15 @@
1#include "node_impl.h" 1#include "node_impl.h"
2 2
3#include "animation_impl.h" 3#include "animation_impl.h"
4#include "camera_impl.h" 4#include "memory.h"
5#include "light_impl.h"
6#include "model_impl.h"
7#include "object_impl.h" 5#include "object_impl.h"
6#include "render/llr_impl.h"
8#include "scene_graph.h" 7#include "scene_graph.h"
9#include "scene_memory.h"
10 8
11#include "gfx_assert.h" 9#include "gfx_assert.h"
12 10
11#include <gfx/scene.h>
12
13#include <cstring.h> 13#include <cstring.h>
14#include <log/log.h> 14#include <log/log.h>
15 15
@@ -19,7 +19,7 @@ static void scene_node_make(SceneNode* node) {
19 node->transform = mat4_id(); 19 node->transform = mat4_id();
20} 20}
21 21
22SceneNode* gfx_make_node() { 22SceneNode* gfx_make_node(void) {
23 SceneNode* node = mem_alloc_node(); 23 SceneNode* node = mem_alloc_node();
24 scene_node_make(node); 24 scene_node_make(node);
25 return node; 25 return node;
@@ -30,16 +30,14 @@ SceneNode* gfx_make_anima_node(Anima* anima) {
30 SceneNode* node = gfx_make_node(); 30 SceneNode* node = gfx_make_node();
31 node->type = AnimaNode; 31 node->type = AnimaNode;
32 node->anima = mem_get_anima_index(anima); 32 node->anima = mem_get_anima_index(anima);
33 anima->parent = mem_get_node_index(node);
34 return node; 33 return node;
35} 34}
36 35
37SceneNode* gfx_make_camera_node(SceneCamera* camera) { 36SceneNode* gfx_make_camera_node(Camera* camera) {
38 assert(camera); 37 assert(camera);
39 SceneNode* node = gfx_make_node(); 38 SceneNode* node = gfx_make_node();
40 node->type = CameraNode; 39 node->type = CameraNode;
41 node->camera = mem_get_camera_index(camera); 40 node->camera = mem_get_camera_index(camera);
42 camera->parent = mem_get_node_index(node);
43 return node; 41 return node;
44} 42}
45 43
@@ -48,7 +46,6 @@ SceneNode* gfx_make_light_node(Light* light) {
48 SceneNode* node = gfx_make_node(); 46 SceneNode* node = gfx_make_node();
49 node->type = LightNode; 47 node->type = LightNode;
50 node->light = mem_get_light_index(light); 48 node->light = mem_get_light_index(light);
51 light->parent = mem_get_node_index(node);
52 return node; 49 return node;
53} 50}
54 51
@@ -57,7 +54,6 @@ SceneNode* gfx_make_model_node(Model* model) {
57 SceneNode* node = gfx_make_node(); 54 SceneNode* node = gfx_make_node();
58 node->type = ModelNode; 55 node->type = ModelNode;
59 node->model = mem_get_model_index(model); 56 node->model = mem_get_model_index(model);
60 model->parent = mem_get_node_index(node);
61 return node; 57 return node;
62} 58}
63 59
@@ -66,7 +62,6 @@ SceneNode* gfx_make_object_node(SceneObject* object) {
66 SceneNode* node = gfx_make_node(); 62 SceneNode* node = gfx_make_node();
67 node->type = ObjectNode; 63 node->type = ObjectNode;
68 node->object = mem_get_object_index(object); 64 node->object = mem_get_object_index(object);
69 object->parent = mem_get_node_index(node);
70 return node; 65 return node;
71} 66}
72 67
@@ -74,24 +69,19 @@ SceneNode* gfx_make_object_node(SceneObject* object) {
74static void free_node_resource(SceneNode* node) { 69static void free_node_resource(SceneNode* node) {
75 assert(node); 70 assert(node);
76 71
77 // Set the resource's parent node back to 0 to avoid a recursive call into
78 // gfx_del_node().
79 switch (node->type) { 72 switch (node->type) {
80 case AnimaNode: { 73 case AnimaNode: {
81 Anima* anima = mem_get_anima(node->anima); 74 Anima* anima = mem_get_anima(node->anima);
82 anima->parent.val = 0;
83 gfx_destroy_anima(&anima); 75 gfx_destroy_anima(&anima);
84 return; 76 return;
85 } 77 }
86 case CameraNode: { 78 case CameraNode: {
87 SceneCamera* camera = mem_get_camera(node->camera); 79 Camera* camera = mem_get_camera(node->camera);
88 camera->parent.val = 0;
89 gfx_destroy_camera(&camera); 80 gfx_destroy_camera(&camera);
90 return; 81 return;
91 } 82 }
92 case LightNode: { 83 case LightNode: {
93 Light* light = mem_get_light(node->light); 84 Light* light = mem_get_light(node->light);
94 light->parent.val = 0;
95 gfx_destroy_light(&light); 85 gfx_destroy_light(&light);
96 return; 86 return;
97 } 87 }
@@ -100,63 +90,16 @@ static void free_node_resource(SceneNode* node) {
100 } 90 }
101 case ObjectNode: { 91 case ObjectNode: {
102 SceneObject* object = mem_get_object(node->object); 92 SceneObject* object = mem_get_object(node->object);
103 object->parent.val = 0;
104 gfx_destroy_object(&object); 93 gfx_destroy_object(&object);
105 return; 94 return;
106 } 95 }
107 case LogicalNode: 96 case LogicalNode: {
108 return; // Logical nodes have no resource. 97 return; // Logical nodes have no resource.
109 } 98 }
99 }
110 FAIL("unhandled node type"); 100 FAIL("unhandled node type");
111} 101}
112 102
113void gfx_construct_anima_node(SceneNode* node, Anima* anima) {
114 assert(node);
115 assert(anima);
116 free_node_resource(node);
117 node->type = AnimaNode;
118 node->anima = mem_get_anima_index(anima);
119 anima->parent = mem_get_node_index(node);
120}
121
122void gfx_construct_camera_node(SceneNode* node, SceneCamera* camera) {
123 assert(node);
124 assert(camera);
125 free_node_resource(node);
126 node->type = CameraNode;
127 node->camera = mem_get_camera_index(camera);
128 camera->parent = mem_get_node_index(node);
129}
130
131// TODO: Add a common helper function between each gfx_make_xyz_node() and
132// gfx_construct_xyz_node() pair.
133void gfx_construct_light_node(SceneNode* node, Light* light) {
134 assert(node);
135 assert(light);
136 free_node_resource(node);
137 node->type = LightNode;
138 node->light = mem_get_light_index(light);
139 light->parent = mem_get_node_index(node);
140}
141
142void gfx_construct_model_node(SceneNode* node, Model* model) {
143 assert(node);
144 assert(model);
145 free_node_resource(node);
146 node->type = ModelNode;
147 node->model = mem_get_model_index(model);
148 model->parent = mem_get_node_index(node);
149}
150
151void gfx_construct_object_node(SceneNode* node, SceneObject* object) {
152 assert(node);
153 assert(object);
154 free_node_resource(node);
155 node->type = ObjectNode;
156 node->object = mem_get_object_index(object);
157 object->parent = mem_get_node_index(node);
158}
159
160static void destroy_node_rec(SceneNode* node) { 103static void destroy_node_rec(SceneNode* node) {
161 assert(node); 104 assert(node);
162 105
@@ -217,11 +160,11 @@ Anima* gfx_get_node_anima_mut(SceneNode* node) {
217 NODE_GET(node, anima, AnimaNode); 160 NODE_GET(node, anima, AnimaNode);
218} 161}
219 162
220const SceneCamera* gfx_get_node_camera(const SceneNode* node) { 163const Camera* gfx_get_node_camera(const SceneNode* node) {
221 NODE_GET(node, camera, CameraNode); 164 NODE_GET(node, camera, CameraNode);
222} 165}
223 166
224SceneCamera* gfx_get_node_camera_mut(SceneNode* node) { 167Camera* gfx_get_node_camera_mut(SceneNode* node) {
225 NODE_GET(node, camera, CameraNode); 168 NODE_GET(node, camera, CameraNode);
226} 169}
227 170
diff --git a/gfx/src/scene/node_impl.h b/src/scene/node_impl.h
index c79f252..9e65588 100644
--- a/gfx/src/scene/node_impl.h
+++ b/src/scene/node_impl.h
@@ -1,10 +1,10 @@
1#pragma once 1#pragma once
2 2
3#include <gfx/scene/node.h> 3#include <gfx/scene.h>
4 4
5#include "types.h" 5#include "../types.h"
6 6
7#include <cstring.h> 7#include <math/camera.h>
8#include <math/mat4.h> 8#include <math/mat4.h>
9 9
10/// Scene node. 10/// Scene node.
diff --git a/gfx/src/scene/object.c b/src/scene/object.c
index e8e3ee6..ac86b39 100644
--- a/gfx/src/scene/object.c
+++ b/src/scene/object.c
@@ -2,9 +2,10 @@
2 2
3#include <gfx/core.h> 3#include <gfx/core.h>
4 4
5#include "mesh_impl.h" 5#include "memory.h"
6#include "node_impl.h" 6#include "render/llr_impl.h"
7#include "scene_memory.h" 7#include "scene/mesh_impl.h"
8#include "scene/node_impl.h"
8 9
9#include <assert.h> 10#include <assert.h>
10 11
@@ -57,11 +58,7 @@ SceneObject* gfx_make_object(const ObjectDesc* desc) {
57 58
58void gfx_destroy_object(SceneObject** object) { 59void gfx_destroy_object(SceneObject** object) {
59 assert(object); 60 assert(object);
60
61 if (*object) { 61 if (*object) {
62 if ((*object)->parent.val) {
63 gfx_del_node((*object)->parent);
64 }
65 mem_free_object(object); 62 mem_free_object(object);
66 } 63 }
67} 64}
diff --git a/gfx/src/scene/object_impl.h b/src/scene/object_impl.h
index 88f8e31..345d615 100644
--- a/gfx/src/scene/object_impl.h
+++ b/src/scene/object_impl.h
@@ -1,10 +1,8 @@
1#pragma once 1#pragma once
2 2
3#include <gfx/scene/object.h> 3#include <gfx/scene.h>
4 4
5#include "types.h" 5#include "../types.h"
6
7#include <math/mat4.h>
8 6
9typedef struct MeshLink { 7typedef struct MeshLink {
10 mesh_idx mesh; 8 mesh_idx mesh;
@@ -21,6 +19,5 @@ 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.
25 aabb3 box; 22 aabb3 box;
26} SceneObject; 23} SceneObject;
diff --git a/src/scene/scene.c b/src/scene/scene.c
new file mode 100644
index 0000000..52ddb58
--- /dev/null
+++ b/src/scene/scene.c
@@ -0,0 +1,31 @@
1#include "scene_impl.h"
2
3#include "memory.h"
4#include "node_impl.h"
5
6#include <assert.h>
7
8Scene* gfx_make_scene(void) {
9 Scene* scene = mem_alloc_scene();
10 scene->root = mem_get_node_index(gfx_make_node());
11 return scene;
12}
13
14void gfx_destroy_scene(Scene** scene) {
15 assert(scene);
16 if (*scene) {
17 SceneNode* node = mem_get_node((*scene)->root);
18 gfx_destroy_node(&node);
19 mem_free_scene(scene);
20 }
21}
22
23const SceneNode* gfx_get_scene_root(const Scene* scene) {
24 assert(scene);
25 return mem_get_node(scene->root);
26}
27
28SceneNode* gfx_get_scene_root_mut(Scene* scene) {
29 assert(scene);
30 return (SceneNode*)gfx_get_scene_root(scene);
31}
diff --git a/gfx/src/scene/scene_graph.h b/src/scene/scene_graph.h
index a26f828..36c3a98 100644
--- a/gfx/src/scene/scene_graph.h
+++ b/src/scene/scene_graph.h
@@ -1,40 +1,46 @@
1/// Functions for list manipulation. 1/// Functions for list manipulation.
2#pragma once 2#pragma once
3 3
4#include "scene_memory.h" 4#include "memory.h"
5 5
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 Camera *: 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/src/scene/scene_impl.h b/src/scene/scene_impl.h
new file mode 100644
index 0000000..ad2e892
--- /dev/null
+++ b/src/scene/scene_impl.h
@@ -0,0 +1,9 @@
1#pragma once
2
3#include <gfx/scene.h>
4
5#include "../types.h"
6
7typedef struct Scene {
8 node_idx root;
9} Scene;
diff --git a/gfx/src/scene/types.h b/src/types.h
index d0ffc41..d0ffc41 100644
--- a/gfx/src/scene/types.h
+++ b/src/types.h
diff --git a/gfx/src/util/geometry.c b/src/util/geometry.c
index afe0109..2ea0c82 100644
--- a/gfx/src/util/geometry.c
+++ b/src/util/geometry.c
@@ -17,12 +17,13 @@ static void make_quad_01_positions(vec2 positions[4]) {
17} 17}
18 18
19static GeometryDesc make_quad_desc(vec2 positions[4]) { 19static GeometryDesc make_quad_desc(vec2 positions[4]) {
20 GeometryDesc desc = (GeometryDesc){0}; 20 return (GeometryDesc){
21 desc.positions2d.data = positions; 21 .positions2d = (BufferView2d){.data = positions,
22 desc.positions2d.size_bytes = 4 * sizeof(vec2); 22 .size_bytes = 4 * sizeof(vec2),
23 desc.num_verts = 4; 23 .count = 4},
24 desc.type = TriangleStrip; 24 .num_verts = 4,
25 return desc; 25 .type = TriangleStrip
26 };
26} 27}
27 28
28Geometry* gfx_make_quad_11(GfxCore* gfxcore) { 29Geometry* gfx_make_quad_11(GfxCore* gfxcore) {
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..f4561f2 100644
--- a/gfx/src/util/skyquad.c
+++ b/src/util/skyquad.c
@@ -1,18 +1,11 @@
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/render/llr.h>
5#include <gfx/scene/light.h> 5#include <gfx/scene.h>
6#include <gfx/scene/material.h>
7#include <gfx/scene/mesh.h>
8#include <gfx/scene/node.h>
9#include <gfx/scene/object.h>
10#include <gfx/scene/scene.h>
11#include <gfx/util/geometry.h> 6#include <gfx/util/geometry.h>
12#include <gfx/util/shader.h> 7#include <gfx/util/shader.h>
13 8
14#include <math/vec4.h>
15
16#include <assert.h> 9#include <assert.h>
17 10
18SceneObject* gfx_make_skyquad(GfxCore* gfxcore, const Texture* texture) { 11SceneObject* gfx_make_skyquad(GfxCore* gfxcore, const Texture* texture) {
@@ -36,10 +29,9 @@ SceneObject* gfx_make_skyquad(GfxCore* gfxcore, const Texture* texture) {
36 } 29 }
37 30
38 MaterialDesc material_desc = (MaterialDesc){0}; 31 MaterialDesc material_desc = (MaterialDesc){0};
39 material_desc.uniforms[0] = (ShaderUniform){ 32 material_desc.uniforms[0] = (ShaderUniform){.type = UniformTexture,
40 .type = UniformTexture, 33 .value.texture = texture,
41 .value.texture = texture, 34 .name = sstring_make("Skyquad")};
42 .name = sstring_make("Skyquad")};
43 material_desc.num_uniforms = 1; 35 material_desc.num_uniforms = 1;
44 material = gfx_make_material(&material_desc); 36 material = gfx_make_material(&material_desc);
45 if (!material) { 37 if (!material) {