├── .github
└── workflows
│ └── build.yml
├── .gitignore
├── COPYING
├── README.md
├── build.zig
├── build.zig.zon
└── src
├── TODO.md
├── bin
├── hydra
│ ├── README.md
│ ├── blender.py
│ ├── camera.cpp
│ ├── camera.hpp
│ ├── hydra.zig
│ ├── instancer.cpp
│ ├── instancer.hpp
│ ├── material.cpp
│ ├── material.hpp
│ ├── mesh.cpp
│ ├── mesh.hpp
│ ├── moonshine.h
│ ├── renderBuffer.cpp
│ ├── renderBuffer.hpp
│ ├── renderDelegate.cpp
│ ├── renderDelegate.hpp
│ ├── renderParam.hpp
│ ├── renderPass.cpp
│ ├── renderPass.hpp
│ ├── rendererPlugin.cpp
│ └── rendererPlugin.hpp
├── offline.zig
└── online
│ ├── ObjectPicker.zig
│ ├── SyncCopier.zig
│ ├── input.hlsl
│ └── online.zig
└── lib
├── README.md
├── Window.zig
├── core
├── DestructionQueue.zig
├── Encoder.zig
├── Image.zig
├── VulkanContext.zig
├── core.zig
├── descriptor.zig
├── mem.zig
├── pipeline.zig
├── shader_source.zig
└── vk_helpers.zig
├── displaysystem
├── Display.zig
├── Swapchain.zig
└── displaysystem.zig
├── engine.zig
├── fileformats
├── dds.zig
├── exr.zig
├── fileformats.zig
└── wuffs.zig
├── gui
├── Platform.zig
├── gui.zig
└── imgui.zig
├── hrtsystem
├── Accel.zig
├── BackgroundManager.zig
├── CameraManager.zig
├── ConstantSpectra.zig
├── MaterialManager.zig
├── MeshManager.zig
├── ModelManager.zig
├── Scene.zig
├── Sensor.zig
├── World.zig
├── hrtsystem.zig
├── pipeline.zig
└── shaders
│ ├── background
│ ├── equirectangular_to_equal_area.hlsl
│ └── fold.hlsl
│ ├── bsdf.hlsl
│ ├── camera.hlsl
│ ├── integrator.hlsl
│ ├── intersection.hlsl
│ ├── light.hlsl
│ ├── local_light
│ ├── fold1.hlsl
│ ├── fold3.hlsl
│ ├── geometry_power.hlsl
│ ├── instance_power.hlsl
│ └── triangle_power.hlsl
│ ├── main.hlsl
│ ├── material.hlsl
│ ├── medium.hlsl
│ ├── phase_function.hlsl
│ ├── ray.hlsl
│ ├── reflection_frame.hlsl
│ ├── scene.hlsl
│ ├── spectrum.hlsl
│ ├── volume.hlsl
│ ├── volume_tracker.hlsl
│ └── world.hlsl
├── shaders
└── utils
│ ├── helpers.hlsl
│ ├── mappings.hlsl
│ ├── math.hlsl
│ ├── random.hlsl
│ └── reservoir.hlsl
├── test_runner.zig
├── tests.zig
└── vector.zig
/.github/workflows/build.yml:
--------------------------------------------------------------------------------
1 | name: Check
2 |
3 | on:
4 | push:
5 | branches: [ trunk ]
6 |
7 | env:
8 | ZIG_VERSION: 0.14.0
9 |
10 | jobs:
11 | build-linux:
12 | runs-on: ubuntu-22.04
13 |
14 | steps:
15 | - name: Checkout repo
16 | uses: actions/checkout@v4
17 |
18 | - name: Install system dependencies
19 | run: |
20 | sudo apt-get update
21 | sudo apt-get install wayland-protocols libwayland-dev libxkbcommon-dev # Wayland
22 | sudo apt-get install libxcursor-dev libxrandr-dev libxinerama-dev libxi-dev # X11
23 |
24 | - name: Install DirectXShaderCompiler
25 | run: |
26 | curl https://github.com/microsoft/DirectXShaderCompiler/releases/download/v1.8.2407/linux_dxc_2024_07_31.x86_64.tar.gz -L | tar -zx
27 | echo "$PWD/bin" >> $GITHUB_PATH
28 |
29 | - name: Setup Zig
30 | uses: mlugg/setup-zig@v1
31 | with:
32 | version: ${{env.ZIG_VERSION}}
33 |
34 | - name: Type check
35 | run: zig build check
36 |
37 | build-windows:
38 | runs-on: windows-2022
39 |
40 | steps:
41 | - name: Checkout repo
42 | uses: actions/checkout@v4
43 |
44 | - name: Install DirectXShaderCompiler
45 | run: |
46 | Invoke-WebRequest -uri "https://github.com/microsoft/DirectXShaderCompiler/releases/download/v1.8.2407/dxc_2024_07_31.zip" -Method "GET" -OutFile dxc.zip
47 | Expand-Archive dxc.zip
48 | Add-Content $env:GITHUB_PATH ";.\dxc\bin\x86"
49 |
50 | - name: Setup Zig
51 | uses: mlugg/setup-zig@v1
52 | with:
53 | version: ${{env.ZIG_VERSION}}
54 |
55 | - name: Type check
56 | run: zig build check
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # zig build artifacts
2 | .zig-cache/
3 | zig-out/
4 |
5 | # Any testing asset files
6 | **.exr
7 | **.glb
8 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 |
2 |
3 | # Moonshine
4 |
5 | **A spectral path tracer built with Zig + Vulkan**
6 |
7 |
8 | [](https://blendswap.com/blend/12584)
9 | *Salle de bain by nacimus, rendered with Moonshine*
10 |
11 | ### Features
12 | * Binaries
13 | * offline -- a headless offline renderer
14 | * online -- a real-time windowed renderer
15 | * hydra -- a hydra render delegate
16 | * Light Transport
17 | * Full spectral path tracing
18 | * Direct light sampling with multiple importance sampling for all lights and materials
19 | * Lights
20 | * 360° environment maps
21 | * Sampled via [hierarchical warping](https://pharr.org/matt/blog/2019/06/05/visualizing-env-light-warpings)
22 | * Emissive meshes
23 | * Sampled according to power via 1D [hierarchical warping](https://pharr.org/matt/blog/2019/06/05/visualizing-env-light-warpings)
24 | * Materials
25 | * Standard PBR with metallic + roughness
26 | * Mirror
27 | * Glass with dispersion
28 | * Homogeneous volumes
29 |
30 | ### Dependencies
31 | #### Build
32 | * zig (see version [in CI](.github/workflows/build.yml))
33 | * DirectXShaderCompiler
34 | * For the online (real-time) renderer:
35 | * For Linux (Ubuntu, similar on others):
36 | * For Wayland: `wayland-protocols` `libwayland-dev` `libxkbcommon-dev`
37 | * For X11: `libxcursor-dev` `libxrandr-dev` `libxinerama-dev` `libxi-dev`
38 | #### Run
39 | * A GPU supporting Vulkan ray tracing
40 |
41 | ### License
42 |
43 | This project is licensed under the AGPL.
44 |
--------------------------------------------------------------------------------
/build.zig.zon:
--------------------------------------------------------------------------------
1 | .{
2 | .name = .moonshine,
3 | .version = "0.0.0",
4 |
5 | .dependencies = .{
6 | .vulkan_zig = .{
7 | .url = "https://github.com/Snektron/vulkan-zig/archive/fdbe6d60d1a937ee64f4e37c2acd0c76a7588f24.tar.gz",
8 | .hash = "vulkan-0.0.0-r7Ytx2dUAwDhoUvuIqliqPUbyDZZi0bl_CQIwspUpHs_",
9 | },
10 | .vulkan_headers = .{
11 | .url = "https://github.com/KhronosGroup/Vulkan-Headers/archive/9c77de5c3dd216f28e407eec65ed9c0a296c1f74.tar.gz",
12 | .hash = "N-V-__8AAHwr7wGgESDREQVGeJWFvErxgAnCu78GY5gT5-Cc",
13 | },
14 | .dcimgui = .{
15 | .url = "https://github.com/dearimgui/dear_bindings/releases/download/master_nogeneratedefaultargfunctions_2025-05-18T04-48-54/AllBindingFiles.zip",
16 | .hash = "N-V-__8AAP5AVwBNhUwp0cB7zotClmmctA8_Tx5Xaqw7x2LN",
17 | },
18 | .imgui = .{
19 | .url = "https://github.com/ocornut/imgui/archive/cdb5cbe6f8786ced227624a71bc8a677618e5327.tar.gz",
20 | .hash = "N-V-__8AAFMPawC25cFqglS-ARiXPjNMkV56NJmokWQJQApN",
21 | },
22 | .glfw = .{
23 | .url = "https://github.com/glfw/glfw/archive/e7ea71be039836da3a98cea55ae5569cb5eb885c.tar.gz",
24 | .hash = "N-V-__8AAMrJSwAUGb9-vTzkNR-5LXS81MR__ZRVfF3tWgG6",
25 | },
26 | .tinyexr = .{
27 | .url = "https://github.com/syoyo/tinyexr/archive/735ff73ce5959cf005eb99ce517c9bcecab89dfb.tar.gz",
28 | .hash = "N-V-__8AAGC4nQHVa2CJUUb6saSEtcSR7c3wMfzHy9oF6m1g",
29 | },
30 | .zgltf = .{
31 | .url = "https://github.com/kooparse/zgltf/archive/b6579c3887c7ab0eef5f1eda09abbbc1f04d76ce.tar.gz",
32 | .hash = "zgltf-0.1.0-sX6QAqIKAgAfb7yQlVrdsLwCNc6Fx8wFOLIQRaEGJnVC",
33 | },
34 | .wuffs = .{
35 | .url = "https://github.com/google/wuffs-mirror-release-c/archive/a29749ebe0be57d2b19d8406475bd2326d0f1a85.tar.gz",
36 | .hash = "N-V-__8AALcpVQBr9VERrZc0MwDxDcN2zInDaZPF91ZMaUjq",
37 | },
38 | },
39 | .fingerprint = 0xccaef4ce7dcdd508,
40 | .minimum_zig_version = "0.14.0",
41 | .paths = .{
42 | "",
43 | },
44 | }
45 |
--------------------------------------------------------------------------------
/src/TODO.md:
--------------------------------------------------------------------------------
1 | ### // TODO
2 | * Cameras
3 | * Fisheye
4 | * Lens System
5 | * Materials
6 | * Conductor with complex IOR
7 | * Transmissive with roughness
8 | * Material composition
9 | * Lights
10 | * Experiment with sampling triangle via solid angle after selecting it via area
11 | * Experiment with unifying sampling mesh lights and environment map
12 | * BVH
13 | * Sampling of filtered environment maps
14 | * Testing
15 | * Proper statistical tests GPU sampling routines
16 | * Proper statistical tests to make sure images have expected mean/variance
17 | * Resource management
18 | * Make sure we have all necessary `errdefers`
19 | * GPU resource arrays should be resizable
20 | * Need some sort of way to do async/parallel resource creation (transfers, processing)
21 | * Use physical (with correct scales) units
22 | * Integrators
23 | * ReSTIR
24 | * Spectral EXR output
25 | * Tonemapping
26 | * HDR display
27 | * A satisfying implementation is blocked by [HDR display metadata querying in Vulkan](https://github.com/KhronosGroup/Vulkan-Docs/issues/1787)
28 | * Make all objects scale-invariant
29 | * You should get the exact same image no matter what the root transform is, even if it is non-orthogonal
30 | * Cameras and backgrounds are currently missing this
--------------------------------------------------------------------------------
/src/bin/hydra/README.md:
--------------------------------------------------------------------------------
1 | ### Here be dragons
2 |
3 | I don't really know how Hydra works or what the proper idioms are, so this whole subproject is in the "rapid prototyping" phase rather than the "principled, polished work" phase.
--------------------------------------------------------------------------------
/src/bin/hydra/blender.py:
--------------------------------------------------------------------------------
1 | # Blender add-on for moonshine
2 | # Moonshine delegate must be in PXR_PLUGINPATH_NAME
3 |
4 | import bpy
5 |
6 | class MoonshineRenderEngine(bpy.types.HydraRenderEngine):
7 | bl_idname = 'HYDRA_MOONSHINE'
8 | bl_label = "Moonshine"
9 |
10 | bl_use_preview = True
11 | bl_use_gpu_context = False
12 | bl_use_materialx = False
13 |
14 | bl_delegate_id = 'HdMoonshinePlugin'
15 |
16 | def view_draw(self, context, depsgraph):
17 | super().view_draw(context, depsgraph)
18 | self.tag_redraw() # hack for accumulating samples; TODO: figure out how cycles does this
19 |
20 | register, unregister = bpy.utils.register_classes_factory((
21 | MoonshineRenderEngine,
22 | ))
23 |
24 | if __name__ == "__main__":
25 | register()
--------------------------------------------------------------------------------
/src/bin/hydra/camera.cpp:
--------------------------------------------------------------------------------
1 | #include "moonshine.h"
2 |
3 | #include "camera.hpp"
4 | #include "renderDelegate.hpp"
5 |
6 | #include
7 | #include
8 | #include
9 |
10 | PXR_NAMESPACE_OPEN_SCOPE
11 |
12 | HdMoonshineCamera::HdMoonshineCamera(SdfPath const& id) : HdCamera(id) {}
13 |
14 | void HdMoonshineCamera::Sync(HdSceneDelegate* sceneDelegate, HdRenderParam* renderParam, HdDirtyBits* dirtyBits) {
15 | HdCamera::Sync(sceneDelegate, renderParam, dirtyBits);
16 | HdMoonshine* msne = static_cast(renderParam)->_moonshine;
17 |
18 | GfMatrix4f transform = GfMatrix4f(GetTransform());
19 | GfVec3f origin = transform.Transform(GfVec3f(0.0, 0.0, 0.0));
20 | GfVec3f forward = transform.TransformDir(GfVec3f(0.0, 0.0, -1.0));
21 | GfVec3f up = transform.TransformDir(GfVec3f(0.0, -1.0, 0.0)); // insert negation here as we want to flip resultant image
22 |
23 | forward.Normalize();
24 | up.Normalize();
25 |
26 | Lens lens = Lens {
27 | .origin = F32x3 { .x = origin[0], .y = origin[1], .z = origin[2] },
28 | .forward = F32x3 { .x = forward[0], .y = forward[1], .z = forward[2] },
29 | .up = F32x3 { .x = up[0], .y = up[1], .z = up[2] },
30 | .vfov = 2.0f * std::atan(GetVerticalAperture() / (2.0f * GetFocalLength())),
31 | .aperture = 0,
32 | .focus_distance = 1,
33 | };
34 |
35 | if (_handle == -1) {
36 | _handle = HdMoonshineCreateLens(msne, lens);
37 | } else {
38 | HdMoonshineSetLens(msne, _handle, lens);
39 | }
40 | }
41 |
42 | PXR_NAMESPACE_CLOSE_SCOPE
43 |
--------------------------------------------------------------------------------
/src/bin/hydra/camera.hpp:
--------------------------------------------------------------------------------
1 | #pragma once
2 |
3 | #include "moonshine.h"
4 |
5 | #include "pxr/pxr.h"
6 | #include "pxr/imaging/hd/camera.h"
7 |
8 | PXR_NAMESPACE_OPEN_SCOPE
9 |
10 | class HdMoonshineCamera final : public HdCamera {
11 | public:
12 | HdMoonshineCamera(SdfPath const& id);
13 | ~HdMoonshineCamera() override = default;
14 |
15 | void Sync(HdSceneDelegate* sceneDelegate, HdRenderParam* renderParam, HdDirtyBits* dirtyBits) override;
16 |
17 | LensHandle _handle = -1;
18 | protected:
19 | HdMoonshineCamera(const HdMoonshineCamera&) = delete;
20 | HdMoonshineCamera &operator =(const HdMoonshineCamera&) = delete;
21 | };
22 |
23 | PXR_NAMESPACE_CLOSE_SCOPE
24 |
--------------------------------------------------------------------------------
/src/bin/hydra/instancer.cpp:
--------------------------------------------------------------------------------
1 | #include "instancer.hpp"
2 |
3 | #include "pxr/imaging/hd/sceneDelegate.h"
4 | #include "pxr/imaging/hd/tokens.h"
5 |
6 | #include "pxr/base/gf/vec3f.h"
7 | #include "pxr/base/gf/vec4f.h"
8 | #include "pxr/base/gf/matrix4d.h"
9 | #include "pxr/base/gf/rotation.h"
10 | #include "pxr/base/gf/quaternion.h"
11 |
12 | PXR_NAMESPACE_OPEN_SCOPE
13 |
14 | HdMoonshineInstancer::HdMoonshineInstancer(HdSceneDelegate* delegate, SdfPath const& id) : HdInstancer(delegate, id) {}
15 |
16 | HdMoonshineInstancer::~HdMoonshineInstancer() {}
17 |
18 | void HdMoonshineInstancer::Sync(HdSceneDelegate* delegate, HdRenderParam* renderParam, HdDirtyBits* dirtyBits) {
19 | _UpdateInstancer(delegate, dirtyBits);
20 |
21 | if (HdChangeTracker::IsAnyPrimvarDirty(*dirtyBits, GetId())) {
22 | SdfPath const& id = GetId();
23 | HdPrimvarDescriptorVector primvars = delegate->GetPrimvarDescriptors(id, HdInterpolationInstance);
24 |
25 | for (HdPrimvarDescriptor const& pv: primvars) {
26 | if (HdChangeTracker::IsPrimvarDirty(*dirtyBits, id, pv.name)) {
27 | VtValue value = delegate->Get(id, pv.name);
28 | if (!value.IsEmpty()) {
29 | primvarMap_[pv.name] = value;
30 | }
31 | }
32 | }
33 | }
34 | }
35 |
36 | VtMatrix4dArray HdMoonshineInstancer::ComputeInstanceTransforms(SdfPath const &prototypeId) {
37 | GfMatrix4d instancerTransform = GetDelegate()->GetInstancerTransform(GetId());
38 | VtIntArray instanceIndices = GetDelegate()->GetInstanceIndices(GetId(), prototypeId);
39 |
40 | VtMatrix4dArray instanceTransforms(instanceIndices.size());
41 |
42 | VtValue translationsValue = primvarMap_[HdInstancerTokens->instanceTranslations];
43 | VtValue rotationsValue = primvarMap_[HdInstancerTokens->instanceRotations];
44 | VtValue scalesValue = primvarMap_[HdInstancerTokens->instanceScales];
45 | VtValue transformsValue = primvarMap_[HdInstancerTokens->instanceTransforms];
46 |
47 | VtVec3dArray translations;
48 | if (translationsValue.CanCast()) {
49 | translations = translationsValue.Cast().UncheckedGet();
50 | }
51 |
52 | VtQuatdArray rotations;
53 | if (rotationsValue.CanCast()) {
54 | rotations = rotationsValue.Cast().UncheckedGet();
55 | }
56 |
57 | VtVec3dArray scales;
58 | if (scalesValue.CanCast()) {
59 | scales = scalesValue.Cast().UncheckedGet();
60 | }
61 |
62 | VtMatrix4dArray transforms;
63 | if (transformsValue.CanCast()) {
64 | transforms = transformsValue.Cast().UncheckedGet();
65 | }
66 |
67 | for (size_t i = 0; i < instanceIndices.size(); i++) {
68 | int instanceIndex = instanceIndices[i];
69 |
70 | GfMatrix4d out = instancerTransform;
71 |
72 | GfMatrix4d temp;
73 | if (i < translations.size()) {
74 | temp.SetTranslate(translations[instanceIndex]);
75 | out = temp * out;
76 | }
77 | if (i < rotations.size()) {
78 | temp.SetRotate(rotations[instanceIndex]);
79 | out = temp * out;
80 | }
81 | if (i < scales.size()) {
82 | temp.SetScale(scales[instanceIndex]);
83 | out = temp * out;
84 | }
85 | if (i < transforms.size()) {
86 | temp = transforms[instanceIndex];
87 | out = temp * out;
88 | }
89 |
90 | instanceTransforms[i] = out;
91 | }
92 |
93 | if (GetParentId().IsEmpty()) {
94 | return instanceTransforms;
95 | }
96 |
97 | HdInstancer *parentInstancer = GetDelegate()->GetRenderIndex().GetInstancer(GetParentId());
98 | VtMatrix4dArray parentTransforms = static_cast(parentInstancer)->ComputeInstanceTransforms(GetId());
99 |
100 | VtMatrix4dArray final(parentTransforms.size() * instanceTransforms.size());
101 | for (size_t i = 0; i < parentTransforms.size(); ++i) {
102 | for (size_t j = 0; j < instanceTransforms.size(); ++j) {
103 | final[i * instanceTransforms.size() + j] = instanceTransforms[j] * parentTransforms[i];
104 | }
105 | }
106 |
107 | return final;
108 | }
109 |
110 | PXR_NAMESPACE_CLOSE_SCOPE
111 |
--------------------------------------------------------------------------------
/src/bin/hydra/instancer.hpp:
--------------------------------------------------------------------------------
1 | #pragma once
2 |
3 | #include "moonshine.h"
4 |
5 | #include "pxr/pxr.h"
6 | #include "pxr/imaging/hd/instancer.h"
7 |
8 | PXR_NAMESPACE_OPEN_SCOPE
9 |
10 | class HdMoonshineInstancer : public HdInstancer {
11 | public:
12 | HdMoonshineInstancer(HdSceneDelegate* delegate, SdfPath const& id);
13 | ~HdMoonshineInstancer();
14 |
15 | void Sync(HdSceneDelegate *sceneDelegate, HdRenderParam *renderParam, HdDirtyBits *dirtyBits) override;
16 | VtMatrix4dArray ComputeInstanceTransforms(const SdfPath& prototypeId);
17 | private:
18 | TfHashMap primvarMap_;
19 | };
20 |
21 | PXR_NAMESPACE_CLOSE_SCOPE
22 |
--------------------------------------------------------------------------------
/src/bin/hydra/material.hpp:
--------------------------------------------------------------------------------
1 | #pragma once
2 |
3 | #include
4 | #include
5 |
6 | #include "renderParam.hpp"
7 |
8 | PXR_NAMESPACE_OPEN_SCOPE
9 |
10 | class HdMoonshineMaterial final : public HdMaterial
11 | {
12 | public:
13 | HdMoonshineMaterial(const SdfPath& id, const HdMoonshineRenderParam& renderParam);
14 | ~HdMoonshineMaterial() override;
15 |
16 | public:
17 | HdDirtyBits GetInitialDirtyBitsMask() const override;
18 |
19 | void Sync(HdSceneDelegate* sceneDelegate, HdRenderParam* renderParam, HdDirtyBits* dirtyBits) override;
20 |
21 | MaterialHandle _handle;
22 | };
23 |
24 | PXR_NAMESPACE_CLOSE_SCOPE
--------------------------------------------------------------------------------
/src/bin/hydra/mesh.hpp:
--------------------------------------------------------------------------------
1 | #pragma once
2 |
3 | #include
4 |
5 | #include
6 | #include
7 | #include
8 |
9 | #include "renderParam.hpp"
10 |
11 | #include "moonshine.h"
12 |
13 | PXR_NAMESPACE_OPEN_SCOPE
14 |
15 | class HdMoonshineMesh final : public HdMesh {
16 | public:
17 | HdMoonshineMesh(SdfPath const& id, const HdMoonshineRenderParam& renderParam);
18 | ~HdMoonshineMesh() override = default;
19 |
20 | HdDirtyBits GetInitialDirtyBitsMask() const override;
21 |
22 | void Sync(HdSceneDelegate* sceneDelegate, HdRenderParam* renderParam, HdDirtyBits* dirtyBits, TfToken const &reprToken) override;
23 |
24 | void Finalize(HdRenderParam *renderParam) override;
25 | protected:
26 | void _InitRepr(TfToken const &reprToken, HdDirtyBits *dirtyBits) override;
27 |
28 | HdDirtyBits _PropagateDirtyBits(HdDirtyBits bits) const override;
29 |
30 | HdMoonshineMesh(const HdMoonshineMesh&) = delete;
31 | HdMoonshineMesh &operator =(const HdMoonshineMesh&) = delete;
32 | private:
33 | std::optional FindPrimvarInterpolation(HdSceneDelegate* sceneDelegate, TfToken name) const;
34 |
35 | template
36 | VtArray ComputePrimvar(HdSceneDelegate* sceneDelegate, VtVec3iArray const& indices, TfToken primvarName) const;
37 |
38 | GfMatrix4f _transform{1.0f};
39 | MeshHandle _mesh;
40 | MaterialHandle _material;
41 |
42 | // these two have same len
43 | std::vector _instances = {};
44 | std::vector _instancesTransforms = {};
45 | };
46 |
47 | PXR_NAMESPACE_CLOSE_SCOPE
48 |
--------------------------------------------------------------------------------
/src/bin/hydra/moonshine.h:
--------------------------------------------------------------------------------
1 | // sort of viewing this whole file as a temporary hack
2 | // until emit-h is resurrected
3 |
4 | #pragma once
5 |
6 | #include
7 | #include
8 | #include
9 |
10 | typedef uint32_t MeshHandle;
11 | typedef uint32_t ImageHandle;
12 | typedef uint32_t MaterialHandle;
13 | typedef uint32_t SensorHandle;
14 | typedef uint32_t LensHandle;
15 | typedef uint32_t InstanceHandle;
16 |
17 | typedef struct F32x2 {
18 | float x, y;
19 | } F32x2;
20 |
21 | typedef struct F32x3 {
22 | float x, y, z;
23 | } F32x3;
24 |
25 | typedef struct F32x4 {
26 | float x, y, z, w;
27 | } F32x4;
28 |
29 | typedef struct U32x3 {
30 | uint32_t x, y, z;
31 | } U32x3;
32 |
33 | typedef struct Mat4x3 {
34 | F32x4 x, y, z;
35 | } Mat4x3;
36 |
37 | typedef struct Extent2D {
38 | uint32_t width;
39 | uint32_t height;
40 | } Extent2D;
41 |
42 | typedef struct Lens {
43 | F32x3 origin;
44 | F32x3 forward;
45 | F32x3 up;
46 | float vfov;
47 | float aperture;
48 | float focus_distance;
49 | } Lens;
50 |
51 | typedef struct Material {
52 | ImageHandle normal;
53 | ImageHandle emissive;
54 | ImageHandle color;
55 | ImageHandle metalness;
56 | ImageHandle roughness;
57 | float ior;
58 | } Material;
59 |
60 | typedef enum TextureFormat {
61 | f16x4,
62 | u8x1,
63 | u8x2,
64 | u8x4,
65 | u8x4_srgb,
66 | } TextureFormat;
67 |
68 | typedef struct HdMoonshine HdMoonshine;
69 | extern "C" HdMoonshine* HdMoonshineCreate(void);
70 | extern "C" void HdMoonshineDestroy(HdMoonshine*);
71 | extern "C" bool HdMoonshineRender(HdMoonshine*, SensorHandle, LensHandle);
72 | extern "C" bool HdMoonshineRebuildPipeline(HdMoonshine*);
73 | extern "C" MeshHandle HdMoonshineCreateMesh(HdMoonshine*, const F32x3*, const F32x3*, const F32x2*, size_t);
74 | extern "C" ImageHandle HdMoonshineCreateSolidTexture1(HdMoonshine*, float, const char*);
75 | extern "C" ImageHandle HdMoonshineCreateSolidTexture2(HdMoonshine*, F32x2, const char*);
76 | extern "C" ImageHandle HdMoonshineCreateSolidTexture3(HdMoonshine*, F32x3, const char*);
77 | extern "C" ImageHandle HdMoonshineCreateRawTexture(HdMoonshine*, uint8_t*, Extent2D, TextureFormat, const char*);
78 | extern "C" MaterialHandle HdMoonshineCreateMaterial(HdMoonshine*, Material);
79 | extern "C" void HdMoonshineSetMaterialNormal(HdMoonshine*, MaterialHandle, ImageHandle);
80 | extern "C" void HdMoonshineSetMaterialEmissive(HdMoonshine*, MaterialHandle, ImageHandle);
81 | extern "C" void HdMoonshineSetMaterialColor(HdMoonshine*, MaterialHandle, ImageHandle);
82 | extern "C" void HdMoonshineSetMaterialMetalness(HdMoonshine*, MaterialHandle, ImageHandle);
83 | extern "C" void HdMoonshineSetMaterialRoughness(HdMoonshine*, MaterialHandle, ImageHandle);
84 | extern "C" void HdMoonshineSetMaterialIOR(HdMoonshine*, MaterialHandle, float);
85 | extern "C" InstanceHandle HdMoonshineCreateInstance(HdMoonshine*, Mat4x3, MeshHandle, MaterialHandle, bool);
86 | extern "C" void HdMoonshineDestroyInstance(HdMoonshine*, InstanceHandle);
87 | extern "C" void HdMoonshineSetInstanceTransform(HdMoonshine*, InstanceHandle, Mat4x3);
88 | extern "C" void HdMoonshineSetInstanceVisibility(HdMoonshine*, InstanceHandle, bool);
89 | extern "C" SensorHandle HdMoonshineCreateSensor(HdMoonshine*, Extent2D);
90 | extern "C" float* HdMoonshineGetSensorData(const HdMoonshine*, SensorHandle);
91 | extern "C" LensHandle HdMoonshineCreateLens(HdMoonshine*, Lens);
92 | extern "C" void HdMoonshineSetLens(HdMoonshine*, LensHandle, Lens);
93 |
--------------------------------------------------------------------------------
/src/bin/hydra/renderBuffer.cpp:
--------------------------------------------------------------------------------
1 | #include "renderBuffer.hpp"
2 | #include "moonshine.h"
3 | #include "renderDelegate.hpp"
4 |
5 | #include "pxr/imaging/hd/renderBuffer.h"
6 | #include "pxr/imaging/hd/sceneDelegate.h"
7 | #include "pxr/imaging/hd/renderIndex.h"
8 | #include "pxr/base/gf/vec3i.h"
9 |
10 | #include
11 |
12 | PXR_NAMESPACE_OPEN_SCOPE
13 |
14 | HdMoonshineRenderBuffer::HdMoonshineRenderBuffer(SdfPath const& id, HdMoonshineRenderDelegate* renderDelegate) : HdRenderBuffer(id), _renderDelegate(renderDelegate) {}
15 |
16 | HdMoonshineRenderBuffer::~HdMoonshineRenderBuffer() = default;
17 |
18 | void HdMoonshineRenderBuffer::_Deallocate() {} // TODO
19 |
20 | bool HdMoonshineRenderBuffer::Allocate(GfVec3i const& dimensions, HdFormat format, bool multiSampled)
21 | {
22 | _width = dimensions[0];
23 | _height = dimensions[1];
24 |
25 | _sensor = HdMoonshineCreateSensor(_renderDelegate->_moonshine, Extent2D { .width = _width, .height = _height });
26 | _data = reinterpret_cast(HdMoonshineGetSensorData(_renderDelegate->_moonshine, _sensor));
27 |
28 | return true;
29 | }
30 |
31 | void HdMoonshineRenderBuffer::Resolve() {}
32 |
33 | PXR_NAMESPACE_CLOSE_SCOPE
34 |
--------------------------------------------------------------------------------
/src/bin/hydra/renderBuffer.hpp:
--------------------------------------------------------------------------------
1 | #pragma once
2 |
3 | #include "moonshine.h"
4 |
5 | #include "pxr/pxr.h"
6 | #include "pxr/imaging/hd/renderBuffer.h"
7 | #include "renderDelegate.hpp"
8 |
9 | PXR_NAMESPACE_OPEN_SCOPE
10 |
11 | class HdMoonshineRenderBuffer : public HdRenderBuffer
12 | {
13 | public:
14 | HdMoonshineRenderBuffer(SdfPath const& id, HdMoonshineRenderDelegate* renderDelegate);
15 | ~HdMoonshineRenderBuffer() override;
16 |
17 | bool Allocate(GfVec3i const& dimensions, HdFormat format, bool multiSampled) override;
18 |
19 | unsigned int GetWidth() const override { return _width; }
20 | unsigned int GetHeight() const override { return _height; }
21 | unsigned int GetDepth() const override { return 1; }
22 | HdFormat GetFormat() const override { return HdFormatFloat32Vec4; }
23 | bool IsMultiSampled() const override { return false; }
24 |
25 | void* Map() override {
26 | return _data;
27 | }
28 |
29 | void Unmap() override {}
30 |
31 | bool IsMapped() const override {
32 | return false;
33 | }
34 |
35 | bool IsConverged() const override {
36 | return false;
37 | }
38 |
39 | void Resolve() override;
40 |
41 | SensorHandle _sensor;
42 | private:
43 | void _Deallocate() override;
44 |
45 | HdMoonshineRenderDelegate* _renderDelegate;
46 | unsigned int _width;
47 | unsigned int _height;
48 | uint8_t* _data = nullptr;
49 | };
50 |
51 | PXR_NAMESPACE_CLOSE_SCOPE
52 |
--------------------------------------------------------------------------------
/src/bin/hydra/renderDelegate.cpp:
--------------------------------------------------------------------------------
1 | #include
2 |
3 | #include
4 |
5 | #include "renderDelegate.hpp"
6 | #include "renderPass.hpp"
7 | #include "renderBuffer.hpp"
8 | #include "mesh.hpp"
9 | #include "camera.hpp"
10 | #include "instancer.hpp"
11 | #include "material.hpp"
12 |
13 | PXR_NAMESPACE_OPEN_SCOPE
14 |
15 | TF_DEFINE_PRIVATE_TOKENS(_tokens,
16 | (rebuildPipeline)
17 | );
18 |
19 | const TfTokenVector HdMoonshineRenderDelegate::SUPPORTED_RPRIM_TYPES = {
20 | HdPrimTypeTokens->mesh,
21 | };
22 |
23 | const TfTokenVector HdMoonshineRenderDelegate::SUPPORTED_SPRIM_TYPES = {
24 | HdPrimTypeTokens->camera,
25 | HdPrimTypeTokens->extComputation,
26 | HdPrimTypeTokens->material,
27 | };
28 |
29 | const TfTokenVector HdMoonshineRenderDelegate::SUPPORTED_BPRIM_TYPES = {
30 | HdPrimTypeTokens->renderBuffer,
31 | };
32 |
33 | HdMoonshineRenderDelegate::HdMoonshineRenderDelegate() : HdRenderDelegate() {
34 | _Initialize();
35 | }
36 |
37 | HdMoonshineRenderDelegate::HdMoonshineRenderDelegate(HdRenderSettingsMap const& settingsMap) : HdRenderDelegate(settingsMap) {
38 | _Initialize();
39 | }
40 |
41 | HdCommandDescriptors HdMoonshineRenderDelegate::GetCommandDescriptors() const {
42 | HdCommandDescriptor commandDesc(_tokens->rebuildPipeline, "Rebuild pipeline", {});
43 | return { commandDesc };
44 | }
45 |
46 | bool HdMoonshineRenderDelegate::InvokeCommand(const TfToken &command, const HdCommandArgs &args) {
47 | if (command == _tokens->rebuildPipeline) {
48 | HdMoonshineRebuildPipeline(_moonshine);
49 | return true;
50 | } else {
51 | TF_CODING_ERROR("Unknown command %s!", command.GetText());
52 | return false;
53 | }
54 | }
55 |
56 | void HdMoonshineRenderDelegate::_Initialize() {
57 | _moonshine = HdMoonshineCreate();
58 | _resourceRegistry = std::make_shared();
59 | _renderParam = std::make_unique(_moonshine);
60 | }
61 |
62 | HdMoonshineRenderDelegate::~HdMoonshineRenderDelegate() {
63 | _resourceRegistry.reset();
64 | HdMoonshineDestroy(_moonshine);
65 | }
66 |
67 | TfTokenVector const& HdMoonshineRenderDelegate::GetSupportedRprimTypes() const {
68 | return SUPPORTED_RPRIM_TYPES;
69 | }
70 |
71 | TfTokenVector const& HdMoonshineRenderDelegate::GetSupportedSprimTypes() const {
72 | return SUPPORTED_SPRIM_TYPES;
73 | }
74 |
75 | TfTokenVector const& HdMoonshineRenderDelegate::GetSupportedBprimTypes() const {
76 | return SUPPORTED_BPRIM_TYPES;
77 | }
78 |
79 | HdResourceRegistrySharedPtr HdMoonshineRenderDelegate::GetResourceRegistry() const {
80 | return _resourceRegistry;
81 | }
82 |
83 | void HdMoonshineRenderDelegate::CommitResources(HdChangeTracker *tracker) {}
84 |
85 | HdRenderPassSharedPtr HdMoonshineRenderDelegate::CreateRenderPass(HdRenderIndex *index, HdRprimCollection const& collection) {
86 | return HdRenderPassSharedPtr(new HdMoonshineRenderPass(index, collection));
87 | }
88 |
89 | HdRprim* HdMoonshineRenderDelegate::CreateRprim(TfToken const& typeId, SdfPath const& rprimId) {
90 | if (typeId == HdPrimTypeTokens->mesh) {
91 | return new HdMoonshineMesh(rprimId, *_renderParam);
92 | } else {
93 | TF_CODING_ERROR("Unknown Rprim type %s", typeId.GetText());
94 | return nullptr;
95 | }
96 | }
97 |
98 | void HdMoonshineRenderDelegate::DestroyRprim(HdRprim *rPrim) {
99 | delete rPrim;
100 | }
101 |
102 | HdSprim* HdMoonshineRenderDelegate::CreateSprim(TfToken const& typeId, SdfPath const& sprimId) {
103 | if (typeId == HdPrimTypeTokens->camera) {
104 | return new HdMoonshineCamera(sprimId);
105 | } else if (typeId == HdPrimTypeTokens->extComputation) {
106 | return new HdExtComputation(sprimId);
107 | } else if (typeId == HdPrimTypeTokens->material) {
108 | return new HdMoonshineMaterial(sprimId, *_renderParam);
109 | } else {
110 | TF_CODING_ERROR("Unknown Sprim type %s", typeId.GetText());
111 | return nullptr;
112 | }
113 | }
114 |
115 | HdSprim* HdMoonshineRenderDelegate::CreateFallbackSprim(TfToken const& typeId) {
116 | if (typeId == HdPrimTypeTokens->camera) {
117 | return new HdMoonshineCamera(SdfPath::EmptyPath());
118 | } else if (typeId == HdPrimTypeTokens->extComputation) {
119 | return new HdExtComputation(SdfPath::EmptyPath());
120 | } else if (typeId == HdPrimTypeTokens->material) {
121 | return new HdMoonshineMaterial(SdfPath::EmptyPath(), *_renderParam);
122 | } else {
123 | TF_CODING_ERROR("Unknown fallback Sprim type %s", typeId.GetText());
124 | return nullptr;
125 | }
126 | }
127 |
128 | void HdMoonshineRenderDelegate::DestroySprim(HdSprim *sPrim) {
129 | delete sPrim;
130 | }
131 |
132 | HdBprim* HdMoonshineRenderDelegate::CreateBprim(TfToken const& typeId, SdfPath const& bprimId) {
133 | if (typeId == HdPrimTypeTokens->renderBuffer) {
134 | return new HdMoonshineRenderBuffer(bprimId, this);
135 | } else {
136 | TF_CODING_ERROR("Unknown Bprim type %s", typeId.GetText());
137 | return nullptr;
138 | }
139 | }
140 |
141 | HdBprim* HdMoonshineRenderDelegate::CreateFallbackBprim(TfToken const& typeId) {
142 | if (typeId == HdPrimTypeTokens->renderBuffer) {
143 | return new HdMoonshineRenderBuffer(SdfPath::EmptyPath(), this);
144 | } else {
145 | TF_CODING_ERROR("Unknown fallback Bprim type %s", typeId.GetText());
146 | return nullptr;
147 | }
148 | }
149 |
150 | void HdMoonshineRenderDelegate::DestroyBprim(HdBprim *bPrim) {
151 | delete bPrim;
152 | }
153 |
154 | HdInstancer* HdMoonshineRenderDelegate::CreateInstancer(HdSceneDelegate *delegate, SdfPath const& id) {
155 | return new HdMoonshineInstancer(delegate, id);
156 | }
157 |
158 | void HdMoonshineRenderDelegate::DestroyInstancer(HdInstancer *instancer) {
159 | delete instancer;
160 | }
161 |
162 | HdRenderParam* HdMoonshineRenderDelegate::GetRenderParam() const {
163 | return _renderParam.get();
164 | }
165 |
166 | HdAovDescriptor HdMoonshineRenderDelegate::GetDefaultAovDescriptor(TfToken const& name) const {
167 | if (name == HdAovTokens->color) {
168 | return HdAovDescriptor(HdFormatFloat32Vec3, false, VtValue(GfVec4f(0.0f)));
169 | } else {
170 | return HdAovDescriptor();
171 | }
172 | }
173 |
174 | PXR_NAMESPACE_CLOSE_SCOPE
175 |
--------------------------------------------------------------------------------
/src/bin/hydra/renderDelegate.hpp:
--------------------------------------------------------------------------------
1 | #pragma once
2 |
3 | #include
4 |
5 | #include
6 | #include
7 | #include
8 |
9 | #include "renderParam.hpp"
10 |
11 | #include "moonshine.h"
12 |
13 | PXR_NAMESPACE_OPEN_SCOPE
14 |
15 | class HdMoonshineRenderDelegate final : public HdRenderDelegate
16 | {
17 | public:
18 | HdMoonshineRenderDelegate();
19 | HdMoonshineRenderDelegate(HdRenderSettingsMap const& settingsMap);
20 | ~HdMoonshineRenderDelegate();
21 |
22 | HdCommandDescriptors GetCommandDescriptors() const override;
23 |
24 | bool InvokeCommand(const TfToken &command, const HdCommandArgs &args) override;
25 |
26 | const TfTokenVector &GetSupportedRprimTypes() const override;
27 | const TfTokenVector &GetSupportedSprimTypes() const override;
28 | const TfTokenVector &GetSupportedBprimTypes() const override;
29 |
30 | HdResourceRegistrySharedPtr GetResourceRegistry() const override;
31 |
32 | HdRenderPassSharedPtr CreateRenderPass(HdRenderIndex *index, HdRprimCollection const& collection) override;
33 |
34 | HdInstancer *CreateInstancer(HdSceneDelegate *delegate, SdfPath const& id) override;
35 | void DestroyInstancer(HdInstancer *instancer) override;
36 |
37 | HdRprim *CreateRprim(TfToken const& typeId, SdfPath const& rprimId) override;
38 | void DestroyRprim(HdRprim *rPrim) override;
39 |
40 | HdSprim *CreateSprim(TfToken const& typeId, SdfPath const& sprimId) override;
41 | HdSprim *CreateFallbackSprim(TfToken const& typeId) override;
42 | void DestroySprim(HdSprim *sprim) override;
43 |
44 | HdBprim *CreateBprim(TfToken const& typeId, SdfPath const& bprimId) override;
45 | HdBprim *CreateFallbackBprim(TfToken const& typeId) override;
46 | void DestroyBprim(HdBprim *bprim) override;
47 |
48 | void CommitResources(HdChangeTracker *tracker) override;
49 |
50 | HdRenderParam *GetRenderParam() const override;
51 |
52 | HdAovDescriptor GetDefaultAovDescriptor(TfToken const& name) const override;
53 | HdMoonshine* _moonshine;
54 | private:
55 | static const TfTokenVector SUPPORTED_RPRIM_TYPES;
56 | static const TfTokenVector SUPPORTED_SPRIM_TYPES;
57 | static const TfTokenVector SUPPORTED_BPRIM_TYPES;
58 |
59 | void _Initialize();
60 |
61 | HdResourceRegistrySharedPtr _resourceRegistry;
62 | std::unique_ptr _renderParam;
63 |
64 | HdMoonshineRenderDelegate(const HdMoonshineRenderDelegate &) = delete;
65 | HdMoonshineRenderDelegate &operator =(const HdMoonshineRenderDelegate &) = delete;
66 | };
67 |
68 | PXR_NAMESPACE_CLOSE_SCOPE
--------------------------------------------------------------------------------
/src/bin/hydra/renderParam.hpp:
--------------------------------------------------------------------------------
1 | #pragma once
2 |
3 | #include
4 |
5 | #include "moonshine.h"
6 |
7 | PXR_NAMESPACE_OPEN_SCOPE
8 |
9 | class HdMoonshineRenderParam final : public HdRenderParam
10 | {
11 | public:
12 | HdMoonshineRenderParam(HdMoonshine* moonshine) : _moonshine(moonshine) {
13 | _black3 = HdMoonshineCreateSolidTexture3(_moonshine, F32x3 { .x = 0.0f, .y = 0.0f, .z = 0.0f }, "black3");
14 | _black1 = HdMoonshineCreateSolidTexture1(_moonshine, 0.0, "black1");
15 | _upNormal = HdMoonshineCreateSolidTexture2(_moonshine, F32x2 { .x = 0.5f, .y = 0.5f }, "up normal");
16 | _grey3 = HdMoonshineCreateSolidTexture3(_moonshine, F32x3 { .x = 0.5f, .y = 0.5f, .z = 0.5f }, "grey3");
17 | _white1 = HdMoonshineCreateSolidTexture1(_moonshine, 1.0, "white1");
18 | _defaultMaterial = HdMoonshineCreateMaterial(_moonshine, Material {
19 | .normal = _upNormal,
20 | .emissive = _black3,
21 | .color = _grey3,
22 | .metalness = _black1,
23 | .roughness = _white1,
24 | .ior = 1.5,
25 | });
26 | }
27 |
28 | HdMoonshine* _moonshine;
29 |
30 | // some defaults
31 | ImageHandle _black3;
32 | ImageHandle _black1;
33 | ImageHandle _upNormal;
34 | ImageHandle _grey3;
35 | ImageHandle _white1;
36 | MaterialHandle _defaultMaterial;
37 | };
38 |
39 | PXR_NAMESPACE_CLOSE_SCOPE
--------------------------------------------------------------------------------
/src/bin/hydra/renderPass.cpp:
--------------------------------------------------------------------------------
1 | #include "moonshine.h"
2 |
3 | #include "camera.hpp"
4 | #include "renderPass.hpp"
5 | #include "renderBuffer.hpp"
6 | #include "renderDelegate.hpp"
7 |
8 | #include
9 | #include
10 |
11 | PXR_NAMESPACE_OPEN_SCOPE
12 |
13 | HdMoonshineRenderPass::HdMoonshineRenderPass(HdRenderIndex *index, HdRprimCollection const &collection) : HdRenderPass(index, collection) {}
14 |
15 | HdMoonshineRenderPass::~HdMoonshineRenderPass() {}
16 |
17 | void HdMoonshineRenderPass::_Execute(HdRenderPassStateSharedPtr const& renderPassState, TfTokenVector const& renderTags) {
18 | for (const auto aov : renderPassState->GetAovBindings()) {
19 | if (aov.aovName == HdAovTokens->color) {
20 | HdRenderIndex* renderIndex = GetRenderIndex();
21 | HdMoonshineRenderDelegate* renderDelegate = static_cast(renderIndex->GetRenderDelegate());
22 | const HdMoonshineCamera* camera = static_cast(renderPassState->GetCamera());
23 |
24 | HdMoonshineRenderBuffer* renderBuffer = static_cast(aov.renderBuffer);
25 | HdMoonshineRender(renderDelegate->_moonshine, renderBuffer->_sensor, camera->_handle);
26 | }
27 | }
28 | }
29 |
30 | PXR_NAMESPACE_CLOSE_SCOPE
--------------------------------------------------------------------------------
/src/bin/hydra/renderPass.hpp:
--------------------------------------------------------------------------------
1 | #pragma once
2 |
3 | #include "pxr/pxr.h"
4 | #include "pxr/imaging/hd/renderPass.h"
5 |
6 | PXR_NAMESPACE_OPEN_SCOPE
7 |
8 | class HdMoonshineRenderPass final : public HdRenderPass
9 | {
10 | public:
11 | HdMoonshineRenderPass(HdRenderIndex *index, HdRprimCollection const &collection);
12 | ~HdMoonshineRenderPass() override;
13 | protected:
14 | void _Execute(HdRenderPassStateSharedPtr const& renderPassState, TfTokenVector const& renderTags) override;
15 | };
16 |
17 | PXR_NAMESPACE_CLOSE_SCOPE
18 |
--------------------------------------------------------------------------------
/src/bin/hydra/rendererPlugin.cpp:
--------------------------------------------------------------------------------
1 | #include "rendererPlugin.hpp"
2 | #include "renderDelegate.hpp"
3 |
4 | #include "pxr/imaging/hd/rendererPluginRegistry.h"
5 |
6 | PXR_NAMESPACE_OPEN_SCOPE
7 |
8 | TF_REGISTRY_FUNCTION(TfType) {
9 | HdRendererPluginRegistry::Define();
10 | }
11 |
12 | HdRenderDelegate* HdMoonshinePlugin::CreateRenderDelegate() {
13 | return new HdMoonshineRenderDelegate();
14 | }
15 |
16 | HdRenderDelegate* HdMoonshinePlugin::CreateRenderDelegate(HdRenderSettingsMap const& settingsMap)
17 | {
18 | return new HdMoonshineRenderDelegate(settingsMap);
19 | }
20 |
21 | void HdMoonshinePlugin::DeleteRenderDelegate(HdRenderDelegate *renderDelegate) {
22 | delete renderDelegate;
23 | }
24 |
25 | bool HdMoonshinePlugin::IsSupported(bool gpuEnabled) const {
26 | return gpuEnabled;
27 | }
28 |
29 | PXR_NAMESPACE_CLOSE_SCOPE
--------------------------------------------------------------------------------
/src/bin/hydra/rendererPlugin.hpp:
--------------------------------------------------------------------------------
1 | #pragma once
2 |
3 | #include "pxr/imaging/hd/rendererPlugin.h"
4 |
5 | PXR_NAMESPACE_OPEN_SCOPE
6 |
7 | class HdMoonshinePlugin final : public HdRendererPlugin
8 | {
9 | public:
10 | HdMoonshinePlugin() = default;
11 | ~HdMoonshinePlugin() = default;
12 | HdRenderDelegate* CreateRenderDelegate() override;
13 | HdRenderDelegate* CreateRenderDelegate(HdRenderSettingsMap const& settingsMap) override;
14 | void DeleteRenderDelegate(HdRenderDelegate *renderDelegate) override;
15 | bool IsSupported(bool gpuEnabled) const override;
16 | private:
17 | HdMoonshinePlugin(const HdMoonshinePlugin&) = delete;
18 | HdMoonshinePlugin &operator =(const HdMoonshinePlugin&) = delete;
19 | };
20 |
21 | PXR_NAMESPACE_CLOSE_SCOPE
22 |
--------------------------------------------------------------------------------
/src/bin/offline.zig:
--------------------------------------------------------------------------------
1 | const std = @import("std");
2 | const vk = @import("vulkan");
3 |
4 | const engine = @import("engine");
5 |
6 | const core = engine.core;
7 | const VulkanContext = core.VulkanContext;
8 | const Encoder = core.Encoder;
9 | const Pipeline = engine.hrtsystem.pipeline.StandardPipeline;
10 | const Scene = engine.hrtsystem.Scene;
11 |
12 | const vk_helpers = core.vk_helpers;
13 | const exr = engine.fileformats.exr;
14 |
15 | const vector = engine.vector;
16 | const F32x3 = vector.Vec3(f32);
17 | const Mat4x3 = vector.Mat4x3(f32);
18 |
19 | const Config = struct {
20 | in_filepath: []const u8, // must be gltf/glb
21 | out_filepath: []const u8, // must be exr
22 | skybox_filepath: []const u8, // must be exr
23 | spp: u32,
24 | extent: vk.Extent2D,
25 |
26 | fn fromCli(allocator: std.mem.Allocator) !Config {
27 | const args = try std.process.argsAlloc(allocator);
28 | defer std.process.argsFree(allocator, args);
29 | if (args.len < 4) return error.BadArgs;
30 |
31 | const in_filepath = args[1];
32 | if (!std.mem.eql(u8, std.fs.path.extension(in_filepath), ".glb") and !std.mem.eql(u8, std.fs.path.extension(in_filepath), ".gltf")) return error.OnlySupportsGltfInput;
33 |
34 | const skybox_filepath = args[2];
35 | if (!std.mem.eql(u8, std.fs.path.extension(skybox_filepath), ".exr")) return error.OnlySupportsExrSkybox;
36 |
37 | const out_filepath = args[3];
38 | if (!std.mem.eql(u8, std.fs.path.extension(out_filepath), ".exr")) return error.OnlySupportsExrOutput;
39 |
40 | const spp = if (args.len > 4) try std.fmt.parseInt(u32, args[4], 10) else 16;
41 |
42 | return Config {
43 | .in_filepath = try allocator.dupe(u8, in_filepath),
44 | .out_filepath = try allocator.dupe(u8, out_filepath),
45 | .skybox_filepath = try allocator.dupe(u8, skybox_filepath),
46 | .spp = spp,
47 | .extent = vk.Extent2D { .width = 1280, .height = 720 }, // TODO: cli
48 | };
49 | }
50 |
51 | fn destroy(self: Config, allocator: std.mem.Allocator) void {
52 | allocator.free(self.in_filepath);
53 | allocator.free(self.out_filepath);
54 | allocator.free(self.skybox_filepath);
55 | }
56 | };
57 |
58 | const IntervalLogger = struct {
59 | last_time: std.time.Instant,
60 |
61 | fn start() !IntervalLogger {
62 | return IntervalLogger {
63 | .last_time = try std.time.Instant.now(),
64 | };
65 | }
66 |
67 | fn log(self: *IntervalLogger, state: []const u8) !void {
68 | const new_time = try std.time.Instant.now();
69 | const elapsed = new_time.since(self.last_time);
70 | const ms = elapsed / std.time.ns_per_ms;
71 | const s = ms / std.time.ms_per_s;
72 | try std.io.getStdOut().writer().print("{}.{:0>3} seconds to {s}\n", .{ s, ms, state });
73 | self.last_time = new_time;
74 | }
75 | };
76 |
77 | pub fn main() !void {
78 | var logger = try IntervalLogger.start();
79 |
80 | var gpa = std.heap.GeneralPurposeAllocator(.{}) {};
81 | defer _ = gpa.deinit();
82 | const allocator = gpa.allocator();
83 |
84 | const config = try Config.fromCli(allocator);
85 | defer config.destroy(allocator);
86 |
87 | const context = try VulkanContext.create(allocator, "offline", &.{}, &engine.hrtsystem.required_device_extensions, &engine.hrtsystem.required_device_features, null);
88 | defer context.destroy(allocator);
89 |
90 | var encoder = try Encoder.create(&context, "main");
91 | defer encoder.destroy(&context);
92 |
93 | try logger.log("set up initial state");
94 |
95 | try encoder.begin();
96 | var scene = try Scene.fromGltfExr(&context, allocator, &encoder, config.in_filepath, config.skybox_filepath, config.extent);
97 | defer scene.destroy(&context, allocator);
98 | try encoder.submitAndIdleUntilDone(&context);
99 |
100 | try logger.log("load world");
101 |
102 | var pipeline = try Pipeline.create(&context, allocator, .{}, .{ scene.background.sampler }, .{ scene.world.materials.textures.descriptor_layout.handle, scene.world.constant_specta.descriptor_layout.handle });
103 | defer pipeline.destroy(&context);
104 |
105 | try logger.log("create pipeline");
106 |
107 | const output_buffer = try core.mem.DownloadBuffer([4]f32).create(&context, scene.camera.sensors.items[0].extent.width * scene.camera.sensors.items[0].extent.height, "output");
108 | defer output_buffer.destroy(&context);
109 |
110 | // actual ray tracing
111 | {
112 | try encoder.begin();
113 |
114 | // prepare our stuff
115 | scene.camera.sensors.items[0].recordPrepareForCapture(encoder.buffer, .{ .compute_shader_bit = true }, .{});
116 |
117 | // bind our stuff
118 | pipeline.recordBindPipeline(encoder.buffer);
119 | pipeline.recordBindAdditionalDescriptorSets(encoder.buffer, .{ scene.world.materials.textures.descriptor_set, scene.world.constant_specta.descriptor_set });
120 | pipeline.recordPushDescriptors(encoder.buffer, scene.pushDescriptors(0, 0, 0));
121 |
122 | for (0..config.spp) |sample_count| {
123 | // push our stuff
124 | pipeline.recordPushConstants(encoder.buffer, scene.pushConstants(0, 0, 0, scene.camera.sensors.items[0].sample_count));
125 |
126 | // trace our stuff
127 | pipeline.recordDispatchThreads2D(encoder.buffer, scene.camera.sensors.items[0].extent);
128 |
129 | // if not last invocation, need barrier cuz we write to images
130 | if (sample_count != config.spp) {
131 | encoder.buffer.pipelineBarrier2(&vk.DependencyInfo {
132 | .image_memory_barrier_count = 1,
133 | .p_image_memory_barriers = &[_]vk.ImageMemoryBarrier2 {
134 | .{
135 | .src_stage_mask = .{ .compute_shader_bit = true },
136 | .src_access_mask = if (sample_count == 0) .{ .shader_storage_write_bit = true } else .{ .shader_storage_write_bit = true, .shader_storage_read_bit = true },
137 | .dst_stage_mask = .{ .compute_shader_bit = true },
138 | .dst_access_mask = .{ .shader_storage_write_bit = true, .shader_storage_read_bit = true },
139 | .old_layout = .general,
140 | .new_layout = .general,
141 | .src_queue_family_index = vk.QUEUE_FAMILY_IGNORED,
142 | .dst_queue_family_index = vk.QUEUE_FAMILY_IGNORED,
143 | .image = scene.camera.sensors.items[0].image.handle,
144 | .subresource_range = .{
145 | .aspect_mask = .{ .color_bit = true },
146 | .base_mip_level = 0,
147 | .level_count = 1,
148 | .base_array_layer = 0,
149 | .layer_count = vk.REMAINING_ARRAY_LAYERS,
150 | },
151 | }
152 | },
153 | });
154 | }
155 | scene.camera.sensors.items[0].sample_count += 1;
156 | }
157 |
158 | // copy our stuff
159 | scene.camera.sensors.items[0].recordPrepareForCopy(encoder.buffer, .{ .compute_shader_bit = true }, .{ .copy_bit = true });
160 |
161 | // copy rendered image to host-visible staging buffer
162 | encoder.copyImageToBuffer(scene.camera.sensors.items[0].image.handle, .transfer_src_optimal, scene.camera.sensors.items[0].extent, output_buffer.handle);
163 |
164 | try encoder.submitAndIdleUntilDone(&context);
165 | }
166 |
167 | try logger.log("render");
168 |
169 | // now done with GPU stuff/all rendering; can write from output buffer to exr
170 | try exr.helpers.Rgba2D.save(exr.helpers.Rgba2D { .ptr = output_buffer.mapped, .extent = scene.camera.sensors.items[0].extent }, allocator, config.out_filepath);
171 |
172 | try logger.log("write exr");
173 | }
174 |
--------------------------------------------------------------------------------
/src/bin/online/ObjectPicker.zig:
--------------------------------------------------------------------------------
1 | const std = @import("std");
2 | const vk = @import("vulkan");
3 | const shaders = @import("shaders");
4 |
5 | const engine = @import("engine");
6 | const core = engine.core;
7 | const VulkanContext = core.VulkanContext;
8 | const Encoder = core.Encoder;
9 |
10 | const hrtsystem = engine.hrtsystem;
11 | const Sensor = hrtsystem.Sensor;
12 | const Camera = hrtsystem.CameraManager;
13 |
14 | const F32x2 = engine.vector.Vec2(f32);
15 |
16 | const Self = @This();
17 |
18 | pub const Intersection = extern struct {
19 | instance_index: i32, // -1 if clicked background
20 | geometry_index: u32,
21 | primitive_index: u32,
22 | barycentrics: F32x2,
23 |
24 | pub fn toClickedObject(self: Intersection) ?ClickedObject {
25 | if (self.instance_index == -1) {
26 | return null;
27 | } else {
28 | return ClickedObject {
29 | .instance_index = @intCast(self.instance_index),
30 | .geometry_index = self.geometry_index,
31 | .primitive_index = self.primitive_index,
32 | .barycentrics = self.barycentrics,
33 | };
34 | }
35 | }
36 | };
37 |
38 | pub const ClickedObject = struct {
39 | instance_index: u32,
40 | geometry_index: u32,
41 | primitive_index: u32,
42 | barycentrics: F32x2,
43 | };
44 |
45 | pub const Pipeline = core.pipeline.Pipeline(.{
46 | .local_size = vk.Extent3D { .width = 1, .height = 1, .depth = 1 },
47 | .shader_source = shaders.input,
48 | .PushConstants = extern struct {
49 | camera: Camera.Camera,
50 | aspect_ratio: f32,
51 | click_position: F32x2,
52 | },
53 | .PushSetBindings = struct {
54 | tlas: vk.AccelerationStructureKHR,
55 | output_image: core.pipeline.StorageImage,
56 | click_data: core.mem.BufferSlice(Intersection),
57 | },
58 | });
59 |
60 | buffer: core.mem.Buffer(Intersection, .{ .host_visible_bit = true, .host_coherent_bit = true }, .{ .storage_buffer_bit = true }),
61 | pipeline: Pipeline,
62 |
63 | encoder: Encoder,
64 | ready_fence: vk.Fence,
65 |
66 | pub fn create(vc: *const VulkanContext, allocator: std.mem.Allocator) !Self {
67 | const buffer = try core.mem.Buffer(Intersection, .{ .host_visible_bit = true, .host_coherent_bit = true }, .{ .storage_buffer_bit = true }).create(vc, 1, "object picker");
68 | errdefer buffer.destroy(vc);
69 |
70 | var pipeline = try Pipeline.create(vc, allocator, .{}, .{}, .{});
71 | errdefer pipeline.destroy(vc);
72 |
73 | var encoder = try Encoder.create(vc, "object picker");
74 | errdefer encoder.destroy(vc);
75 |
76 | const ready_fence = try vc.device.createFence(&.{
77 | .flags = .{},
78 | }, null);
79 | errdefer vc.device.destroyFence(ready_fence, null);
80 |
81 | return Self {
82 | .buffer = buffer,
83 | .pipeline = pipeline,
84 |
85 | .encoder = encoder,
86 | .ready_fence = ready_fence,
87 | };
88 | }
89 |
90 | pub fn getClickedObject(self: *Self, vc: *const VulkanContext, accel: vk.AccelerationStructureKHR, normalized_coords: F32x2, camera: Camera.Camera, sensor: Sensor) !?ClickedObject {
91 | // begin
92 | try self.encoder.begin();
93 |
94 | // bind pipeline + sets
95 | self.pipeline.recordBindPipeline(self.encoder.buffer);
96 | self.pipeline.recordPushDescriptors(self.encoder.buffer, Pipeline.PushSetBindings {
97 | .tlas = accel,
98 | .output_image = .{ .view = sensor.image.view },
99 | .click_data = self.buffer.deviceSlice(),
100 | });
101 |
102 | self.pipeline.recordPushConstants(self.encoder.buffer, .{ .camera = camera, .aspect_ratio = sensor.aspectRatio(), .click_position = normalized_coords });
103 |
104 | // trace rays
105 | self.pipeline.recordDispatchThreads1D(self.encoder.buffer, 1);
106 |
107 | // end
108 | try self.encoder.submit(vc.queue, .{ .fence = self.ready_fence });
109 |
110 | _ = try vc.device.waitForFences(1, (&self.ready_fence)[0..1], vk.TRUE, std.math.maxInt(u64));
111 | try vc.device.resetFences(1, (&self.ready_fence)[0..1]);
112 | try vc.device.resetCommandPool(self.encoder.pool, .{});
113 |
114 | return self.buffer.hostSlice()[0].toClickedObject();
115 | }
116 |
117 | pub fn destroy(self: *Self, vc: *const VulkanContext) void {
118 | self.buffer.destroy(vc);
119 | self.pipeline.destroy(vc);
120 | self.encoder.destroy(vc);
121 | vc.device.destroyFence(self.ready_fence, null);
122 | }
123 |
--------------------------------------------------------------------------------
/src/bin/online/SyncCopier.zig:
--------------------------------------------------------------------------------
1 | // TODO: this should really be killed
2 | //
3 | // really stupid and inefficient way to get stuff from device-local buffers/images back to CPU
4 | // should not be used for "real" transfers that benefit from efficiency
5 | //
6 | // more-so designed for ease-of-use for debugging and inspecting stuff that doesn't usually need to be inspected
7 |
8 | const core = @import("engine").core;
9 | const VulkanContext = core.VulkanContext;
10 | const Encoder = core.Encoder;
11 |
12 | const std = @import("std");
13 | const vk = @import("vulkan");
14 |
15 | buffer: core.mem.DownloadBuffer(u8),
16 |
17 | encoder: Encoder,
18 | ready_fence: vk.Fence,
19 |
20 | const Self = @This();
21 |
22 | pub fn create(vc: *const VulkanContext, max_bytes: u32) !Self {
23 | const buffer = try core.mem.DownloadBuffer(u8).create(vc, max_bytes, "sync copier");
24 |
25 | var encoder = try Encoder.create(vc, "sync copier");
26 | errdefer encoder.destroy(vc);
27 |
28 | const ready_fence = try vc.device.createFence(&.{}, null);
29 |
30 | return Self {
31 | .buffer = buffer,
32 |
33 | .encoder = encoder,
34 | .ready_fence = ready_fence,
35 | };
36 | }
37 |
38 | pub fn copyBufferItem(self: *Self, vc: *const VulkanContext, comptime BufferInner: type, buffer: vk.Buffer, idx: vk.DeviceSize) !BufferInner {
39 | std.debug.assert(@sizeOf(BufferInner) <= self.buffer.len);
40 |
41 | try self.encoder.begin();
42 | self.encoder.copyBuffer(buffer, self.buffer.handle, &[_]vk.BufferCopy {
43 | .{
44 | .src_offset = @sizeOf(BufferInner) * idx,
45 | .dst_offset = 0,
46 | .size = @sizeOf(BufferInner),
47 | }
48 | });
49 | try self.encoder.submit(vc.queue, .{ .fence = self.ready_fence });
50 |
51 | _ = try vc.device.waitForFences(1, (&self.ready_fence)[0..1], vk.TRUE, std.math.maxInt(u64));
52 | try vc.device.resetFences(1, (&self.ready_fence)[0..1]);
53 | try vc.device.resetCommandPool(self.encoder.pool, .{});
54 |
55 | return @as(*BufferInner, @ptrCast(@alignCast(self.buffer.mapped))).*;
56 | }
57 |
58 | pub fn copyImagePixel(self: *Self, vc: *const VulkanContext, comptime PixelType: type, src_image: vk.Image, src_layout: vk.ImageLayout, offset: vk.Offset3D) !PixelType {
59 | std.debug.assert(@sizeOf(PixelType) <= self.buffer.len);
60 |
61 | try self.encoder.begin();
62 | self.encoder.buffer.copyImageToBuffer(src_image, src_layout, self.buffer.handle, 1, (&vk.BufferImageCopy {
63 | .buffer_offset = 0,
64 | .buffer_row_length = 0,
65 | .buffer_image_height = 0,
66 | .image_subresource = vk.ImageSubresourceLayers {
67 | .aspect_mask = .{ .color_bit = true },
68 | .mip_level = 0,
69 | .base_array_layer = 0,
70 | .layer_count = 1,
71 | },
72 | .image_offset = offset,
73 | .image_extent = vk.Extent3D {
74 | .width = 1,
75 | .height = 1,
76 | .depth = 1,
77 | }
78 | })[0..1]);
79 | try self.encoder.submit(vc.queue, .{ .fence = self.ready_fence });
80 |
81 | _ = try vc.device.waitForFences(1, (&self.ready_fence)[0..1], vk.TRUE, std.math.maxInt(u64));
82 | try vc.device.resetFences(1, (&self.ready_fence)[0..1]);
83 | try vc.device.resetCommandPool(self.encoder.pool, .{});
84 |
85 | return @as(*PixelType, @ptrCast(@alignCast(self.buffer.mapped))).*;
86 | }
87 |
88 | pub fn destroy(self: *Self, vc: *const VulkanContext) void {
89 | self.buffer.destroy(vc);
90 | self.encoder.destroy(vc);
91 | vc.device.destroyFence(self.ready_fence, null);
92 | }
93 |
--------------------------------------------------------------------------------
/src/bin/online/input.hlsl:
--------------------------------------------------------------------------------
1 | #include "../../lib/hrtsystem/shaders/camera.hlsl"
2 | #include "../../lib/hrtsystem/shaders/intersection.hlsl"
3 |
4 | [[vk::binding(0, 0)]] RaytracingAccelerationStructure TLAS;
5 | [[vk::binding(1, 0)]] RWTexture2D dOutputImage;
6 | [[vk::binding(2, 0)]] RWStructuredBuffer click_data;
7 |
8 | struct PushConsts {
9 | Camera camera;
10 | float2 coords;
11 | };
12 | [[vk::push_constant]] PushConsts pushConsts;
13 |
14 | [numthreads(1, 1, 1)]
15 | void main() {
16 | Camera camera = pushConsts.camera;
17 | // make camera have perfect focus
18 | camera.thinLens.focusDistance = 1.0f;
19 | camera.thinLens.aperture = 0.0f;
20 | Ray ray = pushConsts.camera.generateRay(pushConsts.coords, float2(0, 0));
21 |
22 | click_data[0] = Intersection::find(TLAS, ray);
23 | }
24 |
--------------------------------------------------------------------------------
/src/lib/README.md:
--------------------------------------------------------------------------------
1 | # Core engine
2 |
3 | ### Systems
4 | Each "system" is a relatively self-contained... system that is designed to do a specific thing.
5 | Systems require certain instance/device functions to be enabled in order to use them, so they export them from their root file.
6 |
7 | #### Current systems
8 | * `core` - basic things like context, allocation, commonly used utilities, etc
9 | * `hrtsystem` - general hardware-RT ray traced rendering tasks and scene representation
10 | * `displaysystem` - platform-agnostic abstraction for rendering images and managing e.g., swapchain, double-buffering, presentation, etc
11 |
--------------------------------------------------------------------------------
/src/lib/Window.zig:
--------------------------------------------------------------------------------
1 | // Thin wrapper for GLFW atm
2 |
3 | const c = @import("glfw");
4 | const vk = @import("vulkan");
5 | const std = @import("std");
6 |
7 | const Error = error {
8 | InitFail,
9 | WindowCreateFail,
10 | SurfaceCreateFail,
11 | };
12 |
13 | const Self = @This();
14 |
15 | handle: *c.GLFWwindow,
16 |
17 | pub fn create(width: u32, height: u32, app_name: [*:0]const u8) Error!Self {
18 | const Callback = struct {
19 | fn callback(code: c_int, message: [*c]const u8) callconv(.C) void {
20 | std.log.warn("glfw: {}: {s}", .{code, message});
21 | }
22 | };
23 | _ = c.glfwSetErrorCallback(Callback.callback);
24 |
25 | if (c.glfwInit() != c.GLFW_TRUE) return Error.InitFail;
26 |
27 | c.glfwWindowHint(c.GLFW_CLIENT_API, c.GLFW_NO_API);
28 |
29 | const handle = c.glfwCreateWindow(@intCast(width), @intCast(height), app_name, null, null) orelse {
30 | c.glfwTerminate();
31 | return Error.WindowCreateFail;
32 | };
33 |
34 | return Self {
35 | .handle = handle,
36 | };
37 | }
38 |
39 | pub fn getPhysicalDevicePresentationSupport(instance: vk.Instance, device: vk.PhysicalDevice, idx: u32) bool {
40 | return c.glfwGetPhysicalDevicePresentationSupport(instance, device, idx) == c.GLFW_TRUE;
41 | }
42 |
43 | // abusing the fact a little bit that we know that glfw always asks for two extensions
44 | pub fn getRequiredInstanceExtensions(self: *const Self) [2][*:0]const u8 {
45 | _ = self; // ensure we're initialized
46 |
47 | var glfw_extension_count: u32 = 0;
48 | const extensions = c.glfwGetRequiredInstanceExtensions(&glfw_extension_count);
49 | std.debug.assert(glfw_extension_count == 2);
50 |
51 | return @as([*]const [*:0]const u8, @ptrCast(extensions))[0..2].*;
52 | }
53 |
54 | pub fn shouldClose(self: *const Self) bool {
55 | return c.glfwWindowShouldClose(self.handle) == c.GLFW_TRUE;
56 | }
57 |
58 | pub const Mode = enum(c_int) {
59 | normal = c.GLFW_CURSOR_NORMAL,
60 | hidden = c.GLFW_CURSOR_HIDDEN,
61 | disabled = c.GLFW_CURSOR_DISABLED,
62 | };
63 | pub fn setCursorMode(self: *const Self, value: Mode) void {
64 | c.glfwSetInputMode(self.handle, c.GLFW_CURSOR, @intFromEnum(value));
65 | }
66 |
67 | pub fn pollEvents(self: *const Self) void {
68 | _ = self; // just ensure we're initialized
69 | c.glfwPollEvents();
70 | }
71 |
72 | pub fn createSurface(self: *const Self, instance: vk.Instance) Error!vk.SurfaceKHR {
73 | var surface: vk.SurfaceKHR = undefined;
74 | if (c.glfwCreateWindowSurface(instance, self.handle, null, &surface) != vk.Result.success) return Error.SurfaceCreateFail; // this could give more details
75 | return surface;
76 | }
77 |
78 | pub fn getExtent(self: *const Self) vk.Extent2D {
79 | var width: c_int = undefined;
80 | var height: c_int = undefined;
81 | c.glfwGetFramebufferSize(self.handle, &width, &height);
82 | return vk.Extent2D {
83 | .width = @intCast(width),
84 | .height = @intCast(height),
85 | };
86 | }
87 |
88 | pub fn destroy(self: *const Self) void {
89 | c.glfwDestroyWindow(self.handle);
90 | c.glfwTerminate();
91 | }
92 |
--------------------------------------------------------------------------------
/src/lib/core/DestructionQueue.zig:
--------------------------------------------------------------------------------
1 | const std = @import("std");
2 | const vk = @import("vulkan");
3 |
4 | const core = @import("./core.zig");
5 | const VulkanContext = core.VulkanContext;
6 | const DeviceBuffer = core.Allocator.DeviceBuffer;
7 |
8 | // type erased Vulkan object
9 | const Destruction = struct {
10 | object_type: vk.ObjectType,
11 | destroyee: u64,
12 |
13 | fn destroy(self: Destruction, vc: *const VulkanContext) void {
14 | switch (self.object_type) {
15 | .swapchain_khr => if (comptime @hasField(VulkanContext.Device.Wrapper.Dispatch, "vkDestroySwapchainKHR")) vc.device.destroySwapchainKHR(@enumFromInt(self.destroyee), null) else unreachable,
16 | .pipeline_layout => vc.device.destroyPipelineLayout(@enumFromInt(self.destroyee), null),
17 | .pipeline => vc.device.destroyPipeline(@enumFromInt(self.destroyee), null),
18 | .buffer => vc.device.destroyBuffer(@enumFromInt(self.destroyee), null),
19 | .image_view => vc.device.destroyImageView(@enumFromInt(self.destroyee), null),
20 | .image => vc.device.destroyImage(@enumFromInt(self.destroyee), null),
21 | .device_memory => vc.device.freeMemory(@enumFromInt(self.destroyee), null),
22 | .acceleration_structure_khr => vc.device.destroyAccelerationStructureKHR(@enumFromInt(self.destroyee), null),
23 | else => unreachable, // TODO
24 | }
25 | }
26 | };
27 |
28 | // TODO: this should be an SoA type of thing like list((tag, list(union)))
29 | queue: std.ArrayListUnmanaged(Destruction) = .{},
30 |
31 | const Self = @This();
32 |
33 | // works on any type exclusively made up of Vulkan objects and primitives
34 | pub fn append(self: *Self, allocator: std.mem.Allocator, item: anytype) !void {
35 | const T = @TypeOf(item);
36 |
37 | if (comptime @typeInfo(T) == .@"struct") {
38 | inline for (@typeInfo(T).@"struct".fields) |field| {
39 | if (field.type != void and @typeInfo(field.type) != .int) {
40 | try self.append(allocator, @field(item, field.name));
41 | }
42 | }
43 | } else {
44 | try self.queue.append(allocator, Destruction {
45 | .object_type = comptime core.vk_helpers.typeToObjectType(T),
46 | .destroyee = @intFromEnum(item),
47 | });
48 | }
49 | }
50 |
51 | pub fn clear(self: *Self, vc: *const VulkanContext) void {
52 | for (self.queue.items) |*item| item.destroy(vc);
53 | self.queue.clearRetainingCapacity();
54 | }
55 |
56 | pub fn destroy(self: *Self, vc: *const VulkanContext, allocator: std.mem.Allocator) void {
57 | self.clear(vc);
58 | self.queue.deinit(allocator);
59 | }
60 |
--------------------------------------------------------------------------------
/src/lib/core/Image.zig:
--------------------------------------------------------------------------------
1 | const vk = @import("vulkan");
2 | const std = @import("std");
3 |
4 | const engine = @import("../engine.zig");
5 | const core = engine.core;
6 | const vk_helpers = core.vk_helpers;
7 | const VulkanContext = core.VulkanContext;
8 |
9 | const Self = @This();
10 |
11 | handle: vk.Image,
12 | view: vk.ImageView,
13 | memory: vk.DeviceMemory,
14 |
15 | pub fn create(vc: *const VulkanContext, size: vk.Extent2D, usage: vk.ImageUsageFlags, format: vk.Format, with_mips: bool, name: [:0]const u8) !Self {
16 | const extent = vk.Extent3D {
17 | .width = size.width,
18 | .height = size.height,
19 | .depth = 1,
20 | };
21 |
22 | const image_create_info = vk.ImageCreateInfo {
23 | .image_type = if (extent.height == 1 and extent.width != 1) .@"1d" else .@"2d",
24 | .format = format,
25 | .extent = extent,
26 | .mip_levels = if (with_mips) std.math.log2(@max(extent.width, extent.height, extent.depth)) + 1 else 1,
27 | .array_layers = 1,
28 | .samples = .{ .@"1_bit" = true },
29 | .tiling = .optimal,
30 | .usage = usage,
31 | .sharing_mode = .exclusive,
32 | .queue_family_index_count = 0,
33 | .p_queue_family_indices = undefined,
34 | .initial_layout = .undefined,
35 | };
36 |
37 | const handle = try vc.device.createImage(&image_create_info, null);
38 | errdefer vc.device.destroyImage(handle, null);
39 | if (name.len != 0) try vk_helpers.setDebugName(vc.device, handle, name);
40 |
41 | const mem_requirements = vc.device.getImageMemoryRequirements(handle);
42 |
43 | const memory = try vc.device.allocateMemory(&.{
44 | .allocation_size = mem_requirements.size,
45 | .memory_type_index = try vc.findMemoryType(mem_requirements.memory_type_bits, .{ .device_local_bit = true }),
46 | }, null);
47 | errdefer vc.device.freeMemory(memory, null);
48 |
49 | try vc.device.bindImageMemory(handle, memory, 0);
50 |
51 | const view_create_info = vk.ImageViewCreateInfo {
52 | .flags = .{},
53 | .image = handle,
54 | .view_type = if (extent.height == 1 and extent.width != 1) vk.ImageViewType.@"1d" else vk.ImageViewType.@"2d",
55 | .format = format,
56 | .components = .{
57 | .r = .identity,
58 | .g = .identity,
59 | .b = .identity,
60 | .a = .identity,
61 | },
62 | .subresource_range = .{
63 | .aspect_mask = .{ .color_bit = true },
64 | .base_mip_level = 0,
65 | .level_count = vk.REMAINING_MIP_LEVELS,
66 | .base_array_layer = 0,
67 | .layer_count = vk.REMAINING_ARRAY_LAYERS,
68 | },
69 | };
70 |
71 | const view = try vc.device.createImageView(&view_create_info, null);
72 | errdefer vc.device.destroyImageView(view, null);
73 |
74 | return Self {
75 | .memory = memory,
76 | .handle = handle,
77 | .view = view,
78 | };
79 | }
80 |
81 | pub fn destroy(self: Self, vc: *const VulkanContext) void {
82 | vc.device.destroyImageView(self.view, null);
83 | vc.device.destroyImage(self.handle, null);
84 | vc.device.freeMemory(self.memory, null);
85 | }
86 |
--------------------------------------------------------------------------------
/src/lib/core/core.zig:
--------------------------------------------------------------------------------
1 | pub const VulkanContext = @import("./VulkanContext.zig");
2 | pub const Encoder = @import("./Encoder.zig");
3 | pub const DestructionQueue = @import("./DestructionQueue.zig");
4 | pub const Image = @import("./Image.zig");
5 |
6 | pub const mem = @import("./mem.zig");
7 | pub const descriptor = @import("./descriptor.zig");
8 | pub const pipeline = @import("./pipeline.zig");
9 |
10 | pub const vk_helpers = @import("./vk_helpers.zig");
11 |
--------------------------------------------------------------------------------
/src/lib/core/descriptor.zig:
--------------------------------------------------------------------------------
1 | const std = @import("std");
2 | const vk = @import("vulkan");
3 | const core = @import("../engine.zig").core;
4 | const vk_helpers = core.vk_helpers;
5 | const VulkanContext = core.VulkanContext;
6 |
7 | pub const Binding = struct {
8 | descriptor_type: vk.DescriptorType,
9 | descriptor_count: u32,
10 | stage_flags: vk.ShaderStageFlags,
11 | binding_flags: vk.DescriptorBindingFlags = .{},
12 | };
13 |
14 | pub fn DescriptorLayout(comptime bindings: []const Binding, comptime layout_flags: vk.DescriptorSetLayoutCreateFlags, comptime max_sets: comptime_int, comptime debug_name: [*:0]const u8) type {
15 | return struct {
16 | handle: vk.DescriptorSetLayout,
17 | pool: if (is_push_descriptor) void else vk.DescriptorPool,
18 |
19 | const Self = @This();
20 |
21 | pub const sampler_count = blk: {
22 | var count = 0;
23 | for (bindings) |binding| {
24 | if (binding.descriptor_type == .sampler or binding.descriptor_type == .combined_image_sampler) {
25 | count += binding.descriptor_count;
26 | }
27 | }
28 | break :blk count;
29 | };
30 |
31 | const is_push_descriptor = layout_flags.contains(.{ .push_descriptor_bit = true });
32 |
33 | pub fn create(vc: *const VulkanContext, samplers: [sampler_count]vk.Sampler) !Self {
34 | var vk_bindings: [bindings.len]vk.DescriptorSetLayoutBinding = undefined;
35 | const vk_binding_flags = blk: {
36 | comptime var vk_binding_flags: [bindings.len]vk.DescriptorBindingFlags = undefined;
37 | comptime var samplers_so_far = 0;
38 | inline for (bindings, &vk_bindings, &vk_binding_flags, 0..) |binding, *vk_binding, *vk_binding_flag, binding_index| {
39 | vk_binding.* = vk.DescriptorSetLayoutBinding {
40 | .binding = binding_index,
41 | .descriptor_type = binding.descriptor_type,
42 | .descriptor_count = binding.descriptor_count,
43 | .stage_flags = binding.stage_flags,
44 | };
45 | if (binding.descriptor_type == .sampler or binding.descriptor_type == .combined_image_sampler) {
46 | vk_binding.p_immutable_samplers = samplers[samplers_so_far..samplers_so_far + binding.descriptor_count];
47 | samplers_so_far += binding.descriptor_count;
48 | }
49 | vk_binding_flag.* = binding.binding_flags;
50 | }
51 | break :blk vk_binding_flags;
52 | };
53 | const create_info = vk.DescriptorSetLayoutCreateInfo {
54 | .flags = layout_flags,
55 | .binding_count = bindings.len,
56 | .p_bindings = &vk_bindings,
57 | .p_next = (&vk.DescriptorSetLayoutBindingFlagsCreateInfo {
58 | .binding_count = bindings.len,
59 | .p_binding_flags = &vk_binding_flags,
60 | })[0..1],
61 | };
62 | const handle = try vc.device.createDescriptorSetLayout(&create_info, null);
63 | errdefer vc.device.destroyDescriptorSetLayout(handle, null);
64 |
65 | try vk_helpers.setDebugName(vc.device, handle, debug_name);
66 |
67 | const pool_sizes = blk: {
68 | comptime var pool_sizes: [bindings.len]vk.DescriptorPoolSize = undefined;
69 | comptime for (&pool_sizes, bindings) |*pool_size, binding| {
70 | pool_size.* = .{
71 | .type = binding.descriptor_type,
72 | .descriptor_count = binding.descriptor_count * max_sets,
73 | };
74 | };
75 | break :blk pool_sizes;
76 | };
77 |
78 | return Self {
79 | .handle = handle,
80 | .pool = if (is_push_descriptor) {} else try vc.device.createDescriptorPool(&.{
81 | .flags = .{},
82 | .max_sets = max_sets,
83 | .pool_size_count = pool_sizes.len,
84 | .p_pool_sizes = &pool_sizes,
85 | }, null),
86 | };
87 | }
88 |
89 | pub usingnamespace if (is_push_descriptor) struct {} else struct {
90 | pub fn allocate_set(self: *const Self, vc: *const VulkanContext, writes: [bindings.len]vk.WriteDescriptorSet) !vk.DescriptorSet {
91 | var descriptor_set: vk.DescriptorSet = undefined;
92 |
93 | try vc.device.allocateDescriptorSets(&vk.DescriptorSetAllocateInfo {
94 | .descriptor_pool = self.pool,
95 | .descriptor_set_count = 1,
96 | .p_set_layouts = (&self.handle)[0..1],
97 | }, (&descriptor_set)[0..1]);
98 |
99 | // avoid writing descriptors in certain invalid states:
100 | // 1. descriptor type is sampler (only immutable samplers are supported)
101 | // 2. descriptor count is zero
102 | // 3. buffer is vk_null_handle
103 | var valid_writes: [bindings.len]vk.WriteDescriptorSet = undefined;
104 | var valid_write_count: u32 = 0;
105 | for (writes) |write| {
106 | if (write.descriptor_type == .sampler) continue;
107 | if (write.descriptor_count == 0) continue;
108 | switch (write.descriptor_type) {
109 | .storage_buffer => if (write.p_buffer_info[0].buffer == .null_handle) continue,
110 | else => {},
111 | }
112 |
113 | valid_writes[valid_write_count] = write;
114 | valid_writes[valid_write_count].dst_set = descriptor_set;
115 | valid_write_count += 1;
116 | }
117 |
118 | vc.device.updateDescriptorSets(valid_write_count, &valid_writes, 0, undefined);
119 |
120 | return descriptor_set;
121 | }
122 | };
123 |
124 | pub fn destroy(self: *Self, vc: *const VulkanContext) void {
125 | if (!is_push_descriptor) vc.device.destroyDescriptorPool(self.pool, null);
126 | vc.device.destroyDescriptorSetLayout(self.handle, null);
127 | }
128 | };
129 | }
130 |
--------------------------------------------------------------------------------
/src/lib/core/shader_source.zig:
--------------------------------------------------------------------------------
1 | name: [:0]const u8,
2 | code: []const u32,
3 | command: []const []const u8,
--------------------------------------------------------------------------------
/src/lib/core/vk_helpers.zig:
--------------------------------------------------------------------------------
1 | const vk = @import("vulkan");
2 | const std = @import("std");
3 | const VulkanContext = @import("./VulkanContext.zig");
4 | const build_options = @import("build_options");
5 |
6 | pub fn typeToObjectType(comptime in: type) vk.ObjectType {
7 | return switch(in) {
8 | vk.DescriptorSetLayout => .descriptor_set_layout,
9 | vk.DescriptorSet => .descriptor_set,
10 | vk.Buffer => .buffer,
11 | vk.CommandBuffer => .command_buffer,
12 | vk.Image => .image,
13 | vk.Pipeline => .pipeline,
14 | vk.PipelineLayout => .pipeline_layout,
15 | vk.ShaderModule => .shader_module,
16 | vk.DeviceMemory => .device_memory,
17 | vk.SwapchainKHR => .swapchain_khr,
18 | vk.ImageView => .image_view,
19 | vk.AccelerationStructureKHR => .acceleration_structure_khr,
20 | else => @compileError("unknown type " ++ @typeName(in)), // TODO: add more
21 | };
22 | }
23 |
24 | pub fn setDebugName(device: VulkanContext.Device, object: anytype, name: [*:0]const u8) !void {
25 | // TODO: this does not make sense, debug names can be needed separately from validation
26 | // for other types of debugging/profiling
27 | const want_debug_names = build_options.vk_validation != .ignore;
28 |
29 | if ((comptime want_debug_names) and object != .null_handle) {
30 | try device.setDebugUtilsObjectNameEXT(&.{
31 | .object_type = comptime typeToObjectType(@TypeOf(object)),
32 | .object_handle = @intFromEnum(object),
33 | .p_object_name = name,
34 | });
35 | }
36 | }
37 |
38 | pub fn texelBlockSize(format: vk.Format) vk.DeviceSize {
39 | return switch (format) {
40 | .r8_unorm => 1,
41 | .r8g8_unorm => 2,
42 | .r8g8b8a8_srgb, .r32_sfloat => 4,
43 | .r32g32_sfloat => 8,
44 | .r32g32b32a32_sfloat => 16,
45 | else => unreachable, // TODO
46 | };
47 | }
48 |
49 | pub fn typeToFormat(comptime in: type) vk.Format {
50 | const vector = @import("../engine.zig").vector;
51 | return switch (in) {
52 | u8 => .r8_unorm,
53 | vector.Vec2(u8) => .r8g8_unorm,
54 | vector.Vec4(u8) => .r8g8b8a8_srgb, // TODO: not assume srgb
55 | f32 => .r32_sfloat,
56 | vector.Vec2(f32) => .r32g32_sfloat,
57 | vector.Vec4(f32) => .r32g32b32a32_sfloat,
58 | else => unreachable, // TODO
59 | };
60 | }
61 |
62 |
63 | fn GetVkSliceInternal(comptime func: anytype) type {
64 | const params = @typeInfo(@TypeOf(func)).@"fn".params;
65 | const OptionalType = params[params.len - 1].type.?;
66 | const PtrType = @typeInfo(OptionalType).optional.child;
67 | return @typeInfo(PtrType).pointer.child;
68 | }
69 |
70 | fn GetVkSliceBoundedReturn(comptime max_size: comptime_int, comptime func: anytype) type {
71 | const FuncReturn = @typeInfo(@TypeOf(func)).@"fn".return_type.?;
72 |
73 | const BaseReturn = std.BoundedArray(GetVkSliceInternal(func), max_size);
74 |
75 | return switch (@typeInfo(FuncReturn)) {
76 | .error_union => |err| err.error_set!BaseReturn,
77 | .void => BaseReturn,
78 | else => unreachable,
79 | };
80 | }
81 |
82 | // wrapper around functions like vkGetPhysicalDeviceSurfacePresentModesKHR
83 | // they have some initial args, then a length and output address
84 | // this is designed for small stuff to allocate on stack, where size maximum size is comptime known
85 | //
86 | // in optimized modes, only calls the actual function only once, based on the assumption that the caller correctly predicts max possible return size.
87 | // in safe modes, checks that the actual size does not exceed the maximum size, which may call the function more than once
88 | pub fn getVkSliceBounded(comptime max_size: comptime_int, func: anytype, partial_args: anytype) GetVkSliceBoundedReturn(max_size, func) {
89 | const T = GetVkSliceInternal(func);
90 |
91 | var buffer: [max_size]T = undefined;
92 | var len: u32 = max_size;
93 |
94 | const always_succeeds = @typeInfo(@TypeOf(func)).@"fn".return_type == void;
95 | if (always_succeeds) {
96 | if (std.debug.runtime_safety) {
97 | var actual_len: u32 = undefined;
98 | @call(.auto, func, partial_args ++ .{ &actual_len, null });
99 | std.debug.assert(actual_len < max_size);
100 | }
101 | @call(.auto, func, partial_args ++ .{ &len, &buffer });
102 | } else {
103 | const res = try @call(.auto, func, partial_args ++ .{ &len, &buffer });
104 | std.debug.assert(res != .incomplete);
105 | }
106 |
107 | return std.BoundedArray(T, max_size) {
108 | .buffer = buffer,
109 | .len = @intCast(len),
110 | };
111 | }
112 |
113 | pub fn getVkSlice(allocator: std.mem.Allocator, func: anytype, partial_args: anytype) ![]GetVkSliceInternal(func) {
114 | const T = GetVkSliceInternal(func);
115 |
116 | var len: u32 = undefined;
117 | _ = try @call(.auto, func, partial_args ++ .{ &len, null });
118 |
119 | const buffer = try allocator.alloc(T, len);
120 |
121 | const res = try @call(.auto, func, partial_args ++ .{ &len, buffer.ptr });
122 | std.debug.assert(res != .incomplete);
123 |
124 | return buffer;
125 | }
126 |
--------------------------------------------------------------------------------
/src/lib/displaysystem/Display.zig:
--------------------------------------------------------------------------------
1 | const std = @import("std");
2 | const vk = @import("vulkan");
3 |
4 | const engine = @import("../engine.zig");
5 | const core = engine.core;
6 | const VulkanContext = core.VulkanContext;
7 | const DestructionQueue = core.DestructionQueue;
8 | const Encoder = core.Encoder;
9 | const vk_helpers = core.vk_helpers;
10 |
11 | const Swapchain = engine.displaysystem.Swapchain;
12 |
13 | const metrics = @import("build_options").vk_metrics;
14 |
15 | // DOUBLE BUFFER STRATEGY
16 | // This uses the strategy that I think is most decent while being the simplest to implement.
17 | //
18 | // We have two "frames in flight", frame A and frame B, which cycle.
19 | // While frame A is being processed by the GPU and displayed, we are recording frame B.
20 | // Essentially this means that while frame A is doing GPU work, frame B is doing CPU work.
21 | //
22 | // This means that for e.g., vertex animation, we don't need to keep two separate GPU vertex buffers,
23 | // as just one GPU task is being done at a time. We just queue the update in command buffer B via a
24 | // buffer copy or update command, which doesn't affect the work of command buffer A.
25 | //
26 | // The catch here is that we must keep two copies of non-static GPU-accesible host data, as when we are updating
27 | // the data for command buffer B, command buffer A could be using that data in another operation.
28 | // In many cases, this can be avoided by using vkCmdUpdateBuffer rather than a transfer operation.
29 | pub const frames_in_flight = 2;
30 |
31 | const Self = @This();
32 |
33 | frames: [frames_in_flight]Frame,
34 | frame_index: u8,
35 |
36 | swapchain: Swapchain,
37 |
38 | timestamp_period: if (metrics) f32 else void,
39 | last_frame_time_ns: if (metrics) f64 else void,
40 |
41 | // uses initial_extent as the render extent -- that is, the buffer that is actually being rendered into, irrespective of window size
42 | // then during rendering the render buffer is blitted into the swapchain images
43 | pub fn create(vc: *const VulkanContext, initial_extent: vk.Extent2D, surface: vk.SurfaceKHR) !Self {
44 | var swapchain = try Swapchain.create(vc, initial_extent, surface);
45 | errdefer swapchain.destroy(vc);
46 |
47 | var frames: [frames_in_flight]Frame = undefined;
48 | inline for (&frames, 0..) |*frame, i| {
49 | frame.* = try Frame.create(vc, std.fmt.comptimePrint("frame {}", .{i}), i != 0);
50 | }
51 |
52 | const timestamp_period = if (metrics) blk: {
53 | var properties = vk.PhysicalDeviceProperties2 {
54 | .properties = undefined,
55 | };
56 |
57 | vc.instance.getPhysicalDeviceProperties2(vc.physical_device.handle, &properties);
58 |
59 | break :blk properties.properties.limits.timestamp_period;
60 | } else {};
61 |
62 | return Self {
63 | .swapchain = swapchain,
64 | .frames = frames,
65 | .frame_index = 0,
66 |
67 | .timestamp_period = timestamp_period,
68 | .last_frame_time_ns = if (metrics) 0.0 else {},
69 | };
70 | }
71 |
72 | pub fn destroy(self: *Self, vc: *const VulkanContext) void {
73 | self.swapchain.destroy(vc);
74 | inline for (&self.frames) |*frame| {
75 | frame.destroy(vc);
76 | }
77 | }
78 |
79 | pub fn startFrame(self: *Self, vc: *const VulkanContext) !*Encoder {
80 | const frame = &self.frames[self.frame_index];
81 |
82 | _ = try self.swapchain.acquireNextImage(vc, frame.image_acquired);
83 |
84 | try frame.encoder.begin();
85 |
86 | if (metrics) frame.encoder.buffer.writeTimestamp2(.{ .top_of_pipe_bit = true }, frame.query_pool, 0);
87 |
88 | return &frame.encoder;
89 | }
90 |
91 | pub fn recreate(self: *Self, vc: *const VulkanContext, new_extent: vk.Extent2D) !vk.SwapchainKHR {
92 | return try self.swapchain.recreate(vc, new_extent);
93 | }
94 |
95 | pub fn endFrame(self: *Self, vc: *const VulkanContext) !vk.Result {
96 | const result = blk: {
97 | const frame = self.frames[self.frame_index];
98 |
99 | if (metrics) frame.encoder.buffer.writeTimestamp2(.{ .bottom_of_pipe_bit = true }, frame.query_pool, 1);
100 |
101 | try frame.encoder.submit(vc.queue, .{
102 | .wait_semaphore_infos = &[_]vk.SemaphoreSubmitInfoKHR {
103 | .{
104 | .semaphore = frame.image_acquired,
105 | .value = 0,
106 | .stage_mask = .{ .color_attachment_output_bit = true },
107 | .device_index = 0,
108 | }
109 | },
110 | .signal_semaphore_infos = &[_]vk.SemaphoreSubmitInfoKHR {
111 | .{
112 | .semaphore = frame.command_completed,
113 | .value = 0,
114 | .stage_mask = .{ .color_attachment_output_bit = true },
115 | .device_index = 0,
116 | }
117 | },
118 | .fence = frame.fence,
119 | });
120 |
121 | // TODO: the synchronization here is not sufficient, as the semaphore
122 | // may still be in use by the previous usage of this swapchain image.
123 | // fix this with VK_EXT_swapchain_maintenance1 once it becomes
124 | // more widely supported.
125 | break :blk self.swapchain.present(vc, frame.command_completed);
126 | };
127 |
128 | self.frame_index = (self.frame_index + 1) % frames_in_flight;
129 |
130 | // wait for next frame to ensure CPU is not too far ahead of GPU
131 | var next_frame = &self.frames[self.frame_index];
132 | _ = try vc.device.waitForFences(1, (&next_frame.fence)[0..1], vk.TRUE, std.math.maxInt(u64));
133 |
134 | // collect metrics if enabled
135 | if (metrics) {
136 | var timestamps: [2]u64 = undefined;
137 | const query_result = try vc.device.getQueryPoolResults(next_frame.query_pool, 0, 2, 2 * @sizeOf(u64), ×tamps, @sizeOf(u64), .{.@"64_bit" = true });
138 | self.last_frame_time_ns = if (query_result == .success) @as(f64, @floatFromInt(timestamps[1] - timestamps[0])) * self.timestamp_period else std.math.nan(f64);
139 | }
140 |
141 | // reset resources associated with next frame so it is ready for use on next startFrame
142 | try next_frame.reset(vc);
143 |
144 | return result;
145 | }
146 |
147 | const Frame = struct {
148 | image_acquired: vk.Semaphore,
149 | command_completed: vk.Semaphore,
150 | fence: vk.Fence,
151 |
152 | encoder: Encoder,
153 |
154 | query_pool: if (metrics) vk.QueryPool else void,
155 |
156 | fn create(vc: *const VulkanContext, name: [*:0]const u8, fence_initially_signaled: bool) !Frame {
157 | const image_acquired = try vc.device.createSemaphore(&.{}, null);
158 | errdefer vc.device.destroySemaphore(image_acquired, null);
159 |
160 | const command_completed = try vc.device.createSemaphore(&.{}, null);
161 | errdefer vc.device.destroySemaphore(command_completed, null);
162 |
163 | const fence = try vc.device.createFence(&.{
164 | .flags = if (fence_initially_signaled) .{ .signaled_bit = true } else .{},
165 | }, null);
166 |
167 | var encoder = try Encoder.create(vc, name);
168 | errdefer encoder.destroy(vc);
169 |
170 | const query_pool = if (metrics) try vc.device.createQueryPool(&.{
171 | .query_type = .timestamp,
172 | .query_count = 2,
173 | }, null) else undefined;
174 | errdefer if (metrics) vc.device.destroyQueryPool(query_pool, null);
175 | if (metrics) vc.device.resetQueryPool(query_pool, 0, 2);
176 |
177 | return Frame {
178 | .image_acquired = image_acquired,
179 | .command_completed = command_completed,
180 | .fence = fence,
181 |
182 | .encoder = encoder,
183 |
184 | .query_pool = query_pool,
185 | };
186 | }
187 |
188 | // frame must not be in use
189 | fn reset(self: *Frame, vc: *const VulkanContext) !void {
190 | try vc.device.resetFences(1, (&self.fence)[0..1]);
191 |
192 | if (metrics) {
193 | vc.device.resetQueryPool(self.query_pool, 0, 2);
194 | }
195 | try vc.device.resetCommandPool(self.encoder.pool, .{});
196 | self.encoder.clearResources(vc);
197 | }
198 |
199 | fn destroy(self: *Frame, vc: *const VulkanContext) void {
200 | vc.device.destroySemaphore(self.image_acquired, null);
201 | vc.device.destroySemaphore(self.command_completed, null);
202 | vc.device.destroyFence(self.fence, null);
203 | self.encoder.destroy(vc);
204 | if (metrics) vc.device.destroyQueryPool(self.query_pool, null);
205 | }
206 | };
207 |
--------------------------------------------------------------------------------
/src/lib/displaysystem/Swapchain.zig:
--------------------------------------------------------------------------------
1 | const std = @import("std");
2 | const vk = @import("vulkan");
3 |
4 | const engine = @import("../engine.zig");
5 | const VulkanContext = engine.core.VulkanContext;
6 | const vk_helpers = engine.core.vk_helpers;
7 |
8 | const SwapchainError = error {
9 | InvalidSurfaceDimensions,
10 | };
11 |
12 | pub const max_image_count = 3;
13 |
14 | surface: vk.SurfaceKHR,
15 | handle: vk.SwapchainKHR,
16 | images: std.BoundedArray(vk.Image, max_image_count),
17 | image_index: u32,
18 | extent: vk.Extent2D,
19 | image_format: vk.Format,
20 |
21 | const Self = @This();
22 |
23 | // takes ownership of surface
24 | pub fn create(vc: *const VulkanContext, ideal_extent: vk.Extent2D, surface: vk.SurfaceKHR) !Self {
25 | return try createFromOld(vc, ideal_extent, surface, .null_handle);
26 | }
27 |
28 | fn createFromOld(vc: *const VulkanContext, ideal_extent: vk.Extent2D, surface: vk.SurfaceKHR, old_handle: vk.SwapchainKHR) !Self {
29 | const settings = try SwapSettings.find(vc, ideal_extent, surface);
30 |
31 | const queue_family_indices = [_]u32{ vc.physical_device.queue_family_index };
32 |
33 | const handle = try vc.device.createSwapchainKHR(&.{
34 | .surface = surface,
35 | .min_image_count = settings.image_count,
36 | .image_format = settings.format.format,
37 | .image_color_space = settings.format.color_space,
38 | .image_extent = settings.extent,
39 | .image_array_layers = 1,
40 | .image_usage = .{ .color_attachment_bit = true, .transfer_dst_bit = true },
41 | .image_sharing_mode = settings.image_sharing_mode,
42 | .queue_family_index_count = @as(u32, @intCast(queue_family_indices.len)),
43 | .p_queue_family_indices = &queue_family_indices,
44 | .pre_transform = settings.pre_transform,
45 | .composite_alpha = .{ .opaque_bit_khr = true },
46 | .present_mode = settings.present_mode,
47 | .clipped = vk.TRUE,
48 | .old_swapchain = old_handle,
49 | }, null);
50 | errdefer vc.device.destroySwapchainKHR(handle, null);
51 |
52 | const images = try vk_helpers.getVkSliceBounded(max_image_count, @TypeOf(vc.device).getSwapchainImagesKHR, .{ vc.device, handle });
53 |
54 | return Self {
55 | .surface = surface,
56 | .handle = handle,
57 | .images = images,
58 | .image_format = settings.format.format,
59 | .image_index = undefined, // this is odd, is it the best?
60 | .extent = settings.extent,
61 | };
62 | }
63 |
64 | pub fn currentImage(self: *const Self) vk.Image {
65 | return self.images.get(self.image_index);
66 | }
67 |
68 | // returns old handle
69 | pub fn recreate(self: *Self, vc: *const VulkanContext, extent: vk.Extent2D) !vk.SwapchainKHR {
70 | const old = self.handle;
71 | self.* = try createFromOld(vc, extent, self.surface, self.handle);
72 | return old;
73 | }
74 |
75 | pub fn acquireNextImage(self: *Self, vc: *const VulkanContext, semaphore: vk.Semaphore) !vk.Result {
76 | const result = try vc.device.acquireNextImage2KHR(&.{
77 | .swapchain = self.handle,
78 | .timeout = std.math.maxInt(u64),
79 | .semaphore = semaphore,
80 | .fence = .null_handle,
81 | .device_mask = 1,
82 | });
83 | self.image_index = result.image_index;
84 | return result.result;
85 | }
86 |
87 | pub fn present(self: *const Self, vc: *const VulkanContext, semaphore: vk.Semaphore) !vk.Result {
88 | return try vc.queue.presentKHR(&vk.PresentInfoKHR {
89 | .wait_semaphore_count = 1,
90 | .p_wait_semaphores = (&semaphore)[0..1],
91 | .swapchain_count = 1,
92 | .p_swapchains = (&self.handle)[0..1],
93 | .p_image_indices = (&self.image_index)[0..1],
94 | });
95 | }
96 |
97 | pub fn destroy(self: *const Self, vc: *const VulkanContext) void {
98 | vc.device.destroySwapchainKHR(self.handle, null);
99 | vc.instance.destroySurfaceKHR(self.surface, null);
100 | }
101 |
102 | const SwapSettings = struct {
103 | format: vk.SurfaceFormatKHR,
104 | present_mode: vk.PresentModeKHR,
105 | image_count: u32,
106 | image_sharing_mode: vk.SharingMode,
107 | pre_transform: vk.SurfaceTransformFlagsKHR,
108 | extent: vk.Extent2D,
109 |
110 | // updates mutable extent
111 | pub fn find(vc: *const VulkanContext, extent: vk.Extent2D, surface: vk.SurfaceKHR) !SwapSettings {
112 | const caps = try vc.instance.getPhysicalDeviceSurfaceCapabilitiesKHR(vc.physical_device.handle, surface);
113 |
114 | return SwapSettings {
115 | .format = try findFormat(vc, surface),
116 | .present_mode = try findPresentMode(vc, surface),
117 | .image_count = if (caps.max_image_count == 0) caps.min_image_count + 1 else @min(caps.min_image_count + 1, caps.max_image_count),
118 | .image_sharing_mode = .exclusive,
119 | .pre_transform = caps.current_transform,
120 | .extent = try calculateExtent(extent, caps),
121 | };
122 | }
123 |
124 | pub fn findPresentMode(vc: *const VulkanContext, surface: vk.SurfaceKHR) !vk.PresentModeKHR {
125 | const ideal = vk.PresentModeKHR.immediate_khr;
126 |
127 | const present_modes = (try vk_helpers.getVkSliceBounded(8, @TypeOf(vc.instance).getPhysicalDeviceSurfacePresentModesKHR, .{ vc.instance, vc.physical_device.handle, surface })).slice();
128 |
129 | for (present_modes) |present_mode| {
130 | if (std.meta.eql(present_mode, ideal)) {
131 | return ideal;
132 | }
133 | }
134 |
135 | return present_modes[0];
136 | }
137 |
138 | pub fn findFormat(vc: *const VulkanContext, surface: vk.SurfaceKHR) !vk.SurfaceFormatKHR {
139 | const ideal = vk.SurfaceFormatKHR {
140 | .format = .b8g8r8a8_srgb,
141 | .color_space = .srgb_nonlinear_khr,
142 | };
143 |
144 | const formats = (try vk_helpers.getVkSliceBounded(8, @TypeOf(vc.instance).getPhysicalDeviceSurfaceFormatsKHR, .{ vc.instance, vc.physical_device.handle, surface })).slice();
145 |
146 | for (formats) |format| {
147 | if (std.meta.eql(format, ideal)) {
148 | return ideal;
149 | }
150 | }
151 |
152 | return formats[0];
153 | }
154 |
155 | pub fn calculateExtent(extent: vk.Extent2D, caps: vk.SurfaceCapabilitiesKHR) !vk.Extent2D {
156 | if (caps.current_extent.width == std.math.maxInt(u32)) {
157 | return vk.Extent2D {
158 | .width = std.math.clamp(extent.width, caps.min_image_extent.width, caps.max_image_extent.width),
159 | .height = std.math.clamp(extent.height, caps.min_image_extent.height, caps.max_image_extent.height),
160 | };
161 | } else {
162 | return caps.current_extent;
163 | }
164 | if (extent.height == 0 and extent.width == 0) {
165 | return SwapchainError.InvalidSurfaceDimensions;
166 | }
167 | }
168 | };
169 |
--------------------------------------------------------------------------------
/src/lib/displaysystem/displaysystem.zig:
--------------------------------------------------------------------------------
1 | pub const Display = @import("./Display.zig");
2 | pub const Swapchain = @import("./Swapchain.zig");
3 |
4 | const vk = @import("vulkan");
5 |
6 | pub const required_device_extensions = [_][*:0]const u8{
7 | vk.extensions.khr_swapchain.name,
8 | };
9 |
--------------------------------------------------------------------------------
/src/lib/engine.zig:
--------------------------------------------------------------------------------
1 | // systems
2 | pub const hrtsystem = @import("./hrtsystem/hrtsystem.zig");
3 | pub const fileformats = @import("./fileformats/fileformats.zig");
4 | pub const displaysystem = @import("./displaysystem/displaysystem.zig");
5 | pub const core = @import("./core/core.zig");
6 | pub const Window = @import("./Window.zig");
7 | pub const gui = @import("./gui/gui.zig");
8 |
9 | // utils
10 | pub const vector = @import("./vector.zig");
11 |
--------------------------------------------------------------------------------
/src/lib/fileformats/dds.zig:
--------------------------------------------------------------------------------
1 | const vk = @import("vulkan");
2 | const std = @import("std");
3 |
4 | // https://docs.microsoft.com/en-us/windows/win32/direct3ddds/dx-graphics-dds-reference
5 | pub const PixelFormat = extern struct {
6 | size: u32, // expected to be 32
7 | flags: u32, // flags for pixel format
8 | four_cc: u32, // four characters indicating format type - for us, expected to be "DX10"
9 | rgb_bit_count: u32, // "Number of bits in an RGB (possibly including alpha) format"
10 | r_bit_mask: u32, // mask for red data
11 | g_bit_mask: u32, // mask for green data
12 | b_bit_mask: u32, // mask for blue data
13 | a_bit_mask: u32, // mask for alpha data
14 |
15 | fn verify(self: *const PixelFormat) void {
16 | std.debug.assert(self.size == 32);
17 | std.debug.assert(self.four_cc == comptime std.mem.readIntNative(u32, "DX10"));
18 | }
19 | };
20 |
21 | pub const Header = extern struct {
22 | size: u32, // expected to be 124
23 | flags: u32, // flags indicating which fields below have valid data
24 | height: u32, // height of image
25 | width: u32, // width of image
26 | pitch_or_linear_size: u32, // "The pitch or number of bytes per scan line in an uncompressed texture"
27 | depth: u32, // depth of image
28 | mip_map_count: u32, // number of mipmaps in image
29 | reserved_1: [11]u32, // unused
30 | ddspf: PixelFormat, // details about pixels
31 | caps: u32, // "Specifies the complexity of the surfaces stored."
32 | caps2: u32, // "Additional detail about the surfaces stored."
33 | caps3: u32, // unused
34 | caps4: u32, // unused
35 | reserved_2: u32, // unused
36 |
37 | fn verify(self: *const Header) void {
38 | std.debug.assert(self.size == 124);
39 | self.ddspf.verify();
40 | }
41 | };
42 |
43 | pub const HeaderDXT10 = extern struct {
44 | dxgi_format: u32, // the surface pixel format; this should probably be an enum
45 | resource_dimension: u32, // texture dimension
46 | misc_flag: u32, // misc flags
47 | array_size: u32, // number of elements in array
48 | misc_flags_2: u32, // additional metadata
49 | };
50 |
51 | pub const FileInfo = extern struct {
52 | magic: u32, // expected to be 542327876, hex for "DDS"
53 | header: Header, // first header
54 | header_10: HeaderDXT10, // second header
55 |
56 | // just some random sanity checks to make sure we actually are getting a DDS file
57 | pub fn verify(self: *const FileInfo) void {
58 | std.debug.assert(self.magic == 542327876);
59 | self.header.verify();
60 | }
61 |
62 | pub fn getExtent(self: *const FileInfo) vk.Extent2D {
63 | return vk.Extent2D {
64 | .width = self.header.width,
65 | .height = self.header.height,
66 | };
67 | }
68 |
69 | pub fn getFormat(self: *const FileInfo) vk.Format {
70 | return switch (self.header_10.dxgi_format) {
71 | 71 => .bc1_rgb_srgb_block,
72 | 80 => .bc4_unorm_block,
73 | 83 => .bc5_unorm_block,
74 | 95 => .bc6h_ufloat_block,
75 | 96 => .bc6h_sfloat_block,
76 | else => unreachable, // TODO
77 | };
78 | }
79 |
80 | pub fn isCubemap(self: *const FileInfo) bool {
81 | return self.header_10.misc_flag == 4;
82 | }
83 | };
--------------------------------------------------------------------------------
/src/lib/fileformats/exr.zig:
--------------------------------------------------------------------------------
1 | // at top level this just has ziggified C API -- but also have a higher-level helpers namespace
2 |
3 | const c = @import("tinyexr");
4 | const std = @import("std");
5 |
6 | pub const TinyExrError = error {
7 | InvalidMagicNumber,
8 | InvalidExrVersion,
9 | InvalidArgument,
10 | InvalidData,
11 | InvalidFile,
12 | InvalidParameter,
13 | CantOpenFile,
14 | UnsupportedFormat,
15 | InvalidHeader,
16 | UnsupportedFeature,
17 | CantWriteFile,
18 | SerializationFailed,
19 | LayerNotFound,
20 | DataTooLarge,
21 | };
22 |
23 | pub const Image = c.EXRImage;
24 | pub const Header = c.EXRHeader;
25 | pub const Version = c.EXRVersion;
26 | pub const ChannelInfo = c.EXRChannelInfo;
27 |
28 | pub const PixelType = enum(c_int) {
29 | uint = 0,
30 | half = 1,
31 | float = 2,
32 | };
33 |
34 | fn intToStatus(err_code: c_int) TinyExrError!void {
35 | return switch (err_code) {
36 | 0 => {},
37 | -1 => TinyExrError.InvalidMagicNumber,
38 | -2 => TinyExrError.InvalidExrVersion,
39 | -3 => TinyExrError.InvalidArgument,
40 | -4 => TinyExrError.InvalidData,
41 | -5 => TinyExrError.InvalidFile,
42 | -6 => TinyExrError.InvalidParameter,
43 | -7 => TinyExrError.CantOpenFile,
44 | -8 => TinyExrError.UnsupportedFormat,
45 | -9 => TinyExrError.InvalidHeader,
46 | -10 => TinyExrError.UnsupportedFeature,
47 | -11 => TinyExrError.CantWriteFile,
48 | -12 => TinyExrError.SerializationFailed,
49 | -13 => TinyExrError.LayerNotFound,
50 | -14 => TinyExrError.DataTooLarge,
51 | else => unreachable,
52 | };
53 | }
54 |
55 | pub fn RetType(comptime T: type) type {
56 | return switch (@typeInfo(T).@"fn".return_type.?) {
57 | c_int => void,
58 | usize => usize,
59 | else => unreachable, // TODO
60 | };
61 | }
62 |
63 | pub fn handleError(func: anytype, args: anytype) TinyExrError!RetType(@TypeOf(func)) {
64 | var err_message: [*c]const u8 = undefined;
65 | const ref = &err_message;
66 | const new_args = args ++ .{ ref };
67 | switch (@typeInfo(@TypeOf(func)).@"fn".return_type.?) {
68 | c_int => intToStatus(@call(.auto, func, new_args)) catch |err| {
69 | std.log.err("tinyexr: {}: {s}", .{ err, err_message });
70 | c.FreeEXRErrorMessage(err_message);
71 | return err;
72 | },
73 | usize => {
74 | const n = @call(.auto, func, new_args);
75 | if (n == 0) {
76 | std.log.err("tinyexr: {s}", .{ err_message });
77 | c.FreeEXRErrorMessage(err_message);
78 | return TinyExrError.InvalidData; // want to return some sort of error here but not sure what
79 | } else return n;
80 | },
81 | else => comptime unreachable,
82 | }
83 | }
84 |
85 | pub fn saveExrImageToFile(image: *const Image, header: *const Header, filename: [*:0]const u8) TinyExrError!void {
86 | return handleError(c.SaveEXRImageToFile, .{ image, header, filename });
87 | }
88 |
89 | pub fn saveEXRImageToMemory(image: *const Image, header: *const Header, memory: *[*c]u8) TinyExrError!usize {
90 | return handleError(c.SaveEXRImageToMemory, .{ image, header, memory });
91 | }
92 |
93 | pub fn loadExrImageFromFile(image: *Image, header: *const Header, filename: [*:0]const u8) TinyExrError!void {
94 | return handleError(c.LoadExrImageFromFile, .{ image, header, filename });
95 | }
96 |
97 | pub fn parseExrHeaderFromFile(header: *Header, version: *const Version, filename: [*:0]const u8) TinyExrError!void {
98 | return handleError(c.ParseEXRHeaderFromFile, .{ header, version, filename });
99 | }
100 |
101 | pub fn parseExrVersionFromFile(version: *Version, filename: [*:0]const u8) TinyExrError!void {
102 | return intToStatus(c.ParseEXRVersionFromFile(version, filename));
103 | }
104 |
105 | pub fn loadEXR(out_rgba: *[*c]f32, width: *c_int, height: *c_int, filename: [*:0]const u8) TinyExrError!void {
106 | return handleError(c.LoadEXR, .{ out_rgba, width, height, filename });
107 | }
108 |
109 | pub fn loadEXRFromMemory(out_rgba: *[*c]f32, width: *c_int, height: *c_int, memory: [*]const u8, size: usize) TinyExrError!void {
110 | return handleError(c.LoadEXRFromMemory, .{ out_rgba, width, height, memory, size });
111 | }
112 |
113 | pub fn initExrHeader(header: *Header) void {
114 | c.InitEXRHeader(header);
115 | }
116 |
117 | pub fn initExrImage(image: *Image) void {
118 | c.InitEXRImage(image);
119 | }
120 |
121 | // things that don't actually correspond to things in tinyexr but are convenient for this project
122 | pub const helpers = struct {
123 | const vk = @import("vulkan");
124 |
125 | // RGB image stored in memory as RGBA buffer
126 | pub const Rgba2D = struct {
127 | ptr: [*][4]f32,
128 | extent: vk.Extent2D,
129 |
130 | pub fn asSlice(self: Rgba2D) [][4]f32 {
131 | var slice: [][4]f32 = undefined;
132 | slice.ptr = self.ptr;
133 | slice.len = self.extent.width * self.extent.height;
134 | return slice;
135 | }
136 |
137 | pub fn save(self: Rgba2D, allocator: std.mem.Allocator, out_filename: []const u8) !void {
138 | const channel_count = 3;
139 |
140 | var header: Header = undefined;
141 | initExrHeader(&header);
142 |
143 | var image: Image = undefined;
144 | initExrImage(&image);
145 |
146 | const pixel_count = self.extent.width * self.extent.height;
147 |
148 | const ImageChannels = std.MultiArrayList(struct {
149 | r: f32,
150 | g: f32,
151 | b: f32,
152 | });
153 | var image_channels = ImageChannels {};
154 | defer image_channels.deinit(allocator);
155 |
156 | try image_channels.ensureUnusedCapacity(allocator, pixel_count);
157 |
158 | for (0..pixel_count) |i| {
159 | image_channels.appendAssumeCapacity(.{
160 | .r = self.asSlice()[i][0],
161 | .g = self.asSlice()[i][1],
162 | .b = self.asSlice()[i][2],
163 | });
164 | }
165 |
166 | const image_channels_slice = image_channels.slice();
167 | image.num_channels = channel_count;
168 | image.images = @constCast(&[3][*c]u8 {
169 | image_channels_slice.ptrs[2],
170 | image_channels_slice.ptrs[1],
171 | image_channels_slice.ptrs[0],
172 | });
173 | image.width = @intCast(self.extent.width);
174 | image.height = @intCast(self.extent.height);
175 |
176 | const header_channels = try allocator.alloc(ChannelInfo, channel_count);
177 | defer allocator.free(header_channels);
178 |
179 | const pixel_types = try allocator.alloc(c_int, channel_count);
180 | defer allocator.free(pixel_types);
181 | const requested_pixel_types = try allocator.alloc(c_int, channel_count);
182 | defer allocator.free(requested_pixel_types);
183 |
184 | header.num_channels = channel_count;
185 | header.channels = header_channels.ptr;
186 |
187 | header.channels[0].name[0] = 'B';
188 | header.channels[0].name[1] = 0;
189 | header.channels[1].name[0] = 'G';
190 | header.channels[1].name[1] = 0;
191 | header.channels[2].name[0] = 'R';
192 | header.channels[2].name[1] = 0;
193 |
194 | header.pixel_types = pixel_types.ptr;
195 | header.requested_pixel_types = requested_pixel_types.ptr;
196 |
197 | inline for (0..channel_count) |i| {
198 | header.pixel_types[i] = @intFromEnum(PixelType.float);
199 | header.requested_pixel_types[i] = @intFromEnum(PixelType.float);
200 | }
201 |
202 | var data: [*c]u8 = undefined;
203 | const file_size = try saveEXRImageToMemory(&image, &header, &data);
204 | defer std.c.free(data);
205 | try std.fs.cwd().writeFile(.{ .sub_path = out_filename, .data = data[0..file_size]});
206 | }
207 |
208 | pub fn load(allocator: std.mem.Allocator, filename: []const u8) !Rgba2D {
209 | const file_content = try std.fs.cwd().readFileAlloc(allocator, filename, std.math.maxInt(usize));
210 | defer allocator.free(file_content);
211 |
212 | var out_rgba: [*c]f32 = undefined;
213 | var width: c_int = undefined;
214 | var height: c_int = undefined;
215 | try loadEXRFromMemory(&out_rgba, &width, &height, file_content.ptr, file_content.len);
216 | defer std.c.free(out_rgba);
217 | const malloc_slice = Rgba2D {
218 | .ptr = @ptrCast(out_rgba),
219 | .extent = vk.Extent2D {
220 | .width = @intCast(width),
221 | .height = @intCast(height),
222 | },
223 | };
224 | const out = Rgba2D {
225 | .ptr = (try allocator.dupe([4]f32, malloc_slice.asSlice())).ptr, // copy into zig allocator
226 | .extent = malloc_slice.extent,
227 | };
228 | return out;
229 | }
230 | };
231 | };
232 |
--------------------------------------------------------------------------------
/src/lib/fileformats/fileformats.zig:
--------------------------------------------------------------------------------
1 | pub const exr = @import("exr.zig");
2 | pub const wuffs = @import("wuffs.zig");
3 | pub const dds = @import("dds.zig");
4 |
--------------------------------------------------------------------------------
/src/lib/fileformats/wuffs.zig:
--------------------------------------------------------------------------------
1 | const c = @import("wuffs");
2 | const std = @import("std");
3 |
4 | fn ToWuffsSliceType(comptime T: type) type {
5 | return switch (T) {
6 | u8 => c.wuffs_base__slice_u8,
7 | u16 => c.wuffs_base__slice_u16,
8 | u32 => c.wuffs_base__slice_u32,
9 | u64 => c.wuffs_base__slice_u64,
10 | else => @compileError(@typeName(T) ++ " is not a valid wuffs slice type"),
11 | };
12 | }
13 |
14 | fn toWuffsSlice(comptime T: type, slice: []T) ToWuffsSliceType(T) {
15 | const f = switch (T) {
16 | u8 => c.wuffs_base__make_slice_u8,
17 | u16 => c.wuffs_base__make_slice_u16,
18 | u32 => c.wuffs_base__make_slice_u32,
19 | u64 => c.wuffs_base__make_slice_u64,
20 | else => @compileError(@typeName(T) ++ " is not a valid wuffs slice type"),
21 | };
22 | return f(slice.ptr, slice.len);
23 | }
24 |
25 | fn statusToError(status: c.wuffs_base__status) !void {
26 | // TODO: make this actually useful
27 | if (!c.wuffs_base__status__is_ok(&status)) return error.WuffsError;
28 | }
29 |
30 | const FourCC = enum(u32) {
31 | unknown = 0,
32 | abxr = c.WUFFS_BASE__FOURCC__ABXR,
33 | abxs = c.WUFFS_BASE__FOURCC__ABXS,
34 | bgcl = c.WUFFS_BASE__FOURCC__BGCL,
35 | bmp = c.WUFFS_BASE__FOURCC__BMP,
36 | brtl = c.WUFFS_BASE__FOURCC__BRTL,
37 | bz2 = c.WUFFS_BASE__FOURCC__BZ2,
38 | cbor = c.WUFFS_BASE__FOURCC__CBOR,
39 | chrm = c.WUFFS_BASE__FOURCC__CHRM,
40 | css = c.WUFFS_BASE__FOURCC__CSS,
41 | eps = c.WUFFS_BASE__FOURCC__EPS,
42 | etc2 = c.WUFFS_BASE__FOURCC__ETC2,
43 | exif = c.WUFFS_BASE__FOURCC__EXIF,
44 | flac = c.WUFFS_BASE__FOURCC__FLAC,
45 | gama = c.WUFFS_BASE__FOURCC__GAMA,
46 | gif = c.WUFFS_BASE__FOURCC__GIF,
47 | gz = c.WUFFS_BASE__FOURCC__GZ,
48 | heif = c.WUFFS_BASE__FOURCC__HEIF,
49 | html = c.WUFFS_BASE__FOURCC__HTML,
50 | iccp = c.WUFFS_BASE__FOURCC__ICCP,
51 | ico = c.WUFFS_BASE__FOURCC__ICO,
52 | icvg = c.WUFFS_BASE__FOURCC__ICVG,
53 | ini = c.WUFFS_BASE__FOURCC__INI,
54 | jpeg = c.WUFFS_BASE__FOURCC__JPEG,
55 | js = c.WUFFS_BASE__FOURCC__JS,
56 | json = c.WUFFS_BASE__FOURCC__JSON,
57 | jwcc = c.WUFFS_BASE__FOURCC__JWCC,
58 | kvp = c.WUFFS_BASE__FOURCC__KVP,
59 | kvpk = c.WUFFS_BASE__FOURCC__KVPK,
60 | kvpv = c.WUFFS_BASE__FOURCC__KVPV,
61 | lz4 = c.WUFFS_BASE__FOURCC__LZ4,
62 | lzip = c.WUFFS_BASE__FOURCC__LZIP,
63 | lzma = c.WUFFS_BASE__FOURCC__LZMA,
64 | md = c.WUFFS_BASE__FOURCC__MD,
65 | mtim = c.WUFFS_BASE__FOURCC__MTIM,
66 | mp3 = c.WUFFS_BASE__FOURCC__MP3,
67 | nie = c.WUFFS_BASE__FOURCC__NIE,
68 | npbm = c.WUFFS_BASE__FOURCC__NPBM,
69 | ofs2 = c.WUFFS_BASE__FOURCC__OFS2,
70 | otf = c.WUFFS_BASE__FOURCC__OTF,
71 | pdf = c.WUFFS_BASE__FOURCC__PDF,
72 | phyd = c.WUFFS_BASE__FOURCC__PHYD,
73 | png = c.WUFFS_BASE__FOURCC__PNG,
74 | ps = c.WUFFS_BASE__FOURCC__PS,
75 | qoi = c.WUFFS_BASE__FOURCC__QOI,
76 | rac = c.WUFFS_BASE__FOURCC__RAC,
77 | raw = c.WUFFS_BASE__FOURCC__RAW,
78 | riff = c.WUFFS_BASE__FOURCC__RIFF,
79 | rigl = c.WUFFS_BASE__FOURCC__RIGL,
80 | snpy = c.WUFFS_BASE__FOURCC__SNPY,
81 | srgb = c.WUFFS_BASE__FOURCC__SRGB,
82 | svg = c.WUFFS_BASE__FOURCC__SVG,
83 | tar = c.WUFFS_BASE__FOURCC__TAR,
84 | text = c.WUFFS_BASE__FOURCC__TEXT,
85 | tga = c.WUFFS_BASE__FOURCC__TGA,
86 | th = c.WUFFS_BASE__FOURCC__TH,
87 | tiff = c.WUFFS_BASE__FOURCC__TIFF,
88 | toml = c.WUFFS_BASE__FOURCC__TOML,
89 | wave = c.WUFFS_BASE__FOURCC__WAVE,
90 | wbmp = c.WUFFS_BASE__FOURCC__WBMP,
91 | webp = c.WUFFS_BASE__FOURCC__WEBP,
92 | woff = c.WUFFS_BASE__FOURCC__WOFF,
93 | xml = c.WUFFS_BASE__FOURCC__XML,
94 | xmp = c.WUFFS_BASE__FOURCC__XMP,
95 | xz = c.WUFFS_BASE__FOURCC__XZ,
96 | zip = c.WUFFS_BASE__FOURCC__ZIP,
97 | zlib = c.WUFFS_BASE__FOURCC__ZLIB,
98 | zstd = c.WUFFS_BASE__FOURCC__ZSTD,
99 |
100 | fn toDecodableImageFileFormat(self: FourCC) ?DecodableImageFileFormat {
101 | return switch (self) {
102 | .bmp => .bmp,
103 | .etc2 => .etc2,
104 | .gif => .gif,
105 | .jpeg => .jpeg,
106 | .nie => .nie,
107 | .npbm => .netpbm,
108 | .png => .png,
109 | .qoi => .qoi,
110 | .tga => .targa,
111 | .th => .thumbhash,
112 | .wbmp => .wbmp,
113 | .webp => .webp,
114 | else => null,
115 | };
116 | }
117 | };
118 |
119 | const DecodableImageFileFormat = enum {
120 | bmp,
121 | etc2,
122 | gif,
123 | jpeg,
124 | nie,
125 | netpbm,
126 | png,
127 | qoi,
128 | targa,
129 | thumbhash,
130 | wbmp,
131 | webp,
132 | };
133 |
134 | fn guessFourCC(slice: []const u8) !FourCC {
135 | const res = c.wuffs_base__magic_number_guess_fourcc(toWuffsSlice(u8, @constCast(slice)), true);
136 | if (res == -1) return error.Incomplete else return @enumFromInt(res);
137 | }
138 |
139 | fn createDecoder(format: DecodableImageFileFormat, allocator: std.mem.Allocator) !*c.wuffs_base__image_decoder {
140 | inline for (comptime std.meta.tags(DecodableImageFileFormat)) |tag| {
141 | if (tag == format) {
142 | const size = @field(c, "sizeof__wuffs_" ++ @tagName(tag) ++ "__decoder")();
143 | const slice = try allocator.alloc(u8, size);
144 | errdefer allocator.free(slice);
145 |
146 | try statusToError(@field(c, "wuffs_" ++ @tagName(tag) ++ "__decoder__initialize")(@ptrCast(slice), size, c.WUFFS_VERSION, c.WUFFS_INITIALIZE__LEAVE_INTERNAL_BUFFERS_UNINITIALIZED));
147 |
148 | return @field(c, "wuffs_" ++ @tagName(tag) ++ "__decoder__upcast_as__wuffs_base__image_decoder")(@ptrCast(slice)).?;
149 | }
150 | } else unreachable;
151 | }
152 |
153 | fn destroyDecoder(format: DecodableImageFileFormat, allocator: std.mem.Allocator, decoder: *const c.wuffs_base__image_decoder) void {
154 | inline for (comptime std.meta.tags(DecodableImageFileFormat)) |tag| {
155 | if (tag == format) {
156 | const bytes: [*]const u8 = @ptrCast(decoder);
157 | const size = @field(c, "sizeof__wuffs_" ++ @tagName(tag) ++ "__decoder")();
158 | const slice = bytes[0..size];
159 | allocator.free(slice);
160 | return;
161 | }
162 | } else unreachable;
163 | }
164 |
165 | pub fn load(allocator: std.mem.Allocator, buffer: []const u8) !std.meta.Tuple(&.{[]const u8, u32, u32}) {
166 | var src = c.wuffs_base__ptr_u8__reader(@constCast(buffer.ptr), buffer.len, true);
167 | const format = (try guessFourCC(buffer)).toDecodableImageFileFormat() orelse return error.NotImage;
168 |
169 | const decoder = try createDecoder(format, allocator);
170 | defer destroyDecoder(format, allocator, decoder);
171 |
172 | var ic: c.wuffs_base__image_config = undefined;
173 | try statusToError(c.wuffs_base__image_decoder__decode_image_config(decoder, &ic, &src));
174 |
175 | const width = c.wuffs_base__pixel_config__width(&ic.pixcfg);
176 | const height = c.wuffs_base__pixel_config__height(&ic.pixcfg);
177 |
178 | var pc: c.wuffs_base__pixel_config = undefined;
179 | c.wuffs_base__pixel_config__set(&pc, c.WUFFS_BASE__PIXEL_FORMAT__RGB, c.WUFFS_BASE__PIXEL_SUBSAMPLING__NONE, width, height);
180 |
181 | const work_buffer = try allocator.alloc(u8, c.wuffs_base__image_decoder__workbuf_len(decoder).max_incl);
182 | defer allocator.free(work_buffer);
183 |
184 | const pixel_buffer = try allocator.alloc(u8, width * height * 3);
185 |
186 | var pb: c.wuffs_base__pixel_buffer = undefined;
187 | try statusToError(c.wuffs_base__pixel_buffer__set_from_slice(&pb, &pc, toWuffsSlice(u8, pixel_buffer)));
188 |
189 | try statusToError(c.wuffs_base__image_decoder__decode_frame(decoder, &pb, &src, c.WUFFS_BASE__PIXEL_BLEND__SRC, toWuffsSlice(u8, work_buffer), null));
190 |
191 | return .{ pixel_buffer, width, height };
192 | }
--------------------------------------------------------------------------------
/src/lib/gui/gui.zig:
--------------------------------------------------------------------------------
1 | pub const Platform = @import("./Platform.zig");
2 | pub const imgui = @import("./imgui.zig");
3 |
--------------------------------------------------------------------------------
/src/lib/hrtsystem/BackgroundManager.zig:
--------------------------------------------------------------------------------
1 | const std = @import("std");
2 | const vk = @import("vulkan");
3 |
4 | const engine = @import("../engine.zig");
5 | const VulkanContext = engine.core.VulkanContext;
6 | const Encoder = engine.core.Encoder;
7 | const Image = engine.core.Image;
8 |
9 | const vector = @import("../vector.zig");
10 | const shaders = @import("hrtsystem_shaders");
11 |
12 | const Rgba2D = engine.fileformats.exr.helpers.Rgba2D;
13 | const Mat3 = vector.Mat3(f32);
14 |
15 | pub const Background = struct {
16 | image: Image,
17 | transform: Mat3,
18 | };
19 |
20 | backgrounds: std.ArrayListUnmanaged(Background),
21 | sampler: vk.Sampler,
22 | equirectangular_to_equal_area_pipeline: EquirectangularToEqualAreaPipeline,
23 | fold_pipeline: FoldPipeline,
24 |
25 | const Self = @This();
26 |
27 | const EquirectangularToEqualAreaPipeline = engine.core.pipeline.Pipeline(.{
28 | .shader_source = shaders.equirectangular_to_equal_area,
29 | .local_size = vk.Extent3D { .width = 8, .height = 8, .depth = 1 },
30 | .PushSetBindings = struct {
31 | src_texture: engine.core.pipeline.CombinedImageSampler,
32 | dst_image: engine.core.pipeline.StorageImage,
33 | }
34 | });
35 |
36 | const FoldPipeline = engine.core.pipeline.Pipeline(.{
37 | .shader_source = shaders.background_fold,
38 | .local_size = vk.Extent3D { .width = 8, .height = 8, .depth = 1 },
39 | .PushSetBindings = struct {
40 | src_mip: engine.core.pipeline.SampledImage,
41 | dst_mip: engine.core.pipeline.StorageImage,
42 | }
43 | });
44 |
45 | pub fn create(vc: *const VulkanContext, allocator: std.mem.Allocator) !Self {
46 | const sampler = try vc.device.createSampler(&.{
47 | .flags = .{},
48 | .mag_filter = .linear,
49 | .min_filter = .linear,
50 | .mipmap_mode = .nearest,
51 | .address_mode_u = .mirrored_repeat,
52 | .address_mode_v = .mirrored_repeat,
53 | .address_mode_w = .mirrored_repeat,
54 | .mip_lod_bias = 0.0,
55 | .anisotropy_enable = vk.FALSE,
56 | .max_anisotropy = 0.0,
57 | .compare_enable = vk.FALSE,
58 | .compare_op = .always,
59 | .min_lod = 0.0,
60 | .max_lod = 0.0,
61 | .border_color = .float_opaque_white,
62 | .unnormalized_coordinates = vk.FALSE,
63 | }, null);
64 | errdefer vc.device.destroySampler(sampler, null);
65 |
66 | var equirectangular_to_equal_area_pipeline = try EquirectangularToEqualAreaPipeline.create(vc, allocator, .{}, .{ sampler }, .{});
67 | errdefer equirectangular_to_equal_area_pipeline.destroy(vc);
68 |
69 | var fold_pipeline = try FoldPipeline.create(vc, allocator, .{}, .{}, .{});
70 | errdefer fold_pipeline.destroy(vc);
71 |
72 | return Self {
73 | .backgrounds = .{},
74 | .sampler = sampler,
75 | .equirectangular_to_equal_area_pipeline = equirectangular_to_equal_area_pipeline,
76 | .fold_pipeline = fold_pipeline,
77 | };
78 | }
79 |
80 | pub fn addDefaultBackground(self: *Self, vc: *const VulkanContext, allocator: std.mem.Allocator, encoder: *Encoder) !Handle {
81 | var color = [4]f32 { 1.0, 1.0, 1.0, 1.0 };
82 | const rgba = Rgba2D {
83 | .ptr = @ptrCast(&color),
84 | .extent = .{
85 | .width = 1,
86 | .height = 1,
87 | }
88 | };
89 | return try self.addBackground(vc, allocator, encoder, rgba, Mat3.identity, "default white");
90 | }
91 |
92 | // this should probably be a parameter, or should infer proper value for this
93 | //
94 | // the equal area map size will be the biggest power of two greater than
95 | // or equal to the equirectangular height, clamped to maximum_equal_area_map_size
96 | const maximum_equal_area_map_size = 16384;
97 | const shader_local_size = 8; // must be kept in sync with shader -- looks like HLSL doesn't support setting this via spec constants
98 |
99 | pub const Handle = u32;
100 | // color_image should be equirectangular, which is converted to equal area.
101 | //
102 | // in "Parameterization-Independent Importance Sampling of Environment Maps",
103 | // the author retains the original environment map for illumination,
104 | // only using equal area for importance sampling.
105 | // I tried that here but it seems to produce noisier results for e.g., sunny skies
106 | // compared to just keeping everything in the same parameterization.
107 | pub fn addBackground(self: *Self, vc: *const VulkanContext, allocator: std.mem.Allocator, encoder: *Encoder, color_image: Rgba2D, transform: Mat3, name: []const u8) !Handle {
108 | const equirectangular_extent = color_image.extent;
109 |
110 | const texture_name_equirectangular = try std.fmt.allocPrintZ(allocator, "background {s} equirectangular", .{ name });
111 | defer allocator.free(texture_name_equirectangular);
112 | const equirectangular_image = try Image.create(vc, equirectangular_extent, .{ .transfer_dst_bit = true, .sampled_bit = true }, .r32g32b32a32_sfloat, false, texture_name_equirectangular);
113 | try encoder.attachResource(equirectangular_image);
114 |
115 | const equirectangular_image_host = try encoder.uploadAllocator().alignedAlloc([4]f32, engine.core.vk_helpers.texelBlockSize(.r32g32b32a32_sfloat), color_image.asSlice().len);
116 | @memcpy(equirectangular_image_host, color_image.asSlice());
117 |
118 | const equal_area_map_size: u32 = @min(std.math.ceilPowerOfTwoAssert(u32, color_image.extent.width), maximum_equal_area_map_size);
119 | const equal_area_extent = vk.Extent2D { .width = equal_area_map_size, .height = equal_area_map_size };
120 |
121 | const texture_name_equal_area = try std.fmt.allocPrintZ(allocator, "background {s} equal area", .{ name });
122 | defer allocator.free(texture_name_equal_area);
123 | const equal_area_image = try Image.create(vc, equal_area_extent, .{ .storage_bit = true, .sampled_bit = true }, .r32g32b32a32_sfloat, true, texture_name_equal_area);
124 | errdefer equal_area_image.destroy(vc);
125 |
126 | const actual_mip_count = std.math.log2(equal_area_map_size) + 1;
127 | const maximum_mip_count = comptime std.math.log2(maximum_equal_area_map_size) + 1;
128 | var mip_views = std.BoundedArray(vk.ImageView, maximum_mip_count) {};
129 | for (0..actual_mip_count) |level_index| {
130 | const view = try vc.device.createImageView(&vk.ImageViewCreateInfo {
131 | .flags = .{},
132 | .image = equal_area_image.handle,
133 | .view_type = vk.ImageViewType.@"2d",
134 | .format = .r32g32b32a32_sfloat,
135 | .components = .{
136 | .r = .identity,
137 | .g = .identity,
138 | .b = .identity,
139 | .a = .identity,
140 | },
141 | .subresource_range = .{
142 | .aspect_mask = .{ .color_bit = true },
143 | .base_mip_level = @intCast(level_index),
144 | .level_count = 1,
145 | .base_array_layer = 0,
146 | .layer_count = vk.REMAINING_ARRAY_LAYERS,
147 | },
148 | }, null);
149 | try mip_views.append(view);
150 | try encoder.attachResource(view);
151 | }
152 |
153 | // copy equirectangular image to device
154 | encoder.barrier(&[_]Encoder.ImageBarrier {
155 | .{
156 | .dst_stage_mask = .{ .copy_bit = true },
157 | .dst_access_mask = .{ .transfer_write_bit = true },
158 | .old_layout = .undefined,
159 | .new_layout = .transfer_dst_optimal,
160 | .image = equirectangular_image.handle,
161 | },
162 | .{
163 | .dst_stage_mask = .{ .compute_shader_bit = true },
164 | .dst_access_mask = .{ .shader_write_bit = true },
165 | .old_layout = .undefined,
166 | .new_layout = .general,
167 | .image = equal_area_image.handle,
168 | },
169 | }, &.{});
170 |
171 | const equirectangular_image_host_slice = encoder.upload_allocator.getBufferSlice(equirectangular_image_host);
172 | encoder.copyBufferToImage(equirectangular_image_host_slice.handle, equirectangular_image_host_slice.offset, equirectangular_image.handle, .transfer_dst_optimal, equirectangular_extent);
173 |
174 | encoder.barrier(&[_]Encoder.ImageBarrier {
175 | .{
176 | .src_stage_mask = .{ .copy_bit = true },
177 | .src_access_mask = .{ .transfer_write_bit = true },
178 | .dst_stage_mask = .{ .compute_shader_bit = true },
179 | .dst_access_mask = .{ .shader_read_bit = true },
180 | .old_layout = .transfer_dst_optimal,
181 | .new_layout = .shader_read_only_optimal,
182 | .image = equirectangular_image.handle,
183 | },
184 | }, &.{});
185 |
186 | // do conversion
187 | self.equirectangular_to_equal_area_pipeline.recordBindPipeline(encoder.buffer);
188 | self.equirectangular_to_equal_area_pipeline.recordPushDescriptors(encoder.buffer, .{
189 | .src_texture = .{ .view = equirectangular_image.view },
190 | .dst_image = .{ .view = equal_area_image.view },
191 | });
192 | const dispatch_size = if (equal_area_map_size > shader_local_size) @divExact(equal_area_map_size, shader_local_size) else 1;
193 | self.equirectangular_to_equal_area_pipeline.recordDispatchWorkgroups(encoder.buffer, .{ .width = dispatch_size, .height = dispatch_size, .depth = 1 });
194 |
195 | self.fold_pipeline.recordBindPipeline(encoder.buffer);
196 | for (1..mip_views.len) |dst_mip_level| {
197 | encoder.barrier(&[_]Encoder.ImageBarrier {
198 | .{
199 | .src_stage_mask = .{ .compute_shader_bit = true },
200 | .src_access_mask = .{ .shader_write_bit = true },
201 | .dst_stage_mask = .{ .compute_shader_bit = true },
202 | .dst_access_mask = .{ .shader_read_bit = true },
203 | .old_layout = .general,
204 | .new_layout = .shader_read_only_optimal,
205 | .image = equal_area_image.handle,
206 | .base_mip_level = @intCast(dst_mip_level - 1),
207 | .level_count = 1,
208 | },
209 | }, &.{});
210 | self.fold_pipeline.recordPushDescriptors(encoder.buffer, .{
211 | .src_mip = .{ .view = mip_views.get(dst_mip_level - 1) },
212 | .dst_mip = .{ .view = mip_views.get(dst_mip_level) },
213 | });
214 | const dst_mip_size = std.math.pow(u32, 2, @intCast(mip_views.len - dst_mip_level));
215 | const mip_dispatch_size = if (dst_mip_size > shader_local_size) @divExact(dst_mip_size, shader_local_size) else 1;
216 | self.fold_pipeline.recordDispatchWorkgroups(encoder.buffer, .{ .width = mip_dispatch_size, .height = mip_dispatch_size, .depth = 1 });
217 | }
218 | encoder.barrier(&[_]Encoder.ImageBarrier {
219 | .{
220 | .src_stage_mask = .{ .compute_shader_bit = true },
221 | .src_access_mask = .{ .shader_write_bit = true },
222 | .dst_stage_mask = .{ .compute_shader_bit = true },
223 | .dst_access_mask = .{ .shader_read_bit = true },
224 | .old_layout = .general,
225 | .new_layout = .shader_read_only_optimal,
226 | .image = equal_area_image.handle,
227 | .base_mip_level = @intCast(mip_views.len - 1),
228 | .level_count = 1,
229 | },
230 | }, &.{});
231 |
232 | try self.backgrounds.append(allocator, Background {
233 | .image = equal_area_image,
234 | .transform = transform,
235 | });
236 |
237 | return @intCast(self.backgrounds.items.len - 1);
238 | }
239 |
240 | pub fn destroy(self: *Self, vc: *const VulkanContext, allocator: std.mem.Allocator) void {
241 | for (self.backgrounds.items) |background| {
242 | background.image.destroy(vc);
243 | }
244 | self.backgrounds.deinit(allocator);
245 | self.equirectangular_to_equal_area_pipeline.destroy(vc);
246 | self.fold_pipeline.destroy(vc);
247 | vc.device.destroySampler(self.sampler, null);
248 | }
249 |
--------------------------------------------------------------------------------
/src/lib/hrtsystem/CameraManager.zig:
--------------------------------------------------------------------------------
1 | const std = @import("std");
2 | const vk = @import("vulkan");
3 | const Gltf = @import("zgltf");
4 |
5 | const engine = @import("../engine.zig");
6 | const core = engine.core;
7 | const VulkanContext = core.VulkanContext;
8 | const Encoder = core.Encoder;
9 |
10 | const Sensor = engine.hrtsystem.Sensor;
11 |
12 | const Mat4 = engine.vector.Mat4(f32);
13 | const Mat4x3 = engine.vector.Mat4x3(f32);
14 |
15 | pub const Model = enum(u32) {
16 | thin_lens,
17 | orthographic,
18 | };
19 |
20 | pub const ThinLens = extern struct {
21 | vfov: f32 = std.math.pi / 4.0,
22 | aperture: f32 = 0,
23 | focus_distance: f32 = 1,
24 | };
25 |
26 | pub const Orthographic = extern struct {
27 | vscale: f32 = 1,
28 | };
29 |
30 | // store camera models as a struct rather than a tagged union because:
31 | // 1. this way in interactive modes states of non-selected is saved
32 | // 2. can pass to spirv directly
33 | pub const Camera = extern struct {
34 | transform: Mat4x3 = Mat4.identity.truncateRow(),
35 | model: Model = .thin_lens,
36 | thin_lens: ThinLens = .{},
37 | orthographic: Orthographic = .{},
38 | };
39 |
40 | sensors: std.ArrayListUnmanaged(Sensor) = .{},
41 | cameras: std.ArrayListUnmanaged(std.meta.Tuple(&.{[:0]const u8, Camera })) = .{},
42 |
43 | const Self = @This();
44 |
45 | pub const SensorHandle = u32;
46 | pub fn appendSensor(self: *Self, vc: *const VulkanContext, allocator: std.mem.Allocator, extent: vk.Extent2D) !SensorHandle {
47 | var buf: [32]u8 = undefined;
48 | const name = try std.fmt.bufPrintZ(&buf, "render {}", .{self.sensors.items.len});
49 |
50 | try self.sensors.append(allocator, try Sensor.create(vc, extent, name));
51 | return @intCast(self.sensors.items.len - 1);
52 | }
53 |
54 | pub const CameraHandle = u32;
55 | pub fn appendCamera(self: *Self, allocator: std.mem.Allocator, camera: Camera, name: [:0]const u8) !CameraHandle {
56 | try self.cameras.append(allocator, .{name, camera});
57 | return @intCast(self.cameras.items.len - 1);
58 | }
59 |
60 | pub fn clearAllSensors(self: *Self) void {
61 | for (self.sensors.items) |*sensor| {
62 | sensor.clear();
63 | }
64 | }
65 |
66 | pub fn destroy(self: *Self, vc: *const VulkanContext, allocator: std.mem.Allocator) void {
67 | for (self.sensors.items) |*sensor| {
68 | sensor.destroy(vc);
69 | }
70 | for (self.cameras.items) |*camera| {
71 | allocator.free(camera[0]);
72 | }
73 | self.sensors.deinit(allocator);
74 | self.cameras.deinit(allocator);
75 | }
76 |
--------------------------------------------------------------------------------
/src/lib/hrtsystem/MeshManager.zig:
--------------------------------------------------------------------------------
1 | const vk = @import("vulkan");
2 | const std = @import("std");
3 |
4 | const engine = @import("../engine.zig");
5 |
6 | const core = engine.core;
7 | const VulkanContext = core.VulkanContext;
8 | const Encoder = core.Encoder;
9 |
10 | const vector = @import("../vector.zig");
11 | const U32x3 = vector.Vec3(u32);
12 | const F32x3 = vector.Vec3(f32);
13 | const F32x2 = vector.Vec2(f32);
14 |
15 | pub const Mesh = struct {
16 | pub const Parameters = struct {
17 | name: []const u8,
18 | // vertices
19 | positions: core.mem.BufferSlice(F32x3),
20 | normals: ?core.mem.BufferSlice(F32x3),
21 | texcoords: ?core.mem.BufferSlice(F32x2),
22 |
23 | // indices
24 | indices: ?core.mem.BufferSlice(U32x3),
25 | };
26 |
27 | pub const Host = struct {
28 | position_buffer: core.mem.DeviceBuffer(F32x3, .{ .shader_device_address_bit = true, .transfer_dst_bit = true, .acceleration_structure_build_input_read_only_bit_khr = true }),
29 | texcoord_buffer: core.mem.DeviceBuffer(F32x2, .{ .shader_device_address_bit = true, .transfer_dst_bit = true }),
30 | normal_buffer: core.mem.DeviceBuffer(F32x3, .{ .shader_device_address_bit = true, .transfer_dst_bit = true }),
31 |
32 | vertex_count: u32,
33 |
34 | index_buffer: core.mem.DeviceBuffer(U32x3, .{ .shader_device_address_bit = true, .transfer_dst_bit = true, .acceleration_structure_build_input_read_only_bit_khr = true }),
35 | index_count: u32,
36 |
37 | pub fn triangleCount(self: @This()) u32 {
38 | return if (self.index_count != 0) self.index_count else @divExact(self.vertex_count, 3);
39 | }
40 | };
41 |
42 | pub const Device = extern struct {
43 | position_address: vk.DeviceAddress,
44 | texcoord_address: vk.DeviceAddress,
45 | normal_address: vk.DeviceAddress,
46 |
47 | index_address: vk.DeviceAddress,
48 |
49 | triangle_count: u64,
50 | };
51 | };
52 |
53 | host: std.MultiArrayList(Mesh.Host) = .{},
54 | device: core.mem.DeviceBuffer(Mesh.Device, .{ .transfer_dst_bit = true, .storage_buffer_bit = true }) = .{},
55 |
56 | const Self = @This();
57 |
58 | const max_meshes = 4096; // TODO: resizable buffers
59 |
60 | pub const Handle = u32;
61 |
62 | pub fn upload(self: *Self, vc: *const VulkanContext, allocator: std.mem.Allocator, encoder: *Encoder, parameters: Mesh.Parameters) !Handle {
63 | std.debug.assert(self.host.len < max_meshes);
64 |
65 | const position_buffer = blk: {
66 | const buffer_name = try std.fmt.allocPrintZ(allocator, "mesh {s} positions", .{ parameters.name });
67 | defer allocator.free(buffer_name);
68 | const gpu_buffer = try core.mem.DeviceBuffer(F32x3, .{ .shader_device_address_bit = true, .transfer_dst_bit = true, .acceleration_structure_build_input_read_only_bit_khr = true }).create(vc, parameters.positions.len, buffer_name);
69 | gpu_buffer.uploadFrom(encoder, 0, parameters.positions);
70 |
71 | break :blk gpu_buffer;
72 | };
73 | errdefer position_buffer.destroy(vc);
74 |
75 | const texcoord_buffer = blk: {
76 | if (parameters.texcoords) |texcoords| {
77 | const buffer_name = try std.fmt.allocPrintZ(allocator, "mesh {s} texcoords", .{ parameters.name });
78 | defer allocator.free(buffer_name);
79 | const gpu_buffer = try core.mem.DeviceBuffer(F32x2, .{ .shader_device_address_bit = true, .transfer_dst_bit = true }).create(vc, texcoords.len, buffer_name);
80 | gpu_buffer.uploadFrom(encoder, 0, texcoords);
81 | break :blk gpu_buffer;
82 | } else {
83 | break :blk core.mem.DeviceBuffer(F32x2, .{ .shader_device_address_bit = true, .transfer_dst_bit = true }) {};
84 | }
85 | };
86 | errdefer texcoord_buffer.destroy(vc);
87 |
88 | const normal_buffer = blk: {
89 | if (parameters.normals) |normals| {
90 | const buffer_name = try std.fmt.allocPrintZ(allocator, "mesh {s} normals", .{ parameters.name });
91 | defer allocator.free(buffer_name);
92 | const gpu_buffer = try core.mem.DeviceBuffer(F32x3, .{ .shader_device_address_bit = true, .transfer_dst_bit = true }).create(vc, normals.len, buffer_name);
93 | gpu_buffer.uploadFrom(encoder, 0, normals);
94 | break :blk gpu_buffer;
95 | } else {
96 | break :blk core.mem.DeviceBuffer(F32x3, .{ .shader_device_address_bit = true, .transfer_dst_bit = true }) {};
97 | }
98 | };
99 | errdefer normal_buffer.destroy(vc);
100 |
101 | const index_buffer = blk: {
102 | if (parameters.indices) |indices| {
103 | const buffer_name = try std.fmt.allocPrintZ(allocator, "mesh {s} incides", .{ parameters.name });
104 | defer allocator.free(buffer_name);
105 | const gpu_buffer = try core.mem.DeviceBuffer(U32x3, .{ .shader_device_address_bit = true, .transfer_dst_bit = true, .acceleration_structure_build_input_read_only_bit_khr = true }).create(vc, indices.len, buffer_name);
106 | gpu_buffer.uploadFrom(encoder, 0, indices);
107 | break :blk gpu_buffer;
108 | } else {
109 | break :blk core.mem.DeviceBuffer(U32x3, .{ .shader_device_address_bit = true, .transfer_dst_bit = true, .acceleration_structure_build_input_read_only_bit_khr = true }) {};
110 | }
111 | };
112 | errdefer index_buffer.destroy(vc);
113 |
114 | const host = Mesh.Host {
115 | .position_buffer = position_buffer,
116 | .texcoord_buffer = texcoord_buffer,
117 | .normal_buffer = normal_buffer,
118 |
119 | .vertex_count = @intCast(parameters.positions.len),
120 |
121 | .index_buffer = index_buffer,
122 | .index_count = if (parameters.indices) |indices| @intCast(indices.len) else 0,
123 | };
124 |
125 | const device = Mesh.Device {
126 | .position_address = position_buffer.getAddress(vc),
127 | .texcoord_address = texcoord_buffer.getAddress(vc),
128 | .normal_address = normal_buffer.getAddress(vc) ,
129 |
130 | .index_address = index_buffer.getAddress(vc),
131 |
132 | .triangle_count = host.triangleCount(),
133 | };
134 |
135 | if (self.device.isNull()) self.device = try core.mem.DeviceBuffer(Mesh.Device, .{ .transfer_dst_bit = true, .storage_buffer_bit = true }).create(vc, max_meshes, "meshes");
136 | self.device.updateFrom(encoder, self.host.len, &.{ device });
137 |
138 | try self.host.append(allocator, host);
139 |
140 | return @intCast(self.host.len - 1);
141 | }
142 |
143 | pub fn destroy(self: *Self, vc: *const VulkanContext, allocator: std.mem.Allocator) void {
144 | const slice = self.host.slice();
145 | const position_buffers = slice.items(.position_buffer);
146 | const texcoord_buffers = slice.items(.texcoord_buffer);
147 | const normal_buffers = slice.items(.normal_buffer);
148 | const index_buffers = slice.items(.index_buffer);
149 |
150 | for (position_buffers, texcoord_buffers, normal_buffers, index_buffers) |position_buffer, texcoord_buffer, normal_buffer, index_buffer| {
151 | position_buffer.destroy(vc);
152 | texcoord_buffer.destroy(vc);
153 | normal_buffer.destroy(vc);
154 | index_buffer.destroy(vc);
155 | }
156 | self.host.deinit(allocator);
157 |
158 | self.device.destroy(vc);
159 | }
160 |
--------------------------------------------------------------------------------
/src/lib/hrtsystem/Scene.zig:
--------------------------------------------------------------------------------
1 | const std = @import("std");
2 | const vk = @import("vulkan");
3 | const Gltf = @import("zgltf");
4 |
5 | const engine = @import("../engine.zig");
6 |
7 | const core = engine.core;
8 | const VulkanContext = core.VulkanContext;
9 | const Encoder = core.Encoder;
10 |
11 | const Background = @import("./BackgroundManager.zig");
12 | const World = @import("./World.zig");
13 | const Camera = @import("./CameraManager.zig");
14 | const Material = @import("./MaterialManager.zig");
15 |
16 | const exr = engine.fileformats.exr;
17 |
18 | const vector = @import("../vector.zig");
19 | const F32x3 = vector.Vec3(f32);
20 | const F32x4 = vector.Vec4(f32);
21 | const Mat3 = vector.Mat3(f32);
22 | const Mat4 = vector.Mat4(f32);
23 | const Mat4x3 = vector.Mat4x3(f32);
24 |
25 | const Self = @This();
26 |
27 | world: World,
28 | background: Background,
29 | camera: Camera,
30 | global_volume: Material.Volume = .{},
31 |
32 | // glTF doesn't correspond very well to the internal data structures here so this is very inefficient
33 | // also very inefficient because it's written very inefficiently, can remove a lot of copying, but that's a problem for another time
34 | // inspection bool specifies whether some buffers should be created with the `transfer_src_flag` for inspection
35 | pub fn fromGltfExr(vc: *const VulkanContext, allocator: std.mem.Allocator, encoder: *Encoder, gltf_filepath: []const u8, skybox_filepath: []const u8, extent: vk.Extent2D) !Self {
36 | var gltf = Gltf.init(allocator);
37 | defer gltf.deinit();
38 |
39 | const buffer = try std.fs.cwd().readFileAllocOptions(
40 | allocator,
41 | gltf_filepath,
42 | std.math.maxInt(usize),
43 | null,
44 | 4,
45 | null
46 | );
47 | defer allocator.free(buffer);
48 | try gltf.parse(buffer);
49 |
50 | var camera = Camera {};
51 | errdefer camera.destroy(vc, allocator);
52 | _ = try camera.appendSensor(vc, allocator, extent);
53 |
54 | {
55 | // gltf spec:
56 | // > The camera is defined such that the local +X axis is to the right,
57 | // > the “lens” looks towards the local -Z axis,
58 | // > and the top of the camera is aligned with the local +Y axis.
59 | const msne_camera_to_gltf_camera = Mat4.fromRows(.{
60 | .new(.{ 0, 1, 0, 0}),
61 | .new(.{ 0, 0,-1, 0}),
62 | .new(.{-1, 0, 0, 0}),
63 | .new(.{ 0, 0, 0, 1}),
64 | });
65 |
66 | for (gltf.data.nodes.items) |node| {
67 | if (node.camera) |camera_idx| {
68 | const gltf_camera = gltf.data.cameras.items[camera_idx];
69 | const mat_array = Gltf.getGlobalTransform(&gltf.data, node);
70 | const transform = Mat4.fromCols(.{ .new(mat_array[0]), .new(mat_array[1]), .new(mat_array[2]), .new(mat_array[3]) });
71 | _ = try camera.appendCamera(allocator, Camera.Camera {
72 | .transform = transform.mul(msne_camera_to_gltf_camera).truncateRow(),
73 | .model = switch (gltf_camera.type) {
74 | .perspective => .thin_lens,
75 | .orthographic => .orthographic,
76 | },
77 | .thin_lens = Camera.ThinLens {
78 | .vfov = if (gltf_camera.type == .perspective) gltf_camera.type.perspective.yfov else std.math.pi / 4.0,
79 | },
80 | .orthographic = Camera.Orthographic {
81 | .vscale = if (gltf_camera.type == .orthographic) gltf_camera.type.orthographic.ymag else 1,
82 | },
83 | }, try allocator.dupeZ(u8, gltf_camera.name));
84 | }
85 | }
86 |
87 | // add default camera if none loaded
88 | if (camera.cameras.items.len == 0) {
89 | const transform = Mat4.fromRows(.{
90 | .new(.{1, 0, 0, 0}),
91 | .new(.{0, 1, 0, 0}),
92 | .new(.{0, 0, 1, 5}), // looking at origin
93 | .new(.{0, 0, 0, 1}),
94 | });
95 | _ = try camera.appendCamera(allocator, Camera.Camera {
96 | .transform = transform.mul(msne_camera_to_gltf_camera).truncateRow(),
97 | }, try allocator.dupeZ(u8, "default"));
98 | }
99 | }
100 |
101 | var world = try World.fromGltf(vc, allocator, encoder, gltf, std.fs.path.dirname(gltf_filepath));
102 | errdefer world.destroy(vc, allocator);
103 |
104 | var background = try Background.create(vc, allocator);
105 | errdefer background.destroy(vc, allocator);
106 | {
107 | const skybox_image = try exr.helpers.Rgba2D.load(allocator, skybox_filepath);
108 | defer allocator.free(skybox_image.asSlice());
109 | _ = try background.addBackground(vc, allocator, encoder, skybox_image, Mat3.fromRows(.{
110 | // glTF assets have +Y as up, but our environment maps have +Z as up. swap the two.
111 | .new(.{ 1, 0, 0}),
112 | .new(.{ 0, 0, 1}),
113 | .new(.{ 0, 1, 0}),
114 | }), "exr");
115 | }
116 |
117 | return Self {
118 | .world = world,
119 | .background = background,
120 | .camera = camera,
121 | };
122 | }
123 |
124 | pub fn pushDescriptors(self: *const Self, camera: u32, sensor: u32, background: u32) engine.hrtsystem.pipeline.StandardBindings {
125 | _ = camera;
126 | return engine.hrtsystem.pipeline.StandardBindings {
127 | .tlas = self.world.accel.tlas_handle,
128 | .instances = self.world.accel.instances_device.deviceSlice(),
129 | .world_to_instances = self.world.accel.world_to_instance_device.deviceSlice(),
130 | .meshes = self.world.meshes.device.deviceSlice(),
131 | .geometries = self.world.models.geometries_device.deviceSlice(),
132 | .models = self.world.models.models_device.deviceSlice(),
133 | .materials = self.world.materials.materials.deviceSlice(),
134 | .instance_powers = self.world.accel.instance_powers.deviceSlice(),
135 | .background_image = .{ .view = self.background.backgrounds.items[background].image.view },
136 | .output_image = .{ .view = self.camera.sensors.items[sensor].image.view },
137 | };
138 | }
139 |
140 | pub fn pushConstants(self: *const Self, camera: u32, sensor: u32, background: u32, seed: u32) engine.hrtsystem.pipeline.StandardPushConstants {
141 | return engine.hrtsystem.pipeline.StandardPushConstants {
142 | .instance_count = self.world.accel.instance_count,
143 | .camera = self.camera.cameras.items[camera][1],
144 | .aspect_ratio = self.camera.sensors.items[sensor].aspectRatio(),
145 | .sample_count = self.camera.sensors.items[sensor].sample_count,
146 | .global_volume = self.global_volume,
147 | .background_to_world = self.background.backgrounds.items[background].transform,
148 | .seed = seed,
149 | };
150 | }
151 |
152 | pub fn destroy(self: *Self, vc: *const VulkanContext, allocator: std.mem.Allocator) void {
153 | self.world.destroy(vc, allocator);
154 | self.background.destroy(vc, allocator);
155 | self.camera.destroy(vc, allocator);
156 | }
157 |
--------------------------------------------------------------------------------
/src/lib/hrtsystem/Sensor.zig:
--------------------------------------------------------------------------------
1 | const std = @import("std");
2 | const vk = @import("vulkan");
3 |
4 | const engine = @import("../engine.zig");
5 | const VulkanContext = engine.core.VulkanContext;
6 | const Encoder = engine.core.Encoder;
7 | const Image = engine.core.Image;
8 |
9 | image: Image,
10 | extent: vk.Extent2D,
11 | sample_count: u32,
12 |
13 | const Self = @This();
14 |
15 | pub fn create(vc: *const VulkanContext, extent: vk.Extent2D, name: [:0]const u8) !Self {
16 | const image = try Image.create(vc, extent, .{ .storage_bit = true, .transfer_src_bit = true, }, .r32g32b32a32_sfloat, false, name);
17 | errdefer image.destroy(vc);
18 |
19 | return Self {
20 | .image = image,
21 | .extent = extent,
22 | .sample_count = 0,
23 | };
24 | }
25 |
26 | pub fn aspectRatio(self: Self) f32 {
27 | return @as(f32, @floatFromInt(self.extent.width)) / @as(f32, @floatFromInt(self.extent.height));
28 | }
29 |
30 | // intended to be used in a loop, e.g
31 | //
32 | // while rendering:
33 | // recordPrepareForCapture(...)
34 | // ...
35 | // recordPrepareForCopy(...)
36 | pub fn recordPrepareForCapture(self: *const Self, command_buffer: VulkanContext.CommandBuffer, capture_stage: vk.PipelineStageFlags2, copy_stage: vk.PipelineStageFlags2) void {
37 | command_buffer.pipelineBarrier2(&vk.DependencyInfo{
38 | .image_memory_barrier_count = 1,
39 | .p_image_memory_barriers = (&vk.ImageMemoryBarrier2{
40 | .src_stage_mask = copy_stage,
41 | .src_access_mask = if (!std.meta.eql(copy_stage, .{})) .{ .transfer_read_bit = true } else .{},
42 | .dst_stage_mask = capture_stage,
43 | .dst_access_mask = if (self.sample_count == 0) .{ .shader_storage_write_bit = true } else .{ .shader_storage_write_bit = true, .shader_storage_read_bit = true },
44 | .old_layout = if (self.sample_count == 0) .undefined else .transfer_src_optimal,
45 | .new_layout = .general,
46 | .src_queue_family_index = vk.QUEUE_FAMILY_IGNORED,
47 | .dst_queue_family_index = vk.QUEUE_FAMILY_IGNORED,
48 | .image = self.image.handle,
49 | .subresource_range = .{
50 | .aspect_mask = .{ .color_bit = true },
51 | .base_mip_level = 0,
52 | .level_count = 1,
53 | .base_array_layer = 0,
54 | .layer_count = vk.REMAINING_ARRAY_LAYERS,
55 | },
56 | })[0..1],
57 | });
58 | }
59 |
60 | pub fn recordPrepareForCopy(self: *const Self, command_buffer: VulkanContext.CommandBuffer, capture_stage: vk.PipelineStageFlags2, copy_stage: vk.PipelineStageFlags2) void {
61 | command_buffer.pipelineBarrier2(&vk.DependencyInfo{
62 | .image_memory_barrier_count = 1,
63 | .p_image_memory_barriers = (&vk.ImageMemoryBarrier2 {
64 | .src_stage_mask = capture_stage,
65 | .src_access_mask = if (self.sample_count == 0) .{ .shader_storage_write_bit = true } else .{ .shader_storage_write_bit = true, .shader_storage_read_bit = true },
66 | .dst_stage_mask = copy_stage,
67 | .dst_access_mask = if (!std.meta.eql(copy_stage, .{})) .{ .transfer_read_bit = true } else .{},
68 | .old_layout = .general,
69 | .new_layout = .transfer_src_optimal,
70 | .src_queue_family_index = vk.QUEUE_FAMILY_IGNORED,
71 | .dst_queue_family_index = vk.QUEUE_FAMILY_IGNORED,
72 | .image = self.image.handle,
73 | .subresource_range = .{
74 | .aspect_mask = .{ .color_bit = true },
75 | .base_mip_level = 0,
76 | .level_count = 1,
77 | .base_array_layer = 0,
78 | .layer_count = vk.REMAINING_ARRAY_LAYERS,
79 | },
80 | })[0..1],
81 | });
82 | }
83 |
84 | pub fn clear(self: *Self) void {
85 | self.sample_count = 0;
86 | }
87 |
88 | pub fn destroy(self: *Self, vc: *const VulkanContext) void {
89 | self.image.destroy(vc);
90 | }
91 |
--------------------------------------------------------------------------------
/src/lib/hrtsystem/hrtsystem.zig:
--------------------------------------------------------------------------------
1 | pub const Accel = @import("Accel.zig");
2 | pub const CameraManager = @import("CameraManager.zig");
3 | pub const MeshManager = @import("MeshManager.zig");
4 | pub const MaterialManager = @import("MaterialManager.zig");
5 | pub const ModelManager = @import("ModelManager.zig");
6 | pub const BackgroundManager = @import("BackgroundManager.zig");
7 | pub const pipeline = @import("pipeline.zig");
8 | pub const World = @import("World.zig");
9 | pub const Scene = @import("Scene.zig");
10 | pub const ConstantSpectra = @import("ConstantSpectra.zig");
11 | pub const Sensor = @import("Sensor.zig");
12 |
13 | const vk = @import("vulkan");
14 |
15 | pub const required_device_extensions = [_][*:0]const u8{
16 | vk.extensions.khr_deferred_host_operations.name,
17 | vk.extensions.khr_acceleration_structure.name,
18 | vk.extensions.khr_ray_query.name,
19 | };
20 |
21 | pub const required_device_features = vk.PhysicalDeviceRayQueryFeaturesKHR {
22 | .p_next = @constCast(&vk.PhysicalDeviceAccelerationStructureFeaturesKHR {
23 | .acceleration_structure = vk.TRUE,
24 | }),
25 | .ray_query = vk.TRUE,
26 | };
27 |
--------------------------------------------------------------------------------
/src/lib/hrtsystem/pipeline.zig:
--------------------------------------------------------------------------------
1 | const shaders = @import("hrtsystem_shaders");
2 | const vk = @import("vulkan");
3 | const std = @import("std");
4 | const build_options = @import("build_options");
5 |
6 | const engine = @import("../engine.zig");
7 | const core = engine.core;
8 | const Pipeline = core.pipeline.Pipeline;
9 | const VulkanContext = core.VulkanContext;
10 | const Encoder = core.Encoder;
11 | const descriptor = core.descriptor;
12 |
13 | const Camera = @import("./CameraManager.zig");
14 | const Material = @import("./MaterialManager.zig");
15 |
16 | const vector = engine.vector;
17 | const F32x2 = vector.Vec2(f32);
18 | const F32x3 = vector.Vec3(f32);
19 | const Mat3 = vector.Mat3(f32);
20 | const Mat4x3 = vector.Mat4x3(f32);
21 |
22 | pub const StandardBindings = struct {
23 | tlas: ?vk.AccelerationStructureKHR,
24 | instances: ?core.mem.BufferSlice(vk.AccelerationStructureInstanceKHR),
25 | world_to_instances: ?core.mem.BufferSlice(Mat4x3),
26 | meshes: ?core.mem.BufferSlice(engine.hrtsystem.MeshManager.Mesh.Device),
27 | geometries: ?core.mem.BufferSlice(engine.hrtsystem.ModelManager.Geometry.Device),
28 | models: ?core.mem.BufferSlice(engine.hrtsystem.ModelManager.Model.Device),
29 | materials: ?core.mem.BufferSlice(engine.hrtsystem.MaterialManager.Material.Device),
30 | instance_powers: core.mem.BufferSlice(F32x3),
31 | background_image: core.pipeline.CombinedImageSampler,
32 | output_image: core.pipeline.StorageImage,
33 | };
34 |
35 | pub const StandardPushConstants = extern struct {
36 | instance_count: u32,
37 | camera: Camera.Camera,
38 | aspect_ratio: f32,
39 | sample_count: u32,
40 | global_volume: Material.Volume,
41 | background_to_world: Mat3,
42 | seed: u32,
43 | };
44 |
45 | pub const Integrator = enum(u32) {
46 | direct_lighting,
47 | path_tracing,
48 | volume_path_tracing,
49 | };
50 |
51 | pub const StandardPipeline = Pipeline(.{
52 | .local_size = vk.Extent3D { .width = 8, .height = 8, .depth = 1 },
53 | .shader_source = shaders.main,
54 | .SpecConstants = extern struct {
55 | integrator: Integrator = .path_tracing,
56 | direct_lighting_env_samples: u32 = 1,
57 | direct_lighting_mesh_samples: u32 = 1,
58 | direct_lighting_brdf_samples: u32 = 1,
59 | path_tracing_russian_roulette_depth: u32 = 3,
60 | path_tracing_env_samples_per_bounce: u32 = 1,
61 | path_tracing_mesh_samples_per_bounce: u32 = 1,
62 | volume_path_tracing_russian_roulette_depth: u32 = 3,
63 | volume_path_tracing_env_samples_per_bounce: u32 = 1,
64 | volume_path_tracing_mesh_samples_per_bounce: u32 = 1,
65 | },
66 | .PushConstants = StandardPushConstants,
67 | .additional_descriptor_layout_count = 2,
68 | .PushSetBindings = StandardBindings,
69 | });
70 |
71 | pub fn dispatch(pipeline: anytype, encoder: *Encoder, extent: vk.Extent2D) void {
72 | const width = std.math.divCeil(extent.width, 32) catch unreachable;
73 | const height = std.math.divCeil(extent.height, 32) catch unreachable;
74 | pipeline.recordDispatch(encoder.buffer, .{ .width = width, .height = height, .depth = 1 });
75 | }
--------------------------------------------------------------------------------
/src/lib/hrtsystem/shaders/background/equirectangular_to_equal_area.hlsl:
--------------------------------------------------------------------------------
1 | #include "../../../shaders/utils/helpers.hlsl"
2 | #include "../../../shaders/utils/mappings.hlsl"
3 |
4 | [[vk::combinedImageSampler]] [[vk::binding(0, 0)]] Texture2D srcTexture;
5 | [[vk::combinedImageSampler]] [[vk::binding(0, 0)]] SamplerState srcTextureSampler;
6 |
7 | [[vk::binding(1, 0)]] RWTexture2D dstImage;
8 |
9 | [numthreads(8, 8, 1)]
10 | void main(uint3 dispatchXYZ: SV_DispatchThreadID) {
11 | const uint2 pixelIndex = dispatchXYZ.xy;
12 | const uint2 dstImageSize = textureDimensions(dstImage);
13 |
14 | if (any(pixelIndex >= dstImageSize)) return;
15 |
16 | float3 color = float3(0.0, 0.0, 0.0);
17 | const uint samples_per_dim = 3;
18 | for (uint i = 0; i < samples_per_dim; i++) {
19 | for (uint j = 0; j < samples_per_dim; j++) {
20 | const float2 subpixel = float2(1 + i, 1 + j) / float2(samples_per_dim + 1, samples_per_dim + 1);
21 | const float2 dstCoords = (float2(pixelIndex) + subpixel) / float2(dstImageSize);
22 | const float3 dir = squareToEqualAreaSphere(dstCoords);
23 | const float2 srcCoords = cartesianToSpherical(dir) / float2(2 * PI, PI);
24 | // not sure if there's a standard canonical environment map orientation,
25 | // but rotate this half a turn so that our default matches blender
26 | const float2 srcCoordsRotated = frac(srcCoords + float2(0.5, 0));
27 | color += srcTexture.SampleLevel(srcTextureSampler, srcCoordsRotated, 0); // could also technically compute some sort of gradient and get area subtended by this pixel
28 | }
29 | }
30 |
31 | const uint total_samples = samples_per_dim * samples_per_dim;
32 | dstImage[pixelIndex] = float4(color / float(total_samples), 1);
33 | }
--------------------------------------------------------------------------------
/src/lib/hrtsystem/shaders/background/fold.hlsl:
--------------------------------------------------------------------------------
1 | #include "../../../shaders/utils/helpers.hlsl"
2 |
3 | [[vk::binding(0, 0)]] Texture2D srcMip;
4 | [[vk::binding(1, 0)]] RWTexture2D dstMip;
5 |
6 | [numthreads(8, 8, 1)]
7 | void main(uint3 dispatchXYZ: SV_DispatchThreadID) {
8 | const uint2 pixelIndex = dispatchXYZ.xy;
9 | const uint2 dstImageSize = textureDimensions(dstMip);
10 |
11 | if (any(pixelIndex >= dstImageSize)) return;
12 |
13 | dstMip[pixelIndex] = srcMip[2 * pixelIndex + uint2(0, 0)]
14 | + srcMip[2 * pixelIndex + uint2(1, 0)]
15 | + srcMip[2 * pixelIndex + uint2(0, 1)]
16 | + srcMip[2 * pixelIndex + uint2(1, 1)];
17 | }
--------------------------------------------------------------------------------
/src/lib/hrtsystem/shaders/camera.hlsl:
--------------------------------------------------------------------------------
1 | #pragma once
2 |
3 | #include "../../shaders/utils/mappings.hlsl"
4 | #include "../../shaders/utils/helpers.hlsl"
5 | #include "ray.hlsl"
6 |
7 | // in camera space, cameras are oriented:
8 | // * forward along +X
9 | // * right along +Y
10 | // * down along +Z
11 |
12 | enum class CameraModel : uint {
13 | ThinLens,
14 | Orthographic,
15 | };
16 |
17 | struct ThinLens {
18 | float vfov;
19 | float aperture;
20 | float focusDistance;
21 |
22 | // uv is [-aspectRatio, aspectRatio], [-1, 1]
23 | Ray generateRay(const float2 rand, const float2 uv) {
24 | const float2 halfViewport = tan(vfov / 2);
25 |
26 | const float3 directionCameraSpaceUnorm = float3(1.0, uv * halfViewport);
27 |
28 | const float2 lens = aperture * squareToUniformDiskConcentric(rand) / 2.0;
29 | const float3 focus = focusDistance * directionCameraSpaceUnorm;
30 |
31 | Ray ray;
32 | ray.origin = float3(0.0, lens);
33 | ray.direction = normalize(focus - ray.origin);
34 |
35 | return ray;
36 | }
37 | };
38 |
39 | struct Orthographic {
40 | float vscale;
41 |
42 | Ray generateRay(const float2 uv) {
43 | const float2 halfViewport = vscale;
44 |
45 | Ray ray;
46 | ray.origin = float3(0.0, uv * halfViewport);
47 | ray.direction = float3(1, 0, 0);
48 |
49 | return ray;
50 | }
51 | };
52 |
53 | struct Camera {
54 | row_major float3x4 toWorld;
55 | CameraModel model;
56 | ThinLens thinLens;
57 | Orthographic orthographic;
58 | float aspect;
59 |
60 | Ray generateRay(const float2 uv, const float2 rand) {
61 | const float2 uvScaled = (uv * 2 - 1) * float2(aspect, 1);
62 | const Ray rayCameraSpace = thinLens.generateRay(rand, uvScaled);
63 | switch (model) {
64 | case CameraModel::ThinLens: {
65 | return thinLens.generateRay(rand, uvScaled).transformed(toWorld);
66 | }
67 | case CameraModel::Orthographic: {
68 | return orthographic.generateRay(uvScaled).transformed(toWorld);
69 | }
70 | }
71 | }
72 | };
73 |
74 |
--------------------------------------------------------------------------------
/src/lib/hrtsystem/shaders/intersection.hlsl:
--------------------------------------------------------------------------------
1 | #pragma once
2 |
3 | #include "../../shaders/utils/math.hlsl"
4 | #include "ray.hlsl"
5 |
6 | struct Intersection {
7 | uint instanceIndex;
8 | uint geometryIndex;
9 | uint primitiveIndex;
10 | float2 barycentrics;
11 |
12 | static Intersection create(uint instanceIndex, uint geometryIndex, uint primitiveIndex, float2 barycentrics) {
13 | Intersection its;
14 | its.instanceIndex = instanceIndex;
15 | its.geometryIndex = geometryIndex;
16 | its.primitiveIndex = primitiveIndex;
17 | its.barycentrics = barycentrics;
18 | return its;
19 | }
20 |
21 | static Intersection createMiss() {
22 | Intersection its;
23 | its.instanceIndex = MAX_UINT;
24 | return its;
25 | }
26 |
27 | static Intersection find(RaytracingAccelerationStructure accel, Ray ray) {
28 | return Intersection::find(accel, ray, 1.#INF);
29 | }
30 |
31 | static Intersection find(RaytracingAccelerationStructure accel, Ray ray, float tmax) {
32 | return Intersection::find(accel, ray, tmax, 0xFF);
33 | }
34 |
35 | static Intersection find(RaytracingAccelerationStructure accel, Ray ray, float tmax, uint mask) {
36 | RayQuery q;
37 | q.TraceRayInline(accel, 0, mask, ray.desc(0, tmax));
38 |
39 | while (q.Proceed()) {};
40 |
41 | if (q.CommittedStatus() != COMMITTED_TRIANGLE_HIT) {
42 | return Intersection::createMiss();
43 | }
44 |
45 | // for some reason dxc won't let me put an else here :think:
46 | return Intersection::create(
47 | q.CommittedInstanceIndex(),
48 | q.CommittedGeometryIndex(),
49 | q.CommittedPrimitiveIndex(),
50 | q.CommittedTriangleBarycentrics()
51 | );
52 | }
53 |
54 | bool hit() {
55 | return instanceIndex != MAX_UINT;
56 | }
57 | };
58 |
59 | struct ShadowIntersection {
60 | bool inShadow;
61 |
62 | static bool hit(RaytracingAccelerationStructure accel, Ray ray, float tmax) {
63 | return ShadowIntersection::hit(accel, ray, tmax, 0xFF);
64 | }
65 |
66 | static bool hit(RaytracingAccelerationStructure accel, Ray ray, float tmax, uint mask) {
67 | RayQuery q;
68 | q.TraceRayInline(accel, 0, mask, ray.desc(0, tmax));
69 | q.Proceed();
70 |
71 | ShadowIntersection its;
72 | its.inShadow = q.CommittedStatus() == COMMITTED_TRIANGLE_HIT;
73 | return its.inShadow;
74 | }
75 | };
76 |
--------------------------------------------------------------------------------
/src/lib/hrtsystem/shaders/local_light/fold1.hlsl:
--------------------------------------------------------------------------------
1 | [[vk::binding(0, 0)]] RWStructuredBuffer levels;
2 |
3 | struct PushConsts {
4 | uint srcLevelOffset;
5 | uint dstLevelOffset;
6 | uint maxSrcIndex;
7 | };
8 | [[vk::push_constant]] PushConsts pushConsts;
9 |
10 | [numthreads(32, 1, 1)]
11 | void main(uint3 dispatchXYZ: SV_DispatchThreadID) {
12 | const uint dstIndex = dispatchXYZ.x;
13 | const uint dstLevelSize = pushConsts.srcLevelOffset - pushConsts.dstLevelOffset;
14 |
15 | if (any(dstIndex >= dstLevelSize)) return;
16 |
17 | const uint srcBaseIndex = 2 * dstIndex;
18 |
19 | if (srcBaseIndex < pushConsts.maxSrcIndex) {
20 | levels[pushConsts.dstLevelOffset + dstIndex] = levels[pushConsts.srcLevelOffset + srcBaseIndex + 0]
21 | + levels[pushConsts.srcLevelOffset + srcBaseIndex + 1];
22 | } else {
23 | levels[pushConsts.dstLevelOffset + dstIndex] = 0;
24 | }
25 | }
--------------------------------------------------------------------------------
/src/lib/hrtsystem/shaders/local_light/fold3.hlsl:
--------------------------------------------------------------------------------
1 | [[vk::binding(0, 0)]] RWStructuredBuffer levels;
2 |
3 | struct PushConsts {
4 | uint srcLevelOffset;
5 | uint dstLevelOffset;
6 | uint maxSrcIndex;
7 | };
8 | [[vk::push_constant]] PushConsts pushConsts;
9 |
10 | [numthreads(32, 1, 1)]
11 | void main(uint3 dispatchXYZ: SV_DispatchThreadID) {
12 | const uint dstIndex = dispatchXYZ.x;
13 | const uint dstLevelSize = pushConsts.srcLevelOffset - pushConsts.dstLevelOffset;
14 |
15 | if (any(dstIndex >= dstLevelSize)) return;
16 |
17 | const uint srcBaseIndex = 2 * dstIndex;
18 |
19 | if (srcBaseIndex < pushConsts.maxSrcIndex) {
20 | levels[pushConsts.dstLevelOffset + dstIndex] = levels[pushConsts.srcLevelOffset + srcBaseIndex + 0]
21 | + levels[pushConsts.srcLevelOffset + srcBaseIndex + 1];
22 | } else {
23 | levels[pushConsts.dstLevelOffset + dstIndex] = 0;
24 | }
25 | }
--------------------------------------------------------------------------------
/src/lib/hrtsystem/shaders/local_light/geometry_power.hlsl:
--------------------------------------------------------------------------------
1 | #include "../world.hlsl"
2 |
3 | [[vk::binding(0, 0)]] StructuredBuffer dGeometries;
4 |
5 | // dst
6 | [[vk::binding(1, 0)]] RWStructuredBuffer dstPower;
7 |
8 | // mesh info
9 | struct PushConsts {
10 | uint geometryCount;
11 | uint srcOffset;
12 | uint dstOffset;
13 | };
14 | [[vk::push_constant]] PushConsts pushConsts;
15 |
16 | [numthreads(32, 1, 1)]
17 | void main(uint3 dispatchXYZ: SV_DispatchThreadID) {
18 | const uint srcIdx = dispatchXYZ.x;
19 |
20 | if (srcIdx >= pushConsts.geometryCount) {
21 | if (pushConsts.geometryCount != 1 && pushConsts.geometryCount % 2 == 1 && srcIdx == pushConsts.geometryCount) {
22 | // may be one element of padding, which should be zeroed
23 | dstPower[pushConsts.dstOffset + srcIdx] = 0;
24 | }
25 | return;
26 | }
27 |
28 | const Geometry geometry = dGeometries[pushConsts.srcOffset + srcIdx];
29 | const float3x3 power = vk::RawBufferLoad(geometry.trianglePowersAddress); // TODO: transform
30 |
31 | dstPower[pushConsts.dstOffset + srcIdx] = power;
32 | }
--------------------------------------------------------------------------------
/src/lib/hrtsystem/shaders/local_light/instance_power.hlsl:
--------------------------------------------------------------------------------
1 | #include "../world.hlsl"
2 |
3 | [[vk::binding(0, 0)]] StructuredBuffer dInstances;
4 | [[vk::binding(1, 0)]] StructuredBuffer dWorldToInstance;
5 | [[vk::binding(2, 0)]] StructuredBuffer dModels;
6 |
7 | // dst
8 | [[vk::binding(3, 0)]] RWStructuredBuffer dstPower;
9 |
10 | struct PushConsts {
11 | uint instanceCount;
12 | uint dstOffset;
13 | };
14 | [[vk::push_constant]] PushConsts pushConsts;
15 |
16 | [numthreads(32, 1, 1)]
17 | void main(uint3 dispatchXYZ: SV_DispatchThreadID) {
18 | const uint srcInstance = dispatchXYZ.x;
19 |
20 | if (srcInstance >= pushConsts.instanceCount) {
21 | if (pushConsts.instanceCount != 1 && pushConsts.instanceCount % 2 == 1 && srcInstance == pushConsts.instanceCount) {
22 | // may be one element of padding, which should be zeroed
23 | dstPower[pushConsts.dstOffset + srcInstance] = 0;
24 | }
25 | return;
26 | }
27 |
28 | const Instance instance = dInstances[srcInstance];
29 | const Model model = dModels[instance.instanceCustomIndex];
30 |
31 | const float3x3 toWorld = (float3x3)instance.transform;
32 | const float3x3 toLocal = (float3x3)dWorldToInstance[srcInstance];
33 | const float3x3 cofactor = abs(transpose(toLocal) * determinant(toWorld));
34 |
35 | const float3x3 modelPower = transpose(vk::RawBufferLoad(model.geometryPowersAddress));
36 | dstPower[pushConsts.dstOffset + srcInstance] = float3(
37 | normL1(mul(cofactor, modelPower[0])),
38 | normL1(mul(cofactor, modelPower[1])),
39 | normL1(mul(cofactor, modelPower[2]))
40 | );
41 | }
--------------------------------------------------------------------------------
/src/lib/hrtsystem/shaders/local_light/triangle_power.hlsl:
--------------------------------------------------------------------------------
1 | #include "../world.hlsl"
2 |
3 | [[vk::binding(0, 0)]] StructuredBuffer dMeshes;
4 | [[vk::binding(1, 0)]] StructuredBuffer dMaterials;
5 |
6 | // dst
7 | [[vk::binding(2, 0)]] RWStructuredBuffer dstPower;
8 |
9 | // mesh info
10 | struct PushConsts {
11 | uint meshIndex;
12 | uint materialIndex;
13 | uint triangleCount;
14 | uint dstOffset;
15 | };
16 | [[vk::push_constant]] PushConsts pushConsts;
17 |
18 | [numthreads(32, 1, 1)]
19 | void main(uint3 dispatchXYZ: SV_DispatchThreadID) {
20 | const uint srcPrimitive = dispatchXYZ.x;
21 |
22 | if (srcPrimitive >= pushConsts.triangleCount) {
23 | if (pushConsts.triangleCount != 1 && pushConsts.triangleCount % 2 == 1 && srcPrimitive == pushConsts.triangleCount) {
24 | // may be one element of padding, which should be zeroed
25 | dstPower[pushConsts.dstOffset + srcPrimitive] = 0;
26 | }
27 | return;
28 | }
29 |
30 | const TriangleLocalSpace tri = dMeshes[pushConsts.meshIndex].triangleLocalSpace(srcPrimitive);
31 | const Material material = dMaterials[pushConsts.materialIndex];
32 |
33 | float3 totalEmissive = 0;
34 |
35 | const uint samplesPerDimension = 8;
36 | for (uint i = 0; i < samplesPerDimension; i++) {
37 | for (uint j = 0; j < samplesPerDimension; j++) {
38 | const float2 barycentrics = squareToTriangle(float2(i, j) / float(samplesPerDimension));
39 | const float2 texcoord = interpolate(float3(1.0 - barycentrics.x - barycentrics.y, barycentrics.x, barycentrics.y), tri.texcoords);
40 | totalEmissive += dTextures[NonUniformResourceIndex(material.emissive)].SampleLevel(dTextureSampler, texcoord, 0).rgb;
41 | }
42 | }
43 |
44 | const float3 averageEmissive = totalEmissive / float(samplesPerDimension * samplesPerDimension);
45 | const float3 projectedArea = tri.bivector() / 2.0;
46 | const float3x3 power = PI * float3x3(
47 | abs(projectedArea.x) * averageEmissive,
48 | abs(projectedArea.y) * averageEmissive,
49 | abs(projectedArea.z) * averageEmissive
50 | );
51 |
52 | dstPower[pushConsts.dstOffset + srcPrimitive] = power;
53 | }
--------------------------------------------------------------------------------
/src/lib/hrtsystem/shaders/main.hlsl:
--------------------------------------------------------------------------------
1 | #include "intersection.hlsl"
2 | #include "camera.hlsl"
3 | #include "scene.hlsl"
4 | #include "integrator.hlsl"
5 | #include "medium.hlsl"
6 |
7 | // I use the `d` prefix to indicate a descriptor variable
8 | // because as a functional programmer impure functions scare me
9 |
10 | // GEOMETRY
11 | [[vk::binding(0, 0)]] RaytracingAccelerationStructure dTLAS;
12 | [[vk::binding(1, 0)]] StructuredBuffer dInstances;
13 | [[vk::binding(2, 0)]] StructuredBuffer dWorldToInstance;
14 | [[vk::binding(3, 0)]] StructuredBuffer dMeshes;
15 | [[vk::binding(4, 0)]] StructuredBuffer dGeometries;
16 | [[vk::binding(5, 0)]] StructuredBuffer dModels;
17 | [[vk::binding(6, 0)]] StructuredBuffer dMaterials;
18 |
19 | // EMISSIVE TRIANGLES
20 | [[vk::binding(7, 0)]] StructuredBuffer dInstancePower;
21 |
22 | // BACKGROUND
23 | [[vk::combinedImageSampler]] [[vk::binding(8, 0)]] Texture2D dBackgroundTexture;
24 | [[vk::combinedImageSampler]] [[vk::binding(8, 0)]] SamplerState dBackgroundSampler;
25 |
26 | // OUTPUT
27 | [[vk::binding(9, 0)]] RWTexture2D dOutputImage;
28 |
29 | // PUSH CONSTANTS
30 | struct PushConsts {
31 | uint instanceCount;
32 | Camera camera;
33 | uint sampleCount;
34 | ChromaticVolume globalVolume;
35 | float3x3 backgroundToWorld;
36 | uint seed;
37 | };
38 | [[vk::push_constant]] PushConsts pushConsts;
39 |
40 | enum class IntegratorType : uint {
41 | DirectLight,
42 | PathTracing,
43 | VolumePathTracing,
44 | };
45 |
46 | [[vk::constant_id(0)]] const uint dIntegratorType = 1;
47 | [[vk::constant_id(1)]] const uint dDirectLightEnvSamples = 1; // how many times the environment map should be sampled for light
48 | [[vk::constant_id(2)]] const uint dDirectLightMeshSamples = 1; // how many times emissive meshes should be sampled for light
49 | [[vk::constant_id(3)]] const uint dDirectLightBrdfSamples = 1; // how many times BRDF should be sampled for light
50 | [[vk::constant_id(4)]] const uint dPathTracingRussianRouletteDepth = 3; // at which bounce depth russian roulette should start
51 | [[vk::constant_id(5)]] const uint dPathTracingEnvSamplesPerBounce = 1; // how many times the environment map should be sampled per bounce for light
52 | [[vk::constant_id(6)]] const uint dPathTracingMeshSamplesPerBounce = 1; // how many times emissive meshes should be sampled per bounce for light
53 | [[vk::constant_id(7)]] const uint dVolumePathTracingRussianRouletteDepth = 3; // at which bounce depth russian roulette should start
54 | [[vk::constant_id(8)]] const uint dVolumePathTracingEnvSamplesPerBounce = 1; // how many times the environment map should be sampled per bounce for light
55 | [[vk::constant_id(9)]] const uint dVolumePathTracingMeshSamplesPerBounce = 1; // how many times emissive meshes should be sampled per bounce for light
56 |
57 | [numthreads(8, 8, 1)]
58 | void main(uint3 dispatchXYZ: SV_DispatchThreadID) {
59 | const uint2 imageCoords = dispatchXYZ.xy;
60 | const uint2 imageSize = textureDimensions(dOutputImage);
61 |
62 | if (any(imageCoords >= imageSize)) return;
63 |
64 | World world;
65 | world.instances = dInstances;
66 | world.worldToInstance = dWorldToInstance;
67 | world.meshes = dMeshes;
68 | world.models = dModels;
69 | world.geometries = dGeometries;
70 | world.materials = dMaterials;
71 |
72 | Scene scene;
73 | scene.tlas = dTLAS;
74 | scene.world = world;
75 | scene.envMap = EnvMap::create(pushConsts.backgroundToWorld, dBackgroundTexture, dBackgroundSampler);
76 | scene.instanceLights = InstanceLights::create(dInstancePower, pushConsts.instanceCount, world);
77 | scene.globalVolume = pushConsts.globalVolume;
78 |
79 | Rng rng = Rng::fromSeed(uint3(pushConsts.seed, imageCoords.x, imageCoords.y));
80 |
81 | // set up initial ray
82 | const float2 jitter = float2(rng.getFloat(), rng.getFloat());
83 | const float2 imageUV = (imageCoords + jitter) / imageSize;
84 | const Ray initialRay = pushConsts.camera.generateRay(imageUV, float2(rng.getFloat(), rng.getFloat()));
85 |
86 | // trace the ray
87 | WavelengthSample w = WavelengthSample::sampleVisible(rng.getFloat());
88 |
89 | const IntegratorType integratorType = (IntegratorType)dIntegratorType;
90 | float radiance;
91 | switch (integratorType) {
92 | case IntegratorType::DirectLight: {
93 | const DirectLightIntegrator integrator = DirectLightIntegrator::create(dDirectLightEnvSamples, dDirectLightMeshSamples, dDirectLightBrdfSamples);
94 | radiance = integrator.incomingRadiance(scene, initialRay, w.λ, rng);
95 | break;
96 | }
97 | case IntegratorType::PathTracing: {
98 | const PathTracingIntegrator integrator = PathTracingIntegrator::create(dPathTracingRussianRouletteDepth, dPathTracingEnvSamplesPerBounce, dPathTracingMeshSamplesPerBounce);
99 | radiance = integrator.incomingRadiance(scene, initialRay, w.λ, rng);
100 | break;
101 | }
102 | case IntegratorType::VolumePathTracing: {
103 | const VolumePathTracingIntegrator integrator = VolumePathTracingIntegrator::create(dVolumePathTracingRussianRouletteDepth, dVolumePathTracingEnvSamplesPerBounce, dVolumePathTracingMeshSamplesPerBounce);
104 | radiance = integrator.incomingRadiance(scene, initialRay, w.λ, rng);
105 | break;
106 | }
107 | }
108 |
109 | // accumulate
110 | const float3 priorSampleAverage = pushConsts.sampleCount == 0 ? 0 : dOutputImage[imageCoords].xyz;
111 | const float3 newSample = Spectrum::toLinearSRGB(w.λ, radiance) / w.pdf;
112 | const float3 newAverage = accumulate(priorSampleAverage, newSample, pushConsts.sampleCount);
113 | dOutputImage[imageCoords] = float4(newAverage, 1);
114 | }
115 |
--------------------------------------------------------------------------------
/src/lib/hrtsystem/shaders/material.hlsl:
--------------------------------------------------------------------------------
1 | #pragma once
2 |
3 | [[vk::binding(0, 1)]] Texture2D dTextures[];
4 | [[vk::binding(1, 1)]] SamplerState dTextureSampler;
5 |
6 | #include "spectrum.hlsl"
7 | #include "volume.hlsl"
8 | #include "bsdf.hlsl"
9 |
10 | float3 decodeNormal(float2 rg) {
11 | rg = rg * 2 - 1;
12 | return float3(rg, sqrt(1.0 - saturate(dot(rg, rg)))); // saturate due to float/compression annoyingness
13 | }
14 |
15 | float3 tangentNormalToWorld(float3 normalTangentSpace, Frame tangentFrame) {
16 | return normalize(tangentFrame.frameToWorld(normalTangentSpace)).xyz;
17 | }
18 |
19 | Frame createTextureFrame(float3 normalWorldSpace, Frame tangentFrame) {
20 | Frame textureFrame = tangentFrame;
21 | textureFrame.n = normalWorldSpace;
22 | textureFrame.reorthogonalize();
23 |
24 | return textureFrame;
25 | }
26 |
27 | enum class BSDFType : uint {
28 | Glass,
29 | Lambert,
30 | PerfectMirror,
31 | StandardPBR,
32 | };
33 |
34 | struct Material {
35 | uint normal;
36 | uint emissive;
37 |
38 | // volume enclosed by the mesh of this material
39 | ChromaticVolume volume;
40 |
41 | // find appropriate thing to decode from address using `type`
42 | BSDFType type;
43 | uint64_t addr;
44 |
45 | Frame getTextureFrame(float2 texcoords, Frame tangentFrame) {
46 | const float2 rg = dTextures[NonUniformResourceIndex(normal)].SampleLevel(dTextureSampler, texcoords, 0).rg;
47 | const float3 normalTangentSpace = decodeNormal(rg);
48 | const float3 normalWorldSpace = tangentNormalToWorld(normalTangentSpace, tangentFrame);
49 | return createTextureFrame(normalWorldSpace, tangentFrame);
50 | }
51 |
52 | float getEmissive(float λ, float2 texcoords) {
53 | return Spectrum::sampleEmission(λ, dTextures[NonUniformResourceIndex(emissive)].SampleLevel(dTextureSampler, texcoords, 0).rgb);
54 | }
55 | };
56 |
57 | struct PolymorphicBSDF : BSDF {
58 | BSDFType type;
59 | uint64_t addr;
60 | float2 texcoords;
61 | float λ;
62 | Frame shadingFrame;
63 | Frame triangleFrame;
64 |
65 | float intIOR;
66 | float extIOR;
67 |
68 | bool thin;
69 |
70 | static PolymorphicBSDF load(Material material, bool thin, float intIOR, float extIOR, float2 texcoords, Frame shadingFrame, Frame triangleFrame, float λ) {
71 | PolymorphicBSDF bsdf;
72 | bsdf.type = material.type;
73 | bsdf.addr = material.addr;
74 | bsdf.texcoords = texcoords;
75 | bsdf.λ = λ;
76 | bsdf.shadingFrame = shadingFrame;
77 | bsdf.triangleFrame = triangleFrame;
78 | bsdf.intIOR = thin ? material.volume.IOR.at(λ) : intIOR;
79 | bsdf.extIOR = extIOR;
80 | bsdf.thin = thin;
81 | return bsdf;
82 | }
83 |
84 | BSDFEvaluation evaluate(float3 w_i, float3 w_o) {
85 | BSDFEvaluation eval = BSDFEvaluation::empty();
86 |
87 | // zero out cases where the shading normal and geometric normal disagree on the type of event
88 | const bool geometricTransmission = sign(dot(w_i, triangleFrame.n)) != sign(dot(w_o, triangleFrame.n));
89 | const bool shadingTransmission = sign(dot(w_i, shadingFrame.n)) != sign(dot(w_o, shadingFrame.n));
90 | if (geometricTransmission != shadingTransmission) return eval;
91 |
92 | const float3 w_i_frame = shadingFrame.worldToFrame(w_i);
93 | const float3 w_o_frame = shadingFrame.worldToFrame(w_o);
94 |
95 | switch (type) {
96 | case BSDFType::StandardPBR: {
97 | StandardPBR m = StandardPBR::load(addr, extIOR, texcoords, λ);
98 | eval = m.evaluate(w_i_frame, w_o_frame);
99 | break;
100 | }
101 | case BSDFType::Lambert: {
102 | Lambert m = Lambert::load(addr, texcoords, λ);
103 | eval = m.evaluate(w_i_frame, w_o_frame);
104 | break;
105 | }
106 | case BSDFType::PerfectMirror: {
107 | PerfectMirror m;
108 | eval = m.evaluate(w_i_frame, w_o_frame);
109 | break;
110 | }
111 | case BSDFType::Glass: {
112 | Glass m = Glass::load(intIOR, extIOR, thin);
113 | eval = m.evaluate(w_i_frame, w_o_frame);
114 | break;
115 | }
116 | }
117 |
118 | return eval;
119 | }
120 |
121 | BSDFSample sample(float3 w_o, float2 square) {
122 | const float3 w_o_frame = shadingFrame.worldToFrame(w_o);
123 | BSDFSample sample;
124 | switch (type) {
125 | case BSDFType::StandardPBR: {
126 | StandardPBR m = StandardPBR::load(addr, extIOR, texcoords, λ);
127 | sample = m.sample(w_o_frame, square);
128 | break;
129 | }
130 | case BSDFType::Lambert: {
131 | Lambert m = Lambert::load(addr, texcoords, λ);
132 | sample = m.sample(w_o_frame, square);
133 | break;
134 | }
135 | case BSDFType::PerfectMirror: {
136 | PerfectMirror m;
137 | sample = m.sample(w_o_frame, square);
138 | break;
139 | }
140 | case BSDFType::Glass: {
141 | Glass m = Glass::load(intIOR, extIOR, thin);
142 | sample = m.sample(w_o_frame, square);
143 | break;
144 | }
145 | }
146 | sample.dir = shadingFrame.frameToWorld(sample.dir);
147 |
148 | // zero out cases where the shading normal and geometric normal disagree on the type of event
149 | const bool geometricTransmission = sign(dot(sample.dir, triangleFrame.n)) != sign(dot(w_o, triangleFrame.n));
150 | const bool shadingTransmission = sign(dot(sample.dir, shadingFrame.n)) != sign(dot(w_o, shadingFrame.n));
151 | if (geometricTransmission != shadingTransmission) {
152 | sample.eval.attenuation = 0;
153 | sample.eval.pdf = 0;
154 | }
155 |
156 | return sample;
157 | }
158 |
159 | bool isDelta() {
160 | switch (type) {
161 | case BSDFType::StandardPBR: {
162 | return StandardPBR::isDelta();
163 | }
164 | case BSDFType::Lambert: {
165 | return Lambert::isDelta();
166 | }
167 | case BSDFType::PerfectMirror: {
168 | return PerfectMirror::isDelta();
169 | }
170 | case BSDFType::Glass: {
171 | return Glass::isDelta();
172 | }
173 | }
174 | }
175 | };
176 |
--------------------------------------------------------------------------------
/src/lib/hrtsystem/shaders/medium.hlsl:
--------------------------------------------------------------------------------
1 | #pragma once
2 |
3 | #include "spectrum.hlsl"
4 |
5 | interface Medium {
6 | float transmittance(float distance);
7 | float pdf(float distance);
8 | float sample(float rand);
9 | };
10 |
11 | struct Homogeneous : Medium {
12 | float σ_s;
13 | float σ_a;
14 |
15 | static Homogeneous create(float σ_s, float σ_a) {
16 | Homogeneous h;
17 | h.σ_s = σ_s;
18 | h.σ_a = σ_a;
19 | return h;
20 | }
21 |
22 | static Homogeneous none() {
23 | return Homogeneous::create(0, 0);
24 | }
25 |
26 | float σ_t() {
27 | return σ_s + σ_a;
28 | }
29 |
30 | float transmittance(float distance) {
31 | if (σ_t() == 0) {
32 | // if distance == INF and σ_t() == 0 we want 1 as a result, not nan
33 | return 1;
34 | } else {
35 | return exp(-σ_t() * distance);
36 | }
37 | }
38 |
39 | // P(t > distance), integral of pdf beyond distance
40 | float pMoreThanT(float distance) {
41 | return transmittance(distance); // happens to be the same as transmittance for homogeneous media
42 | }
43 |
44 | float pdf(float distance) {
45 | return exp(-σ_t() * distance) * σ_t();
46 | }
47 |
48 | // TODO: doesn't sampling proportionally to σ_s (rather than σ_t) make more sense?
49 | // as the border case where σ_s == 0 should mean there is no scattering
50 | float sample(float rand) {
51 | return -log(1 - rand) / σ_t();
52 | }
53 | };
54 |
55 | struct ChromaticHomogeneous {
56 | float3 σ_s;
57 | float3 σ_a;
58 |
59 | Homogeneous at(const float λ) {
60 | return Homogeneous::create(Spectrum::sampleReflectance(λ, σ_s), Spectrum::sampleReflectance(λ, σ_a));
61 | }
62 | };
63 |
--------------------------------------------------------------------------------
/src/lib/hrtsystem/shaders/phase_function.hlsl:
--------------------------------------------------------------------------------
1 | #pragma once
2 |
3 | #include "../../shaders/utils/mappings.hlsl"
4 | #include "bsdf.hlsl"
5 |
6 | struct Isotropic : BSDF {
7 | BSDFEvaluation evaluate(float3 w_i, float3 w_o) {
8 | BSDFEvaluation eval;
9 | eval.attenuation = 1 / (4 * PI);
10 | eval.pdf = 1 / (4 * PI);
11 | return eval;
12 | }
13 |
14 | BSDFSample sample(float3 w_o, float2 square) {
15 | const float3 w_i = squareToUniformSphere(square);
16 | BSDFSample sample;
17 | sample.dir = w_i;
18 | sample.eval = evaluate(w_i, w_o);
19 | sample.eval.attenuation = sample.eval.attenuation / sample.eval.pdf;
20 | return sample;
21 | }
22 | };
23 |
24 | struct HenyeyGreenstein : BSDF {
25 | float g;
26 |
27 | static HenyeyGreenstein create(const float g) {
28 | HenyeyGreenstein phase;
29 | phase.g = g;
30 | return phase;
31 | }
32 |
33 | static float HG(const float g, const float cosTheta) {
34 | const float denom = 1 + g * g + 2 * g * cosTheta;
35 | return (1 - g * g) / (denom * sqrt(denom) * 4 * PI);
36 | }
37 |
38 | BSDFEvaluation evaluate(float3 w_i, float3 w_o) {
39 | BSDFEvaluation eval;
40 | eval.attenuation = HG(g, dot(w_i, w_o));
41 | eval.pdf = HG(g, dot(w_i, w_o));
42 | return eval;
43 | }
44 |
45 | BSDFSample sample(float3 w_o, float2 square) {
46 | // TODO: is there a sampling formulation here that doesn't need to special case zero?
47 | float cosTheta;
48 | if (abs(g) < 1e-3f) {
49 | cosTheta = 1 - 2 * square.x;
50 | } else {
51 | cosTheta = -1 / (2 * g) * (1 + g * g - (1 - g * g) * (1 - g * g) / (1 + g - 2 * g * square.x));
52 | }
53 | const float sinTheta = sqrt(1 - cosTheta * cosTheta);
54 | const float phi = 2 * PI * square.y;
55 | const float3 w_i = Frame::create(w_o).frameToWorld(sphericalToCartesian(sinTheta, cosTheta, phi));
56 | BSDFSample sample;
57 | sample.dir = w_i;
58 | sample.eval.pdf = HG(g, cosTheta);
59 | sample.eval.attenuation = 1;
60 | return sample;
61 | }
62 | };
--------------------------------------------------------------------------------
/src/lib/hrtsystem/shaders/ray.hlsl:
--------------------------------------------------------------------------------
1 | #pragma once
2 |
3 | struct Ray {
4 | float3 origin;
5 | float3 direction;
6 |
7 | RayDesc desc(float tmin, float tmax) {
8 | RayDesc desc;
9 | desc.Origin = origin;
10 | desc.Direction = direction;
11 | desc.TMin = tmin;
12 | desc.TMax = tmax;
13 | return desc;
14 | }
15 |
16 | Ray transformed(float3x4 mat) {
17 | Ray ray;
18 | ray.origin = mul(mat, float4(origin, 1.0));
19 | ray.direction = normalize(mul(mat, float4(direction, 0.0)));
20 | return ray;
21 | }
22 | };
--------------------------------------------------------------------------------
/src/lib/hrtsystem/shaders/reflection_frame.hlsl:
--------------------------------------------------------------------------------
1 | #pragma once
2 |
3 | #include "../../shaders/utils/math.hlsl"
4 |
5 | struct Frame {
6 | float3 n; // normal
7 | float3 s; // tangent
8 | float3 t; // bitangent
9 |
10 | // takes in a normalized vector, returns a frame where x,y,z are bitangent, normal, and tangent respectively
11 | static Frame create(float3 n) {
12 | float3 t, s;
13 | coordinateSystem(n, t, s);
14 | return Frame::create(n, s, t);
15 | }
16 |
17 | static Frame create(float3 n, float3 s, float3 t) {
18 | Frame frame;
19 | frame.n = n;
20 | frame.s = s;
21 | frame.t = t;
22 | return frame;
23 | }
24 |
25 | Frame inSpace(float3x3 m) {
26 | float3 n2 = normalize(mul(m, n));
27 | float3 s2 = normalize(mul(m, s));
28 | float3 t2 = normalize(mul(m, t));
29 |
30 | return Frame::create(n2, s2, t2);
31 | }
32 |
33 | void reorthogonalize() {
34 | // Gram-Schmidt
35 | s = normalize(s - n * dot(n, s));
36 | t = normalize(cross(n, s));
37 | }
38 |
39 | float3 worldToFrame(float3 v) {
40 | float3x3 toFrame = { s, t, n };
41 | return mul(toFrame, v);
42 | }
43 |
44 | float3 frameToWorld(float3 v) {
45 | float3x3 toFrame = { s, t, n };
46 | return mul(transpose(toFrame), v);
47 | }
48 |
49 | static float cosTheta(float3 v) {
50 | return v.z;
51 | }
52 |
53 | static float cos2Theta(float3 v) {
54 | return v.z * v.z;
55 | }
56 |
57 | static float sin2Theta(float3 v) {
58 | return max(0.0, 1.0 - cos2Theta(v));
59 | }
60 |
61 | static float sinTheta(float3 v) {
62 | return sqrt(sin2Theta(v));
63 | }
64 |
65 | static float tanTheta(float3 v) {
66 | return sinTheta(v) / cosTheta(v);
67 | }
68 |
69 | static float tan2Theta(float3 v) {
70 | return sin2Theta(v) / cos2Theta(v);
71 | }
72 |
73 | static float cosPhi(float3 v) {
74 | float sinTheta = Frame::sinTheta(v);
75 | return (sinTheta == 0.0) ? 1.0 : clamp(v.x / sinTheta, -1.0, 1.0);
76 | }
77 |
78 | static float sinPhi(float3 v) {
79 | float sinTheta = Frame::sinTheta(v);
80 | return (sinTheta == 0.0) ? 0.0 : clamp(v.y / sinTheta, -1.0, 1.0);
81 | }
82 |
83 | static bool sameHemisphere(float3 v1, float3 v2) {
84 | return v1.z * v2.z > 0.0;
85 | }
86 | };
87 |
88 |
--------------------------------------------------------------------------------
/src/lib/hrtsystem/shaders/scene.hlsl:
--------------------------------------------------------------------------------
1 | #pragma once
2 |
3 | #include "light.hlsl"
4 | #include "world.hlsl"
5 | #include "volume.hlsl"
6 |
7 | struct Scene {
8 | RaytracingAccelerationStructure tlas;
9 | World world;
10 | EnvMap envMap;
11 | InstanceLights instanceLights;
12 | ChromaticVolume globalVolume;
13 | };
14 |
--------------------------------------------------------------------------------
/src/lib/hrtsystem/shaders/spectrum.hlsl:
--------------------------------------------------------------------------------
1 | #pragma once
2 |
3 | [[vk::binding(0, 2)]] SamplerState dSpectrumSampler;
4 | [[vk::binding(1, 2)]] Texture1D dSpectrumCIEX;
5 | [[vk::binding(2, 2)]] Texture1D dSpectrumCIEY;
6 | [[vk::binding(3, 2)]] Texture1D dSpectrumCIEZ;
7 | [[vk::binding(4, 2)]] Texture1D dSpectrumR;
8 | [[vk::binding(5, 2)]] Texture1D dSpectrumG;
9 | [[vk::binding(6, 2)]] Texture1D dSpectrumB;
10 | [[vk::binding(7, 2)]] Texture1D dSpectrumD65;
11 |
12 | #include "../../shaders/utils/random.hlsl"
13 | #include "../../shaders/utils/math.hlsl"
14 |
15 | static const float CIE1931YIntegral = 106.85691710117189;
16 |
17 | namespace Spectrum {
18 | // exclusive range
19 | float sampleTabulated(const float λ, const float start, const float end, Texture1D t) {
20 | return t.SampleLevel(dSpectrumSampler, (λ - start) / (end - start), 0);
21 | }
22 |
23 | float sampleReflectance(const float λ, const float3 reflectance) {
24 | const float samplesStart = 360;
25 | const float samplesEnd = 831;
26 | const float3 rgb = float3(
27 | sampleTabulated(λ, samplesStart, samplesEnd, dSpectrumR),
28 | sampleTabulated(λ, samplesStart, samplesEnd, dSpectrumG),
29 | sampleTabulated(λ, samplesStart, samplesEnd, dSpectrumB)
30 | );
31 | return dot(rgb, reflectance);
32 | }
33 |
34 | // a somewhat roundabout way of doing this but I believe it's correct
35 | float sampleEmission(const float λ, const float3 emission) {
36 | const float sampledReflectance = sampleReflectance(λ, emission);
37 | const float sampledD65 = sampleTabulated(λ, 300, 831, dSpectrumD65);
38 | return sampledReflectance * sampledD65;
39 | }
40 |
41 | float3 toXYZ(const float λ, const float s) {
42 | const float samplesStart = 360;
43 | const float samplesEnd = 831;
44 | const float3 rgb = float3(
45 | sampleTabulated(λ, samplesStart, samplesEnd, dSpectrumCIEX),
46 | sampleTabulated(λ, samplesStart, samplesEnd, dSpectrumCIEY),
47 | sampleTabulated(λ, samplesStart, samplesEnd, dSpectrumCIEZ)
48 | );
49 | return rgb * s / CIE1931YIntegral;
50 | }
51 |
52 | float3 toLinearSRGB(const float λ, const float s) {
53 | const float3 xyz = toXYZ(λ, s);
54 | const float3x3 XYZtoLinearSRGB = { 0.03276749869518854, -0.015543557073358668, -0.00504115364541362, -0.009800789737065342, 0.018969392573362078, 0.00042019608374440075, 0.0005626856849785213, -0.0020631808449212427, 0.010691028014591895 };
55 | return mul(XYZtoLinearSRGB, xyz);
56 | }
57 | };
58 |
59 | struct WavelengthSample {
60 | float λ;
61 | float pdf;
62 |
63 | static WavelengthSample sampleUniform(const float start, const float end, const float rand) {
64 | WavelengthSample s;
65 | s.λ = lerp(start, end, rand);
66 | s.pdf = 1 / (end - start);
67 | return s;
68 | }
69 |
70 | static WavelengthSample sampleVisible(const float rand) {
71 | WavelengthSample s;
72 | s.λ = 538 - 138.888889f * atanh(0.85691062f - 1.82750197f * rand);
73 | s.pdf = 0.0039398042f / pow(cosh(0.0072f * (s.λ - 538)), 2);
74 | return s;
75 | }
76 | };
77 |
--------------------------------------------------------------------------------
/src/lib/hrtsystem/shaders/volume.hlsl:
--------------------------------------------------------------------------------
1 | #pragma once
2 |
3 | #include "medium.hlsl"
4 | #include "bsdf.hlsl"
5 | #include "phase_function.hlsl"
6 |
7 | struct Volume {
8 | Homogeneous medium;
9 | HenyeyGreenstein phase;
10 | float IOR;
11 |
12 | static Volume create(Homogeneous medium, HenyeyGreenstein phase, float IOR) {
13 | Volume v;
14 | v.medium = medium;
15 | v.phase = phase;
16 | v.IOR = IOR;
17 | return v;
18 | }
19 |
20 | static Volume transparent(float IOR) {
21 | return Volume::create(Homogeneous::none(), HenyeyGreenstein::create(0), IOR);
22 | }
23 | };
24 |
25 | struct ChromaticVolume {
26 | ChromaticHomogeneous medium;
27 | HenyeyGreenstein phase;
28 | CauchyIOR IOR;
29 |
30 | Volume at(const float λ) {
31 | return Volume::create(medium.at(λ), phase, IOR.at(λ));
32 | }
33 | };
34 |
--------------------------------------------------------------------------------
/src/lib/hrtsystem/shaders/volume_tracker.hlsl:
--------------------------------------------------------------------------------
1 | #include "volume.hlsl"
2 |
3 | // probably does not satisfy the actual definition of an algebra as it's not closed
4 | // and there's not always an inverse
5 | namespace VolumeAlgebra {
6 | Volume add(Volume lhs, Volume rhs) {
7 | Volume o;
8 | o.medium.σ_s = lhs.medium.σ_s + rhs.medium.σ_s;
9 | o.medium.σ_a = lhs.medium.σ_a + rhs.medium.σ_a;
10 | o.phase.g = lhs.phase.g + rhs.phase.g;
11 | o.IOR = lhs.IOR * rhs.IOR;
12 | return o;
13 | }
14 |
15 | Volume sub(Volume lhs, Volume rhs) {
16 | Volume o;
17 | o.medium.σ_s = lhs.medium.σ_s - rhs.medium.σ_s;
18 | o.medium.σ_a = lhs.medium.σ_a - rhs.medium.σ_a;
19 | o.phase.g = lhs.phase.g - rhs.phase.g;
20 | o.IOR = lhs.IOR / rhs.IOR;
21 | return o;
22 | }
23 |
24 | Volume clampToValid(Volume v) {
25 | v.medium.σ_s = max(v.medium.σ_s, 0);
26 | v.medium.σ_a = max(v.medium.σ_a, 0);
27 | v.phase.g = clamp(v.phase.g, -0.9999999, 0.9999999); // TODO: handle delta phase functions
28 | return v;
29 | }
30 | };
31 |
32 | struct VolumeTracker {
33 | static const uint VOLUME_PRIORITY_COUNT = 8;
34 |
35 | Volume current[VOLUME_PRIORITY_COUNT];
36 | int depth[VOLUME_PRIORITY_COUNT - 1]; // last is always global, which is known to have depth == 0
37 |
38 | Volume other;
39 | bool inside;
40 | uint priority;
41 |
42 | // find volume stack containing the world-space position. expensive
43 | static VolumeTracker fromPosition(Scene scene, float3 position, float λ) {
44 | VolumeTracker t;
45 | for (uint i = 0; i < VOLUME_PRIORITY_COUNT - 1; i++) {
46 | t.depth[i] = 0;
47 | t.current[i + 1] = scene.globalVolume.at(λ);
48 | }
49 | t.current[0] = scene.globalVolume.at(λ);
50 | t.inside = true; // should be unused
51 | t.priority = 0;
52 |
53 | Ray ray = { position, float3(0, 1, 0) }; // direction arbitrary
54 | for (Intersection its = Intersection::find(scene.tlas, ray, 1.#INF, ~Instance::THIN_MASK); its.hit(); its = Intersection::find(scene.tlas, ray, 1.#INF, ~Instance::THIN_MASK)) {
55 | const SurfacePoint surface = scene.world.surfacePoint(its.instanceIndex, its.geometryIndex, its.primitiveIndex, its.barycentrics);
56 | const Volume volume = scene.world.material(its.instanceIndex, its.geometryIndex).volume.at(λ);
57 | const uint priority = scene.world.priority(its.instanceIndex);
58 |
59 | const bool entering = dot(ray.direction, surface.triangleFrame.n) < 0;
60 | if (entering) {
61 | // make sure we don't get something *slightly* above zero when we should've had zero
62 | // the more principled thing here is probably some ULP shenanigans
63 | t.current[priority] = VolumeAlgebra::sub(t.current[priority], VolumeAlgebra::add(volume, Volume::create(Homogeneous::create(volume.medium.σ_s * 0.0000001, volume.medium.σ_a * 0.0000001), HenyeyGreenstein::create(0), 1)));
64 | t.depth[priority - 1] -= 1;
65 | } else {
66 | t.current[priority] = VolumeAlgebra::add(t.current[priority], volume);
67 | t.depth[priority - 1] += 1;
68 | }
69 | ray.origin = surface.position + faceForward(surface.triangleFrame.n, ray.direction) * surface.spawnOffset;
70 | }
71 |
72 | t.other = t.current[t.activePriority()];
73 | return t;
74 | }
75 |
76 | // surface should only be considered if this returns true
77 | bool newBoundary(bool newInside, uint newPriority, Volume newVolume) {
78 | inside = newInside;
79 | priority = newPriority;
80 | if (inside) {
81 | if (depth[priority - 1] > 1) {
82 | other = VolumeAlgebra::sub(current[priority], newVolume);
83 | } else {
84 | other = current[0];
85 | for (uint i = priority - 1; i > 0; i--) {
86 | if (depth[i - 1] != 0) {
87 | other = current[i];
88 | break;
89 | }
90 | }
91 | }
92 | } else {
93 | if (depth[priority - 1] > 0) {
94 | other = VolumeAlgebra::add(current[priority], newVolume);
95 | } else {
96 | other = newVolume;
97 | }
98 | }
99 | return activePriority() <= priority;
100 | }
101 |
102 | bool isIndexMatched() {
103 | return currentVolume().IOR == other.IOR;
104 | }
105 |
106 | Volume currentVolume() {
107 | // assume that negative numbers here are due to bad roundoff, and clamp
108 | return VolumeAlgebra::clampToValid(current[activePriority()]);
109 | }
110 |
111 | uint activePriority() {
112 | for (uint i = VOLUME_PRIORITY_COUNT - 1; i > 0; i--) {
113 | if (depth[i - 1] != 0) return i;
114 | }
115 | return 0;
116 | }
117 |
118 | Volume internal() {
119 | if (inside) {
120 | return currentVolume();
121 | } else {
122 | return VolumeAlgebra::clampToValid(other);
123 | }
124 | }
125 |
126 | Volume external() {
127 | if (inside) {
128 | return VolumeAlgebra::clampToValid(other);
129 | } else {
130 | return currentVolume();
131 | }
132 | }
133 |
134 | void cross() {
135 | current[priority] = other;
136 | depth[priority - 1] += inside ? -1 : 1;
137 | }
138 | };
--------------------------------------------------------------------------------
/src/lib/hrtsystem/shaders/world.hlsl:
--------------------------------------------------------------------------------
1 | #pragma once
2 |
3 | #include "reflection_frame.hlsl"
4 | #include "material.hlsl"
5 |
6 | struct Instance { // same required by vulkan on host side
7 | row_major float3x4 transform;
8 | uint instanceCustomIndex : 24;
9 | uint mask : 8;
10 | uint instanceShaderBindingTableRecordOffset : 24;
11 | uint flags : 8;
12 | uint64_t accelerationStructureReference;
13 |
14 | static const uint THIN_MASK = 0b10000000;
15 |
16 | bool thin() {
17 | return mask == THIN_MASK;
18 | }
19 |
20 | uint priority() {
21 | return firstbithigh(mask) + 1;
22 | }
23 | };
24 |
25 | struct Geometry {
26 | uint meshIndex;
27 | uint materialIndex;
28 | uint64_t trianglePowersAddress;
29 | uint64_t trianglePowersSize;
30 | };
31 |
32 | struct SurfacePoint {
33 | float3 position;
34 | float2 texcoord;
35 |
36 | Frame triangleFrame; // from triangle positions
37 | Frame frame; // from vertex attributes
38 |
39 | float spawnOffset; // minimum offset along normal that a ray will not intersect
40 | };
41 |
42 | float3 loadPosition(uint64_t addr, uint index) {
43 | return vk::RawBufferLoad(addr + sizeof(float3) * index);
44 | }
45 |
46 | float2 loadTexcoord(uint64_t addr, uint index) {
47 | return vk::RawBufferLoad(addr + sizeof(float2) * index);
48 | }
49 |
50 | float3 loadNormal(uint64_t addr, uint index) {
51 | return vk::RawBufferLoad(addr + sizeof(float3) * index);
52 | }
53 |
54 | void getTangentBitangent(float3 positions[3], float2 texcoords[3], out float3 tangent, out float3 bitangent) {
55 | float2 deltaT10 = texcoords[1] - texcoords[0];
56 | float2 deltaT20 = texcoords[2] - texcoords[0];
57 |
58 | float3 deltaP10 = positions[1] - positions[0];
59 | float3 deltaP20 = positions[2] - positions[0];
60 |
61 | float det = deltaT10.x * deltaT20.y - deltaT10.y * deltaT20.x;
62 | if (det == 0.0) {
63 | coordinateSystem(normalize(cross(deltaP10, deltaP20)), tangent, bitangent);
64 | } else {
65 | tangent = normalize((deltaT20.y * deltaP10 - deltaT10.y * deltaP20) / det);
66 | bitangent = normalize((-deltaT20.x * deltaP10 + deltaT10.x * deltaP20) / det);
67 | }
68 | }
69 |
70 | template
71 | T interpolate(float3 barycentrics, T v[3]) {
72 | return barycentrics.x * v[0] + barycentrics.y * v[1] + barycentrics.z * v[2];
73 | }
74 |
75 | struct TriangleLocalSpace {
76 | float3 positions[3];
77 | float3 normals[3];
78 | float2 texcoords[3];
79 |
80 | // TODO: currently this does both barycentric evaluation and local to world
81 | // conversion. there's probably a way to decouple this into two functions
82 | SurfacePoint surfacePoint(const float2 attribs, const float3x4 toWorld, const float3x4 toLocal) {
83 | SurfacePoint surface;
84 |
85 | const float3 barycentrics = float3(1.0 - attribs.x - attribs.y, attribs.x, attribs.y);
86 |
87 | const float3 edge1 = positions[1] - positions[0];
88 | const float3 edge2 = positions[2] - positions[0];
89 | surface.position = positions[0] + ((attribs.x * edge1) + (attribs.y * edge2));
90 |
91 | surface.texcoord = interpolate(barycentrics, texcoords);
92 |
93 | getTangentBitangent(positions, texcoords, surface.triangleFrame.s, surface.triangleFrame.t);
94 | surface.triangleFrame.n = normalize(cross(edge1, edge2));
95 | surface.triangleFrame.reorthogonalize();
96 |
97 | surface.frame = surface.triangleFrame;
98 | surface.frame.n = normalize(interpolate(barycentrics, normals));
99 | surface.frame.reorthogonalize();
100 |
101 | const float3 worldPosition = mul(toWorld, float4(surface.position, 1.0));
102 |
103 | const float3x3 inverseTranspose = transpose((float3x3)toLocal);
104 | // https://developer.nvidia.com/blog/solving-self-intersection-artifacts-in-directx-raytracing/
105 | {
106 | float3 wldNormal = mul(inverseTranspose, surface.triangleFrame.n);
107 |
108 | const float wldScale = rsqrt(dot(wldNormal, wldNormal));
109 | wldNormal = mul(wldScale, wldNormal);
110 |
111 | // nvidia magic constants
112 | const float c0 = 5.9604644775390625E-8f;
113 | const float c1 = 1.788139769587360206060111522674560546875E-7f;
114 | const float c2 = 1.19209317972490680404007434844970703125E-7f;
115 |
116 | const float3 extent3 = abs(edge1) + abs(edge2) + abs(edge1 - edge2);
117 | const float extent = max(max(extent3.x, extent3.y), extent3.z);
118 |
119 | float3 objErr = c0 * abs(positions[0]) + mul(c1, extent);
120 |
121 | const float3 wldErr = c1 * mul(abs((float3x3)toWorld), abs(surface.position)) + mul(c2, abs(transpose(toWorld)[3]));
122 |
123 | objErr += c2 * mul(abs(toLocal), float4(abs(worldPosition), 1));
124 |
125 | const float wldOffset = dot(wldErr, abs(wldNormal));
126 | const float objOffset = dot(objErr, abs(surface.triangleFrame.n));
127 |
128 | surface.spawnOffset = wldScale * objOffset + wldOffset;
129 | }
130 |
131 | // convert to world space
132 | {
133 | surface.position = worldPosition;
134 |
135 | surface.triangleFrame = surface.triangleFrame.inSpace(inverseTranspose);
136 | surface.frame = surface.frame.inSpace(inverseTranspose);
137 | }
138 |
139 | return surface;
140 | }
141 |
142 | float3 bivector() {
143 | return cross(positions[1] - positions[0], positions[2] - positions[0]);
144 | }
145 |
146 | float area(const float3x4 toLocal, const float3x4 toWorld) {
147 | const float3x3 inverseTranspose = transpose((float3x3)toLocal);
148 | const float3x3 cofactor = inverseTranspose * determinant((float3x3)toWorld);
149 | const float3 globalBivector = mul(cofactor, bivector());
150 | return length(globalBivector) / 2.0;
151 | }
152 | };
153 |
154 | struct Mesh {
155 | uint64_t positionAddress;
156 | uint64_t texcoordAddress; // may be zero, for no texcoords
157 | uint64_t normalAddress; // may be zero, for no vertex normals
158 |
159 | uint64_t indexAddress; // may be zero, for unindexed geometry
160 |
161 | uint64_t triangleCount;
162 |
163 | TriangleLocalSpace triangleLocalSpace(const uint primitiveIndex) {
164 | TriangleLocalSpace t;
165 |
166 | const uint3 ind = indexAddress != 0 ? vk::RawBufferLoad(indexAddress + sizeof(uint3) * primitiveIndex) : float3(primitiveIndex * 3 + 0, primitiveIndex * 3 + 1, primitiveIndex * 3 + 2);
167 |
168 | // positions always available
169 | t.positions[0] = loadPosition(positionAddress, ind.x);
170 | t.positions[1] = loadPosition(positionAddress, ind.y);
171 | t.positions[2] = loadPosition(positionAddress, ind.z);
172 |
173 | // texcoords optional
174 | if (texcoordAddress != 0) {
175 | t.texcoords[0] = loadTexcoord(texcoordAddress, ind.x);
176 | t.texcoords[1] = loadTexcoord(texcoordAddress, ind.y);
177 | t.texcoords[2] = loadTexcoord(texcoordAddress, ind.z);
178 | } else {
179 | // sane defaults for constant textures
180 | t.texcoords[0] = float2(0, 0);
181 | t.texcoords[1] = float2(1, 0);
182 | t.texcoords[2] = float2(1, 1);
183 | }
184 |
185 | // normals optional
186 | if (normalAddress != 0) {
187 | t.normals[0] = loadNormal(normalAddress, ind.x);
188 | t.normals[1] = loadNormal(normalAddress, ind.y);
189 | t.normals[2] = loadNormal(normalAddress, ind.z);
190 | } else {
191 | // use triangle normal
192 | const float3 normal = normalize(cross(t.positions[1] - t.positions[0], t.positions[2] - t.positions[0]));
193 | t.normals[0] = normal;
194 | t.normals[1] = normal;
195 | t.normals[2] = normal;
196 | }
197 |
198 | return t;
199 | }
200 | };
201 |
202 | struct Model {
203 | uint geometryOffset;
204 | uint geometryCount;
205 | uint64_t geometryPowersAddress;
206 | uint64_t geometryPowersSize;
207 | };
208 |
209 | struct World {
210 | StructuredBuffer instances;
211 | StructuredBuffer worldToInstance;
212 |
213 | StructuredBuffer models;
214 | StructuredBuffer meshes;
215 | StructuredBuffer geometries;
216 |
217 | StructuredBuffer materials;
218 |
219 | Mesh mesh(uint instanceIndex, uint geometryIndex) {
220 | const uint instanceID = instances[instanceIndex].instanceCustomIndex;
221 | const Geometry geometry = geometries[NonUniformResourceIndex(models[instanceID].geometryOffset + geometryIndex)];
222 | return meshes[NonUniformResourceIndex(geometry.meshIndex)];
223 | }
224 |
225 | Material material(uint instanceIndex, uint geometryIndex) {
226 | const uint instanceID = instances[instanceIndex].instanceCustomIndex;
227 | const Geometry geometry = geometries[NonUniformResourceIndex(models[instanceID].geometryOffset + geometryIndex)];
228 | return materials[NonUniformResourceIndex(geometry.materialIndex)];
229 | }
230 |
231 | bool thin(uint instanceIndex) {
232 | return instances[instanceIndex].thin();
233 | }
234 |
235 | uint priority(uint instanceIndex) {
236 | return instances[instanceIndex].priority();
237 | }
238 |
239 | float triangleArea(uint instanceIndex, uint geometryIndex, uint primitiveIndex) {
240 | return triangleLocalSpace(instanceIndex, geometryIndex, primitiveIndex).area(toLocal(instanceIndex), toWorld(instanceIndex));
241 | }
242 |
243 | TriangleLocalSpace triangleLocalSpace(uint instanceIndex, uint geometryIndex, uint primitiveIndex) {
244 | return this.mesh(instanceIndex, geometryIndex).triangleLocalSpace(primitiveIndex);
245 | }
246 |
247 | float3x4 toWorld(uint instanceIndex) {
248 | return instances[NonUniformResourceIndex(instanceIndex)].transform;
249 | }
250 |
251 | float3x4 toLocal(uint instanceIndex) {
252 | return worldToInstance[NonUniformResourceIndex(instanceIndex)];
253 | }
254 |
255 | SurfacePoint surfacePoint(uint instanceIndex, uint geometryIndex, uint primitiveIndex, float2 attribs) {
256 | return triangleLocalSpace(instanceIndex, geometryIndex, primitiveIndex).surfacePoint(attribs, toWorld(instanceIndex), toLocal(instanceIndex));
257 | }
258 | };
259 |
--------------------------------------------------------------------------------
/src/lib/shaders/utils/helpers.hlsl:
--------------------------------------------------------------------------------
1 | template
2 | uint2 textureDimensions(Texture2D texture) {
3 | uint2 dimensions;
4 | texture.GetDimensions(dimensions.x, dimensions.y);
5 | return dimensions;
6 | }
7 |
8 | template
9 | uint2 textureDimensions(RWTexture2D texture) {
10 | uint2 dimensions;
11 | texture.GetDimensions(dimensions.x, dimensions.y);
12 | return dimensions;
13 | }
14 |
15 | template
16 | uint textureDimensions(Texture1D texture) {
17 | uint dimension;
18 | texture.GetDimensions(dimension);
19 | return dimension;
20 | }
21 |
22 | template
23 | uint textureDimensions(RWTexture1D texture) {
24 | uint dimension;
25 | texture.GetDimensions(dimension);
26 | return dimension;
27 | }
28 |
29 | template
30 | uint bufferDimensions(StructuredBuffer buffer) {
31 | uint size;
32 | uint stride;
33 | buffer.GetDimensions(size, stride);
34 | return size;
35 | }
36 |
37 | template
38 | uint bufferDimensions(RWStructuredBuffer buffer) {
39 | uint size;
40 | uint stride;
41 | buffer.GetDimensions(size, stride);
42 | return size;
43 | }
44 |
--------------------------------------------------------------------------------
/src/lib/shaders/utils/mappings.hlsl:
--------------------------------------------------------------------------------
1 | #pragma once
2 |
3 | #include "math.hlsl"
4 |
5 | // https://eheitzresearch.wordpress.com/749-2/
6 | float2 squareToTriangle(float2 square) {
7 | if (square.x < square.y) {
8 | return float2(square.x / 2, square.y - (square.x / 2));
9 | } else {
10 | return float2(square.x - (square.y / 2), square.y / 2);
11 | }
12 | }
13 |
14 | float2 squareToGaussian(float2 square) {
15 | const float u1 = 1.0 - square.x;
16 | const float u2 = square.y;
17 | const float r = sqrt(-2.0 * log(u1));
18 | const float theta = 2 * PI * u2;
19 | return r * float2(cos(theta), sin(theta));
20 | }
21 |
22 | float2 squareToUniformDiskConcentric(float2 square) {
23 | float2 uOffset = 2.0 * square - float2(1.0, 1.0);
24 |
25 | if (all(uOffset == float2(0.0, 0.0))) {
26 | return float2(0.0, 0.0);
27 | }
28 |
29 | float theta, r;
30 |
31 | if (abs(uOffset.x) > abs(uOffset.y)) {
32 | r = uOffset.x;
33 | theta = (PI / 4) * (uOffset.y / uOffset.x);
34 | } else {
35 | r = uOffset.y;
36 | theta = (PI / 2) - (PI / 4) * (uOffset.x / uOffset.y);
37 | }
38 |
39 | return r * float2(cos(theta), sin(theta));
40 | }
41 |
42 | float3 squareToCosineHemisphere(float2 square) {
43 | float2 d = squareToUniformDiskConcentric(square);
44 | float z = sqrt(max(0.0, 1.0 - dot(d, d)));
45 |
46 | return float3(d.x, d.y, z);
47 | }
48 |
49 | float3 squareToUniformHemisphere(float2 square) {
50 | float z = square.x;
51 | float r = sqrt(max(0.0, 1.0 - z * z));
52 | float phi = 2 * PI * square.y;
53 | return float3(r * cos(phi), r * sin(phi), z);
54 | }
55 |
56 | float3 squareToUniformSphere(float2 square) {
57 | float z = 1 - 2 * square.x;
58 | float r = sqrt(1 - z * z);
59 | float phi = 2 * PI * square.y;
60 | return float3(r * float2(cos(phi), sin(phi)), z);
61 | }
62 |
63 | float3 sphericalToCartesian(float sinTheta, float cosTheta, float phi) {
64 | return float3(sinTheta * cos(phi), sinTheta * sin(phi), cosTheta);
65 | }
66 |
67 | // (phi, theta) -- ([0, 2pi], [0, pi])
68 | // assumes vector normalized
69 | float2 cartesianToSpherical(float3 v) {
70 | float p = atan2(v.y, v.x);
71 | float phi = (p < 0) ? (p + 2 * PI) : p;
72 | float theta = acos(v.z);
73 | return float2(phi, theta);
74 | }
75 |
76 | // from PBRTv4 3.8.3 "Equal-Area Mapping"
77 | float3 squareToEqualAreaSphere(float2 square) {
78 | const float2 uv = 2.0 * square - float2(1.0, 1.0);
79 | const float2 uvp = abs(uv);
80 |
81 | const float signedDistance = 1.0 - (uvp.x + uvp.y);
82 | const float d = abs(signedDistance);
83 | const float r = 1.0 - d;
84 |
85 | const float phi = (r == 0.0 ? 1.0 : (uvp.y - uvp.x) / r + 1.0) * PI / 4.0;
86 | const float3 signs = sign(float3(uv.x, uv.y, signedDistance));
87 |
88 | return signs * float3(
89 | cos(phi) * r * sqrt(2.0 - r * r),
90 | sin(phi) * r * sqrt(2.0 - r * r),
91 | 1.0f - r * r
92 | );
93 | }
94 |
95 | float2 squareToEqualAreaSphereInverse(float3 dir) {
96 | const float3 xyz = abs(dir);
97 | const float r = sqrt(1.0 - xyz.z);
98 |
99 | float phi = all(xyz.xy == float2(0.0, 0.0)) ? 0.0 : atan2(min(xyz.x, xyz.y), max(xyz.x, xyz.y)) * 2.0 / PI;
100 | if (xyz.x < xyz.y) phi = 1.0 - phi;
101 |
102 | float2 uv = float2(r - phi * r, phi * r);
103 |
104 | if (dir.z < 0) uv = float2(1.0, 1.0) - float2(uv.y, uv.x);
105 |
106 | uv *= sign(dir.xy);
107 |
108 | return (uv + float2(1.0, 1.0)) / 2.0;
109 | }
110 |
111 | // selects true with probability p (false otherwise),
112 | // remapping rand back into (0..1)
113 | bool coinFlipRemap(float p, inout float rand) {
114 | if (rand < p) {
115 | rand /= p;
116 | return true;
117 | } else {
118 | rand = (rand - p) / (1.0 - p);
119 | return false;
120 | }
121 | }
122 |
--------------------------------------------------------------------------------
/src/lib/shaders/utils/math.hlsl:
--------------------------------------------------------------------------------
1 | #pragma once
2 |
3 | static const float PI = 3.14159265;
4 | static const float EPSILON = 0.000000119;
5 | static const uint MAX_UINT = 0xFFFFFFFF;
6 |
7 | float3 vectorToColor(float3 v) {
8 | return (v + 1.0) / 2.0;
9 | }
10 |
11 | float atanh(float x) {
12 | return log((1 + x) / (1 - x)) / 2;
13 | }
14 |
15 | float luminance(float3 color) {
16 | return 0.2126 * color.r +
17 | 0.7152 * color.g +
18 | 0.0722 * color.b;
19 | }
20 |
21 | float3 faceForward(float3 n, float3 d) {
22 | return dot(n, d) > 0 ? n : -n;
23 | }
24 |
25 | float2 faceForward(float2 n, float2 d) {
26 | return dot(n, d) > 0 ? n : -n;
27 | }
28 |
29 | // https://www.nu42.com/2015/03/how-you-average-numbers.html
30 | float3 accumulate(const float3 priorAverage, const float3 newSample, const uint sampleCount) {
31 | return priorAverage + (newSample - priorAverage) / (sampleCount + 1);
32 | }
33 |
34 | void coordinateSystem(float3 v1, out float3 v2, out float3 v3) {
35 | if (abs(v1.x) > abs(v1.y)) {
36 | v2 = float3(-v1.z, 0.0, v1.x) / sqrt(v1.x * v1.x + v1.z * v1.z);
37 | } else {
38 | v2 = float3(0.0, v1.z, -v1.y) / sqrt(v1.y * v1.y + v1.z * v1.z);
39 | }
40 |
41 | v3 = cross(v2, v1);
42 | }
43 |
44 | uint log2Int(const uint x) {
45 | return firstbithigh(x);
46 | }
47 |
48 | uint log2IntCeil(const uint x) {
49 | if (x == 1) return 0;
50 | return log2Int(x - 1) + 1;
51 | }
52 |
53 | float normL1(const float3 v) {
54 | return abs(v.x) + abs(v.y) + abs(v.z);
55 | }
56 |
57 | float normL2(const float3 v) {
58 | return length(v);
59 | }
60 |
61 | float normLInf(const float3 v) {
62 | return max(max(abs(v.x), abs(v.y)), abs(v.z));
63 | }
64 |
65 | // TODO: switch to this when figure it why it doesn't work
66 | // void coordinateSystem(float3 v1, out float3 v2, out float3 v3) {
67 | // float sign = v1.z >= 0.0f ? 1.0f : -1.0f;
68 | // float a = -1 / (sign + v1.z);
69 | // float b = v1.x * v1.y * a;
70 | // v2 = float3(1 + sign * sqrt(v1.x) * a, sign * b, -sign * v1.x);
71 | // v3 = float3(b, sign + sqrt(v1.y) * a, -v1.y);
72 | // }
73 |
--------------------------------------------------------------------------------
/src/lib/shaders/utils/random.hlsl:
--------------------------------------------------------------------------------
1 | #pragma once
2 |
3 | #include "math.hlsl"
4 |
5 | // https://www.reedbeta.com/blog/hash-functions-for-gpu-rendering/
6 | // https://jcgt.org/published/0009/03/02/
7 | namespace Hash {
8 | uint lcg(uint a) {
9 | const uint multiplier = 747796405u;
10 | const uint increment = 2891336453u;
11 | return a * multiplier + increment;
12 | }
13 |
14 | // RXS-M-XS PCG permutation function
15 | uint rxs_m_xs(uint a) {
16 | const uint b = ((a >> ((a >> 28u) + 4u)) ^ a) * 277803737u;
17 | return (b >> 22u) ^ b;
18 | }
19 |
20 | uint pcg(uint a) {
21 | return rxs_m_xs(lcg(a));
22 | }
23 | }
24 |
25 | struct Rng {
26 | uint state;
27 |
28 | static Rng fromSeed(uint3 seed) {
29 | Rng rng;
30 | rng.state = Hash::pcg(seed.x + Hash::pcg(seed.y + Hash::pcg(seed.z)));
31 | return rng;
32 | }
33 |
34 | void stepState() {
35 | state = Hash::lcg(state);
36 | }
37 |
38 | float getFloat() {
39 | stepState();
40 |
41 | uint hashed_uint = Hash::rxs_m_xs(state);
42 |
43 | // convert to float [0-1)
44 | // https://pharr.org/matt/blog/2022/03/05/sampling-fp-unit-interval
45 | return float(hashed_uint >> 8) * 0x1p-24f;
46 | }
47 | };
48 |
--------------------------------------------------------------------------------
/src/lib/shaders/utils/reservoir.hlsl:
--------------------------------------------------------------------------------
1 | #pragma once
2 |
3 | #include "mappings.hlsl"
4 |
5 | template
6 | struct Reservoir {
7 | Candidate selected;
8 | float weightSum;
9 |
10 | static Reservoir empty() {
11 | Reservoir r;
12 | r.weightSum = 0.0;
13 | return r;
14 | }
15 |
16 | void update(const Candidate newCandidate, const float newWeight, inout float rand) {
17 | weightSum += newWeight;
18 | if (coinFlipRemap(newWeight / weightSum, rand)) {
19 | selected = newCandidate;
20 | }
21 | }
22 |
23 | // not valid to look at selected unless this returns true
24 | bool valid() {
25 | return weightSum != 0;
26 | }
27 | };
28 |
--------------------------------------------------------------------------------
/src/lib/test_runner.zig:
--------------------------------------------------------------------------------
1 | const std = @import("std");
2 | const builtin = @import("builtin");
3 |
4 | const engine = @import("engine");
5 |
6 | pub fn main() void {
7 | const enable_print = true;
8 | const print_all = true;
9 |
10 | var passed: u64 = 0;
11 | var skipped: u64 = 0;
12 | var failed: u64 = 0;
13 | const stderr = if (enable_print) std.io.getStdErr() else {};
14 | for (builtin.test_functions) |test_fn| {
15 | if (enable_print and print_all) {
16 | stderr.writeAll(test_fn.name) catch {};
17 | stderr.writeAll("... ") catch {};
18 | }
19 | test_fn.func() catch |err| {
20 | if (enable_print and !print_all) {
21 | stderr.writeAll(test_fn.name) catch {};
22 | stderr.writeAll("... ") catch {};
23 | }
24 | if (err != error.SkipZigTest) {
25 | if (enable_print) stderr.writer().print("FAIL {}\n", .{ err }) catch {};
26 | failed += 1;
27 | if (!enable_print) return err;
28 | continue;
29 | }
30 | if (enable_print) stderr.writeAll("SKIP\n") catch {};
31 | skipped += 1;
32 | continue;
33 | };
34 | if (enable_print and print_all) stderr.writeAll("PASS\n") catch {};
35 | passed += 1;
36 | }
37 | if (enable_print) {
38 | stderr.writer().print("{} passed, {} skipped, {} failed\n", .{ passed, skipped, failed }) catch {};
39 | if (failed != 0) std.process.exit(1);
40 | }
41 | }
42 |
43 |
--------------------------------------------------------------------------------