├── .gitattributes ├── .gitignore ├── .vscode ├── launch.json ├── settings.json └── tasks.json ├── Cargo.lock ├── Cargo.toml ├── LICENSE ├── README.md ├── assets ├── castle.vox ├── shaders │ ├── auto_exposure.comp │ ├── auto_exposure.playout │ ├── auto_exposure_avg.comp │ ├── final_gather │ │ ├── ambient_occlusion.rchit │ │ ├── ambient_occlusion.rgen │ │ ├── ambient_occlusion.rint │ │ ├── ambient_occlusion.rmiss │ │ ├── final_gather.rchit │ │ ├── final_gather.rgen │ │ ├── final_gather.rmiss │ │ ├── nee.rmiss │ │ └── rough.rint │ ├── headers │ │ ├── camera.glsl │ │ ├── color.glsl │ │ ├── layout.playout │ │ ├── normal.glsl │ │ ├── nrd.glsl │ │ ├── reservoir.glsl │ │ ├── sbt.glsl │ │ ├── sky.glsl │ │ ├── spatial_hash.glsl │ │ ├── standard.glsl │ │ └── surfel.glsl │ ├── primary │ │ ├── hit.rchit │ │ ├── hit.rint │ │ ├── miss.rmiss │ │ └── primary.rgen │ ├── surfel │ │ ├── nee.rmiss │ │ ├── surfel.rchit │ │ ├── surfel.rgen │ │ └── surfel.rmiss │ ├── tone_map.comp │ └── tone_map.playout ├── stbn │ ├── scalar_2Dx1Dx1D_128x128x64x1.png │ ├── unitvec2_2Dx1D_128x128x64.png │ ├── unitvec3_2Dx1D_128x128x64.png │ ├── unitvec3_cosine_2Dx1D_128x128x64.png │ ├── vec2_2Dx1D_128x128x64.png │ └── vec3_2Dx1D_128x128x64.png └── teapot.vox ├── crates ├── render │ ├── Cargo.toml │ └── src │ │ ├── accel_struct │ │ ├── blas.rs │ │ ├── instance_vec.rs │ │ ├── mod.rs │ │ └── tlas.rs │ │ ├── deferred_task.rs │ │ ├── geometry.rs │ │ ├── lib.rs │ │ ├── material.rs │ │ ├── noise.rs │ │ ├── pipeline │ │ ├── auto_exposure.rs │ │ ├── builder.rs │ │ ├── cache.rs │ │ ├── compute.rs │ │ ├── dataset.bin │ │ ├── datasetSolar.bin │ │ ├── manager.rs │ │ ├── mod.rs │ │ ├── nrd.rs │ │ ├── plugin.rs │ │ ├── sky.rs │ │ ├── standard.rs │ │ └── tone_mapping.rs │ │ ├── projection.rs │ │ ├── sbt.rs │ │ └── shader │ │ ├── glsl.rs │ │ ├── mod.rs │ │ └── spirv.rs ├── rhyolite │ ├── Cargo.toml │ └── src │ │ ├── accel_struct │ │ ├── blas.rs │ │ ├── build.rs │ │ └── mod.rs │ │ ├── allocator.rs │ │ ├── commands │ │ ├── mod.rs │ │ └── pool.rs │ │ ├── debug.rs │ │ ├── descriptor │ │ ├── layout.rs │ │ ├── mod.rs │ │ └── pool.rs │ │ ├── device.rs │ │ ├── dho.rs │ │ ├── error_handler.rs │ │ ├── future │ │ ├── block.rs │ │ ├── exec.rs │ │ ├── ext.rs │ │ ├── mod.rs │ │ └── state.rs │ │ ├── instance.rs │ │ ├── lib.rs │ │ ├── physical_device.rs │ │ ├── pipeline │ │ ├── cache.rs │ │ ├── compute.rs │ │ ├── layout.rs │ │ ├── mod.rs │ │ └── rtx.rs │ │ ├── queue │ │ ├── compile.rs │ │ ├── exec.rs │ │ ├── mod.rs │ │ └── router.rs │ │ ├── resources │ │ ├── buffer.rs │ │ ├── copy.rs │ │ ├── image.rs │ │ ├── image_view.rs │ │ ├── managed_buffer_vec.rs │ │ ├── mod.rs │ │ └── staging_ring_buffer.rs │ │ ├── sampler.rs │ │ ├── semaphore.rs │ │ ├── shader.rs │ │ ├── surface.rs │ │ ├── swapchain.rs │ │ └── utils │ │ ├── either.rs │ │ ├── format.rs │ │ ├── merge_iter.rs │ │ ├── merge_ranges.rs │ │ ├── mod.rs │ │ ├── retainer.rs │ │ ├── send_marker.rs │ │ └── unsized_vec.rs ├── rhyolite_bevy │ ├── Cargo.toml │ └── src │ │ ├── image.rs │ │ ├── lib.rs │ │ ├── loaders │ │ ├── mod.rs │ │ └── png.rs │ │ ├── queue.rs │ │ ├── swapchain.rs │ │ └── types.rs ├── rhyolite_macro │ ├── Cargo.toml │ └── src │ │ ├── commands.rs │ │ ├── commands_join.rs │ │ ├── gpu.rs │ │ ├── lib.rs │ │ ├── push_constant.rs │ │ └── transformer.rs ├── sentry │ ├── Cargo.toml │ └── src │ │ └── lib.rs ├── vdb │ ├── Cargo.toml │ └── src │ │ ├── accessor.rs │ │ ├── bitmask.rs │ │ ├── lib.rs │ │ ├── node │ │ ├── internal.rs │ │ ├── leaf.rs │ │ ├── mod.rs │ │ └── root.rs │ │ ├── pool.rs │ │ └── tree.rs └── vox │ ├── Cargo.toml │ └── src │ ├── collector.rs │ ├── geometry.rs │ ├── lib.rs │ ├── loader.rs │ ├── material.rs │ └── palette.rs ├── examples └── castle.rs ├── rust-toolchain.toml └── src └── lib.rs /.gitattributes: -------------------------------------------------------------------------------- 1 | *.vox filter=lfs diff=lfs merge=lfs -text 2 | *.png filter=lfs diff=lfs merge=lfs -text 3 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | *.spv 3 | /imported_assets 4 | /assets/**/*.meta 5 | -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | // Use IntelliSense to learn about possible attributes. 3 | // Hover to view descriptions of existing attributes. 4 | // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 5 | "version": "0.2.0", 6 | "configurations": [ 7 | { 8 | "type": "lldb", 9 | "request": "launch", 10 | "name": "Debug example 'castle'", 11 | "cargo": { 12 | "args": [ 13 | "build", 14 | "--example=castle", 15 | "--package=dust" 16 | ], 17 | "filter": { 18 | "name": "castle", 19 | "kind": "example" 20 | } 21 | }, 22 | "args": [], 23 | "cwd": "${workspaceFolder}", 24 | "env": { 25 | "CARGO_MANIFEST_DIR": "${workspaceFolder}" 26 | } 27 | } 28 | ] 29 | } 30 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "shaderc-lint.glslcArgs": "--target-env=vulkan1.3 -O -g", 3 | "shaderc-lint.buildOnSave": false, 4 | "shaderc-lint.includeSupport": true, 5 | "shaderc-lint.defaultGLSLVersion": "460" 6 | } 7 | -------------------------------------------------------------------------------- /.vscode/tasks.json: -------------------------------------------------------------------------------- 1 | { 2 | // See https://go.microsoft.com/fwlink/?LinkId=733558 3 | // for the documentation about the tasks.json format 4 | "version": "2.0.0", 5 | "tasks": [ 6 | { 7 | "label": "Run castle example", 8 | "type": "shell", 9 | "command": "cargo", // note: full path to the cargo 10 | "args": [ 11 | "run", 12 | "--release", 13 | "--example", 14 | "castle" 15 | ], 16 | "group": { 17 | "kind": "build", 18 | "isDefault": true 19 | } 20 | } 21 | ] 22 | } 23 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "dust" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | [workspace] 7 | members = [ 8 | "crates/*" 9 | ] 10 | 11 | [dependencies] 12 | dust-sentry = { path = "./crates/sentry", optional = true } 13 | 14 | [dev-dependencies] 15 | pin-project = "1.0" 16 | rhyolite = { path = "./crates/rhyolite" } 17 | rhyolite-bevy = { path = "./crates/rhyolite_bevy" } 18 | dust-render = { path = "./crates/render" } 19 | dust-vox = { path = "./crates/vox" } 20 | bevy_app = { git = "https://github.com/bevyengine/bevy.git", rev = "2c7eab1b4c4ec6c533b6b609d5ddf8a7282f2c4f" } 21 | bevy_ecs = { git = "https://github.com/bevyengine/bevy.git", rev = "2c7eab1b4c4ec6c533b6b609d5ddf8a7282f2c4f" } 22 | bevy_log = { git = "https://github.com/bevyengine/bevy.git", rev = "2c7eab1b4c4ec6c533b6b609d5ddf8a7282f2c4f" } 23 | bevy_window = { git = "https://github.com/bevyengine/bevy.git", rev = "2c7eab1b4c4ec6c533b6b609d5ddf8a7282f2c4f" } 24 | bevy_input = { git = "https://github.com/bevyengine/bevy.git", rev = "2c7eab1b4c4ec6c533b6b609d5ddf8a7282f2c4f" } 25 | bevy_core = { git = "https://github.com/bevyengine/bevy.git", rev = "2c7eab1b4c4ec6c533b6b609d5ddf8a7282f2c4f" } 26 | bevy_hierarchy = { git = "https://github.com/bevyengine/bevy.git", rev = "2c7eab1b4c4ec6c533b6b609d5ddf8a7282f2c4f" } 27 | bevy_transform = { git = "https://github.com/bevyengine/bevy.git", rev = "2c7eab1b4c4ec6c533b6b609d5ddf8a7282f2c4f" } 28 | bevy_winit = { git = "https://github.com/bevyengine/bevy.git", rev = "2c7eab1b4c4ec6c533b6b609d5ddf8a7282f2c4f", features = ["x11", "wayland"] } 29 | bevy_time = { git = "https://github.com/bevyengine/bevy.git", rev = "2c7eab1b4c4ec6c533b6b609d5ddf8a7282f2c4f" } 30 | bevy_a11y = { git = "https://github.com/bevyengine/bevy.git", rev = "2c7eab1b4c4ec6c533b6b609d5ddf8a7282f2c4f" } 31 | bevy_asset = { git = "https://github.com/bevyengine/bevy.git", rev = "2c7eab1b4c4ec6c533b6b609d5ddf8a7282f2c4f", features = ["file_watcher"] } 32 | bevy_scene = { git = "https://github.com/bevyengine/bevy.git", rev = "2c7eab1b4c4ec6c533b6b609d5ddf8a7282f2c4f", default-features=false } 33 | bevy_diagnostic = { git = "https://github.com/bevyengine/bevy.git", rev = "2c7eab1b4c4ec6c533b6b609d5ddf8a7282f2c4f" } 34 | image = "0.24" 35 | glam = "^0.24" 36 | reqwest = { version = "*", features = ["blocking"] } 37 | smooth-bevy-cameras = { git = "https://github.com/Neo-Zhixing/smooth-bevy-cameras", rev = "2d11fd208395f7ab64d731683eb7bd3002fd8d41" } 38 | 39 | [features] 40 | sentry = ["dust-sentry"] 41 | aftermath = ["dust-sentry/aftermath"] 42 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Dust Engine 2 | 3 | The Dust Engine tries to become a powerful tool for creating immersive voxel-based games with stunning graphics. We're building the Dust Engine to explore and address some of the [big problems for voxel games](https://dust.rs/posts/12#the-big-problems-for-voxel-games). Our current prioity is createing a real-time global illumination renderer. Please see this [blog post](https://dust.rs/posts/14) for details on how it's working. 4 | 5 | It is built on top of the awesome [Bevy Engine](https://github.com/bevyengine/bevy). 6 | 7 | Please note that this engine is currently under development and is not ready for production use. However, we encourage you to explore and experiment with it. 8 | 9 | ## Features 10 | - Voxel-based worlds: The engine provides the infrastructure to create and manipulate voxel-based worlds. Voxel data can be efficiently stored and rendered to create immersive game environments. 11 | - Vulkan hardware raytracing: It leverages the power of Vulkan hardware raytracing to achieve real-time global illumination on detailed voxel scenes. 12 | - Future-based render graphs: The `rhyolite` library offers a simple async-like interface for GPU syncronization and resource lifetime management. The rendering pipeline is designed to be highly customizable. 13 | - Spatial Data structures: The storage and management of the scene geometry was powered by a spatial database inspired by OpenVDB, tailored for hardware ray tracing. 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | ## Requirements 22 | ### Nightly Rust compiler 23 | ### Compatible graphics card 24 | Your graphics card must have Vulkan 1.3 and hardware raytracing support. 25 | Please ensure that your hardware supports the required Vulkan extensions (`VK_KHR_ray_tracing_pipeline`, `VK_KHR_acceleration_structure`). 26 | That includes the following devices: 27 | - NVIDIA GTX1060 and above 28 | - AMD RX6600 and above 29 | - Intel Arc A380 and above 30 | - AMD RDNA2+ integrated graphics. That includes all Ryzen 6000 series laptop CPUs, all Ryzen 7000 series laptop CPUs except 7030 series, and potentially Steam Deck, PS5 and Xbox. 31 | 32 | ### Compatible operating system 33 | The engine is only substantially tested on Windows 34 | 35 | Should also work on Linux, but it's tested to a lesser degree. If you run into any problems, please let us know on our [Discord Server](https://discord.com/invite/7R26SXn8CT). 36 | 37 | Windows Subsystem for Linux is unsupported. 38 | 39 | macOS support is possible in the future through MoltenVK. 40 | 41 | ## How to run 42 | First, download and install the [Vulkan SDK](https://vulkan.lunarg.com/sdk/home). We need the GLSL shader compiler from it. 43 | 44 | Before cloning this repository, ensure that you have [Git LFS](https://git-lfs.com/) installed. If you cloned 45 | the repository without Git LFS, manually pull the LFS files: 46 | ```bash 47 | git lfs fetch --all 48 | ``` 49 | 50 | Finally, compile and run the `castle` example. 51 | ```bash 52 | cd .. 53 | cargo run --release --example castle 54 | ``` 55 | ## What if this doesn't work 56 | If you encounter any issues while running this program, we highly encourage you to enable the `sentry` feature 57 | when compiling the program. [Sentry](https://sentry.io) helps us identify and debug issues by providing detailed crash reports. 58 | 59 | If you are experiencing a DEVICE_LOST error while using an NVIDIA GPU, we recommend enabling NVIDIA Aftermath to help us diagnose the issue. NVIDIA Aftermath provides additional information about GPU crashes and can assist in debugging. 60 | 61 | ```sh 62 | cargo run --release --example castle --features sentry 63 | 64 | # For DEVICE_LOST crashes on NVIDIA GPUs, you may also send NVIDIA Aftermath Crash Reports 65 | cargo run --release --example castle --features sentry --features aftermath 66 | 67 | ``` 68 | 69 | 70 | ## Contributing 71 | The Dust Engine is still in very early stages of development, and we welcome and appreciate contributions of any kind. 72 | Your input, ideas, and expertise can make a real difference in the development of this project. If you encounter any issues, have suggestions, or would like to contribute, feel free to join our [Discord server](https://discord.com/invite/7R26SXn8CT). 73 | 74 | ## License 75 | The Voxel Game Engine is released under the MPL-2.0 License. Please review the license file for more details. 76 | 77 | -------------------------------------------------------------------------------- /assets/castle.vox: -------------------------------------------------------------------------------- 1 | version https://git-lfs.github.com/spec/v1 2 | oid sha256:cbc09a7c84fc44d5f669c0c0801715fb281c2c2c09ae78853c9ee1ac7a4161a2 3 | size 88233039 4 | -------------------------------------------------------------------------------- /assets/shaders/auto_exposure.comp: -------------------------------------------------------------------------------- 1 | #version 460 2 | #extension GL_EXT_debug_printf : enable 3 | layout(local_size_x = 16, local_size_y = 16, local_size_z = 1) in; 4 | 5 | layout(set = 0, binding = 0, rgba16f) uniform image2D u_illuminance; 6 | layout(set = 0, binding = 1) uniform Params { 7 | float minLogLum; 8 | float logLumRange; 9 | float timeCoeff; 10 | } u_params; 11 | layout(set = 0, binding = 2) buffer Histogram { 12 | uint histogram[256]; 13 | float avg; 14 | } u_histogram; 15 | 16 | 17 | shared uint histogramShared[256]; 18 | 19 | // For a given color and luminance range, return the histogram bin index 20 | uint colorToBin(vec3 hdrColor) { 21 | // Convert our RGB value to Luminance, see note for RGB_TO_LUM macro above 22 | float lum = dot(hdrColor, vec3(0.299, 0.587, 0.114)); 23 | 24 | // Avoid taking the log of zero 25 | if (lum < 0.005) { 26 | return 0; 27 | } 28 | 29 | // Calculate the log_2 luminance and express it as a value in [0.0, 1.0] 30 | // where 0.0 represents the minimum luminance, and 1.0 represents the max. 31 | float logLum = clamp((log2(lum) - u_params.minLogLum) * (1.0 / u_params.logLumRange), 0.0, 1.0); 32 | 33 | // Map [0, 1] to [1, 255]. The zeroth bin is handled by the epsilon check above. 34 | return uint(logLum * 254.0 + 1.0); 35 | } 36 | 37 | vec3 _NRD_YCoCgToLinear( vec3 color ) 38 | { 39 | float t = color.x - color.z; 40 | 41 | vec3 r; 42 | r.y = color.x + color.z; 43 | r.x = t + color.y; 44 | r.z = t - color.y; 45 | 46 | return max( r, 0.0 ); 47 | } 48 | vec4 REBLUR_BackEnd_UnpackRadianceAndNormHitDist( vec4 data ) 49 | { 50 | data.xyz = _NRD_YCoCgToLinear( data.xyz ); 51 | 52 | return data; 53 | } 54 | 55 | 56 | void main() { 57 | // clear shared memory bins 58 | histogramShared[gl_LocalInvocationIndex] = 0; 59 | barrier(); 60 | 61 | uvec2 dim = imageSize(u_illuminance).xy; 62 | // Ignore threads that map to areas beyond the bounds of our HDR image 63 | if (gl_GlobalInvocationID.x < dim.x && gl_GlobalInvocationID.y < dim.y) { 64 | vec4 hdrColor = imageLoad(u_illuminance, ivec2(gl_GlobalInvocationID.xy)); 65 | hdrColor = REBLUR_BackEnd_UnpackRadianceAndNormHitDist(hdrColor); 66 | uint binIndex = colorToBin(hdrColor.xyz); 67 | // We use an atomic add to ensure we don't write to the same bin in our 68 | // histogram from two different threads at the same time. 69 | atomicAdd(histogramShared[binIndex], 1); 70 | } 71 | 72 | barrier(); 73 | atomicAdd(u_histogram.histogram[gl_LocalInvocationIndex], histogramShared[gl_LocalInvocationIndex]); 74 | } -------------------------------------------------------------------------------- /assets/shaders/auto_exposure.playout: -------------------------------------------------------------------------------- 1 | struct Params { 2 | minLogLum: f32, 3 | logLumRange: f32, 4 | timeCoeff: f32, 5 | } 6 | struct Histogram { 7 | histogram: [u32; 256], 8 | avg: f32, 9 | } 10 | #[set] 11 | struct AutoExposureLayout { 12 | #![stage(COMPUTE)] 13 | illuminance: StorageImage, 14 | params: InlineUniformBlock, 15 | histogram: StorageBuffer, 16 | } 17 | 18 | -------------------------------------------------------------------------------- /assets/shaders/auto_exposure_avg.comp: -------------------------------------------------------------------------------- 1 | #version 460 2 | #extension GL_EXT_control_flow_attributes: require 3 | layout(local_size_x = 256, local_size_y = 1, local_size_z = 1) in; 4 | 5 | layout(set = 0, binding = 0, rgba16f) uniform image2D u_illuminance; 6 | layout(set = 0, binding = 1) uniform Params { 7 | float minLogLum; 8 | float logLumRange; 9 | float timeCoeff; 10 | } u_params; 11 | layout(set = 0, binding = 2) buffer Histogram { 12 | uint histogram[256]; 13 | float avg; 14 | } u_histogram; 15 | 16 | 17 | shared uint histogramShared[256]; 18 | 19 | void main() { 20 | uint countForThisBin = u_histogram.histogram[gl_LocalInvocationIndex]; 21 | 22 | 23 | // This gives higher weight to higher lum pixels.... why??? 24 | histogramShared[gl_LocalInvocationIndex] = countForThisBin * gl_LocalInvocationIndex; 25 | barrier(); 26 | u_histogram.histogram[gl_LocalInvocationIndex] = 0; 27 | 28 | [[unroll]] 29 | for (uint cutoff = (256 >> 1); cutoff > 0; cutoff >>= 1) { 30 | if (gl_LocalInvocationIndex < cutoff) { 31 | histogramShared[gl_LocalInvocationIndex] += histogramShared[gl_LocalInvocationIndex + cutoff]; 32 | } 33 | barrier(); 34 | } 35 | // Here we take our weighted sum and divide it by the number of pixels 36 | // that had luminance greater than zero (since the index == 0, we can 37 | // use countForThisBin to find the number of black pixels) 38 | 39 | if (gl_LocalInvocationIndex == 0) { 40 | uvec2 dim = imageSize(u_illuminance).xy; 41 | uint numPixels = dim.x * dim.y; 42 | float weightedLogAverage = (histogramShared[0] / max(numPixels, 1.0)) - 1.0; 43 | 44 | // Map from our histogram space to actual luminance 45 | float weightedAvgLum = exp2(((weightedLogAverage / 254.0) * u_params.logLumRange) + u_params.minLogLum); 46 | 47 | // The new stored value will be interpolated using the last frames value 48 | // to prevent sudden shifts in the exposure. 49 | float lumLastFrame = u_histogram.avg; 50 | float adaptedLum = lumLastFrame + (weightedAvgLum - lumLastFrame) * u_params.timeCoeff; 51 | u_histogram.avg = adaptedLum; 52 | } 53 | } -------------------------------------------------------------------------------- /assets/shaders/final_gather/ambient_occlusion.rchit: -------------------------------------------------------------------------------- 1 | #include "../headers/standard.glsl" 2 | #include "../headers/layout.playout" 3 | #include "../headers/nrd.glsl" 4 | 5 | layout(location = 0) rayPayloadInEXT struct RayPayload { 6 | vec3 illuminance; 7 | } payload; 8 | 9 | 10 | void main() { 11 | vec4 packed = REBLUR_FrontEnd_PackRadianceAndNormHitDist(payload.illuminance, gl_HitTEXT); 12 | imageStore(img_illuminance, ivec2(gl_LaunchIDEXT.xy), packed); 13 | } 14 | -------------------------------------------------------------------------------- /assets/shaders/final_gather/ambient_occlusion.rgen: -------------------------------------------------------------------------------- 1 | #include "../headers/standard.glsl" 2 | #include "../headers/layout.playout" 3 | #include "../headers/nrd.glsl" 4 | #include "../headers/normal.glsl" 5 | #include "../headers/camera.glsl" 6 | 7 | 8 | 9 | layout(location = 0) rayPayloadEXT struct RayPayload { 10 | vec3 illuminance; 11 | } payload; 12 | 13 | 14 | void main() { 15 | float hitT = imageLoad(img_depth, ivec2(gl_LaunchIDEXT.xy)).x; 16 | if (hitT == 1.0 / 0.0) { 17 | // Did not hit. 18 | return; 19 | } 20 | 21 | float unused; 22 | vec3 normalWorld = NRD_FrontEnd_UnpackNormalAndRoughness(imageLoad(img_normal, ivec2(gl_LaunchIDEXT.xy)), unused).xyz; 23 | vec3 hitLocation = hitT * camera_ray_dir() + camera_origin() + normalWorld * 0.01; 24 | 25 | vec4 in_value = imageLoad(img_illuminance, ivec2(gl_LaunchIDEXT.xy)); 26 | in_value = REBLUR_BackEnd_UnpackRadianceAndNormHitDist(in_value); 27 | 28 | vec3 noiseSample = texelFetch(blue_noise[5], ivec2((gl_LaunchIDEXT.xy + uvec2(7, 183) + push_constants.rand) % textureSize(blue_noise[5], 0)), 0).xyz * 2.0 - 1.0; 29 | // noiseSample is weighted on the z axis 30 | noiseSample = rotateVectorByNormal(normalWorld, noiseSample); 31 | 32 | payload.illuminance = in_value.xyz; 33 | #ifdef CONTRIBUTION_DIRECT 34 | vec3 sunDir = sunlight_config.direction.xyz; 35 | if (dot(sunDir, normalWorld) > 0.0) { 36 | traceRayEXT( 37 | acceleration_structure, 38 | gl_RayFlagsOpaqueEXT | gl_RayFlagsSkipClosestHitShaderEXT | gl_RayFlagsTerminateOnFirstHitEXT, // RayFlags 39 | 0xFF, // CullMask 40 | 1, // SBT offset, ray type index // Use the same intersection shader. We need higher-quality intersection for shadow rays as well. 41 | 4, // SBT stride, number of ray types // TODO: Make this a shader constant 42 | 5, // missIndex 43 | hitLocation, // ray origin 44 | 0.1, // ray min range. If we set this to 0.0, VK_DEVICE_LOST. Time stuck: 2 days 45 | normalize(sunDir), // outgoing direction: cosine weighted on the normal direction 46 | 10000.0, // tmax 47 | 0 // payload 48 | ); 49 | } 50 | #endif 51 | 52 | // Shoot shadow ray 53 | traceRayEXT( 54 | acceleration_structure, 55 | gl_RayFlagsOpaqueEXT, // RayFlags 56 | 0xFF, // CullMask 57 | 1, // SBT offset, ray type index // Use the same intersection shader. We need higher-quality intersection for shadow rays as well. 58 | 4, // SBT stride, number of ray types // TODO: Make this a shader constant 59 | 1, // missIndex 60 | hitLocation, // ray origin 61 | 0.1, // ray min range. If we set this to 0.0, VK_DEVICE_LOST. Time stuck: 2 days 62 | normalize(noiseSample), // direction 63 | AMBIENT_OCCLUSION_THRESHOLD, // tmax 64 | 0 // payload 65 | ); 66 | } 67 | -------------------------------------------------------------------------------- /assets/shaders/final_gather/ambient_occlusion.rmiss: -------------------------------------------------------------------------------- 1 | #include "../headers/standard.glsl" 2 | #include "../headers/layout.playout" 3 | #include "../headers/nrd.glsl" 4 | 5 | layout(location = 0) rayPayloadInEXT struct RayPayload { 6 | vec3 illuminance; 7 | } payload; 8 | 9 | 10 | void main() { 11 | vec4 packed = REBLUR_FrontEnd_PackRadianceAndNormHitDist(payload.illuminance, 0.0); 12 | imageStore(img_illuminance, ivec2(gl_LaunchIDEXT.xy), packed); 13 | } 14 | -------------------------------------------------------------------------------- /assets/shaders/final_gather/final_gather.rchit: -------------------------------------------------------------------------------- 1 | 2 | #include "../headers/standard.glsl" 3 | #include "../headers/sbt.glsl" 4 | #include "../headers/normal.glsl" 5 | #include "../headers/layout.playout" 6 | #include "../headers/spatial_hash.glsl" 7 | #include "../headers/surfel.glsl" 8 | #include "../headers/color.glsl" 9 | #include "../headers/nrd.glsl" 10 | 11 | 12 | hitAttributeEXT HitAttribute { 13 | uint _unused; 14 | } hitAttributes; 15 | layout(location = 0) rayPayloadInEXT struct RayPayload { 16 | vec3 illuminance; 17 | } payload; 18 | 19 | #ifdef SHADER_INT_64 20 | #define GridType uint64_t 21 | uint GridNumVoxels(GridType grid) { 22 | u32vec2 unpacked = unpack32(grid); 23 | return bitCount(masked.x) + bitCount(masked.y); 24 | } 25 | #else 26 | #define GridType u32vec2 27 | uint GridNumVoxels(GridType grid) { 28 | return bitCount(grid.x) + bitCount(grid.y); 29 | } 30 | #endif 31 | 32 | // The ray goes from the primary hit point to the secondary hit point. 33 | // We sample the spatial hash at the secondary hit point, find out 34 | // the outgoing radiance, and store into the radiance texture. 35 | void main() { 36 | Block block = sbt.geometryInfo.blocks[gl_PrimitiveID]; 37 | 38 | vec3 aabbCenterObject = block.position.xyz + 2.0; 39 | vec3 hitPointObject = gl_HitTEXT * gl_ObjectRayDirectionEXT + gl_ObjectRayOriginEXT; 40 | vec3 aabbNormalObject = hitPointObject - aabbCenterObject; // normal of the larger voxels 41 | vec3 aabbNormalWorld = CubedNormalize(gl_ObjectToWorldEXT * vec4(aabbNormalObject, 0.0)); 42 | vec3 aabbCenterWorld = gl_ObjectToWorldEXT * vec4(aabbCenterObject, 1.0); // Center of the larger voxels 43 | 44 | 45 | SpatialHashKey key; 46 | key.position = ivec3((aabbCenterWorld / 4.0)); 47 | key.direction = normal2FaceID(aabbNormalWorld); 48 | 49 | vec3 indirect_radiance; 50 | uint sample_count; 51 | bool found = SpatialHashGet(key, indirect_radiance, sample_count); 52 | float probability_to_schedule = 1.0 / float(sample_count + 2); 53 | float noise_sample = texelFetch(blue_noise[0], ivec2((gl_LaunchIDEXT.xy + uvec2(34, 21) + push_constants.rand) % textureSize(blue_noise[0], 0)), 0).x; 54 | 55 | if (noise_sample > probability_to_schedule) { 56 | uint index = gl_LaunchIDEXT.x + gl_LaunchIDEXT.y * gl_LaunchSizeEXT.x; 57 | index = index % SurfelPoolSize; 58 | 59 | SurfelEntry entry; 60 | entry.position = aabbCenterWorld; 61 | entry.direction = normal2FaceID(aabbNormalWorld); 62 | surfel_pool[index] = entry; 63 | } 64 | 65 | // indirect radiance is the incoming radiance at the secondary hit location. 66 | // Multiply with albedo to convert into outgoing radiance at secondary hit location. 67 | // Convert into albedo 68 | uint32_t packed_albedo = block.avg_albedo; 69 | vec4 albedo = vec4( 70 | float((packed_albedo >> 22) & 1023) / 1023.0, 71 | float((packed_albedo >> 12) & 1023) / 1023.0, 72 | float((packed_albedo >> 2) & 1023) / 1023.0, 73 | float(packed_albedo & 3) / 3.0 74 | ); 75 | 76 | albedo.x = SRGBToLinear(albedo.x); 77 | albedo.y = SRGBToLinear(albedo.y); 78 | albedo.z = SRGBToLinear(albedo.z); 79 | 80 | indirect_radiance = sRGB2AECScg(AECScg2sRGB(indirect_radiance) * albedo.xyz); 81 | 82 | vec3 value = payload.illuminance; 83 | #ifdef CONTRIBUTION_SECONDARY_SPATIAL_HASH 84 | value += indirect_radiance; 85 | #endif 86 | vec4 packed = REBLUR_FrontEnd_PackRadianceAndNormHitDist(value, gl_HitTEXT); 87 | 88 | #ifndef DEBUG_VISUALIZE_SPATIAL_HASH 89 | imageStore(img_illuminance, ivec2(gl_LaunchIDEXT.xy), packed); 90 | #endif 91 | } 92 | 93 | // TODO: final gather and surfel should both use the corser grid. 94 | -------------------------------------------------------------------------------- /assets/shaders/final_gather/final_gather.rgen: -------------------------------------------------------------------------------- 1 | #include "../headers/standard.glsl" 2 | #include "../headers/layout.playout" 3 | #include "../headers/nrd.glsl" 4 | #include "../headers/normal.glsl" 5 | #include "../headers/camera.glsl" 6 | 7 | 8 | 9 | layout(location = 0) rayPayloadEXT struct RayPayload { 10 | vec3 illuminance; 11 | } payload; 12 | 13 | 14 | void main() { 15 | float hitT = imageLoad(img_depth, ivec2(gl_LaunchIDEXT.xy)).x; 16 | if (hitT == 1.0 / 0.0) { 17 | // Did not hit. 18 | return; 19 | } 20 | 21 | vec4 in_value = imageLoad(img_illuminance, ivec2(gl_LaunchIDEXT.xy)); 22 | in_value = REBLUR_BackEnd_UnpackRadianceAndNormHitDist(in_value); 23 | if (in_value.w > 0.0) { 24 | // Ambient occlusion pass resolved. 25 | return; 26 | } 27 | 28 | float unused; 29 | vec3 normalWorld = NRD_FrontEnd_UnpackNormalAndRoughness(imageLoad(img_normal, ivec2(gl_LaunchIDEXT.xy)), unused).xyz; 30 | vec3 hitLocation = hitT * camera_ray_dir() + camera_origin() + normalWorld * 0.01; 31 | 32 | vec3 noiseSample = texelFetch(blue_noise[5], ivec2((gl_LaunchIDEXT.xy + uvec2(7, 183) + push_constants.rand) % textureSize(blue_noise[5], 0)), 0).xyz * 2.0 - 1.0; 33 | // noiseSample is weighted on the z axis 34 | noiseSample = rotateVectorByNormal(normalWorld, noiseSample); 35 | 36 | // Shoot shadow ray 37 | payload.illuminance = in_value.xyz; 38 | 39 | traceRayEXT( 40 | acceleration_structure, 41 | gl_RayFlagsOpaqueEXT, // RayFlags 42 | 0xFF, // CullMask 43 | 2, // SBT offset, ray type index // Use the same intersection shader. We need higher-quality intersection for shadow rays as well. 44 | 4, // SBT stride, number of ray types // TODO: Make this a shader constant 45 | 2, // missIndex 46 | hitLocation, // ray origin 47 | AMBIENT_OCCLUSION_THRESHOLD, // ray min range. If we set this to 0.0, VK_DEVICE_LOST. Time stuck: 2 days 48 | normalize(noiseSample), // direction 49 | camera.far, // tmax 50 | 0 // payload 51 | ); 52 | } 53 | -------------------------------------------------------------------------------- /assets/shaders/final_gather/final_gather.rmiss: -------------------------------------------------------------------------------- 1 | #include "../headers/standard.glsl" 2 | #include "../headers/layout.playout" 3 | #include "../headers/color.glsl" 4 | #include "../headers/sky.glsl" 5 | #include "../headers/nrd.glsl" 6 | 7 | layout(location = 0) rayPayloadInEXT struct RayPayload { 8 | vec3 illuminance; 9 | } payload; 10 | 11 | 12 | void main() { 13 | vec3 sky_illuminance = payload.illuminance; 14 | 15 | #ifdef CONTRIBUTION_SECONDARY_SKYLIGHT 16 | sky_illuminance += arhosek_sky_radiance(normalize(gl_WorldRayDirectionEXT)); 17 | #endif 18 | vec4 packed = REBLUR_FrontEnd_PackRadianceAndNormHitDist(sky_illuminance, 0.0); 19 | 20 | #ifndef DEBUG_VISUALIZE_SPATIAL_HASH 21 | imageStore(img_illuminance, ivec2(gl_LaunchIDEXT.xy), packed); 22 | #endif 23 | 24 | } 25 | -------------------------------------------------------------------------------- /assets/shaders/final_gather/nee.rmiss: -------------------------------------------------------------------------------- 1 | #include "../headers/standard.glsl" 2 | #include "../headers/layout.playout" 3 | #include "../headers/color.glsl" 4 | #include "../headers/sky.glsl" 5 | #include "../headers/nrd.glsl" 6 | 7 | layout(location = 0) rayPayloadInEXT struct RayPayload { 8 | vec3 illuminance; 9 | } payload; 10 | 11 | void main() { 12 | float unused; 13 | vec3 normalWorld = NRD_FrontEnd_UnpackNormalAndRoughness(imageLoad(img_normal, ivec2(gl_LaunchIDEXT.xy)), unused).xyz; 14 | // On each frame, the ray hits the sun with probability (1.0 - cos(sunlight_config.solar_intensity.w)) 15 | // But with the shadow rays, we hit the sun with probability 1. 16 | // So, we adjust the radiance of the sun by that factor. 17 | 18 | vec3 strength = arhosek_sun_radiance(normalize(gl_WorldRayDirectionEXT)) * 19 | (1.0 - cos(sunlight_config.solar_intensity.w)); 20 | 21 | payload.illuminance += strength * dot(normalWorld, gl_WorldRayDirectionEXT); 22 | } 23 | 24 | -------------------------------------------------------------------------------- /assets/shaders/final_gather/rough.rint: -------------------------------------------------------------------------------- 1 | #include "../headers/standard.glsl" 2 | #include "../headers/sbt.glsl" 3 | 4 | hitAttributeEXT HitAttribute { 5 | uint _unused; 6 | } hitAttributes; 7 | 8 | #ifdef SHADER_INT_64 9 | #define GridType uint64_t 10 | #define GridCheck(grid, hit) ((grid & (uint64_t(1) << hit)) == 0) 11 | #define GridIsEmpty(grid) (grid == 0) 12 | #else 13 | #define GridType u32vec2 14 | #define GridCheck(grid, hit) (((hit < 32) ?(grid.x & (1 << hit)):(grid.y & (1 << (hit - 32)))) == 0) 15 | #define GridIsEmpty(grid) (grid.x == 0 && grid.y == 0) 16 | #endif 17 | 18 | 19 | layout(constant_id = 2) const bool RoughIntersectionTest = false; 20 | 21 | vec2 intersectAABB(vec3 origin, vec3 dir, vec3 box_min, vec3 box_max) { 22 | vec3 tMin = (box_min - origin) / dir; 23 | vec3 tMax = (box_max - origin) / dir; 24 | vec3 t1 = min(tMin, tMax); 25 | vec3 t2 = max(tMin, tMax); 26 | float t_min = max(max(t1.x, t1.y), t1.z); 27 | float t_max = min(min(t2.x, t2.y), t2.z); 28 | return vec2(t_min, t_max); 29 | } 30 | 31 | // TODO: consolidate intersection shaders 32 | 33 | 34 | /** 35 | Intersects ray with a unix box with 4x4x4 voxels. A branchless implementation of 36 | Amanatides, John & Woo, Andrew. (1987). A Fast Voxel Traversal Algorithm for Ray Tracing. Proceedings of EuroGraphics. 87. 37 | 38 | @param(origin): The origin of the ray. 39 | @param(dir): The direction of the ray. Does not have to be normalized. 40 | @param(grid): The occupancy of the 64 voxels encoded in z-curve order. 41 | */ 42 | void dda(vec3 origin, vec3 dir, GridType grid, float scale) { 43 | // Init phase 44 | 45 | // The initialization phase begins by identifying the voxel in which the ray origin is found. If the ray 46 | // origin is outside the grid, we find the point in which the ray enters the grid and take the adjacent voxel. 47 | // The integer variables `position` are initialized to the starting voxel coordinates. 48 | 49 | // We assume that the AABB box is located in 0-1. We extend that to 0-4 so we match our DDAed unit box of 4x4x4. 50 | vec2 initialIntersectionT = intersectAABB(origin, dir, vec3(0.0, 0.0, 0.0), vec3(4.0, 4.0, 4.0)); 51 | if (initialIntersectionT.x >= initialIntersectionT.y) { 52 | // No intersection 53 | return; 54 | } 55 | if (GridIsEmpty(grid)) { 56 | return; 57 | } 58 | reportIntersectionEXT(initialIntersectionT.x, 0); 59 | } 60 | 61 | 62 | void main() 63 | { 64 | Block block = sbt.geometryInfo.blocks[gl_PrimitiveID]; 65 | vec3 min = block.position.xyz; 66 | 67 | dda( 68 | gl_ObjectRayOriginEXT - min, // origin 69 | gl_ObjectRayDirectionEXT, // dir 70 | #ifdef SHADER_INT_64 71 | block.mask, 72 | #else 73 | u32vec2(block.mask1, block.mask2), 74 | #endif 75 | 1.0 76 | ); 77 | } 78 | -------------------------------------------------------------------------------- /assets/shaders/headers/camera.glsl: -------------------------------------------------------------------------------- 1 | vec3 camera_origin() { 2 | return vec3(camera.position_x, camera.position_y, camera.position_z); 3 | } 4 | vec3 camera_ray_dir() { 5 | const vec2 pixelNDC = (vec2(gl_LaunchIDEXT.xy) + vec2(0.5)) / vec2(gl_LaunchSizeEXT.xy); 6 | 7 | vec2 pixelCamera = 2 * pixelNDC - 1; 8 | pixelCamera.y *= -1; 9 | pixelCamera.x *= float(gl_LaunchSizeEXT.x) / float(gl_LaunchSizeEXT.y); 10 | pixelCamera *= camera.tan_half_fov; 11 | 12 | const mat3 rotationMatrix = mat3(camera.camera_view_col0, camera.camera_view_col1, camera.camera_view_col2); 13 | 14 | const vec3 pixelCameraWorld = rotationMatrix * vec3(pixelCamera, -1); 15 | return pixelCameraWorld; 16 | } 17 | -------------------------------------------------------------------------------- /assets/shaders/headers/color.glsl: -------------------------------------------------------------------------------- 1 | float SRGBToLinear(float color) 2 | { 3 | // Approximately pow(color, 2.2) 4 | return color < 0.04045 ? color / 12.92 : pow(abs(color + 0.055) / 1.055, 2.4); 5 | } 6 | 7 | 8 | vec3 sRGB2AECScg(vec3 srgb) { 9 | mat3 transform = mat3( 10 | 0.6031065, 0.07011794, 0.022178888, 11 | 0.32633433, 0.9199162, 0.11607823, 12 | 0.047995567, 0.012763573, 0.94101846 13 | ); 14 | return transform * srgb; 15 | } 16 | vec3 AECScg2sRGB(vec3 srgb) { 17 | mat3 transform = mat3( 18 | 1.7312546, -0.131619, -0.024568284, 19 | -0.6040432, 1.1348418, -0.12575036, 20 | -0.08010775, -0.008679431, 1.0656371 21 | ); 22 | return transform * srgb; 23 | } 24 | vec3 XYZ2ACEScg(vec3 srgb) { 25 | mat3 transform = mat3( 26 | 1.6410228, -0.66366285, 0.011721907, 27 | -0.32480323, 1.6153315, -0.0082844375, 28 | -0.23642465, 0.016756356, 0.9883947 29 | ); 30 | return transform * srgb; 31 | } 32 | vec3 ACEScg2XYZ(vec3 srgb) { 33 | mat3 transform = mat3( 34 | 0.66245437, 0.2722288, -0.0055746622, 35 | 0.13400422, 0.6740818, 0.00406073, 36 | 0.15618773, 0.05368953, 1.0103393 37 | ); 38 | return transform * srgb; 39 | } 40 | -------------------------------------------------------------------------------- /assets/shaders/headers/layout.playout: -------------------------------------------------------------------------------- 1 | struct SurfelEntry { 2 | position: Vec3, // The coordinates of the center of the box. 3 | direction: u32, // [0, 6) indicating one of the six faces of the cube 4 | } 5 | 6 | struct PackedReservoir { 7 | sample_count: u16, 8 | direction: u32, 9 | radiance: u32, 10 | weight: f32, 11 | } 12 | 13 | struct SpatialHashEntry { 14 | fingerprint: u32, 15 | radiance: u32, 16 | last_accessed_frame: u16, 17 | sample_count: u16, 18 | } 19 | 20 | struct CameraSettings { 21 | view_proj: Mat4, 22 | inverse_view_proj: Mat4, 23 | camera_view_col0: Vec3, 24 | position_x: f32, 25 | camera_view_col1: Vec3, 26 | position_y: f32, 27 | camera_view_col2: Vec3, 28 | position_z: f32, 29 | tan_half_fov: f32, 30 | far: f32, 31 | near: f32, 32 | _padding: f32, 33 | } 34 | 35 | struct ArHosekSkyModelChannelConfiguration { 36 | configs0: Vec4, 37 | configs1: Vec4, 38 | configs2: f32, 39 | radiance: f32, 40 | ld_coefficient0: f32, 41 | ld_coefficient1: f32, 42 | ld_coefficient2: Vec4, 43 | } 44 | 45 | struct ArHosekSkyModelConfiguration { 46 | r: ArHosekSkyModelChannelConfiguration, 47 | g: ArHosekSkyModelChannelConfiguration, 48 | b: ArHosekSkyModelChannelConfiguration, 49 | direction: Vec4, // normalized. 50 | solar_intensity: Vec4, // w is solar radius 51 | } 52 | 53 | #[set] 54 | struct StandardLayout { 55 | #![stage(MISS | CLOSEST_HIT | RAYGEN)] 56 | img_illuminance: StorageImage, 57 | 58 | img_illuminance_denoised: StorageImage, 59 | 60 | img_albedo: StorageImage, 61 | img_normal: StorageImage, 62 | img_depth: StorageImage, 63 | img_motion: StorageImage, 64 | img_voxel_id: StorageImage, 65 | 66 | blue_noise: [SampledImage; 6], 67 | 68 | sunlight_config: UniformBuffer, 69 | camera_last_frame: UniformBuffer, 70 | camera: UniformBuffer, 71 | 72 | instances: StorageBuffer<[Mat4]>, 73 | 74 | #[layout = scalar] 75 | spatial_hash: StorageBuffer<[SpatialHashEntry]>, 76 | surfel_pool: StorageBuffer<[SurfelEntry]>, 77 | 78 | 79 | #[binding = 14] 80 | acceleration_structure: AccelerationStructure, 81 | } 82 | 83 | #[push_constants] 84 | struct PushConstants { 85 | #![stage(MISS | CLOSEST_HIT | RAYGEN)] 86 | rand: u32, 87 | frame_index: u32, 88 | } 89 | -------------------------------------------------------------------------------- /assets/shaders/headers/normal.glsl: -------------------------------------------------------------------------------- 1 | 2 | // input param: a vector with only one component being 1 or -1, the rest being 0 3 | // +1 0 0 | 0b101 4 | // -1 0 0 | 0b100 5 | // 0 +1 0 | 0b011 6 | // 0 -1 0 | 0b010 7 | // 0 0 +1 | 0b001 8 | // 0 0 -1 | 0b000 9 | uint8_t normal2FaceID(vec3 normalObject) { 10 | float s = clamp(normalObject.x + normalObject.y + normalObject.z, 0.0, 1.0); // Sign of the nonzero component 11 | uint8_t faceId = uint8_t(round(s)); // The lowest digit is 1 if the sign is positive, 0 otherwise 12 | 13 | // 4 (0b100) if z is the nonzero component, 2 (0b010) if y is the nonzero component, 0 if x is the nonzero component 14 | uint8_t index = uint8_t(round(abs(normalObject.z))) * uint8_t(4) + uint8_t(round(abs(normalObject.y))) * uint8_t(2); 15 | 16 | faceId += index; 17 | return faceId; 18 | } 19 | 20 | vec3 faceId2Normal(uint8_t faceId) { 21 | float s = float(faceId & 1) * 2.0 - 1.0; // Extract the lowest component and restore as the sign. 22 | 23 | vec3 normal = vec3(0); 24 | normal[faceId >> 1] = s; 25 | return normal; 26 | } 27 | 28 | // This function rotates `target` from the z axis by the same amount as `normal`. 29 | // param: normal: a unit vector. 30 | // sample: the vector to be rotated 31 | vec3 rotateVectorByNormal(vec3 normal, vec3 target) { 32 | vec4 quat = normalize(vec4(-normal.y, normal.x, 0.0, 1.0 + normal.z)); 33 | if (normal.z < -0.99999) { 34 | quat = vec4(-1.0, 0.0, 0.0, 0.0); 35 | } 36 | return 2.0 * dot(quat.xyz, target) * quat.xyz + (quat.w * quat.w - dot(quat.xyz, quat.xyz)) * target + 2.0 * quat.w * cross(quat.xyz, target); 37 | } 38 | 39 | vec3 CubedNormalize(vec3 dir) { 40 | vec3 dir_abs = abs(dir); 41 | float max_element = max(dir_abs.x, max(dir_abs.y, dir_abs.z)); 42 | return sign(dir) * step(max_element, dir_abs); 43 | } 44 | -------------------------------------------------------------------------------- /assets/shaders/headers/nrd.glsl: -------------------------------------------------------------------------------- 1 | 2 | vec2 _NRD_EncodeUnitVector( vec3 v, const bool bSigned ) 3 | { 4 | v /= dot( abs( v ), vec3(1.0) ); 5 | 6 | vec2 octWrap = ( 1.0 - abs( v.yx ) ) * ( step( 0.0, v.xy ) * 2.0 - 1.0 ); 7 | v.xy = v.z >= 0.0 ? v.xy : octWrap; 8 | 9 | return bSigned ? v.xy : v.xy * 0.5 + 0.5; 10 | } 11 | 12 | #define NRD_ROUGHNESS_ENCODING_SQ_LINEAR 0 // linearRoughness * linearRoughness 13 | #define NRD_ROUGHNESS_ENCODING_LINEAR 1 // linearRoughness 14 | #define NRD_ROUGHNESS_ENCODING_SQRT_LINEAR 2 // sqrt( linearRoughness ) 15 | 16 | #define NRD_NORMAL_ENCODING_RGBA8_UNORM 0 17 | #define NRD_NORMAL_ENCODING_RGBA8_SNORM 1 18 | #define NRD_NORMAL_ENCODING_R10G10B10A2_UNORM 2 // supports material ID bits 19 | #define NRD_NORMAL_ENCODING_RGBA16_UNORM 3 20 | #define NRD_NORMAL_ENCODING_RGBA16_SNORM 4 // also can be used with FP formats 21 | 22 | 23 | #define NRD_NORMAL_ENCODING NRD_NORMAL_ENCODING_R10G10B10A2_UNORM 24 | #define NRD_ROUGHNESS_ENCODING NRD_ROUGHNESS_ENCODING_LINEAR 25 | vec4 NRD_FrontEnd_PackNormalAndRoughness(vec3 N, float roughness, float materialID ) 26 | { 27 | vec4 p; 28 | 29 | #if( NRD_ROUGHNESS_ENCODING == NRD_ROUGHNESS_ENCODING_SQRT_LINEAR ) 30 | roughness = sqrt( clamp( roughness, 0, 1 ) ); 31 | #elif( NRD_ROUGHNESS_ENCODING == NRD_ROUGHNESS_ENCODING_SQ_LINEAR ) 32 | roughness *= roughness; 33 | #endif 34 | 35 | #if( NRD_NORMAL_ENCODING == NRD_NORMAL_ENCODING_R10G10B10A2_UNORM ) 36 | p.xy = _NRD_EncodeUnitVector( N, false ); 37 | p.z = roughness; 38 | p.w = clamp( materialID / 3.0, 0.0, 1.0 ); 39 | #else 40 | // Best fit ( optional ) 41 | N /= max( abs( N.x ), max( abs( N.y ), abs( N.z ) ) ); 42 | 43 | #if( NRD_NORMAL_ENCODING == NRD_NORMAL_ENCODING_RGBA8_UNORM || NRD_NORMAL_ENCODING == NRD_NORMAL_ENCODING_RGBA16_UNORM ) 44 | N = N * 0.5 + 0.5; 45 | #endif 46 | 47 | p.xyz = N; 48 | p.w = roughness; 49 | #endif 50 | 51 | return p; 52 | } 53 | 54 | vec3 _NRD_DecodeUnitVector( vec2 p, const bool bSigned, const bool bNormalize ) 55 | { 56 | p = bSigned ? p : ( p * 2.0 - 1.0 ); 57 | 58 | // https://twitter.com/Stubbesaurus/status/937994790553227264 59 | vec3 n = vec3( p.xy, 1.0 - abs( p.x ) - abs( p.y ) ); 60 | float t = clamp( -n.z, 0.0, 1.0 ); 61 | n.xy -= t * ( step( 0.0, n.xy ) * 2.0 - 1.0 ); 62 | 63 | return bNormalize ? normalize( n ) : n; 64 | } 65 | 66 | vec4 NRD_FrontEnd_UnpackNormalAndRoughness( vec4 p, out float materialID ) 67 | { 68 | vec4 r; 69 | #if( NRD_NORMAL_ENCODING == NRD_NORMAL_ENCODING_R10G10B10A2_UNORM ) 70 | r.xyz = _NRD_DecodeUnitVector( p.xy, false, false ); 71 | r.w = p.z; 72 | 73 | materialID = p.w; 74 | #else 75 | #if( NRD_NORMAL_ENCODING == NRD_NORMAL_ENCODING_RGBA8_UNORM || NRD_NORMAL_ENCODING == NRD_NORMAL_ENCODING_RGBA16_UNORM ) 76 | p.xyz = p.xyz * 2.0 - 1.0; 77 | #endif 78 | 79 | r.xyz = p.xyz; 80 | r.w = p.w; 81 | 82 | materialID = 0; 83 | #endif 84 | 85 | r.xyz = normalize( r.xyz ); 86 | 87 | #if( NRD_ROUGHNESS_ENCODING == NRD_ROUGHNESS_ENCODING_SQRT_LINEAR ) 88 | r.w *= r.w; 89 | #elif( NRD_ROUGHNESS_ENCODING == NRD_ROUGHNESS_ENCODING_SQ_LINEAR ) 90 | r.w = sqrt( r.w ); 91 | #endif 92 | 93 | return r; 94 | } 95 | 96 | 97 | #define NRD_FP16_MIN 1e-7 // min allowed hitDist (0 = no data) 98 | 99 | vec3 _NRD_LinearToYCoCg( vec3 color ) 100 | { 101 | float Y = dot( color, vec3( 0.25, 0.5, 0.25 ) ); 102 | float Co = dot( color, vec3( 0.5, 0.0, -0.5 ) ); 103 | float Cg = dot( color, vec3( -0.25, 0.5, -0.25 ) ); 104 | 105 | return vec3( Y, Co, Cg ); 106 | } 107 | 108 | 109 | 110 | vec3 _NRD_YCoCgToLinear( vec3 color ) 111 | { 112 | float t = color.x - color.z; 113 | 114 | vec3 r; 115 | r.y = color.x + color.z; 116 | r.x = t + color.y; 117 | r.z = t - color.y; 118 | 119 | return max( r, 0.0 ); 120 | } 121 | vec4 REBLUR_BackEnd_UnpackRadianceAndNormHitDist( vec4 data ) 122 | { 123 | data.xyz = _NRD_YCoCgToLinear( data.xyz ); 124 | 125 | return data; 126 | } 127 | 128 | 129 | 130 | vec4 REBLUR_FrontEnd_PackRadianceAndNormHitDist( vec3 radiance, float normHitDist) 131 | { 132 | /* 133 | if( sanitize ) 134 | { 135 | radiance = any( isnan( radiance ) | isinf( radiance ) ) ? 0 : clamp( radiance, 0, NRD_FP16_MAX ); 136 | normHitDist = ( isnan( normHitDist ) | isinf( normHitDist ) ) ? 0 : saturate( normHitDist ); 137 | } 138 | */ 139 | 140 | // "0" is reserved to mark "no data" samples, skipped due to probabilistic sampling 141 | if( normHitDist != 0 ) 142 | normHitDist = max( normHitDist, NRD_FP16_MIN ); 143 | 144 | radiance = _NRD_LinearToYCoCg( radiance ); 145 | 146 | return vec4( radiance, normHitDist ); 147 | } 148 | 149 | -------------------------------------------------------------------------------- /assets/shaders/headers/sbt.glsl: -------------------------------------------------------------------------------- 1 | struct Block 2 | { 3 | u16vec4 position; 4 | #ifdef SHADER_INT_64 5 | uint64_t mask; 6 | #else 7 | uint32_t mask1; 8 | uint32_t mask2; 9 | #endif 10 | uint32_t material_ptr; 11 | 12 | // avg albedo, R10G10B10A2 13 | uint32_t avg_albedo; 14 | }; 15 | 16 | 17 | layout(buffer_reference, buffer_reference_align = 8, scalar) buffer GeometryInfo { 18 | Block blocks[]; 19 | }; 20 | layout(buffer_reference, buffer_reference_align = 1, scalar) buffer MaterialInfo { 21 | uint8_t materials[]; 22 | }; 23 | layout(buffer_reference) buffer PaletteInfo { 24 | u8vec4 palette[]; 25 | }; 26 | 27 | layout(shaderRecordEXT) buffer Sbt { 28 | GeometryInfo geometryInfo; 29 | MaterialInfo materialInfo; 30 | PaletteInfo paletteInfo; 31 | } sbt; 32 | -------------------------------------------------------------------------------- /assets/shaders/headers/sky.glsl: -------------------------------------------------------------------------------- 1 | float ArHosekSkyModel_GetRadianceInternal( 2 | float[9] configuration, 3 | float cos_theta, 4 | float gamma, 5 | float cos_gamma 6 | ) 7 | { 8 | float expM = exp(configuration[4] * gamma); 9 | float rayM = cos_gamma * cos_gamma; 10 | float mieM = (1.0 + rayM) / pow((1.0 + configuration[8]*configuration[8] - 2.0*configuration[8]*cos_gamma), 1.5); 11 | float zenith = sqrt(cos_theta); 12 | 13 | return (1.0 + configuration[0] * exp(configuration[1] / (cos_theta + 0.01))) * 14 | (configuration[2] + configuration[3] * expM + configuration[5] * rayM + configuration[6] * mieM + configuration[7] * zenith); 15 | } 16 | 17 | // dir: normalized view direction vector 18 | vec3 arhosek_sky_radiance(vec3 dir) 19 | { 20 | if (sunlight_config.direction.y <= 0) { 21 | // Avoid NaN problems. 22 | return vec3(0); 23 | } 24 | float cos_theta = clamp(dir.y, 0, 1); 25 | float cos_gamma = dot(dir, sunlight_config.direction.xyz); 26 | float gamma = acos(cos_gamma); 27 | 28 | 29 | float x = 30 | ArHosekSkyModel_GetRadianceInternal( 31 | float[]( 32 | sunlight_config.r.configs0.x, 33 | sunlight_config.r.configs0.y, 34 | sunlight_config.r.configs0.z, 35 | sunlight_config.r.configs0.w, 36 | sunlight_config.r.configs1.x, 37 | sunlight_config.r.configs1.y, 38 | sunlight_config.r.configs1.z, 39 | sunlight_config.r.configs1.w, 40 | sunlight_config.r.configs2 41 | ), 42 | cos_theta, 43 | gamma, cos_gamma 44 | ) * sunlight_config.r.radiance; 45 | float y = 46 | ArHosekSkyModel_GetRadianceInternal( 47 | float[]( 48 | sunlight_config.g.configs0.x, 49 | sunlight_config.g.configs0.y, 50 | sunlight_config.g.configs0.z, 51 | sunlight_config.g.configs0.w, 52 | sunlight_config.g.configs1.x, 53 | sunlight_config.g.configs1.y, 54 | sunlight_config.g.configs1.z, 55 | sunlight_config.g.configs1.w, 56 | sunlight_config.g.configs2 57 | ), 58 | cos_theta, 59 | gamma, cos_gamma 60 | ) * sunlight_config.g.radiance; 61 | float z = 62 | ArHosekSkyModel_GetRadianceInternal( 63 | float[]( 64 | sunlight_config.b.configs0.x, 65 | sunlight_config.b.configs0.y, 66 | sunlight_config.b.configs0.z, 67 | sunlight_config.b.configs0.w, 68 | sunlight_config.b.configs1.x, 69 | sunlight_config.b.configs1.y, 70 | sunlight_config.b.configs1.z, 71 | sunlight_config.b.configs1.w, 72 | sunlight_config.b.configs2 73 | ), 74 | cos_theta, 75 | gamma, cos_gamma 76 | ) * sunlight_config.b.radiance; 77 | vec3 sky_color = vec3(x, y, z) * 683.0; 78 | return XYZ2ACEScg(sky_color); 79 | } 80 | 81 | vec3 arhosek_sun_radiance( 82 | vec3 dir 83 | ) { 84 | float cos_gamma = dot(dir, sunlight_config.direction.xyz); 85 | if (cos_gamma < 0.0 || dir.y < 0.0) { 86 | return vec3(0.0); 87 | } 88 | float sol_rad_sin = sin(sunlight_config.solar_intensity.w); 89 | float ar2 = 1.0 / (sol_rad_sin * sol_rad_sin); 90 | float singamma = 1.0 - (cos_gamma * cos_gamma); 91 | float sc2 = 1.0 - ar2 * singamma * singamma; 92 | if (sc2 <= 0.0) { 93 | return vec3(0.0); 94 | } 95 | float sampleCosine = sqrt(sc2); 96 | 97 | vec3 darkeningFactor = vec3(sunlight_config.r.ld_coefficient0, sunlight_config.g.ld_coefficient0, sunlight_config.b.ld_coefficient0); 98 | 99 | 100 | darkeningFactor += vec3(sunlight_config.r.ld_coefficient1, sunlight_config.g.ld_coefficient1, sunlight_config.b.ld_coefficient1) * sampleCosine; 101 | 102 | float currentSampleCosine = sampleCosine; 103 | [[unroll]] 104 | for (uint i = 0; i < 4; i++) { 105 | currentSampleCosine *= sampleCosine; 106 | darkeningFactor += vec3( 107 | sunlight_config.r.ld_coefficient2[i], 108 | sunlight_config.g.ld_coefficient2[i], 109 | sunlight_config.b.ld_coefficient2[i] 110 | ) * currentSampleCosine; 111 | } 112 | return XYZ2ACEScg(sunlight_config.solar_intensity.xyz * darkeningFactor); 113 | } 114 | -------------------------------------------------------------------------------- /assets/shaders/headers/standard.glsl: -------------------------------------------------------------------------------- 1 | #extension GL_EXT_ray_tracing : require 2 | #extension GL_EXT_shader_16bit_storage: require 3 | #extension GL_EXT_shader_explicit_arithmetic_types_int32 : require 4 | #extension GL_EXT_shader_explicit_arithmetic_types_int16: require 5 | #extension GL_EXT_shader_explicit_arithmetic_types_int8: require 6 | #extension GL_EXT_nonuniform_qualifier : require 7 | #extension GL_EXT_buffer_reference : require 8 | #extension GL_EXT_scalar_block_layout : require 9 | #extension GL_EXT_samplerless_texture_functions: require 10 | #extension GL_EXT_control_flow_attributes: require 11 | 12 | #extension GL_EXT_debug_printf : enable 13 | 14 | #define M_PI 3.1415926535897932384626433832795 15 | 16 | // Eye -> Object -> Skylight 17 | #define CONTRIBUTION_SECONDARY_SKYLIGHT 18 | // Eye -> Object -> Object (sample spatial hash) 19 | #define CONTRIBUTION_SECONDARY_SPATIAL_HASH 20 | // Eye -> Object -> Sun 21 | #define CONTRIBUTION_DIRECT 22 | 23 | // Eye -> Object -> Surfel -> Sun 24 | #define CONTRIBUTION_SECONDARY_SUNLIGHT 25 | 26 | //#define DEBUG_VISUALIZE_SPATIAL_HASH 27 | 28 | #define AMBIENT_OCCLUSION_THRESHOLD 8.0 29 | -------------------------------------------------------------------------------- /assets/shaders/headers/surfel.glsl: -------------------------------------------------------------------------------- 1 | 2 | layout(constant_id = 1) const uint32_t SurfelPoolSize = 720*480; 3 | -------------------------------------------------------------------------------- /assets/shaders/primary/hit.rchit: -------------------------------------------------------------------------------- 1 | #include "../headers/standard.glsl" 2 | #include "../headers/sbt.glsl" 3 | #include "../headers/normal.glsl" 4 | #include "../headers/nrd.glsl" 5 | #include "../headers/layout.playout" 6 | #include "../headers/color.glsl" 7 | 8 | #ifdef DEBUG_VISUALIZE_SPATIAL_HASH 9 | #include "../headers/spatial_hash.glsl" 10 | #endif 11 | 12 | hitAttributeEXT HitAttribute { 13 | uint voxelId; 14 | } hitAttributes; 15 | 16 | void main() { 17 | Block block = sbt.geometryInfo.blocks[gl_PrimitiveID]; 18 | 19 | // Calculate nexthit location 20 | vec3 hitPointObject = gl_HitTEXT * gl_ObjectRayDirectionEXT + gl_ObjectRayOriginEXT; 21 | vec3 offsetInBox = vec3(hitAttributes.voxelId >> 4, (hitAttributes.voxelId >> 2) & 3, hitAttributes.voxelId & 3); 22 | 23 | #ifdef DEBUG_VISUALIZE_SPATIAL_HASH 24 | vec3 boxCenterObject = block.position.xyz + vec3(2.0); 25 | #else 26 | vec3 boxCenterObject = block.position.xyz + offsetInBox + vec3(0.5); 27 | #endif 28 | 29 | vec3 normalObject = CubedNormalize(hitPointObject - boxCenterObject); 30 | vec3 normalWorld = gl_ObjectToWorldEXT * vec4(normalObject, 0.0); 31 | 32 | 33 | uint8_t palette_index = uint8_t(0); 34 | #ifdef DEBUG_VISUALIZE_SPATIAL_HASH 35 | vec3 boxCenterWorld = gl_ObjectToWorldEXT * vec4(boxCenterObject, 1.0); 36 | SpatialHashKey key; 37 | key.position = ivec3((boxCenterWorld / 4.0)); 38 | key.direction = normal2FaceID(normalWorld); 39 | 40 | vec3 indirect_radiance; // The amount of incoming radiance at the voxel 41 | uint sample_count; 42 | bool found = SpatialHashGet(key, indirect_radiance, sample_count); 43 | vec4 packed = REBLUR_FrontEnd_PackRadianceAndNormHitDist(indirect_radiance, 0.0); 44 | imageStore(img_illuminance, ivec2(gl_LaunchIDEXT.xy), packed); 45 | 46 | uint32_t packed_albedo = block.avg_albedo; 47 | vec4 albedo = vec4( 48 | float((packed_albedo >> 22) & 1023) / 1023.0, 49 | float((packed_albedo >> 12) & 1023) / 1023.0, 50 | float((packed_albedo >> 2) & 1023) / 1023.0, 51 | float(packed_albedo & 3) / 3.0 52 | ); 53 | 54 | imageStore(img_albedo, ivec2(gl_LaunchIDEXT.xy), albedo); 55 | #else 56 | 57 | imageStore(img_illuminance, ivec2(gl_LaunchIDEXT.xy), vec4(0.0)); 58 | // Sample the albedo from the voxel 59 | #ifdef SHADER_INT_64 60 | u32vec2 masked = unpack32(block.mask & ((uint64_t(1) << hitAttributes.voxelId) - 1)); 61 | uint32_t voxelMemoryOffset = bitCount(masked.x) + bitCount(masked.y); 62 | #else 63 | u32vec2 masked = u32vec2( 64 | hitAttributes.voxelId < 32 ? block.mask1 & ((1 << hitAttributes.voxelId) - 1) : block.mask1, 65 | hitAttributes.voxelId >= 32 ? block.mask2 & ((1 << (hitAttributes.voxelId - 32)) - 1) : 0 66 | ); 67 | uint32_t voxelMemoryOffset = uint32_t(bitCount(masked.x) + bitCount(masked.y)); 68 | #endif 69 | 70 | 71 | palette_index = sbt.materialInfo.materials[block.material_ptr + voxelMemoryOffset]; 72 | u8vec4 color = sbt.paletteInfo.palette[palette_index]; 73 | 74 | vec3 albedo = color.xyz / 255.0; 75 | 76 | imageStore(img_albedo, ivec2(gl_LaunchIDEXT.xy), vec4(albedo, 1.0)); 77 | #endif 78 | 79 | 80 | // Store the contribution from photon maps 81 | imageStore(img_depth, ivec2(gl_LaunchIDEXT.xy), vec4(gl_HitTEXT)); 82 | 83 | imageStore(img_normal, ivec2(gl_LaunchIDEXT.xy), NRD_FrontEnd_PackNormalAndRoughness(normalWorld, 1.0, float(palette_index))); 84 | 85 | 86 | // Saved: | 8 bit voxel id | 8 bit palette_index | 16 bit instance id | 87 | uint voxel_id_info = (uint(hitAttributes.voxelId) << 24) | uint(gl_InstanceID & 0xFFFF) | (uint(palette_index) << 16); 88 | imageStore(img_voxel_id, ivec2(gl_LaunchIDEXT.xy), uvec4(voxel_id_info, 0, 0, 0)); 89 | 90 | vec3 hitPointWorld = gl_HitTEXT * gl_WorldRayDirectionEXT + gl_WorldRayOriginEXT; 91 | vec3 hitPointModel = gl_WorldToObjectEXT * vec4(hitPointWorld, 1.0); 92 | vec4 hitPointWorldLastFrameH = instances[gl_InstanceID] * vec4(hitPointModel, 1.0); 93 | vec3 hitPointWorldLastFrame = hitPointWorldLastFrameH.xyz / hitPointWorldLastFrameH.w; 94 | imageStore(img_motion, ivec2(gl_LaunchIDEXT.xy), vec4(hitPointWorldLastFrame - hitPointWorld, 0.0)); 95 | } 96 | -------------------------------------------------------------------------------- /assets/shaders/primary/miss.rmiss: -------------------------------------------------------------------------------- 1 | #include "../headers/standard.glsl" 2 | #include "../headers/layout.playout" 3 | #include "../headers/color.glsl" 4 | #include "../headers/sky.glsl" 5 | #include "../headers/nrd.glsl" 6 | 7 | void main() { 8 | vec3 dir = normalize(gl_WorldRayDirectionEXT); 9 | vec3 sky_color_xyz = arhosek_sky_radiance(dir) + arhosek_sun_radiance(dir); 10 | 11 | vec4 packed = REBLUR_FrontEnd_PackRadianceAndNormHitDist(sky_color_xyz / 3.14, 100000.0); 12 | // TODO: figure out why we need to divide by PI here. 13 | imageStore(img_illuminance_denoised, ivec2(gl_LaunchIDEXT.xy), packed); 14 | imageStore(img_albedo, ivec2(gl_LaunchIDEXT.xy), vec4(1.0)); 15 | imageStore(img_depth, ivec2(gl_LaunchIDEXT.xy), vec4(1.0 / 0.0)); 16 | imageStore(img_motion, ivec2(gl_LaunchIDEXT.xy), vec4(0.0)); 17 | } 18 | -------------------------------------------------------------------------------- /assets/shaders/primary/primary.rgen: -------------------------------------------------------------------------------- 1 | #include "../headers/standard.glsl" 2 | #include "../headers/layout.playout" 3 | #include "../headers/camera.glsl" 4 | 5 | layout(location = 0) rayPayloadEXT uint8_t _rayPayloadNotUsed; 6 | 7 | 8 | void main() { 9 | traceRayEXT( 10 | acceleration_structure, 11 | gl_RayFlagsOpaqueEXT , // RayFlags 12 | 0xFF, // CullMask 13 | 0, // SBT offset, ray type index 14 | 4, // SBT stride, number of ray types 15 | 0, // missIndex 16 | camera_origin(), // ray origin 17 | camera.near, // ray min range 18 | camera_ray_dir(), // direction 19 | camera.far, // tmax 20 | 0 // payload 21 | ); 22 | } 23 | -------------------------------------------------------------------------------- /assets/shaders/surfel/nee.rmiss: -------------------------------------------------------------------------------- 1 | #include "../headers/standard.glsl" 2 | #include "../headers/layout.playout" 3 | #include "../headers/color.glsl" 4 | #include "../headers/sky.glsl" 5 | #include "../headers/standard.glsl" 6 | #include "../headers/surfel.glsl" 7 | #include "../headers/spatial_hash.glsl" 8 | #include "../headers/normal.glsl" 9 | 10 | layout(location = 0) rayPayloadInEXT struct RayPayload { 11 | vec3 radiance; 12 | } payload; 13 | 14 | 15 | void main() { 16 | SurfelEntry surfel = surfel_pool[gl_LaunchIDEXT.x]; 17 | 18 | SpatialHashKey key; 19 | key.position = ivec3((surfel.position / 4.0)); 20 | key.direction = uint8_t(surfel.direction); 21 | 22 | vec3 normal = faceId2Normal(uint8_t(surfel.direction)); // world space 23 | vec3 strength = arhosek_sun_radiance(normalize(gl_WorldRayDirectionEXT)) * (1.0 - cos(sunlight_config.solar_intensity.w)); 24 | vec3 illuminance = vec3(strength) * dot(normal, gl_WorldRayDirectionEXT); 25 | 26 | payload.radiance += illuminance; 27 | } 28 | -------------------------------------------------------------------------------- /assets/shaders/surfel/surfel.rchit: -------------------------------------------------------------------------------- 1 | #include "../headers/standard.glsl" 2 | #include "../headers/layout.playout" 3 | #include "../headers/sbt.glsl" 4 | #include "../headers/normal.glsl" 5 | #include "../headers/spatial_hash.glsl" 6 | #include "../headers/color.glsl" 7 | #include "../headers/surfel.glsl" 8 | 9 | hitAttributeEXT HitAttribute { 10 | uint voxelId; 11 | } hitAttributes; 12 | 13 | 14 | layout(location = 0) rayPayloadInEXT struct RayPayload { 15 | vec3 radiance; 16 | } payload; 17 | 18 | 19 | #ifdef SHADER_INT_64 20 | #define GridType uint64_t 21 | uint GridNumVoxels(GridType grid) { 22 | u32vec2 unpacked = unpack32(grid); 23 | return bitCount(masked.x) + bitCount(masked.y); 24 | } 25 | #else 26 | #define GridType u32vec2 27 | uint GridNumVoxels(GridType grid) { 28 | return bitCount(grid.x) + bitCount(grid.y); 29 | } 30 | #endif 31 | 32 | 33 | // Light leaves the surfel (S) and hit another voxel (V). 34 | // We sample V and find out the outgoing radiance from the spatial hash. 35 | void main() { 36 | Block block = sbt.geometryInfo.blocks[gl_PrimitiveID]; 37 | vec3 aabbCenterObject = block.position.xyz + 2.0; 38 | 39 | vec3 hitPointObject = gl_HitTEXT * gl_ObjectRayDirectionEXT + gl_ObjectRayOriginEXT; 40 | vec3 normalObject = hitPointObject - aabbCenterObject; 41 | vec3 normalWorld = CubedNormalize(gl_ObjectToWorldEXT * vec4(normalObject, 0.0)); 42 | 43 | vec3 aabbCenterWorld = gl_ObjectToWorldEXT * vec4(aabbCenterObject, 1.0); 44 | 45 | SpatialHashKey key; 46 | key.position = ivec3((aabbCenterWorld / 4.0)); 47 | key.direction = normal2FaceID(normalWorld); 48 | 49 | vec3 radiance; 50 | uint sample_count = 0; 51 | // Sample the hit location in the spatial hash 52 | bool found = SpatialHashGet(key, radiance, sample_count); 53 | 54 | uvec2 noiseSampleLocation; 55 | uint width = textureSize(blue_noise[0], 0).x; 56 | noiseSampleLocation.y = gl_LaunchIDEXT.x / width; 57 | noiseSampleLocation.x = gl_LaunchIDEXT.x - noiseSampleLocation.y * width; 58 | float rand = texelFetch(blue_noise[0], ivec2((noiseSampleLocation + uvec2(114, 40) + push_constants.rand) % textureSize(blue_noise[0], 0)), 0).x; 59 | if (found) { 60 | uint32_t packed_albedo = block.avg_albedo; 61 | vec4 albedo = vec4( 62 | float((packed_albedo >> 22) & 1023) / 1023.0, 63 | float((packed_albedo >> 12) & 1023) / 1023.0, 64 | float((packed_albedo >> 2) & 1023) / 1023.0, 65 | float(packed_albedo & 3) / 3.0 66 | ); 67 | albedo.x = SRGBToLinear(albedo.x); 68 | albedo.y = SRGBToLinear(albedo.y); 69 | albedo.z = SRGBToLinear(albedo.z); 70 | 71 | radiance = sRGB2AECScg(AECScg2sRGB(radiance) * albedo.xyz); 72 | 73 | 74 | // Add to surfel value 75 | SurfelEntry surfel = surfel_pool[gl_LaunchIDEXT.x]; 76 | 77 | SpatialHashKey key; 78 | key.position = ivec3((surfel.position / 4.0)); 79 | key.direction = uint8_t(surfel.direction); 80 | // `radiance` is the incoming radiance at the hit location. 81 | // Spatial hash stores the incoming radiance. The incoming radiance at the 82 | // surfel location is the outgoing radiance at the hit location. 83 | SpatialHashInsert(key, radiance + payload.radiance); 84 | // TODO: Optimize the Insert / Get flow so they only do the loop once. 85 | 86 | // TODO: Maybe add more samples to the patch: need heuristic. 87 | } else { 88 | // TODO: enqueue the patch stocastically 89 | // TODO: Also need to enqueue a sample of strength = 0 90 | // TODO: weight expotential fallout over time 91 | float probability_to_schedule = 1.0 / float(sample_count + 2); 92 | if (rand > probability_to_schedule) { 93 | uint index = gl_LaunchIDEXT.x % SurfelPoolSize; 94 | 95 | 96 | SurfelEntry entry; 97 | entry.position = aabbCenterWorld; 98 | entry.direction = normal2FaceID(normalWorld); 99 | surfel_pool[index] = entry; 100 | } 101 | } 102 | } 103 | 104 | 105 | 106 | 107 | -------------------------------------------------------------------------------- /assets/shaders/surfel/surfel.rgen: -------------------------------------------------------------------------------- 1 | #include "../headers/standard.glsl" 2 | #include "../headers/layout.playout" 3 | #include "../headers/surfel.glsl" 4 | #include "../headers/normal.glsl" 5 | #include "../headers/color.glsl" 6 | 7 | layout(location = 0) rayPayloadEXT struct RayPayload { 8 | vec3 radiance; 9 | } payload; 10 | 11 | 12 | void main() { 13 | SurfelEntry entry = surfel_pool[gl_LaunchIDEXT.x]; 14 | if (entry.direction >= 6) { // invalid entry 15 | return; 16 | } 17 | 18 | vec3 normal = faceId2Normal(uint8_t(entry.direction)); // world space 19 | 20 | 21 | uvec2 noiseSampleLocation; 22 | uint width = textureSize(blue_noise[1], 0).x; 23 | noiseSampleLocation.y = gl_LaunchIDEXT.x / width; 24 | noiseSampleLocation.x = gl_LaunchIDEXT.x - noiseSampleLocation.y * width; 25 | 26 | // Location on the patch 27 | vec3 origin = entry.position + 2.01 * normal; 28 | 29 | // Outgoing direction 30 | vec3 noiseSample = texelFetch(blue_noise[5], ivec2((noiseSampleLocation + uvec2(16, 47) + push_constants.rand) % textureSize(blue_noise[5], 0)), 0).xyz * 2.0 - 1.0; 31 | // noiseSample is weighted on the z axis 32 | noiseSample = rotateVectorByNormal(normal, noiseSample); 33 | 34 | 35 | vec3 sunDir = sunlight_config.direction.xyz; 36 | payload.radiance = vec3(0.0); 37 | #ifdef CONTRIBUTION_SECONDARY_SUNLIGHT 38 | if (dot(sunDir, normal) > 0.0) { 39 | traceRayEXT( 40 | acceleration_structure, 41 | gl_RayFlagsOpaqueEXT | gl_RayFlagsSkipClosestHitShaderEXT | gl_RayFlagsTerminateOnFirstHitEXT, // RayFlags 42 | 0xFF, // CullMask 43 | 3, // SBT offset, ray type index // Use the same intersection shader. We need higher-quality intersection for shadow rays as well. 44 | 4, // SBT stride, number of ray types // TODO: Make this a shader constant 45 | 4, // missIndex 46 | origin, // ray origin 47 | 0.1, // ray min range. If we set this to 0.0, VK_DEVICE_LOST. Time stuck: 2 days 48 | normalize(sunDir), // outgoing direction: cosine weighted on the normal direction 49 | 10000.0, // tmax 50 | 0 // payload 51 | ); 52 | } 53 | #endif 54 | traceRayEXT( 55 | acceleration_structure, 56 | gl_RayFlagsOpaqueEXT, // RayFlags 57 | 0xFF, // CullMask 58 | 3, // SBT offset, ray type index // Use the same intersection shader. We need higher-quality intersection for shadow rays as well. 59 | 4, // SBT stride, number of ray types // TODO: Make this a shader constant 60 | 3, // missIndex 61 | origin, // ray origin 62 | 0.1, // ray min range. If we set this to 0.0, VK_DEVICE_LOST. Time stuck: 2 days 63 | normalize(noiseSample), // outgoing direction: cosine weighted on the normal direction 64 | 10000.0, // tmax 65 | 0 // payload 66 | ); 67 | } 68 | -------------------------------------------------------------------------------- /assets/shaders/surfel/surfel.rmiss: -------------------------------------------------------------------------------- 1 | #include "../headers/standard.glsl" 2 | #include "../headers/layout.playout" 3 | #include "../headers/color.glsl" 4 | #include "../headers/sky.glsl" 5 | #include "../headers/standard.glsl" 6 | #include "../headers/surfel.glsl" 7 | #include "../headers/spatial_hash.glsl" 8 | 9 | layout(location = 0) rayPayloadInEXT struct RayPayload { 10 | vec3 radiance; 11 | } payload; 12 | 13 | 14 | void main() { 15 | vec3 sky_illuminance = arhosek_sky_radiance(normalize(gl_WorldRayDirectionEXT)); 16 | 17 | SurfelEntry surfel = surfel_pool[gl_LaunchIDEXT.x]; 18 | 19 | SpatialHashKey key; 20 | key.position = ivec3((surfel.position / 4.0)); 21 | key.direction = uint8_t(surfel.direction); 22 | 23 | SpatialHashInsert(key, sky_illuminance + payload.radiance); 24 | 25 | // TODO: stocastically sample the lights as well. 26 | } 27 | -------------------------------------------------------------------------------- /assets/shaders/tone_map.playout: -------------------------------------------------------------------------------- 1 | struct Histogram { 2 | avg: f32, 3 | } 4 | 5 | #[set] 6 | struct StandardLayout { 7 | #![stage(COMPUTE)] 8 | u_src_img: StorageImage, 9 | u_src_img_albedo: StorageImage, 10 | u_dst_img: StorageImage, 11 | u_histogram: UniformBuffer, 12 | } 13 | -------------------------------------------------------------------------------- /assets/stbn/scalar_2Dx1Dx1D_128x128x64x1.png: -------------------------------------------------------------------------------- 1 | version https://git-lfs.github.com/spec/v1 2 | oid sha256:448a1d7afedbc4ae584ea07c6b4094af5903140c44736bc13c3688528173d47c 3 | size 1060989 4 | -------------------------------------------------------------------------------- /assets/stbn/unitvec2_2Dx1D_128x128x64.png: -------------------------------------------------------------------------------- 1 | version https://git-lfs.github.com/spec/v1 2 | oid sha256:146080adb458eb03cede6ab5c646fcee147f1140f0f650a36f1020c8a06d0080 3 | size 2109565 4 | -------------------------------------------------------------------------------- /assets/stbn/unitvec3_2Dx1D_128x128x64.png: -------------------------------------------------------------------------------- 1 | version https://git-lfs.github.com/spec/v1 2 | oid sha256:758bc87084630e218325a06c23189655b1730b4028f2726d2fe6b9be71fc0313 3 | size 3158461 4 | -------------------------------------------------------------------------------- /assets/stbn/unitvec3_cosine_2Dx1D_128x128x64.png: -------------------------------------------------------------------------------- 1 | version https://git-lfs.github.com/spec/v1 2 | oid sha256:11f209d7962aa9f690cf00fe9a2bdf6d6728415ff646a28b33a9754fc73c7be2 3 | size 3124383 4 | -------------------------------------------------------------------------------- /assets/stbn/vec2_2Dx1D_128x128x64.png: -------------------------------------------------------------------------------- 1 | version https://git-lfs.github.com/spec/v1 2 | oid sha256:b0acfd5bd001a3bd10d58ced01c6b1fd473b695c6bb52ae8da78ad0c82a019c0 3 | size 2109565 4 | -------------------------------------------------------------------------------- /assets/stbn/vec3_2Dx1D_128x128x64.png: -------------------------------------------------------------------------------- 1 | version https://git-lfs.github.com/spec/v1 2 | oid sha256:031232de2cfc1de858e72640ca1040ed500f6d30beb0ddcd92c4d9cf11ebe942 3 | size 3158461 4 | -------------------------------------------------------------------------------- /assets/teapot.vox: -------------------------------------------------------------------------------- 1 | version https://git-lfs.github.com/spec/v1 2 | oid sha256:68cdab795e00401866ae46584d31608da2a3137065bfdfc21ab8fdd9d67b52ff 3 | size 143083 4 | -------------------------------------------------------------------------------- /crates/render/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "dust-render" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 7 | 8 | [dependencies] 9 | rhyolite-bevy = { path = "../rhyolite_bevy" } 10 | rhyolite = { path = "../rhyolite" } 11 | bevy_tasks = { git = "https://github.com/bevyengine/bevy.git", rev = "2c7eab1b4c4ec6c533b6b609d5ddf8a7282f2c4f", features = ["multi-threaded"] } 12 | bevy_app = { git = "https://github.com/bevyengine/bevy.git", rev = "2c7eab1b4c4ec6c533b6b609d5ddf8a7282f2c4f" } 13 | bevy_asset = { git = "https://github.com/bevyengine/bevy.git", rev = "2c7eab1b4c4ec6c533b6b609d5ddf8a7282f2c4f", features = ["multi-threaded", "asset_processor"] } 14 | bevy_ecs = { git = "https://github.com/bevyengine/bevy.git", rev = "2c7eab1b4c4ec6c533b6b609d5ddf8a7282f2c4f" } 15 | bevy_hierarchy = { git = "https://github.com/bevyengine/bevy.git", rev = "2c7eab1b4c4ec6c533b6b609d5ddf8a7282f2c4f" } 16 | bevy_transform = { git = "https://github.com/bevyengine/bevy.git", rev = "2c7eab1b4c4ec6c533b6b609d5ddf8a7282f2c4f" } 17 | bevy_reflect = { git = "https://github.com/bevyengine/bevy.git", rev = "2c7eab1b4c4ec6c533b6b609d5ddf8a7282f2c4f" } 18 | bevy_math = { git = "https://github.com/bevyengine/bevy.git", rev = "2c7eab1b4c4ec6c533b6b609d5ddf8a7282f2c4f" } 19 | bevy_time = { git = "https://github.com/bevyengine/bevy.git", rev = "2c7eab1b4c4ec6c533b6b609d5ddf8a7282f2c4f" } 20 | bevy_utils = { git = "https://github.com/bevyengine/bevy.git", rev = "2c7eab1b4c4ec6c533b6b609d5ddf8a7282f2c4f" } 21 | thiserror = "1" 22 | once_cell = "1.17" 23 | futures-lite = "1.11" 24 | crossbeam-channel = "^0.5" 25 | pin-project = "1" 26 | tracing = "0.1" 27 | crevice = { git = "https://github.com/LPGhatguy/crevice", features = ["glam"], rev = "18ae25df9f37de69f02f1328851e7b9f4bca343c" } 28 | rand = "0.8" 29 | arrayvec = "0.7" 30 | 31 | nrd-sys = "0.2" 32 | bytemuck = "1.13" 33 | 34 | # Processor dependencies 35 | shaderc = { version = "0.8", optional = true } 36 | playout = { git = "https://github.com/dust-engine/playout", rev = "e3658f015a013bbbcf8d15b04280979463f578da", optional = true } 37 | playout_macro = { git = "https://github.com/dust-engine/playout", rev = "e3658f015a013bbbcf8d15b04280979463f578da" } 38 | 39 | [features] 40 | default = ["glsl"] 41 | glsl = ["shaderc", "playout"] 42 | -------------------------------------------------------------------------------- /crates/render/src/accel_struct/instance_vec.rs: -------------------------------------------------------------------------------- 1 | use std::marker::PhantomData; 2 | 3 | use bevy_app::{Plugin, PostUpdate}; 4 | use bevy_ecs::{ 5 | prelude::Component, 6 | query::{Changed, Or}, 7 | schedule::IntoSystemConfigs, 8 | system::{Query, ResMut, Resource}, 9 | }; 10 | use crevice::std430::AsStd430; 11 | use rhyolite::{ash::vk, ManagedBufferVec}; 12 | use rhyolite_bevy::{Allocator, RenderSystems}; 13 | 14 | use crate::TLASIndex; 15 | 16 | /// Plugin that collects instance-related 17 | pub struct InstanceVecPlugin 18 | where 19 | ::Output: Send + Sync, 20 | { 21 | buffer_usage_flags: vk::BufferUsageFlags, 22 | alignment: u32, 23 | _marker: PhantomData<(Item, Marker)>, 24 | } 25 | impl InstanceVecPlugin 26 | where 27 | ::Output: Send + Sync, 28 | { 29 | pub fn new(usage_flags: vk::BufferUsageFlags, alignment: u32) -> Self { 30 | Self { 31 | buffer_usage_flags: usage_flags, 32 | alignment, 33 | _marker: PhantomData, 34 | } 35 | } 36 | } 37 | impl Plugin for InstanceVecPlugin 38 | where 39 | ::Output: Send + Sync, 40 | { 41 | fn build(&self, app: &mut bevy_app::App) { 42 | let allocator: Allocator = app.world.resource::().clone(); 43 | app.insert_resource(InstanceVecStore:: { 44 | buffer: ManagedBufferVec::new( 45 | allocator.into_inner(), 46 | self.buffer_usage_flags, 47 | self.alignment, 48 | ), 49 | }); 50 | app.add_systems( 51 | PostUpdate, 52 | (bevy_ecs::schedule::apply_deferred, collect::) 53 | .chain() 54 | .after(super::tlas::tlas_system::) 55 | .in_set(RenderSystems::SetUp), 56 | ); 57 | } 58 | } 59 | 60 | #[derive(Resource)] 61 | pub struct InstanceVecStore { 62 | pub buffer: ManagedBufferVec<::Output>, 63 | } 64 | 65 | pub trait InstanceVecItem: Component { 66 | type Data: AsStd430 + Send + Sync; 67 | fn data(&self) -> Self::Data; 68 | } 69 | 70 | fn collect( 71 | mut store: ResMut>, 72 | query: Query<(&TLASIndex, &Item), Or<(Changed, Changed>)>>, 73 | ) where 74 | ::Output: Send + Sync, 75 | { 76 | for (tlas_index, data) in query.iter() { 77 | store 78 | .buffer 79 | .set(tlas_index.index as usize, data.data().as_std430()) 80 | } 81 | // TODO: implement removal. 82 | } 83 | -------------------------------------------------------------------------------- /crates/render/src/accel_struct/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod blas; 2 | pub mod instance_vec; 3 | pub mod tlas; 4 | -------------------------------------------------------------------------------- /crates/render/src/deferred_task.rs: -------------------------------------------------------------------------------- 1 | use std::sync::Arc; 2 | 3 | use once_cell::sync::OnceCell; 4 | use rhyolite::ash::{prelude::VkResult, vk}; 5 | 6 | static DEFERRED_TASK_POOL: OnceCell = OnceCell::new(); 7 | pub struct DeferredTaskPool { 8 | dho: Arc, 9 | } 10 | 11 | impl DeferredTaskPool { 12 | pub fn init(device: Arc) { 13 | DEFERRED_TASK_POOL.get_or_init(|| Self { 14 | dho: Arc::new(rhyolite::DeferredOperationTaskPool::new(device)), 15 | }); 16 | } 17 | pub fn get() -> &'static Self { 18 | DEFERRED_TASK_POOL.get().expect( 19 | "A DeferredTaskPool has not been initialized yet. Please call \ 20 | DeferredTaskPool::init beforehand.", 21 | ) 22 | } 23 | pub fn inner(&self) -> &Arc { 24 | &self.dho 25 | } 26 | } 27 | 28 | pub enum DeferredValue { 29 | Pending(bevy_tasks::Task>), 30 | Done(T), 31 | Errored(vk::Result), 32 | None, 33 | } 34 | impl DeferredValue { 35 | pub fn is_done(&self) -> bool { 36 | match self { 37 | Self::Done(_) => true, 38 | Self::Pending(task) => task.is_finished(), 39 | _ => false, 40 | } 41 | } 42 | pub fn is_none(&self) -> bool { 43 | match self { 44 | Self::None => true, 45 | _ => false, 46 | } 47 | } 48 | pub fn map(&self, mapper: impl FnOnce(&T) -> R) -> Option { 49 | match self { 50 | Self::Done(value) => Some(mapper(value)), 51 | _ => None, 52 | } 53 | } 54 | pub fn take(&mut self) -> Option { 55 | match self { 56 | Self::Pending(task) => { 57 | if task.is_finished() { 58 | let task = match std::mem::replace(self, Self::None) { 59 | Self::Pending(task) => task, 60 | _ => unreachable!(), 61 | }; 62 | let value = futures_lite::future::block_on(task); 63 | match value { 64 | Ok(value) => return Some(value), 65 | Err(result) => { 66 | *self = Self::Errored(result); 67 | return None; 68 | } 69 | } 70 | } else { 71 | return None; 72 | } 73 | } 74 | Self::Done(_) => { 75 | let value = std::mem::replace(self, Self::None); 76 | match value { 77 | Self::Done(value) => return Some(value), 78 | _ => unreachable!(), 79 | } 80 | } 81 | Self::Errored(_) => return None, 82 | Self::None => return None, 83 | } 84 | } 85 | pub fn try_get(&mut self) -> Option<&mut T> { 86 | match self { 87 | Self::Pending(task) => { 88 | if task.is_finished() { 89 | let task = match std::mem::replace(self, Self::None) { 90 | Self::Pending(task) => task, 91 | _ => unreachable!(), 92 | }; 93 | let value = futures_lite::future::block_on(task); 94 | match value { 95 | Ok(value) => { 96 | *self = Self::Done(value); 97 | match self { 98 | Self::Done(value) => return Some(value), 99 | _ => unreachable!(), 100 | } 101 | } 102 | Err(result) => { 103 | *self = Self::Errored(result); 104 | return None; 105 | } 106 | } 107 | } else { 108 | return None; 109 | } 110 | } 111 | Self::Done(value) => return Some(value), 112 | Self::Errored(_) => return None, 113 | Self::None => return None, 114 | } 115 | } 116 | } 117 | impl From>> for DeferredValue { 118 | fn from(task: bevy_tasks::Task>) -> Self { 119 | DeferredValue::Pending(task) 120 | } 121 | } 122 | -------------------------------------------------------------------------------- /crates/render/src/geometry.rs: -------------------------------------------------------------------------------- 1 | use std::{alloc::Layout, marker::PhantomData, sync::Arc}; 2 | 3 | use bevy_app::{Plugin, PostUpdate}; 4 | use bevy_asset::Asset; 5 | use bevy_ecs::schedule::IntoSystemConfigs; 6 | use bevy_reflect::TypePath; 7 | use rhyolite::{ash::vk, future::GPUCommandFuture, ResidentBuffer}; 8 | use rhyolite_bevy::RenderSystems; 9 | 10 | use crate::accel_struct::blas::build_blas_system; 11 | 12 | pub enum GeometryType { 13 | AABBs, 14 | Triangles, 15 | } 16 | 17 | pub trait Geometry: Send + Sync + 'static + Asset + TypePath { 18 | const TYPE: GeometryType; 19 | 20 | type BLASInputBufferFuture: GPUCommandFuture>; 21 | fn blas_input_buffer(&self) -> Self::BLASInputBufferFuture; 22 | 23 | fn geometry_flags(&self) -> vk::GeometryFlagsKHR { 24 | vk::GeometryFlagsKHR::OPAQUE 25 | } 26 | 27 | /// Layout for one single AABB entry 28 | fn layout(&self) -> Layout { 29 | Layout::new::() 30 | } 31 | } 32 | 33 | pub struct GeometryPlugin { 34 | _marker: PhantomData, 35 | } 36 | 37 | impl Default for GeometryPlugin { 38 | fn default() -> Self { 39 | Self { 40 | _marker: PhantomData, 41 | } 42 | } 43 | } 44 | 45 | impl Plugin for GeometryPlugin { 46 | fn build(&self, app: &mut bevy_app::App) { 47 | app.add_systems( 48 | PostUpdate, 49 | (crate::accel_struct::blas::geometry_normalize_system:: 50 | .in_set(RenderSystems::SetUp) 51 | .before_ignore_deferred(build_blas_system),), 52 | ); 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /crates/render/src/lib.rs: -------------------------------------------------------------------------------- 1 | #![feature(let_chains)] 2 | #![feature(generators)] 3 | #![feature(associated_type_defaults)] 4 | #![feature(alloc_layout_extra)] 5 | #![feature(int_roundings)] 6 | #![feature(associated_type_bounds)] 7 | #![feature(specialization)] 8 | #![feature(btree_extract_if)] 9 | 10 | use bevy_app::{Plugin, PostUpdate}; 11 | mod accel_struct; 12 | mod deferred_task; 13 | mod geometry; 14 | mod material; 15 | mod noise; 16 | pub mod pipeline; 17 | mod projection; 18 | mod sbt; 19 | mod shader; 20 | use accel_struct::blas::{build_blas_system, BlasStore}; 21 | pub use accel_struct::tlas::*; 22 | use bevy_asset::AssetApp; 23 | use bevy_ecs::{prelude::Component, reflect::ReflectComponent, schedule::IntoSystemConfigs}; 24 | use bevy_reflect::Reflect; 25 | use deferred_task::DeferredTaskPool; 26 | pub use geometry::*; 27 | pub use material::*; 28 | pub use noise::BlueNoise; 29 | pub use pipeline::*; 30 | pub use projection::*; 31 | use rhyolite::ash::vk; 32 | use rhyolite_bevy::RenderSystems; 33 | pub use shader::*; 34 | 35 | pub struct RenderPlugin { 36 | /// When true, the RenderPlugin will add TLASPlugin. As a result, 37 | /// a default TLASStore will be inserted into the world with all entites with a Renderable component 38 | /// included. 39 | /// 40 | /// In certain use cases you might want to build separate TLAS for different sets of entities. You may 41 | /// turn off this default behavior and add your own TLAS inclusion markers and TLASPlugin. 42 | /// 43 | /// Default: true 44 | pub tlas_include_all: bool, 45 | 46 | /// Use the standard pipeline. 47 | pub use_standard_pipeline: bool, 48 | } 49 | impl Default for RenderPlugin { 50 | fn default() -> Self { 51 | Self { 52 | tlas_include_all: true, 53 | use_standard_pipeline: true, 54 | } 55 | } 56 | } 57 | 58 | impl Plugin for RenderPlugin { 59 | fn build(&self, app: &mut bevy_app::App) { 60 | app.add_plugins(rhyolite_bevy::RenderPlugin { 61 | enabled_instance_extensions: vec![ 62 | rhyolite::ash::extensions::ext::DebugUtils::name(), 63 | rhyolite::ash::extensions::khr::Surface::name(), 64 | ], 65 | enabled_device_extensions: vec![ 66 | rhyolite::ash::extensions::khr::Swapchain::name(), 67 | rhyolite::ash::extensions::khr::DeferredHostOperations::name(), 68 | rhyolite::ash::extensions::khr::AccelerationStructure::name(), 69 | rhyolite::ash::extensions::khr::RayTracingPipeline::name(), 70 | rhyolite::ash::extensions::khr::PushDescriptor::name(), 71 | rhyolite::ash::vk::KhrPipelineLibraryFn::name(), 72 | ], 73 | enabled_device_features: Box::new(rhyolite::PhysicalDeviceFeatures { 74 | v13: vk::PhysicalDeviceVulkan13Features { 75 | synchronization2: vk::TRUE, 76 | inline_uniform_block: vk::TRUE, 77 | maintenance4: vk::TRUE, 78 | ..Default::default() 79 | }, 80 | v12: vk::PhysicalDeviceVulkan12Features { 81 | timeline_semaphore: vk::TRUE, 82 | buffer_device_address: vk::TRUE, 83 | shader_int8: vk::TRUE, 84 | storage_buffer8_bit_access: vk::TRUE, 85 | scalar_block_layout: vk::TRUE, 86 | ..Default::default() 87 | }, 88 | v11: vk::PhysicalDeviceVulkan11Features { 89 | storage_buffer16_bit_access: vk::TRUE, 90 | ..Default::default() 91 | }, 92 | inner: vk::PhysicalDeviceFeatures2 { 93 | features: vk::PhysicalDeviceFeatures { 94 | shader_int16: vk::TRUE, 95 | ..Default::default() 96 | }, 97 | ..Default::default() 98 | }, 99 | acceleration_structure: vk::PhysicalDeviceAccelerationStructureFeaturesKHR { 100 | acceleration_structure: vk::TRUE, 101 | ..Default::default() 102 | }, 103 | ray_tracing: vk::PhysicalDeviceRayTracingPipelineFeaturesKHR { 104 | ray_tracing_pipeline: vk::TRUE, 105 | ..Default::default() 106 | }, 107 | ..Default::default() 108 | }), 109 | ..rhyolite_bevy::RenderPlugin::default() 110 | }) 111 | .add_plugins(PipelineCachePlugin::default()) 112 | .register_type::() 113 | .add_systems(PostUpdate, build_blas_system.in_set(RenderSystems::SetUp)) 114 | .init_resource::() 115 | .init_asset::() 116 | .init_resource::() 117 | .init_resource::(); 118 | 119 | let device = app.world.resource::(); 120 | DeferredTaskPool::init(device.inner().clone()); 121 | app.register_asset_loader(SpirvLoader::new(device.clone())); 122 | 123 | if self.tlas_include_all { 124 | app.add_plugins(TLASPlugin::::default()); 125 | } 126 | if self.use_standard_pipeline { 127 | app.add_plugins(StandardPipelinePlugin) 128 | .add_plugins(RayTracingPipelinePlugin::::default()); 129 | } 130 | 131 | #[cfg(feature = "glsl")] 132 | app.add_plugins(shader::glsl::GlslPlugin); 133 | } 134 | } 135 | 136 | #[derive(Component, Reflect)] 137 | #[reflect(Component)] 138 | pub struct Renderable { 139 | #[reflect(ignore)] 140 | pub blas_build_flags: vk::BuildAccelerationStructureFlagsKHR, 141 | } 142 | impl Default for Renderable { 143 | fn default() -> Self { 144 | Self { 145 | blas_build_flags: vk::BuildAccelerationStructureFlagsKHR::PREFER_FAST_TRACE, 146 | } 147 | } 148 | } 149 | -------------------------------------------------------------------------------- /crates/render/src/material.rs: -------------------------------------------------------------------------------- 1 | use std::{ 2 | collections::{HashMap, HashSet}, 3 | marker::PhantomData, 4 | }; 5 | 6 | use bevy_app::{Plugin, PostUpdate}; 7 | use bevy_asset::{Asset, AssetEvent, AssetId, AssetServer, Assets, Handle}; 8 | use bevy_ecs::{ 9 | prelude::{Entity, EventReader}, 10 | query::Changed, 11 | system::{Commands, Local, Query, Res, ResMut, SystemParam, SystemParamItem}, 12 | }; 13 | use bevy_reflect::TypePath; 14 | 15 | use crate::{ 16 | pipeline::RayTracingPipeline, pipeline::RayTracingPipelineBuilder, sbt::SbtIndex, 17 | shader::SpecializedShader, 18 | }; 19 | 20 | pub type MaterialType = rhyolite::RayTracingHitGroupType; 21 | // Handle is a component 22 | pub trait Material: Send + Sync + 'static + Asset + TypePath { 23 | type Pipeline: RayTracingPipeline; 24 | const TYPE: MaterialType; 25 | fn rahit_shader(ray_type: u32, asset_server: &AssetServer) -> Option; 26 | fn rchit_shader(ray_type: u32, asset_server: &AssetServer) -> Option; 27 | fn intersection_shader(ray_type: u32, asset_server: &AssetServer) -> Option; 28 | 29 | type ShaderParameters; 30 | type ShaderParameterParams: SystemParam; 31 | fn parameters( 32 | &self, 33 | ray_type: u32, 34 | params: &mut SystemParamItem, 35 | ) -> Self::ShaderParameters; 36 | } 37 | 38 | pub struct MaterialPlugin { 39 | _marker: PhantomData, 40 | } 41 | impl Default for MaterialPlugin { 42 | fn default() -> Self { 43 | Self { 44 | _marker: PhantomData, 45 | } 46 | } 47 | } 48 | impl Plugin for MaterialPlugin { 49 | fn build(&self, app: &mut bevy_app::App) { 50 | app.world 51 | .resource_scope::, ()>( 52 | |world, mut pipeline_builder| { 53 | pipeline_builder.register_material::(world.resource()); 54 | }, 55 | ); 56 | app.add_systems(PostUpdate, material_system::); 57 | } 58 | } 59 | 60 | struct MaterialStore { 61 | sbt_indices: HashMap, SbtIndex>, 62 | entitites: HashMap, HashSet>, 63 | } 64 | impl Default for MaterialStore { 65 | fn default() -> Self { 66 | Self { 67 | sbt_indices: Default::default(), 68 | entitites: Default::default(), 69 | } 70 | } 71 | } 72 | 73 | fn material_system( 74 | mut commands: Commands, 75 | mut store: Local>, 76 | mut pipeline: ResMut, 77 | materials: Res>, 78 | mut events: EventReader>, 79 | query: Query<(Entity, &Handle), Changed>>, 80 | 81 | mut params: bevy_ecs::system::StaticSystemParam, 82 | ) { 83 | for (entity, handle) in query.iter() { 84 | store 85 | .entitites 86 | .entry(handle.id()) 87 | .or_default() 88 | .insert(entity); 89 | if let Some(sbt_index) = store.sbt_indices.get(&handle.id()) { 90 | // If this returns Some, it means `AssetEvent::Created` was already received, 91 | // and the SBT entry was already created. Add that to the entity. 92 | // If it does not already exist, do nothing. The SbtIndex will be added to the 93 | // entity later when `AssetEvent::Created` was called. 94 | commands.entity(entity).insert(*sbt_index); 95 | } 96 | } 97 | for event in events.read() { 98 | match event { 99 | AssetEvent::Added { id } | AssetEvent::Modified { id } => { 100 | let material = materials.get(*id).unwrap(); 101 | let sbt_index = pipeline.material_instance_added(material, &mut params); 102 | // Now, for all entities with Handle, add SbtIndex. 103 | if let Some(old_sbt_index) = store.sbt_indices.get(id) && old_sbt_index == &sbt_index { 104 | 105 | } else { 106 | store.sbt_indices.insert(*id, sbt_index); 107 | if let Some(entities) = store.entitites.get(id) { 108 | for entity in entities.iter() { 109 | commands.entity(*entity).insert(sbt_index); 110 | } 111 | } 112 | } 113 | } 114 | AssetEvent::Removed { id: _ } => { 115 | todo!() 116 | } 117 | _ => (), 118 | } 119 | } 120 | } 121 | -------------------------------------------------------------------------------- /crates/render/src/noise.rs: -------------------------------------------------------------------------------- 1 | use bevy_asset::{AssetServer, Assets}; 2 | use bevy_ecs::world::FromWorld; 3 | use rhyolite::ash::vk; 4 | use rhyolite_bevy::SlicedImageArray; 5 | 6 | #[derive(bevy_ecs::system::Resource)] 7 | pub struct BlueNoise { 8 | pub scalar: bevy_asset::Handle, 9 | pub vec2: bevy_asset::Handle, 10 | pub unitvec2: bevy_asset::Handle, 11 | pub vec3: bevy_asset::Handle, 12 | pub unitvec3: bevy_asset::Handle, 13 | pub unitvec3_cosine: bevy_asset::Handle, 14 | } 15 | 16 | impl FromWorld for BlueNoise { 17 | fn from_world(world: &mut bevy_ecs::world::World) -> Self { 18 | let asset_server = world.resource::(); 19 | BlueNoise { 20 | scalar: asset_server.load("stbn/scalar_2Dx1Dx1D_128x128x64x1.png"), 21 | vec2: asset_server.load("stbn/vec2_2Dx1D_128x128x64.png"), 22 | unitvec2: asset_server.load("stbn/unitvec2_2Dx1D_128x128x64.png"), 23 | vec3: asset_server.load("stbn/vec3_2Dx1D_128x128x64.png"), 24 | unitvec3: asset_server.load("stbn/unitvec3_2Dx1D_128x128x64.png"), 25 | unitvec3_cosine: asset_server.load("stbn/unitvec3_cosine_2Dx1D_128x128x64.png"), 26 | } 27 | } 28 | } 29 | 30 | impl BlueNoise { 31 | pub fn as_descriptors( 32 | &self, 33 | image_arrays: &Assets, 34 | index: u32, 35 | ) -> Option<[vk::DescriptorImageInfo; 6]> { 36 | use rhyolite::{ImageLike, ImageViewExt}; 37 | let mut descriptors: [vk::DescriptorImageInfo; 6] = [Default::default(); 6]; 38 | let handles = [ 39 | &self.scalar, 40 | &self.vec2, 41 | &self.unitvec2, 42 | &self.vec3, 43 | &self.unitvec3, 44 | &self.unitvec3_cosine, 45 | ]; 46 | for (_i, (desc, handle)) in descriptors.iter_mut().zip(handles.iter()).enumerate() { 47 | let Some(img) = image_arrays.get(*handle) else { 48 | return None; 49 | }; 50 | let noise_texture_index = index % img.subresource_range().layer_count; 51 | *desc = img 52 | .slice(noise_texture_index as usize) 53 | .as_descriptor(vk::ImageLayout::SHADER_READ_ONLY_OPTIMAL); 54 | } 55 | Some(descriptors) 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /crates/render/src/pipeline/builder.rs: -------------------------------------------------------------------------------- 1 | use std::{alloc::Layout, collections::HashMap, marker::PhantomData}; 2 | 3 | use bevy_asset::AssetServer; 4 | use bevy_ecs::{system::Resource, world::World}; 5 | use rhyolite::HasDevice; 6 | use rhyolite_bevy::Allocator; 7 | 8 | use crate::material::Material; 9 | 10 | use super::{ 11 | RayTracingPipeline, RayTracingPipelineCharacteristics, 12 | RayTracingPipelineCharacteristicsMaterialInfo, 13 | }; 14 | 15 | #[derive(Resource)] 16 | pub struct RayTracingPipelineBuilder { 17 | allocator: rhyolite_bevy::Allocator, 18 | layout_size: usize, 19 | layout_align: usize, 20 | 21 | material_to_index: HashMap, 22 | materials: Vec, 23 | /// Raygen shaders, miss shaders, callable shaders. 24 | _marker: PhantomData

, 25 | } 26 | impl RayTracingPipelineBuilder

{ 27 | pub fn new(world: &World) -> Self { 28 | RayTracingPipelineBuilder { 29 | allocator: world.resource::().clone(), 30 | layout_align: 0, 31 | layout_size: 0, 32 | material_to_index: Default::default(), 33 | materials: Vec::new(), 34 | _marker: PhantomData, 35 | } 36 | } 37 | pub fn register_material>(&mut self, asset_server: &AssetServer) { 38 | let new_material_entry_layout = Layout::new::(); 39 | self.layout_size = self.layout_size.max(new_material_entry_layout.size()); 40 | self.layout_align = self.layout_align.max(new_material_entry_layout.align()); 41 | let id = self.materials.len(); 42 | self.material_to_index 43 | .insert(std::any::TypeId::of::(), id); 44 | self.materials 45 | .push(RayTracingPipelineCharacteristicsMaterialInfo { 46 | ty: M::TYPE, 47 | shaders: (0..P::num_raytypes()) 48 | .map(|ray_type| { 49 | let rchit = M::rchit_shader(ray_type, asset_server); 50 | let rint = M::intersection_shader(ray_type, asset_server); 51 | let rahit = M::rahit_shader(ray_type, asset_server); 52 | (rchit, rint, rahit) 53 | }) 54 | .collect(), 55 | }); 56 | } 57 | pub fn build(self, num_frame_in_flight: u32, asset_server: &AssetServer) -> P { 58 | let pipeline_layout = P::pipeline_layout(self.allocator.device()); 59 | let characteristics = RayTracingPipelineCharacteristics { 60 | num_frame_in_flight, 61 | layout: pipeline_layout, 62 | sbt_param_layout: Layout::from_size_align(self.layout_size, self.layout_align) 63 | .unwrap_or(Layout::new::<()>()), 64 | material_to_index: self.material_to_index, 65 | materials: self.materials, 66 | num_raytype: P::num_raytypes(), 67 | create_info: P::create_info(), 68 | }; 69 | P::new(self.allocator, characteristics, asset_server) 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /crates/render/src/pipeline/compute.rs: -------------------------------------------------------------------------------- 1 | use std::sync::Arc; 2 | 3 | use bevy_asset::Assets; 4 | 5 | use bevy_tasks::AsyncComputeTaskPool; 6 | use rhyolite::{ 7 | ash::{prelude::VkResult, vk}, 8 | PipelineLayout, 9 | }; 10 | 11 | use crate::{ 12 | deferred_task::DeferredValue, shader::SpecializedShader, CachablePipeline, PipelineBuildInfo, 13 | ShaderModule, 14 | }; 15 | 16 | #[derive(Clone)] 17 | pub struct ComputePipelineBuildInfo { 18 | pub layout: Arc, 19 | pub shader: SpecializedShader, 20 | } 21 | 22 | impl CachablePipeline for rhyolite::ComputePipeline { 23 | type BuildInfo = ComputePipelineBuildInfo; 24 | } 25 | impl PipelineBuildInfo for ComputePipelineBuildInfo { 26 | type Pipeline = rhyolite::ComputePipeline; 27 | fn build( 28 | self, 29 | assets: &Assets, 30 | pipeline_cache: Option<&Arc>, 31 | ) -> DeferredValue> { 32 | let Some(shader) = assets.get(&self.shader.shader) else { 33 | return DeferredValue::None; 34 | }; 35 | let shader = shader.inner().clone(); 36 | let cache = pipeline_cache.map(|a| a.clone()); 37 | let pipeline: bevy_tasks::Task>> = 38 | AsyncComputeTaskPool::get().spawn(async move { 39 | let specialized_shader = rhyolite::shader::SpecializedShader { 40 | stage: self.shader.stage, 41 | flags: self.shader.flags, 42 | shader, 43 | specialization_info: self.shader.specialization_info.clone(), 44 | entry_point: self.shader.entry_point, 45 | }; 46 | let pipeline = rhyolite::ComputePipeline::create_with_shader_and_layout( 47 | specialized_shader, 48 | self.layout.clone(), 49 | vk::PipelineCreateFlags::empty(), 50 | cache.as_ref().map(|a| a.as_ref()), 51 | )?; 52 | Ok(Arc::new(pipeline)) 53 | }); 54 | pipeline.into() 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /crates/render/src/pipeline/dataset.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dust-engine/dust/7ea772ba792a76c00f629dd1cb103ea69c272e17/crates/render/src/pipeline/dataset.bin -------------------------------------------------------------------------------- /crates/render/src/pipeline/datasetSolar.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dust-engine/dust/7ea772ba792a76c00f629dd1cb103ea69c272e17/crates/render/src/pipeline/datasetSolar.bin -------------------------------------------------------------------------------- /crates/render/src/pipeline/mod.rs: -------------------------------------------------------------------------------- 1 | use std::{alloc::Layout, collections::HashMap, sync::Arc}; 2 | 3 | use bevy_asset::AssetServer; 4 | use bevy_ecs::{ 5 | prelude::Component, 6 | system::{Resource, SystemParamItem}, 7 | }; 8 | use rhyolite::PipelineLayout; 9 | use rhyolite_bevy::Allocator; 10 | mod auto_exposure; 11 | mod builder; 12 | mod cache; 13 | mod compute; 14 | mod manager; 15 | pub mod nrd; 16 | mod plugin; 17 | mod sky; 18 | mod standard; 19 | mod tone_mapping; 20 | 21 | pub use cache::*; 22 | pub use compute::*; 23 | pub use sky::Sunlight; 24 | 25 | use crate::{material::Material, sbt::SbtIndex, shader::SpecializedShader, Renderable}; 26 | pub use builder::RayTracingPipelineBuilder; 27 | pub use manager::{RayTracingPipelineManager, RayTracingPipelineManagerSpecializedPipeline}; 28 | pub use tone_mapping::{ToneMappingPipeline, ToneMappingPipelineRenderParams}; 29 | 30 | pub use auto_exposure::*; 31 | pub use plugin::RayTracingPipelinePlugin; 32 | pub use standard::{ 33 | use_gbuffer, StandardPipeline, StandardPipelinePlugin, StandardPipelineRenderParams, 34 | }; 35 | 36 | #[derive(Clone)] 37 | struct RayTracingPipelineCharacteristicsMaterialInfo { 38 | ty: rhyolite::RayTracingHitGroupType, 39 | /// Pipeline library containing n hitgroups, where n = number of ray types. 40 | shaders: Vec<( 41 | Option, 42 | Option, 43 | Option, 44 | )>, 45 | } 46 | impl RayTracingPipelineCharacteristics { 47 | pub fn material_count(&self) -> usize { 48 | self.material_to_index.len() 49 | } 50 | } 51 | 52 | #[derive(Clone)] 53 | pub struct RayTracingPipelineCharacteristics { 54 | pub num_frame_in_flight: u32, 55 | pub layout: Arc, 56 | pub sbt_param_layout: Layout, 57 | material_to_index: HashMap, 58 | materials: Vec, 59 | 60 | pub num_raytype: u32, 61 | create_info: rhyolite::RayTracingPipelineLibraryCreateInfo, 62 | } 63 | 64 | /// Generally contains one or more RayTracingPipelineManager, 65 | /// and one SbtManager 66 | pub trait RayTracingPipeline: Send + Sync + 'static + Resource { 67 | /// A marker type for entities applicable to this pipeline. 68 | /// SbtIndex will only be inserted for entities with the marker component. 69 | type Marker: Component = Renderable; 70 | fn num_raytypes() -> u32 { 71 | 1 72 | } 73 | 74 | fn pipeline_layout(device: &Arc) -> Arc; 75 | /// Assuming that the ray tracing pipeline contains a number of sub-pipelines, 76 | /// each managed by a RayTracingPipelineManager, 77 | /// implementations generally need to do the following: 78 | /// 1. For each sub-pipeline rendering the material, call material_instance_added 79 | /// 2. map from (material, raytype) to hitgroup index using the pipeline objects. 80 | /// hitgroup index needs to be adjusted by subpipeline 81 | /// 3. Call material instance add(material.parameters, hitgroup_index) on sbtmanager 82 | fn material_instance_added>( 83 | &mut self, 84 | material: &M, 85 | params: &mut SystemParamItem, 86 | ) -> SbtIndex; 87 | fn material_instance_removed>(&mut self) {} 88 | 89 | fn create_info() -> rhyolite::RayTracingPipelineLibraryCreateInfo { 90 | Default::default() 91 | } 92 | 93 | fn new( 94 | allocator: Allocator, 95 | pipeline_characteristic: RayTracingPipelineCharacteristics, 96 | asset_server: &AssetServer, 97 | ) -> Self; 98 | } 99 | -------------------------------------------------------------------------------- /crates/render/src/pipeline/plugin.rs: -------------------------------------------------------------------------------- 1 | use std::marker::PhantomData; 2 | 3 | use bevy_app::Plugin; 4 | use bevy_asset::AssetServer; 5 | 6 | use crate::{RayTracingPipeline, RayTracingPipelineBuilder}; 7 | 8 | pub struct RayTracingPipelinePlugin { 9 | _marker: PhantomData

, 10 | } 11 | impl Default for RayTracingPipelinePlugin

{ 12 | fn default() -> Self { 13 | Self { 14 | _marker: PhantomData, 15 | } 16 | } 17 | } 18 | impl Plugin for RayTracingPipelinePlugin

{ 19 | fn build(&self, app: &mut bevy_app::App) { 20 | app.insert_resource(RayTracingPipelineBuilder::

::new(&app.world)); 21 | } 22 | fn cleanup(&self, app: &mut bevy_app::App) { 23 | let builder = app 24 | .world 25 | .remove_resource::>() 26 | .unwrap(); 27 | let queues = app.world.resource::(); 28 | let asset_server = app.world.resource::(); 29 | let pipeline = builder.build(queues.num_frame_in_flight(), asset_server); 30 | app.insert_resource(pipeline); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /crates/render/src/projection.rs: -------------------------------------------------------------------------------- 1 | use bevy_ecs::prelude::Component; 2 | 3 | #[derive(Clone, Component)] 4 | pub struct PinholeProjection { 5 | pub fov: f32, 6 | 7 | /// The distance from the camera in world units of the viewing frustum's near plane. 8 | /// 9 | /// Objects closer to the camera than this value will not be visible. 10 | /// 11 | /// Defaults to a value of `0.1`. 12 | pub near: f32, 13 | 14 | /// The distance from the camera in world units of the viewing frustum's far plane. 15 | /// 16 | /// Objects farther from the camera than this value will not be visible. 17 | /// 18 | /// Defaults to a value of `1000.0`. 19 | pub far: f32, 20 | } 21 | impl Default for PinholeProjection { 22 | fn default() -> Self { 23 | Self { 24 | fov: std::f32::consts::FRAC_PI_4, 25 | near: 0.1, 26 | far: 10000.0, 27 | } 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /crates/render/src/shader/mod.rs: -------------------------------------------------------------------------------- 1 | mod spirv; 2 | 3 | #[cfg(feature = "glsl")] 4 | pub mod glsl; 5 | 6 | pub use spirv::*; 7 | -------------------------------------------------------------------------------- /crates/render/src/shader/spirv.rs: -------------------------------------------------------------------------------- 1 | use std::{ffi::CStr, sync::Arc}; 2 | 3 | use bevy_asset::{Asset, AssetLoader, Handle}; 4 | use bevy_reflect::TypePath; 5 | use futures_lite::AsyncReadExt; 6 | use rhyolite::{ash::vk, cstr, shader::SpecializationInfo}; 7 | use thiserror::Error; 8 | 9 | #[derive(TypePath, Asset)] 10 | pub struct ShaderModule(Arc); 11 | impl ShaderModule { 12 | pub fn inner(&self) -> &Arc { 13 | &self.0 14 | } 15 | } 16 | 17 | // TODO: Pipelines don't need to own the specialized shader once they've been created. 18 | #[derive(Clone)] 19 | pub struct SpecializedShader { 20 | pub stage: vk::ShaderStageFlags, 21 | pub flags: vk::PipelineShaderStageCreateFlags, 22 | pub shader: Handle, 23 | pub specialization_info: SpecializationInfo, 24 | pub entry_point: &'static CStr, 25 | } 26 | impl SpecializedShader { 27 | pub fn for_shader(shader: Handle, stage: vk::ShaderStageFlags) -> Self { 28 | Self { 29 | stage, 30 | flags: vk::PipelineShaderStageCreateFlags::empty(), 31 | shader, 32 | specialization_info: SpecializationInfo::default(), 33 | entry_point: cstr!("main"), 34 | } 35 | } 36 | pub fn with_const(mut self, constant_id: u32, item: T) -> Self { 37 | self.specialization_info.push(constant_id, item); 38 | self 39 | } 40 | } 41 | 42 | #[derive(Debug, Error)] 43 | pub enum SpirvLoaderError { 44 | #[error("io error: {0}")] 45 | IoError(#[from] std::io::Error), 46 | 47 | #[error("vulkan error: {0:?}")] 48 | VkError(#[from] vk::Result), 49 | } 50 | pub struct SpirvLoader { 51 | device: rhyolite_bevy::Device, 52 | } 53 | impl SpirvLoader { 54 | pub(crate) fn new(device: rhyolite_bevy::Device) -> Self { 55 | Self { device } 56 | } 57 | } 58 | impl AssetLoader for SpirvLoader { 59 | type Asset = ShaderModule; 60 | type Settings = (); 61 | type Error = SpirvLoaderError; 62 | fn load<'a>( 63 | &'a self, 64 | reader: &'a mut bevy_asset::io::Reader, 65 | _settings: &'a Self::Settings, 66 | _load_context: &'a mut bevy_asset::LoadContext, 67 | ) -> bevy_utils::BoxedFuture<'a, Result> { 68 | let device = self.device.inner().clone(); 69 | return Box::pin(async move { 70 | let mut bytes = Vec::new(); 71 | reader.read_to_end(&mut bytes).await?; 72 | assert!(bytes.len() % 4 == 0); 73 | let bytes = unsafe { 74 | std::slice::from_raw_parts(bytes.as_ptr() as *const u32, bytes.len() / 4) 75 | }; 76 | let shader = rhyolite::shader::SpirvShader { data: bytes }.build(device)?; 77 | Ok(ShaderModule(Arc::new(shader))) 78 | }); 79 | } 80 | 81 | fn extensions(&self) -> &[&str] { 82 | &["spv"] 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /crates/rhyolite/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "rhyolite" 3 | version = "0.1.0" 4 | edition = "2021" 5 | repository = "https://github.com/dust-engine/dust" 6 | license = "MIT OR Apache-2.0" 7 | categories = ["game-engines", "graphics", "gui", "rendering"] 8 | description = "A refreshingly simple data-driven game engine and app framework" 9 | 10 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 11 | 12 | [dependencies] 13 | ash = "0.37" 14 | ash-window = "0.12" 15 | raw-window-handle = "0.5" 16 | tracing = "0.1" 17 | rhyolite-macro = { path = "../rhyolite_macro", version = "0.1.0" } 18 | vma = "0.3.1" 19 | blocking = "1.2" 20 | pin-project = "1.0" 21 | cstr = "0.2" 22 | crossbeam-channel = "0.5" 23 | crossbeam-queue = "0.3" 24 | event-listener = "2.5.1" 25 | bytemuck = "1.13" 26 | glam = "0.24" 27 | smallvec = "1" 28 | serde = { version = "1", features = ["derive"] } 29 | -------------------------------------------------------------------------------- /crates/rhyolite/src/accel_struct/blas.rs: -------------------------------------------------------------------------------- 1 | use std::{alloc::Layout, sync::Arc}; 2 | 3 | use ash::prelude::VkResult; 4 | use ash::vk; 5 | 6 | use crate::Allocator; 7 | use crate::BufferLike; 8 | 9 | use crate::HasDevice; 10 | use crate::ResidentBuffer; 11 | 12 | use super::build::AccelerationStructureBuild; 13 | use super::AccelerationStructure; 14 | 15 | /// Builds one AABB BLAS containing many geometries 16 | pub struct AabbBlasBuilder { 17 | geometries: Vec<(Arc, usize, vk::GeometryFlagsKHR, u32)>, // data, stride, flags, num_primitives 18 | flags: vk::BuildAccelerationStructureFlagsKHR, 19 | num_primitives: u64, 20 | geometry_primitive_counts: Vec, 21 | primitive_datasize: usize, 22 | } 23 | 24 | impl AabbBlasBuilder { 25 | pub fn new(flags: vk::BuildAccelerationStructureFlagsKHR) -> Self { 26 | Self { 27 | geometries: Vec::new(), 28 | flags, 29 | num_primitives: 0, 30 | geometry_primitive_counts: Vec::new(), 31 | primitive_datasize: 0, 32 | } 33 | } 34 | pub fn add_geometry( 35 | &mut self, 36 | primitives: Arc, 37 | flags: vk::GeometryFlagsKHR, 38 | layout: Layout, // Layout for one AABB entry 39 | ) { 40 | // There might be two cases where vk::AabbPositionsKHR aren't layed out with a stride = 24 41 | // 1. The user wants to interleave some other metadata between vk::AabbPositionsKHR. 42 | // Vulkan only guarantees that the intersection shader will be called for items within the AABB, 43 | // so without raw f32 AABB data there might be visible artifacts. 44 | // The primitive buffer likely needs to stay in device memory persistently for this, and the user might want to 45 | // interleave some other metadata alongside the vk::AabbPositionsKHR. 46 | // 2. Using the same buffer for two or more geometries, interleaving the data. We assume that this use case 47 | // would be very rare, so the design of the API does not consider this. 48 | let stride = { 49 | // verify that the layout is OK 50 | let padding_in_slice = layout.padding_needed_for(layout.align()); 51 | // VUID-VkAccelerationStructureGeometryAabbsDataKHR-stride-03545: stride must be a multiple of 8 52 | let padding_in_buffer = layout.padding_needed_for(8); 53 | debug_assert_eq!( 54 | padding_in_slice, padding_in_buffer, 55 | "Type is incompatible. Stride between items must be a multiple of 8." 56 | ); 57 | let stride = layout.size() + padding_in_buffer; 58 | debug_assert!(stride <= u32::MAX as usize); 59 | debug_assert!(stride % 8 == 0); 60 | stride 61 | }; 62 | let num_primitives = primitives.size() / stride as u64; 63 | self.num_primitives += num_primitives; 64 | self.primitive_datasize += primitives.size() as usize; 65 | self.geometries 66 | .push((primitives, stride, flags, num_primitives as u32)); 67 | self.geometry_primitive_counts.push(num_primitives as u32); 68 | } 69 | pub fn build(self, allocator: Allocator) -> VkResult { 70 | let geometries: Vec = self 71 | .geometries 72 | .iter() 73 | .map( 74 | |(_, stride, flags, _)| vk::AccelerationStructureGeometryKHR { 75 | geometry_type: vk::GeometryTypeKHR::AABBS, 76 | geometry: vk::AccelerationStructureGeometryDataKHR { 77 | aabbs: vk::AccelerationStructureGeometryAabbsDataKHR { 78 | // No need to touch the data pointer here, since this VkAccelerationStructureGeometryKHR is 79 | // used for VkgetAccelerationStructureBuildSizes only. 80 | stride: *stride as u64, 81 | ..Default::default() 82 | }, 83 | }, 84 | flags: *flags, 85 | ..Default::default() 86 | }, 87 | ) 88 | .collect(); 89 | unsafe { 90 | let build_size = allocator 91 | .device() 92 | .accel_struct_loader() 93 | .get_acceleration_structure_build_sizes( 94 | vk::AccelerationStructureBuildTypeKHR::DEVICE, 95 | &vk::AccelerationStructureBuildGeometryInfoKHR { 96 | ty: vk::AccelerationStructureTypeKHR::BOTTOM_LEVEL, 97 | flags: self.flags, 98 | mode: vk::BuildAccelerationStructureModeKHR::BUILD, 99 | geometry_count: self.geometries.len() as u32, 100 | p_geometries: geometries.as_ptr(), 101 | ..Default::default() 102 | }, 103 | &self.geometry_primitive_counts, 104 | ); 105 | let accel_struct = AccelerationStructure::new_blas_aabb( 106 | &allocator, 107 | build_size.acceleration_structure_size, 108 | )?; 109 | Ok(AccelerationStructureBuild { 110 | accel_struct, 111 | build_size, 112 | geometries: self.geometries.into_boxed_slice(), 113 | primitive_datasize: self.primitive_datasize, 114 | }) 115 | } 116 | } 117 | } 118 | -------------------------------------------------------------------------------- /crates/rhyolite/src/accel_struct/mod.rs: -------------------------------------------------------------------------------- 1 | use std::sync::Arc; 2 | 3 | use ash::{ 4 | prelude::VkResult, 5 | vk::{self, Handle}, 6 | }; 7 | 8 | use crate::{ 9 | debug::DebugObject, future::RenderData, Allocator, BufferLike, Device, HasDevice, 10 | ResidentBuffer, 11 | }; 12 | 13 | pub mod blas; 14 | pub mod build; 15 | 16 | pub struct AccelerationStructure { 17 | pub flags: vk::BuildAccelerationStructureFlagsKHR, 18 | raw: vk::AccelerationStructureKHR, 19 | device_address: vk::DeviceAddress, 20 | ty: AccelerationStructureType, 21 | buffer: ResidentBuffer, 22 | } 23 | impl RenderData for AccelerationStructure {} 24 | impl RenderData for Arc {} 25 | 26 | impl HasDevice for AccelerationStructure { 27 | fn device(&self) -> &Arc { 28 | self.buffer.device() 29 | } 30 | } 31 | impl DebugObject for AccelerationStructure { 32 | fn object_handle(&mut self) -> u64 { 33 | self.raw.as_raw() 34 | } 35 | 36 | const OBJECT_TYPE: vk::ObjectType = vk::ObjectType::ACCELERATION_STRUCTURE_KHR; 37 | } 38 | 39 | impl AccelerationStructure { 40 | pub fn device_address(&self) -> vk::DeviceAddress { 41 | self.device_address 42 | } 43 | pub fn raw(&self) -> vk::AccelerationStructureKHR { 44 | self.raw 45 | } 46 | pub fn new_blas_aabb(allocator: &Allocator, size: vk::DeviceSize) -> VkResult { 47 | Self::new( 48 | allocator, 49 | size, 50 | AccelerationStructureType::BottomLevelAABB, 51 | vk::AccelerationStructureCreateFlagsKHR::empty(), 52 | ) 53 | } 54 | pub fn new_blas_triangle(allocator: &Allocator, size: vk::DeviceSize) -> VkResult { 55 | Self::new( 56 | allocator, 57 | size, 58 | AccelerationStructureType::BottomLevelTriangle, 59 | vk::AccelerationStructureCreateFlagsKHR::empty(), 60 | ) 61 | } 62 | pub fn new_tlas(allocator: &Allocator, size: vk::DeviceSize) -> VkResult { 63 | Self::new( 64 | allocator, 65 | size, 66 | AccelerationStructureType::TopLevel, 67 | vk::AccelerationStructureCreateFlagsKHR::empty(), 68 | ) 69 | } 70 | fn new( 71 | allocator: &Allocator, 72 | size: vk::DeviceSize, 73 | ty: AccelerationStructureType, 74 | create_flags: vk::AccelerationStructureCreateFlagsKHR, 75 | ) -> VkResult { 76 | let mut backing_buffer = allocator 77 | .create_device_buffer_uninit( 78 | size, 79 | vk::BufferUsageFlags::ACCELERATION_STRUCTURE_STORAGE_KHR, 80 | 0, 81 | ) 82 | .unwrap(); 83 | backing_buffer 84 | .set_name("AccelerationStructure Backing Storage") 85 | .unwrap(); 86 | let accel_struct = unsafe { 87 | allocator 88 | .device() 89 | .accel_struct_loader() 90 | .create_acceleration_structure( 91 | &vk::AccelerationStructureCreateInfoKHR { 92 | create_flags, 93 | buffer: backing_buffer.raw_buffer(), 94 | offset: 0, 95 | size, 96 | ty: ty.into(), 97 | ..Default::default() 98 | }, 99 | None, 100 | ) 101 | }?; 102 | let device_address = unsafe { 103 | allocator 104 | .device() 105 | .accel_struct_loader() 106 | .get_acceleration_structure_device_address( 107 | &vk::AccelerationStructureDeviceAddressInfoKHR { 108 | acceleration_structure: accel_struct, 109 | ..Default::default() 110 | }, 111 | ) 112 | }; 113 | Ok(Self { 114 | flags: vk::BuildAccelerationStructureFlagsKHR::empty(), 115 | raw: accel_struct, 116 | device_address, 117 | ty, 118 | buffer: backing_buffer, 119 | }) 120 | } 121 | } 122 | 123 | impl Drop for AccelerationStructure { 124 | fn drop(&mut self) { 125 | unsafe { 126 | self.device() 127 | .accel_struct_loader() 128 | .destroy_acceleration_structure(self.raw, None) 129 | } 130 | } 131 | } 132 | 133 | #[derive(Clone, Copy)] 134 | pub enum AccelerationStructureType { 135 | TopLevel, 136 | BottomLevelAABB, 137 | BottomLevelTriangle, 138 | } 139 | impl From for vk::AccelerationStructureTypeKHR { 140 | fn from(value: AccelerationStructureType) -> Self { 141 | match value { 142 | AccelerationStructureType::TopLevel => vk::AccelerationStructureTypeKHR::TOP_LEVEL, 143 | AccelerationStructureType::BottomLevelAABB 144 | | AccelerationStructureType::BottomLevelTriangle => { 145 | vk::AccelerationStructureTypeKHR::BOTTOM_LEVEL 146 | } 147 | } 148 | } 149 | } 150 | -------------------------------------------------------------------------------- /crates/rhyolite/src/allocator.rs: -------------------------------------------------------------------------------- 1 | use std::sync::Arc; 2 | 3 | use crate::{Device, HasDevice}; 4 | use ash::vk; 5 | 6 | struct AllocatorInner { 7 | // This needs to be defined before device, so that it gets dropped hefore device gets dropped. 8 | inner: vma::Allocator, 9 | device: Arc, 10 | } 11 | 12 | #[derive(Clone)] 13 | pub struct Allocator { 14 | pub(crate) inner: Arc, 15 | device: Arc, 16 | } 17 | 18 | impl Allocator { 19 | pub fn inner(&self) -> &vma::Allocator { 20 | &self.inner 21 | } 22 | pub fn new(device: Arc) -> Self { 23 | let mut allocator_flags: vma::AllocatorCreateFlags = vma::AllocatorCreateFlags::empty(); 24 | if device 25 | .physical_device() 26 | .features() 27 | .v12 28 | .buffer_device_address 29 | == vk::TRUE 30 | { 31 | allocator_flags |= vma::AllocatorCreateFlags::BUFFER_DEVICE_ADDRESS; 32 | } 33 | 34 | let allocator = vma::Allocator::new( 35 | vma::AllocatorCreateInfo::new( 36 | device.instance().as_ref(), 37 | device.as_ref(), 38 | device.physical_device().raw(), 39 | ) 40 | .vulkan_api_version(vk::make_api_version(0, 1, 3, 0)) 41 | .flags(allocator_flags), 42 | ) 43 | .unwrap(); 44 | Self { 45 | inner: Arc::new(allocator), 46 | device, 47 | } 48 | } 49 | } 50 | 51 | impl HasDevice for Allocator { 52 | fn device(&self) -> &Arc { 53 | &self.device 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /crates/rhyolite/src/commands/mod.rs: -------------------------------------------------------------------------------- 1 | mod pool; 2 | 3 | pub use pool::*; 4 | 5 | use ash::vk; 6 | 7 | pub trait CommandBufferLike { 8 | fn raw_command_buffer(&self) -> vk::CommandBuffer; 9 | } 10 | -------------------------------------------------------------------------------- /crates/rhyolite/src/commands/pool.rs: -------------------------------------------------------------------------------- 1 | use crate::{Device, HasDevice}; 2 | use ash::vk; 3 | 4 | use std::sync::Arc; 5 | 6 | /// An unsafe command pool. Command buffer lifecycles are unmanaged. 7 | pub struct UnsafeCommandPool { 8 | device: Arc, 9 | command_pool: vk::CommandPool, 10 | queue_family_index: u32, 11 | } 12 | unsafe impl Send for UnsafeCommandPool {} 13 | impl !Sync for UnsafeCommandPool {} 14 | 15 | impl UnsafeCommandPool { 16 | pub fn new( 17 | device: Arc, 18 | queue_family_index: u32, 19 | flags: vk::CommandPoolCreateFlags, 20 | ) -> Self { 21 | let command_pool = unsafe { 22 | device.create_command_pool( 23 | &vk::CommandPoolCreateInfo { 24 | queue_family_index, 25 | flags, 26 | ..Default::default() 27 | }, 28 | None, 29 | ) 30 | } 31 | .unwrap(); 32 | Self { 33 | device, 34 | command_pool, 35 | queue_family_index, 36 | } 37 | } 38 | /// Marked unsafe because allocated command buffers won't be recycled automatically. 39 | pub unsafe fn allocate_n(&self, secondary: bool) -> [vk::CommandBuffer; N] { 40 | let mut command_buffer = [vk::CommandBuffer::null(); N]; 41 | (self.device.fp_v1_0().allocate_command_buffers)( 42 | self.device.handle(), 43 | &vk::CommandBufferAllocateInfo { 44 | command_pool: self.command_pool, 45 | level: if secondary { 46 | vk::CommandBufferLevel::SECONDARY 47 | } else { 48 | vk::CommandBufferLevel::PRIMARY 49 | }, 50 | command_buffer_count: N as u32, 51 | ..Default::default() 52 | }, 53 | command_buffer.as_mut_ptr(), 54 | ) 55 | .result() 56 | .unwrap(); 57 | command_buffer 58 | } 59 | pub unsafe fn free(&self, bufs: &[vk::CommandBuffer]) { 60 | self.device.free_command_buffers(self.command_pool, bufs) 61 | } 62 | /// Marked unsafe because allocated command buffers won't be recycled automatically. 63 | pub unsafe fn allocate_one(&self, secondary: bool) -> vk::CommandBuffer { 64 | let command_buffer: [vk::CommandBuffer; 1] = self.allocate_n(secondary); 65 | command_buffer[0] 66 | } 67 | pub fn reset(&mut self, release_resources: bool) { 68 | unsafe { 69 | self.device 70 | .reset_command_pool( 71 | self.command_pool, 72 | if release_resources { 73 | vk::CommandPoolResetFlags::RELEASE_RESOURCES 74 | } else { 75 | vk::CommandPoolResetFlags::empty() 76 | }, 77 | ) 78 | .unwrap(); 79 | } 80 | } 81 | } 82 | 83 | impl Drop for UnsafeCommandPool { 84 | fn drop(&mut self) { 85 | unsafe { 86 | self.device.destroy_command_pool(self.command_pool, None); 87 | } 88 | } 89 | } 90 | 91 | pub struct SharedCommandPool { 92 | pool: UnsafeCommandPool, 93 | command_buffers: Vec, 94 | indice: usize, 95 | } 96 | impl HasDevice for SharedCommandPool { 97 | fn device(&self) -> &Arc { 98 | &self.pool.device 99 | } 100 | } 101 | impl SharedCommandPool { 102 | pub fn new(device: Arc, queue_family_index: u32) -> Self { 103 | let pool = UnsafeCommandPool::new( 104 | device, 105 | queue_family_index, 106 | vk::CommandPoolCreateFlags::TRANSIENT, 107 | ); 108 | Self { 109 | pool, 110 | command_buffers: Vec::new(), 111 | indice: 0, 112 | } 113 | } 114 | pub fn queue_family_index(&self) -> u32 { 115 | self.pool.queue_family_index 116 | } 117 | pub fn allocate_one(&mut self) -> vk::CommandBuffer { 118 | if self.indice >= self.command_buffers.len() { 119 | let buffer = unsafe { self.pool.allocate_one(false) }; 120 | self.command_buffers.push(buffer); 121 | self.indice += 1; 122 | buffer 123 | } else { 124 | let raw_buffer = self.command_buffers[self.indice]; 125 | self.indice += 1; 126 | raw_buffer 127 | } 128 | } 129 | pub fn reset(&mut self, release_resources: bool) { 130 | self.indice = 0; 131 | self.pool.reset(release_resources); 132 | } 133 | } 134 | -------------------------------------------------------------------------------- /crates/rhyolite/src/descriptor/layout.rs: -------------------------------------------------------------------------------- 1 | use crate::Device; 2 | use ash::{prelude::VkResult, vk}; 3 | use std::{collections::BTreeMap, sync::Arc}; 4 | 5 | pub struct DescriptorSetLayout { 6 | device: Arc, 7 | pub(crate) raw: vk::DescriptorSetLayout, 8 | pub(crate) desc_types: Vec<(vk::DescriptorType, u32)>, 9 | } 10 | impl Drop for DescriptorSetLayout { 11 | fn drop(&mut self) { 12 | unsafe { 13 | self.device.destroy_descriptor_set_layout(self.raw, None); 14 | } 15 | } 16 | } 17 | impl DescriptorSetLayout { 18 | /// Users should obtain the layout from the cache. 19 | /// TODO: Actually cache this, or not. 20 | pub fn new( 21 | device: Arc, 22 | binding_infos: &[vk::DescriptorSetLayoutBinding], 23 | flags: vk::DescriptorSetLayoutCreateFlags, 24 | ) -> VkResult { 25 | let raw = unsafe { 26 | device.create_descriptor_set_layout( 27 | &vk::DescriptorSetLayoutCreateInfo { 28 | flags, 29 | binding_count: binding_infos.len() as u32, 30 | p_bindings: binding_infos.as_ptr(), 31 | ..Default::default() 32 | }, 33 | None, 34 | ) 35 | }?; 36 | 37 | let mut desc_types = BTreeMap::new(); 38 | 39 | for binding in binding_infos.iter() { 40 | if binding.p_immutable_samplers.is_null() { 41 | let count = desc_types.entry(binding.descriptor_type).or_insert(0); 42 | if binding.descriptor_type == vk::DescriptorType::INLINE_UNIFORM_BLOCK { 43 | // We take the next multiple of 8 here because on AMD, descriptor pool allocations seem 44 | // to be aligned to the 8 byte boundary. See 45 | // https://gist.github.com/Neo-Zhixing/992a0e789e34b59285026dd8161b9112 46 | *count += binding.descriptor_count.next_multiple_of(8); 47 | } else { 48 | *count += binding.descriptor_count; 49 | } 50 | } else { 51 | if binding.descriptor_type == vk::DescriptorType::COMBINED_IMAGE_SAMPLER { 52 | let count = desc_types.entry(binding.descriptor_type).or_insert(0); 53 | *count += binding.descriptor_count; 54 | } else { 55 | // Don't need separate descriptor if the sampler was built into the layout 56 | assert_eq!(binding.descriptor_type, vk::DescriptorType::SAMPLER); 57 | } 58 | } 59 | } 60 | 61 | Ok(Self { 62 | device, 63 | raw, 64 | desc_types: desc_types.into_iter().collect(), 65 | }) 66 | } 67 | pub unsafe fn raw(&self) -> vk::DescriptorSetLayout { 68 | self.raw 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /crates/rhyolite/src/descriptor/mod.rs: -------------------------------------------------------------------------------- 1 | mod layout; 2 | mod pool; 3 | 4 | use ash::vk; 5 | pub use layout::*; 6 | pub use pool::*; 7 | 8 | // descriptor pool should be a recycled resource. 9 | // It doesn't have to be per unique descriptor layout, but it can be made against a list of descriptor layouts. 10 | // It should then generate `all` of the descriptors for us to bind. 11 | // It does not have to be per-frame, but it needs to have enough capacity, 12 | // When writing descriptor, a comparison should be made first, If equal, skip writing desceritpro. 13 | pub use crate::macros::PushConstants; 14 | pub trait PushConstants { 15 | // TODO: Make this a const property, pending rust offset_of! macro 16 | fn ranges() -> Vec; 17 | } 18 | -------------------------------------------------------------------------------- /crates/rhyolite/src/error_handler.rs: -------------------------------------------------------------------------------- 1 | use ash::vk; 2 | 3 | static DEVICE_LOST_HANDLER: std::sync::OnceLock = std::sync::OnceLock::new(); 4 | 5 | pub fn handle_device_lost(error: vk::Result) -> vk::Result { 6 | if error != vk::Result::ERROR_DEVICE_LOST { 7 | return error; 8 | } 9 | if let Some(handler) = DEVICE_LOST_HANDLER.get() { 10 | (handler)(); 11 | } 12 | tracing::error!("Unhandled {:?}", error); 13 | std::process::exit(1); 14 | } 15 | 16 | pub fn set_global_device_lost_handler(handler: fn()) -> Result<(), fn()> { 17 | DEVICE_LOST_HANDLER.set(handler) 18 | } 19 | -------------------------------------------------------------------------------- /crates/rhyolite/src/lib.rs: -------------------------------------------------------------------------------- 1 | #![feature(generators, generator_trait)] 2 | #![feature(trait_alias)] 3 | #![feature(negative_impls)] 4 | #![feature(const_trait_impl)] 5 | #![feature(get_mut_unchecked)] 6 | #![feature(result_option_inspect)] 7 | #![feature(alloc_layout_extra)] 8 | #![feature(type_alias_impl_trait)] 9 | #![feature(let_chains)] 10 | #![feature(int_roundings)] 11 | #![feature(unsized_locals)] 12 | #![feature(associated_type_bounds)] 13 | #![feature(adt_const_params)] 14 | 15 | pub use bytemuck::offset_of; 16 | pub use cstr::cstr; 17 | 18 | pub extern crate ash; 19 | pub extern crate rhyolite_macro as macros; 20 | pub extern crate self as rhyolite; 21 | 22 | pub mod accel_struct; 23 | mod allocator; 24 | pub mod commands; 25 | pub mod debug; 26 | pub mod descriptor; 27 | mod device; 28 | mod dho; 29 | pub use dho::*; 30 | pub mod error_handler; 31 | pub mod future; 32 | mod instance; 33 | mod physical_device; 34 | mod pipeline; 35 | pub mod queue; 36 | mod resources; 37 | mod sampler; 38 | mod semaphore; 39 | pub mod shader; 40 | mod surface; 41 | pub mod swapchain; 42 | pub mod utils; 43 | 44 | pub use device::{Device, HasDevice}; 45 | pub use instance::*; 46 | pub use physical_device::*; 47 | pub use pipeline::*; 48 | pub use queue::*; 49 | pub use resources::*; 50 | pub use sampler::Sampler; 51 | pub use semaphore::*; 52 | pub use surface::*; 53 | pub use swapchain::*; 54 | 55 | pub use allocator::Allocator; 56 | 57 | pub use smallvec; 58 | -------------------------------------------------------------------------------- /crates/rhyolite/src/pipeline/cache.rs: -------------------------------------------------------------------------------- 1 | use std::sync::Arc; 2 | 3 | use ash::vk; 4 | 5 | use crate::Device; 6 | 7 | pub struct PipelineCache { 8 | device: Arc, 9 | cache: vk::PipelineCache, 10 | } 11 | 12 | impl Drop for PipelineCache { 13 | fn drop(&mut self) { 14 | unsafe { 15 | self.device.destroy_pipeline_cache(self.cache, None); 16 | } 17 | } 18 | } 19 | impl PipelineCache { 20 | pub fn new(device: Arc) -> Self { 21 | let cache = unsafe { 22 | device 23 | .create_pipeline_cache( 24 | &vk::PipelineCacheCreateInfo { 25 | initial_data_size: 0, 26 | p_initial_data: std::ptr::null(), 27 | ..Default::default() 28 | }, 29 | None, 30 | ) 31 | .unwrap() 32 | }; 33 | Self { device, cache } 34 | } 35 | pub fn merge(&mut self, other: impl IntoIterator) { 36 | let caches: Vec = other 37 | .into_iter() 38 | .map(|f| { 39 | assert!(Arc::ptr_eq(&self.device, &f.device)); 40 | f.cache 41 | }) 42 | .collect(); 43 | unsafe { 44 | self.device 45 | .merge_pipeline_caches(self.cache, &caches) 46 | .unwrap() 47 | } 48 | } 49 | pub fn merge_one(&mut self, other: PipelineCache) { 50 | assert!(Arc::ptr_eq(&self.device, &other.device)); 51 | unsafe { 52 | self.device 53 | .merge_pipeline_caches(self.cache, &[other.cache]) 54 | .unwrap() 55 | } 56 | } 57 | pub fn serialize(&self) -> SerializedPipelineCache { 58 | SerializedPipelineCache { 59 | data: unsafe { 60 | self.device 61 | .get_pipeline_cache_data(self.cache) 62 | .unwrap() 63 | .into_boxed_slice() 64 | }, 65 | } 66 | } 67 | pub fn deserialize(device: Arc, data: SerializedPipelineCache) -> Self { 68 | let cache = unsafe { 69 | device 70 | .create_pipeline_cache( 71 | &vk::PipelineCacheCreateInfo { 72 | initial_data_size: data.data.len(), 73 | p_initial_data: data.data.as_ptr() as *const std::ffi::c_void, 74 | ..Default::default() 75 | }, 76 | None, 77 | ) 78 | .unwrap() 79 | }; 80 | Self { device, cache } 81 | } 82 | pub unsafe fn raw(&self) -> vk::PipelineCache { 83 | self.cache 84 | } 85 | } 86 | 87 | pub struct SerializedPipelineCache { 88 | data: Box<[u8]>, 89 | } 90 | impl SerializedPipelineCache { 91 | pub fn headers(&self) -> &vk::PipelineCacheHeaderVersionOne { 92 | // This assumes little endian. 93 | let slice = &self.data[0..std::mem::size_of::()]; 94 | unsafe { &*(slice.as_ptr() as *const vk::PipelineCacheHeaderVersionOne) } 95 | } 96 | } 97 | -------------------------------------------------------------------------------- /crates/rhyolite/src/pipeline/compute.rs: -------------------------------------------------------------------------------- 1 | use ash::{prelude::VkResult, vk}; 2 | 3 | use super::PipelineCache; 4 | use crate::{ 5 | shader::{ShaderModule, SpecializedShader}, 6 | HasDevice, 7 | }; 8 | 9 | use super::PipelineLayout; 10 | use std::{ops::Deref, sync::Arc}; 11 | 12 | pub struct ComputePipeline { 13 | layout: Arc, 14 | pipeline: vk::Pipeline, 15 | } 16 | impl ComputePipeline { 17 | pub fn layout(&self) -> &Arc { 18 | &self.layout 19 | } 20 | pub fn raw(&self) -> vk::Pipeline { 21 | self.pipeline 22 | } 23 | pub fn raw_layout(&self) -> vk::PipelineLayout { 24 | self.layout.raw() 25 | } 26 | } 27 | impl HasDevice for ComputePipeline { 28 | fn device(&self) -> &Arc { 29 | self.layout.device() 30 | } 31 | } 32 | impl Drop for ComputePipeline { 33 | fn drop(&mut self) { 34 | unsafe { self.layout.device().destroy_pipeline(self.pipeline, None) } 35 | } 36 | } 37 | pub struct ComputePipelineCreateInfo<'a> { 38 | pub pipeline_layout_create_flags: vk::PipelineLayoutCreateFlags, 39 | pub pipeline_create_flags: vk::PipelineCreateFlags, 40 | pub pipeline_cache: Option<&'a PipelineCache>, 41 | } 42 | 43 | impl<'a> Default for ComputePipelineCreateInfo<'a> { 44 | fn default() -> Self { 45 | Self { 46 | pipeline_layout_create_flags: vk::PipelineLayoutCreateFlags::empty(), 47 | pipeline_create_flags: vk::PipelineCreateFlags::empty(), 48 | pipeline_cache: None, 49 | } 50 | } 51 | } 52 | 53 | impl ComputePipeline { 54 | pub fn create_with_shader_and_layout<'a, S: Deref>( 55 | shader: SpecializedShader<'a, S>, 56 | layout: Arc, 57 | pipeline_create_flags: vk::PipelineCreateFlags, 58 | pipeline_cache: Option<&'a PipelineCache>, 59 | ) -> VkResult { 60 | let device = shader.device().clone(); 61 | let pipeline = unsafe { 62 | let mut pipeline = vk::Pipeline::null(); 63 | let specialization_info = shader.specialization_info.raw_info(); 64 | (device.fp_v1_0().create_compute_pipelines)( 65 | device.handle(), 66 | pipeline_cache.map(|a| a.raw()).unwrap_or_default(), 67 | 1, 68 | &vk::ComputePipelineCreateInfo { 69 | flags: pipeline_create_flags, 70 | stage: vk::PipelineShaderStageCreateInfo { 71 | flags: shader.flags, 72 | stage: vk::ShaderStageFlags::COMPUTE, 73 | module: shader.shader.raw(), 74 | p_name: shader.entry_point.as_ptr(), 75 | p_specialization_info: &specialization_info, 76 | ..Default::default() 77 | }, 78 | layout: layout.raw(), 79 | // Do not use pipeline derivative as they're not beneficial. 80 | // https://stackoverflow.com/questions/37135130/vulkan-creating-and-benefit-of-pipeline-derivatives 81 | base_pipeline_handle: vk::Pipeline::null(), 82 | base_pipeline_index: 0, 83 | ..Default::default() 84 | }, 85 | std::ptr::null(), 86 | (&mut pipeline) as *mut _, 87 | ) 88 | .result_with_success(pipeline) 89 | }?; 90 | Ok(Self { layout, pipeline }) 91 | } 92 | } 93 | -------------------------------------------------------------------------------- /crates/rhyolite/src/pipeline/layout.rs: -------------------------------------------------------------------------------- 1 | use crate::{descriptor::DescriptorSetLayout, shader::ShaderModuleEntryPoint, Device, HasDevice}; 2 | use ash::{prelude::VkResult, vk}; 3 | use std::sync::Arc; 4 | 5 | pub struct PipelineLayout { 6 | device: Arc, 7 | inner: vk::PipelineLayout, 8 | 9 | desc_sets: Vec>, 10 | push_constant_range: Vec, 11 | } 12 | 13 | impl PipelineLayout { 14 | pub fn desc_sets(&self) -> &[Arc] { 15 | &self.desc_sets 16 | } 17 | pub fn push_constant_range(&self) -> &[vk::PushConstantRange] { 18 | &self.push_constant_range 19 | } 20 | pub fn new( 21 | device: Arc, 22 | set_layouts: Vec>, 23 | push_constant_ranges: &[vk::PushConstantRange], 24 | flags: vk::PipelineLayoutCreateFlags, 25 | ) -> VkResult { 26 | let raw_set_layouts: Vec<_> = set_layouts.iter().map(|a| unsafe { a.raw() }).collect(); 27 | let info = vk::PipelineLayoutCreateInfo { 28 | flags, 29 | set_layout_count: raw_set_layouts.len() as u32, 30 | p_set_layouts: raw_set_layouts.as_ptr(), 31 | push_constant_range_count: push_constant_ranges.len() as u32, 32 | p_push_constant_ranges: push_constant_ranges.as_ptr(), 33 | ..Default::default() 34 | }; 35 | 36 | let layout = unsafe { device.create_pipeline_layout(&info, None)? }; 37 | Ok(Self { 38 | device, 39 | inner: layout, 40 | desc_sets: set_layouts, 41 | push_constant_range: Vec::new(), 42 | }) 43 | } 44 | /// Create pipeline layout for pipelines with only one shader entry point. Only applicable to compute shaders. 45 | pub fn for_layout( 46 | device: Arc, 47 | entry_point: ShaderModuleEntryPoint, 48 | flags: vk::PipelineLayoutCreateFlags, 49 | ) -> VkResult { 50 | let set_layouts: Vec<_> = entry_point 51 | .desc_sets 52 | .iter() 53 | .map(|a| unsafe { a.raw() }) 54 | .collect(); 55 | let mut info = vk::PipelineLayoutCreateInfo { 56 | flags, 57 | set_layout_count: set_layouts.len() as u32, 58 | p_set_layouts: set_layouts.as_ptr(), 59 | ..Default::default() 60 | }; 61 | if let Some(push_constant_range) = entry_point.push_constant_range.as_ref() { 62 | info.push_constant_range_count = 1; 63 | info.p_push_constant_ranges = push_constant_range; 64 | } 65 | let layout = unsafe { device.create_pipeline_layout(&info, None)? }; 66 | Ok(Self { 67 | device, 68 | inner: layout, 69 | desc_sets: entry_point.desc_sets, 70 | push_constant_range: if let Some(range) = entry_point.push_constant_range { 71 | vec![range] 72 | } else { 73 | Vec::new() 74 | }, 75 | }) 76 | } 77 | pub fn raw(&self) -> vk::PipelineLayout { 78 | self.inner 79 | } 80 | } 81 | impl HasDevice for PipelineLayout { 82 | fn device(&self) -> &Arc { 83 | &self.device 84 | } 85 | } 86 | impl Drop for PipelineLayout { 87 | fn drop(&mut self) { 88 | unsafe { 89 | self.device.destroy_pipeline_layout(self.inner, None); 90 | } 91 | } 92 | } 93 | -------------------------------------------------------------------------------- /crates/rhyolite/src/pipeline/mod.rs: -------------------------------------------------------------------------------- 1 | mod cache; 2 | mod compute; 3 | mod layout; 4 | mod rtx; 5 | pub use cache::*; 6 | pub use compute::*; 7 | pub use layout::*; 8 | pub use rtx::*; 9 | -------------------------------------------------------------------------------- /crates/rhyolite/src/queue/mod.rs: -------------------------------------------------------------------------------- 1 | mod exec; 2 | 3 | use ash::vk; 4 | pub use exec::*; 5 | 6 | mod router; 7 | pub use compile::{CompiledQueueFuture, QueueCompileExt}; 8 | pub use router::{QueueType, QueuesRouter}; 9 | mod compile; 10 | 11 | pub struct QueueInfo { 12 | /// (Queue family, index in that family) indexed by queue index 13 | pub queues: Vec<(u32, u32)>, 14 | 15 | /// Mapping from queue families to queue refs 16 | pub families: Vec, 17 | } 18 | impl QueueInfo { 19 | pub fn new(num_queue_family: u32, queue_create_infos: &[vk::DeviceQueueCreateInfo]) -> Self { 20 | let mut families = vec![QueueMask::empty(); num_queue_family as usize]; 21 | 22 | let mut queues: Vec<(u32, u32)> = Vec::new(); 23 | 24 | for info in queue_create_infos.iter() { 25 | for i in 0..info.queue_count { 26 | families[info.queue_family_index as usize].set_queue(QueueRef(queues.len() as u8)); 27 | queues.push((info.queue_family_index, i)); 28 | } 29 | } 30 | Self { queues, families } 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /crates/rhyolite/src/resources/copy.rs: -------------------------------------------------------------------------------- 1 | use std::{ 2 | ops::{Deref, DerefMut}, 3 | pin::Pin, 4 | task::Poll, 5 | }; 6 | 7 | use ash::vk; 8 | use pin_project::pin_project; 9 | 10 | use crate::{ 11 | future::{ 12 | CommandBufferRecordContext, GPUCommandFuture, RenderData, RenderImage, RenderRes, 13 | StageContext, 14 | }, 15 | BufferLike, HasDevice, ImageLike, 16 | }; 17 | 18 | #[pin_project] 19 | pub struct CopyBufferToImageFuture< 20 | S: BufferLike + RenderData, 21 | T: ImageLike + RenderData, 22 | SRef: Deref>, 23 | TRef: DerefMut>, 24 | > { 25 | pub src: SRef, 26 | pub dst: TRef, 27 | 28 | pub buffer_row_length: u32, 29 | pub buffer_image_height: u32, 30 | 31 | /// The initial x, y, z offsets in texels of the sub-region of the source or destination image data. 32 | pub image_offset: vk::Offset3D, 33 | 34 | /// The size in texels of the image to copy in width, height and depth. 35 | pub image_extent: vk::Extent3D, 36 | 37 | pub target_layout: vk::ImageLayout, 38 | } 39 | impl< 40 | S: BufferLike + RenderData, 41 | T: ImageLike + RenderData, 42 | SRef: Deref>, 43 | TRef: DerefMut>, 44 | > GPUCommandFuture for CopyBufferToImageFuture 45 | { 46 | type Output = (); 47 | type RetainedState = (); 48 | type RecycledState = (); 49 | #[inline] 50 | fn record( 51 | self: Pin<&mut Self>, 52 | ctx: &mut CommandBufferRecordContext, 53 | _recycled_state: &mut Self::RecycledState, 54 | ) -> Poll<(Self::Output, Self::RetainedState)> { 55 | let this = self.project(); 56 | let src = this.src.deref().inner(); 57 | let dst = this.dst.deref_mut().inner_mut(); 58 | 59 | let dst_subresource_range = dst.subresource_range(); 60 | let region = vk::BufferImageCopy { 61 | buffer_offset: src.offset(), 62 | image_subresource: vk::ImageSubresourceLayers { 63 | aspect_mask: dst_subresource_range.aspect_mask, 64 | mip_level: dst_subresource_range.base_mip_level, 65 | base_array_layer: dst_subresource_range.base_array_layer, 66 | layer_count: dst_subresource_range.layer_count, 67 | }, 68 | buffer_image_height: *this.buffer_image_height, 69 | buffer_row_length: *this.buffer_row_length, 70 | image_extent: *this.image_extent, 71 | image_offset: *this.image_offset, 72 | }; 73 | ctx.record(|ctx, command_buffer| unsafe { 74 | ctx.device().cmd_copy_buffer_to_image( 75 | command_buffer, 76 | src.raw_buffer(), 77 | dst.raw_image(), 78 | *this.target_layout, 79 | &[region], 80 | ); 81 | }); 82 | Poll::Ready(((), ())) 83 | } 84 | fn context(self: Pin<&mut Self>, ctx: &mut StageContext) { 85 | let this = self.project(); 86 | ctx.read( 87 | this.src, 88 | vk::PipelineStageFlags2::COPY, 89 | vk::AccessFlags2::TRANSFER_READ, 90 | ); 91 | 92 | ctx.write_image( 93 | this.dst, 94 | vk::PipelineStageFlags2::COPY, 95 | vk::AccessFlags2::TRANSFER_WRITE, 96 | *this.target_layout, 97 | ); 98 | } 99 | } 100 | 101 | /// Copy data for a tightly packed image from a buffer to an image object, covering the entire extent of the image. 102 | pub fn copy_buffer_to_image< 103 | S: BufferLike + RenderData, 104 | T: ImageLike + RenderData, 105 | SRef: Deref>, 106 | TRef: DerefMut>, 107 | >( 108 | src: SRef, 109 | dst: TRef, 110 | target_layout: vk::ImageLayout, 111 | ) -> CopyBufferToImageFuture { 112 | let dst_subresource_range = dst.inner().subresource_range(); 113 | assert_eq!(dst_subresource_range.level_count, 1); 114 | assert_ne!(dst_subresource_range.layer_count, 0); 115 | assert_ne!( 116 | dst_subresource_range.layer_count, 117 | vk::REMAINING_ARRAY_LAYERS 118 | ); 119 | 120 | let image_extent = dst.inner().extent(); 121 | CopyBufferToImageFuture { 122 | src, 123 | dst, 124 | image_extent, 125 | image_offset: vk::Offset3D::default(), 126 | 127 | // If either of these values is zero, that aspect of the buffer memory is considered to be tightly packed according to the imageExtent. 128 | buffer_image_height: 0, 129 | buffer_row_length: 0, 130 | target_layout, 131 | } 132 | } 133 | 134 | #[pin_project] 135 | pub struct EnsureImageLayoutFuture< 136 | T: ImageLike + RenderData, 137 | TRef: DerefMut>, 138 | > { 139 | pub dst: TRef, 140 | pub target_layout: vk::ImageLayout, 141 | } 142 | impl>> GPUCommandFuture 143 | for EnsureImageLayoutFuture 144 | { 145 | type Output = (); 146 | type RetainedState = (); 147 | type RecycledState = (); 148 | #[inline] 149 | fn record( 150 | self: Pin<&mut Self>, 151 | _ctx: &mut CommandBufferRecordContext, 152 | _recycled_state: &mut Self::RecycledState, 153 | ) -> Poll<(Self::Output, Self::RetainedState)> { 154 | Poll::Ready(((), ())) 155 | } 156 | fn context(self: Pin<&mut Self>, ctx: &mut StageContext) { 157 | let this = self.project(); 158 | // Guard against all future reads. 159 | ctx.read_image( 160 | this.dst, 161 | vk::PipelineStageFlags2::ALL_COMMANDS, 162 | vk::AccessFlags2::MEMORY_READ, 163 | *this.target_layout, 164 | ); 165 | } 166 | } 167 | 168 | pub fn ensure_image_layout>>( 169 | dst: TRef, 170 | target_layout: vk::ImageLayout, 171 | ) -> EnsureImageLayoutFuture { 172 | EnsureImageLayoutFuture { dst, target_layout } 173 | } 174 | -------------------------------------------------------------------------------- /crates/rhyolite/src/resources/mod.rs: -------------------------------------------------------------------------------- 1 | mod buffer; 2 | mod copy; 3 | mod image; 4 | pub use buffer::*; 5 | pub use copy::*; 6 | pub use image::*; 7 | mod image_view; 8 | pub use image_view::*; 9 | mod managed_buffer_vec; 10 | pub use managed_buffer_vec::*; 11 | mod staging_ring_buffer; 12 | pub use staging_ring_buffer::*; 13 | 14 | #[derive(Clone)] 15 | pub enum SharingMode<'a> { 16 | Exclusive, 17 | Concurrent { queue_family_indices: &'a [u32] }, 18 | } 19 | impl<'a> Default for SharingMode<'a> { 20 | fn default() -> Self { 21 | Self::Exclusive 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /crates/rhyolite/src/sampler.rs: -------------------------------------------------------------------------------- 1 | use crate::Device; 2 | use ash::{prelude::VkResult, vk}; 3 | use std::{fmt::Debug, sync::Arc}; 4 | 5 | pub struct Sampler { 6 | device: Arc, 7 | inner: vk::Sampler, 8 | } 9 | impl Debug for Sampler { 10 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 11 | self.inner.fmt(f) 12 | } 13 | } 14 | 15 | impl Sampler { 16 | pub fn new(device: Arc, info: &vk::SamplerCreateInfo) -> VkResult { 17 | let inner = unsafe { device.create_sampler(info, None) }?; 18 | Ok(Self { device, inner }) 19 | } 20 | pub fn raw(&self) -> &vk::Sampler { 21 | &self.inner 22 | } 23 | } 24 | 25 | impl Drop for Sampler { 26 | fn drop(&mut self) { 27 | unsafe { self.device.destroy_sampler(self.inner, None) } 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /crates/rhyolite/src/semaphore.rs: -------------------------------------------------------------------------------- 1 | use std::sync::Arc; 2 | 3 | use crate::Device; 4 | use ash::{prelude::VkResult, vk}; 5 | 6 | pub struct Semaphore { 7 | device: Arc, 8 | pub(crate) semaphore: vk::Semaphore, 9 | } 10 | 11 | impl crate::HasDevice for Semaphore { 12 | fn device(&self) -> &Arc { 13 | &self.device 14 | } 15 | } 16 | 17 | impl crate::debug::DebugObject for Semaphore { 18 | const OBJECT_TYPE: vk::ObjectType = vk::ObjectType::SEMAPHORE; 19 | fn object_handle(&mut self) -> u64 { 20 | unsafe { std::mem::transmute(self.semaphore) } 21 | } 22 | } 23 | 24 | impl std::fmt::Debug for Semaphore { 25 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 26 | f.write_fmt(format_args!("Semaphore({:?})", self.semaphore)) 27 | } 28 | } 29 | 30 | impl Semaphore { 31 | pub fn new(device: Arc) -> VkResult { 32 | let create_info = vk::SemaphoreCreateInfo::default(); 33 | let semaphore = unsafe { device.create_semaphore(&create_info, None)? }; 34 | Ok(Self { device, semaphore }) 35 | } 36 | } 37 | 38 | impl Drop for Semaphore { 39 | fn drop(&mut self) { 40 | tracing::debug!(semaphore = ?self.semaphore, "drop semaphore"); 41 | // Safety: Host access to semaphore must be externally synchronized 42 | // We have &mut self thus exclusive access to self.semaphore 43 | unsafe { 44 | self.device.destroy_semaphore(self.semaphore, None); 45 | } 46 | } 47 | } 48 | 49 | pub struct TimelineSemaphore { 50 | device: Arc, 51 | pub(crate) semaphore: vk::Semaphore, 52 | } 53 | 54 | impl crate::debug::DebugObject for TimelineSemaphore { 55 | const OBJECT_TYPE: vk::ObjectType = vk::ObjectType::SEMAPHORE; 56 | fn object_handle(&mut self) -> u64 { 57 | unsafe { std::mem::transmute(self.semaphore) } 58 | } 59 | } 60 | 61 | impl std::fmt::Debug for TimelineSemaphore { 62 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 63 | f.write_fmt(format_args!("TimelineSemaphore({:?})", self.semaphore)) 64 | } 65 | } 66 | 67 | impl crate::HasDevice for TimelineSemaphore { 68 | fn device(&self) -> &Arc { 69 | &self.device 70 | } 71 | } 72 | 73 | impl TimelineSemaphore { 74 | pub fn new(device: Arc, initial_value: u64) -> VkResult { 75 | let type_info = vk::SemaphoreTypeCreateInfo::builder() 76 | .semaphore_type(vk::SemaphoreType::TIMELINE) 77 | .initial_value(initial_value) 78 | .build(); 79 | let create_info = vk::SemaphoreCreateInfo { 80 | p_next: &type_info as *const _ as *const std::ffi::c_void, 81 | ..Default::default() 82 | }; 83 | let semaphore = unsafe { device.create_semaphore(&create_info, None)? }; 84 | Ok(TimelineSemaphore { device, semaphore }) 85 | } 86 | pub fn signal(&self, value: u64) -> VkResult<()> { 87 | unsafe { 88 | self.device.signal_semaphore(&vk::SemaphoreSignalInfo { 89 | semaphore: self.semaphore, 90 | value, 91 | ..Default::default() 92 | }) 93 | } 94 | } 95 | pub fn value(&self) -> VkResult { 96 | unsafe { self.device.get_semaphore_counter_value(self.semaphore) } 97 | } 98 | /// Block the current thread until the semaphore reaches (>=) the given value 99 | pub fn wait(self: &TimelineSemaphore, value: u64) -> VkResult<()> { 100 | unsafe { 101 | self.device.wait_semaphores( 102 | &vk::SemaphoreWaitInfo { 103 | semaphore_count: 1, 104 | p_semaphores: &self.semaphore, 105 | p_values: &value, 106 | ..Default::default() 107 | }, 108 | std::u64::MAX, 109 | ) 110 | } 111 | } 112 | } 113 | -------------------------------------------------------------------------------- /crates/rhyolite/src/shader.rs: -------------------------------------------------------------------------------- 1 | use ash::prelude::VkResult; 2 | use ash::vk; 3 | 4 | use std::ffi::CStr; 5 | use std::fmt::Debug; 6 | use std::ops::Deref; 7 | use std::sync::Arc; 8 | 9 | use crate::descriptor::DescriptorSetLayout; 10 | 11 | use crate::{Device, HasDevice}; 12 | 13 | pub struct SpirvShader> { 14 | pub data: T, 15 | } 16 | 17 | impl> SpirvShader { 18 | pub fn build(self, device: Arc) -> VkResult { 19 | ShaderModule::new(device, &self.data) 20 | } 21 | } 22 | 23 | pub struct ShaderModule { 24 | device: Arc, 25 | module: vk::ShaderModule, 26 | } 27 | impl Debug for ShaderModule { 28 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 29 | f.debug_tuple("ShaderModule").field(&self.module).finish() 30 | } 31 | } 32 | impl ShaderModule { 33 | pub fn new(device: Arc, data: &[u32]) -> VkResult { 34 | let module = unsafe { 35 | device.create_shader_module( 36 | &vk::ShaderModuleCreateInfo { 37 | code_size: std::mem::size_of_val(data), 38 | p_code: data.as_ptr(), 39 | ..Default::default() 40 | }, 41 | None, 42 | ) 43 | }?; 44 | Ok(Self { device, module }) 45 | } 46 | pub fn raw(&self) -> vk::ShaderModule { 47 | self.module 48 | } 49 | pub fn specialized<'a>( 50 | &'a self, 51 | entry_point: &'a CStr, 52 | stage: vk::ShaderStageFlags, 53 | ) -> SpecializedShader<&'a ShaderModule> { 54 | SpecializedShader { 55 | stage, 56 | flags: vk::PipelineShaderStageCreateFlags::empty(), 57 | shader: self, 58 | specialization_info: Default::default(), 59 | entry_point, 60 | } 61 | } 62 | } 63 | 64 | impl HasDevice for ShaderModule { 65 | fn device(&self) -> &Arc { 66 | &self.device 67 | } 68 | } 69 | impl Drop for ShaderModule { 70 | fn drop(&mut self) { 71 | unsafe { 72 | self.device.destroy_shader_module(self.module, None); 73 | } 74 | } 75 | } 76 | 77 | #[derive(Clone)] 78 | pub struct ShaderModuleEntryPoint { 79 | pub stage: vk::ShaderStageFlags, 80 | pub desc_sets: Vec>, 81 | pub push_constant_range: Option, 82 | } 83 | 84 | #[derive(Clone, Default, Debug)] 85 | pub struct SpecializationInfo { 86 | pub(super) data: Vec, 87 | pub(super) entries: Vec, 88 | } 89 | impl SpecializationInfo { 90 | pub unsafe fn raw_info(&self) -> vk::SpecializationInfo { 91 | vk::SpecializationInfo { 92 | map_entry_count: self.entries.len() as u32, 93 | p_map_entries: if self.entries.is_empty() { 94 | std::ptr::null() 95 | } else { 96 | self.entries.as_ptr() 97 | }, 98 | data_size: self.data.len(), 99 | p_data: if self.data.is_empty() { 100 | std::ptr::null() 101 | } else { 102 | self.data.as_ptr() as *const _ 103 | }, 104 | } 105 | } 106 | pub const fn new() -> Self { 107 | Self { 108 | data: Vec::new(), 109 | entries: Vec::new(), 110 | } 111 | } 112 | pub fn push(&mut self, constant_id: u32, item: T) { 113 | if std::any::TypeId::of::() == std::any::TypeId::of::() { 114 | unsafe { 115 | let value: bool = std::mem::transmute_copy(&item); 116 | self.push_bool(constant_id, value); 117 | return; 118 | } 119 | } 120 | let size = std::mem::size_of::(); 121 | self.entries.push(vk::SpecializationMapEntry { 122 | constant_id, 123 | offset: self.data.len() as u32, 124 | size, 125 | }); 126 | self.data.reserve(size); 127 | unsafe { 128 | let target_ptr = self.data.as_mut_ptr().add(self.data.len()); 129 | std::ptr::copy_nonoverlapping(&item as *const T as *const u8, target_ptr, size); 130 | self.data.set_len(self.data.len() + size); 131 | } 132 | } 133 | fn push_bool(&mut self, constant_id: u32, item: bool) { 134 | let size = std::mem::size_of::(); 135 | self.entries.push(vk::SpecializationMapEntry { 136 | constant_id, 137 | offset: self.data.len() as u32, 138 | size, 139 | }); 140 | self.data.reserve(size); 141 | unsafe { 142 | let item: vk::Bool32 = if item { vk::TRUE } else { vk::FALSE }; 143 | let target_ptr = self.data.as_mut_ptr().add(self.data.len()); 144 | std::ptr::copy_nonoverlapping( 145 | &item as *const vk::Bool32 as *const u8, 146 | target_ptr, 147 | size, 148 | ); 149 | self.data.set_len(self.data.len() + size); 150 | } 151 | } 152 | } 153 | 154 | #[derive(Clone, Debug)] 155 | pub struct SpecializedShader<'a, S: Deref> { 156 | pub stage: vk::ShaderStageFlags, 157 | pub flags: vk::PipelineShaderStageCreateFlags, 158 | pub shader: S, 159 | pub specialization_info: SpecializationInfo, 160 | pub entry_point: &'a CStr, 161 | } 162 | impl<'a, S: Deref> HasDevice for SpecializedShader<'a, S> { 163 | fn device(&self) -> &Arc { 164 | &self.shader.device 165 | } 166 | } 167 | -------------------------------------------------------------------------------- /crates/rhyolite/src/surface.rs: -------------------------------------------------------------------------------- 1 | use crate::{Instance, PhysicalDevice}; 2 | use ash::extensions::khr; 3 | use ash::prelude::VkResult; 4 | use ash::vk; 5 | use std::ops::Deref; 6 | use std::sync::Arc; 7 | 8 | pub struct SurfaceLoader { 9 | instance: Arc, 10 | loader: khr::Surface, 11 | } 12 | 13 | impl SurfaceLoader { 14 | pub fn new(instance: Arc) -> Self { 15 | let loader = khr::Surface::new(instance.entry(), &instance); 16 | SurfaceLoader { instance, loader } 17 | } 18 | pub fn instance(&self) -> &Arc { 19 | &self.instance 20 | } 21 | } 22 | 23 | impl Deref for SurfaceLoader { 24 | type Target = khr::Surface; 25 | 26 | fn deref(&self) -> &Self::Target { 27 | &self.loader 28 | } 29 | } 30 | 31 | pub struct Surface { 32 | instance: Arc, 33 | pub(crate) surface: vk::SurfaceKHR, 34 | } 35 | 36 | impl Surface { 37 | pub fn raw(&self) -> vk::SurfaceKHR { 38 | self.surface 39 | } 40 | pub fn instance(&self) -> &Arc { 41 | &self.instance 42 | } 43 | pub fn create( 44 | instance: Arc, 45 | window_handle: &impl raw_window_handle::HasRawWindowHandle, 46 | display_handle: &impl raw_window_handle::HasRawDisplayHandle, 47 | ) -> VkResult { 48 | let surface = unsafe { 49 | ash_window::create_surface( 50 | instance.entry(), 51 | &instance, 52 | display_handle.raw_display_handle(), 53 | window_handle.raw_window_handle(), 54 | None, 55 | )? 56 | }; 57 | Ok(Surface { instance, surface }) 58 | } 59 | 60 | /// Query the basic capabilities of a surface, needed in order to create a swapchain 61 | pub fn get_capabilities( 62 | &self, 63 | pdevice: &PhysicalDevice, 64 | ) -> VkResult { 65 | assert_eq!(pdevice.instance().handle(), self.instance.handle(), "Both of physicalDevice, and surface must have been created, allocated, or retrieved from the same VkInstance"); 66 | unsafe { 67 | self.instance 68 | .surface_loader() 69 | .get_physical_device_surface_capabilities(pdevice.raw(), self.surface) 70 | } 71 | } 72 | 73 | /// Determine whether a queue family of a physical device supports presentation to a given surface 74 | pub fn supports_queue_family( 75 | &self, 76 | pdevice: &PhysicalDevice, 77 | queue_family_index: u32, 78 | ) -> VkResult { 79 | assert_eq!(pdevice.instance().handle(), self.instance.handle(), "Both of physicalDevice, and surface must have been created, allocated, or retrieved from the same VkInstance"); 80 | unsafe { 81 | self.instance 82 | .surface_loader() 83 | .get_physical_device_surface_support( 84 | pdevice.raw(), 85 | queue_family_index, 86 | self.surface, 87 | ) 88 | } 89 | } 90 | 91 | /// Query color formats supported by surface 92 | pub fn get_formats(&self, pdevice: &PhysicalDevice) -> VkResult> { 93 | assert_eq!(pdevice.instance().handle(), self.instance.handle(), "Both of physicalDevice, and surface must have been created, allocated, or retrieved from the same VkInstance"); 94 | unsafe { 95 | self.instance 96 | .surface_loader() 97 | .get_physical_device_surface_formats(pdevice.raw(), self.surface) 98 | } 99 | } 100 | 101 | pub fn pick_format( 102 | &self, 103 | pdevice: &PhysicalDevice, 104 | usage: vk::ImageUsageFlags, 105 | ) -> VkResult> { 106 | assert!(!usage.is_empty()); 107 | let formats = self.get_formats(pdevice)?; 108 | let format = formats 109 | .into_iter() 110 | .filter(|f| { 111 | let properties = pdevice 112 | .image_format_properties(&vk::PhysicalDeviceImageFormatInfo2 { 113 | format: f.format, 114 | ty: vk::ImageType::TYPE_2D, // We're gonna use this for presentation on a surface (screen) so the image should be 2D 115 | tiling: vk::ImageTiling::OPTIMAL, // Of course you want optimal tiling for presentation right 116 | usage, 117 | flags: vk::ImageCreateFlags::empty(), // Nothing fancy should be needed for presentation here 118 | ..Default::default() 119 | }) 120 | .unwrap(); 121 | properties.is_some() 122 | }) 123 | .next(); 124 | Ok(format) 125 | } 126 | 127 | /// Query color formats supported by surface 128 | pub fn get_present_modes(&self, pdevice: &PhysicalDevice) -> VkResult> { 129 | assert_eq!(pdevice.instance().handle(), self.instance.handle(), "Both of physicalDevice, and surface must have been created, allocated, or retrieved from the same VkInstance"); 130 | unsafe { 131 | self.instance 132 | .surface_loader() 133 | .get_physical_device_surface_present_modes(pdevice.raw(), self.surface) 134 | } 135 | } 136 | } 137 | 138 | impl Drop for Surface { 139 | fn drop(&mut self) { 140 | tracing::info!(surface = ?self.surface, "drop surface"); 141 | unsafe { 142 | self.instance 143 | .surface_loader() 144 | .destroy_surface(self.surface, None); 145 | } 146 | } 147 | } 148 | -------------------------------------------------------------------------------- /crates/rhyolite/src/utils/either.rs: -------------------------------------------------------------------------------- 1 | use crate::future::RenderData; 2 | 3 | pub enum Either { 4 | Left(A), 5 | Right(B), 6 | } 7 | 8 | impl RenderData for Either { 9 | fn tracking_feedback(&mut self, feedback: &crate::future::TrackingFeedback) { 10 | match self { 11 | Either::Left(a) => a.tracking_feedback(feedback), 12 | Either::Right(a) => a.tracking_feedback(feedback), 13 | } 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /crates/rhyolite/src/utils/merge_iter.rs: -------------------------------------------------------------------------------- 1 | use core::cmp::Ordering; 2 | use core::fmt::{self, Debug}; 3 | use core::iter::FusedIterator; 4 | use std::cmp::max; 5 | use std::collections::btree_map::Iter; 6 | use std::collections::BTreeMap; 7 | 8 | /// Core of an iterator that merges the output of two strictly ascending iterators, 9 | /// for instance a union or a symmetric difference. 10 | pub struct MergeIterInner { 11 | a: I1, 12 | b: I2, 13 | peeked: Option>, 14 | } 15 | 16 | /// Benchmarks faster than wrapping both iterators in a Peekable, 17 | /// probably because we can afford to impose a FusedIterator bound. 18 | #[derive(Clone, Debug)] 19 | enum Peeked { 20 | A(I1::Item), 21 | B(I2::Item), 22 | } 23 | 24 | impl Clone for MergeIterInner 25 | where 26 | I1: Clone, 27 | I1::Item: Clone, 28 | I2: Clone, 29 | I2::Item: Clone, 30 | { 31 | fn clone(&self) -> Self { 32 | Self { 33 | a: self.a.clone(), 34 | b: self.b.clone(), 35 | peeked: self.peeked.clone(), 36 | } 37 | } 38 | } 39 | 40 | impl Debug for MergeIterInner 41 | where 42 | I1: Debug, 43 | I1::Item: Debug, 44 | I2: Debug, 45 | I2::Item: Debug, 46 | { 47 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 48 | f.debug_tuple("MergeIterInner") 49 | .field(&self.a) 50 | .field(&self.b) 51 | .field(&self.peeked) 52 | .finish() 53 | } 54 | } 55 | 56 | impl MergeIterInner { 57 | /// Creates a new core for an iterator merging a pair of sources. 58 | pub fn new(a: I1, b: I2) -> Self { 59 | MergeIterInner { a, b, peeked: None } 60 | } 61 | 62 | /// Returns the next pair of items stemming from the pair of sources 63 | /// being merged. If both returned options contain a value, that value 64 | /// is equal and occurs in both sources. If one of the returned options 65 | /// contains a value, that value doesn't occur in the other source (or 66 | /// the sources are not strictly ascending). If neither returned option 67 | /// contains a value, iteration has finished and subsequent calls will 68 | /// return the same empty pair. 69 | pub fn nexts Ordering>( 70 | &mut self, 71 | cmp: Cmp, 72 | ) -> (Option, Option) 73 | where 74 | I1: FusedIterator, 75 | I2: FusedIterator, 76 | { 77 | let mut a_next; 78 | let mut b_next; 79 | match self.peeked.take() { 80 | Some(Peeked::A(next)) => { 81 | a_next = Some(next); 82 | b_next = self.b.next(); 83 | } 84 | Some(Peeked::B(next)) => { 85 | b_next = Some(next); 86 | a_next = self.a.next(); 87 | } 88 | None => { 89 | a_next = self.a.next(); 90 | b_next = self.b.next(); 91 | } 92 | } 93 | if let (Some(ref a1), Some(ref b1)) = (&a_next, &b_next) { 94 | match cmp(a1, b1) { 95 | Ordering::Less => self.peeked = b_next.take().map(Peeked::B), 96 | Ordering::Greater => self.peeked = a_next.take().map(Peeked::A), 97 | Ordering::Equal => (), 98 | } 99 | } 100 | (a_next, b_next) 101 | } 102 | 103 | /// Returns a pair of upper bounds for the `size_hint` of the final iterator. 104 | pub fn lens(&self) -> (usize, usize) 105 | where 106 | I1: ExactSizeIterator, 107 | I2: ExactSizeIterator, 108 | { 109 | match self.peeked { 110 | Some(Peeked::A(_)) => (1 + self.a.len(), self.b.len()), 111 | Some(Peeked::B(_)) => (self.a.len(), 1 + self.b.len()), 112 | _ => (self.a.len(), self.b.len()), 113 | } 114 | } 115 | } 116 | 117 | pub struct Union<'a, K: 'a, V1: 'a, V2: 'a> { 118 | inner: MergeIterInner, Iter<'a, K, V2>>, 119 | } 120 | 121 | impl<'a, K: Ord, V1, V2> Iterator for Union<'a, K, V1, V2> { 122 | type Item = (&'a K, Option<&'a V1>, Option<&'a V2>); 123 | 124 | fn next(&mut self) -> Option { 125 | let (a_next, b_next) = self.inner.nexts(|a, b| a.0.cmp(b.0)); 126 | let key = a_next.map(|a| a.0).or(b_next.map(|b| b.0)); 127 | if let Some(key) = key { 128 | let a_next = a_next.map(|a| a.1); 129 | let b_next = b_next.map(|b| b.1); 130 | Some((key, a_next, b_next)) 131 | } else { 132 | None 133 | } 134 | } 135 | 136 | fn size_hint(&self) -> (usize, Option) { 137 | let (a_len, b_len) = self.inner.lens(); 138 | // No checked_add - see SymmetricDifference::size_hint. 139 | (max(a_len, b_len), Some(a_len + b_len)) 140 | } 141 | 142 | fn min(mut self) -> Option { 143 | self.next() 144 | } 145 | } 146 | 147 | pub fn btree_map_union<'a, K, V1, V2>( 148 | a: &'a BTreeMap, 149 | b: &'a BTreeMap, 150 | ) -> Union<'a, K, V1, V2> { 151 | Union { 152 | inner: MergeIterInner::new(a.iter(), b.iter()), 153 | } 154 | } 155 | -------------------------------------------------------------------------------- /crates/rhyolite/src/utils/merge_ranges.rs: -------------------------------------------------------------------------------- 1 | /// Given iterator of the form `Iterator` 2 | /// emitting ordered items, 3 | /// `MergeRangeIterator` emits list of (start, size) 4 | /// for each consecutive range of numbers, where `start` 5 | /// is the starting indice of the range, and `size` is the 6 | /// number of consecutive items. 7 | pub struct MergeRangeIterator> { 8 | inner: ITER, 9 | last: Option, 10 | } 11 | 12 | impl> Iterator for MergeRangeIterator { 13 | type Item = (usize, usize); 14 | 15 | fn next(&mut self) -> Option { 16 | // (start, size) 17 | let mut state: Option<(usize, usize)> = self.last.take().map(|last| (last, 1)); 18 | loop { 19 | let Some(next) = self.inner.next() else { 20 | return state; 21 | }; 22 | 23 | if let Some((start, size)) = state.as_mut() { 24 | if next == *start + *size { 25 | *size += 1; 26 | continue; 27 | } else { 28 | self.last = Some(next); 29 | return Some((*start, *size)); 30 | } 31 | } else { 32 | state = Some((next, 1)); 33 | } 34 | } 35 | } 36 | } 37 | 38 | pub trait MergeRangeIteratorExt { 39 | fn merge_ranges(self) -> MergeRangeIterator 40 | where 41 | Self: Iterator + Sized, 42 | { 43 | MergeRangeIterator { 44 | inner: self, 45 | last: None, 46 | } 47 | } 48 | } 49 | impl + Sized> MergeRangeIteratorExt for T {} 50 | 51 | #[cfg(test)] 52 | mod tests { 53 | use super::*; 54 | #[test] 55 | fn test_empty() { 56 | let mut iter = [].into_iter().merge_ranges(); 57 | assert!(iter.next().is_none()); 58 | } 59 | #[test] 60 | fn test_one() { 61 | let mut iter = [10].into_iter().merge_ranges(); 62 | assert_eq!(iter.next().unwrap(), (10, 1)); 63 | assert!(iter.next().is_none()); 64 | } 65 | 66 | #[test] 67 | fn test() { 68 | let mut iter = [11, 12, 15, 16, 17].into_iter().merge_ranges(); 69 | assert_eq!(iter.next().unwrap(), (11, 2)); 70 | assert_eq!(iter.next().unwrap(), (15, 3)); 71 | assert!(iter.next().is_none()); 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /crates/rhyolite/src/utils/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod either; 2 | pub mod format; 3 | pub mod merge_iter; 4 | pub mod merge_ranges; 5 | pub mod retainer; 6 | pub mod send_marker; 7 | -------------------------------------------------------------------------------- /crates/rhyolite/src/utils/retainer.rs: -------------------------------------------------------------------------------- 1 | use std::{ 2 | ops::{Deref, DerefMut}, 3 | sync::Arc, 4 | }; 5 | 6 | pub struct Retainer { 7 | inner: Arc, 8 | } 9 | unsafe impl Send for Retainer {} 10 | unsafe impl Sync for Retainer {} 11 | 12 | pub struct RetainerHandle { 13 | inner: Arc, 14 | } 15 | 16 | impl Retainer { 17 | pub fn new(inner: T) -> Self { 18 | Self { 19 | inner: Arc::new(inner), 20 | } 21 | } 22 | pub fn handle(&self) -> RetainerHandle { 23 | RetainerHandle { 24 | inner: self.inner.clone(), 25 | } 26 | } 27 | } 28 | 29 | impl Deref for Retainer { 30 | type Target = T; 31 | 32 | fn deref(&self) -> &Self::Target { 33 | self.inner.as_ref() 34 | } 35 | } 36 | 37 | impl DerefMut for Retainer { 38 | fn deref_mut(&mut self) -> &mut Self::Target { 39 | unsafe { Arc::get_mut_unchecked(&mut self.inner) } 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /crates/rhyolite/src/utils/send_marker.rs: -------------------------------------------------------------------------------- 1 | use std::ops::{Deref, DerefMut}; 2 | 3 | pub struct SendMarker(T); 4 | 5 | unsafe impl Send for SendMarker {} 6 | unsafe impl Sync for SendMarker {} 7 | impl SendMarker { 8 | pub unsafe fn new(inner: T) -> Self { 9 | Self(inner) 10 | } 11 | 12 | pub unsafe fn unwrap(self) -> T { 13 | self.0 14 | } 15 | } 16 | 17 | impl Deref for SendMarker { 18 | type Target = T; 19 | 20 | fn deref(&self) -> &Self::Target { 21 | &self.0 22 | } 23 | } 24 | impl DerefMut for SendMarker { 25 | fn deref_mut(&mut self) -> &mut Self::Target { 26 | &mut self.0 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /crates/rhyolite/src/utils/unsized_vec.rs: -------------------------------------------------------------------------------- 1 | use std::{marker::{PhantomData}, alloc::Layout, mem::ManuallyDrop, ops::{Index, IndexMut}, ptr::{NonNull, Pointee}}; 2 | 3 | pub struct UnsizedVec { 4 | metadata: Vec<::Metadata>, 5 | inner: Vec, 6 | layout: Layout, 7 | _marker: PhantomData 8 | } 9 | 10 | impl UnsizedVec { 11 | pub fn push(&mut self, mut item: T) { 12 | let item_layout = std::alloc::Layout::for_value(&item); 13 | assert!(item_layout.pad_to_align().size() <= self.layout.pad_to_align().size()); 14 | 15 | let ptr = unsafe { 16 | NonNull::new_unchecked(&mut item) 17 | }; 18 | let (raw_ptr, metadata) = ptr.to_raw_parts(); 19 | self.metadata.push(metadata); 20 | 21 | let offset = self.inner.len(); 22 | self.inner.extend(std::iter::repeat(0).take(self.layout.pad_to_align().size())); 23 | unsafe { 24 | std::ptr::copy_nonoverlapping(raw_ptr.cast::().as_ptr(), self.inner.as_mut_ptr().add(offset), item_layout.size()); 25 | } 26 | std::mem::forget_unsized(item); 27 | } 28 | pub fn len(&self) -> usize { 29 | self.metadata.len() 30 | } 31 | pub fn clear(&mut self) { 32 | for i in 0..self.metadata.len() { 33 | unsafe { 34 | std::ptr::drop_in_place(&mut self[i]); 35 | } 36 | } 37 | self.metadata.clear(); 38 | self.inner.clear(); 39 | } 40 | } 41 | 42 | impl Drop for UnsizedVec { 43 | fn drop(&mut self) { 44 | for i in 0..self.metadata.len() { 45 | unsafe { 46 | std::ptr::drop_in_place(&mut self[i]); 47 | } 48 | } 49 | } 50 | } 51 | 52 | 53 | 54 | 55 | impl Index for UnsizedVec { 56 | type Output = T; 57 | 58 | fn index(&self, index: usize) -> &Self::Output { 59 | let metadata = self.metadata[index]; 60 | let index = index * self.layout.pad_to_align().size(); 61 | unsafe { 62 | let ptr = self.inner.as_ptr().add(index); 63 | let ptr: NonNull<()> = NonNull::new_unchecked(ptr as *mut u8).cast(); 64 | let ptr: NonNull = NonNull::from_raw_parts(ptr, metadata); 65 | ptr.as_ref() 66 | } 67 | } 68 | } 69 | impl IndexMut for UnsizedVec { 70 | fn index_mut(&mut self, index: usize) -> &mut Self::Output { 71 | let metadata = self.metadata[index]; 72 | let index = index * self.layout.pad_to_align().size(); 73 | unsafe { 74 | let ptr = self.inner.as_mut_ptr().add(index); 75 | let ptr: NonNull<()> = NonNull::new_unchecked(ptr).cast(); 76 | let mut ptr: NonNull = NonNull::from_raw_parts(ptr, metadata); 77 | ptr.as_mut() 78 | } 79 | } 80 | } 81 | 82 | -------------------------------------------------------------------------------- /crates/rhyolite_bevy/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "rhyolite-bevy" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 7 | 8 | [dependencies] 9 | rhyolite = { path = "../rhyolite" } 10 | bevy_app = { git = "https://github.com/bevyengine/bevy.git", rev = "2c7eab1b4c4ec6c533b6b609d5ddf8a7282f2c4f" } 11 | bevy_tasks = { git = "https://github.com/bevyengine/bevy.git", rev = "2c7eab1b4c4ec6c533b6b609d5ddf8a7282f2c4f", features = ["multi-threaded"] } 12 | bevy_ecs = { git = "https://github.com/bevyengine/bevy.git", rev = "2c7eab1b4c4ec6c533b6b609d5ddf8a7282f2c4f" } 13 | bevy_window = { git = "https://github.com/bevyengine/bevy.git", rev = "2c7eab1b4c4ec6c533b6b609d5ddf8a7282f2c4f" } 14 | bevy_asset = { git = "https://github.com/bevyengine/bevy.git", rev = "2c7eab1b4c4ec6c533b6b609d5ddf8a7282f2c4f" } 15 | bevy_reflect = { git = "https://github.com/bevyengine/bevy.git", rev = "2c7eab1b4c4ec6c533b6b609d5ddf8a7282f2c4f" } 16 | bevy_a11y = { git = "https://github.com/bevyengine/bevy.git", rev = "2c7eab1b4c4ec6c533b6b609d5ddf8a7282f2c4f" } 17 | bevy_utils = { git = "https://github.com/bevyengine/bevy.git", rev = "2c7eab1b4c4ec6c533b6b609d5ddf8a7282f2c4f" } 18 | bevy_winit = { git = "https://github.com/bevyengine/bevy.git", rev = "2c7eab1b4c4ec6c533b6b609d5ddf8a7282f2c4f", features = ["x11", "wayland"] } 19 | thread_local = "^1" 20 | thiserror = "1" 21 | event-listener = "2.5.1" 22 | blocking = "1.2" 23 | crossbeam-channel = "^0.5" 24 | tracing = "0.1" 25 | png = "0.17" 26 | winit = "0.28" 27 | raw-window-handle = "0.5.2" 28 | serde = { version = "1", features = ["derive"] } 29 | 30 | [dev-dependencies] 31 | image = "0.24" 32 | pin-project = "1.0" 33 | reqwest = { version = "*", features = ["blocking"] } 34 | bevy_log = { git = "https://github.com/bevyengine/bevy.git", rev = "2c7eab1b4c4ec6c533b6b609d5ddf8a7282f2c4f" } 35 | bevy_input = { git = "https://github.com/bevyengine/bevy.git", rev = "2c7eab1b4c4ec6c533b6b609d5ddf8a7282f2c4f" } 36 | bevy_core = { git = "https://github.com/bevyengine/bevy.git", rev = "2c7eab1b4c4ec6c533b6b609d5ddf8a7282f2c4f" } 37 | bevy_hierarchy = { git = "https://github.com/bevyengine/bevy.git", rev = "2c7eab1b4c4ec6c533b6b609d5ddf8a7282f2c4f" } 38 | bevy_transform = { git = "https://github.com/bevyengine/bevy.git", rev = "2c7eab1b4c4ec6c533b6b609d5ddf8a7282f2c4f" } 39 | bevy_time = { git = "https://github.com/bevyengine/bevy.git", rev = "2c7eab1b4c4ec6c533b6b609d5ddf8a7282f2c4f" } 40 | bevy_diagnostic = { git = "https://github.com/bevyengine/bevy.git", rev = "2c7eab1b4c4ec6c533b6b609d5ddf8a7282f2c4f" } 41 | -------------------------------------------------------------------------------- /crates/rhyolite_bevy/src/image.rs: -------------------------------------------------------------------------------- 1 | use bevy_asset::Asset; 2 | use bevy_reflect::TypePath; 3 | use rhyolite::HasDevice; 4 | use rhyolite::{ 5 | ash::{prelude::VkResult, vk}, 6 | ImageArraySlicedViews, ImageLike, ResidentImage, 7 | }; 8 | 9 | #[derive(TypePath, Asset)] 10 | pub struct Image(ResidentImage); 11 | impl Image { 12 | pub fn new(image: ResidentImage) -> Self { 13 | Self(image) 14 | } 15 | } 16 | impl HasDevice for Image { 17 | fn device(&self) -> &std::sync::Arc { 18 | self.0.device() 19 | } 20 | } 21 | 22 | impl ImageLike for Image { 23 | fn raw_image(&self) -> rhyolite::ash::vk::Image { 24 | self.0.raw_image() 25 | } 26 | fn subresource_range(&self) -> rhyolite::ash::vk::ImageSubresourceRange { 27 | self.0.subresource_range() 28 | } 29 | fn offset(&self) -> rhyolite::ash::vk::Offset3D { 30 | self.0.offset() 31 | } 32 | fn format(&self) -> rhyolite::ash::vk::Format { 33 | self.0.format() 34 | } 35 | fn extent(&self) -> rhyolite::ash::vk::Extent3D { 36 | self.0.extent() 37 | } 38 | } 39 | 40 | #[derive(TypePath, Asset)] 41 | pub struct SlicedImageArray(ImageArraySlicedViews); 42 | impl SlicedImageArray { 43 | pub fn new(image: ResidentImage) -> VkResult { 44 | let image = ImageArraySlicedViews::new(image, vk::ImageViewType::TYPE_2D)?; 45 | Ok(Self(image)) 46 | } 47 | pub fn slice(&self, index: usize) -> rhyolite::ImageArraySliceView { 48 | self.0.slice(index) 49 | } 50 | pub fn slice_mut(&mut self, index: usize) -> rhyolite::ImageArraySliceViewMut { 51 | self.0.slice_mut(index) 52 | } 53 | } 54 | impl HasDevice for SlicedImageArray { 55 | fn device(&self) -> &std::sync::Arc { 56 | self.0.device() 57 | } 58 | } 59 | impl ImageLike for SlicedImageArray { 60 | fn raw_image(&self) -> vk::Image { 61 | self.0.raw_image() 62 | } 63 | 64 | fn subresource_range(&self) -> vk::ImageSubresourceRange { 65 | self.0.subresource_range() 66 | } 67 | 68 | fn extent(&self) -> vk::Extent3D { 69 | self.0.extent() 70 | } 71 | 72 | fn format(&self) -> vk::Format { 73 | self.0.format() 74 | } 75 | 76 | fn offset(&self) -> vk::Offset3D { 77 | self.0.offset() 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /crates/rhyolite_bevy/src/loaders/mod.rs: -------------------------------------------------------------------------------- 1 | mod png; 2 | pub use self::png::PngLoader; 3 | -------------------------------------------------------------------------------- /crates/rhyolite_bevy/src/types.rs: -------------------------------------------------------------------------------- 1 | use std::{ops::Deref, sync::Arc}; 2 | 3 | use bevy_ecs::{system::Resource, world::FromWorld}; 4 | use rhyolite::ash::prelude::VkResult; 5 | 6 | #[derive(Resource, Clone)] 7 | pub struct Allocator(rhyolite::Allocator); 8 | impl Allocator { 9 | pub fn new(inner: rhyolite::Allocator) -> Self { 10 | Allocator(inner) 11 | } 12 | pub fn into_inner(self) -> rhyolite::Allocator { 13 | self.0 14 | } 15 | } 16 | impl Deref for Allocator { 17 | type Target = rhyolite::Allocator; 18 | fn deref(&self) -> &Self::Target { 19 | &self.0 20 | } 21 | } 22 | #[derive(Resource, Clone)] 23 | pub struct Device(Arc); 24 | 25 | impl Device { 26 | pub fn new(inner: Arc) -> Self { 27 | Device(inner) 28 | } 29 | pub fn inner(&self) -> &Arc { 30 | &self.0 31 | } 32 | } 33 | 34 | impl Deref for Device { 35 | type Target = Arc; 36 | 37 | fn deref(&self) -> &Self::Target { 38 | self.inner() 39 | } 40 | } 41 | 42 | pub enum SharingMode { 43 | Exclusive, 44 | Concurrent { queue_family_indices: Vec }, 45 | } 46 | 47 | impl Default for SharingMode { 48 | fn default() -> Self { 49 | Self::Exclusive 50 | } 51 | } 52 | 53 | impl<'a> From<&'a SharingMode> for rhyolite::SharingMode<'a> { 54 | fn from(value: &'a SharingMode) -> Self { 55 | match value { 56 | SharingMode::Exclusive => rhyolite::SharingMode::Exclusive, 57 | SharingMode::Concurrent { 58 | queue_family_indices, 59 | } => rhyolite::SharingMode::Concurrent { 60 | queue_family_indices: &queue_family_indices, 61 | }, 62 | } 63 | } 64 | } 65 | 66 | #[derive(Resource, Clone)] 67 | pub struct StagingRingBuffer(Arc); 68 | impl Deref for StagingRingBuffer { 69 | type Target = Arc; 70 | fn deref(&self) -> &Self::Target { 71 | &self.0 72 | } 73 | } 74 | impl FromWorld for StagingRingBuffer { 75 | fn from_world(world: &mut bevy_ecs::world::World) -> Self { 76 | let device: &Device = world.resource(); 77 | Self(Arc::new( 78 | rhyolite::StagingRingBuffer::new(device.inner().clone()).unwrap(), 79 | )) 80 | } 81 | } 82 | impl StagingRingBuffer { 83 | pub fn new(device: &Arc) -> VkResult { 84 | let ring_buffer = rhyolite::StagingRingBuffer::new(device.clone())?; 85 | Ok(Self(Arc::new(ring_buffer))) 86 | } 87 | } 88 | -------------------------------------------------------------------------------- /crates/rhyolite_macro/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "rhyolite-macro" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | [lib] 7 | proc-macro = true 8 | 9 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 10 | 11 | [dependencies] 12 | syn = { version = "2", features = ["full"] } 13 | quote = "1.0" 14 | proc-macro2 = "1.0" 15 | -------------------------------------------------------------------------------- /crates/rhyolite_macro/src/commands_join.rs: -------------------------------------------------------------------------------- 1 | use quote::ToTokens; 2 | use syn::parse::{Parse, ParseStream}; 3 | 4 | struct MacroJoin { 5 | pub exprs: syn::punctuated::Punctuated, 6 | } 7 | impl Parse for MacroJoin { 8 | fn parse(input: ParseStream) -> syn::Result { 9 | Ok(MacroJoin { 10 | exprs: syn::punctuated::Punctuated::parse_separated_nonempty(input)?, 11 | }) 12 | } 13 | } 14 | 15 | pub fn proc_macro_join(input: proc_macro2::TokenStream) -> proc_macro2::TokenStream { 16 | let input = match syn::parse2::(input) { 17 | Ok(input) => input, 18 | Err(err) => return err.to_compile_error(), 19 | }; 20 | if input.exprs.len() == 0 { 21 | return syn::Error::new( 22 | proc_macro2::Span::call_site(), 23 | "Expects at least one argument", 24 | ) 25 | .to_compile_error(); 26 | } 27 | if input.exprs.len() == 1 { 28 | return input.exprs[0].clone().into_token_stream(); 29 | } 30 | 31 | let mut token_stream = proc_macro2::TokenStream::new(); 32 | token_stream.extend(input.exprs[0].clone().into_token_stream()); 33 | 34 | // a.join(b).join(c)... 35 | for item in input.exprs.iter().skip(1) { 36 | token_stream.extend(quote::quote! {.join(#item)}.into_iter()); 37 | } 38 | 39 | let num_expressions = input.exprs.len(); 40 | 41 | // __join_0, __join_1, __join_2, ... 42 | let output_expression = (0..num_expressions).map(|i| quote::format_ident!("__join_{}", i)); 43 | 44 | let input_expression = { 45 | use proc_macro2::{Delimiter, Group, Ident, Punct, Spacing, Span, TokenStream, TokenTree}; 46 | let mut t = Some(TokenTree::Group(Group::new(Delimiter::Parenthesis, { 47 | let mut t = TokenStream::new(); 48 | t.extend(Some(TokenTree::Ident(Ident::new( 49 | "__join_0", 50 | Span::call_site(), 51 | )))); 52 | t.extend(Some(TokenTree::Punct(Punct::new(',', Spacing::Alone)))); 53 | t.extend(Some(TokenTree::Ident(Ident::new( 54 | "__join_1", 55 | Span::call_site(), 56 | )))); 57 | t 58 | }))); 59 | (2..num_expressions).for_each(|i| { 60 | let prev = t.take().unwrap().into_token_stream(); 61 | t = Some(TokenTree::Group(Group::new(Delimiter::Parenthesis, { 62 | let mut a = TokenStream::new(); 63 | a.extend(Some(prev)); 64 | a.extend(Some(TokenTree::Punct(Punct::new(',', Spacing::Alone)))); 65 | a.extend(Some(TokenTree::Ident(quote::format_ident!("__join_{}", i)))); 66 | a 67 | }))); 68 | }); 69 | t 70 | }; 71 | token_stream 72 | .extend(quote::quote! {.map(|#input_expression| (#(#output_expression),*))}.into_iter()); 73 | token_stream 74 | } 75 | -------------------------------------------------------------------------------- /crates/rhyolite_macro/src/lib.rs: -------------------------------------------------------------------------------- 1 | #![feature(track_path, proc_macro_span, proc_macro_diagnostic, extend_one)] 2 | #![feature(let_chains)] 3 | #![feature(extract_if)] 4 | 5 | use syn::parse::{Parse, ParseStream}; 6 | 7 | extern crate proc_macro; 8 | mod commands; 9 | mod commands_join; 10 | mod gpu; 11 | mod push_constant; 12 | mod transformer; 13 | 14 | struct ExprGpuAsync { 15 | pub mv: Option, 16 | pub stmts: Vec, 17 | } 18 | impl Parse for ExprGpuAsync { 19 | fn parse(input: ParseStream) -> syn::Result { 20 | Ok(ExprGpuAsync { 21 | mv: input.parse()?, 22 | stmts: syn::Block::parse_within(input)?, 23 | }) 24 | } 25 | } 26 | 27 | #[proc_macro] 28 | pub fn commands(input: proc_macro::TokenStream) -> proc_macro::TokenStream { 29 | commands::proc_macro_commands(input.into()).into() 30 | } 31 | 32 | #[proc_macro] 33 | pub fn gpu(input: proc_macro::TokenStream) -> proc_macro::TokenStream { 34 | gpu::proc_macro_gpu(input.into()).into() 35 | } 36 | 37 | #[proc_macro] 38 | pub fn join(input: proc_macro::TokenStream) -> proc_macro::TokenStream { 39 | commands_join::proc_macro_join(input.into()).into() 40 | } 41 | 42 | #[proc_macro_derive(PushConstants, attributes(stage))] 43 | pub fn push_constant(input: proc_macro::TokenStream) -> proc_macro::TokenStream { 44 | push_constant::push_constant(input.into()).into() 45 | } 46 | -------------------------------------------------------------------------------- /crates/rhyolite_macro/src/push_constant.rs: -------------------------------------------------------------------------------- 1 | use std::collections::{HashMap, HashSet}; 2 | 3 | use quote::ToTokens; 4 | use syn::{parse::Parse, punctuated::Punctuated, spanned::Spanned, Token}; 5 | 6 | struct PushConstantRange { 7 | stage: syn::Path, 8 | starting_field: syn::Ident, 9 | ending_field: Option, 10 | } 11 | 12 | struct PushConstantFieldAttr { 13 | stages: Punctuated, 14 | } 15 | impl Parse for PushConstantFieldAttr { 16 | fn parse(input: syn::parse::ParseStream) -> syn::Result { 17 | Ok(Self { 18 | stages: input.parse_terminated(syn::Path::parse, syn::Token![,])?, 19 | }) 20 | } 21 | } 22 | 23 | pub fn push_constant(input_token_stream: proc_macro2::TokenStream) -> proc_macro2::TokenStream { 24 | let mut input = match syn::parse2::(input_token_stream) { 25 | Ok(input) => input, 26 | Err(err) => return err.to_compile_error(), 27 | }; 28 | let fields = match &mut input.fields { 29 | syn::Fields::Named(field) => &mut field.named, 30 | syn::Fields::Unnamed(field) => &mut field.unnamed, 31 | syn::Fields::Unit => todo!(), 32 | }; 33 | 34 | let mut current_active_stages: HashMap = HashMap::new(); 35 | for field in fields.iter_mut() { 36 | let Some(stage) = field 37 | .attrs 38 | .extract_if(|a| a.meta.path().is_ident("stage")) 39 | .next() 40 | else { 41 | return syn::Error::new(field.span(), "Field is missing the `stage` attribute") 42 | .to_compile_error(); 43 | }; 44 | let stage = match stage.meta.require_list() { 45 | Ok(stage) => stage, 46 | Err(err) => return err.to_compile_error(), 47 | }; 48 | 49 | let tokens: PushConstantFieldAttr = match stage.parse_args() { 50 | Ok(stage) => stage, 51 | Err(err) => return err.to_compile_error(), 52 | }; 53 | let stages: HashSet = tokens 54 | .stages 55 | .iter() 56 | .map(|a| a.to_token_stream().to_string()) 57 | .collect(); 58 | 59 | for stage in tokens.stages { 60 | let entry = current_active_stages 61 | .entry(stage.to_token_stream().to_string()) 62 | .or_insert_with(|| PushConstantRange { 63 | stage, 64 | starting_field: field.ident.clone().unwrap(), 65 | ending_field: None, 66 | }); 67 | if entry.ending_field.is_some() { 68 | return syn::Error::new(field.span(), "Non-contiguous stage").to_compile_error(); 69 | } 70 | } 71 | 72 | for (key, value) in current_active_stages.iter_mut() { 73 | if !stages.contains(key) { 74 | assert!(value.ending_field.is_none()); 75 | value.ending_field = Some(field.ident.clone().unwrap()) 76 | } 77 | } 78 | } 79 | 80 | let name = input.ident.clone(); 81 | 82 | let ranges = current_active_stages.values().map(|range| { 83 | let stage = range.stage.clone(); 84 | let field_start = range.starting_field.clone(); 85 | let size = if let Some(field_end) = range.ending_field.clone() { 86 | quote::quote!(::rhyolite::offset_of!(#name, #field_end) - ::rhyolite::offset_of!(#name, #field_start)) 87 | } else { 88 | quote::quote!(::std::mem::size_of::<#name>() - ::rhyolite::offset_of!(#name, #field_start)) 89 | }; 90 | quote::quote! { 91 | ::rhyolite::ash::vk::PushConstantRange { 92 | stage_flags: #stage, 93 | offset: ::rhyolite::offset_of!(#name, #field_start) as u32, 94 | size: (#size) as u32 95 | } 96 | } 97 | }); 98 | quote::quote!( 99 | 100 | impl ::rhyolite::descriptor::PushConstants for #name { 101 | fn ranges() -> Vec { 102 | vec![ 103 | #(#ranges),* 104 | ] 105 | } 106 | } 107 | ) 108 | } 109 | -------------------------------------------------------------------------------- /crates/sentry/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "dust-sentry" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 7 | 8 | [dependencies] 9 | tracing = "0.1" 10 | sentry = "^0.31" 11 | sentry-tracing = "^0.31" 12 | tracing-subscriber = "^0.3" 13 | bevy_app = { git = "https://github.com/bevyengine/bevy.git", rev = "2c7eab1b4c4ec6c533b6b609d5ddf8a7282f2c4f" } 14 | rhyolite-bevy = { path = "../rhyolite_bevy" } 15 | rhyolite = { path = "../rhyolite" } 16 | 17 | 18 | aftermath-rs = { version = "0.1.2", optional = true } 19 | 20 | 21 | [features] 22 | aftermath = ["aftermath-rs"] 23 | -------------------------------------------------------------------------------- /crates/vdb/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "dust_vdb" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 7 | 8 | [dependencies] 9 | glam = "^0.24" 10 | fxhash = "0.2" 11 | nohash = "0.2.0" 12 | 13 | [dev-dependencies] 14 | rand = "0.8.5" 15 | -------------------------------------------------------------------------------- /crates/vdb/src/bitmask.rs: -------------------------------------------------------------------------------- 1 | use std::mem::size_of; 2 | 3 | #[derive(Clone)] 4 | pub struct BitMask 5 | where 6 | [(); SIZE / size_of::() / 8]: Sized, 7 | { 8 | pub(crate) data: [usize; SIZE / size_of::() / 8], 9 | } 10 | 11 | impl Default for BitMask 12 | where 13 | [(); SIZE / size_of::() / 8]: Sized, 14 | { 15 | fn default() -> Self { 16 | Self { 17 | data: [0; SIZE / size_of::() / 8], 18 | } 19 | } 20 | } 21 | 22 | impl std::fmt::Debug for BitMask 23 | where 24 | [(); SIZE / size_of::() / 8]: Sized, 25 | { 26 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 27 | for item in self.data.iter() { 28 | f.write_fmt(format_args!("{:#064b}\n", item))?; 29 | } 30 | Ok(()) 31 | } 32 | } 33 | 34 | impl BitMask 35 | where 36 | [(); SIZE / size_of::() / 8]: Sized, 37 | { 38 | pub fn new() -> Self { 39 | Self { 40 | data: [0; SIZE / size_of::() / 8], 41 | } 42 | } 43 | #[inline] 44 | pub fn get(&self, index: usize) -> bool { 45 | let i = index / size_of::() / 8; 46 | let j = index - i * size_of::() * 8; 47 | let val = self.data[i]; 48 | 49 | let bit = (val >> j) & 1; 50 | bit != 0 51 | } 52 | #[inline] 53 | pub fn set(&mut self, index: usize, val: bool) { 54 | let i = index / size_of::() / 8; 55 | let j = index - i * size_of::() * 8; 56 | let entry = &mut self.data[i]; 57 | if val { 58 | *entry |= 1 << j; 59 | } else { 60 | *entry &= !(1 << j); 61 | } 62 | } 63 | 64 | pub fn iter_set_bits(&self) -> SetBitIterator { 65 | SetBitIterator { 66 | bitmask: self, 67 | i: 0, 68 | state: self.data[0], 69 | } 70 | } 71 | 72 | pub fn is_zeroed(&self) -> bool { 73 | self.data.iter().all(|&a| a == 0) 74 | } 75 | 76 | pub fn count_ones(&self) -> usize { 77 | self.data.iter().map(|a| a.count_ones() as usize).sum() 78 | } 79 | } 80 | 81 | /// ``` 82 | /// #![feature(generic_const_exprs)] 83 | /// let mut bitmask = dust_vdb::BitMask::<128>::new(); 84 | /// bitmask.set(12, true); 85 | /// bitmask.set(101, true); 86 | /// let mut iter = bitmask.iter_set_bits(); 87 | /// assert_eq!(iter.next(), Some(12)); 88 | /// assert_eq!(iter.next(), Some(101)); 89 | /// assert!(iter.next().is_none()); 90 | /// ``` 91 | pub struct SetBitIterator<'a, const SIZE: usize> 92 | where 93 | [(); SIZE / size_of::() / 8]: Sized, 94 | { 95 | bitmask: &'a BitMask, 96 | i: usize, 97 | state: usize, 98 | } 99 | 100 | impl<'a, const SIZE: usize> Iterator for SetBitIterator<'a, SIZE> 101 | where 102 | [(); SIZE / size_of::() / 8]: Sized, 103 | { 104 | type Item = usize; 105 | 106 | fn next(&mut self) -> Option { 107 | const NUM_BITS: usize = std::mem::size_of::() * 8; 108 | loop { 109 | if self.state == 0 { 110 | if self.i == self.bitmask.data.len() - 1 { 111 | return None; 112 | } 113 | self.i += 1; 114 | self.state = self.bitmask.data[self.i]; 115 | continue; 116 | } 117 | 118 | let t = self.state & (!self.state).wrapping_add(1); 119 | let r = self.state.trailing_zeros() as usize; 120 | let result = self.i * NUM_BITS + r; 121 | self.state ^= t; 122 | return Some(result); 123 | } 124 | } 125 | } 126 | -------------------------------------------------------------------------------- /crates/vdb/src/lib.rs: -------------------------------------------------------------------------------- 1 | #![feature(int_roundings)] 2 | #![feature(stdsimd)] 3 | #![feature(generic_const_exprs)] 4 | #![feature(adt_const_params)] 5 | #![feature(maybe_uninit_uninit_array)] 6 | #![feature(alloc_layout_extra)] 7 | #![feature(const_maybe_uninit_uninit_array)] 8 | #![feature(const_maybe_uninit_write)] 9 | #![feature(const_maybe_uninit_assume_init_read)] 10 | #![feature(const_trait_impl)] 11 | #![feature(const_mut_refs)] 12 | #![feature(const_for)] 13 | #![feature(const_intoiterator_identity)] 14 | #![feature(portable_simd)] 15 | 16 | mod accessor; 17 | mod bitmask; 18 | mod node; 19 | mod pool; 20 | mod tree; 21 | 22 | pub use bitmask::BitMask; 23 | pub use pool::Pool; 24 | pub use tree::Tree; 25 | 26 | pub use accessor::Accessor; 27 | pub use node::*; 28 | 29 | pub extern crate self as dust_vdb; 30 | 31 | #[derive(Clone, Copy, PartialEq, Eq, std::marker::ConstParamTy)] 32 | pub struct ConstUVec3 { 33 | pub x: u32, 34 | pub y: u32, 35 | pub z: u32, 36 | } 37 | 38 | impl ConstUVec3 { 39 | pub const fn to_glam(self) -> glam::UVec3 { 40 | glam::UVec3 { 41 | x: self.x, 42 | y: self.y, 43 | z: self.z, 44 | } 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /crates/vdb/src/node/mod.rs: -------------------------------------------------------------------------------- 1 | mod internal; 2 | mod leaf; 3 | mod root; 4 | 5 | use std::alloc::Layout; 6 | use std::cell::UnsafeCell; 7 | use std::fmt::Debug; 8 | use std::mem::MaybeUninit; 9 | 10 | use glam::UVec3; 11 | pub use internal::*; 12 | pub use leaf::*; 13 | pub use root::*; 14 | 15 | use crate::{ConstUVec3, Pool}; 16 | 17 | pub struct NodeMeta { 18 | pub(crate) layout: Layout, 19 | pub(crate) getter: 20 | fn(pools: &[Pool], coords: UVec3, ptr: u32, cached_path: &mut [u32]) -> Option, 21 | pub(crate) setter: 22 | fn(pools: &mut [Pool], coords: UVec3, ptr: u32, value: Option, cached_path: &mut [u32]), 23 | pub(crate) extent_log2: UVec3, 24 | pub(crate) fanout_log2: UVec3, 25 | 26 | pub(crate) extent_mask: UVec3, // = (1 << extent_log2) - 1 27 | } 28 | 29 | pub trait Node: 'static + Default + Debug { 30 | /// span of the node. 31 | type LeafType: IsLeaf; 32 | const EXTENT_LOG2: UVec3; 33 | const EXTENT: UVec3; 34 | const EXTENT_MASK: UVec3; // = (1 << extent_log2) - 1 35 | /// Max number of child nodes. 36 | const SIZE: usize; 37 | 38 | /// This is 0 for leaf nodes and +1 for each layer of nodes above leaves. 39 | const LEVEL: usize; 40 | fn new() -> Self; 41 | 42 | type Voxel; 43 | 44 | /// Get the value of a voxel at the specified coordinates within the node space. 45 | /// This is called when the node was owned. 46 | /// Implementation will write to cached_path for all levels below the current level. 47 | fn get(&self, pools: &[Pool], coords: UVec3, cached_path: &mut [u32]) -> Option; 48 | /// Set the value of a voxel at the specified coordinates within the node space. 49 | /// This is called when the node was owned. 50 | /// Implementation will write to cached_path for all levels below the current level. 51 | fn set( 52 | &mut self, 53 | pools: &mut [Pool], 54 | coords: UVec3, 55 | value: Option, 56 | cached_path: &mut [u32], 57 | ); 58 | 59 | /// Get the value of a voxel at the specified coordinates within the node space. 60 | /// This is called when the node was located in a node pool. 61 | /// Implementation will write to cached_path for all levels including the current level. 62 | fn get_in_pools( 63 | pools: &[Pool], 64 | coords: UVec3, 65 | ptr: u32, 66 | cached_path: &mut [u32], 67 | ) -> Option; 68 | /// Set the value of a voxel at the specified coordinates within the node space. 69 | /// This is called when the node was located in a node pool. 70 | /// Implementation will write to cached_path for all levels including the current level. 71 | fn set_in_pools( 72 | pools: &mut [Pool], 73 | coords: UVec3, 74 | ptr: u32, 75 | value: Option, 76 | cached_path: &mut [u32], 77 | ); 78 | 79 | type Iterator<'a>: Iterator; 80 | /// This is called when the node was owned as the root node in the tree. 81 | fn iter<'a>(&'a self, pools: &'a [Pool], offset: UVec3) -> Self::Iterator<'a>; 82 | /// This is called when the node was located in a node pool. 83 | fn iter_in_pool<'a>(pools: &'a [Pool], ptr: u32, offset: UVec3) -> Self::Iterator<'a>; 84 | 85 | type LeafIterator<'a>: Iterator)>; 86 | /// This is called when the node was owned as the root node in the tree. 87 | fn iter_leaf<'a>(&'a self, pools: &'a [Pool], offset: UVec3) -> Self::LeafIterator<'a>; 88 | /// This is called when the node was located in a node pool. 89 | fn iter_leaf_in_pool<'a>(pools: &'a [Pool], ptr: u32, offset: UVec3) -> Self::LeafIterator<'a>; 90 | } 91 | 92 | /// Trait that contains const methods for the node. 93 | #[const_trait] 94 | pub trait NodeConst: Node { 95 | /// Method that congregates metadata of each level of the tree into an array. 96 | /// Implementation shoud write NodeMeta into `metas[Self::LEVEL]`. 97 | fn write_meta(metas: &mut [MaybeUninit>]); 98 | } 99 | 100 | /// Macro that simplifies tree type construction. 101 | /// ``` 102 | /// use dust_vdb::{hierarchy, Node}; 103 | /// // Create a 4x4x4 LeafNode 104 | /// let hierarchy = ::new(); 105 | /// // Create a two-level tree with 2x2x2 leafs and 8x8x8 root. 106 | /// let hierarchy = ::new(); 107 | /// // Create a three-level tree with 2x2x2 leafs, 4x4x4 intermediate nodes and 4x4x4 root. 108 | /// let hierarchy = ::new(); 109 | /// // Create a three-level tree with infinite size (implemented with a HashMap), 4x4x4 intermediate nodes and 2x2x2 leafs. 110 | /// let hierarchy = ::new(); 111 | /// ``` 112 | #[macro_export] 113 | macro_rules! hierarchy { 114 | ($e: tt) => { 115 | $crate::LeafNode<{dust_vdb::ConstUVec3{x:$e,y:$e,z:$e}}> 116 | }; 117 | (#, $($n:tt),+) => { 118 | $crate::RootNode 119 | }; 120 | ($e: tt, $($n:tt),+) => { 121 | $crate::InternalNode:: 122 | }; 123 | } 124 | 125 | /// Returns the size of a grid represented by the log2 of its extent. 126 | /// This is needed because of Rust limitations. 127 | /// Won't need this once we're allowed to use Self::Size in the bounds. 128 | pub const fn size_of_grid(log2: ConstUVec3) -> usize { 129 | return 1 << (log2.x + log2.y + log2.z); 130 | } 131 | -------------------------------------------------------------------------------- /crates/vdb/src/pool.rs: -------------------------------------------------------------------------------- 1 | use std::{alloc::Layout, marker::PhantomData, mem::MaybeUninit}; 2 | 3 | pub struct Pool { 4 | /// Size of one individual allocation 5 | layout: Layout, 6 | /// Head of freelist 7 | head: u32, 8 | 9 | /// Top of free items. 10 | top: u32, 11 | /// Number of items to request when we run out of space. 12 | /// When running out of space, request (1 << chunk_size_log2) * size bytes. 13 | chunk_size_log2: usize, 14 | chunks: Vec<*mut u8>, 15 | 16 | count: u32, 17 | } 18 | 19 | unsafe impl Send for Pool {} 20 | unsafe impl Sync for Pool {} 21 | 22 | /// A memory pool for objects of the same layout. 23 | /// ``` 24 | /// use std::alloc::Layout; 25 | /// use dust_vdb::Pool; 26 | /// let item: u64 = 0; 27 | /// // Create a pool of u64s with 2 items in each block. 28 | /// unsafe { 29 | /// let mut pool = Pool::new(Layout::for_value(&item), 1); 30 | /// assert_eq!(pool.alloc::(), 0); 31 | /// assert_eq!(pool.alloc::(), 1); 32 | /// assert_eq!(pool.alloc::(), 2); 33 | /// assert_eq!(pool.alloc::(), 3); 34 | /// assert_eq!(pool.num_chunks(), 2); 35 | /// 36 | /// pool.free(1); 37 | /// pool.free(2); 38 | /// assert_eq!(pool.alloc::(), 2); 39 | /// assert_eq!(pool.alloc::(), 1); 40 | /// assert_eq!(pool.alloc::(), 4); 41 | /// } 42 | /// ``` 43 | impl Pool { 44 | pub fn new(layout: Layout, chunk_size_log2: usize) -> Self { 45 | Self { 46 | layout: layout.pad_to_align(), 47 | head: u32::MAX, 48 | top: 0, 49 | chunk_size_log2, 50 | chunks: Vec::new(), 51 | count: 0, 52 | } 53 | } 54 | pub fn count(&self) -> u32 { 55 | self.count 56 | } 57 | pub unsafe fn alloc(&mut self) -> u32 { 58 | debug_assert_eq!(Layout::new::(), self.layout); 59 | let ptr = self.alloc_uninitialized(); 60 | let item = self.get_item_mut::(ptr); 61 | *item = T::default(); 62 | ptr 63 | } 64 | pub unsafe fn alloc_uninitialized(&mut self) -> u32 { 65 | self.count += 1; 66 | if self.head == u32::MAX { 67 | // allocate new 68 | let top = self.top; 69 | let chunk_index = top as usize >> self.chunk_size_log2; 70 | if chunk_index >= self.chunks.len() { 71 | // allocate new block 72 | let (layout, _) = self.layout.repeat(1 << self.chunk_size_log2).unwrap(); 73 | let block = std::alloc::alloc_zeroed(layout); 74 | self.chunks.push(block); 75 | } 76 | self.top += 1; 77 | top 78 | } else { 79 | // take from freelist 80 | let item_location = self.get_mut(self.head); 81 | let next_available_location = *(item_location as *const u32); 82 | let head = self.head; 83 | self.head = next_available_location; 84 | return head; 85 | } 86 | } 87 | pub fn free(&mut self, index: u32) { 88 | self.count -= 1; 89 | unsafe { 90 | let current_free_location = self.get_mut(index); 91 | 92 | // The first 4 bytes of the entry is populated with self.head 93 | *(current_free_location as *mut u32) = self.head; 94 | 95 | // All other bytes are zeroed 96 | let slice = std::slice::from_raw_parts_mut(current_free_location, self.layout.size()); 97 | slice[std::mem::size_of::()..].fill(0); 98 | 99 | // push to freelist 100 | self.head = index; 101 | } 102 | } 103 | 104 | pub fn num_chunks(&self) -> usize { 105 | self.chunks.len() 106 | } 107 | 108 | #[inline] 109 | pub unsafe fn get(&self, ptr: u32) -> *const u8 { 110 | let chunk_index = (ptr as usize) >> self.chunk_size_log2; 111 | let item_index = (ptr as usize) & ((1 << self.chunk_size_log2) - 1); 112 | return self 113 | .chunks 114 | .get_unchecked(chunk_index) 115 | .add(item_index * self.layout.size()); 116 | } 117 | #[inline] 118 | pub unsafe fn get_mut(&mut self, ptr: u32) -> *mut u8 { 119 | let ptr = self.get(ptr); 120 | ptr as *mut u8 121 | } 122 | 123 | #[inline] 124 | pub unsafe fn get_item(&self, ptr: u32) -> &T { 125 | debug_assert_eq!(Layout::new::().pad_to_align(), self.layout); 126 | &*(self.get(ptr) as *const T) 127 | } 128 | #[inline] 129 | pub unsafe fn get_item_mut(&mut self, ptr: u32) -> &mut T { 130 | debug_assert_eq!(Layout::new::().pad_to_align(), self.layout); 131 | &mut *(self.get_mut(ptr) as *mut T) 132 | } 133 | 134 | pub fn iter_entries(&self) -> PoolIterator { 135 | debug_assert_eq!(Layout::new::().pad_to_align(), self.layout); 136 | PoolIterator { 137 | pool: self, 138 | cur: 0, 139 | _marker: PhantomData, 140 | } 141 | } 142 | } 143 | 144 | pub struct PoolIterator<'a, T> { 145 | pool: &'a Pool, 146 | cur: u32, 147 | _marker: PhantomData, 148 | } 149 | 150 | impl<'a, T: 'a> Iterator for PoolIterator<'a, T> { 151 | type Item = &'a MaybeUninit; 152 | 153 | fn next(&mut self) -> Option { 154 | if self.cur >= self.pool.top { 155 | return None; 156 | } 157 | let item: &'a MaybeUninit = unsafe { 158 | let item = self.pool.get(self.cur); 159 | std::mem::transmute(item) 160 | }; 161 | self.cur += 1; 162 | Some(item) 163 | } 164 | } 165 | 166 | impl Drop for Pool { 167 | fn drop(&mut self) { 168 | unsafe { 169 | let (layout, _) = self.layout.repeat(1 << self.chunk_size_log2).unwrap(); 170 | for chunk in self.chunks.iter() { 171 | let chunk = *chunk; 172 | std::alloc::dealloc(chunk, layout); 173 | } 174 | } 175 | } 176 | } 177 | -------------------------------------------------------------------------------- /crates/vox/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "dust-vox" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 7 | 8 | [dependencies] 9 | dot_vox = "5.1" 10 | dust-render = { path = "../render" } 11 | bevy_app = { git = "https://github.com/bevyengine/bevy.git", rev = "2c7eab1b4c4ec6c533b6b609d5ddf8a7282f2c4f" } 12 | bevy_ecs = { git = "https://github.com/bevyengine/bevy.git", rev = "2c7eab1b4c4ec6c533b6b609d5ddf8a7282f2c4f" } 13 | bevy_scene = { git = "https://github.com/bevyengine/bevy.git", rev = "2c7eab1b4c4ec6c533b6b609d5ddf8a7282f2c4f", default-features=false, features=["serialize"] } 14 | bevy_asset = { git = "https://github.com/bevyengine/bevy.git", rev = "2c7eab1b4c4ec6c533b6b609d5ddf8a7282f2c4f" } 15 | bevy_reflect = { git = "https://github.com/bevyengine/bevy.git", rev = "2c7eab1b4c4ec6c533b6b609d5ddf8a7282f2c4f" } 16 | bevy_hierarchy = { git = "https://github.com/bevyengine/bevy.git", rev = "2c7eab1b4c4ec6c533b6b609d5ddf8a7282f2c4f" } 17 | bevy_transform = { git = "https://github.com/bevyengine/bevy.git", rev = "2c7eab1b4c4ec6c533b6b609d5ddf8a7282f2c4f" } 18 | bevy_utils = { git = "https://github.com/bevyengine/bevy.git", rev = "2c7eab1b4c4ec6c533b6b609d5ddf8a7282f2c4f" } 19 | rhyolite = { path = "../rhyolite" } 20 | rhyolite-bevy = { path = "../rhyolite_bevy" } 21 | dust_vdb = { path = "../vdb" } 22 | glam = "^0.24" 23 | thiserror = "1" 24 | rayon = "1.7" 25 | -------------------------------------------------------------------------------- /crates/vox/src/collector.rs: -------------------------------------------------------------------------------- 1 | /// dox_vox::Voxel to solid materials 2 | pub struct ModelIndexCollector { 3 | grid: Box<[u8; 256 * 256 * 256]>, 4 | block_counts: Box<[u32; 64 * 64 * 64]>, 5 | count: usize, 6 | } 7 | 8 | impl ModelIndexCollector { 9 | pub fn new() -> Self { 10 | unsafe { 11 | let grid_ptr = 12 | std::alloc::alloc_zeroed(std::alloc::Layout::new::<[u8; 256 * 256 * 256]>()); 13 | let block_counts_ptr = 14 | std::alloc::alloc_zeroed(std::alloc::Layout::new::<[u32; 64 * 64 * 64]>()); 15 | 16 | Self { 17 | count: 0, 18 | grid: Box::from_raw(grid_ptr as *mut [u8; 256 * 256 * 256]), 19 | block_counts: Box::from_raw(block_counts_ptr as *mut [u32; 64 * 64 * 64]), 20 | } 21 | } 22 | } 23 | pub fn set(&mut self, voxel: dot_vox::Voxel) { 24 | self.count += 1; 25 | let block_index = (voxel.x >> 2, voxel.y >> 2, voxel.z >> 2); 26 | let block_index = 27 | block_index.0 as usize + block_index.1 as usize * 64 + block_index.2 as usize * 64 * 64; 28 | 29 | self.block_counts[block_index] += 1; 30 | 31 | let index = (voxel.z & 0b11) | ((voxel.y & 0b11) << 2) | ((voxel.x & 0b11) << 4); 32 | // Use one-based index here so that 0 indicates null 33 | self.grid[block_index * 4 * 4 * 4 + index as usize] = voxel.i + 1; 34 | } 35 | } 36 | pub struct ModelIndexCollectorIterator { 37 | collector: ModelIndexCollector, 38 | current: usize, 39 | } 40 | 41 | impl ModelIndexCollectorIterator { 42 | pub fn running_sum(&self) -> &[u32; 64 * 64 * 64] { 43 | &self.collector.block_counts 44 | } 45 | } 46 | 47 | impl Iterator for ModelIndexCollectorIterator { 48 | type Item = u8; 49 | 50 | fn next(&mut self) -> Option { 51 | while self.current < 256 * 256 * 256 { 52 | let val = self.collector.grid[self.current]; 53 | self.current += 1; 54 | if val == 0 { 55 | continue; 56 | } 57 | return Some(val - 1); // Convert back into zero-based index here 58 | } 59 | None 60 | } 61 | fn size_hint(&self) -> (usize, Option) { 62 | (self.len(), Some(self.len())) 63 | } 64 | } 65 | impl ExactSizeIterator for ModelIndexCollectorIterator { 66 | fn len(&self) -> usize { 67 | self.collector.count 68 | } 69 | } 70 | 71 | impl IntoIterator for ModelIndexCollector { 72 | type Item = u8; 73 | 74 | type IntoIter = ModelIndexCollectorIterator; 75 | 76 | fn into_iter(mut self) -> Self::IntoIter { 77 | let mut sum: u32 = 0; 78 | for i in self.block_counts.iter_mut() { 79 | let value = *i; 80 | *i = sum; 81 | sum += value; 82 | } 83 | ModelIndexCollectorIterator { 84 | collector: self, 85 | current: 0, 86 | } 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /crates/vox/src/lib.rs: -------------------------------------------------------------------------------- 1 | #![feature(generic_const_exprs)] 2 | #![feature(generators)] 3 | 4 | mod collector; 5 | mod loader; 6 | mod palette; 7 | 8 | use bevy_asset::{AssetApp, Handle}; 9 | mod geometry; 10 | mod material; 11 | 12 | use dust_render::{GeometryPlugin, MaterialPlugin, Renderable}; 13 | use dust_vdb::hierarchy; 14 | pub use geometry::VoxGeometry; 15 | pub use loader::*; 16 | pub use material::PaletteMaterial; 17 | pub use palette::VoxPalette; 18 | 19 | pub type TreeRoot = hierarchy!(4, 2, 2); 20 | pub type Tree = dust_vdb::Tree; 21 | 22 | #[derive(Default)] 23 | pub struct VoxPlugin; 24 | impl bevy_app::Plugin for VoxPlugin { 25 | fn build(&self, app: &mut bevy_app::App) { 26 | app.init_asset_loader::() 27 | .init_asset::() 28 | .init_asset::() 29 | .init_asset::() 30 | .init_asset::() 31 | .add_plugins(GeometryPlugin::::default()) 32 | .add_plugins(MaterialPlugin::::default()); 33 | } 34 | } 35 | 36 | #[derive(bevy_ecs::bundle::Bundle)] 37 | pub struct VoxBundle { 38 | transform: bevy_transform::prelude::Transform, 39 | global_transform: bevy_transform::prelude::GlobalTransform, 40 | geometry_handle: Handle, 41 | material_handle: Handle, 42 | renderable: Renderable, 43 | } 44 | impl VoxBundle { 45 | pub fn from_geometry_material( 46 | geometry: Handle, 47 | material: Handle, 48 | ) -> Self { 49 | VoxBundle { 50 | transform: bevy_transform::prelude::Transform::default(), 51 | global_transform: bevy_transform::prelude::GlobalTransform::default(), 52 | geometry_handle: geometry, 53 | material_handle: material, 54 | renderable: Default::default(), 55 | } 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /crates/vox/src/material.rs: -------------------------------------------------------------------------------- 1 | use bevy_asset::{Asset, AssetServer, Assets, Handle}; 2 | use bevy_ecs::system::{lifetimeless::SRes, SystemParamItem}; 3 | use dust_render::{MaterialType, StandardPipeline}; 4 | 5 | use crate::{VoxGeometry, VoxPalette}; 6 | use dust_render::SpecializedShader; 7 | use rhyolite::{ash::vk, BufferLike, ResidentBuffer}; 8 | 9 | #[derive(bevy_reflect::TypePath, Asset)] 10 | pub struct PaletteMaterial { 11 | pub(crate) palette: Handle, 12 | pub(crate) geometry: Handle, 13 | /// Compacted list of indexes into the palette array. 14 | data: ResidentBuffer, 15 | } 16 | impl PaletteMaterial { 17 | pub fn new( 18 | geometry: Handle, 19 | palette: Handle, 20 | data: ResidentBuffer, 21 | ) -> Self { 22 | Self { 23 | palette, 24 | data, 25 | geometry, 26 | } 27 | } 28 | } 29 | 30 | #[repr(C)] 31 | pub struct PaletteMaterialShaderParams { 32 | /// Pointer to a list of u64 indexed by block id 33 | geometry_ptr: u64, 34 | 35 | /// Pointer to a list of u8, indexed by voxel id, each denoting offset into palette_ptr. 36 | /// Voxel id is defined as block id + offset inside block. 37 | material_ptr: u64, 38 | 39 | /// Pointer to a list of 256 u8 colors 40 | palette_ptr: u64, 41 | } 42 | 43 | impl dust_render::Material for PaletteMaterial { 44 | type Pipeline = StandardPipeline; 45 | 46 | const TYPE: MaterialType = MaterialType::Procedural; 47 | 48 | fn rahit_shader(_ray_type: u32, _asset_server: &AssetServer) -> Option { 49 | None 50 | } 51 | 52 | fn rchit_shader(ray_type: u32, asset_server: &AssetServer) -> Option { 53 | match ray_type { 54 | Self::Pipeline::PRIMARY_RAYTYPE => Some(SpecializedShader::for_shader( 55 | asset_server.load("shaders/primary/hit.rchit"), 56 | vk::ShaderStageFlags::CLOSEST_HIT_KHR, 57 | )), 58 | Self::Pipeline::FINAL_GATHER_RAYTYPE => Some(SpecializedShader::for_shader( 59 | asset_server.load("shaders/final_gather/final_gather.rchit"), 60 | vk::ShaderStageFlags::CLOSEST_HIT_KHR, 61 | )), 62 | Self::Pipeline::SURFEL_RAYTYPE => Some(SpecializedShader::for_shader( 63 | asset_server.load("shaders/surfel/surfel.rchit"), 64 | vk::ShaderStageFlags::CLOSEST_HIT_KHR, 65 | )), 66 | Self::Pipeline::AMBIENT_OCCLUSION_RAYTYPE => Some(SpecializedShader::for_shader( 67 | asset_server.load("shaders/final_gather/ambient_occlusion.rchit"), 68 | vk::ShaderStageFlags::CLOSEST_HIT_KHR, 69 | )), 70 | _ => None, 71 | } 72 | } 73 | 74 | fn intersection_shader(ray_type: u32, asset_server: &AssetServer) -> Option { 75 | match ray_type { 76 | Self::Pipeline::FINAL_GATHER_RAYTYPE | Self::Pipeline::SURFEL_RAYTYPE => { 77 | Some(SpecializedShader::for_shader( 78 | asset_server.load("shaders/final_gather/rough.rint"), 79 | vk::ShaderStageFlags::INTERSECTION_KHR, 80 | )) 81 | } 82 | Self::Pipeline::AMBIENT_OCCLUSION_RAYTYPE => { 83 | let rough_intersection_test_threshold: f32 = 0.0; 84 | Some( 85 | SpecializedShader::for_shader( 86 | asset_server.load("shaders/final_gather/ambient_occlusion.rint"), 87 | vk::ShaderStageFlags::INTERSECTION_KHR, 88 | ) 89 | .with_const(2, rough_intersection_test_threshold), 90 | ) 91 | } 92 | Self::Pipeline::PRIMARY_RAYTYPE => Some(SpecializedShader::for_shader( 93 | asset_server.load("shaders/primary/hit.rint"), 94 | vk::ShaderStageFlags::INTERSECTION_KHR, 95 | )), 96 | _ => unreachable!(), 97 | } 98 | } 99 | 100 | type ShaderParameters = PaletteMaterialShaderParams; 101 | type ShaderParameterParams = ( 102 | SRes>, 103 | SRes>, 104 | SRes>, 105 | ); 106 | fn parameters( 107 | &self, 108 | _ray_type: u32, 109 | params: &mut SystemParamItem, 110 | ) -> Self::ShaderParameters { 111 | let (geometry_store, palette_store, material_store) = params; 112 | let geometry = geometry_store.get(&self.geometry).unwrap(); 113 | let palette = palette_store.get(&self.palette).unwrap(); 114 | PaletteMaterialShaderParams { 115 | geometry_ptr: geometry.geometry_buffer().device_address(), 116 | material_ptr: self.data.device_address(), 117 | palette_ptr: palette.buffer.device_address(), 118 | } 119 | } 120 | } 121 | -------------------------------------------------------------------------------- /crates/vox/src/palette.rs: -------------------------------------------------------------------------------- 1 | use bevy_asset::Asset; 2 | use rhyolite::{future::RenderData, ResidentBuffer}; 3 | 4 | #[derive(bevy_reflect::TypePath, Asset)] 5 | pub struct VoxPalette { 6 | pub colors: Box<[dot_vox::Color; 255]>, 7 | pub buffer: ResidentBuffer, 8 | } 9 | impl RenderData for VoxPalette {} 10 | -------------------------------------------------------------------------------- /rust-toolchain.toml: -------------------------------------------------------------------------------- 1 | [toolchain] 2 | # Using an older version of nightly until keyword generics land in rustc. 3 | # https://github.com/rust-lang/rust/issues/110395 4 | channel = "nightly-2023-07-08" 5 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | pub fn add(left: usize, right: usize) -> usize { 2 | left + right 3 | } 4 | 5 | #[cfg(test)] 6 | mod tests { 7 | use super::*; 8 | 9 | #[test] 10 | fn it_works() { 11 | let result = add(2, 2); 12 | assert_eq!(result, 4); 13 | } 14 | } 15 | --------------------------------------------------------------------------------