├── .clang-format ├── .gersemirc ├── .git-blame-ignore-revs ├── .gitattributes ├── .github └── workflows │ ├── check-changelog.yml │ ├── gh-pages.yml │ ├── macos-linux-windows-pixi.yml │ └── update-lockfiles.yml ├── .gitignore ├── .gitmodules ├── .pre-commit-config.yaml ├── .vscode └── settings.json ├── CHANGELOG.md ├── CMakeLists.txt ├── LICENSE ├── README.md ├── assets └── meshes │ ├── cube.obj │ └── teapot.obj ├── bindings ├── nanobind │ ├── CMakeLists.txt │ └── module.cpp └── python │ ├── CMakeLists.txt │ ├── candlewick │ ├── __init__.py │ └── video_context.py │ └── src │ ├── expose-mesh-data.cpp │ ├── expose-renderer.cpp │ ├── expose-visualizer.cpp │ ├── fwd.hpp │ └── module.cpp ├── doc ├── Doxyfile.extra.in ├── depth-prepass.png ├── primitives.png ├── robot-info-panel.png └── softshadows.png ├── examples ├── CMakeLists.txt ├── ColoredCube.cpp ├── LitMesh.cpp ├── MeshNormalsRgb.cpp ├── Triangle.cpp ├── Ur5WithSystems.cpp ├── Visualizer.cpp ├── lib │ ├── Common.cpp │ ├── Common.h │ ├── GenHeightfield.cpp │ ├── GenHeightfield.h │ ├── PerlinNoise.cpp │ └── PerlinNoise.h └── python │ ├── go2.py │ ├── show_robot_description.py │ └── ur10_loop.py ├── external └── CMakeLists.txt ├── pixi.lock ├── pixi.toml ├── process_shaders.py ├── scripts └── pixi │ └── activation.sh ├── shaders ├── compiled │ ├── BasicTriangle.vert.json │ ├── BasicTriangle.vert.msl │ ├── BasicTriangle.vert.spv │ ├── DrawQuad.vert.json │ ├── DrawQuad.vert.msl │ ├── DrawQuad.vert.spv │ ├── FrustumDebug.vert.json │ ├── FrustumDebug.vert.msl │ ├── FrustumDebug.vert.spv │ ├── Hud3dElement.frag.json │ ├── Hud3dElement.frag.msl │ ├── Hud3dElement.frag.spv │ ├── Hud3dElement.vert.json │ ├── Hud3dElement.vert.msl │ ├── Hud3dElement.vert.spv │ ├── PbrBasic.frag.json │ ├── PbrBasic.frag.msl │ ├── PbrBasic.frag.spv │ ├── PbrBasic.vert.json │ ├── PbrBasic.vert.msl │ ├── PbrBasic.vert.spv │ ├── PbrTransparent.frag.json │ ├── PbrTransparent.frag.msl │ ├── PbrTransparent.frag.spv │ ├── PointSprite.frag.json │ ├── PointSprite.frag.msl │ ├── PointSprite.frag.spv │ ├── PointSprite.vert.json │ ├── PointSprite.vert.msl │ ├── PointSprite.vert.spv │ ├── RenderDepth.frag.json │ ├── RenderDepth.frag.msl │ ├── RenderDepth.frag.spv │ ├── SSAO.frag.json │ ├── SSAO.frag.msl │ ├── SSAO.frag.spv │ ├── SSAOblur.frag.json │ ├── SSAOblur.frag.msl │ ├── SSAOblur.frag.spv │ ├── ScreenSpaceShadows.frag.json │ ├── ScreenSpaceShadows.frag.msl │ ├── ScreenSpaceShadows.frag.spv │ ├── ShadowCast.frag.json │ ├── ShadowCast.frag.msl │ ├── ShadowCast.frag.spv │ ├── ShadowCast.vert.json │ ├── ShadowCast.vert.msl │ ├── ShadowCast.vert.spv │ ├── SolidColor.frag.json │ ├── SolidColor.frag.msl │ ├── SolidColor.frag.spv │ ├── VertexColor.frag.json │ ├── VertexColor.frag.msl │ ├── VertexColor.frag.spv │ ├── VertexColor.vert.json │ ├── VertexColor.vert.msl │ ├── VertexColor.vert.spv │ ├── VertexNormal.frag.json │ ├── VertexNormal.frag.msl │ ├── VertexNormal.frag.spv │ ├── VertexNormal.vert.json │ ├── VertexNormal.vert.msl │ ├── VertexNormal.vert.spv │ ├── WBOITComposite.frag.json │ ├── WBOITComposite.frag.msl │ └── WBOITComposite.frag.spv └── src │ ├── BasicTriangle.vert │ ├── DrawQuad.vert │ ├── FrustumDebug.vert │ ├── Hud3dElement.frag │ ├── Hud3dElement.vert │ ├── PbrBasic.frag │ ├── PbrBasic.vert │ ├── PbrTransparent.frag │ ├── PointSprite.frag │ ├── PointSprite.vert │ ├── RenderDepth.frag │ ├── SSAO.frag │ ├── SSAOblur.frag │ ├── ScreenSpaceShadows.frag │ ├── ShadowCast.frag │ ├── ShadowCast.vert │ ├── SolidColor.frag │ ├── VertexColor.frag │ ├── VertexColor.vert │ ├── VertexNormal.frag │ ├── VertexNormal.vert │ ├── WBOITComposite.frag │ ├── config.glsl │ ├── pbr_lighting.glsl │ ├── tone_mapping.glsl │ └── utils.glsl ├── src ├── CMakeLists.txt └── candlewick │ ├── core │ ├── Camera.cpp │ ├── Camera.h │ ├── CameraControls.h │ ├── Collision.h │ ├── CommandBuffer.cpp │ ├── CommandBuffer.h │ ├── Components.cpp │ ├── Components.h │ ├── Core.h │ ├── DebugScene.cpp │ ├── DebugScene.h │ ├── DefaultVertex.h │ ├── DepthAndShadowPass.cpp │ ├── DepthAndShadowPass.h │ ├── Device.cpp │ ├── Device.h │ ├── FileDialogGui.cpp │ ├── GuiSystem.cpp │ ├── GuiSystem.h │ ├── LightUniforms.h │ ├── MaterialUniform.h │ ├── Mesh.cpp │ ├── Mesh.h │ ├── MeshLayout.h │ ├── RenderContext.cpp │ ├── RenderContext.h │ ├── Renderer.h │ ├── Scene.h │ ├── Shader.cpp │ ├── Shader.h │ ├── Tags.h │ ├── Texture.cpp │ ├── Texture.h │ ├── TransformUniforms.h │ ├── Window.h │ ├── debug │ │ ├── DepthViz.cpp │ │ ├── DepthViz.h │ │ ├── Frustum.cpp │ │ └── Frustum.h │ ├── errors.cpp │ ├── errors.h │ ├── math_types.h │ └── math_util.cpp │ ├── multibody │ ├── Components.h │ ├── LoadCoalGeometries.cpp │ ├── LoadCoalGeometries.h │ ├── LoadCoalPrimitives.h │ ├── LoadPinocchioGeometry.cpp │ ├── LoadPinocchioGeometry.h │ ├── Multibody.h │ ├── RobotDebug.cpp │ ├── RobotDebug.h │ ├── RobotScene.cpp │ ├── RobotScene.h │ ├── Visualizer.cpp │ ├── Visualizer.h │ └── internal │ │ ├── pinocchio_info_gui.cpp │ │ └── visualizer_gui.cpp │ ├── posteffects │ ├── SSAO.cpp │ ├── SSAO.h │ ├── ScreenSpaceShadows.cpp │ └── ScreenSpaceShadows.h │ ├── primitives │ ├── Arrow.cpp │ ├── Arrow.h │ ├── Capsule.cpp │ ├── Capsule.h │ ├── Cone.cpp │ ├── Cone.h │ ├── Cube.cpp │ ├── Cube.h │ ├── Cylinder.cpp │ ├── Cylinder.h │ ├── Grid.cpp │ ├── Grid.h │ ├── Heightfield.cpp │ ├── Heightfield.h │ ├── Internal.cpp │ ├── Internal.h │ ├── Plane.cpp │ ├── Plane.h │ ├── Primitives.h │ ├── Sphere.cpp │ └── Sphere.h │ ├── third-party │ ├── .clang-format-ignore │ ├── download.sh │ ├── float16_t.hpp │ ├── fpng.cpp │ └── fpng.h │ └── utils │ ├── LoadMaterial.cpp │ ├── LoadMaterial.h │ ├── LoadMesh.cpp │ ├── LoadMesh.h │ ├── MeshData.cpp │ ├── MeshData.h │ ├── MeshDataView.cpp │ ├── MeshDataView.h │ ├── MeshTransforms.cpp │ ├── MeshTransforms.h │ ├── PixelFormatConversion.cpp │ ├── PixelFormatConversion.h │ ├── StridedView.h │ ├── Utils.h │ ├── VideoRecorder.cpp │ ├── VideoRecorder.h │ ├── WriteTextureToImage.cpp │ └── WriteTextureToImage.h └── tests ├── CMakeLists.txt ├── TestMeshData.cpp └── TestStrided.cpp /.clang-format: -------------------------------------------------------------------------------- 1 | --- 2 | Language: Cpp 3 | BasedOnStyle: LLVM 4 | SortIncludes: false 5 | 6 | IndentPPDirectives: None 7 | NamespaceIndentation: Inner 8 | 9 | BreakConstructorInitializers: BeforeComma 10 | PackConstructorInitializers: NextLine 11 | -------------------------------------------------------------------------------- /.gersemirc: -------------------------------------------------------------------------------- 1 | definitions: [./CMakeLists.txt,./cmake,./src,./tests/CMakeLists.txt] 2 | line_length: 80 3 | indent: 2 4 | warn_about_unknown_commands: false 5 | -------------------------------------------------------------------------------- /.git-blame-ignore-revs: -------------------------------------------------------------------------------- 1 | # run clang formatting (ManifoldFR 2025-05-13) 2 | e1a864ca76047c7338214ee00d9a63de1c7a70d8 3 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | # SCM syntax highlighting & preventing 3-way merges 2 | pixi.lock merge=binary linguist-language=YAML linguist-generated=true 3 | -------------------------------------------------------------------------------- /.github/workflows/check-changelog.yml: -------------------------------------------------------------------------------- 1 | name: Check-changelog 2 | on: 3 | pull_request: 4 | types: [assigned, opened, synchronize, reopened, labeled, unlabeled] 5 | branches: 6 | - main 7 | jobs: 8 | check-changelog: 9 | name: Check changelog action 10 | runs-on: ubuntu-latest 11 | steps: 12 | - uses: tarides/changelog-check-action@v3 13 | with: 14 | changelog: CHANGELOG.md 15 | -------------------------------------------------------------------------------- /.github/workflows/gh-pages.yml: -------------------------------------------------------------------------------- 1 | name: gh-pages 2 | on: 3 | push: 4 | branches: 5 | - main 6 | workflow_dispatch: 7 | 8 | permissions: 9 | pages: write 10 | id-token: write 11 | 12 | concurrency: 13 | group: "pages" 14 | cancel-in-progress: false 15 | 16 | jobs: 17 | build: 18 | runs-on: ubuntu-latest 19 | steps: 20 | - uses: actions/checkout@v4 21 | with: 22 | submodules: recursive 23 | # ensure git describe --tags works (for cmake modules to set PROJECT_VERSION) 24 | fetch-depth: 0 25 | fetch-tags: true 26 | 27 | - uses: prefix-dev/setup-pixi@v0.8.8 28 | with: 29 | cache: true 30 | environments: doc 31 | 32 | - name: Build documentation 33 | shell: bash -l {0} 34 | run: | 35 | pixi run -e doc doc 36 | 37 | - name: Upload to GitHub pages 38 | uses: actions/upload-pages-artifact@v3 39 | with: 40 | path: build/doc/doxygen-html 41 | 42 | deploy: 43 | runs-on: ubuntu-latest 44 | needs: build 45 | environment: 46 | name: github-pages 47 | url: ${{ steps.deployment.outputs.page_url }} 48 | steps: 49 | - name: Deploy GitHub Pages site 50 | uses: actions/deploy-pages@v4 51 | id: deployment 52 | -------------------------------------------------------------------------------- /.github/workflows/macos-linux-windows-pixi.yml: -------------------------------------------------------------------------------- 1 | name: CI - macOS/Linux (pixi) 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | paths-ignore: 8 | - .gitignore 9 | - '*.md' 10 | - LICENSE 11 | - .pre-commit-config.yaml 12 | - CHANGELOG.md 13 | pull_request: 14 | paths-ignore: 15 | - .gitignore 16 | - '*.md' 17 | - LICENSE 18 | - .pre-commit-config.yaml 19 | - CHANGELOG.md 20 | concurrency: 21 | group: ${{ github.workflow }}-${{ github.ref }} 22 | cancel-in-progress: true 23 | 24 | jobs: 25 | candlewick-pixi: 26 | name: ${{ matrix.os }} - env ${{ matrix.environment }} (${{ matrix.build_type }}) 27 | runs-on: ${{ matrix.os }} 28 | env: 29 | CCACHE_BASEDIR: "${GITHUB_WORKSPACE}" 30 | CCACHE_DIR: "${GITHUB_WORKSPACE}/.ccache" 31 | CCACHE_COMPRESS: true 32 | CCACHE_COMPRESSLEVEL: 5 33 | # Since pixi will install a compiler, the compiler mtime will be changed. 34 | # This can invalidate the cache (https://ccache.dev/manual/latest.html#config_compiler_check) 35 | CCACHE_COMPILERCHECK: content 36 | BUILD_ADVANCED_TESTING: ${{ matrix.BUILD_ADVANCED_TESTING }} 37 | 38 | strategy: 39 | fail-fast: false 40 | matrix: 41 | os: [ubuntu-latest, macos-latest] 42 | environment: [all-test] 43 | build_type: [Release] 44 | 45 | steps: 46 | - uses: actions/checkout@v4 47 | with: 48 | submodules: recursive 49 | 50 | - uses: actions/cache@v4 51 | with: 52 | path: .ccache 53 | key: ccache-macos-linux-pixi-${{ matrix.os }}-${{ matrix.build_type }}-${{ matrix.environment }}-${{ github.sha }} 54 | restore-keys: ccache-macos-linux-pixi-${{ matrix.os }}-${{ matrix.build_type }}-${{ matrix.environment }}- 55 | 56 | - uses: prefix-dev/setup-pixi@v0.8.4 57 | with: 58 | cache: true 59 | environments: ${{ matrix.environment }} 60 | 61 | - name: Build candlewick [macOS/Linux] 62 | env: 63 | CMAKE_BUILD_PARALLEL_LEVEL: 2 64 | CANDLEWICK_BUILD_TYPE: ${{ matrix.build_type }} 65 | run: | 66 | # Clear ccache statistics 67 | pixi run -e ${{ matrix.environment }} ccache -z 68 | 69 | pixi run -e ${{ matrix.environment }} build 70 | 71 | - name: Test candlewick [macOS/Linux] 72 | run: | 73 | pixi run -e ${{ matrix.environment }} ctest --test-dir build --output-on-failure 74 | 75 | - name: Display ccache statistics 76 | shell: bash -el {0} 77 | run: | 78 | pixi run -e ${{ matrix.environment }} ccache -sv 79 | 80 | check: 81 | if: always() 82 | name: check-macos-linux-pixi 83 | 84 | needs: 85 | - candlewick-pixi 86 | 87 | runs-on: Ubuntu-latest 88 | 89 | steps: 90 | - name: Decide whether the needed jobs succeeded or failed 91 | uses: re-actors/alls-green@release/v1 92 | with: 93 | jobs: ${{ toJSON(needs) }} 94 | -------------------------------------------------------------------------------- /.github/workflows/update-lockfiles.yml: -------------------------------------------------------------------------------- 1 | name: Update lockfiles 2 | 3 | permissions: 4 | contents: write 5 | pull-requests: write 6 | 7 | on: 8 | workflow_dispatch: 9 | schedule: 10 | - cron: 0 5 1 * * 11 | 12 | jobs: 13 | pixi-update: 14 | runs-on: ubuntu-latest 15 | steps: 16 | - uses: actions/checkout@v4 17 | - name: Set up pixi 18 | uses: prefix-dev/setup-pixi@v0.8.8 19 | with: 20 | run-install: false 21 | - name: Update lockfiles 22 | run: | 23 | set -o pipefail 24 | pixi update --json | pixi exec pixi-diff-to-markdown >> diff.md 25 | - name: Create pull request 26 | uses: peter-evans/create-pull-request@v7 27 | with: 28 | token: ${{ secrets.GITHUB_TOKEN }} 29 | commit-message: Update pixi lockfile 30 | title: Update pixi lockfile 31 | body-path: diff.md 32 | branch: update-pixi 33 | base: main 34 | labels: pixi 35 | delete-branch: true 36 | add-paths: pixi.lock 37 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .cache/ 2 | build*/ 3 | !.vscode/ 4 | frag.spv 5 | vert.spv 6 | compile_commands.json 7 | *.mp4 8 | 9 | # pixi environments 10 | .pixi 11 | *.egg-info 12 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "external/imgui"] 2 | path = external/imgui 3 | url = https://github.com/ocornut/imgui.git 4 | [submodule "cmake"] 5 | path = cmake 6 | url = https://github.com/jrl-umi3218/jrl-cmakemodules.git 7 | [submodule "doc/doxygen-awesome-css"] 8 | path = doc/doxygen-awesome-css 9 | url = https://github.com/jothepro/doxygen-awesome-css.git 10 | [submodule "examples/robot_descriptions_cpp"] 11 | path = examples/robot_descriptions_cpp 12 | url = https://github.com/ManifoldFR/robot_descriptions_cpp.git 13 | -------------------------------------------------------------------------------- /.pre-commit-config.yaml: -------------------------------------------------------------------------------- 1 | ci: 2 | autoupdate_branch: devel 3 | autofix_prs: false 4 | autoupdate_schedule: quarterly 5 | submodules: true 6 | repos: 7 | - repo: https://github.com/pre-commit/mirrors-clang-format 8 | rev: v20.1.0 9 | hooks: 10 | - id: clang-format 11 | types_or: [] 12 | types: [text] 13 | files: '\.(cpp|cxx|c|h|hpp|hxx|txx)$' 14 | - repo: https://github.com/pre-commit/pre-commit-hooks 15 | rev: v5.0.0 16 | hooks: 17 | - id: check-added-large-files 18 | - id: check-case-conflict 19 | - id: check-yaml 20 | exclude: ^packaging/conda/ 21 | - id: detect-private-key 22 | - id: end-of-file-fixer 23 | - id: mixed-line-ending 24 | - id: check-merge-conflict 25 | - id: trailing-whitespace 26 | exclude: | 27 | (?x)^( 28 | doc/doxygen-awesome.* 29 | )$ 30 | - repo: https://github.com/astral-sh/ruff-pre-commit 31 | rev: 'v0.11.4' 32 | hooks: 33 | - id: ruff 34 | args: [--fix, --exit-non-zero-on-fix] 35 | - id: ruff-format 36 | - repo: https://github.com/BlankSpruce/gersemi 37 | rev: 0.19.2 38 | hooks: 39 | - id: gersemi 40 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "glsllint.glslangValidatorArgs": ["-V100"] 3 | } 4 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | BSD 2-Clause License 2 | 3 | Copyright (c) 2024-2025, Inria 4 | All rights reserved. 5 | 6 | Redistribution and use in source and binary forms, with or without 7 | modification, are permitted provided that the following conditions are met: 8 | 9 | 1. Redistributions of source code must retain the above copyright notice, this 10 | list of conditions and the following disclaimer. 11 | 12 | 2. Redistributions in binary form must reproduce the above copyright notice, 13 | this list of conditions and the following disclaimer in the documentation 14 | and/or other materials provided with the distribution. 15 | 16 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 17 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 18 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 19 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 20 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 21 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 22 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 23 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 24 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 25 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 26 | -------------------------------------------------------------------------------- /assets/meshes/cube.obj: -------------------------------------------------------------------------------- 1 | # Blender v2.76 (sub 0) OBJ File: '' 2 | # www.blender.org 3 | mtllib cube.mtl 4 | o Cube 5 | v 1.000000 -1.000000 -1.000000 6 | v 1.000000 -1.000000 1.000000 7 | v -1.000000 -1.000000 1.000000 8 | v -1.000000 -1.000000 -1.000000 9 | v 1.000000 1.000000 -0.999999 10 | v 0.999999 1.000000 1.000001 11 | v -1.000000 1.000000 1.000000 12 | v -1.000000 1.000000 -1.000000 13 | vt 1.000000 0.333333 14 | vt 1.000000 0.666667 15 | vt 0.666667 0.666667 16 | vt 0.666667 0.333333 17 | vt 0.666667 0.000000 18 | vt 0.000000 0.333333 19 | vt 0.000000 0.000000 20 | vt 0.333333 0.000000 21 | vt 0.333333 1.000000 22 | vt 0.000000 1.000000 23 | vt 0.000000 0.666667 24 | vt 0.333333 0.333333 25 | vt 0.333333 0.666667 26 | vt 1.000000 0.000000 27 | vn 0.000000 -1.000000 0.000000 28 | vn 0.000000 1.000000 0.000000 29 | vn 1.000000 0.000000 0.000000 30 | vn -0.000000 0.000000 1.000000 31 | vn -1.000000 -0.000000 -0.000000 32 | vn 0.000000 0.000000 -1.000000 33 | usemtl Material 34 | s off 35 | f 2/1/1 3/2/1 4/3/1 36 | f 8/1/2 7/4/2 6/5/2 37 | f 5/6/3 6/7/3 2/8/3 38 | f 6/8/4 7/5/4 3/4/4 39 | f 3/9/5 7/10/5 8/11/5 40 | f 1/12/6 4/13/6 8/11/6 41 | f 1/4/1 2/1/1 4/3/1 42 | f 5/14/2 8/1/2 6/5/2 43 | f 1/12/3 5/6/3 2/8/3 44 | f 2/12/4 6/8/4 3/4/4 45 | f 4/13/5 3/9/5 8/11/5 46 | f 5/6/6 1/12/6 8/11/6 47 | -------------------------------------------------------------------------------- /bindings/nanobind/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | if(CMAKE_VERSION VERSION_LESS 3.18) 2 | set(DEV_MODULE Development) 3 | else() 4 | set(DEV_MODULE Development.Module) 5 | endif() 6 | 7 | find_package(Python 3.10 COMPONENTS Interpreter ${DEV_MODULE} REQUIRED) 8 | 9 | execute_process( 10 | COMMAND "${Python_EXECUTABLE}" -m nanobind --cmake_dir 11 | OUTPUT_STRIP_TRAILING_WHITESPACE 12 | OUTPUT_VARIABLE nanobind_ROOT 13 | ) 14 | find_package(nanobind CONFIG REQUIRED) 15 | 16 | function(_add_nanobind_bindings) 17 | set(_python_SOURCES module.cpp) 18 | nanobind_add_module(pycandlewick_nb NB_STATIC NB_SUPPRESS_WARNINGS ${_python_SOURCES}) 19 | target_link_libraries(pycandlewick_nb PRIVATE candlewick) 20 | set_target_properties( 21 | pycandlewick_nb 22 | PROPERTIES LIBRARY_OUTPUT_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR} 23 | ) 24 | 25 | if(BUILD_PINOCCHIO_VISUALIZER) 26 | target_compile_definitions( 27 | pycandlewick_nb 28 | PRIVATE CANDLEWICK_PYTHON_PINOCCHIO_SUPPORT 29 | ) 30 | endif() 31 | endfunction() 32 | 33 | _add_nanobind_bindings() 34 | -------------------------------------------------------------------------------- /bindings/nanobind/module.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include "candlewick/core/RenderContext.h" 4 | 5 | namespace nb = nanobind; 6 | using namespace nb::literals; 7 | using namespace candlewick; 8 | 9 | #define _c(name) name = SDL_GPU_SHADERFORMAT_##name 10 | enum class ShaderFormat_wrap : SDL_GPUShaderFormat { 11 | _c(INVALID), 12 | _c(PRIVATE), 13 | _c(SPIRV), 14 | _c(DXBC), 15 | _c(DXIL), 16 | _c(MSL), 17 | _c(METALLIB) 18 | }; 19 | #undef _c 20 | 21 | void exposeCore(nb::module_ &m) { 22 | nb::class_(m, "Device").def("driverName", &Device::driverName); 23 | 24 | m.def("get_num_gpu_drivers", SDL_GetNumGPUDrivers, 25 | "Get number of available GPU drivers."); 26 | 27 | m.def("get_gpu_driver_name", SDL_GetGPUDriver, ("index"_a), 28 | "Get the name of the GPU driver of the corresponding index."); 29 | 30 | #define _c(name) value(#name, ShaderFormat_wrap::name) 31 | nb::enum_(m, "ShaderFormat") 32 | ._c(INVALID) 33 | ._c(PRIVATE) 34 | ._c(SPIRV) 35 | ._c(DXBC) 36 | ._c(DXIL) 37 | ._c(MSL) 38 | ._c(METALLIB); 39 | #undef _c 40 | 41 | m.def( 42 | "auto_detect_shader_format_subset", 43 | [](nb::str name) { 44 | return ShaderFormat_wrap( 45 | auto_detect_shader_format_subset(name.c_str())); 46 | }, 47 | "name"_a); 48 | 49 | nb::class_(m, "RenderContext") 50 | .def_ro("device", &RenderContext::device); 51 | } 52 | 53 | NB_MODULE(pycandlewick_nb, m) { exposeCore(m); } 54 | -------------------------------------------------------------------------------- /bindings/python/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | include(${PROJECT_SOURCE_DIR}/cmake/python.cmake) 2 | if(GENERATE_PYTHON_STUBS) 3 | include(${PROJECT_SOURCE_DIR}/cmake/stubs.cmake) 4 | endif(GENERATE_PYTHON_STUBS) 5 | 6 | find_package(eigenpy 3.2.0 REQUIRED) 7 | 8 | set( 9 | pycandlewick_SOURCES 10 | src/expose-mesh-data.cpp 11 | src/expose-renderer.cpp 12 | src/module.cpp 13 | ) 14 | 15 | set(PYLIB_INSTALL_DIR ${PYTHON_SITELIB}/${PROJECT_NAME}) 16 | 17 | Python3_add_library(pycandlewick MODULE WITH_SOABI ${pycandlewick_SOURCES}) 18 | target_link_libraries(pycandlewick PRIVATE candlewick eigenpy::eigenpy) 19 | target_include_directories( 20 | pycandlewick 21 | PRIVATE $ 22 | ) 23 | if(BUILD_PINOCCHIO_VISUALIZER) 24 | target_sources(pycandlewick PRIVATE src/expose-visualizer.cpp) 25 | # Add definition for the core library 26 | target_compile_definitions( 27 | pycandlewick 28 | PRIVATE CANDLEWICK_PYTHON_PINOCCHIO_SUPPORT 29 | ) 30 | endif() 31 | 32 | set_target_properties( 33 | pycandlewick 34 | PROPERTIES LIBRARY_OUTPUT_DIRECTORY ${PROJECT_NAME} 35 | ) 36 | if(UNIX) 37 | GET_RELATIVE_RPATH(${PYLIB_INSTALL_DIR} PYLIB_INSTALL_RPATH) 38 | set_target_properties( 39 | pycandlewick 40 | PROPERTIES INSTALL_RPATH "${PYLIB_INSTALL_RPATH}" 41 | ) 42 | endif() 43 | 44 | install( 45 | TARGETS pycandlewick 46 | EXPORT ${TARGETS_EXPORT_NAME} 47 | LIBRARY DESTINATION ${PYLIB_INSTALL_DIR} 48 | ) 49 | 50 | if(GENERATE_PYTHON_STUBS) 51 | LOAD_STUBGEN() 52 | GENERATE_STUBS( 53 | ${CMAKE_CURRENT_BINARY_DIR} 54 | ${PROJECT_NAME} 55 | ${PYTHON_SITELIB} 56 | pycandlewick 57 | ) 58 | endif(GENERATE_PYTHON_STUBS) 59 | 60 | set(PYTHON_FILES __init__.py video_context.py) 61 | 62 | foreach(pyfile ${PYTHON_FILES}) 63 | PYTHON_INSTALL_ON_SITE(${PROJECT_NAME} ${pyfile}) 64 | endforeach() 65 | -------------------------------------------------------------------------------- /bindings/python/candlewick/__init__.py: -------------------------------------------------------------------------------- 1 | # ruff: noqa: F401, F403, F405 2 | from .pycandlewick import * # noqa 3 | from .pycandlewick import __version__ 4 | from . import video_context 5 | 6 | 7 | def _process(): 8 | import sys 9 | import inspect 10 | from pathlib import Path 11 | from . import pycandlewick 12 | 13 | lib_name = "candlewick" 14 | submodules = inspect.getmembers(pycandlewick, inspect.ismodule) 15 | for mod_info in submodules: 16 | mod_name = "{}.{}".format(lib_name, mod_info[0]) 17 | sys.modules[mod_name] = mod_info[1] 18 | mod_info[1].__file__ = pycandlewick.__file__ 19 | mod_info[1].__name__ = mod_name 20 | 21 | share_dir = Path(__file__).parent / "../../../../share" 22 | shaders_dir = share_dir.resolve() / "candlewick" / "shaders" 23 | compiled_shaders_dir = shaders_dir / "compiled" 24 | pycandlewick.setShadersDirectory(str(compiled_shaders_dir)) 25 | 26 | 27 | _process() 28 | -------------------------------------------------------------------------------- /bindings/python/candlewick/video_context.py: -------------------------------------------------------------------------------- 1 | from .pycandlewick.multibody import Visualizer 2 | from contextlib import contextmanager 3 | 4 | 5 | @contextmanager 6 | def create_recorder_context(viz: Visualizer, filename: str, /, fps: int = 30): 7 | viz.startRecording(filename, fps) 8 | try: 9 | yield 10 | finally: 11 | viz.stopRecording() 12 | 13 | 14 | __all__ = ["create_recorder_context"] 15 | -------------------------------------------------------------------------------- /bindings/python/src/expose-mesh-data.cpp: -------------------------------------------------------------------------------- 1 | #include "fwd.hpp" 2 | #include "candlewick/utils/MeshData.h" 3 | 4 | using namespace candlewick; 5 | 6 | void exposeMeshData() { 7 | 8 | bp::class_("MeshLayout", bp::no_init); 9 | 10 | bp::class_("MeshData", bp::no_init) 11 | .def_readonly("layout", &MeshData::layout) 12 | .add_property("numVertices", &MeshData::numVertices, 13 | "Number of vertices.") 14 | .add_property("vertexSize", &MeshData::vertexSize) 15 | .add_property("vertexBytes", &MeshData::vertexBytes, 16 | "Number of bytes in the vertex data.") 17 | .add_property("numIndices", &MeshData::numIndices, "Number of indices.") 18 | .add_property("isIndexed", &MeshData::isIndexed, 19 | "Whether the mesh is indexed.") 20 | 21 | ; 22 | } 23 | -------------------------------------------------------------------------------- /bindings/python/src/expose-renderer.cpp: -------------------------------------------------------------------------------- 1 | #include "fwd.hpp" 2 | #include "candlewick/core/RenderContext.h" 3 | 4 | using namespace candlewick; 5 | 6 | BOOST_PYTHON_FUNCTION_OVERLOADS(adfs_overloads, 7 | auto_detect_shader_format_subset, 0, 1) 8 | 9 | void exposeRenderer() { 10 | bp::class_("Device", bp::no_init) 11 | .def("driverName", &Device::driverName, ("self"_a)) 12 | .def("shaderFormats", &Device::shaderFormats); 13 | 14 | bp::def("get_num_gpu_drivers", SDL_GetNumGPUDrivers, 15 | "Get number of available GPU drivers."); 16 | 17 | bp::def("get_gpu_driver_name", SDL_GetGPUDriver, ("index"_a), 18 | "Get the name of the GPU driver of the corresponding index."); 19 | 20 | bp::def( 21 | "get_gpu_drivers", 22 | +[] { 23 | int n = SDL_GetNumGPUDrivers(); 24 | bp::list a; 25 | if (n > 0) { 26 | for (int i = 0; i < n; i++) 27 | a.append(SDL_GetGPUDriver(i)); 28 | } 29 | return a; 30 | }, 31 | "Get all GPU drivers' names."); 32 | 33 | bp::def("auto_detect_shader_format_subset", auto_detect_shader_format_subset, 34 | adfs_overloads{ 35 | ("driver_name"_a = NULL), 36 | "Automatically detect the compatible set of shader formats."}); 37 | 38 | bp::class_("RenderContext", bp::no_init) 39 | .def_readonly("device", &RenderContext::device); 40 | } 41 | -------------------------------------------------------------------------------- /bindings/python/src/expose-visualizer.cpp: -------------------------------------------------------------------------------- 1 | #include "fwd.hpp" 2 | #include 3 | 4 | #include "candlewick/multibody/Visualizer.h" 5 | #include 6 | 7 | #include 8 | #include 9 | #include 10 | 11 | using namespace candlewick::multibody; 12 | 13 | #define DEF_PROP_PROXY(name) \ 14 | add_property(#name, bp::make_function( \ 15 | +[](Visualizer &v) -> auto & { return v.name(); }, \ 16 | bp::return_internal_reference<>())) 17 | 18 | void exposeVisualizer() { 19 | using pinocchio::python::VisualizerPythonVisitor; 20 | 21 | eigenpy::OptionalConverter::registration(); 22 | bp::class_("VisualizerConfig", bp::init<>("self"_a)) 23 | .def_readwrite("width", &Visualizer::Config::width) 24 | .def_readwrite("height", &Visualizer::Config::height); 25 | 26 | bp::class_("Visualizer", bp::no_init) 27 | .def(bp::init( 29 | ("self"_a, "config", "model", "visual_model"))) 30 | .def(bp::init( 33 | ("self"_a, "config", "model", "visual_model", "data", "visual_data"))) 34 | .def(VisualizerPythonVisitor{}) 35 | .def_readonly("renderer", &Visualizer::renderer) 36 | .def_readwrite("worldSceneBounds", &Visualizer::worldSceneBounds) 37 | .def( 38 | "takeScreenshot", 39 | +[](Visualizer &viz, const std::string &filename) { 40 | viz.takeScreenshot(filename); 41 | }, 42 | ("self"_a, "filename"), "Save a screenshot to the specified file.") 43 | .def( 44 | "startRecording", 45 | +[](Visualizer &viz, const std::string &filename, int fps) { 46 | viz.startRecording(filename, fps); 47 | }, 48 | ("self"_a, "filename"_a, "fps"_a = 30)) 49 | .def("stopRecording", &Visualizer::stopRecording, "self"_a) 50 | // fix for Pinocchio 3.5.0 51 | #if PINOCCHIO_VERSION_AT_MOST(3, 5, 0) 52 | .DEF_PROP_PROXY(model) 53 | .DEF_PROP_PROXY(visualModel) 54 | .DEF_PROP_PROXY(collisionModel) 55 | .DEF_PROP_PROXY(data) 56 | .DEF_PROP_PROXY(visualData) 57 | .DEF_PROP_PROXY(collisionData) 58 | #endif 59 | .add_property("shouldExit", &Visualizer::shouldExit); 60 | } 61 | #undef DEF_PROP_PROXY 62 | -------------------------------------------------------------------------------- /bindings/python/src/fwd.hpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | namespace bp = boost::python; 4 | 5 | inline bp::arg operator""_a(const char *name, std::size_t) { 6 | return bp::arg(name); 7 | } 8 | 9 | inline std::string get_scope_name(bp::scope scope) { 10 | return std::string(bp::extract(scope.attr("__name__"))); 11 | } 12 | 13 | /// \brief Create or retrieve a Python scope (that is, a class or module 14 | /// namespace). 15 | /// 16 | /// \returns The submodule with the input name. 17 | inline bp::object get_namespace(const std::string &name) { 18 | bp::scope cur_scope; // current scope 19 | const std::string complete_name = get_scope_name(cur_scope) + "." + name; 20 | bp::object submodule(bp::borrowed(PyImport_AddModule(complete_name.c_str()))); 21 | cur_scope.attr(name.c_str()) = submodule; 22 | return submodule; 23 | } 24 | -------------------------------------------------------------------------------- /bindings/python/src/module.cpp: -------------------------------------------------------------------------------- 1 | #include "fwd.hpp" 2 | #include "candlewick/config.h" 3 | #include "candlewick/core/Shader.h" 4 | 5 | #include 6 | 7 | using namespace candlewick; 8 | 9 | void exposeMeshData(); 10 | void exposeRenderer(); 11 | #ifdef CANDLEWICK_PYTHON_PINOCCHIO_SUPPORT 12 | void exposeVisualizer(); 13 | #endif 14 | 15 | BOOST_PYTHON_MODULE(pycandlewick) { 16 | bp::import("eigenpy"); 17 | bp::scope current_scope; 18 | current_scope.attr("__version__") = bp::str(CANDLEWICK_VERSION); 19 | 20 | if (!SDL_InitSubSystem(SDL_INIT_VIDEO)) { 21 | throw std::runtime_error(std::format( 22 | "Failed to initialize SDL subsystems: \'%s\'", SDL_GetError())); 23 | } 24 | bp::def("setShadersDirectory", &setShadersDirectory, ("path"_a)); 25 | bp::def("currentShaderDirectory", ¤tShaderDirectory); 26 | 27 | // Register SDL_Quit() as a function to call when interpreter exits. 28 | Py_AtExit(SDL_Quit); 29 | 30 | exposeMeshData(); 31 | exposeRenderer(); 32 | #ifdef CANDLEWICK_PYTHON_PINOCCHIO_SUPPORT 33 | { 34 | bp::scope submod = get_namespace("multibody"); 35 | exposeVisualizer(); 36 | } 37 | #endif 38 | } 39 | -------------------------------------------------------------------------------- /doc/Doxyfile.extra.in: -------------------------------------------------------------------------------- 1 | HTML_EXTRA_STYLESHEET = @AWESOME_CSS_DIR@/doxygen-awesome.css 2 | HTML_COLORSTYLE = LIGHT 3 | DISABLE_INDEX = NO 4 | FULL_SIDEBAR = NO 5 | EXCLUDE_SYMBOLS = detail::* 6 | EXCLUDE_PATTERNS = @PROJECT_SOURCE_DIR@/src/candlewick/third-party/* 7 | IMAGE_PATH = @PROJECT_SOURCE_DIR@/doc 8 | USE_MDFILE_AS_MAINPAGE = @PROJECT_SOURCE_DIR@/README.md 9 | -------------------------------------------------------------------------------- /doc/depth-prepass.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Simple-Robotics/candlewick/ea464b81ee7736da4996ea929869fc7b6542e7f7/doc/depth-prepass.png -------------------------------------------------------------------------------- /doc/primitives.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Simple-Robotics/candlewick/ea464b81ee7736da4996ea929869fc7b6542e7f7/doc/primitives.png -------------------------------------------------------------------------------- /doc/robot-info-panel.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Simple-Robotics/candlewick/ea464b81ee7736da4996ea929869fc7b6542e7f7/doc/robot-info-panel.png -------------------------------------------------------------------------------- /doc/softshadows.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Simple-Robotics/candlewick/ea464b81ee7736da4996ea929869fc7b6542e7f7/doc/softshadows.png -------------------------------------------------------------------------------- /examples/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | find_package(CLI11 CONFIG REQUIRED) 2 | 3 | add_library(ExamplesCommon STATIC lib/Common.cpp lib/PerlinNoise.cpp) 4 | target_link_libraries(ExamplesCommon PUBLIC candlewick_core imgui_headers) 5 | target_include_directories( 6 | ExamplesCommon 7 | PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}/lib 8 | ) 9 | 10 | if(BUILD_PINOCCHIO_VISUALIZER) 11 | target_sources(ExamplesCommon PRIVATE lib/GenHeightfield.cpp) 12 | target_link_libraries(ExamplesCommon PUBLIC candlewick_multibody) 13 | 14 | set(ROBOT_DESCRIPTIONS_CPP_TESTING OFF CACHE BOOL "") 15 | add_subdirectory(robot_descriptions_cpp EXCLUDE_FROM_ALL) 16 | endif() 17 | 18 | function(add_candlewick_example filename) 19 | cmake_path(GET filename STEM name) 20 | message(STATUS "Add C++ example ${name}") 21 | add_executable(${name} ${filename}) 22 | # always link to ExamplesCommon 23 | target_link_libraries(${name} PRIVATE ExamplesCommon) 24 | foreach(arg ${ARGN}) 25 | target_link_libraries(${name} PRIVATE ${arg}) 26 | endforeach() 27 | endfunction() 28 | 29 | add_candlewick_example(Triangle.cpp) 30 | add_candlewick_example(ColoredCube.cpp) 31 | add_candlewick_example(MeshNormalsRgb.cpp) 32 | add_candlewick_example(LitMesh.cpp) 33 | if(BUILD_PINOCCHIO_VISUALIZER) 34 | add_candlewick_example( 35 | Ur5WithSystems.cpp 36 | CLI11::CLI11 37 | robot_descriptions_cpp 38 | ) 39 | add_candlewick_example(Visualizer.cpp CLI11::CLI11 robot_descriptions_cpp) 40 | endif() 41 | -------------------------------------------------------------------------------- /examples/Visualizer.cpp: -------------------------------------------------------------------------------- 1 | #include "candlewick/multibody/Visualizer.h" 2 | 3 | #include 4 | 5 | #include 6 | #include 7 | #include 8 | 9 | #include 10 | #include 11 | #include 12 | 13 | using namespace candlewick::multibody; 14 | using std::chrono::steady_clock; 15 | 16 | int main(int argc, char **argv) { 17 | CLI::App app{"Visualizer example"}; 18 | argv = app.ensure_utf8(argv); 19 | double fps; 20 | app.add_option("--fps", fps, "Framerate") 21 | ->default_val(60); 22 | 23 | CLI11_PARSE(app, argc, argv); 24 | 25 | pin::Model model; 26 | pin::GeometryModel geom_model; 27 | robot_descriptions::loadModelsFromToml("ur.toml", "ur5_gripper", model, 28 | &geom_model, NULL); 29 | 30 | Visualizer visualizer{{1920, 1280}, model, geom_model}; 31 | assert(!visualizer.hasExternalData()); 32 | 33 | Eigen::VectorXd q0 = pin::neutral(model); 34 | Eigen::VectorXd q1 = pin::randomConfiguration(model); 35 | 36 | double dt = 1. / static_cast(fps); 37 | std::chrono::duration dt_ms{dt}; 38 | Eigen::VectorXd q = q0; 39 | double t = 0.; 40 | 41 | while (!visualizer.shouldExit()) { 42 | const auto now = steady_clock::now(); 43 | 44 | double alpha = std::sin(t); 45 | pin::interpolate(model, q0, q1, alpha, q); 46 | 47 | visualizer.display(q); 48 | std::this_thread::sleep_until(now + dt_ms); 49 | 50 | t += dt; 51 | } 52 | return 0; 53 | } 54 | -------------------------------------------------------------------------------- /examples/lib/Common.cpp: -------------------------------------------------------------------------------- 1 | #include "candlewick/core/math_types.h" 2 | #include "candlewick/primitives/Cube.h" 3 | #include "candlewick/utils/MeshTransforms.h" 4 | 5 | #include "Common.h" 6 | #include 7 | #include 8 | 9 | using namespace candlewick; 10 | 11 | MeshData loadCube(float size, const Float2 &loc) { 12 | MeshData m = loadCubeSolid().toOwned(); 13 | m.material.metalness = 0.4f; 14 | m.material.baseColor = 0xaab023ff_rgbaf; 15 | Eigen::Affine3f tr; 16 | tr.setIdentity(); 17 | tr.translate(Float3{loc[0], loc[1], 0.f}); 18 | tr.scale(0.5f * size); 19 | tr.translate(Float3{0, 0, 1.2f}); 20 | apply3DTransformInPlace(m, tr); 21 | return m; 22 | } 23 | -------------------------------------------------------------------------------- /examples/lib/Common.h: -------------------------------------------------------------------------------- 1 | #include "candlewick/core/Core.h" 2 | #include "candlewick/core/Device.h" 3 | #include "candlewick/core/math_types.h" 4 | #include "candlewick/utils/Utils.h" 5 | 6 | #include 7 | 8 | using candlewick::Device; 9 | using candlewick::Float2; 10 | using candlewick::MeshData; 11 | using candlewick::NoInit; 12 | 13 | const float kScrollZoom = 0.05f; 14 | 15 | MeshData loadCube(float size, const Float2 &loc); 16 | -------------------------------------------------------------------------------- /examples/lib/GenHeightfield.cpp: -------------------------------------------------------------------------------- 1 | #include "GenHeightfield.h" 2 | #include "PerlinNoise.h" 3 | #include 4 | 5 | std::shared_ptr> 6 | generatePerlinNoiseHeightfield(Uint32 seed, Uint32 nx, float x_dim) { 7 | PerlinNoise noise{seed}; 8 | 9 | const Uint32 ny = nx; 10 | const float y_dim = x_dim; 11 | Eigen::MatrixXf heights{nx, ny}; 12 | heights.setZero(); 13 | 14 | std::vector amplitudes = {0.1f, 0.3f}; 15 | float base = 2.f; 16 | size_t num_octaves = amplitudes.size(); 17 | 18 | float x, y; 19 | for (Uint32 i = 0; i < nx; i++) { 20 | x = float(i) * x_dim; 21 | for (Uint32 j = 0; j < ny; j++) { 22 | y = float(j) * y_dim; 23 | for (auto k = 0ul; k < num_octaves; k++) { 24 | float w = powf(base, float(k)); 25 | heights(i, j) += amplitudes[k] * noise.noise(w * x, w * y); 26 | } 27 | } 28 | } 29 | assert(!heights.hasNaN()); 30 | return std::make_shared>( 31 | x_dim, y_dim, heights.cast()); 32 | } 33 | -------------------------------------------------------------------------------- /examples/lib/GenHeightfield.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | 6 | /// \brief Generate a heightfield using Perlin noise. 7 | /// \param seed Random seed 8 | /// \param nx Number of subdivisions in x-direction 9 | /// \param x_dim Full width of the heigthfield 10 | std::shared_ptr> 11 | generatePerlinNoiseHeightfield(Uint32 seed, Uint32 nx, float x_dim); 12 | -------------------------------------------------------------------------------- /examples/lib/PerlinNoise.cpp: -------------------------------------------------------------------------------- 1 | #include "PerlinNoise.h" 2 | #include 3 | 4 | static std::array REFERENCE_PERM = { 5 | 151, 160, 137, 91, 90, 15, 131, 13, 201, 95, 96, 53, 194, 233, 7, 6 | 225, 140, 36, 103, 30, 69, 142, 8, 99, 37, 240, 21, 10, 23, 190, 7 | 6, 148, 247, 120, 234, 75, 0, 26, 197, 62, 94, 252, 219, 203, 117, 8 | 35, 11, 32, 57, 177, 33, 88, 237, 149, 56, 87, 174, 20, 125, 136, 9 | 171, 168, 68, 175, 74, 165, 71, 134, 139, 48, 27, 166, 77, 146, 158, 10 | 231, 83, 111, 229, 122, 60, 211, 133, 230, 220, 105, 92, 41, 55, 46, 11 | 245, 40, 244, 102, 143, 54, 65, 25, 63, 161, 1, 216, 80, 73, 209, 12 | 76, 132, 187, 208, 89, 18, 169, 200, 196, 135, 130, 116, 188, 159, 86, 13 | 164, 100, 109, 198, 173, 186, 3, 64, 52, 217, 226, 250, 124, 123, 5, 14 | 202, 38, 147, 118, 126, 255, 82, 85, 212, 207, 206, 59, 227, 47, 16, 15 | 58, 17, 182, 189, 28, 42, 223, 183, 170, 213, 119, 248, 152, 2, 44, 16 | 154, 163, 70, 221, 153, 101, 155, 167, 43, 172, 9, 129, 22, 39, 253, 17 | 19, 98, 108, 110, 79, 113, 224, 232, 178, 185, 112, 104, 218, 246, 97, 18 | 228, 251, 34, 242, 193, 238, 210, 144, 12, 191, 179, 162, 241, 81, 51, 19 | 145, 235, 249, 14, 239, 107, 49, 192, 214, 31, 181, 199, 106, 157, 184, 20 | 84, 204, 176, 115, 121, 50, 45, 127, 4, 150, 254, 138, 236, 205, 93, 21 | 222, 114, 67, 29, 24, 72, 243, 141, 128, 195, 78, 66, 215, 61, 156, 22 | 180}; 23 | 24 | PerlinNoise::PerlinNoise(Uint32 seed) { 25 | // Initialize permutation array with indices 26 | for (auto i = 0ul; i < 256ul; i++) { 27 | perm[i] = REFERENCE_PERM[i]; 28 | } 29 | 30 | std::mt19937 gen{seed}; 31 | std::normal_distribution dist{}; 32 | 33 | for (auto i = 0ul; i < 256ul; i++) { 34 | // Duplicate array to avoid index wrapping 35 | perm[256ul + i] = perm[i]; 36 | 37 | gradients[i] = Float2{dist(gen), dist(gen)}.normalized(); 38 | heights[i] = dist(gen); 39 | } 40 | } 41 | 42 | float PerlinNoise::noise(float x, float y) const { 43 | // Find unit square that contains point 44 | auto x0 = static_cast(std::floor(x)); 45 | auto y0 = static_cast(std::floor(y)); 46 | 47 | // offset within cell 48 | float fx = x - float(x0); 49 | float fy = y - float(y0); 50 | 51 | Float2 g00 = get_gradient(x0, y0); 52 | Float2 g10 = get_gradient(x0 + 1, y0); 53 | Float2 g01 = get_gradient(x0, y0 + 1); 54 | Float2 g11 = get_gradient(x0 + 1, y0 + 1); 55 | float z00 = get_height(x0, y0); 56 | float z10 = get_height(x0 + 1, y0); 57 | float z01 = get_height(x0, y0 + 1); 58 | float z11 = get_height(x0 + 1, y0 + 1); 59 | 60 | // scalar displacement values: 61 | // gradients' component in 4 displacement dirs 62 | float d00 = z00 + g00.dot(Float2{fx, fy}); 63 | float d10 = z10 + g10.dot(Float2{fx - 1, fy}); 64 | float d01 = z01 + g01.dot(Float2{fx, fy - 1}); 65 | float d11 = z11 + g11.dot(Float2{fx - 1, fy - 1}); 66 | 67 | float nxy = fade(1 - fx, 1 - fy) * d00; 68 | nxy += fade(fx, 1 - fy) * d10; 69 | nxy += fade(1 - fx, fy) * d01; 70 | nxy += fade(fx, fy) * d11; 71 | return nxy; 72 | } 73 | -------------------------------------------------------------------------------- /examples/lib/PerlinNoise.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include "candlewick/core/math_types.h" 3 | #include 4 | #include 5 | 6 | using candlewick::Float2; 7 | 8 | /// \brief A 2D perlin noise generator 9 | struct PerlinNoise { 10 | explicit PerlinNoise(Uint32 seed = 1); 11 | 12 | // 2D Perlin noise 13 | float noise(float x, float y) const; 14 | 15 | // permutation array 16 | std::array perm; 17 | // gradients at grid points 18 | std::array gradients; 19 | std::array heights; 20 | 21 | Uint16 hash(Uint32 i, Uint32 j) const { 22 | return perm[perm[i & 255] + (j & 255)] & 255; 23 | } 24 | 25 | /// Lookup gradient in cell ( \p i, \p j ). 26 | Float2 get_gradient(Uint32 i, Uint32 j) const { 27 | return gradients[hash(i, j)]; 28 | } 29 | float get_height(Uint32 i, Uint32 j) const { return heights[hash(i, j)]; } 30 | 31 | /// Perlin fade function, t^5 - 15t^4 + 10t^3 32 | static float fade(float t) { return t * t * t * (t * (t * 6 - 15) + 10); } 33 | static float fade(float x, float y) { return fade(x) * fade(y); } 34 | }; 35 | -------------------------------------------------------------------------------- /examples/python/go2.py: -------------------------------------------------------------------------------- 1 | import candlewick as cdw 2 | from candlewick.multibody import Visualizer, VisualizerConfig 3 | 4 | try: 5 | import go2_description as go2d 6 | except ImportError: 7 | import warnings 8 | 9 | warnings.warn( 10 | "This example requires the go2_description package, which was not found. Download it at: https://github.com/inria-paris-robotics-lab/go2_description" 11 | ) 12 | raise 13 | import tqdm 14 | 15 | 16 | print(f"Current shader directory: {cdw.currentShaderDirectory()}") 17 | 18 | robot = go2d.loadGo2() 19 | 20 | rmodel = robot.model 21 | rdata = robot.data 22 | visual_model = robot.visual_model 23 | visual_data = robot.visual_data 24 | 25 | q0 = rmodel.referenceConfigurations["standing"] 26 | dt = 0.01 27 | 28 | config = VisualizerConfig() 29 | config.width = 1600 30 | config.height = 900 31 | viz = Visualizer(config, rmodel, visual_model, data=rdata, visual_data=visual_data) 32 | assert viz.hasExternalData() 33 | 34 | print( 35 | "Visualizer has renderer:", 36 | viz.renderer, 37 | "driver name:", 38 | viz.renderer.device.driverName(), 39 | ) 40 | print("Scene bounds:", viz.worldSceneBounds) 41 | 42 | with cdw.video_context.create_recorder_context(viz, "go2_record.mp4"): 43 | for i in tqdm.tqdm(range(1000)): 44 | if viz.shouldExit: 45 | break 46 | viz.display(q0) 47 | # time.sleep(dt) 48 | 49 | print("Goodbye...") 50 | -------------------------------------------------------------------------------- /examples/python/show_robot_description.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # -*- coding: utf-8 -*- 3 | # 4 | # SPDX-License-Identifier: Apache-2.0 5 | # Copyright 2025 Inria 6 | 7 | """ 8 | Load a robot description selected from the command line in Candlewick. 9 | 10 | This example requires Pinocchio, installed by e.g. `conda install pinocchio`, 11 | and `robot_descriptions`, installed by e.g. `conda install robot_descriptions`. 12 | """ 13 | 14 | import argparse 15 | 16 | import pinocchio as pin 17 | 18 | from candlewick.multibody import Visualizer, VisualizerConfig 19 | 20 | try: 21 | from robot_descriptions.loaders.pinocchio import load_robot_description 22 | except ImportError as import_error: 23 | raise ImportError( 24 | "Robot descriptions package not found, " 25 | "install it by e.g. `conda install robot_descriptions`." 26 | ) from import_error 27 | 28 | if __name__ == "__main__": 29 | parser = argparse.ArgumentParser(description=__doc__) 30 | parser.add_argument("name", help="name of the robot description") 31 | parser.add_argument( 32 | "--width", 33 | help="window width in px", 34 | type=int, 35 | default=1600, 36 | ) 37 | parser.add_argument( 38 | "--height", 39 | help="window height in px", 40 | type=int, 41 | default=900, 42 | ) 43 | parser.add_argument( 44 | "--altitude", 45 | help="robot altitude offset in [m]", 46 | type=float, 47 | default=0.0, 48 | ) 49 | args = parser.parse_args() 50 | 51 | try: 52 | robot = load_robot_description(args.name, root_joint=pin.JointModelFreeFlyer()) 53 | except ModuleNotFoundError: 54 | robot = load_robot_description( 55 | f"{args.name}_description", root_joint=pin.JointModelFreeFlyer() 56 | ) 57 | 58 | config = VisualizerConfig() 59 | config.width = args.width 60 | config.height = args.height 61 | visualizer = Visualizer(config, robot.model, robot.visual_model) 62 | q = robot.q0.copy() 63 | q[2] += float(args.altitude) 64 | while not visualizer.shouldExit: 65 | visualizer.display(q) 66 | -------------------------------------------------------------------------------- /examples/python/ur10_loop.py: -------------------------------------------------------------------------------- 1 | import example_robot_data as erd 2 | import pinocchio as pin 3 | import time 4 | import numpy as np 5 | from candlewick.multibody import Visualizer, VisualizerConfig 6 | 7 | robot = erd.load("ur10") 8 | model: pin.Model = robot.model 9 | data: pin.Data = robot.data 10 | visual_model = robot.visual_model 11 | 12 | config = VisualizerConfig() 13 | config.width = 1280 14 | config.height = 720 15 | viz = Visualizer(config, model, visual_model) 16 | 17 | print( 18 | "Visualizer has renderer:", 19 | viz.renderer, 20 | "driver name:", 21 | viz.renderer.device.driverName(), 22 | ) 23 | 24 | q0 = pin.neutral(model) 25 | q1 = pin.randomConfiguration(model) 26 | 27 | t = 0.0 28 | dt = 0.02 29 | M = pin.SE3.Identity() 30 | ee_name = "ee_link" 31 | ee_id = model.getFrameId(ee_name) 32 | print(viz.model) 33 | 34 | for i in range(1000): 35 | alpha = np.sin(t) 36 | q = pin.interpolate(model, q0, q1, alpha) 37 | pin.framesForwardKinematics(model, data, q) 38 | M = data.oMf[ee_id] 39 | if viz.shouldExit: 40 | break 41 | viz.display(q) 42 | time.sleep(dt) 43 | t += dt 44 | -------------------------------------------------------------------------------- /external/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | set(IMGUI_DIR ${PROJECT_SOURCE_DIR}/external/imgui) 2 | set( 3 | IMGUI_SRC 4 | ${IMGUI_DIR}/imgui.cpp 5 | ${IMGUI_DIR}/imgui_demo.cpp 6 | ${IMGUI_DIR}/imgui_draw.cpp 7 | ${IMGUI_DIR}/imgui_tables.cpp 8 | ${IMGUI_DIR}/imgui_widgets.cpp 9 | ${IMGUI_DIR}/backends/imgui_impl_sdl3.cpp 10 | ${IMGUI_DIR}/backends/imgui_impl_sdlgpu3.cpp 11 | PARENT_SCOPE 12 | ) 13 | 14 | set(IMGUI_HEADERS ${IMGUI_DIR}/imconfig.h ${IMGUI_DIR}/imgui.h) 15 | 16 | add_library(imgui_headers INTERFACE) 17 | target_link_libraries(imgui_headers INTERFACE SDL3::Headers) 18 | target_include_directories( 19 | imgui_headers 20 | INTERFACE 21 | $ 22 | $ 23 | ) 24 | set_target_properties(imgui_headers PROPERTIES PUBLIC_HEADER "${IMGUI_HEADERS}") 25 | -------------------------------------------------------------------------------- /pixi.toml: -------------------------------------------------------------------------------- 1 | [workspace] 2 | authors = ["ManifoldFR "] 3 | channels = ["conda-forge"] 4 | name = "candlewick" 5 | platforms = ["osx-64", "osx-arm64", "linux-64"] 6 | version = "0.3.1" 7 | license = "BSD-2-Clause" 8 | license-file = "LICENSE" 9 | 10 | [build-dependencies] 11 | cmake = ">=3.26" 12 | ninja = ">=1.12.1" 13 | cxx-compiler = ">=1.9.0" 14 | pkg-config = ">=0.29.2" 15 | ccache = ">=4.9.1" 16 | 17 | [dependencies] 18 | sdl3 = ">=3.2.12,<4" 19 | magic_enum = ">=0.9.7,<0.10" 20 | entt = ">=3.15.0,<4" 21 | nlohmann_json = ">=3.12.0,<4" 22 | coal = ">=3.0.1,<4" 23 | assimp = ">=5.4.3,<6" 24 | eigenpy = ">=3.10.3,<4" 25 | 26 | [activation] 27 | scripts = ["scripts/pixi/activation.sh"] 28 | 29 | [tasks] 30 | configure = { cmd = [ 31 | "cmake", 32 | "-GNinja", 33 | "-B", 34 | "build", 35 | "-S", 36 | ".", 37 | "-DCMAKE_INSTALL_PREFIX=$CONDA_PREFIX", 38 | "-DCMAKE_BUILD_TYPE=$CANDLEWICK_BUILD_TYPE", 39 | "-DBUILD_PYTHON_BINDINGS=$CANDLEWICK_WITH_PYTHON", 40 | "-DGENERATE_PYTHON_STUBS=OFF", 41 | "-DBUILD_PINOCCHIO_VISUALIZER=$CANDLEWICK_WITH_PINOCCHIO", 42 | "-DBUILD_TESTING=$CANDLEWICK_BUILD_TESTS", 43 | ] } 44 | build = { cmd = "cmake --build build --target all", depends-on = ["configure"]} 45 | clean = { cmd = "rm -rf build" } 46 | doc = { cmd = "cmake --build build --target doc", depends-on = ["configure"] } 47 | 48 | [feature.pinocchio.dependencies] 49 | pinocchio = ">=3.5" 50 | ffmpeg = ">=7" 51 | 52 | [feature.pinocchio.activation] 53 | env = { CANDLEWICK_WITH_PINOCCHIO = "ON" } 54 | 55 | [feature.test.dependencies] 56 | gtest = "*" 57 | 58 | [feature.test.activation] 59 | env = { CANDLEWICK_BUILD_TESTS = "ON" } 60 | 61 | [feature.doc.dependencies] 62 | doxygen = "*" 63 | 64 | [environments] 65 | pinocchio = { features = ["pinocchio"] } 66 | # test core 67 | test = { features = ["test"] } 68 | # test all (pinocchio) 69 | all-test = { features = ["pinocchio", "test"]} 70 | # doc environment 71 | doc = { features = ["doc", "default"] } 72 | -------------------------------------------------------------------------------- /process_shaders.py: -------------------------------------------------------------------------------- 1 | # Small util script to run our shaders through glslc 2 | # https://github.com/google/shaderc 3 | import subprocess 4 | import argparse 5 | import pathlib as pt 6 | 7 | parser = argparse.ArgumentParser() 8 | parser.add_argument("shader_name") 9 | group = parser.add_mutually_exclusive_group(required=True) 10 | group.add_argument( 11 | "--stages", nargs="+", help="Shader stages to be compiled to SPIR-V." 12 | ) 13 | group.add_argument( 14 | "--all-stages", 15 | "-a", 16 | action="store_true", 17 | help="Process all available shader stages.", 18 | ) 19 | parser.add_argument( 20 | "--no-cross", 21 | action="store_true", 22 | help="Skip the SPIR-V -> MSL transpiling and JSON metadata step.", 23 | ) 24 | args = parser.parse_args() 25 | 26 | shader_name = args.shader_name 27 | SHADER_SRC_DIR = pt.Path("shaders/src") 28 | SHADER_OUT_DIR = pt.Path("shaders/compiled") 29 | print("Shader src dir:", SHADER_SRC_DIR.absolute()) 30 | if args.all_stages: 31 | stages = list(SHADER_SRC_DIR.glob(f"{shader_name}.[a-z]*")) 32 | else: 33 | stages = [SHADER_SRC_DIR / f"{shader_name}.{stage}" for stage in args.stages] 34 | 35 | print(f"Processing files: {stages}") 36 | assert len(stages) > 0, "No stages found!" 37 | 38 | for stage_file in stages: 39 | assert stage_file.exists() 40 | spv_file = SHADER_OUT_DIR / stage_file.name 41 | spv_file = spv_file.with_suffix(spv_file.suffix + ".spv") 42 | proc = subprocess.run( 43 | [ 44 | "glslc", 45 | stage_file, 46 | f"-I{SHADER_SRC_DIR}", 47 | "--target-env=vulkan1.2", 48 | "-Werror", 49 | "-o", 50 | spv_file, 51 | ], 52 | shell=False, 53 | ) 54 | print(f"Compiling SPV file {spv_file}") 55 | 56 | if not args.no_cross: 57 | for ext in (".json", ".msl"): 58 | out_file = spv_file.with_suffix(ext) 59 | proc2 = subprocess.run( 60 | [ 61 | "shadercross", 62 | spv_file, 63 | "-o", 64 | out_file, 65 | "--msl-version", 66 | "2.1.0", 67 | ], 68 | shell=False, 69 | ) 70 | 71 | if args.no_cross: 72 | print("Skipping SPIR-V -> MSL transpiling and JSON metadata steps.") 73 | -------------------------------------------------------------------------------- /scripts/pixi/activation.sh: -------------------------------------------------------------------------------- 1 | #! /bin/bash 2 | # Activation script 3 | 4 | # Remove flags setup from cxx-compiler 5 | unset CFLAGS 6 | unset CPPFLAGS 7 | unset CXXFLAGS 8 | unset DEBUG_CFLAGS 9 | unset DEBUG_CPPFLAGS 10 | unset DEBUG_CXXFLAGS 11 | unset LDFLAGS 12 | 13 | if [[ $host_alias == *"apple"* ]]; 14 | then 15 | # On OSX setting the rpath and -L it's important to use the conda libc++ instead of the system one. 16 | # If conda-forge use install_name_tool to package some libs, -headerpad_max_install_names is then mandatory 17 | export LDFLAGS="-Wl,-headerpad_max_install_names -Wl,-rpath,$CONDA_PREFIX/lib -L$CONDA_PREFIX/lib" 18 | elif [[ $host_alias == *"linux"* ]]; 19 | then 20 | # On GNU/Linux, I don't know if these flags are mandatory with g++ but 21 | # it allow to use clang++ as compiler 22 | export LDFLAGS="-Wl,-rpath,$CONDA_PREFIX/lib -Wl,-rpath-link,$CONDA_PREFIX/lib -L$CONDA_PREFIX/lib" 23 | 24 | # Conda compiler is named x86_64-conda-linux-gnu-c++, ccache can't resolve it 25 | # (https://ccache.dev/manual/latest.html#config_compiler_type) 26 | export CCACHE_COMPILERTYPE=gcc 27 | fi 28 | 29 | # Setup ccache 30 | export CMAKE_CXX_COMPILER_LAUNCHER=ccache 31 | 32 | # Create compile_commands.json for language server 33 | export CMAKE_EXPORT_COMPILE_COMMANDS=1 34 | 35 | # Activate color output with Ninja 36 | export CMAKE_COLOR_DIAGNOSTICS=1 37 | 38 | # Set default build value only if not previously set 39 | export CANDLEWICK_BUILD_TYPE=${CANDLEWICK_BUILD_TYPE:=Release} 40 | export CANDLEWICK_WITH_PYTHON=${CANDLEWICK_WITH_PYTHON:=ON} 41 | export CANDLEWICK_WITH_PINOCCHIO=${CANDLEWICK_WITH_PINOCCHIO:=OFF} 42 | export CANDLEWICK_BUILD_TESTS=${CANDLEWICK_BUILD_TESTS:=OFF} 43 | -------------------------------------------------------------------------------- /shaders/compiled/BasicTriangle.vert.json: -------------------------------------------------------------------------------- 1 | { "samplers": 0, "storage_textures": 0, "storage_buffers": 0, "uniform_buffers": 0 } 2 | -------------------------------------------------------------------------------- /shaders/compiled/BasicTriangle.vert.msl: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | using namespace metal; 5 | 6 | struct main0_out 7 | { 8 | float4 outColor [[user(locn0)]]; 9 | float4 gl_Position [[position]]; 10 | }; 11 | 12 | vertex main0_out main0(uint gl_VertexIndex [[vertex_id]]) 13 | { 14 | main0_out out = {}; 15 | float2 pos; 16 | if (int(gl_VertexIndex) == 0) 17 | { 18 | pos = float2(-1.0); 19 | out.outColor = float4(1.0, 0.0, 0.0, 1.0); 20 | } 21 | else 22 | { 23 | if (int(gl_VertexIndex) == 1) 24 | { 25 | pos = float2(1.0, -1.0); 26 | out.outColor = float4(0.0, 1.0, 0.0, 1.0); 27 | } 28 | else 29 | { 30 | if (int(gl_VertexIndex) == 2) 31 | { 32 | pos = float2(0.0, 1.0); 33 | out.outColor = float4(0.0, 0.0, 1.0, 1.0); 34 | } 35 | } 36 | } 37 | out.gl_Position = float4(pos, 0.0, 1.0); 38 | return out; 39 | } 40 | -------------------------------------------------------------------------------- /shaders/compiled/BasicTriangle.vert.spv: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Simple-Robotics/candlewick/ea464b81ee7736da4996ea929869fc7b6542e7f7/shaders/compiled/BasicTriangle.vert.spv -------------------------------------------------------------------------------- /shaders/compiled/DrawQuad.vert.json: -------------------------------------------------------------------------------- 1 | { "samplers": 0, "storage_textures": 0, "storage_buffers": 0, "uniform_buffers": 0 } 2 | -------------------------------------------------------------------------------- /shaders/compiled/DrawQuad.vert.msl: -------------------------------------------------------------------------------- 1 | #pragma clang diagnostic ignored "-Wmissing-prototypes" 2 | #pragma clang diagnostic ignored "-Wmissing-braces" 3 | 4 | #include 5 | #include 6 | 7 | using namespace metal; 8 | 9 | template 10 | struct spvUnsafeArray 11 | { 12 | T elements[Num ? Num : 1]; 13 | 14 | thread T& operator [] (size_t pos) thread 15 | { 16 | return elements[pos]; 17 | } 18 | constexpr const thread T& operator [] (size_t pos) const thread 19 | { 20 | return elements[pos]; 21 | } 22 | 23 | device T& operator [] (size_t pos) device 24 | { 25 | return elements[pos]; 26 | } 27 | constexpr const device T& operator [] (size_t pos) const device 28 | { 29 | return elements[pos]; 30 | } 31 | 32 | constexpr const constant T& operator [] (size_t pos) const constant 33 | { 34 | return elements[pos]; 35 | } 36 | 37 | threadgroup T& operator [] (size_t pos) threadgroup 38 | { 39 | return elements[pos]; 40 | } 41 | constexpr const threadgroup T& operator [] (size_t pos) const threadgroup 42 | { 43 | return elements[pos]; 44 | } 45 | }; 46 | 47 | constant spvUnsafeArray _19 = spvUnsafeArray({ float2(-1.0), float2(1.0, -1.0), float2(1.0), float2(-1.0), float2(1.0), float2(-1.0, 1.0) }); 48 | 49 | struct main0_out 50 | { 51 | float2 outUV [[user(locn0)]]; 52 | float4 gl_Position [[position]]; 53 | }; 54 | 55 | vertex main0_out main0(uint gl_VertexIndex [[vertex_id]]) 56 | { 57 | main0_out out = {}; 58 | float2 pos = _19[int(gl_VertexIndex)]; 59 | out.gl_Position = float4(pos, 0.0, 1.0); 60 | out.outUV.x = (pos.x * 0.5) + 0.5; 61 | out.outUV.y = 0.5 - (pos.y * 0.5); 62 | return out; 63 | } 64 | -------------------------------------------------------------------------------- /shaders/compiled/DrawQuad.vert.spv: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Simple-Robotics/candlewick/ea464b81ee7736da4996ea929869fc7b6542e7f7/shaders/compiled/DrawQuad.vert.spv -------------------------------------------------------------------------------- /shaders/compiled/FrustumDebug.vert.json: -------------------------------------------------------------------------------- 1 | { "samplers": 0, "storage_textures": 0, "storage_buffers": 0, "uniform_buffers": 1 } 2 | -------------------------------------------------------------------------------- /shaders/compiled/FrustumDebug.vert.spv: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Simple-Robotics/candlewick/ea464b81ee7736da4996ea929869fc7b6542e7f7/shaders/compiled/FrustumDebug.vert.spv -------------------------------------------------------------------------------- /shaders/compiled/Hud3dElement.frag.json: -------------------------------------------------------------------------------- 1 | { "samplers": 0, "storage_textures": 0, "storage_buffers": 0, "uniform_buffers": 1 } 2 | -------------------------------------------------------------------------------- /shaders/compiled/Hud3dElement.frag.msl: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | using namespace metal; 5 | 6 | struct Color 7 | { 8 | float4 color; 9 | }; 10 | 11 | struct main0_out 12 | { 13 | float4 fragColor [[color(0)]]; 14 | }; 15 | 16 | fragment main0_out main0(constant Color& _12 [[buffer(0)]]) 17 | { 18 | main0_out out = {}; 19 | out.fragColor = _12.color; 20 | return out; 21 | } 22 | -------------------------------------------------------------------------------- /shaders/compiled/Hud3dElement.frag.spv: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Simple-Robotics/candlewick/ea464b81ee7736da4996ea929869fc7b6542e7f7/shaders/compiled/Hud3dElement.frag.spv -------------------------------------------------------------------------------- /shaders/compiled/Hud3dElement.vert.json: -------------------------------------------------------------------------------- 1 | { "samplers": 0, "storage_textures": 0, "storage_buffers": 0, "uniform_buffers": 1 } 2 | -------------------------------------------------------------------------------- /shaders/compiled/Hud3dElement.vert.msl: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | using namespace metal; 5 | 6 | struct TranformBlock 7 | { 8 | float4x4 mvp; 9 | }; 10 | 11 | struct main0_out 12 | { 13 | float4 gl_Position [[position]]; 14 | }; 15 | 16 | struct main0_in 17 | { 18 | float3 inPosition [[attribute(0)]]; 19 | }; 20 | 21 | vertex main0_out main0(main0_in in [[stage_in]], constant TranformBlock& _19 [[buffer(0)]]) 22 | { 23 | main0_out out = {}; 24 | out.gl_Position = _19.mvp * float4(in.inPosition, 1.0); 25 | return out; 26 | } 27 | -------------------------------------------------------------------------------- /shaders/compiled/Hud3dElement.vert.spv: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Simple-Robotics/candlewick/ea464b81ee7736da4996ea929869fc7b6542e7f7/shaders/compiled/Hud3dElement.vert.spv -------------------------------------------------------------------------------- /shaders/compiled/PbrBasic.frag.json: -------------------------------------------------------------------------------- 1 | { "samplers": 2, "storage_textures": 0, "storage_buffers": 0, "uniform_buffers": 4 } 2 | -------------------------------------------------------------------------------- /shaders/compiled/PbrBasic.frag.spv: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Simple-Robotics/candlewick/ea464b81ee7736da4996ea929869fc7b6542e7f7/shaders/compiled/PbrBasic.frag.spv -------------------------------------------------------------------------------- /shaders/compiled/PbrBasic.vert.json: -------------------------------------------------------------------------------- 1 | { "samplers": 0, "storage_textures": 0, "storage_buffers": 0, "uniform_buffers": 2 } 2 | -------------------------------------------------------------------------------- /shaders/compiled/PbrBasic.vert.msl: -------------------------------------------------------------------------------- 1 | #pragma clang diagnostic ignored "-Wmissing-prototypes" 2 | #pragma clang diagnostic ignored "-Wmissing-braces" 3 | 4 | #include 5 | #include 6 | 7 | using namespace metal; 8 | 9 | template 10 | struct spvUnsafeArray 11 | { 12 | T elements[Num ? Num : 1]; 13 | 14 | thread T& operator [] (size_t pos) thread 15 | { 16 | return elements[pos]; 17 | } 18 | constexpr const thread T& operator [] (size_t pos) const thread 19 | { 20 | return elements[pos]; 21 | } 22 | 23 | device T& operator [] (size_t pos) device 24 | { 25 | return elements[pos]; 26 | } 27 | constexpr const device T& operator [] (size_t pos) const device 28 | { 29 | return elements[pos]; 30 | } 31 | 32 | constexpr const constant T& operator [] (size_t pos) const constant 33 | { 34 | return elements[pos]; 35 | } 36 | 37 | threadgroup T& operator [] (size_t pos) threadgroup 38 | { 39 | return elements[pos]; 40 | } 41 | constexpr const threadgroup T& operator [] (size_t pos) const threadgroup 42 | { 43 | return elements[pos]; 44 | } 45 | }; 46 | 47 | struct TranformBlock 48 | { 49 | float4x4 modelView; 50 | float4x4 mvp; 51 | float3x3 normalMatrix; 52 | }; 53 | 54 | struct LightBlockV 55 | { 56 | float4x4 mvp[4]; 57 | int numLights; 58 | }; 59 | 60 | struct main0_out 61 | { 62 | float3 fragViewPos [[user(locn0)]]; 63 | float3 fragViewNormal [[user(locn1)]]; 64 | float3 fragLightPos_0 [[user(locn2)]]; 65 | float3 fragLightPos_1 [[user(locn3)]]; 66 | float3 fragLightPos_2 [[user(locn4)]]; 67 | float3 fragLightPos_3 [[user(locn5)]]; 68 | float4 gl_Position [[position, invariant]]; 69 | }; 70 | 71 | struct main0_in 72 | { 73 | float3 inPosition [[attribute(0)]]; 74 | float3 inNormal [[attribute(1)]]; 75 | }; 76 | 77 | vertex main0_out main0(main0_in in [[stage_in]], constant TranformBlock& _25 [[buffer(0)]], constant LightBlockV& lights [[buffer(1)]]) 78 | { 79 | main0_out out = {}; 80 | spvUnsafeArray fragLightPos = {}; 81 | float4 hp = float4(in.inPosition, 1.0); 82 | out.fragViewPos = float3((_25.modelView * hp).xyz); 83 | out.fragViewNormal = fast::normalize(_25.normalMatrix * in.inNormal); 84 | float4 _53 = _25.mvp * hp; 85 | out.gl_Position = _53; 86 | for (uint i = 0u; i < uint(lights.numLights); i++) 87 | { 88 | float4 flps = lights.mvp[i] * hp; 89 | fragLightPos[i] = flps.xyz / float3(flps.w); 90 | } 91 | out.fragLightPos_0 = fragLightPos[0]; 92 | out.fragLightPos_1 = fragLightPos[1]; 93 | out.fragLightPos_2 = fragLightPos[2]; 94 | out.fragLightPos_3 = fragLightPos[3]; 95 | return out; 96 | } 97 | -------------------------------------------------------------------------------- /shaders/compiled/PbrBasic.vert.spv: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Simple-Robotics/candlewick/ea464b81ee7736da4996ea929869fc7b6542e7f7/shaders/compiled/PbrBasic.vert.spv -------------------------------------------------------------------------------- /shaders/compiled/PbrTransparent.frag.json: -------------------------------------------------------------------------------- 1 | { "samplers": 2, "storage_textures": 0, "storage_buffers": 0, "uniform_buffers": 3 } 2 | -------------------------------------------------------------------------------- /shaders/compiled/PbrTransparent.frag.spv: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Simple-Robotics/candlewick/ea464b81ee7736da4996ea929869fc7b6542e7f7/shaders/compiled/PbrTransparent.frag.spv -------------------------------------------------------------------------------- /shaders/compiled/PointSprite.frag.json: -------------------------------------------------------------------------------- 1 | { "samplers": 0, "storage_textures": 0, "storage_buffers": 0, "uniform_buffers": 0 } 2 | -------------------------------------------------------------------------------- /shaders/compiled/PointSprite.frag.msl: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | using namespace metal; 5 | 6 | struct main0_out 7 | { 8 | float4 fragColor [[color(0)]]; 9 | }; 10 | 11 | struct main0_in 12 | { 13 | float4 pointColor [[user(locn0)]]; 14 | }; 15 | 16 | fragment main0_out main0(main0_in in [[stage_in]], float2 gl_PointCoord [[point_coord]]) 17 | { 18 | main0_out out = {}; 19 | if (length(gl_PointCoord - float2(0.5)) > 0.5) 20 | { 21 | discard_fragment(); 22 | } 23 | out.fragColor = in.pointColor; 24 | return out; 25 | } 26 | -------------------------------------------------------------------------------- /shaders/compiled/PointSprite.frag.spv: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Simple-Robotics/candlewick/ea464b81ee7736da4996ea929869fc7b6542e7f7/shaders/compiled/PointSprite.frag.spv -------------------------------------------------------------------------------- /shaders/compiled/PointSprite.vert.json: -------------------------------------------------------------------------------- 1 | { "samplers": 0, "storage_textures": 0, "storage_buffers": 0, "uniform_buffers": 1 } 2 | -------------------------------------------------------------------------------- /shaders/compiled/PointSprite.vert.msl: -------------------------------------------------------------------------------- 1 | #pragma clang diagnostic ignored "-Wmissing-prototypes" 2 | 3 | #include 4 | #include 5 | 6 | using namespace metal; 7 | 8 | struct TranformBlock 9 | { 10 | float4x4 model; 11 | float4x4 viewProj; 12 | }; 13 | 14 | struct main0_out 15 | { 16 | float4 pointColor [[user(locn0)]]; 17 | float4 gl_Position [[position]]; 18 | float gl_PointSize [[point_size]]; 19 | }; 20 | 21 | struct main0_in 22 | { 23 | float3 position [[attribute(0)]]; 24 | float4 color [[attribute(4)]]; 25 | }; 26 | 27 | static inline __attribute__((always_inline)) 28 | float3 uncharted2ToneMapping_Partial(thread const float3& color) 29 | { 30 | float A = 0.1500000059604644775390625; 31 | float B = 0.5; 32 | float C = 0.100000001490116119384765625; 33 | float D = 0.20000000298023223876953125; 34 | float E = 0.0199999995529651641845703125; 35 | float F = 0.300000011920928955078125; 36 | return (((color * ((color * A) + float3(C * B))) + float3(D * E)) / ((color * ((color * A) + float3(B))) + float3(D * F))) - float3(E / F); 37 | } 38 | 39 | static inline __attribute__((always_inline)) 40 | float3 uncharted2ToneMapping(thread const float3& color) 41 | { 42 | float exposure_bias = 2.0; 43 | float3 param = color * exposure_bias; 44 | float3 curr = uncharted2ToneMapping_Partial(param); 45 | float3 W = float3(11.19999980926513671875); 46 | float3 param_1 = W; 47 | float3 white_scale = float3(1.0) / uncharted2ToneMapping_Partial(param_1); 48 | return curr * white_scale; 49 | } 50 | 51 | vertex main0_out main0(main0_in in [[stage_in]], constant TranformBlock& transform [[buffer(0)]]) 52 | { 53 | main0_out out = {}; 54 | out.gl_Position = (transform.viewProj * transform.model) * float4(in.position, 1.0); 55 | out.gl_PointSize = 5.0; 56 | float3 param = in.color.xyz; 57 | float3 _127 = uncharted2ToneMapping(param); 58 | out.pointColor.x = _127.x; 59 | out.pointColor.y = _127.y; 60 | out.pointColor.z = _127.z; 61 | out.pointColor.w = 1.0; 62 | return out; 63 | } 64 | -------------------------------------------------------------------------------- /shaders/compiled/PointSprite.vert.spv: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Simple-Robotics/candlewick/ea464b81ee7736da4996ea929869fc7b6542e7f7/shaders/compiled/PointSprite.vert.spv -------------------------------------------------------------------------------- /shaders/compiled/RenderDepth.frag.json: -------------------------------------------------------------------------------- 1 | { "samplers": 1, "storage_textures": 0, "storage_buffers": 0, "uniform_buffers": 1 } 2 | -------------------------------------------------------------------------------- /shaders/compiled/RenderDepth.frag.spv: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Simple-Robotics/candlewick/ea464b81ee7736da4996ea929869fc7b6542e7f7/shaders/compiled/RenderDepth.frag.spv -------------------------------------------------------------------------------- /shaders/compiled/SSAO.frag.json: -------------------------------------------------------------------------------- 1 | { "samplers": 3, "storage_textures": 0, "storage_buffers": 0, "uniform_buffers": 2 } 2 | -------------------------------------------------------------------------------- /shaders/compiled/SSAO.frag.spv: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Simple-Robotics/candlewick/ea464b81ee7736da4996ea929869fc7b6542e7f7/shaders/compiled/SSAO.frag.spv -------------------------------------------------------------------------------- /shaders/compiled/SSAOblur.frag.json: -------------------------------------------------------------------------------- 1 | { "samplers": 1, "storage_textures": 0, "storage_buffers": 0, "uniform_buffers": 1 } 2 | -------------------------------------------------------------------------------- /shaders/compiled/SSAOblur.frag.msl: -------------------------------------------------------------------------------- 1 | #pragma clang diagnostic ignored "-Wmissing-prototypes" 2 | #pragma clang diagnostic ignored "-Wmissing-braces" 3 | 4 | #include 5 | #include 6 | 7 | using namespace metal; 8 | 9 | template 10 | struct spvUnsafeArray 11 | { 12 | T elements[Num ? Num : 1]; 13 | 14 | thread T& operator [] (size_t pos) thread 15 | { 16 | return elements[pos]; 17 | } 18 | constexpr const thread T& operator [] (size_t pos) const thread 19 | { 20 | return elements[pos]; 21 | } 22 | 23 | device T& operator [] (size_t pos) device 24 | { 25 | return elements[pos]; 26 | } 27 | constexpr const device T& operator [] (size_t pos) const device 28 | { 29 | return elements[pos]; 30 | } 31 | 32 | constexpr const constant T& operator [] (size_t pos) const constant 33 | { 34 | return elements[pos]; 35 | } 36 | 37 | threadgroup T& operator [] (size_t pos) threadgroup 38 | { 39 | return elements[pos]; 40 | } 41 | constexpr const threadgroup T& operator [] (size_t pos) const threadgroup 42 | { 43 | return elements[pos]; 44 | } 45 | }; 46 | 47 | struct BlurParams 48 | { 49 | float2 direction; 50 | }; 51 | 52 | constant spvUnsafeArray _73 = spvUnsafeArray({ 0.227026998996734619140625, 0.19459460675716400146484375, 0.121621601283550262451171875, 0.054053999483585357666015625, 0.01621600054204463958740234375 }); 53 | 54 | struct main0_out 55 | { 56 | float aoValue [[color(0)]]; 57 | }; 58 | 59 | struct main0_in 60 | { 61 | float2 inUV [[user(locn0)]]; 62 | }; 63 | 64 | fragment main0_out main0(main0_in in [[stage_in]], constant BlurParams& _52 [[buffer(0)]], texture2d aoTex [[texture(0)]], sampler aoTexSmplr [[sampler(0)]]) 65 | { 66 | main0_out out = {}; 67 | float2 texelSize = float2(1.0) / float2(int2(aoTex.get_width(), aoTex.get_height())); 68 | float result = aoTex.sample(aoTexSmplr, in.inUV).x * 0.227026998996734619140625; 69 | for (int i = 1; i <= 4; i++) 70 | { 71 | float2 off = (_52.direction * texelSize) * float(i); 72 | result += (aoTex.sample(aoTexSmplr, (in.inUV + off)).x * _73[i]); 73 | result += (aoTex.sample(aoTexSmplr, (in.inUV - off)).x * _73[i]); 74 | } 75 | out.aoValue = result; 76 | return out; 77 | } 78 | -------------------------------------------------------------------------------- /shaders/compiled/SSAOblur.frag.spv: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Simple-Robotics/candlewick/ea464b81ee7736da4996ea929869fc7b6542e7f7/shaders/compiled/SSAOblur.frag.spv -------------------------------------------------------------------------------- /shaders/compiled/ScreenSpaceShadows.frag.json: -------------------------------------------------------------------------------- 1 | { "samplers": 1, "storage_textures": 0, "storage_buffers": 0, "uniform_buffers": 1 } 2 | -------------------------------------------------------------------------------- /shaders/compiled/ScreenSpaceShadows.frag.msl: -------------------------------------------------------------------------------- 1 | #pragma clang diagnostic ignored "-Wmissing-prototypes" 2 | 3 | #include 4 | #include 5 | 6 | using namespace metal; 7 | 8 | struct ShadowParams 9 | { 10 | float4x4 projection; 11 | float4x4 invProjection; 12 | packed_float3 lightDir; 13 | float maxDistance; 14 | int numSteps; 15 | }; 16 | 17 | struct main0_out 18 | { 19 | float fragShadow [[color(0)]]; 20 | }; 21 | 22 | struct main0_in 23 | { 24 | float2 fragUV [[user(locn0)]]; 25 | }; 26 | 27 | static inline __attribute__((always_inline)) 28 | float3 computeViewPos(thread const float3& ndcPos, constant ShadowParams& _56) 29 | { 30 | float4 viewPos = _56.invProjection * float4(ndcPos, 1.0); 31 | return viewPos.xyz / float3(viewPos.w); 32 | } 33 | 34 | static inline __attribute__((always_inline)) 35 | bool isCoordsInRange(thread const float2& uv) 36 | { 37 | bool _26 = uv.x >= 0.0; 38 | bool _33; 39 | if (_26) 40 | { 41 | _33 = uv.y >= 0.0; 42 | } 43 | else 44 | { 45 | _33 = _26; 46 | } 47 | bool _40; 48 | if (_33) 49 | { 50 | _40 = uv.x <= 1.0; 51 | } 52 | else 53 | { 54 | _40 = _33; 55 | } 56 | bool _46; 57 | if (_40) 58 | { 59 | _46 = uv.y <= 1.0; 60 | } 61 | else 62 | { 63 | _46 = _40; 64 | } 65 | return _46; 66 | } 67 | 68 | fragment main0_out main0(main0_in in [[stage_in]], constant ShadowParams& _56 [[buffer(0)]], texture2d depthTexture [[texture(0)]], sampler depthTextureSmplr [[sampler(0)]]) 69 | { 70 | main0_out out = {}; 71 | float depth = depthTexture.sample(depthTextureSmplr, in.fragUV).x; 72 | if (depth >= 1.0) 73 | { 74 | out.fragShadow = 1.0; 75 | return out; 76 | } 77 | float3 ndcPos = float3((in.fragUV * 2.0) - float2(1.0), depth); 78 | float3 param = ndcPos; 79 | float3 viewPos = computeViewPos(param, _56); 80 | float3 rayPos = viewPos; 81 | float stepSize = 0.0500000007450580596923828125 / float(_56.numSteps); 82 | float3 toLight = fast::normalize(-float3(_56.lightDir)); 83 | float3 rayStep = toLight * stepSize; 84 | float occlusion = 0.0; 85 | for (int i = 0; i < _56.numSteps; i++) 86 | { 87 | rayPos += rayStep; 88 | float4 projectedPos = _56.projection * float4(rayPos, 1.0); 89 | float3 screenPos = projectedPos.xyz / float3(projectedPos.w); 90 | float2 rayUV = float2(0.5) + (screenPos.xy * 0.5); 91 | float rayDepth = screenPos.z; 92 | float sceneDepth = depthTexture.sample(depthTextureSmplr, rayUV).x; 93 | float2 param_1 = rayUV; 94 | if (!isCoordsInRange(param_1)) 95 | { 96 | break; 97 | } 98 | float depthDelta = (rayDepth - sceneDepth) - 0.001000000047497451305389404296875; 99 | if ((depthDelta >= 0.0) && (depthDelta < 0.0199999995529651641845703125)) 100 | { 101 | occlusion = 1.0; 102 | break; 103 | } 104 | } 105 | out.fragShadow = 1.0 - occlusion; 106 | return out; 107 | } 108 | -------------------------------------------------------------------------------- /shaders/compiled/ScreenSpaceShadows.frag.spv: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Simple-Robotics/candlewick/ea464b81ee7736da4996ea929869fc7b6542e7f7/shaders/compiled/ScreenSpaceShadows.frag.spv -------------------------------------------------------------------------------- /shaders/compiled/ShadowCast.frag.json: -------------------------------------------------------------------------------- 1 | { "samplers": 0, "storage_textures": 0, "storage_buffers": 0, "uniform_buffers": 0 } 2 | -------------------------------------------------------------------------------- /shaders/compiled/ShadowCast.frag.msl: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | using namespace metal; 5 | 6 | struct main0_out 7 | { 8 | float gl_FragDepth [[depth(any)]]; 9 | }; 10 | 11 | fragment main0_out main0(float4 gl_FragCoord [[position]]) 12 | { 13 | main0_out out = {}; 14 | out.gl_FragDepth = gl_FragCoord.z; 15 | return out; 16 | } 17 | -------------------------------------------------------------------------------- /shaders/compiled/ShadowCast.frag.spv: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Simple-Robotics/candlewick/ea464b81ee7736da4996ea929869fc7b6542e7f7/shaders/compiled/ShadowCast.frag.spv -------------------------------------------------------------------------------- /shaders/compiled/ShadowCast.vert.json: -------------------------------------------------------------------------------- 1 | { "samplers": 0, "storage_textures": 0, "storage_buffers": 0, "uniform_buffers": 1 } 2 | -------------------------------------------------------------------------------- /shaders/compiled/ShadowCast.vert.msl: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | using namespace metal; 5 | 6 | struct CameraBlock 7 | { 8 | float4x4 mvp; 9 | }; 10 | 11 | struct main0_out 12 | { 13 | float4 gl_Position [[position, invariant]]; 14 | }; 15 | 16 | struct main0_in 17 | { 18 | float3 inPosition [[attribute(0)]]; 19 | }; 20 | 21 | vertex main0_out main0(main0_in in [[stage_in]], constant CameraBlock& _16 [[buffer(0)]]) 22 | { 23 | main0_out out = {}; 24 | float4 _28 = float4(in.inPosition, 1.0); 25 | float4 _29 = _16.mvp * _28; 26 | out.gl_Position = _29; 27 | return out; 28 | } 29 | -------------------------------------------------------------------------------- /shaders/compiled/ShadowCast.vert.spv: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Simple-Robotics/candlewick/ea464b81ee7736da4996ea929869fc7b6542e7f7/shaders/compiled/ShadowCast.vert.spv -------------------------------------------------------------------------------- /shaders/compiled/SolidColor.frag.json: -------------------------------------------------------------------------------- 1 | { "samplers": 0, "storage_textures": 0, "storage_buffers": 0, "uniform_buffers": 0 } 2 | -------------------------------------------------------------------------------- /shaders/compiled/SolidColor.frag.msl: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | using namespace metal; 5 | 6 | struct main0_out 7 | { 8 | float4 FragColor [[color(0)]]; 9 | }; 10 | 11 | struct main0_in 12 | { 13 | float4 Color [[user(locn0)]]; 14 | }; 15 | 16 | fragment main0_out main0(main0_in in [[stage_in]]) 17 | { 18 | main0_out out = {}; 19 | out.FragColor = in.Color; 20 | return out; 21 | } 22 | -------------------------------------------------------------------------------- /shaders/compiled/SolidColor.frag.spv: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Simple-Robotics/candlewick/ea464b81ee7736da4996ea929869fc7b6542e7f7/shaders/compiled/SolidColor.frag.spv -------------------------------------------------------------------------------- /shaders/compiled/VertexColor.frag.json: -------------------------------------------------------------------------------- 1 | { "samplers": 0, "storage_textures": 0, "storage_buffers": 0, "uniform_buffers": 0 } 2 | -------------------------------------------------------------------------------- /shaders/compiled/VertexColor.frag.msl: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | using namespace metal; 5 | 6 | struct main0_out 7 | { 8 | float4 FragColor [[color(0)]]; 9 | }; 10 | 11 | struct main0_in 12 | { 13 | float4 interpColor [[user(locn0)]]; 14 | }; 15 | 16 | fragment main0_out main0(main0_in in [[stage_in]]) 17 | { 18 | main0_out out = {}; 19 | out.FragColor = in.interpColor; 20 | return out; 21 | } 22 | -------------------------------------------------------------------------------- /shaders/compiled/VertexColor.frag.spv: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Simple-Robotics/candlewick/ea464b81ee7736da4996ea929869fc7b6542e7f7/shaders/compiled/VertexColor.frag.spv -------------------------------------------------------------------------------- /shaders/compiled/VertexColor.vert.json: -------------------------------------------------------------------------------- 1 | { "samplers": 0, "storage_textures": 0, "storage_buffers": 0, "uniform_buffers": 1 } 2 | -------------------------------------------------------------------------------- /shaders/compiled/VertexColor.vert.msl: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | using namespace metal; 5 | 6 | struct UniformBlock 7 | { 8 | float4x4 viewProj; 9 | }; 10 | 11 | struct main0_out 12 | { 13 | float4 interpColor [[user(locn0)]]; 14 | float4 gl_Position [[position]]; 15 | }; 16 | 17 | struct main0_in 18 | { 19 | float3 position [[attribute(0)]]; 20 | float4 color [[attribute(4)]]; 21 | }; 22 | 23 | vertex main0_out main0(main0_in in [[stage_in]], constant UniformBlock& _19 [[buffer(0)]]) 24 | { 25 | main0_out out = {}; 26 | out.gl_Position = _19.viewProj * float4(in.position, 1.0); 27 | out.interpColor = in.color; 28 | return out; 29 | } 30 | -------------------------------------------------------------------------------- /shaders/compiled/VertexColor.vert.spv: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Simple-Robotics/candlewick/ea464b81ee7736da4996ea929869fc7b6542e7f7/shaders/compiled/VertexColor.vert.spv -------------------------------------------------------------------------------- /shaders/compiled/VertexNormal.frag.json: -------------------------------------------------------------------------------- 1 | { "samplers": 0, "storage_textures": 0, "storage_buffers": 0, "uniform_buffers": 0 } 2 | -------------------------------------------------------------------------------- /shaders/compiled/VertexNormal.frag.msl: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | using namespace metal; 5 | 6 | struct main0_out 7 | { 8 | float4 FragColor [[color(0)]]; 9 | }; 10 | 11 | struct main0_in 12 | { 13 | float3 f_normal [[user(locn0)]]; 14 | }; 15 | 16 | fragment main0_out main0(main0_in in [[stage_in]]) 17 | { 18 | main0_out out = {}; 19 | out.FragColor.x = in.f_normal.x; 20 | out.FragColor.y = in.f_normal.y; 21 | out.FragColor.z = in.f_normal.z; 22 | return out; 23 | } 24 | -------------------------------------------------------------------------------- /shaders/compiled/VertexNormal.frag.spv: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Simple-Robotics/candlewick/ea464b81ee7736da4996ea929869fc7b6542e7f7/shaders/compiled/VertexNormal.frag.spv -------------------------------------------------------------------------------- /shaders/compiled/VertexNormal.vert.json: -------------------------------------------------------------------------------- 1 | { "samplers": 0, "storage_textures": 0, "storage_buffers": 0, "uniform_buffers": 1 } 2 | -------------------------------------------------------------------------------- /shaders/compiled/VertexNormal.vert.msl: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | using namespace metal; 5 | 6 | struct TranformBlock 7 | { 8 | float4x4 mvp; 9 | float3x3 normalMatrix; 10 | }; 11 | 12 | struct main0_out 13 | { 14 | float3 f_normal [[user(locn0)]]; 15 | float4 gl_Position [[position]]; 16 | }; 17 | 18 | struct main0_in 19 | { 20 | float3 position [[attribute(0)]]; 21 | float3 normal [[attribute(1)]]; 22 | }; 23 | 24 | vertex main0_out main0(main0_in in [[stage_in]], constant TranformBlock& _15 [[buffer(0)]]) 25 | { 26 | main0_out out = {}; 27 | out.f_normal = fast::normalize(_15.normalMatrix * in.normal); 28 | out.gl_Position = _15.mvp * float4(in.position, 1.0); 29 | return out; 30 | } 31 | -------------------------------------------------------------------------------- /shaders/compiled/VertexNormal.vert.spv: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Simple-Robotics/candlewick/ea464b81ee7736da4996ea929869fc7b6542e7f7/shaders/compiled/VertexNormal.vert.spv -------------------------------------------------------------------------------- /shaders/compiled/WBOITComposite.frag.json: -------------------------------------------------------------------------------- 1 | { "samplers": 2, "storage_textures": 0, "storage_buffers": 0, "uniform_buffers": 0 } 2 | -------------------------------------------------------------------------------- /shaders/compiled/WBOITComposite.frag.msl: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | using namespace metal; 5 | 6 | struct main0_out 7 | { 8 | float4 outColor [[color(0)]]; 9 | }; 10 | 11 | fragment main0_out main0(texture2d accumTexture [[texture(0)]], texture2d revealTexture [[texture(1)]], sampler accumTextureSmplr [[sampler(0)]], sampler revealTextureSmplr [[sampler(1)]], float4 gl_FragCoord [[position]]) 12 | { 13 | main0_out out = {}; 14 | float2 viewportSize = float2(int2(accumTexture.get_width(), accumTexture.get_height())); 15 | float2 uv = gl_FragCoord.xy / viewportSize; 16 | float4 accum = accumTexture.sample(accumTextureSmplr, uv); 17 | float reveal = revealTexture.sample(revealTextureSmplr, uv).x; 18 | float3 color = accum.xyz; 19 | if (accum.w > 0.001000000047497451305389404296875) 20 | { 21 | color = accum.xyz / float3(accum.w); 22 | } 23 | out.outColor = float4(color, reveal); 24 | return out; 25 | } 26 | -------------------------------------------------------------------------------- /shaders/compiled/WBOITComposite.frag.spv: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Simple-Robotics/candlewick/ea464b81ee7736da4996ea929869fc7b6542e7f7/shaders/compiled/WBOITComposite.frag.spv -------------------------------------------------------------------------------- /shaders/src/BasicTriangle.vert: -------------------------------------------------------------------------------- 1 | #version 450 2 | 3 | layout (location = 0) out vec4 outColor; 4 | 5 | void main() { 6 | vec2 pos; 7 | 8 | if (gl_VertexIndex == 0) { 9 | pos = vec2(-1, -1); 10 | outColor = vec4(1, 0, 0, 1); 11 | } else if (gl_VertexIndex == 1) { 12 | pos = vec2(1, -1); 13 | outColor = vec4(0, 1, 0, 1); 14 | } else if (gl_VertexIndex == 2) { 15 | pos = vec2(0, 1); 16 | outColor = vec4(0, 0, 1, 1); 17 | } 18 | 19 | gl_Position = vec4(pos, 0, 1); 20 | } 21 | -------------------------------------------------------------------------------- /shaders/src/DrawQuad.vert: -------------------------------------------------------------------------------- 1 | // Vertex shader to render a quad on screen. 2 | // Outputs the corresponding UV coordinates, 3 | // which get interpolated in the fragment shader. 4 | #version 450 5 | 6 | const vec2 positions[6] = vec2[]( 7 | vec2(-1.0, -1.0), 8 | vec2( 1.0, -1.0), 9 | vec2( 1.0, 1.0), 10 | vec2(-1.0, -1.0), 11 | vec2( 1.0, 1.0), 12 | vec2(-1.0, 1.0) 13 | ); 14 | 15 | layout(location = 0) out vec2 outUV; 16 | 17 | void main() { 18 | vec2 pos = positions[gl_VertexIndex]; 19 | gl_Position = vec4(pos, 0.0, 1.0); 20 | // Convert to UV coordinates 21 | outUV.x = pos.x * 0.5 + 0.5; 22 | outUV.y = 0.5 - pos.y * 0.5; 23 | } 24 | -------------------------------------------------------------------------------- /shaders/src/FrustumDebug.vert: -------------------------------------------------------------------------------- 1 | #version 450 2 | 3 | layout(set=1, binding=0) uniform DebugTransform { 4 | mat4 invProj; 5 | mat4 mvp; 6 | vec4 color; 7 | vec3 eyePos; 8 | }; 9 | 10 | layout(location=0) out vec4 outColor; 11 | 12 | // Line indices for frustum edges 13 | const uint[24] LINE_INDICES = uint[]( 14 | // bottom face 15 | 0, 1, // x-edge 16 | 0, 2, // y-edge 17 | 2, 3, // x-edge 18 | 1, 3, // y-edge 19 | // top face 20 | 4, 5, // x-edge 21 | 4, 6, // y-edge 22 | 6, 7, // x-edge 23 | 5, 7, // y-edge 24 | // vertical edges 25 | 0, 4, // z-edge 26 | 1, 5, // z-edge 27 | 2, 6, // z-edge 28 | 3, 7 // z-edge 29 | ); 30 | 31 | vec4 getNDCCorner(uint idx) { 32 | return vec4( 33 | 2.0 * float(idx & 1) - 1.0, 34 | 2.0 * float((idx >> 1) & 1) - 1.0, 35 | 2.0 * float((idx >> 2) & 1) - 1.0, 36 | 1.0 37 | ); 38 | } 39 | 40 | void main() { 41 | if (gl_VertexIndex < 24) { 42 | uint cornerIdx = LINE_INDICES[gl_VertexIndex]; 43 | vec4 ndc = getNDCCorner(cornerIdx); 44 | vec4 viewPos = invProj * ndc; 45 | viewPos /= viewPos.w; 46 | gl_Position = mvp * viewPos; 47 | } else if (gl_VertexIndex < 30) { 48 | uint axisIdx = (gl_VertexIndex - 24) / 2; 49 | uint pointIdx = gl_VertexIndex & 1; 50 | float size = 0.1; 51 | vec3 offset = vec3(0.0); 52 | offset[axisIdx] = pointIdx == 0 ? -size : size; 53 | gl_Position = mvp * vec4(eyePos + offset, 1.0); 54 | } else { 55 | // Z=0 intersection quad 56 | uint lineIdx = (gl_VertexIndex - 30) / 2; 57 | uint pointIdx = gl_VertexIndex & 1; 58 | 59 | uint edgeNum = (lineIdx + pointIdx) % 4; 60 | uint near = edgeNum; 61 | uint far = near + 4; 62 | vec4 center = mix(getNDCCorner(near), getNDCCorner(far), 0.5); 63 | 64 | vec4 viewPos = invProj * center; 65 | viewPos /= viewPos.w; 66 | gl_Position = mvp * viewPos; 67 | } 68 | outColor = color; 69 | } 70 | -------------------------------------------------------------------------------- /shaders/src/Hud3dElement.frag: -------------------------------------------------------------------------------- 1 | #version 450 2 | 3 | layout(location=0) out vec4 fragColor; 4 | 5 | layout(set=3, binding=0) uniform Color { 6 | vec4 color; 7 | }; 8 | 9 | void main() { 10 | fragColor = color; 11 | } 12 | -------------------------------------------------------------------------------- /shaders/src/Hud3dElement.vert: -------------------------------------------------------------------------------- 1 | #version 450 2 | 3 | layout(location=0) in vec3 inPosition; 4 | 5 | layout(set=1, binding=0) uniform TranformBlock { 6 | mat4 mvp; 7 | }; 8 | 9 | void main() { 10 | gl_Position = mvp * vec4(inPosition, 1.0); 11 | } 12 | -------------------------------------------------------------------------------- /shaders/src/PbrBasic.vert: -------------------------------------------------------------------------------- 1 | #version 450 2 | 3 | #include "config.glsl" 4 | 5 | layout(location=0) in vec3 inPosition; 6 | layout(location=1) in vec3 inNormal; 7 | 8 | // world-space position-normal 9 | layout(location=0) out vec3 fragViewPos; 10 | layout(location=1) out vec3 fragViewNormal; 11 | layout(location=2) out vec3 fragLightPos[MAX_NUM_LIGHTS]; 12 | 13 | 14 | // set=1 is required, for some reason 15 | layout(set=1, binding=0) uniform TranformBlock 16 | { 17 | mat4 modelView; 18 | mat4 mvp; 19 | mat3 normalMatrix; 20 | }; 21 | 22 | layout(set=1, binding=1) uniform LightBlockV 23 | { 24 | mat4 mvp[MAX_NUM_LIGHTS]; 25 | int numLights; 26 | } lights; 27 | 28 | out gl_PerVertex { 29 | invariant vec4 gl_Position; 30 | }; 31 | 32 | void main() { 33 | vec4 hp = vec4(inPosition, 1.0); 34 | fragViewPos = vec3(modelView * hp); 35 | fragViewNormal = normalize(normalMatrix * inNormal); 36 | gl_Position = mvp * hp; 37 | 38 | for (uint i = 0; i < lights.numLights; i++) { 39 | vec4 flps = lights.mvp[i] * hp; 40 | fragLightPos[i] = flps.xyz / flps.w; 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /shaders/src/PbrTransparent.frag: -------------------------------------------------------------------------------- 1 | #version 450 2 | #define HAS_WBOIT 3 | #define HAS_SHADOW_MAPS 4 | #define HAS_SSAO 5 | 6 | #include "tone_mapping.glsl" 7 | #include "pbr_lighting.glsl" 8 | #include "config.glsl" 9 | 10 | layout(location=0) in vec3 fragViewPos; 11 | layout(location=1) in vec3 fragViewNormal; 12 | layout(location=2) in vec3 fragLightPos; 13 | 14 | // layout declarations with two outputs 15 | layout(location = 0) out vec4 accum; // R16G16B16A16 accumulation 16 | layout(location = 1) out float reveal; // R8 revealage 17 | 18 | layout (set=3, binding=0) uniform Material { 19 | // material diffuse color 20 | PbrMaterial material; 21 | }; 22 | 23 | layout(set=3, binding=1) uniform LightBlock { 24 | vec3 direction[MAX_NUM_LIGHTS]; 25 | vec3 color[MAX_NUM_LIGHTS]; 26 | float intensity[MAX_NUM_LIGHTS]; 27 | int numLights; 28 | } light; 29 | 30 | layout(set=3, binding=2) uniform EffectParams { 31 | uint useSsao; 32 | } params; 33 | 34 | #ifdef HAS_SHADOW_MAPS 35 | layout (set=2, binding=0) uniform sampler2DShadow shadowMap; 36 | #endif 37 | #ifdef HAS_SSAO 38 | layout (set=2, binding=1) uniform sampler2D ssaoTex; 39 | #endif 40 | 41 | 42 | void main() { 43 | vec3 normal = normalize(fragViewNormal); 44 | vec3 V = normalize(-fragViewPos); 45 | 46 | vec3 Lo = vec3(0.); 47 | for (uint i = 0; i < light.numLights; i++) { 48 | vec3 lightDir = normalize(-light.direction[i]); 49 | Lo += calculatePbrLighting( 50 | normal, 51 | V, 52 | lightDir, 53 | material, 54 | light.color[i], 55 | light.intensity[i] 56 | ); 57 | } 58 | 59 | vec3 ambient = vec3(0.03) * material.baseColor.rgb * material.ao; 60 | vec3 color = ambient + Lo; 61 | 62 | // Tone mapping and gamma correction 63 | color = uncharted2ToneMapping(color); 64 | color = pow(color, vec3(1.0/2.2)); 65 | 66 | // WBOIT specific calculations 67 | float alpha = material.baseColor.a; 68 | 69 | const float z = gl_FragCoord.z; 70 | float w1 = alpha * 10. + 0.01; 71 | float weight = clamp(w1 * (1.0 - z * 0.3), 1e-2, 1e3); 72 | 73 | // Outputs for WBOIT 74 | accum = vec4(color * alpha * weight, alpha); 75 | reveal = 1. - alpha * weight; 76 | } 77 | -------------------------------------------------------------------------------- /shaders/src/PointSprite.frag: -------------------------------------------------------------------------------- 1 | #version 450 2 | 3 | layout(location=0) in vec4 pointColor; 4 | layout(location=0) out vec4 fragColor; 5 | 6 | void main() { 7 | // Can use gl_PointCoord for texture/circular shape 8 | // Can discard fragments to make round points 9 | if (length(gl_PointCoord - 0.5) > 0.5) { 10 | discard; 11 | } 12 | fragColor = pointColor; 13 | } 14 | -------------------------------------------------------------------------------- /shaders/src/PointSprite.vert: -------------------------------------------------------------------------------- 1 | #version 450 2 | 3 | #include "tone_mapping.glsl" 4 | 5 | layout(location=0) in vec3 position; 6 | layout(location=4) in vec4 color; 7 | 8 | layout(set=1, binding=0) uniform TranformBlock 9 | { 10 | mat4 model; 11 | mat4 viewProj; 12 | } transform; 13 | 14 | layout(location=0) out vec4 pointColor; 15 | 16 | void main() { 17 | gl_Position = transform.viewProj * transform.model * vec4(position, 1.0); 18 | gl_PointSize = 5.0; // Fixed size or distance-based 19 | pointColor.rgb = uncharted2ToneMapping(color.rgb); 20 | pointColor.w = 1.0; 21 | } 22 | -------------------------------------------------------------------------------- /shaders/src/RenderDepth.frag: -------------------------------------------------------------------------------- 1 | // To be used with DrawQuad.vert 2 | #version 450 3 | 4 | #include "utils.glsl" 5 | 6 | #define VIZ_GRAYSCALE 0 7 | #define VIZ_HEATMAP 1 8 | 9 | layout(location = 0) in vec2 inUV; 10 | layout(location = 0) out vec4 outColor; 11 | 12 | layout (set=2, binding=0) uniform sampler2D depthTex; 13 | 14 | layout (set=3, binding=0) uniform CameraParams { 15 | int mode; 16 | float near; 17 | float far; 18 | uint isOrtho; // 0 for perspective, 1 for ortho 19 | }; 20 | 21 | vec3 visualizeDepthGrayscale(float depth) { 22 | float lin; 23 | if (isOrtho == 1) { 24 | lin = linearizeDepthOrtho(depth, near, far); 25 | } else { 26 | lin = linearizeDepth(depth, near, far); 27 | } 28 | float norm = (lin - near) / (far - near); 29 | return vec3(norm); 30 | } 31 | 32 | vec3 visualizeDepthHeatmap(float depth) { 33 | float lin; 34 | if (isOrtho == 1) { 35 | lin = linearizeDepthOrtho(depth, near, far); 36 | } else { 37 | lin = linearizeDepth(depth, near, far); 38 | } 39 | // Normalize to 0-1 range based on near/far planes 40 | float normalized = (lin - near) / (far - near); 41 | 42 | // Convert to heatmap colors: 43 | // Blue (cold) -> Cyan -> Green -> Yellow -> Red (hot) 44 | vec3 color; 45 | if(normalized < 0.25) { 46 | // Blue to Cyan 47 | float t = normalized / 0.25; 48 | color = mix(vec3(0,0,1), vec3(0,1,1), t); 49 | } else if(normalized < 0.5) { 50 | // Cyan to Green 51 | float t = (normalized - 0.25) / 0.25; 52 | color = mix(vec3(0,1,1), vec3(0,1,0), t); 53 | } else if(normalized < 0.75) { 54 | // Green to Yellow 55 | float t = (normalized - 0.5) / 0.25; 56 | color = mix(vec3(0,1,0), vec3(1,1,0), t); 57 | } else { 58 | // Yellow to Red 59 | float t = (normalized - 0.75) / 0.25; 60 | color = mix(vec3(1,1,0), vec3(1,0,0), t); 61 | } 62 | return color; 63 | } 64 | 65 | void main() { 66 | float depth = texture(depthTex, inUV).r; 67 | 68 | vec3 color; 69 | switch (mode) { 70 | case VIZ_GRAYSCALE: 71 | color = visualizeDepthGrayscale(depth); 72 | break; 73 | case VIZ_HEATMAP: 74 | color = visualizeDepthHeatmap(depth); 75 | break; 76 | } 77 | outColor.rgb = color; 78 | outColor.w = 1.0; 79 | } 80 | -------------------------------------------------------------------------------- /shaders/src/SSAO.frag: -------------------------------------------------------------------------------- 1 | #version 450 2 | 3 | 4 | layout(location=0) in vec2 inUV; 5 | layout(location=0) out float aoValue; 6 | 7 | layout(set=2, binding=0) uniform sampler2D depthTex; 8 | layout(set=2, binding=1) uniform sampler2D normalMap; 9 | layout(set=2, binding=2) uniform sampler2D ssaoNoise; 10 | 11 | const int SSAO_KERNEL_SIZE = 64; 12 | const float SSAO_RADIUS = 1.0; 13 | const float SSAO_BIAS = 0.01; 14 | const float SSAO_INTENSITY = 1.5; 15 | 16 | layout(set=3, binding=0) uniform SSAOParams { 17 | vec4 samples[SSAO_KERNEL_SIZE]; 18 | } kernel; 19 | 20 | layout(set=3, binding=1) uniform Camera { 21 | mat4 projection; 22 | } camera; 23 | 24 | vec3 getViewPos(float depth, vec2 uv) { 25 | vec4 clipPos = vec4(uv * 2.0 - 1.0, depth, 1.0); 26 | vec4 viewPos = inverse(camera.projection) * clipPos; 27 | return viewPos.xyz / viewPos.w; 28 | } 29 | 30 | vec3 sampleNoiseTexture(vec2 uv) { 31 | vec2 viewport = textureSize(depthTex, 0).xy; 32 | vec2 noiseScale = viewport / 4.0; 33 | uv = uv * noiseScale; 34 | return vec3(texture(ssaoNoise, uv).rg, 0); 35 | } 36 | 37 | float calculatePixelAO(vec2 uv) { 38 | float depth = texture(depthTex, uv).r; 39 | vec3 viewPos = getViewPos(depth, uv); 40 | vec3 viewNormal; 41 | viewNormal.xy = texture(normalMap, uv).xy; 42 | viewNormal.z = sqrt(1 - dot(viewNormal.xy, viewNormal.xy)); 43 | 44 | vec3 randVec = sampleNoiseTexture(uv); 45 | 46 | // tbn matrix for rotating samples 47 | vec3 tangent = normalize(randVec - viewNormal * dot(randVec, viewNormal)); 48 | vec3 bitangent = cross(tangent, viewNormal); 49 | mat3 TBN = mat3(tangent, bitangent, viewNormal); 50 | 51 | // accumulate occlusion 52 | float occlusion = 0.0; 53 | for(int i = 0; i < SSAO_KERNEL_SIZE; i++) { 54 | // get sample position 55 | vec3 samplePos = TBN * kernel.samples[i].xyz; // Rotate sample vector 56 | samplePos = viewPos + samplePos * SSAO_RADIUS; // Move it to view-space position 57 | 58 | // project sample to get its screen-space coordinates 59 | vec4 offset = camera.projection * vec4(samplePos, 1.0); 60 | offset.xy /= offset.w; // Perspective divide 61 | offset.xy = offset.xy * 0.5 + 0.5; // Transform to [0,1] range 62 | 63 | // get sample depth 64 | float sampleDepth = texture(depthTex, offset.xy).r; 65 | 66 | // convert sample's depth to view space for comparison 67 | vec3 sampleViewPos = getViewPos(sampleDepth, offset.xy); 68 | 69 | // Rarnge check & accumulate 70 | float rangeCheck = smoothstep(0.0, 1.0, SSAO_RADIUS / abs(viewPos.z - sampleViewPos.z - SSAO_BIAS)); 71 | occlusion += (sampleViewPos.z >= samplePos.z + SSAO_BIAS ? 1.0 : 0.0) * rangeCheck; 72 | } 73 | 74 | occlusion = 1.0 - (occlusion / SSAO_KERNEL_SIZE) * SSAO_INTENSITY; 75 | return occlusion; 76 | } 77 | 78 | void main() { 79 | aoValue = calculatePixelAO(inUV); 80 | } 81 | -------------------------------------------------------------------------------- /shaders/src/SSAOblur.frag: -------------------------------------------------------------------------------- 1 | #version 450 2 | 3 | layout(location=0) in vec2 inUV; 4 | layout(location=0) out float aoValue; 5 | 6 | layout(set=2, binding=0) uniform sampler2D aoTex; 7 | 8 | 9 | const float weights[5] = float[](0.227027, 0.1945946, 0.1216216, 0.054054, 0.016216); 10 | const int KERNEL_RADIUS = 4; 11 | 12 | layout(set=3, binding=0) uniform BlurParams 13 | { 14 | vec2 direction; 15 | }; 16 | 17 | void main() { 18 | vec2 texelSize = 1.0 / vec2(textureSize(aoTex, 0)); 19 | float result = texture(aoTex, inUV).r * weights[0]; 20 | 21 | for(int i = 1; i <= KERNEL_RADIUS; i++) { 22 | vec2 off = direction * texelSize * i; 23 | result += texture(aoTex, inUV + off).r * weights[i]; 24 | result += texture(aoTex, inUV - off).r * weights[i]; 25 | } 26 | aoValue = result; 27 | } 28 | -------------------------------------------------------------------------------- /shaders/src/ScreenSpaceShadows.frag: -------------------------------------------------------------------------------- 1 | #version 450 2 | 3 | #include "utils.glsl" 4 | 5 | layout(location = 0) in vec2 fragUV; 6 | layout(location = 0) out float fragShadow; 7 | 8 | // Depth texture from pre-pass 9 | layout(set=2, binding=0) uniform sampler2D depthTexture; 10 | 11 | // Parameters for the effect 12 | layout(set=3, binding=0) uniform ShadowParams { 13 | // Camera projection matrix 14 | mat4 projection; 15 | // Inverse projection matrix 16 | mat4 invProjection; 17 | // View-space light direction 18 | vec3 lightDir; 19 | // Maximum ray march distance 20 | float maxDistance; 21 | // Maximum number of steps 22 | int numSteps; 23 | }; 24 | 25 | // reconstruct view-space position from depth 26 | vec3 computeViewPos(vec3 ndcPos) { 27 | vec4 viewPos = invProjection * vec4(ndcPos, 1.0); 28 | return viewPos.xyz / viewPos.w; 29 | } 30 | 31 | const float SSS_THICKNESS = 0.02; 32 | const float SSS_MAX_RAY_DISTANCE = 0.05; 33 | 34 | void main() { 35 | // Sample depth 36 | float depth = texture(depthTexture, fragUV).r; 37 | 38 | if (depth >= 1.0) { 39 | fragShadow = 1.0; 40 | return; 41 | } 42 | 43 | // reconstruct view-space position 44 | const vec3 ndcPos = vec3(fragUV * 2. - 1., depth); 45 | const vec3 viewPos = computeViewPos(ndcPos); 46 | 47 | // initial ray position 48 | vec3 rayPos = viewPos; 49 | 50 | const float stepSize = SSS_MAX_RAY_DISTANCE / float(numSteps); 51 | vec3 toLight = normalize(-lightDir); 52 | vec3 rayStep = toLight * stepSize; 53 | 54 | float depthDelta; 55 | float occlusion = 0.0; 56 | 57 | // march a ray from the fragment towards the light 58 | for (int i = 0; i < numSteps; i++) { 59 | // advance ray 60 | rayPos += rayStep; 61 | 62 | // project current ray position to screen space 63 | vec4 projectedPos = projection * vec4(rayPos, 1.0); 64 | vec3 screenPos = projectedPos.xyz / projectedPos.w; 65 | 66 | // convert to UV coordinates 67 | vec2 rayUV = 0.5 + screenPos.xy * 0.5; 68 | 69 | // depth at the ray depth 70 | float rayDepth = screenPos.z; 71 | float sceneDepth = texture(depthTexture, rayUV).r; 72 | 73 | 74 | if(!isCoordsInRange(rayUV)) { 75 | break; 76 | } 77 | 78 | depthDelta = rayDepth - sceneDepth - 0.001; 79 | 80 | // compare depths: if the current depth is closer than the ray, 81 | // then fragment is occluded. 82 | if ((depthDelta >= 0.0) && (depthDelta < SSS_THICKNESS)) { 83 | occlusion = 1.0; 84 | break; 85 | } 86 | } 87 | 88 | // No shadow found 89 | fragShadow = 1.0 - occlusion; 90 | } 91 | -------------------------------------------------------------------------------- /shaders/src/ShadowCast.frag: -------------------------------------------------------------------------------- 1 | #version 450 2 | 3 | void main() { 4 | gl_FragDepth = gl_FragCoord.z; 5 | } 6 | -------------------------------------------------------------------------------- /shaders/src/ShadowCast.vert: -------------------------------------------------------------------------------- 1 | #version 450 2 | 3 | layout(location=0) in vec3 inPosition; 4 | 5 | layout(set=1, binding=0) uniform CameraBlock { 6 | mat4 mvp; 7 | }; 8 | 9 | out gl_PerVertex { 10 | invariant vec4 gl_Position; 11 | }; 12 | 13 | void main() { 14 | gl_Position = mvp * vec4(inPosition, 1.0); 15 | } 16 | -------------------------------------------------------------------------------- /shaders/src/SolidColor.frag: -------------------------------------------------------------------------------- 1 | #version 450 2 | 3 | layout (location = 0) in vec4 Color; 4 | layout (location = 0) out vec4 FragColor; 5 | 6 | void main() { 7 | FragColor = Color; 8 | } 9 | -------------------------------------------------------------------------------- /shaders/src/VertexColor.frag: -------------------------------------------------------------------------------- 1 | #version 450 2 | 3 | layout(location=0) in vec4 interpColor; 4 | 5 | layout(location=0) out vec4 FragColor; 6 | 7 | void main() { 8 | 9 | FragColor = interpColor; 10 | 11 | } 12 | -------------------------------------------------------------------------------- /shaders/src/VertexColor.vert: -------------------------------------------------------------------------------- 1 | #version 450 2 | 3 | layout(location=0) in vec3 position; 4 | layout(location=4) in vec4 color; 5 | 6 | layout(location=0) out vec4 interpColor; 7 | 8 | // set=1 is required, for some god forsaken reason 9 | // i am not that smart 10 | layout(set=1, binding=0) uniform UniformBlock 11 | { 12 | mat4 viewProj; 13 | }; 14 | 15 | void main() { 16 | 17 | gl_Position = viewProj * vec4(position, 1.0); 18 | 19 | interpColor = color; 20 | } 21 | -------------------------------------------------------------------------------- /shaders/src/VertexNormal.frag: -------------------------------------------------------------------------------- 1 | #version 450 2 | 3 | layout(location=0) in vec3 f_normal; 4 | 5 | layout(location=0) out vec4 FragColor; 6 | 7 | void main() { 8 | 9 | FragColor.xyz = f_normal; 10 | 11 | } 12 | -------------------------------------------------------------------------------- /shaders/src/VertexNormal.vert: -------------------------------------------------------------------------------- 1 | #version 450 2 | 3 | layout(location=0) in vec3 position; 4 | layout(location=1) in vec3 normal; 5 | 6 | layout(location=0) out vec3 f_normal; 7 | 8 | // set=1 is required, see the documentation on SDL3's shader layout 9 | // https://wiki.libsdl.org/SDL3/SDL_CreateGPUShader 10 | layout(set=1, binding=0) uniform TranformBlock 11 | { 12 | mat4 mvp; 13 | mat3 normalMatrix; 14 | }; 15 | 16 | void main() { 17 | 18 | f_normal = normalize(normalMatrix * normal); 19 | gl_Position = mvp * vec4(position, 1.0); 20 | 21 | } 22 | -------------------------------------------------------------------------------- /shaders/src/WBOITComposite.frag: -------------------------------------------------------------------------------- 1 | #version 450 2 | layout(set=2, binding=0) uniform sampler2D accumTexture; 3 | layout(set=2, binding=1) uniform sampler2D revealTexture; 4 | 5 | layout(location = 0) out vec4 outColor; 6 | 7 | void main() { 8 | vec2 viewportSize = textureSize(accumTexture, 0).xy; 9 | vec2 uv = gl_FragCoord.xy / viewportSize; 10 | 11 | vec4 accum = texture(accumTexture, uv); 12 | float reveal = texture(revealTexture, uv).r; 13 | 14 | vec3 color = accum.rgb; 15 | if (accum.a > 0.001) { 16 | color = accum.rgb / accum.a; 17 | } 18 | 19 | // Output final color 20 | outColor = vec4(color, reveal); 21 | } 22 | -------------------------------------------------------------------------------- /shaders/src/config.glsl: -------------------------------------------------------------------------------- 1 | #define MAX_NUM_LIGHTS 4 2 | -------------------------------------------------------------------------------- /shaders/src/pbr_lighting.glsl: -------------------------------------------------------------------------------- 1 | #ifndef _PBR_LIGHTING_GLSL_ 2 | #define _PBR_LIGHTING_GLSL_ 3 | 4 | struct PbrMaterial { 5 | vec4 baseColor; 6 | float metalness; 7 | float roughness; 8 | float ao; 9 | }; 10 | 11 | // Constants 12 | const float PI = 3.14159265359; 13 | const float F0 = 0.04; // Standard base reflectivity 14 | 15 | // Schlick's Fresnel approximation 16 | vec3 fresnelSchlick(float cosTheta, vec3 F0) { 17 | return F0 + (1.0 - F0) * pow(clamp(1.0 - cosTheta, 0.0, 1.0), 5.0); 18 | } 19 | 20 | // Normal Distribution Function (GGX/Trowbridge-Reitz) 21 | float distributionGGX(vec3 normal, vec3 H, float roughness) { 22 | float a = roughness * roughness; 23 | float a2 = a * a; 24 | float NdotH = max(dot(normal, H), 0.0); 25 | float NdotH2 = NdotH * NdotH; 26 | 27 | float denom = (NdotH2 * (a2 - 1.0) + 1.0); 28 | denom = PI * denom * denom; 29 | 30 | return a2 / denom; 31 | } 32 | 33 | // Geometry function (Smith's method with Schlick-GGX) 34 | float geometrySchlickGGX(float NdotV, float roughness) { 35 | float r = (roughness + 1.0); 36 | float k = (r * r) / 8.0; 37 | 38 | float num = NdotV; 39 | float denom = NdotV * (1.0 - k) + k; 40 | 41 | return num / denom; 42 | } 43 | 44 | float geometrySmith(vec3 normal, vec3 V, vec3 L, float roughness) { 45 | float NdotV = max(dot(normal, V), 0.0); 46 | float NdotL = max(dot(normal, L), 0.0); 47 | float ggx2 = geometrySchlickGGX(NdotV, roughness); 48 | float ggx1 = geometrySchlickGGX(NdotL, roughness); 49 | 50 | return ggx1 * ggx2; 51 | } 52 | 53 | // Core PBR calculation - returns direct lighting only 54 | // (no ambient, no shadows, no SSAO - those are applied outside) 55 | vec3 calculatePbrLighting( 56 | vec3 normal, 57 | vec3 V, 58 | vec3 lightDir, 59 | PbrMaterial material, 60 | vec3 lightColor, 61 | float lightIntensity 62 | ) { 63 | vec3 H = normalize(lightDir + V); 64 | 65 | // Base reflectivity 66 | vec3 specColor = mix(F0.rrr, material.baseColor.rgb, material.metalness); 67 | 68 | // Cook-Torrance BRDF 69 | float NDF = distributionGGX(normal, H, material.roughness); 70 | float G = geometrySmith(normal, V, lightDir, material.roughness); 71 | vec3 F = fresnelSchlick(max(dot(H, V), 0.0), specColor); 72 | 73 | // Specular and diffuse components 74 | float denominator = 4.0 * max(dot(normal, V), dot(normal, lightDir)) + 0.0001; 75 | vec3 specular = NDF * G * F / denominator; 76 | 77 | // Energy conservation: diffuse and specular 78 | vec3 kS = F; 79 | vec3 kD = vec3(1.0) - kS; 80 | 81 | // Only non-metallic surfaces have diffuse lighting 82 | kD *= 1.0 - material.metalness; 83 | 84 | // Combine lighting (no attenuation for directional light) 85 | float NdotL = max(dot(normal, lightDir), 0.0); 86 | const vec3 lightCol = lightIntensity * lightColor; 87 | 88 | // Return direct lighting contribution 89 | return (kD * material.baseColor.rgb / PI + specular) * lightCol * NdotL; 90 | } 91 | 92 | #endif // _PBR_LIGHTING_GLSL_ 93 | -------------------------------------------------------------------------------- /shaders/src/tone_mapping.glsl: -------------------------------------------------------------------------------- 1 | // See https://64.github.io/tonemapping/ 2 | 3 | // Reinhard Tone Mapping 4 | vec3 reinhardToneMapping(vec3 color) { 5 | return color / (color + vec3(1.0)); 6 | } 7 | 8 | vec3 exposureToneMapping(vec3 color, float exposure) { 9 | // Exposure adjustment 10 | return 1.0 - exp(-color * exposure); 11 | } 12 | 13 | // ACES Filmic Tone Mapping (Academy Color Encoding System) 14 | vec3 acesFastToneMapping(vec3 color) { 15 | float a = 2.51f; 16 | float b = 0.03f; 17 | float c = 2.43f; 18 | float d = 0.59f; 19 | float e = 0.14f; 20 | 21 | return clamp( 22 | (color * (a * color + vec3(b))) / 23 | (color * (c * color + vec3(d)) + vec3(e)), 24 | vec3(0.0), 25 | vec3(1.0) 26 | ); 27 | } 28 | 29 | // Uncharted 2 Tone Mapping (John Hable) 30 | vec3 uncharted2ToneMapping_Partial(vec3 color) { 31 | float A = 0.15f; // Shoulder strength 32 | float B = 0.50f; // Linear strength 33 | float C = 0.10f; // Linear angle 34 | float D = 0.20f; // Toe strength 35 | float E = 0.02f; // Toe numerator 36 | float F = 0.30f; // Toe denominator 37 | 38 | return (color * (A * color + C * B) + D * E) / 39 | (color * (A * color + B) + D * F) - vec3(E / F); 40 | } 41 | 42 | vec3 uncharted2ToneMapping(vec3 color) { 43 | float exposure_bias = 2.0; 44 | vec3 curr = uncharted2ToneMapping_Partial(exposure_bias * color); 45 | 46 | vec3 W = vec3(11.2f); // White point 47 | vec3 white_scale = vec3(1.0) / uncharted2ToneMapping_Partial(W); 48 | return curr * white_scale; 49 | } 50 | 51 | vec3 saturate(vec3 color) { 52 | return clamp(color, vec3(0.0), vec3(1.0)); 53 | } 54 | -------------------------------------------------------------------------------- /shaders/src/utils.glsl: -------------------------------------------------------------------------------- 1 | #ifndef _UTILS_GLSL_ 2 | #define _UTILS_GLSL_ 3 | 4 | 5 | bool isCoordsInRange(vec3 uv) { 6 | return uv.x >= 0.0 && 7 | uv.y >= 0.0 && 8 | uv.x <= 1.0 && 9 | uv.y <= 1.0 && 10 | uv.z >= 0.0 && 11 | uv.z <= 1.0; 12 | } 13 | 14 | bool isCoordsInRange(vec2 uv) { 15 | return uv.x >= 0.0 && 16 | uv.y >= 0.0 && 17 | uv.x <= 1.0 && 18 | uv.y <= 1.0; 19 | } 20 | 21 | float linearizeDepth(float depth, float zNear, float zFar) { 22 | float z_ndc = 2.0 * depth - 1.0; 23 | return (2.0 * zNear * zFar) / (zFar + zNear - z_ndc * (zFar - zNear)); 24 | } 25 | 26 | float linearizeDepthOrtho(float depth, float zNear, float zFar) { 27 | return zNear + depth * (zFar - zNear); 28 | } 29 | 30 | #endif // _UTILS_GLSL_ 31 | -------------------------------------------------------------------------------- /src/candlewick/core/Camera.cpp: -------------------------------------------------------------------------------- 1 | #include "Camera.h" 2 | #include "CameraControls.h" 3 | 4 | namespace candlewick { 5 | 6 | Mat4f lookAt(const Float3 &eye, const Float3 ¢er, const Float3 &up) { 7 | Mat4f mat; 8 | mat.setIdentity(); 9 | auto R = mat.block<3, 3>(0, 0); 10 | Float3 zaxis = (eye - center).normalized(); 11 | Float3 xaxis = up.cross(zaxis).normalized(); 12 | Float3 yaxis = zaxis.cross(xaxis).normalized(); 13 | R.row(0) = xaxis; 14 | R.row(1) = yaxis; 15 | R.row(2) = zaxis; 16 | // coords of eye in new reference frame 17 | auto tr = mat.col(3).head<3>(); 18 | tr.noalias() = -R * eye; 19 | return mat; 20 | } 21 | 22 | Mat4f perspectiveFromFov(Radf fovY, float aspectRatio, float nearZ, 23 | float farZ) { 24 | float f = 1.0f / std::tan(fovY * 0.5f); 25 | 26 | Mat4f result = Mat4f::Zero(); 27 | result(0, 0) = f / aspectRatio; 28 | result(1, 1) = f; 29 | result(2, 2) = (farZ + nearZ) / (nearZ - farZ); 30 | result(3, 2) = -1.0f; 31 | result(2, 3) = (2.0f * farZ * nearZ) / (nearZ - farZ); 32 | return result; 33 | } 34 | 35 | Mat4f orthographicMatrix(float left, float right, float bottom, float top, 36 | float near, float far) { 37 | const float sx = right - left; 38 | const float sy = top - bottom; 39 | const float zScale = 2.0f / (near - far); 40 | const float m23 = (near + far) / (near - far); 41 | const float px = (left + right) / sx; 42 | const float py = (top + bottom) / sy; 43 | Mat4f proj; 44 | // clang-format off 45 | proj << 2.f / sx, 0. , 0. , -px, 46 | 0. , 2.f / sy, 0. , -py, 47 | 0. , 0. , zScale , m23, 48 | 0. , 0. , 0. , 1.; 49 | // clang-format on 50 | return proj; 51 | } 52 | 53 | Mat4f orthographicMatrix(const Float2 &sizes, float near, float far) { 54 | const float sx = 2.f / sizes.x(); 55 | const float sy = 2.f / sizes.y(); 56 | const float sz = 2.0f / (near - far); 57 | const float m23 = (near + far) / (near - far); 58 | Mat4f proj; 59 | proj << sx, 0., 0., 0., // 60 | 0., sy, 0., 0., // 61 | 0., 0., sz, m23, // 62 | 0., 0., 0., 1.; 63 | return proj; 64 | } 65 | 66 | namespace camera_util { 67 | void rotateAroundPoint(Camera &camera, const Mat3f &R, const Float3 &p) { 68 | Float3 np = R * p - p; 69 | Eigen::Isometry3f A{R}; 70 | A.translate(np); 71 | camera.view = camera.view * A; 72 | } 73 | } // namespace camera_util 74 | 75 | CylindricalCamera &CylindricalCamera::pan(Float2 step_, float sensitivity) { 76 | step_ = sensitivity * step_; 77 | step_.y() = -step_.y(); 78 | const Float3 step{step_.x(), step_.y(), 0.f}; 79 | camera_util::localTranslate(camera, step); 80 | const Float3 worldStep = camera.view.linear().transpose() * step; 81 | target += worldStep; 82 | return *this; 83 | } 84 | 85 | CylindricalCamera &CylindricalCamera::moveInOut(float baseScale, float offset) { 86 | const float alpha = std::pow(baseScale, offset); 87 | Float3 vt = camera.transformPoint(target); 88 | const float curDist = -vt.z(); 89 | 90 | // if > 0, we move closer and currentDist decreases 91 | // this is the step *forwards*. 92 | float step = (alpha - 1.f) * curDist; 93 | const float MIN_DIST = 0.05f; 94 | 95 | if (curDist < 0) { 96 | float recoverStep = 0.1f * curDist; 97 | camera_util::localTranslateZ(camera, recoverStep); 98 | return *this; 99 | } 100 | 101 | if (step > 0 && curDist - step < MIN_DIST) { 102 | step = MIN_DIST - curDist; 103 | } 104 | camera_util::localTranslateZ(camera, step); 105 | return *this; 106 | } 107 | 108 | } // namespace candlewick 109 | -------------------------------------------------------------------------------- /src/candlewick/core/Collision.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include "math_types.h" 3 | #include 4 | #include 5 | 6 | namespace candlewick { 7 | 8 | using coal::AABB; 9 | using coal::OBB; 10 | 11 | inline Mat4f toTransformationMatrix(const AABB &aabb) { 12 | Mat4f T = Mat4f::Identity(); 13 | Float3 halfExtents = 0.5f * (aabb.max_ - aabb.min_).cast(); 14 | T.block<3, 3>(0, 0) = halfExtents.asDiagonal(); 15 | T.topRightCorner<3, 1>() = aabb.center().cast(); 16 | return T; 17 | } 18 | 19 | inline Mat4f toTransformationMatrix(const OBB &obb) { 20 | Mat4f T = Mat4f::Identity(); 21 | auto D = obb.extent.asDiagonal(); 22 | T.block<3, 3>(0, 0) = (obb.axes * D).cast(); 23 | T.topRightCorner<3, 1>() = obb.center().cast(); 24 | return T; 25 | } 26 | 27 | inline std::array getAABBCorners(const AABB &aabb) { 28 | const Float3 min = aabb.min_.cast(); 29 | const Float3 max = aabb.max_.cast(); 30 | 31 | return { 32 | Float3{min.x(), min.y(), min.z()}, // 000 33 | Float3{max.x(), min.y(), min.z()}, // 100 34 | Float3{min.x(), max.y(), min.z()}, // 010 35 | Float3{max.x(), max.y(), min.z()}, // 110 36 | Float3{min.x(), min.y(), max.z()}, // 001 37 | Float3{max.x(), min.y(), max.z()}, // 101 38 | Float3{min.x(), max.y(), max.z()}, // 011 39 | Float3{max.x(), max.y(), max.z()}, // 111 40 | }; 41 | } 42 | 43 | } // namespace candlewick 44 | -------------------------------------------------------------------------------- /src/candlewick/core/CommandBuffer.cpp: -------------------------------------------------------------------------------- 1 | #include "CommandBuffer.h" 2 | #include "Device.h" 3 | 4 | namespace candlewick { 5 | 6 | CommandBuffer::CommandBuffer(const Device &device) { 7 | _cmdBuf = SDL_AcquireGPUCommandBuffer(device); 8 | } 9 | 10 | CommandBuffer::CommandBuffer(CommandBuffer &&other) noexcept 11 | : _cmdBuf(other._cmdBuf) { 12 | other._cmdBuf = nullptr; 13 | } 14 | 15 | CommandBuffer &CommandBuffer::operator=(CommandBuffer &&other) noexcept { 16 | if (active()) { 17 | this->cancel(); 18 | } 19 | _cmdBuf = other._cmdBuf; 20 | other._cmdBuf = nullptr; 21 | return *this; 22 | } 23 | 24 | bool CommandBuffer::cancel() noexcept { 25 | bool ret = SDL_CancelGPUCommandBuffer(_cmdBuf); 26 | _cmdBuf = nullptr; 27 | if (!ret) { 28 | SDL_LogWarn(SDL_LOG_CATEGORY_APPLICATION, 29 | "Failed to cancel command buffer: %s", SDL_GetError()); 30 | return false; 31 | } 32 | return true; 33 | } 34 | 35 | } // namespace candlewick 36 | -------------------------------------------------------------------------------- /src/candlewick/core/Components.cpp: -------------------------------------------------------------------------------- 1 | #include "Components.h" 2 | #include 3 | 4 | namespace candlewick { 5 | bool updateTransparencyClassification(entt::registry ®, entt::entity entity, 6 | const MeshMaterialComponent &mmc) { 7 | bool hasTransparency = std::ranges::any_of(mmc.materials, [](auto &material) { 8 | return material.baseColor.w() < 1.0f; 9 | }); 10 | 11 | if (hasTransparency) { 12 | reg.remove(entity); 13 | } else { 14 | reg.emplace_or_replace(entity); 15 | } 16 | return hasTransparency; 17 | } 18 | } // namespace candlewick 19 | -------------------------------------------------------------------------------- /src/candlewick/core/Components.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include "math_types.h" 3 | #include "Mesh.h" 4 | #include "MaterialUniform.h" 5 | 6 | #include 7 | 8 | namespace candlewick { 9 | 10 | /// Tag struct for denoting an entity as opaque, for render pass organization. 11 | struct Opaque {}; 12 | 13 | /// Tag struct for disabled (invisible) entities. 14 | struct Disable {}; 15 | 16 | // Tag environment entities 17 | struct EnvironmentTag {}; 18 | 19 | struct TransformComponent : Mat4f { 20 | using Mat4f::Mat4f; 21 | using Mat4f::operator=; 22 | }; 23 | 24 | struct MeshMaterialComponent { 25 | Mesh mesh; 26 | std::vector materials; 27 | MeshMaterialComponent(Mesh &&mesh, std::vector &&materials) 28 | : mesh(std::move(mesh)), materials(std::move(materials)) { 29 | assert(mesh.numViews() == materials.size()); 30 | } 31 | }; 32 | 33 | /// \brief Updates (adds or removes) the Opaque tag component for a given 34 | /// entity. 35 | /// \param reg The entity registry 36 | /// \param entity The entity 37 | /// \param mmc The mesh-material element to inspect for existence of any 38 | /// transparent subobjects. 39 | /// \returns whether the entity is transparent. 40 | bool updateTransparencyClassification(entt::registry ®, entt::entity entity, 41 | const MeshMaterialComponent &mmc); 42 | 43 | inline void toggleDisable(entt::registry ®, entt::entity id, bool flag) { 44 | if (flag) 45 | reg.remove(id); 46 | else 47 | reg.emplace(id); 48 | } 49 | 50 | } // namespace candlewick 51 | -------------------------------------------------------------------------------- /src/candlewick/core/Core.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | 6 | #define CDW_ASSERT(condition, msg) assert(((condition) && (msg))) 7 | 8 | namespace candlewick { 9 | struct Camera; 10 | class CommandBuffer; 11 | struct Device; 12 | class Texture; 13 | class Mesh; 14 | class MeshView; 15 | class MeshLayout; 16 | struct Shader; 17 | struct RenderContext; 18 | struct Window; 19 | 20 | using coal::AABB; 21 | 22 | } // namespace candlewick 23 | -------------------------------------------------------------------------------- /src/candlewick/core/DebugScene.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "Scene.h" 4 | #include "Mesh.h" 5 | #include "RenderContext.h" 6 | #include "math_types.h" 7 | 8 | #include 9 | #include 10 | #include 11 | 12 | namespace candlewick { 13 | 14 | enum class DebugPipelines { 15 | TRIANGLE_FILL, 16 | LINE, 17 | }; 18 | 19 | class DebugScene; 20 | 21 | /// \brief A subsystem for the DebugScene. 22 | /// 23 | /// Provides methods for updating debug entities. 24 | /// \sa DebugScene 25 | struct IDebugSubSystem { 26 | virtual void update(DebugScene & /*scene*/) = 0; 27 | virtual ~IDebugSubSystem() = default; 28 | }; 29 | 30 | struct DebugMeshComponent { 31 | DebugPipelines pipeline_type; 32 | Mesh mesh; 33 | // fragment shader 34 | std::vector colors; 35 | bool enable = true; 36 | Float3 scale = Float3::Ones(); 37 | }; 38 | 39 | /// \brief %Scene for organizing debug entities and render systems. 40 | /// 41 | /// This implements a basic render system for DebugMeshComponent. 42 | class DebugScene { 43 | entt::registry &_registry; 44 | const RenderContext &_renderer; 45 | SDL_GPUGraphicsPipeline *_trianglePipeline; 46 | SDL_GPUGraphicsPipeline *_linePipeline; 47 | SDL_GPUTextureFormat _swapchainTextureFormat, _depthFormat; 48 | std::vector> _systems; 49 | 50 | void renderMeshComponents(CommandBuffer &cmdBuf, 51 | SDL_GPURenderPass *render_pass, 52 | const Camera &camera) const; 53 | 54 | public: 55 | enum { TRANSFORM_SLOT = 0 }; 56 | enum { COLOR_SLOT = 0 }; 57 | 58 | DebugScene(entt::registry ®istry, const RenderContext &renderer); 59 | DebugScene(const DebugScene &) = delete; 60 | DebugScene &operator=(const DebugScene &) = delete; 61 | 62 | const Device &device() const noexcept { return _renderer.device; } 63 | entt::registry ®istry() { return _registry; } 64 | const entt::registry ®istry() const { return _registry; } 65 | 66 | /// \brief Add a subsystem (IDebugSubSystem) to the scene. 67 | template System, typename... Args> 68 | System &addSystem(Args &&...args) { 69 | auto sys = std::make_unique(std::forward(args)...); 70 | _systems.push_back(std::move(sys)); 71 | return static_cast(*_systems.back()); 72 | } 73 | 74 | /// \brief Setup pipelines; this will only have an effect **ONCE**. 75 | void setupPipelines(const MeshLayout &layout); 76 | 77 | /// \brief Just the basic 3D triad. 78 | std::tuple addTriad(); 79 | /// \brief Add a basic line grid. 80 | std::tuple 81 | addLineGrid(std::optional color = std::nullopt); 82 | 83 | void update() { 84 | for (auto &system : _systems) { 85 | system->update(*this); 86 | } 87 | } 88 | 89 | void render(CommandBuffer &cmdBuf, const Camera &camera) const; 90 | 91 | void release(); 92 | 93 | ~DebugScene() { release(); } 94 | }; 95 | static_assert(Scene); 96 | 97 | } // namespace candlewick 98 | -------------------------------------------------------------------------------- /src/candlewick/core/DefaultVertex.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "../core/math_types.h" 4 | #include "../core/MeshLayout.h" 5 | 6 | namespace candlewick { 7 | 8 | struct alignas(16) DefaultVertex { 9 | GpuVec3 pos; 10 | alignas(16) GpuVec3 normal; 11 | alignas(16) GpuVec4 color; 12 | alignas(16) GpuVec3 tangent; 13 | }; 14 | static_assert(IsVertexType, ""); 15 | 16 | template <> struct VertexTraits { 17 | static auto layout() { 18 | return MeshLayout{} 19 | .addBinding(0, sizeof(DefaultVertex)) 20 | .addAttribute(VertexAttrib::Position, 0, 21 | SDL_GPU_VERTEXELEMENTFORMAT_FLOAT3, 22 | offsetof(DefaultVertex, pos)) 23 | .addAttribute(VertexAttrib::Normal, 0, 24 | SDL_GPU_VERTEXELEMENTFORMAT_FLOAT3, 25 | offsetof(DefaultVertex, normal)) 26 | .addAttribute(VertexAttrib::Color0, 0, 27 | SDL_GPU_VERTEXELEMENTFORMAT_FLOAT4, 28 | offsetof(DefaultVertex, color)) 29 | .addAttribute(VertexAttrib::Tangent, 0, 30 | SDL_GPU_VERTEXELEMENTFORMAT_FLOAT3, 31 | offsetof(DefaultVertex, tangent)); 32 | } 33 | }; 34 | 35 | } // namespace candlewick 36 | -------------------------------------------------------------------------------- /src/candlewick/core/Device.cpp: -------------------------------------------------------------------------------- 1 | #include "Device.h" 2 | #include "errors.h" 3 | 4 | #include 5 | #include 6 | 7 | namespace candlewick { 8 | 9 | SDL_GPUShaderFormat auto_detect_shader_format_subset(const char *name) { 10 | SDL_GPUShaderFormat available_formats = SDL_GPU_SHADERFORMAT_INVALID; 11 | SDL_GPUShaderFormat formats[]{SDL_GPU_SHADERFORMAT_SPIRV, 12 | SDL_GPU_SHADERFORMAT_DXIL, 13 | SDL_GPU_SHADERFORMAT_MSL}; 14 | for (SDL_GPUShaderFormat test_format : formats) { 15 | if (SDL_GPUSupportsShaderFormats(test_format, name)) 16 | available_formats |= test_format; 17 | } 18 | return available_formats; 19 | } 20 | 21 | Device::Device(SDL_GPUShaderFormat format_flags, bool debug_mode) { 22 | create(format_flags, debug_mode); 23 | } 24 | 25 | void Device::create(SDL_GPUShaderFormat format_flags, bool debug_mode) { 26 | _device = SDL_CreateGPUDevice(format_flags, debug_mode, nullptr); 27 | if (!_device) 28 | throw RAIIException(SDL_GetError()); 29 | const char *driver = SDL_GetGPUDeviceDriver(_device); 30 | SDL_Log("Device driver: %s", driver); 31 | } 32 | 33 | const char *Device::driverName() const noexcept { 34 | return SDL_GetGPUDeviceDriver(_device); 35 | } 36 | 37 | void Device::destroy() noexcept { 38 | if (_device) 39 | SDL_DestroyGPUDevice(_device); 40 | _device = nullptr; 41 | } 42 | 43 | } // namespace candlewick 44 | -------------------------------------------------------------------------------- /src/candlewick/core/Device.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "Core.h" 4 | #include "Tags.h" 5 | #include 6 | 7 | namespace candlewick { 8 | 9 | /// \brief Automatically detect which subset of shader formats (MSL, SPIR-V) are 10 | /// compatible with the device. 11 | /// \param name Device name. Pass nullptr (default) to auto-detect the best 12 | /// device. 13 | SDL_GPUShaderFormat 14 | auto_detect_shader_format_subset(const char *name = nullptr); 15 | 16 | /// \brief RAII wrapper for \c SDL_GPUDevice. 17 | struct Device { 18 | 19 | explicit Device(NoInitT) noexcept; 20 | explicit Device(SDL_GPUShaderFormat format_flags, bool debug_mode = false); 21 | Device(const Device &) = delete; 22 | Device(Device &&other) noexcept; 23 | Device &operator=(Device &&) = delete; 24 | 25 | void create(SDL_GPUShaderFormat format_flags, bool debug_mode = false); 26 | 27 | operator SDL_GPUDevice *() const { return _device; } 28 | operator bool() const { return _device; } 29 | 30 | const char *driverName() const noexcept; 31 | 32 | SDL_GPUShaderFormat shaderFormats() const { 33 | return SDL_GetGPUShaderFormats(_device); 34 | } 35 | 36 | /// \brief Release ownership of and return the \c SDL_GPUDevice handle. 37 | SDL_GPUDevice *release() noexcept { 38 | return _device; 39 | _device = nullptr; 40 | } 41 | void destroy() noexcept; 42 | 43 | bool operator==(const Device &other) const { 44 | return _device && (_device == other._device); 45 | } 46 | 47 | private: 48 | SDL_GPUDevice *_device; 49 | }; 50 | 51 | inline Device::Device(NoInitT) noexcept : _device(nullptr) {} 52 | 53 | inline Device::Device(Device &&other) noexcept { 54 | _device = other._device; 55 | other._device = nullptr; 56 | } 57 | 58 | } // namespace candlewick 59 | -------------------------------------------------------------------------------- /src/candlewick/core/FileDialogGui.cpp: -------------------------------------------------------------------------------- 1 | #include "GuiSystem.h" 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | #include 9 | #include 10 | #include 11 | 12 | namespace candlewick { 13 | 14 | static const SDL_DialogFileFilter g_screenshot_filters[] = { 15 | {"PNG images", "png"}, 16 | {"JPEG images", "jpg;jpeg"}, 17 | {"All images", "png;jpg;jpeg"}, 18 | {"All files", "*"}, 19 | }; 20 | 21 | static const SDL_DialogFileFilter g_video_filters[] = { 22 | {"MP4 files", "mp4;m4v"}, 23 | {"All files", "*"}, 24 | }; 25 | 26 | static const std::pair g_file_filters[] = { 27 | {g_screenshot_filters, SDL_arraysize(g_screenshot_filters)}, 28 | {g_video_filters, SDL_arraysize(g_video_filters)}, 29 | }; 30 | 31 | static void fileCallbackImpl(void *userdata_, const char *const *filelist, 32 | int) { 33 | if (!filelist) { 34 | SDL_Log("An error occured: %s", SDL_GetError()); 35 | return; 36 | } else if (!*filelist) { 37 | SDL_Log("The user did not select any file."); 38 | SDL_Log("Most likely, the dialog was canceled."); 39 | return; 40 | } 41 | 42 | std::string *data = (std::string *)userdata_; 43 | while (*filelist) { 44 | *data = *filelist; 45 | filelist++; 46 | } 47 | } 48 | 49 | static const SDL_Folder g_dialog_file_type_folder[] = { 50 | SDL_FOLDER_PICTURES, 51 | SDL_FOLDER_VIDEOS, 52 | }; 53 | 54 | void guiAddFileDialog(SDL_Window *window, DialogFileType dialog_file_type, 55 | std::string &out) { 56 | const char *initial_path = 57 | SDL_GetUserFolder(g_dialog_file_type_folder[int(dialog_file_type)]); 58 | 59 | auto [filters, nfilters] = g_file_filters[int(dialog_file_type)]; 60 | 61 | if (ImGui::Button("Select...")) { 62 | SDL_ShowSaveFileDialog(fileCallbackImpl, &out, window, filters, nfilters, 63 | initial_path); 64 | } 65 | ImGui::SameLine(); 66 | ImGui::Text("%s", out.empty() ? "(none)" : out.c_str()); 67 | } 68 | 69 | std::string generateMediaFilenameFromTimestamp(const char *prefix, 70 | const char *extension, 71 | DialogFileType file_type) { 72 | const std::chrono::time_point now = std::chrono::system_clock::now(); 73 | auto time = std::chrono::system_clock::to_time_t(now); 74 | auto tm = std::localtime(&time); 75 | const char *picturesDir = 76 | SDL_GetUserFolder(g_dialog_file_type_folder[int(file_type)]); 77 | std::ostringstream oss; 78 | oss << picturesDir << prefix << " " << std::put_time(tm, "%F %H-%M-%S %z") 79 | << extension; 80 | return oss.str(); 81 | } 82 | 83 | } // namespace candlewick 84 | -------------------------------------------------------------------------------- /src/candlewick/core/GuiSystem.h: -------------------------------------------------------------------------------- 1 | /// \defgroup gui_util GUI utilities 2 | /// Tools, render systems, etc... for the Candlewick GUI. 3 | #include "Core.h" 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | struct SDL_Window; 10 | 11 | namespace candlewick { 12 | 13 | class GuiSystem { 14 | RenderContext const *m_renderer; 15 | bool m_initialized = false; 16 | 17 | public: 18 | using GuiBehavior = std::function; 19 | 20 | GuiSystem(const RenderContext &renderer, GuiBehavior behav); 21 | 22 | void addCallback(GuiBehavior cb) { _callbacks.push_back(std::move(cb)); } 23 | 24 | void render(CommandBuffer &cmdBuf); 25 | 26 | void release(); 27 | 28 | bool initialized() const { return m_initialized; } 29 | 30 | std::vector _callbacks; 31 | 32 | private: 33 | bool init(const RenderContext &renderer); 34 | }; 35 | 36 | /// \ingroup gui_util 37 | /// \{ 38 | /// \brief Show an about window providing information about Candlewick. 39 | void showCandlewickAboutWindow(bool *p_open = NULL, float wrap_width = 400.f); 40 | 41 | struct DirectionalLight; 42 | 43 | /// \brief Adds a set of ImGui elements to control a DirectionalLight. 44 | void guiAddLightControls(DirectionalLight &light); 45 | 46 | /// \brief Add controls for multiple lights. 47 | void guiAddLightControls(std::span lights); 48 | 49 | void guiAddLightControls(std::span lights, Uint32 numLights, 50 | Uint32 start = 0); 51 | 52 | /// \brief Adds an ImGui::Checkbox which toggles the Disable component on the 53 | /// entity. 54 | void guiAddDisableCheckbox(const char *label, entt::registry ®, 55 | entt::entity id, bool &flag); 56 | 57 | enum class DialogFileType { IMAGES, VIDEOS }; 58 | 59 | /// \brief Add a GUI button-text pair to select a file to save something to. 60 | /// 61 | /// This function can only be called from the main thread. 62 | void guiAddFileDialog(SDL_Window *window, DialogFileType dialog_file_type, 63 | std::string &filename); 64 | /// \} 65 | 66 | std::string generateMediaFilenameFromTimestamp( 67 | const char *prefix = "cdw_screenshot", const char *extension = ".png", 68 | DialogFileType file_type = DialogFileType::IMAGES); 69 | 70 | } // namespace candlewick 71 | -------------------------------------------------------------------------------- /src/candlewick/core/LightUniforms.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "math_types.h" 4 | 5 | namespace candlewick { 6 | 7 | struct DirectionalLight { 8 | Float3 direction; 9 | Float3 color; 10 | float intensity; 11 | }; 12 | static_assert(std::is_standard_layout_v); 13 | 14 | } // namespace candlewick 15 | -------------------------------------------------------------------------------- /src/candlewick/core/MaterialUniform.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "math_types.h" 4 | 5 | namespace candlewick { 6 | 7 | /// \brief PBR material for metallic-roughness workflow. 8 | struct alignas(16) PbrMaterial { 9 | GpuVec4 baseColor{1., 1., 1., 1.}; 10 | float metalness = 0.f; 11 | float roughness = 1.0f; 12 | float ao = 1.0f; 13 | }; 14 | 15 | /// \brief Material parameters for a Blinn-Phong lighting model. 16 | struct alignas(16) PhongMaterial { 17 | GpuVec4 diffuse; 18 | GpuVec4 ambient{0.2f, 0.2f, 0.2f, 1.0f}; 19 | GpuVec4 specular{0.5f, 0.5f, 0.5f, 1.0f}; 20 | GpuVec4 emissive{Float4::Zero()}; 21 | float shininess = 1.0f; 22 | float reflectivity = 0.0f; 23 | }; 24 | 25 | } // namespace candlewick 26 | -------------------------------------------------------------------------------- /src/candlewick/core/Mesh.cpp: -------------------------------------------------------------------------------- 1 | #include "Mesh.h" 2 | 3 | #include "Device.h" 4 | #include "MeshLayout.h" 5 | #include "./errors.h" 6 | 7 | namespace candlewick { 8 | 9 | MeshView::MeshView(const MeshView &parent, Uint32 subVertexOffset, 10 | Uint32 subVertexCount, Uint32 subIndexOffset, 11 | Uint32 subIndexCount) 12 | : vertexBuffers(parent.vertexBuffers) 13 | , indexBuffer(parent.indexBuffer) 14 | , vertexOffset(parent.vertexOffset + subVertexOffset) 15 | , vertexCount(subVertexCount) 16 | , indexOffset(parent.indexOffset + subIndexOffset) 17 | , indexCount(subIndexCount) { 18 | // assumption: parent MeshView is validated 19 | CDW_ASSERT(validateMeshView(*this), "MeshView failed validation."); 20 | CDW_ASSERT(subVertexOffset + subVertexCount <= parent.vertexCount && 21 | subIndexOffset + subIndexCount <= parent.indexCount, 22 | ""); 23 | } 24 | 25 | Mesh::Mesh(NoInitT) {} 26 | 27 | Mesh::Mesh(const Device &device, const MeshLayout &layout) 28 | : m_device(device), m_layout(layout) { 29 | const Uint32 count = this->m_layout.numBuffers(); 30 | vertexBuffers.resize(count, nullptr); 31 | } 32 | 33 | Mesh::Mesh(Mesh &&other) noexcept 34 | : m_device(other.m_device) 35 | , m_views(std::move(other.m_views)) 36 | , m_layout(other.m_layout) 37 | , vertexCount(other.vertexCount) 38 | , indexCount(other.indexCount) 39 | , vertexBuffers(std::move(other.vertexBuffers)) 40 | , indexBuffer(other.indexBuffer) { 41 | other.m_device = nullptr; 42 | other.indexBuffer = nullptr; 43 | } 44 | 45 | Mesh &Mesh::operator=(Mesh &&other) noexcept { 46 | if (this != &other) { 47 | if (m_device) { 48 | release(); 49 | } 50 | 51 | m_device = other.m_device; 52 | m_views = std::move(other.m_views); 53 | m_layout = std::move(other.m_layout); 54 | vertexCount = std::move(other.vertexCount); 55 | indexCount = std::move(other.indexCount); 56 | vertexBuffers = std::move(other.vertexBuffers); 57 | indexBuffer = std::move(other.indexBuffer); 58 | 59 | other.m_device = nullptr; 60 | other.vertexCount = 0u; 61 | other.indexCount = 0u; 62 | other.indexBuffer = nullptr; 63 | } 64 | return *this; 65 | } 66 | 67 | Mesh &Mesh::bindVertexBuffer(Uint32 slot, SDL_GPUBuffer *buffer) { 68 | for (std::size_t i = 0; i < numVertexBuffers(); i++) { 69 | if (m_layout.m_bufferDescs[i].slot == slot) { 70 | if (vertexBuffers[i]) { 71 | terminate_with_message( 72 | "Rebinding vertex buffer to already occupied slot."); 73 | } 74 | vertexBuffers[i] = buffer; 75 | return *this; 76 | } 77 | } 78 | terminate_with_message("Binding slot not found!"); 79 | } 80 | 81 | Mesh &Mesh::setIndexBuffer(SDL_GPUBuffer *buffer) { 82 | indexBuffer = buffer; 83 | return *this; 84 | } 85 | 86 | void Mesh::release() noexcept { 87 | if (!m_device) 88 | return; 89 | 90 | for (std::size_t i = 0; i < vertexBuffers.size(); i++) { 91 | if (vertexBuffers[i]) 92 | SDL_ReleaseGPUBuffer(m_device, vertexBuffers[i]); 93 | } 94 | vertexBuffers.clear(); 95 | 96 | if (isIndexed()) { 97 | SDL_ReleaseGPUBuffer(m_device, indexBuffer); 98 | indexBuffer = nullptr; 99 | } 100 | } 101 | 102 | MeshView &Mesh::addView(Uint32 vertexOffset, Uint32 vertexSubCount, 103 | Uint32 indexOffset, Uint32 indexSubCount) { 104 | MeshView v; 105 | v.vertexBuffers = vertexBuffers; 106 | v.indexBuffer = indexBuffer; 107 | v.vertexOffset = vertexOffset; 108 | v.vertexCount = vertexSubCount; 109 | v.indexOffset = indexOffset; 110 | v.indexCount = indexSubCount; 111 | 112 | return m_views.emplace_back(std::move(v)); 113 | } 114 | 115 | } // namespace candlewick 116 | -------------------------------------------------------------------------------- /src/candlewick/core/Renderer.h: -------------------------------------------------------------------------------- 1 | #include "candlewick/deprecated.h" 2 | #include "RenderContext.h" 3 | 4 | CANDLEWICK_DEPRECATED_HEADER( 5 | "Include \'\' instead.") 6 | -------------------------------------------------------------------------------- /src/candlewick/core/Scene.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "Core.h" 4 | #include 5 | 6 | namespace candlewick { 7 | 8 | /// \brief The Scene concept requires that there exist functions to render the 9 | /// scene. Provided a command buffer and Camera, and that there is a function to 10 | /// release the resources of the Scene. 11 | template 12 | concept Scene = requires(T t, CommandBuffer &cmdBuf, const Camera &camera) { 13 | { t.render(cmdBuf, camera) } -> std::same_as; 14 | { t.release() } -> std::same_as; 15 | }; 16 | 17 | } // namespace candlewick 18 | -------------------------------------------------------------------------------- /src/candlewick/core/Shader.h: -------------------------------------------------------------------------------- 1 | /// \defgroup shaders Shaders 2 | #pragma once 3 | 4 | #include "Core.h" 5 | #include 6 | 7 | namespace candlewick { 8 | 9 | constexpr const char *g_default_shader_dir = CANDLEWICK_SHADER_BIN_DIR; 10 | 11 | /// \brief Set the current (global) directory where shaders are to be found. 12 | /// \sa currentShaderDirectory() 13 | void setShadersDirectory(const char *path); 14 | /// \brief Get the current (global) directory where shaders are found. 15 | const char *currentShaderDirectory(); 16 | 17 | SDL_GPUShaderStage detect_shader_stage(const char *filename); 18 | 19 | const char *shader_format_name(SDL_GPUShaderFormat shader_format); 20 | 21 | /// \ingroup shaders 22 | /// \brief RAII wrapper around \c SDL_GPUShader, with loading utilities. 23 | /// 24 | /// The Shader class wrap around the SDL GPU shader handle. It creates it upon 25 | /// construction of the class, and will release it upon destruction. 26 | struct Shader { 27 | /// \brief %Shader configuration: number of uniforms, texture samplers, 28 | /// storage textures and storage buffers. 29 | struct Config { 30 | Uint32 uniform_buffers; 31 | Uint32 samplers; 32 | Uint32 storage_textures = 0; 33 | Uint32 storage_buffers = 0; 34 | }; 35 | /// \brief Load a shader to device provided the shader name and configuration. 36 | /// \param device GPU device 37 | /// \param filename Filename of the shader text source, as in the 38 | /// `assets/shaders/src` 39 | /// \param config %Configuration struct 40 | Shader(const Device &device, const char *filename, const Config &config); 41 | /// Deleted copy constructor 42 | Shader(const Shader &) = delete; 43 | /// Move constructor 44 | Shader(Shader &&other) noexcept; 45 | 46 | operator SDL_GPUShader *() const noexcept { return _shader; } 47 | void release() noexcept; 48 | ~Shader() noexcept { release(); } 49 | 50 | /// \brief Load shader from metadata. 51 | static Shader fromMetadata(const Device &device, const char *filename); 52 | 53 | SDL_GPUShaderStage stage() const noexcept { return _stage; } 54 | 55 | private: 56 | SDL_GPUShader *_shader; 57 | SDL_GPUDevice *_device; 58 | SDL_GPUShaderStage _stage; 59 | }; 60 | 61 | inline Shader::Shader(Shader &&other) noexcept 62 | : _shader(other._shader), _device(other._device) { 63 | other._shader = nullptr; 64 | other._device = nullptr; 65 | } 66 | 67 | /// \brief Load shader config from metadata. Metadata filename (in JSON format) 68 | /// is inferred from the shader name. 69 | Shader::Config loadShaderMetadata(const char *shader_name); 70 | 71 | inline Shader Shader::fromMetadata(const Device &device, 72 | const char *shader_name) { 73 | auto config = loadShaderMetadata(shader_name); 74 | return Shader{device, shader_name, config}; 75 | } 76 | 77 | } // namespace candlewick 78 | -------------------------------------------------------------------------------- /src/candlewick/core/Tags.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | namespace candlewick { 4 | 5 | /// \brief Tag type for non-initializing constructors (for e.g. RAII classes) 6 | struct NoInitT { 7 | constexpr explicit NoInitT() {} 8 | }; 9 | constexpr NoInitT NoInit{}; 10 | 11 | } // namespace candlewick 12 | -------------------------------------------------------------------------------- /src/candlewick/core/Texture.cpp: -------------------------------------------------------------------------------- 1 | #include "Texture.h" 2 | #include "Device.h" 3 | #include "errors.h" 4 | #include 5 | 6 | #include 7 | 8 | namespace candlewick { 9 | 10 | Texture::Texture(const Device &device, SDL_GPUTextureCreateInfo texture_desc, 11 | const char *name) 12 | : _device(device) 13 | , _texture(nullptr) 14 | , _description(std::move(texture_desc)) { 15 | if (!(_texture = SDL_CreateGPUTexture(_device, &_description))) { 16 | std::string msg = std::format("Failed to create texture with format (%s)", 17 | magic_enum::enum_name(_description.format)); 18 | if (name) 19 | msg += std::format(" (name %s)", name); 20 | throw RAIIException(std::move(msg)); 21 | } 22 | if (name != nullptr) 23 | SDL_SetGPUTextureName(_device, _texture, name); 24 | } 25 | 26 | Texture::Texture(Texture &&other) noexcept 27 | : _device(other._device) 28 | , _texture(other._texture) 29 | , _description(std::move(other._description)) { 30 | other._device = nullptr; 31 | other._texture = nullptr; 32 | } 33 | 34 | Texture &Texture::operator=(Texture &&other) noexcept { 35 | this->destroy(); 36 | _device = other._device; 37 | _texture = other._texture; 38 | _description = std::move(other._description); 39 | 40 | other._device = nullptr; 41 | other._texture = nullptr; 42 | return *this; 43 | } 44 | 45 | SDL_GPUBlitRegion Texture::blitRegion(Uint32 x, Uint32 y, 46 | Uint32 layer_or_depth_plane) const { 47 | CDW_ASSERT(layer_or_depth_plane < layerCount(), 48 | "layer is higher than layerCount!"); 49 | return { 50 | .texture = _texture, 51 | .mip_level = 0, 52 | .layer_or_depth_plane = layer_or_depth_plane, 53 | .x = x, 54 | .y = y, 55 | .w = width(), 56 | .h = height(), 57 | }; 58 | } 59 | 60 | Uint32 Texture::textureSize() const { 61 | return SDL_CalculateGPUTextureFormatSize(format(), width(), height(), 62 | depth()); 63 | } 64 | 65 | void Texture::destroy() noexcept { 66 | if (_device && _texture) { 67 | SDL_ReleaseGPUTexture(_device, _texture); 68 | _texture = nullptr; 69 | _device = nullptr; 70 | } 71 | } 72 | } // namespace candlewick 73 | -------------------------------------------------------------------------------- /src/candlewick/core/Texture.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "Core.h" 4 | #include "Tags.h" 5 | #include 6 | 7 | namespace candlewick { 8 | 9 | class Texture { 10 | SDL_GPUDevice *_device = nullptr; 11 | SDL_GPUTexture *_texture = nullptr; 12 | SDL_GPUTextureCreateInfo _description; 13 | 14 | public: 15 | Texture(NoInitT) {} 16 | Texture(const Device &device, SDL_GPUTextureCreateInfo texture_desc, 17 | const char *name = nullptr); 18 | 19 | Texture(const Texture &) = delete; 20 | Texture &operator=(const Texture &) = delete; 21 | Texture(Texture &&other) noexcept; 22 | Texture &operator=(Texture &&other) noexcept; 23 | 24 | operator SDL_GPUTexture *() const noexcept { return _texture; } 25 | 26 | bool hasValue() const { return bool(_texture); } 27 | const auto &description() const { return _description; } 28 | SDL_GPUTextureType type() const { return _description.type; } 29 | SDL_GPUTextureFormat format() const { return _description.format; } 30 | SDL_GPUTextureUsageFlags usage() const { return _description.usage; } 31 | Uint32 width() const { return _description.width; } 32 | Uint32 height() const { return _description.height; } 33 | Uint32 depth() const { return _description.layer_count_or_depth; } 34 | Uint32 layerCount() const { return _description.layer_count_or_depth; } 35 | 36 | SDL_GPUBlitRegion blitRegion(Uint32 offset_x, Uint32 y_offset, 37 | Uint32 layer_or_depth_plane = 0) const; 38 | 39 | Uint32 textureSize() const; 40 | 41 | void destroy() noexcept; 42 | ~Texture() noexcept { this->destroy(); } 43 | }; 44 | 45 | } // namespace candlewick 46 | -------------------------------------------------------------------------------- /src/candlewick/core/TransformUniforms.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "math_types.h" 4 | 5 | namespace candlewick { 6 | 7 | struct alignas(16) TransformUniformData { 8 | GpuMat4 modelView; 9 | alignas(16) GpuMat4 mvp; 10 | alignas(16) GpuMat3 normalMatrix; 11 | }; 12 | 13 | } // namespace candlewick 14 | -------------------------------------------------------------------------------- /src/candlewick/core/Window.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "Core.h" 4 | #include "errors.h" 5 | #include 6 | #include 7 | 8 | namespace candlewick { 9 | 10 | /// \brief RAII wrapper for the \c SDL_Window opaque type. 11 | struct Window { 12 | 13 | /// \brief Standard constructor, which forwards to \c SDL_CreateWindow. 14 | explicit Window(const char *title, Sint32 w, Sint32 h, SDL_WindowFlags flags); 15 | 16 | /// \brief This constructor takes ownership of the provided handle. 17 | explicit Window(SDL_Window *ptr) : _handle(ptr) {} 18 | 19 | Window(const Window &) = delete; 20 | Window(Window &&other) noexcept : _handle(other._handle) { 21 | other._handle = nullptr; 22 | } 23 | 24 | Window &operator=(Window &&other) noexcept { 25 | this->~Window(); 26 | _handle = other._handle; 27 | other._handle = nullptr; 28 | return *this; 29 | } 30 | 31 | operator SDL_Window *() const { return _handle; } 32 | 33 | std::array size() const; 34 | 35 | std::array sizeInPixels() const; 36 | 37 | float pixelDensity() const { return SDL_GetWindowPixelDensity(_handle); } 38 | 39 | float displayScale() const { return SDL_GetWindowDisplayScale(_handle); } 40 | 41 | SDL_PixelFormat pixelFormat() const { 42 | return SDL_GetWindowPixelFormat(_handle); 43 | } 44 | 45 | SDL_WindowFlags flags() const { return SDL_GetWindowFlags(_handle); } 46 | 47 | bool setTitle(const char *title) { 48 | return SDL_SetWindowTitle(_handle, title); 49 | } 50 | 51 | std::string_view title() const { return SDL_GetWindowTitle(_handle); } 52 | 53 | void destroy() noexcept { 54 | if (_handle) 55 | SDL_DestroyWindow(_handle); 56 | _handle = nullptr; 57 | } 58 | 59 | ~Window() noexcept { this->destroy(); } 60 | 61 | private: 62 | SDL_Window *_handle; 63 | }; 64 | 65 | inline Window::Window(const char *title, Sint32 w, Sint32 h, 66 | SDL_WindowFlags flags) { 67 | _handle = SDL_CreateWindow(title, w, h, flags); 68 | if (!_handle) { 69 | throw RAIIException(SDL_GetError()); 70 | } 71 | } 72 | 73 | inline std::array Window::size() const { 74 | int width, height; 75 | SDL_GetWindowSize(_handle, &width, &height); 76 | return {width, height}; 77 | } 78 | 79 | inline std::array Window::sizeInPixels() const { 80 | int width, height; 81 | if (!SDL_GetWindowSizeInPixels(_handle, &width, &height)) { 82 | } 83 | return {width, height}; 84 | } 85 | 86 | } // namespace candlewick 87 | -------------------------------------------------------------------------------- /src/candlewick/core/debug/DepthViz.cpp: -------------------------------------------------------------------------------- 1 | #include "DepthViz.h" 2 | #include "../RenderContext.h" 3 | #include "../Shader.h" 4 | #include 5 | 6 | namespace candlewick { 7 | 8 | DepthDebugPass DepthDebugPass::create(const RenderContext &renderer, 9 | SDL_GPUTexture *depthTexture) { 10 | const auto &device = renderer.device; 11 | auto vertexShader = Shader::fromMetadata(device, "DrawQuad.vert"); 12 | auto fragmentShader = Shader::fromMetadata(device, "RenderDepth.frag"); 13 | 14 | SDL_GPUColorTargetDescription color_target_desc; 15 | SDL_zero(color_target_desc); 16 | // render to swapchain 17 | color_target_desc.format = renderer.getSwapchainTextureFormat(); 18 | 19 | /* SAMPLER */ 20 | SDL_GPUSamplerCreateInfo sampler_desc{ 21 | .min_filter = SDL_GPU_FILTER_NEAREST, 22 | .mag_filter = SDL_GPU_FILTER_NEAREST, 23 | .mipmap_mode = SDL_GPU_SAMPLERMIPMAPMODE_NEAREST, 24 | .address_mode_u = SDL_GPU_SAMPLERADDRESSMODE_CLAMP_TO_EDGE, 25 | .address_mode_v = SDL_GPU_SAMPLERADDRESSMODE_CLAMP_TO_EDGE, 26 | .address_mode_w = SDL_GPU_SAMPLERADDRESSMODE_CLAMP_TO_EDGE, 27 | .compare_op = SDL_GPU_COMPAREOP_NEVER, 28 | }; 29 | SDL_GPUSampler *sampler = SDL_CreateGPUSampler(device, &sampler_desc); 30 | 31 | /* PIPELINE */ 32 | SDL_GPUGraphicsPipelineCreateInfo pipeline_desc; 33 | SDL_zero(pipeline_desc); 34 | pipeline_desc.vertex_shader = vertexShader; 35 | pipeline_desc.fragment_shader = fragmentShader; 36 | pipeline_desc.primitive_type = SDL_GPU_PRIMITIVETYPE_TRIANGLELIST; 37 | pipeline_desc.rasterizer_state = {.fill_mode = SDL_GPU_FILLMODE_FILL, 38 | .cull_mode = SDL_GPU_CULLMODE_NONE}; 39 | pipeline_desc.depth_stencil_state = {.enable_depth_test = false, 40 | .enable_depth_write = false}; 41 | pipeline_desc.target_info = {.color_target_descriptions = &color_target_desc, 42 | .num_color_targets = 1, 43 | .has_depth_stencil_target = false}; 44 | 45 | SDL_GPUGraphicsPipeline *pipeline = 46 | SDL_CreateGPUGraphicsPipeline(device, &pipeline_desc); 47 | if (!pipeline) { 48 | auto msg = std::format("Failed to create depth debug pipeline: %s", 49 | SDL_GetError()); 50 | throw std::runtime_error(msg); 51 | } 52 | 53 | return {depthTexture, sampler, pipeline}; 54 | } 55 | 56 | struct alignas(16) cam_param_ubo_t { 57 | Sint32 mode; 58 | float near_plane; 59 | float far_plane; 60 | Uint32 is_ortho; 61 | }; 62 | 63 | void renderDepthDebug(const RenderContext &renderer, 64 | CommandBuffer &command_buffer, const DepthDebugPass &pass, 65 | const DepthDebugPass::Options &opts) { 66 | SDL_GPUColorTargetInfo color_target; 67 | SDL_zero(color_target); 68 | color_target.texture = renderer.swapchain; 69 | color_target.clear_color = {0., 0., 0., 1.}; 70 | color_target.load_op = SDL_GPU_LOADOP_CLEAR; 71 | color_target.store_op = SDL_GPU_STOREOP_STORE; 72 | SDL_GPURenderPass *render_pass = 73 | SDL_BeginGPURenderPass(command_buffer, &color_target, 1, nullptr); 74 | 75 | SDL_BindGPUGraphicsPipeline(render_pass, pass.pipeline); 76 | 77 | rend::bindFragmentSamplers(render_pass, 0, 78 | {{ 79 | .texture = pass.depthTexture, 80 | .sampler = pass.sampler, 81 | }}); 82 | 83 | cam_param_ubo_t cam_ubo{opts.mode, opts.near, opts.far, 84 | opts.cam_proj == CameraProjection::ORTHOGRAPHIC}; 85 | 86 | command_buffer.pushFragmentUniform(0, cam_ubo); 87 | SDL_DrawGPUPrimitives(render_pass, 6, 1, 0, 0); 88 | 89 | SDL_EndGPURenderPass(render_pass); 90 | } 91 | 92 | } // namespace candlewick 93 | -------------------------------------------------------------------------------- /src/candlewick/core/debug/DepthViz.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "../Core.h" 4 | #include 5 | #include "../Camera.h" 6 | 7 | namespace candlewick { 8 | 9 | struct DepthDebugPass { 10 | enum VizStyle : int { VIZ_GRAYSCALE, VIZ_HEATMAP }; 11 | struct Options { 12 | VizStyle mode; 13 | float near; 14 | float far; 15 | CameraProjection cam_proj = CameraProjection::PERSPECTIVE; 16 | }; 17 | SDL_GPUTexture *depthTexture; 18 | SDL_GPUSampler *sampler; 19 | SDL_GPUGraphicsPipeline *pipeline; 20 | 21 | static DepthDebugPass create(const RenderContext &renderer, 22 | SDL_GPUTexture *depthTexture); 23 | 24 | void release(SDL_GPUDevice *device); 25 | }; 26 | 27 | inline void DepthDebugPass::release(SDL_GPUDevice *device) { 28 | if (sampler) { 29 | SDL_ReleaseGPUSampler(device, sampler); 30 | sampler = NULL; 31 | } 32 | if (pipeline) { 33 | SDL_ReleaseGPUGraphicsPipeline(device, pipeline); 34 | pipeline = NULL; 35 | } 36 | } 37 | 38 | void renderDepthDebug(const RenderContext &renderer, CommandBuffer &cmdBuf, 39 | const DepthDebugPass &pass, 40 | const DepthDebugPass::Options &opts); 41 | 42 | } // namespace candlewick 43 | -------------------------------------------------------------------------------- /src/candlewick/core/debug/Frustum.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "../Core.h" 4 | #include "../Device.h" 5 | #include "../Collision.h" 6 | #include "../math_types.h" 7 | 8 | #include 9 | #include 10 | #include 11 | 12 | namespace candlewick { 13 | namespace frustum_debug { 14 | 15 | SDL_GPUGraphicsPipeline * 16 | createFrustumDebugPipeline(const RenderContext &renderer); 17 | 18 | void renderFrustum(CommandBuffer &cmdBuf, SDL_GPURenderPass *render_pass, 19 | const Camera &mainCamera, const Camera &otherCam, 20 | const Float4 &color = 0x40FF00CC_rgbaf); 21 | 22 | void renderAABB(CommandBuffer &cmdBuf, SDL_GPURenderPass *render_pass, 23 | const Camera &camera, const AABB &aabb, 24 | const Float4 &color = 0x00BFFFff_rgbaf); 25 | 26 | void renderOBB(CommandBuffer &cmdBuf, SDL_GPURenderPass *render_pass, 27 | const Camera &camera, const OBB &obb, 28 | const Float4 &color = 0x00BFFFff_rgbaf); 29 | 30 | void renderFrustum(CommandBuffer &cmdBuf, SDL_GPURenderPass *render_pass, 31 | const Mat4f &invProj, const Mat4f &mvp, 32 | const Float3 eyePos, const Float4 &color); 33 | 34 | } // namespace frustum_debug 35 | 36 | struct DebugFrustumComponent { 37 | Camera const *otherCam; 38 | GpuVec4 color; 39 | }; 40 | 41 | struct DebugBoundsComponent { 42 | std::variant bounds; 43 | GpuVec4 color = 0xA03232FF_rgbaf; 44 | }; 45 | 46 | class FrustumBoundsDebugSystem final { 47 | const RenderContext &renderer; 48 | const Device &device; 49 | SDL_GPUGraphicsPipeline *pipeline; 50 | entt::registry &_registry; 51 | 52 | public: 53 | FrustumBoundsDebugSystem(entt::registry ®istry, 54 | const RenderContext &renderer); 55 | 56 | entt::registry ®istry() { return _registry; } 57 | const entt::registry ®istry() const { return _registry; } 58 | 59 | std::tuple 60 | addFrustum(const Camera &otherCam, Float4 color = 0x00BFFFff_rgbaf) { 61 | auto entity = registry().create(); 62 | auto &item = 63 | registry().emplace(entity, &otherCam, color); 64 | return {entity, item}; 65 | } 66 | 67 | template 68 | std::tuple 69 | addBounds(const BoundsType &bounds) { 70 | auto entity = registry().create(); 71 | auto &item = registry().emplace(entity, bounds); 72 | return {entity, item}; 73 | } 74 | 75 | void render(CommandBuffer &cmdBuf, const Camera &camera); 76 | 77 | void release() noexcept { 78 | if (pipeline) 79 | SDL_ReleaseGPUGraphicsPipeline(device, pipeline); 80 | } 81 | 82 | ~FrustumBoundsDebugSystem() { release(); } 83 | }; 84 | 85 | } // namespace candlewick 86 | -------------------------------------------------------------------------------- /src/candlewick/core/errors.cpp: -------------------------------------------------------------------------------- 1 | #include "errors.h" 2 | #include 3 | 4 | namespace candlewick { 5 | RAIIException::RAIIException(std::string_view msg, 6 | std::source_location location) 7 | : std::runtime_error( 8 | std::format("{:s}({:d}) RAIIException: SDL error \'{}\'", 9 | location.file_name(), location.line(), msg.data())) {} 10 | 11 | namespace detail { 12 | std::string _error_message_impl(std::string_view fname, 13 | std::string_view fmtstr, 14 | std::format_args args) { 15 | return std::format("{:s} :: {:s}", fname, std::vformat(fmtstr, args)); 16 | } 17 | } // namespace detail 18 | } // namespace candlewick 19 | -------------------------------------------------------------------------------- /src/candlewick/core/errors.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #if (__cpp_lib_unreachable >= 202202L) 4 | #include 5 | #endif 6 | 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | 15 | namespace candlewick { 16 | 17 | [[noreturn]] inline void unreachable() { 18 | #if (__cpp_lib_unreachable >= 202202L) 19 | std::unreachable(); 20 | #elif defined(_MSC_VER) 21 | __assume(false); 22 | #else 23 | __builtin_unreachable(); 24 | #endif 25 | } 26 | 27 | /// \brief Wrapper for std::runtime_error, which prints out the filename and 28 | /// code line. 29 | struct RAIIException : std::runtime_error { 30 | RAIIException(std::string_view msg, std::source_location location = 31 | std::source_location::current()); 32 | }; 33 | 34 | namespace detail { 35 | 36 | std::string _error_message_impl(std::string_view fname, 37 | std::string_view fmtstr, 38 | std::format_args args); 39 | 40 | template 41 | std::string error_message_format(std::string_view fname, 42 | std::string_view _fmtstr, Ts &&...args) { 43 | return _error_message_impl(fname.data(), _fmtstr, 44 | std::make_format_args(args...)); 45 | } 46 | 47 | } // namespace detail 48 | 49 | template 50 | [[noreturn]] 51 | void terminate_with_message(std::source_location location, std::string_view fmt, 52 | Ts &&...args) { 53 | throw std::runtime_error(detail::error_message_format( 54 | location.function_name(), fmt, std::forward(args)...)); 55 | } 56 | 57 | template 58 | [[noreturn]] 59 | void terminate_with_message(std::string_view fmt, Ts &&...args) { 60 | terminate_with_message(std::source_location::current(), fmt, 61 | std::forward(args)...); 62 | } 63 | 64 | [[noreturn]] 65 | inline void unreachable_with_message( 66 | std::string_view msg, 67 | std::source_location location = std::source_location::current()) { 68 | SDL_LogError( 69 | SDL_LOG_CATEGORY_APPLICATION, "%s", 70 | detail::error_message_format(location.function_name(), "{:s}", msg) 71 | .c_str()); 72 | ::candlewick::unreachable(); 73 | } 74 | 75 | } // namespace candlewick 76 | -------------------------------------------------------------------------------- /src/candlewick/core/math_util.cpp: -------------------------------------------------------------------------------- 1 | #include "math_types.h" 2 | 3 | namespace candlewick { 4 | 5 | template struct Rad; 6 | template struct Deg; 7 | 8 | Vec3u8 hexToRgbi(unsigned long hex) { 9 | unsigned rem = unsigned(hex); // red = quotient of hex by 256^2 10 | unsigned r = rem >> 16; // remainder 11 | rem %= 1 << 16; 12 | unsigned g = rem >> 8; // green = quotient of rem by 256 13 | rem %= 1 << 8; 14 | unsigned b = rem; // blue = remainder 15 | return {Uint8(r), Uint8(g), Uint8(b)}; 16 | }; 17 | 18 | Vec4u8 hexToRgbai(unsigned long hex) { 19 | unsigned rem = unsigned(hex); 20 | unsigned r = rem >> 24; // red = quotient of hex by 256^3 = 2^24 21 | rem %= 1 << 24; 22 | unsigned g = rem >> 16; // green = quotient by 256^2 = 2^16 23 | rem %= 1 << 16; 24 | unsigned b = rem >> 8; // blue = quotient 25 | rem %= 1 << 8; 26 | unsigned a = rem; // alpha = remainder 27 | return {Uint8(r), Uint8(g), Uint8(b), Uint8(a)}; 28 | }; 29 | 30 | } // namespace candlewick 31 | -------------------------------------------------------------------------------- /src/candlewick/multibody/Components.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | namespace candlewick::multibody { 6 | namespace pin = pinocchio; 7 | 8 | struct PinGeomObjComponent { 9 | pin::GeomIndex geom_index; 10 | operator auto() const { return geom_index; } 11 | }; 12 | 13 | struct PinFrameComponent { 14 | pin::FrameIndex frame_id; 15 | operator auto() const { return frame_id; } 16 | }; 17 | 18 | /// Similar to PinFrameComponent, but tags the entity to render the frame 19 | /// velocity - not its placement as e.g. a triad. 20 | struct PinFrameVelocityComponent { 21 | pin::FrameIndex frame_id; 22 | operator auto() const { return frame_id; } 23 | }; 24 | 25 | } // namespace candlewick::multibody 26 | -------------------------------------------------------------------------------- /src/candlewick/multibody/LoadCoalGeometries.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "../utils/Utils.h" 4 | 5 | #include 6 | 7 | namespace coal { 8 | template class HeightField; 9 | class OBBRSS; 10 | class ShapeBase; 11 | class ConvexBase; 12 | } // namespace coal 13 | 14 | namespace candlewick { 15 | 16 | template 17 | decltype(auto) castCoalGeom(const coal::CollisionGeometry &geometry) { 18 | #ifndef DEBUG 19 | return static_cast(geometry); 20 | #else 21 | return dynamic_cast(geometry); 22 | #endif 23 | } 24 | 25 | /// \brief Load primitive given a coal::CollisionGeometry. 26 | /// 27 | /// See the documentation on the available primitives. 28 | /// \sa primitives1 29 | MeshData loadCoalPrimitive(const coal::ShapeBase &geometry); 30 | 31 | MeshData loadCoalConvex(const coal::ConvexBase &geom); 32 | 33 | MeshData loadCoalHeightField(const coal::HeightField &collGeom); 34 | 35 | MeshData loadCoalHeightField(const coal::HeightField &collGeom); 36 | 37 | } // namespace candlewick 38 | -------------------------------------------------------------------------------- /src/candlewick/multibody/LoadCoalPrimitives.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "candlewick/deprecated.h" 4 | #include "candlewick/multibody/LoadCoalGeometries.h" 5 | 6 | CANDLEWICK_DEPRECATED_HEADER( 7 | "This header is deprecated. Use " 8 | " instead.") 9 | -------------------------------------------------------------------------------- /src/candlewick/multibody/LoadPinocchioGeometry.cpp: -------------------------------------------------------------------------------- 1 | #include "LoadPinocchioGeometry.h" 2 | #include "LoadCoalGeometries.h" 3 | #include "../core/errors.h" 4 | #include "../utils/LoadMesh.h" 5 | #include "../utils/MeshTransforms.h" 6 | 7 | #include 8 | #include 9 | 10 | namespace candlewick::multibody { 11 | 12 | void loadGeometryObject(const pin::GeometryObject &gobj, 13 | std::vector &meshData) { 14 | using namespace coal; 15 | 16 | const CollisionGeometry &collgom = *gobj.geometry.get(); 17 | const OBJECT_TYPE objType = collgom.getObjectType(); 18 | 19 | Float4 meshColor = gobj.meshColor.cast(); 20 | const char *meshPath = gobj.meshPath.c_str(); 21 | 22 | switch (objType) { 23 | case OT_BVH: { 24 | loadSceneMeshes(meshPath, meshData); 25 | break; 26 | } 27 | case OT_GEOM: { 28 | const ShapeBase &shape = castCoalGeom(collgom); 29 | meshData.emplace_back(loadCoalPrimitive(shape)); 30 | break; 31 | } 32 | case OT_HFIELD: { 33 | MeshData md{NoInit}; 34 | switch (collgom.getNodeType()) { 35 | case HF_AABB: { 36 | md = loadCoalHeightField(castCoalGeom>(collgom)); 37 | break; 38 | } 39 | case HF_OBBRSS: { 40 | md = loadCoalHeightField(castCoalGeom>(collgom)); 41 | break; 42 | } 43 | default: 44 | terminate_with_message("Geometry must be a heightfield!"); 45 | } 46 | meshData.push_back(std::move(md)); 47 | break; 48 | } 49 | default: 50 | terminate_with_message("Unsupported object type."); 51 | break; 52 | } 53 | for (auto &data : meshData) { 54 | if (gobj.overrideMaterial) 55 | data.material.baseColor = meshColor; 56 | } 57 | } 58 | 59 | } // namespace candlewick::multibody 60 | -------------------------------------------------------------------------------- /src/candlewick/multibody/LoadPinocchioGeometry.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "Multibody.h" 4 | #include "../utils/MeshData.h" 5 | 6 | #include 7 | 8 | namespace candlewick::multibody { 9 | 10 | /// \brief Load an invidual Pinocchio GeometryObject's component geometries into 11 | /// an array of \c MeshData. 12 | void loadGeometryObject(const pin::GeometryObject &gobj, 13 | std::vector &meshData); 14 | 15 | inline std::vector 16 | loadGeometryObject(const pin::GeometryObject &gobj) { 17 | std::vector meshData; 18 | loadGeometryObject(gobj, meshData); 19 | return meshData; 20 | } 21 | 22 | } // namespace candlewick::multibody 23 | -------------------------------------------------------------------------------- /src/candlewick/multibody/Multibody.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | 6 | namespace candlewick { 7 | /// \brief Support for the Pinocchio rigid-body algorithms library and the Coal 8 | /// collision detection library. 9 | /// \details Loaders for assets defined through Pinocchio, Coal collision 10 | /// geometries, classes for creating a \ref Scene for robots from Pinocchio 11 | /// models, etc. 12 | namespace multibody { 13 | namespace pin = pinocchio; 14 | struct RobotDebugSystem; 15 | class RobotScene; 16 | class Visualizer; 17 | 18 | using SE3f = pin::SE3Tpl; 19 | using Motionf = pin::MotionTpl; 20 | using Inertiaf = pin::InertiaTpl; 21 | using Forcef = pin::ForceTpl; 22 | 23 | /// \ingroup gui_util 24 | /// \brief Display Pinocchio model and geometry model info in ImGui. 25 | /// \image html robot-info-panel.png "Robot information panel." 26 | void guiAddPinocchioModelInfo(entt::registry ®, const pin::Model &model, 27 | const pin::GeometryModel &geom_model, 28 | int table_height_lines = 6); 29 | } // namespace multibody 30 | } // namespace candlewick 31 | -------------------------------------------------------------------------------- /src/candlewick/multibody/RobotDebug.cpp: -------------------------------------------------------------------------------- 1 | #include "RobotDebug.h" 2 | 3 | #include "../core/Components.h" 4 | #include "../primitives/Arrow.h" 5 | 6 | #include 7 | 8 | namespace candlewick::multibody { 9 | entt::entity RobotDebugSystem::addFrameTriad(DebugScene &scene, 10 | pin::FrameIndex frame_id, 11 | const Float3 &scale) { 12 | entt::registry ® = scene.registry(); 13 | auto [ent, triad] = scene.addTriad(); 14 | triad.scale = scale; 15 | reg.emplace(ent, frame_id); 16 | return ent; 17 | } 18 | 19 | entt::entity RobotDebugSystem::addFrameVelocityArrow(DebugScene &scene, 20 | pin::FrameIndex frame_id) { 21 | entt::registry ® = scene.registry(); 22 | MeshData arrow_data = loadArrowSolid(false); 23 | Mesh mesh = createMesh(scene.device(), arrow_data, true); 24 | GpuVec4 color = 0xFF217Eff_rgbaf; 25 | 26 | auto entity = reg.create(); 27 | reg.emplace(entity, DebugPipelines::TRIANGLE_FILL, 28 | std::move(mesh), std::vector{color}); 29 | reg.emplace(entity, frame_id); 30 | reg.emplace(entity, Mat4f::Identity()); 31 | return entity; 32 | } 33 | 34 | void RobotDebugSystem::updateFrames(entt::registry ®) { 35 | auto view = reg.view(); 37 | for (auto &&[ent, frame_id, dmc, tr] : view.each()) { 38 | Mat4f pose{m_robotData.oMf[frame_id].cast()}; 39 | auto D = dmc.scale.homogeneous().asDiagonal(); 40 | tr.noalias() = pose * D; 41 | } 42 | } 43 | 44 | void RobotDebugSystem::updateFrameVelocities(entt::registry ®) { 45 | constexpr float vel_scale = 0.5f; 46 | 47 | auto view = reg.view(); 49 | for (auto &&[ent, fvc, dmc, tr] : view.each()) { 50 | Motionf vel = 51 | pin::getFrameVelocity(m_robotModel, m_robotData, fvc, pin::LOCAL) 52 | .cast(); 53 | 54 | const SE3f pose = m_robotData.oMf[fvc].cast(); 55 | Eigen::Quaternionf quatf; 56 | tr = pose.toHomogeneousMatrix(); 57 | auto v = vel.linear(); 58 | Eigen::DiagonalMatrix scaleMatrix(0.3f, 0.3f, 59 | vel_scale * v.norm()); 60 | 61 | // the arrow mesh is posed z-up by default. 62 | // we need to rotate towards where the velocity is pointing, 63 | // then transform to the frame space. 64 | quatf.setFromTwoVectors(Float3::UnitZ(), v); 65 | Mat3f R2 = quatf.toRotationMatrix() * scaleMatrix; 66 | auto R = tr.topLeftCorner<3, 3>(); 67 | R.applyOnTheRight(R2); 68 | } 69 | } 70 | 71 | } // namespace candlewick::multibody 72 | -------------------------------------------------------------------------------- /src/candlewick/multibody/RobotDebug.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "Multibody.h" 4 | #include "Components.h" 5 | #include "../core/DebugScene.h" 6 | 7 | namespace candlewick::multibody { 8 | 9 | /// \brief A debug system for use with Pinocchio geometries. 10 | /// 11 | /// Supports drawing a triad for a frame. Member \ref pinData must 12 | /// refer to an existing pinocchio::Data object at all times. 13 | struct RobotDebugSystem final : IDebugSubSystem { 14 | inline static const Float3 DEFAULT_TRIAD_SCALE = Float3::Constant(0.3333f); 15 | 16 | RobotDebugSystem(const pin::Model &model, const pin::Data &data) 17 | : IDebugSubSystem(), m_robotModel(model), m_robotData(data) {} 18 | 19 | entt::entity addFrameTriad(DebugScene &scene, pin::FrameIndex frame_id, 20 | const Float3 &scale = DEFAULT_TRIAD_SCALE); 21 | 22 | entt::entity addFrameVelocityArrow(DebugScene &scene, 23 | pin::FrameIndex frame_id); 24 | 25 | /// \brief Update the visualization of the frame placements and their 26 | /// velocities. 27 | /// 28 | /// \warning We expect pinocchio::updateFramePlacements() or 29 | /// pinocchio::framesForwardKinematics() to be called first! 30 | void update(DebugScene &scene) { 31 | updateFrames(scene.registry()); 32 | updateFrameVelocities(scene.registry()); 33 | } 34 | 35 | private: 36 | void updateFrames(entt::registry ®); 37 | void updateFrameVelocities(entt::registry ®); 38 | 39 | const pin::Model &m_robotModel; 40 | const pin::Data &m_robotData; 41 | }; 42 | 43 | } // namespace candlewick::multibody 44 | -------------------------------------------------------------------------------- /src/candlewick/posteffects/SSAO.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "../core/Core.h" 4 | #include "../core/Texture.h" 5 | #include 6 | 7 | namespace candlewick { 8 | namespace ssao { 9 | struct SsaoPass { 10 | SDL_GPUDevice *_device = nullptr; 11 | SDL_GPUTexture *inDepthMap = nullptr; 12 | SDL_GPUTexture *inNormalMap = nullptr; 13 | SDL_GPUSampler *texSampler = nullptr; 14 | SDL_GPUGraphicsPipeline *pipeline = nullptr; 15 | Texture ssaoMap{NoInit}; 16 | struct SsaoNoise { 17 | Texture tex{NoInit}; 18 | SDL_GPUSampler *sampler = nullptr; 19 | // The texture will be N x N where N is this value. 20 | Uint32 pixel_window_size; 21 | } ssaoNoise; 22 | SDL_GPUGraphicsPipeline *blurPipeline = nullptr; 23 | // first blur pass target 24 | Texture blurPass1Tex{NoInit}; 25 | 26 | SsaoPass(NoInitT) {} 27 | SsaoPass(const RenderContext &renderer, SDL_GPUTexture *normalMap); 28 | 29 | SsaoPass(SsaoPass &&other) noexcept; 30 | SsaoPass &operator=(SsaoPass &&other) noexcept; 31 | 32 | void render(CommandBuffer &cmdBuf, const Camera &camera); 33 | 34 | // cleanup function 35 | void release() noexcept; 36 | 37 | ~SsaoPass() noexcept { this->release(); } 38 | }; 39 | 40 | } // namespace ssao 41 | } // namespace candlewick 42 | -------------------------------------------------------------------------------- /src/candlewick/posteffects/ScreenSpaceShadows.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "../core/Core.h" 4 | #include "../core/Tags.h" 5 | #include "../core/LightUniforms.h" 6 | 7 | #include 8 | #include 9 | 10 | namespace candlewick { 11 | namespace effects { 12 | 13 | /// \brief WIP screen space shadows 14 | struct ScreenSpaceShadowPass { 15 | struct Config { 16 | float maxDist = 1.0f; 17 | int numSteps = 16; 18 | } config; 19 | SDL_GPUTexture *depthTexture = nullptr; 20 | /// Sampler for the depth texture (e.g. from the prepass) 21 | SDL_GPUSampler *depthSampler = nullptr; 22 | /// Target texture to render the shadow to. 23 | SDL_GPUTexture *targetTexture = nullptr; 24 | /// Render pipeline 25 | SDL_GPUGraphicsPipeline *pipeline = nullptr; 26 | 27 | bool valid() const { 28 | return depthTexture && depthSampler && targetTexture && pipeline; 29 | } 30 | 31 | ScreenSpaceShadowPass(NoInitT) {} 32 | ScreenSpaceShadowPass(const RenderContext &renderer, const Config &config); 33 | 34 | void release(SDL_GPUDevice *device) noexcept; 35 | 36 | void render(CommandBuffer &cmdBuf, const Camera &camera, 37 | const DirectionalLight &light, 38 | std::span castables); 39 | }; 40 | 41 | } // namespace effects 42 | } // namespace candlewick 43 | -------------------------------------------------------------------------------- /src/candlewick/primitives/Arrow.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "../utils/MeshData.h" 4 | 5 | namespace candlewick { 6 | 7 | /// \brief Load a solid 3D arrow. 8 | /// \ingroup primitives1 9 | MeshData loadArrowSolid(bool include_normals = false, float shaft_length = 0.4f, 10 | float shaft_radius = 0.01f, float head_length = 0.1f, 11 | float head_radius = 0.02f, Uint32 segments = 32); 12 | 13 | /// \brief Create a 3D triad 14 | /// \ingroup primitives1 15 | std::array loadTriadSolid(float shaft_length = 0.4f, 16 | float shaft_radius = 0.01f, 17 | float head_length = 0.1f, 18 | float head_radius = 0.02f, 19 | Uint32 segments = 32); 20 | 21 | } // namespace candlewick 22 | -------------------------------------------------------------------------------- /src/candlewick/primitives/Capsule.cpp: -------------------------------------------------------------------------------- 1 | #include "Capsule.h" 2 | #include "Internal.h" 3 | #include "../core/DefaultVertex.h" 4 | 5 | namespace candlewick { 6 | 7 | MeshData loadCapsuleSolid(Uint32 hemisphereRings, Uint32 segments, 8 | float length) { 9 | using constants::Pi_2f; 10 | using constants::Pif; 11 | detail::ConeCylinderBuilder builder; 12 | hemisphereRings = std::max(2u, hemisphereRings); 13 | segments = std::max(3u, segments); 14 | assert(length >= 0.0f); 15 | 16 | const float halfLength = 0.5f * length; 17 | 18 | const Radf ringIncrement{Pif / float(hemisphereRings)}; 19 | 20 | // Bottom cap 21 | builder.add({0.f, 0.f, -1.f - halfLength}, {0.f, 0.f, -1.f}); 22 | for (Uint32 j = 0; j < segments; j++) { 23 | builder.addFace({ 24 | 0u, 25 | (j != segments - 1) ? j + 2 : 1, 26 | j + 1, 27 | }); 28 | } 29 | 30 | // Bottom hemisphere 31 | const Radf ringStart = Radf(ringIncrement - Pi_2f); 32 | builder.addHemisphereVertices(hemisphereRings - 1u, segments, 33 | -halfLength, // start from -l/2 34 | ringStart, // Start from equator 35 | ringIncrement, // Move downwards 36 | 1u // Start from beginning 37 | ); 38 | 39 | // Cylinder middle section 40 | // Connects with hemispheres at their edges 41 | Float2 basePoint{1.f, -halfLength}; // start circle in XY plane 42 | Float2 upDir{0.0f, length}; // up direction for circle 43 | 44 | builder.addCylinderFloors(2u, segments, basePoint, upDir, 45 | builder.currentVertices()); 46 | 47 | // Top hemisphere 48 | builder.addHemisphereVertices(hemisphereRings - 1u, segments, 49 | halfLength, // start at l/2 50 | Radf(0.f), // Start from equator 51 | ringIncrement, // Move upwards 52 | builder.currentVertices() // continue on 53 | ); 54 | 55 | // Top cap 56 | builder.add({0.f, 0.f, 1.f + halfLength}, {0.f, 0.f, 1.f}); 57 | const Uint32 currentCount = builder.currentVertices(); 58 | const Uint32 startIdxTop = currentCount - segments - 1u; 59 | for (Uint32 i = 0; i < segments; i++) { 60 | const Uint32 j = startIdxTop + i; 61 | builder.addFace({ 62 | j, 63 | (i != segments - 1) ? j + 1 : startIdxTop, 64 | currentCount - 1u, 65 | }); 66 | } 67 | 68 | std::vector vertices; 69 | vertices.reserve(builder.currentVertices()); 70 | for (size_t i = 0; i < builder.currentVertices(); i++) { 71 | DefaultVertex v; 72 | v.pos = builder.positions[i]; 73 | v.normal = builder.normals[i]; 74 | vertices.push_back(v); 75 | } 76 | return MeshData{SDL_GPU_PRIMITIVETYPE_TRIANGLELIST, std::move(vertices), 77 | std::move(builder.indices)}; 78 | } 79 | 80 | } // namespace candlewick 81 | -------------------------------------------------------------------------------- /src/candlewick/primitives/Capsule.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "../utils/MeshData.h" 4 | 5 | namespace candlewick { 6 | 7 | /// \brief Load a capsule primitive. 8 | /// 9 | /// This will have unit radius. You can apply a scaling transform to change the 10 | /// radius to what you want. See apply3DTransformInPlace(). 11 | /// \ingroup primitives1 12 | MeshData loadCapsuleSolid(Uint32 hemisphereRings, Uint32 segments, 13 | float length); 14 | 15 | } // namespace candlewick 16 | -------------------------------------------------------------------------------- /src/candlewick/primitives/Cone.cpp: -------------------------------------------------------------------------------- 1 | #include "Cone.h" 2 | #include "Internal.h" 3 | #include "../core/DefaultVertex.h" 4 | 5 | namespace candlewick { 6 | 7 | MeshData loadConeSolid(Uint32 segments, float radius, float length) { 8 | detail::ConeCylinderBuilder builder; 9 | const float halfLength = 0.5f * length; 10 | builder.addCone(segments, radius, -halfLength, length); 11 | 12 | std::vector vertices; 13 | vertices.resize(builder.currentVertices()); 14 | for (std::size_t i = 0; i < builder.currentVertices(); i++) { 15 | vertices[i] = {builder.positions[i], builder.normals[i]}; 16 | } 17 | return MeshData{SDL_GPU_PRIMITIVETYPE_TRIANGLELIST, vertices, 18 | builder.indices}; 19 | } 20 | 21 | } // namespace candlewick 22 | -------------------------------------------------------------------------------- /src/candlewick/primitives/Cone.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "../utils/MeshData.h" 4 | 5 | namespace candlewick { 6 | 7 | /// \brief Load a 3D solid cone. 8 | /// \ingroup primitives1 9 | MeshData loadConeSolid(Uint32 segments, float radius, float length); 10 | 11 | } // namespace candlewick 12 | -------------------------------------------------------------------------------- /src/candlewick/primitives/Cube.cpp: -------------------------------------------------------------------------------- 1 | #include "Cube.h" 2 | #include "../core/DefaultVertex.h" 3 | #include 4 | 5 | namespace candlewick { 6 | constexpr Uint32 indexData[]{ 7 | 0, 1, 2, 0, 2, 3, /* +Z */ 8 | 4, 5, 6, 4, 6, 7, /* +X */ 9 | 8, 9, 10, 8, 10, 11, /* +Y */ 10 | 12, 13, 14, 12, 14, 15, /* -Z */ 11 | 16, 17, 18, 16, 18, 19, /* -Y */ 12 | 20, 21, 22, 20, 22, 23 /* -X */ 13 | }; 14 | 15 | const DefaultVertex vertexData[]{ 16 | {{-1.0f, -1.0f, 1.0f}, {0.0f, 0.0f, 1.0f}}, 17 | {{1.0f, -1.0f, 1.0f}, {0.0f, 0.0f, 1.0f}}, 18 | {{1.0f, 1.0f, 1.0f}, {0.0f, 0.0f, 1.0f}}, /* +Z */ 19 | {{-1.0f, 1.0f, 1.0f}, {0.0f, 0.0f, 1.0f}}, 20 | 21 | {{1.0f, -1.0f, 1.0f}, {1.0f, 0.0f, 0.0f}}, 22 | {{1.0f, -1.0f, -1.0f}, {1.0f, 0.0f, 0.0f}}, 23 | {{1.0f, 1.0f, -1.0f}, {1.0f, 0.0f, 0.0f}}, /* +X */ 24 | {{1.0f, 1.0f, 1.0f}, {1.0f, 0.0f, 0.0f}}, 25 | 26 | {{-1.0f, 1.0f, 1.0f}, {0.0f, 1.0f, 0.0f}}, 27 | {{1.0f, 1.0f, 1.0f}, {0.0f, 1.0f, 0.0f}}, 28 | {{1.0f, 1.0f, -1.0f}, {0.0f, 1.0f, 0.0f}}, /* +Y */ 29 | {{-1.0f, 1.0f, -1.0f}, {0.0f, 1.0f, 0.0f}}, 30 | 31 | {{1.0f, -1.0f, -1.0f}, {0.0f, 0.0f, -1.0f}}, 32 | {{-1.0f, -1.0f, -1.0f}, {0.0f, 0.0f, -1.0f}}, 33 | {{-1.0f, 1.0f, -1.0f}, {0.0f, 0.0f, -1.0f}}, /* -Z */ 34 | {{1.0f, 1.0f, -1.0f}, {0.0f, 0.0f, -1.0f}}, 35 | 36 | {{-1.0f, -1.0f, -1.0f}, {0.0f, -1.0f, 0.0f}}, 37 | {{1.0f, -1.0f, -1.0f}, {0.0f, -1.0f, 0.0f}}, 38 | {{1.0f, -1.0f, 1.0f}, {0.0f, -1.0f, 0.0f}}, /* -Y */ 39 | {{-1.0f, -1.0f, 1.0f}, {0.0f, -1.0f, 0.0f}}, 40 | 41 | {{-1.0f, -1.0f, -1.0f}, {-1.0f, 0.0f, 0.0f}}, 42 | {{-1.0f, -1.0f, 1.0f}, {-1.0f, 0.0f, 0.0f}}, 43 | {{-1.0f, 1.0f, 1.0f}, {-1.0f, 0.0f, 0.0f}}, /* -X */ 44 | {{-1.0f, 1.0f, -1.0f}, {-1.0f, 0.0f, 0.0f}}}; 45 | 46 | MeshDataView loadCubeSolid() { 47 | return {SDL_GPU_PRIMITIVETYPE_TRIANGLELIST, vertexData, indexData}; 48 | } 49 | 50 | } // namespace candlewick 51 | -------------------------------------------------------------------------------- /src/candlewick/primitives/Cube.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "../utils/MeshDataView.h" 4 | 5 | namespace candlewick { 6 | 7 | /// \brief Load MeshDataView for a cube. 8 | /// \ingroup primitives1 9 | MeshDataView loadCubeSolid(); 10 | 11 | } // namespace candlewick 12 | -------------------------------------------------------------------------------- /src/candlewick/primitives/Cylinder.cpp: -------------------------------------------------------------------------------- 1 | #include "Cylinder.h" 2 | #include "Internal.h" 3 | #include "../core/DefaultVertex.h" 4 | 5 | namespace candlewick { 6 | 7 | MeshData loadCylinderSolid(Uint32 rings, Uint32 segments, float radius, 8 | float height) { 9 | detail::ConeCylinderBuilder builder; 10 | const float halfLength = 0.5f * height; 11 | const float z = -halfLength; 12 | 13 | builder.addBottomDisk(segments, radius, z); 14 | assert(rings >= 1); 15 | const Float2 upDir{0., height}; 16 | builder.addCylinderFloors(rings, segments, {radius, z}, upDir, 1); 17 | 18 | builder.addTopDisk(segments, radius, halfLength); 19 | 20 | std::vector vertices; 21 | vertices.resize(builder.currentVertices()); 22 | for (std::size_t i = 0; i < builder.currentVertices(); i++) { 23 | vertices[i] = {builder.positions[i], builder.normals[i]}; 24 | } 25 | return MeshData{ 26 | SDL_GPU_PRIMITIVETYPE_TRIANGLELIST, 27 | std::move(vertices), 28 | std::move(builder.indices), 29 | }; 30 | } 31 | 32 | } // namespace candlewick 33 | -------------------------------------------------------------------------------- /src/candlewick/primitives/Cylinder.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "../utils/MeshData.h" 4 | 5 | namespace candlewick { 6 | 7 | /// \brief Load a solid 3D cylinder. 8 | /// \ingroup primitives1 9 | MeshData loadCylinderSolid(Uint32 rings, Uint32 segments, float radius, 10 | float height); 11 | 12 | } // namespace candlewick 13 | -------------------------------------------------------------------------------- /src/candlewick/primitives/Grid.cpp: -------------------------------------------------------------------------------- 1 | #include "Grid.h" 2 | 3 | #include "Internal.h" 4 | #include 5 | 6 | namespace candlewick { 7 | 8 | MeshData loadGrid(Uint32 xyHalfSize, float scale) { 9 | const Uint32 size = std::max(2 * xyHalfSize, 1u) - 1; 10 | std::vector vertexData; 11 | std::vector indexData; 12 | 13 | vertexData.reserve(size * size); 14 | indexData.resize(4 * size * (size - 1)); 15 | const Float3 center{float(xyHalfSize) * scale, float(xyHalfSize) * scale, 16 | 0.f}; 17 | 18 | size_t idx = 0; 19 | Uint32 i, j; 20 | // y-direction 21 | for (j = 0; j < size; j++) { 22 | // x-direction 23 | for (i = 0; i < size; i++) { 24 | Float3 pos{float(i) * scale, float(j) * scale, 0.f}; 25 | pos -= center; 26 | vertexData.emplace_back(pos); 27 | if (i != size - 1) { 28 | // (i,j) -- (i+1,j) 29 | indexData[idx++] = Uint32(j * size + i); 30 | indexData[idx++] = Uint32(j * size + i + 1); 31 | } 32 | if (j != size - 1) { 33 | // (i,j) 34 | // | 35 | // (i,j+1) 36 | indexData[idx++] = Uint32(j * size + i); 37 | indexData[idx++] = Uint32((j + 1) * size + i); 38 | } 39 | } 40 | } 41 | return MeshData{SDL_GPU_PRIMITIVETYPE_LINELIST, std::move(vertexData), 42 | std::move(indexData)}; 43 | } 44 | 45 | } // namespace candlewick 46 | -------------------------------------------------------------------------------- /src/candlewick/primitives/Grid.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "../utils/MeshData.h" 4 | 5 | namespace candlewick { 6 | 7 | /// \brief Load a line grid. 8 | /// \ingroup primitives1 9 | MeshData loadGrid(Uint32 xyHalfSize, float scale = 0.5f); 10 | 11 | } // namespace candlewick 12 | -------------------------------------------------------------------------------- /src/candlewick/primitives/Heightfield.cpp: -------------------------------------------------------------------------------- 1 | #include "Heightfield.h" 2 | 3 | #include "Internal.h" 4 | 5 | namespace candlewick { 6 | 7 | MeshData loadHeightfield(const Eigen::Ref &heights, 8 | const Eigen::Ref &xgrid, 9 | const Eigen::Ref &ygrid) { 10 | SDL_assert(heights.rows() == xgrid.size()); 11 | SDL_assert(heights.cols() == ygrid.size()); 12 | const auto nx = (Sint32)heights.rows(); 13 | const auto ny = (Sint32)heights.cols(); 14 | SDL_assert(nx > 0); 15 | SDL_assert(ny > 0); 16 | Uint32 vertexCount = Uint32(nx * ny); 17 | std::vector vertexData; 18 | std::vector indexData; 19 | 20 | vertexData.reserve(vertexCount); 21 | indexData.resize(Uint32(4 * nx * (ny - 1))); 22 | size_t idx = 0; 23 | Sint32 ih, jh; 24 | // y-dir 25 | for (jh = 0; jh < ny; jh++) { 26 | // x-dir 27 | for (ih = 0; ih < nx; ih++) { 28 | Float3 pos{xgrid[ih], ygrid[jh], heights(ih, jh)}; 29 | vertexData.emplace_back(pos); 30 | if (ih != nx - 1) { 31 | // connect x to x+dx 32 | indexData[idx++] = Uint32(jh * nx + ih); 33 | indexData[idx++] = Uint32(jh * nx + ih + 1); 34 | } 35 | if (jh != ny - 1) { 36 | // connect y to y+dy 37 | indexData[idx++] = Uint32(jh * nx + ih); 38 | indexData[idx++] = Uint32((jh + 1) * nx + ih); 39 | } 40 | } 41 | } 42 | assert(idx == indexData.size()); 43 | 44 | return MeshData{SDL_GPU_PRIMITIVETYPE_LINELIST, std::move(vertexData), 45 | std::move(indexData)}; 46 | } 47 | 48 | } // namespace candlewick 49 | -------------------------------------------------------------------------------- /src/candlewick/primitives/Heightfield.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "../utils/MeshData.h" 4 | 5 | namespace candlewick { 6 | 7 | /// \brief Load a heightfield, as line geometry. 8 | /// \ingroup primitives1 9 | MeshData loadHeightfield(const Eigen::Ref &heights, 10 | const Eigen::Ref &xgrid, 11 | const Eigen::Ref &ygrid); 12 | } // namespace candlewick 13 | -------------------------------------------------------------------------------- /src/candlewick/primitives/Internal.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include "../core/math_types.h" 3 | #include "../core/MeshLayout.h" 4 | #include "../utils/MeshData.h" 5 | 6 | namespace candlewick { 7 | 8 | struct alignas(16) PosOnlyVertex { 9 | GpuVec3 pos; 10 | }; 11 | 12 | template <> struct VertexTraits { 13 | static auto layout() { 14 | return MeshLayout{} 15 | .addBinding(0, sizeof(PosOnlyVertex)) 16 | .addAttribute(VertexAttrib::Position, 0, 17 | SDL_GPU_VERTEXELEMENTFORMAT_FLOAT3, 18 | offsetof(PosOnlyVertex, pos)); 19 | } 20 | }; 21 | 22 | struct alignas(16) PosNormalVertex { 23 | GpuVec3 pos; 24 | alignas(16) GpuVec3 normal; 25 | }; 26 | 27 | template <> struct VertexTraits { 28 | static auto layout() { 29 | return MeshLayout{} 30 | .addBinding(0, sizeof(PosNormalVertex)) 31 | .addAttribute(VertexAttrib::Position, 0, 32 | SDL_GPU_VERTEXELEMENTFORMAT_FLOAT3, 33 | offsetof(PosNormalVertex, pos)) 34 | .addAttribute(VertexAttrib::Normal, 0, 35 | SDL_GPU_VERTEXELEMENTFORMAT_FLOAT3, 36 | offsetof(PosNormalVertex, normal)); 37 | } 38 | }; 39 | 40 | namespace detail { 41 | 42 | struct ConeCylinderBuilder { 43 | Uint32 currentVertices() const { return Uint32(positions.size()); } 44 | void add(const Float3 &pos, const Float3 &normal); 45 | void addFace(const Uint32 (&face)[3]); 46 | Float3 previousPos(std::size_t offset) const; 47 | Float3 previousNormal(std::size_t offset) const; 48 | 49 | /// \todo generate multiple caps for the disk center, to get smooth normals 50 | void addBottomDisk(Uint32 segments, float radius, float z); 51 | /// \todo merge with previous somehow 52 | void addTopDisk(Uint32 segments, float radius, float z); 53 | void addCone(Uint32 segments, float radius, float zBottom, float length); 54 | void addCylinderFloors(Uint32 numFloors, Uint32 segments, Float2 basePoint, 55 | Float2 upDir, Uint32 startIdx); 56 | void addHemisphereVertices(Uint32 count, Uint32 segments, float zCenter, 57 | Radf ringStart, float ringIncrement, 58 | Uint32 startIdx); 59 | 60 | std::vector positions; 61 | std::vector normals; 62 | std::vector indices; 63 | }; 64 | 65 | inline void ConeCylinderBuilder::add(const Float3 &pos, 66 | const Float3 &normal) { 67 | positions.push_back(pos); 68 | normals.push_back(normal.normalized()); 69 | } 70 | 71 | inline void ConeCylinderBuilder::addFace(const Uint32 (&face)[3]) { 72 | indices.push_back(face[0]); 73 | indices.push_back(face[1]); 74 | indices.push_back(face[2]); 75 | } 76 | 77 | inline Float3 ConeCylinderBuilder::previousPos(std::size_t offset) const { 78 | return positions[positions.size() - offset]; 79 | } 80 | 81 | inline Float3 ConeCylinderBuilder::previousNormal(std::size_t offset) const { 82 | return normals[normals.size() - offset]; 83 | } 84 | 85 | } // namespace detail 86 | } // namespace candlewick 87 | -------------------------------------------------------------------------------- /src/candlewick/primitives/Plane.cpp: -------------------------------------------------------------------------------- 1 | #include "Plane.h" 2 | #include "../core/DefaultVertex.h" 3 | #include "../utils/MeshTransforms.h" 4 | 5 | namespace candlewick { 6 | 7 | // 3——1 8 | // │ /│ 9 | // │/ │ 10 | // 2——0 11 | const DefaultVertex vertexData[]{ 12 | {{+1.f, -1.f, 0.f}, {0.f, 0.f, 1.f}, Float4::Zero(), {+1.f, 0.f, 0.f}}, 13 | {{+1.f, +1.f, 0.f}, {0.f, 0.f, 1.f}, Float4::Zero(), {0.f, +1.f, 0.f}}, 14 | {{-1.f, -1.f, 0.f}, {0.f, 0.f, 1.f}, Float4::Zero(), {-1.f, 0.f, 0.f}}, 15 | {{-1.f, +1.f, 0.f}, {0.f, 0.f, 1.f}, Float4::Zero(), {0.f, +1.f, 0.f}}, 16 | }; 17 | 18 | constexpr Uint32 indexData[] = {0, 1, 2, // 19 | 2, 1, 3}; 20 | 21 | MeshDataView loadPlane() { 22 | return {SDL_GPU_PRIMITIVETYPE_TRIANGLELIST, vertexData, indexData}; 23 | } 24 | 25 | MeshData loadPlaneTiled(float scale, Uint32 xrepeat, Uint32 yrepeat, 26 | bool centered) { 27 | MeshData dataOwned = loadPlane().toOwned(); 28 | { 29 | // normalize to (-1,-1) -- (1,1) 30 | const Eigen::Translation3f tr{0.5, 0.5, 0.}; 31 | apply3DTransformInPlace(dataOwned, Eigen::Affine3f(tr).scale(0.5)); 32 | } 33 | std::vector meshes; 34 | meshes.reserve(xrepeat * yrepeat); 35 | for (Sint32 i = 0; i < Sint32(xrepeat); i++) { 36 | for (Sint32 j = 0; j < Sint32(yrepeat); j++) { 37 | MeshData &m = meshes.emplace_back(MeshData::copy(dataOwned)); 38 | const Eigen::Translation3f tr{float(i) * scale, float(j) * scale, 0.f}; 39 | apply3DTransformInPlace(m, Eigen::Affine3f(tr).scale(scale)); 40 | } 41 | } 42 | MeshData out = mergeMeshes(meshes); 43 | if (centered) { 44 | float xc = 0.5f * scale * float(xrepeat); 45 | float yc = 0.5f * scale * float(yrepeat); 46 | const Eigen::Translation3f center{-xc, -yc, 0.f}; 47 | apply3DTransformInPlace(out, Eigen::Affine3f{center}); 48 | } 49 | return out; 50 | } 51 | 52 | } // namespace candlewick 53 | -------------------------------------------------------------------------------- /src/candlewick/primitives/Plane.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "../utils/MeshDataView.h" 4 | 5 | namespace candlewick { 6 | 7 | /// \ingroup primitives1 8 | MeshDataView loadPlane(); 9 | 10 | /// \ingroup primitives1 11 | MeshData loadPlaneTiled(float scale, Uint32 xrepeat, Uint32 yrepeat, 12 | bool centered = true); 13 | 14 | } // namespace candlewick 15 | -------------------------------------------------------------------------------- /src/candlewick/primitives/Primitives.h: -------------------------------------------------------------------------------- 1 | // clang-format off 2 | /// \defgroup primitives1 Primitives 3 | /// \image html primitives.png "A scene containing some geometric primitives (cone, cylinder, capsule, box, and some arrows)." 4 | /// 5 | /// \{ 6 | /// \file Primitives.h 7 | /// \brief Omnibus header including all of the geometric primitives. 8 | /// \} 9 | /// 10 | // clang-format on 11 | #pragma once 12 | 13 | #include "Arrow.h" 14 | #include "Capsule.h" 15 | #include "Cone.h" 16 | #include "Cube.h" 17 | #include "Cylinder.h" 18 | #include "Grid.h" 19 | #include "Heightfield.h" 20 | #include "Plane.h" 21 | #include "Sphere.h" 22 | -------------------------------------------------------------------------------- /src/candlewick/primitives/Sphere.cpp: -------------------------------------------------------------------------------- 1 | #include "Sphere.h" 2 | #include "Internal.h" 3 | #include "../core/DefaultVertex.h" 4 | 5 | namespace candlewick { 6 | 7 | MeshData loadUvSphereSolid(Uint32 rings, Uint32 segments) { 8 | float ringIncrement = constants::Pif / float(rings); 9 | detail::ConeCylinderBuilder builder; 10 | builder.add({0., 0., -1.f}, {0., 0., -1.f}); 11 | // bottom ring 12 | for (Uint32 j = 0; j < segments; j++) { 13 | builder.addFace({0u, (j != segments - 1) ? j + 2 : 1, j + 1}); 14 | } 15 | 16 | Radf ringStart = Radf(ringIncrement - constants::Pi_2f); 17 | builder.addHemisphereVertices(rings - 1, segments, 0.0f, ringStart, 18 | ringIncrement, 1u); 19 | 20 | builder.add({0., 0., 1.f}, {0., 0., 1.f}); 21 | Uint32 currentCount = builder.currentVertices(); 22 | Uint32 startIdxTop = currentCount - segments - 1u; 23 | for (Uint32 i = 0; i < segments; i++) { 24 | const Uint32 j = startIdxTop + i; 25 | builder.addFace({ 26 | j, 27 | // next vertex - loop back if j is last 28 | (i != segments - 1) ? j + 1 : startIdxTop, 29 | // top vertex - last index! 30 | currentCount - 1u, 31 | }); 32 | } 33 | 34 | std::vector vertices; 35 | for (Uint32 j = 0; j < builder.currentVertices(); j++) { 36 | DefaultVertex v; 37 | v.pos = builder.positions[j]; 38 | v.normal = builder.normals[j]; 39 | vertices.push_back(v); 40 | } 41 | return MeshData{ 42 | SDL_GPU_PRIMITIVETYPE_TRIANGLELIST, 43 | std::move(vertices), 44 | std::move(builder.indices), 45 | }; 46 | } 47 | 48 | } // namespace candlewick 49 | -------------------------------------------------------------------------------- /src/candlewick/primitives/Sphere.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "../utils/MeshData.h" 4 | 5 | namespace candlewick { 6 | 7 | /// \brief Load a sphere primitive organized in bottom-up rings and left-right 8 | /// segments. 9 | /// 10 | /// This will have unit radius. You can apply a scaling transform to change the 11 | /// radius to what you want. See apply3DTransformInPlace(). 12 | /// \ingroup primitives1 13 | MeshData loadUvSphereSolid(Uint32 rings, Uint32 segments); 14 | 15 | } // namespace candlewick 16 | -------------------------------------------------------------------------------- /src/candlewick/third-party/.clang-format-ignore: -------------------------------------------------------------------------------- 1 | ./magic_enum.hpp 2 | ./fpng.h 3 | ./fpng.cpp 4 | ./float16_t.hpp 5 | -------------------------------------------------------------------------------- /src/candlewick/third-party/download.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/bash 2 | 3 | FILES=(" 4 | https://raw.githubusercontent.com/richgel999/fpng/refs/heads/main/src/fpng.h 5 | https://raw.githubusercontent.com/richgel999/fpng/refs/heads/main/src/fpng.cpp 6 | ") 7 | 8 | for file in $FILES 9 | do 10 | wget $file 11 | done 12 | -------------------------------------------------------------------------------- /src/candlewick/utils/LoadMaterial.cpp: -------------------------------------------------------------------------------- 1 | #include "LoadMaterial.h" 2 | #include "candlewick/core/errors.h" 3 | 4 | #include 5 | 6 | namespace candlewick { 7 | 8 | std::string_view assimpPropertyTypeName(aiPropertyTypeInfo type) { 9 | return magic_enum::enum_name(type); 10 | } 11 | 12 | std::string_view assimpShadingModeName(aiShadingMode shading_mode) { 13 | return magic_enum::enum_name(shading_mode); 14 | } 15 | 16 | #define _col(name) (aiColor4D &)*name.data() 17 | 18 | enum class material_load_retc { 19 | INVALID, 20 | PHONG_MISSING_DIFFUSE, 21 | PBR_MISSING_BASE_COLOR, 22 | PBR_MISSING_METALNESS, 23 | PBR_MISSING_ROUGHNESS, 24 | OK, 25 | }; 26 | 27 | material_load_retc loadPhongMaterial(aiMaterial *material, PhongMaterial &out) { 28 | if (!material) 29 | return material_load_retc::INVALID; 30 | 31 | if (material->Get(AI_MATKEY_COLOR_DIFFUSE, _col(out.diffuse)) != 32 | aiReturn_SUCCESS) { 33 | return material_load_retc::PHONG_MISSING_DIFFUSE; 34 | } 35 | material->Get(AI_MATKEY_COLOR_AMBIENT, _col(out.ambient)); 36 | material->Get(AI_MATKEY_COLOR_SPECULAR, _col(out.specular)); 37 | material->Get(AI_MATKEY_COLOR_EMISSIVE, _col(out.emissive)); 38 | material->Get(AI_MATKEY_SHININESS, out.shininess); 39 | material->Get(AI_MATKEY_REFLECTIVITY, out.reflectivity); 40 | return material_load_retc::OK; 41 | } 42 | 43 | material_load_retc loadPbrMaterial(aiMaterial *material, PbrMaterial &out) { 44 | if (!material) 45 | return material_load_retc::INVALID; 46 | 47 | if (material->Get(AI_MATKEY_BASE_COLOR, _col(out.baseColor)) != 48 | aiReturn_SUCCESS) 49 | return material_load_retc::PBR_MISSING_BASE_COLOR; 50 | 51 | material->Get(AI_MATKEY_METALLIC_FACTOR, out.metalness); 52 | material->Get(AI_MATKEY_ROUGHNESS_FACTOR, out.roughness); 53 | return material_load_retc::OK; 54 | } 55 | 56 | #undef _col 57 | 58 | PbrMaterial pbrFromPhong(const PhongMaterial &phong) { 59 | PbrMaterial out; 60 | out.baseColor = phong.diffuse; 61 | auto specularRgb = phong.specular.head<3>(); 62 | float specAvg = specularRgb.mean(); 63 | float specVar = (specularRgb - Float3::Constant(specAvg)).squaredNorm(); 64 | 65 | const float METALLIC_THRESHOLD = 0.2f; 66 | if (specVar > METALLIC_THRESHOLD) { 67 | out.metalness = .5f; 68 | out.baseColor = phong.specular; 69 | } else { 70 | out.metalness = 0.f; 71 | out.baseColor *= (1.0f + specAvg); 72 | } 73 | 74 | float shininess = std::clamp(phong.shininess, 1.0f, 1e3f); 75 | out.roughness = sqrtf(2.f / (shininess + 2.f)); 76 | out.ao = phong.ambient.head<3>().mean(); 77 | out.ao = fmaxf(0.1f, out.ao); 78 | return out; 79 | } 80 | 81 | PbrMaterial loadFromAssimpMaterial(aiMaterial *material) { 82 | PbrMaterial out; 83 | auto retc = loadPbrMaterial(material, out); 84 | if (retc == material_load_retc::OK) 85 | return out; 86 | 87 | PhongMaterial phong; 88 | retc = loadPhongMaterial(material, phong); 89 | 90 | if (retc == material_load_retc::OK) 91 | return pbrFromPhong(phong); 92 | 93 | terminate_with_message("Failed to load material: {:s}", 94 | magic_enum::enum_name(retc)); 95 | } 96 | } // namespace candlewick 97 | -------------------------------------------------------------------------------- /src/candlewick/utils/LoadMaterial.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "../core/MaterialUniform.h" 4 | 5 | #include "Utils.h" 6 | 7 | #include 8 | 9 | namespace candlewick { 10 | 11 | /// \brief Load our PBR material data from an assimp material. 12 | /// 13 | /// If the aiMaterial contains, in fact, a Phong material (\ref 14 | /// PhongMaterial), then a PBR material will be approximated. 15 | PbrMaterial loadFromAssimpMaterial(aiMaterial *material); 16 | 17 | } // namespace candlewick 18 | -------------------------------------------------------------------------------- /src/candlewick/utils/LoadMesh.cpp: -------------------------------------------------------------------------------- 1 | #include "LoadMesh.h" 2 | 3 | #include "MeshData.h" 4 | #include "LoadMaterial.h" 5 | #include "../core/DefaultVertex.h" 6 | 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | 13 | namespace candlewick { 14 | 15 | MeshData loadAiMesh(const aiMesh *inMesh, const aiMatrix4x4 transform) { 16 | using IndexType = MeshData::IndexType; 17 | const Uint32 expectedFaceSize = 3; 18 | 19 | std::vector vertexData; 20 | std::vector indexData; 21 | vertexData.resize(inMesh->mNumVertices); 22 | indexData.resize(inMesh->mNumFaces * expectedFaceSize); 23 | 24 | for (Uint32 vertex_id = 0; vertex_id < inMesh->mNumVertices; vertex_id++) { 25 | aiVector3D pos = inMesh->mVertices[vertex_id]; 26 | pos = transform * pos; 27 | DefaultVertex &vertex = vertexData[vertex_id]; 28 | vertex.pos = Float3::Map(&pos.x); 29 | 30 | aiMatrix3x3 normMatrix(transform); 31 | if (inMesh->HasNormals()) { 32 | aiVector3D n_ = inMesh->mNormals[vertex_id]; 33 | n_ = normMatrix * n_; 34 | vertex.normal = Float3::Map(&n_.x); 35 | } 36 | if (inMesh->HasTangentsAndBitangents()) { 37 | aiVector3D t = inMesh->mTangents[vertex_id]; 38 | t = normMatrix * t; 39 | vertex.tangent = Float3::Map(&t.x); 40 | } 41 | } 42 | 43 | for (Uint32 face_id = 0; face_id < inMesh->mNumFaces; face_id++) { 44 | const aiFace &f = inMesh->mFaces[face_id]; 45 | SDL_assert(f.mNumIndices == expectedFaceSize); 46 | for (Uint32 ii = 0; ii < f.mNumIndices; ii++) { 47 | indexData[face_id * expectedFaceSize + ii] = f.mIndices[ii]; 48 | } 49 | } 50 | return MeshData{SDL_GPU_PRIMITIVETYPE_TRIANGLELIST, std::move(vertexData), 51 | std::move(indexData)}; 52 | } 53 | 54 | static void log_resource_failure( 55 | const char *err_message, 56 | std::source_location loc = std::source_location::current()) { 57 | SDL_LogWarn(SDL_LOG_CATEGORY_APPLICATION, "%s: Failed to load resource [%s]", 58 | loc.function_name(), err_message); 59 | } 60 | 61 | mesh_load_retc loadSceneMeshes(const char *path, 62 | std::vector &meshData) { 63 | 64 | ::Assimp::Importer import; 65 | // remove point primitives 66 | import.SetPropertyInteger(AI_CONFIG_PP_SBP_REMOVE, 67 | aiPrimitiveType_LINE | aiPrimitiveType_POINT); 68 | Uint32 pFlags = aiProcess_CalcTangentSpace | aiProcess_Triangulate | 69 | aiProcess_GenSmoothNormals | aiProcess_SortByPType | 70 | aiProcess_JoinIdenticalVertices | aiProcess_GenUVCoords | 71 | aiProcess_RemoveComponent | aiProcess_FindDegenerates | 72 | aiProcess_PreTransformVertices | 73 | aiProcess_ImproveCacheLocality; 74 | import.SetPropertyBool(AI_CONFIG_IMPORT_COLLADA_IGNORE_UP_DIRECTION, true); 75 | const aiScene *scene = import.ReadFile(path, pFlags); 76 | if (!scene) { 77 | log_resource_failure(import.GetErrorString()); 78 | return mesh_load_retc::FAILED_TO_LOAD; 79 | } 80 | 81 | if (!scene->HasMeshes()) 82 | return mesh_load_retc::NO_MESHES; 83 | 84 | aiMatrix4x4 transform = scene->mRootNode->mTransformation; 85 | for (std::size_t i = 0; i < scene->mNumMeshes; i++) { 86 | aiMesh *inMesh = scene->mMeshes[i]; 87 | MeshData &md = meshData.emplace_back(loadAiMesh(inMesh, transform)); 88 | Uint32 materialId = inMesh->mMaterialIndex; 89 | if (scene->HasMaterials()) { 90 | aiMaterial *material = scene->mMaterials[materialId]; 91 | md.material = loadFromAssimpMaterial(material); 92 | } 93 | } 94 | 95 | return mesh_load_retc::OK; 96 | } 97 | 98 | } // namespace candlewick 99 | -------------------------------------------------------------------------------- /src/candlewick/utils/LoadMesh.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "Utils.h" 4 | #include 5 | #include 6 | 7 | namespace candlewick { 8 | 9 | /// Return codes for \ref loadSceneMeshes(). 10 | enum class mesh_load_retc : Uint16 { 11 | FAILED_TO_LOAD = 1 << 0, 12 | NO_MESHES = 1 << 1, 13 | 14 | OK = 1 << 4, 15 | }; 16 | 17 | /// \brief Load the meshes from the given path. 18 | /// This is implemented using the assimp library. 19 | mesh_load_retc loadSceneMeshes(const char *path, 20 | std::vector &meshData); 21 | } // namespace candlewick 22 | -------------------------------------------------------------------------------- /src/candlewick/utils/MeshDataView.cpp: -------------------------------------------------------------------------------- 1 | #include "MeshDataView.h" 2 | 3 | namespace candlewick { 4 | 5 | MeshDataView::MeshDataView(const MeshData &meshData) 6 | : primitiveType{meshData.primitiveType} 7 | , layout{meshData.layout} 8 | , vertexData{meshData.vertexData().data(), 9 | meshData.numVertices() * layout.vertexSize()} 10 | , indexData{meshData.indexData} {} 11 | 12 | MeshDataView::MeshDataView(SDL_GPUPrimitiveType primitiveType, 13 | const MeshLayout &layout, 14 | std::span vertices, 15 | std::span indices) 16 | : primitiveType{primitiveType} 17 | , layout{layout} 18 | , vertexData{vertices} 19 | , indexData{indices} {} 20 | 21 | MeshData MeshDataView::toOwned() const { 22 | std::vector vtxOut{vertexData.begin(), vertexData.end()}; 23 | std::vector idxOut{indexData.begin(), indexData.end()}; 24 | return MeshData{primitiveType, layout, std::move(vtxOut), std::move(idxOut)}; 25 | } 26 | 27 | } // namespace candlewick 28 | -------------------------------------------------------------------------------- /src/candlewick/utils/MeshDataView.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include "MeshData.h" 5 | 6 | namespace candlewick { 7 | struct MeshDataView : MeshDataBase { 8 | using IndexType = MeshData::IndexType; 9 | SDL_GPUPrimitiveType primitiveType; 10 | MeshLayout layout; 11 | std::span vertexData; 12 | std::span indexData; 13 | 14 | explicit MeshDataView(const MeshData &meshData); 15 | 16 | template 17 | MeshDataView(SDL_GPUPrimitiveType primitiveType, std::span vertices, 18 | std::span indices = {}); 19 | 20 | template 21 | MeshDataView(SDL_GPUPrimitiveType primitiveType, const V (&vertices)[N], 22 | const IndexType (&indices)[M]); 23 | 24 | MeshDataView(SDL_GPUPrimitiveType primitiveType, const MeshLayout &layout, 25 | std::span vertices, 26 | std::span indices = {}); 27 | 28 | MeshData toOwned() const; 29 | }; 30 | 31 | template 32 | MeshDataView::MeshDataView(SDL_GPUPrimitiveType primitiveType, 33 | std::span vertices, 34 | std::span indices) 35 | : primitiveType(primitiveType) 36 | , layout(meshLayoutFor()) 37 | , vertexData(reinterpret_cast(vertices.data()), 38 | vertices.size() * sizeof(V)) 39 | , indexData(indices) {} 40 | 41 | template 42 | MeshDataView::MeshDataView(SDL_GPUPrimitiveType primitiveType, 43 | const V (&vertices)[N], 44 | const IndexType (&indices)[M]) 45 | : MeshDataView(primitiveType, std::span{vertices}, 46 | std::span{indices}) {} 47 | 48 | } // namespace candlewick 49 | -------------------------------------------------------------------------------- /src/candlewick/utils/MeshTransforms.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "Utils.h" 4 | #include 5 | #include 6 | #include 7 | 8 | namespace candlewick { 9 | 10 | /// \brief Apply an \c Eigen::Affine3f 3D transform to a mesh in-place, 11 | /// transforming its vertices. 12 | void apply3DTransformInPlace(MeshData &meshData, const Eigen::Affine3f &tr); 13 | 14 | /// \brief Generate indices for a triangle strip geometry, given the vertex 15 | /// count. 16 | void triangleStripGenerateIndices(Uint32 vertexCount, 17 | std::vector &indices); 18 | 19 | /// \brief Convert \c MeshData object to an indexed mesh. 20 | /// \param meshData the input un-indexed \c MeshData object to be transformed. 21 | /// \warning For now, only supports \ref SDL_GPU_PRIMITIVETYPE_TRIANGLESTRIP. 22 | /// \sa triangleStripGenerateIndices 23 | MeshData generateIndices(const MeshData &meshData); 24 | 25 | namespace detail { 26 | /// \brief Number of vertices and indices in the merged mesh. 27 | std::pair 28 | mergeCalcIndexVertexCount(std::span meshes); 29 | } // namespace detail 30 | 31 | /// \brief Merge meshes down to a single mesh with consistent indexing. 32 | MeshData mergeMeshes(std::span meshes); 33 | 34 | /// \copybrief mergeMeshes(). 35 | MeshData mergeMeshes(std::vector &&meshes); 36 | 37 | } // namespace candlewick 38 | -------------------------------------------------------------------------------- /src/candlewick/utils/PixelFormatConversion.cpp: -------------------------------------------------------------------------------- 1 | #include "PixelFormatConversion.h" 2 | 3 | namespace candlewick { 4 | 5 | void bgraToRgbaConvert(Uint32 *bgraPixels, Uint32 pixelCount) { 6 | // define appropriate masks for BGRA format 7 | Uint32 red_mask = 0x00FF0000; 8 | Uint32 green_mask = 0x0000FF00; 9 | Uint32 blue_mask = 0x000000FF; 10 | Uint32 alpha_mask = 0xFF000000; 11 | 12 | for (Uint32 i = 0; i < pixelCount; ++i) { 13 | Uint32 pixel = bgraPixels[i]; 14 | bgraPixels[i] = ((pixel & red_mask) >> 16) | // Extract Red 15 | ((pixel & green_mask)) | // Keep Green 16 | ((pixel & blue_mask) << 16) | // Extract Blue 17 | (pixel & alpha_mask); // Keep Alpha 18 | } 19 | } 20 | 21 | } // namespace candlewick 22 | -------------------------------------------------------------------------------- /src/candlewick/utils/PixelFormatConversion.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | namespace candlewick { 6 | 7 | /// \brief In-place conversion from 8-bit BGRA to 8-bit RGBA. 8 | /// 9 | /// \param bgraPixels Pointer to input BGRA image. 10 | /// \param pixelCount Number of pixels in the BGRA image. 11 | void bgraToRgbaConvert(Uint32 *bgraPixels, Uint32 pixelCount); 12 | 13 | } // namespace candlewick 14 | -------------------------------------------------------------------------------- /src/candlewick/utils/Utils.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "../core/Core.h" 4 | 5 | namespace candlewick { 6 | 7 | class MeshData; 8 | struct MeshDataView; 9 | 10 | } // namespace candlewick 11 | -------------------------------------------------------------------------------- /src/candlewick/utils/VideoRecorder.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #ifndef CANDLEWICK_WITH_FFMPEG_SUPPORT 4 | #error "Including this file requires candlewick to be built with FFmpeg support" 5 | #endif 6 | #include "../core/Core.h" 7 | #include "../core/Tags.h" 8 | 9 | #include 10 | #include 11 | #include 12 | 13 | namespace candlewick { 14 | namespace media { 15 | 16 | struct VideoRecorderImpl; 17 | 18 | class TransferBufferPool; 19 | 20 | class VideoRecorder { 21 | std::unique_ptr m_impl; 22 | Uint32 m_width; 23 | Uint32 m_height; 24 | 25 | public: 26 | struct Settings { 27 | int fps = 30; 28 | // default: 2.5 Mb/s 29 | int bitRate = 2'500'000u; 30 | int outputWidth = 0; 31 | int outputHeight = 0; 32 | }; 33 | 34 | /// \brief Constructor which will not open the file or stream. 35 | explicit VideoRecorder(NoInitT); 36 | VideoRecorder(VideoRecorder &&) noexcept; 37 | VideoRecorder &operator=(VideoRecorder &&) noexcept; 38 | 39 | /// \brief Open the recording stream. 40 | /// 41 | /// \param width Input data width. 42 | /// \param height Input data height. 43 | /// \param filename Filename to open the outut stream at. 44 | void open(Uint32 width, Uint32 height, std::string_view filename, 45 | Settings settings); 46 | 47 | /// \brief Returns whether the recording stream is open. 48 | bool isRecording() const { return m_impl != nullptr; } 49 | 50 | /// \brief Constructor for the video recorder. 51 | /// 52 | /// \param width Input data width. 53 | /// \param height Input data height. 54 | /// \param settings Video recording settings (fps, bitrate, output file 55 | /// width and height). 56 | /// 57 | /// \note If the settings' output dimensions are not set, they will 58 | /// automatically be set to be the input's dimensions. 59 | /// \sa open() 60 | VideoRecorder(Uint32 width, Uint32 height, std::string_view filename, 61 | Settings settings); 62 | 63 | VideoRecorder(Uint32 width, Uint32 height, std::string_view filename); 64 | 65 | /// \brief Current number of recorded frames. 66 | Uint32 frameCounter() const; 67 | 68 | /// \brief Close the recording stream. 69 | void close() noexcept; 70 | 71 | ~VideoRecorder(); 72 | 73 | void writeTextureToVideoFrame(CommandBuffer &command_buffer, 74 | const Device &device, 75 | TransferBufferPool &pool, 76 | SDL_GPUTexture *texture, 77 | SDL_GPUTextureFormat format); 78 | }; 79 | 80 | } // namespace media 81 | } // namespace candlewick 82 | -------------------------------------------------------------------------------- /src/candlewick/utils/WriteTextureToImage.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "../core/Core.h" 4 | #include 5 | 6 | namespace candlewick { 7 | namespace media { 8 | 9 | struct DownloadResult { 10 | Uint32 *data; 11 | SDL_GPUTextureFormat format; 12 | Uint16 width; 13 | Uint16 height; 14 | SDL_GPUTransferBuffer *buffer; // used for unmapping later 15 | Uint32 payloadSize; 16 | }; 17 | 18 | /// \brief Transfer buffer for the texture downloader. 19 | class TransferBufferPool { 20 | SDL_GPUDevice *_device = nullptr; 21 | SDL_GPUTransferBuffer *_buffer = nullptr; 22 | Uint32 _currentBufSize = 0; 23 | 24 | public: 25 | TransferBufferPool(const Device &device); 26 | void release() noexcept; 27 | ~TransferBufferPool() noexcept { this->release(); } 28 | 29 | SDL_GPUTransferBuffer *acquireBuffer(Uint32 requiredSize); 30 | }; 31 | 32 | /// \brief Download texture to a mapped buffer. 33 | /// 34 | /// \warning The user is expected to unmap the buffer in the result struct. 35 | DownloadResult downloadTexture(CommandBuffer &command_buffer, 36 | const Device &device, TransferBufferPool &pool, 37 | SDL_GPUTexture *texture, 38 | SDL_GPUTextureFormat format, 39 | const Uint16 width, const Uint16 height); 40 | 41 | void saveTextureToFile(CommandBuffer &command_buffer, const Device &device, 42 | TransferBufferPool &pool, SDL_GPUTexture *texture, 43 | SDL_GPUTextureFormat format, const Uint16 width, 44 | const Uint16 height, std::string_view filename); 45 | 46 | } // namespace media 47 | } // namespace candlewick 48 | -------------------------------------------------------------------------------- /tests/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | find_package(GTest REQUIRED) 2 | 3 | function(add_candlewick_test filename) 4 | cmake_path(GET filename STEM name) 5 | add_executable(${name} ${filename}) 6 | target_link_libraries(${name} PRIVATE candlewick_core GTest::gtest_main) 7 | target_link_libraries(${name} PRIVATE ${ARGN}) 8 | add_test(NAME ${name} COMMAND ${name}) 9 | endfunction() 10 | 11 | add_candlewick_test(TestMeshData.cpp) 12 | add_candlewick_test(TestStrided.cpp) 13 | -------------------------------------------------------------------------------- /tests/TestStrided.cpp: -------------------------------------------------------------------------------- 1 | #include "candlewick/utils/StridedView.h" 2 | #include 3 | #include 4 | #include 5 | 6 | using candlewick::strided_view; 7 | 8 | GTEST_TEST(TestStridedView, c_array) { 9 | using T = std::tuple; 10 | T data[5] = {{0, 0.1f}, {10, 2.2f}, {0, 0.3f}, {12, -0.3f}, {0, -13.4f}}; 11 | 12 | strided_view view(data, 2 * sizeof(T)); 13 | EXPECT_EQ(view.size(), sizeof(data) / sizeof(T)); 14 | EXPECT_EQ(view.stride_bytes(), 2 * sizeof(T)); 15 | EXPECT_EQ(view.max_index(), 3); 16 | EXPECT_FALSE(view.empty()); 17 | } 18 | 19 | GTEST_TEST(TestStridedView, vector_int) { 20 | std::vector data; 21 | data.resize(11); 22 | std::iota(data.begin(), data.end(), 0); 23 | 24 | auto stride = 2 * sizeof(int); 25 | strided_view view{data.data(), data.size(), stride}; 26 | EXPECT_EQ(view.size(), data.size()); 27 | EXPECT_EQ(view.stride_bytes(), stride); 28 | EXPECT_EQ(view.max_index(), 6); 29 | EXPECT_FALSE(view.empty()); 30 | 31 | EXPECT_EQ(view.front(), 0); 32 | EXPECT_EQ(view[1], 2); 33 | EXPECT_EQ(view[2], 4); 34 | EXPECT_EQ(view.at(3), 6); 35 | EXPECT_EQ(view.at(4), 8); 36 | EXPECT_EQ(view.at(5), 10); 37 | EXPECT_THROW((void)view.at(6), std::out_of_range); 38 | 39 | int count = 0; 40 | for (auto it = view.begin(); it != view.end(); it++) { 41 | EXPECT_EQ(*it, count); 42 | count += 2; 43 | } 44 | EXPECT_EQ(count, 2 * view.max_index()); 45 | } 46 | 47 | struct test_data { 48 | int a; 49 | double b; 50 | }; 51 | 52 | bool operator==(const test_data &x, const test_data &y) { 53 | return x.a == y.a && x.b == y.b; 54 | } 55 | 56 | GTEST_TEST(TestStridedView, span) { 57 | std::vector data; 58 | data.resize(11); 59 | 60 | for (uint i = 0; i < 11; i++) { 61 | data[i].a = int(i); 62 | data[i].b = 3.f * float(i); 63 | } 64 | 65 | std::span view0{data}; 66 | 67 | auto stride = 3 * sizeof(test_data); 68 | strided_view view{view0, stride}; 69 | EXPECT_EQ(view.size(), data.size()); 70 | EXPECT_EQ(view.stride_bytes(), stride); 71 | EXPECT_FALSE(view.empty()); 72 | 73 | EXPECT_EQ(view[0], view.front()); 74 | EXPECT_EQ(view[0], data[0]); 75 | EXPECT_EQ(view[1], data[3]); 76 | EXPECT_EQ(view[2], data[6]); 77 | EXPECT_EQ(view.at(3), data.at(9)); 78 | EXPECT_THROW((void)view.at(4), std::out_of_range); 79 | } 80 | --------------------------------------------------------------------------------