Full Code of schell/renderling for AI

main a7b44f796a38 cached
283 files
1.4 MB
360.3k tokens
2182 symbols
1 requests
Download .txt
Showing preview only (1,473K chars total). Download the full file or copy to clipboard to get everything.
Repository: schell/renderling
Branch: main
Commit: a7b44f796a38
Files: 283
Total size: 1.4 MB

Directory structure:
gitextract_l0v6lfp5/

├── .cargo/
│   └── config.toml
├── .gitattributes
├── .github/
│   ├── FUNDING.yml
│   └── workflows/
│       └── push.yaml
├── .gitignore
├── .helix/
│   └── snippets/
│       └── rust.json
├── AGENTS.md
├── CONTRIBUTING.md
├── Cargo.toml
├── LICENSE
├── NOTES.md
├── README.md
├── blender/
│   ├── cheetah_cone.blend
│   ├── normal_mapping_brick_sphere.blend
│   ├── pedestal.blend
│   ├── shadow_mapping_point.blend
│   ├── shadow_mapping_points.blend
│   ├── shadow_mapping_sanity_spot.blend
│   ├── shadow_mapping_spots.blend
│   ├── spot_lights.blend
│   └── spot_one.blend
├── clippy.toml
├── crates/
│   ├── example/
│   │   ├── Cargo.toml
│   │   └── src/
│   │       ├── camera.rs
│   │       ├── lib.rs
│   │       ├── main.rs
│   │       ├── time.rs
│   │       └── utils.rs
│   ├── example-culling/
│   │   ├── Cargo.toml
│   │   └── src/
│   │       └── main.rs
│   ├── example-wasm/
│   │   ├── .gitignore
│   │   ├── Cargo.toml
│   │   ├── Trunk.toml
│   │   ├── index.html
│   │   └── src/
│   │       ├── event.rs
│   │       ├── lib.rs
│   │       └── req_animation_frame.rs
│   ├── examples/
│   │   ├── Cargo.toml
│   │   └── src/
│   │       ├── context.rs
│   │       ├── gltf.rs
│   │       ├── lib.rs
│   │       ├── lighting.rs
│   │       ├── skybox.rs
│   │       └── stage.rs
│   ├── img-diff/
│   │   ├── Cargo.toml
│   │   └── src/
│   │       └── lib.rs
│   ├── loading-bytes/
│   │   ├── Cargo.toml
│   │   ├── README.md
│   │   └── src/
│   │       └── lib.rs
│   ├── renderling/
│   │   ├── Cargo.toml
│   │   ├── shaders/
│   │   │   ├── atlas-shader-atlas_blit_fragment.spv
│   │   │   ├── atlas-shader-atlas_blit_vertex.spv
│   │   │   ├── bloom-shader-bloom_downsample_fragment.spv
│   │   │   ├── bloom-shader-bloom_mix_fragment.spv
│   │   │   ├── bloom-shader-bloom_upsample_fragment.spv
│   │   │   ├── bloom-shader-bloom_vertex.spv
│   │   │   ├── compositor-compositor_fragment.spv
│   │   │   ├── compositor-compositor_vertex.spv
│   │   │   ├── convolution-shader-brdf_lut_convolution_fragment.spv
│   │   │   ├── convolution-shader-brdf_lut_convolution_vertex.spv
│   │   │   ├── convolution-shader-generate_mipmap_fragment.spv
│   │   │   ├── convolution-shader-generate_mipmap_vertex.spv
│   │   │   ├── convolution-shader-prefilter_environment_cubemap_fragment.spv
│   │   │   ├── convolution-shader-prefilter_environment_cubemap_vertex.spv
│   │   │   ├── cubemap-shader-cubemap_sampling_test_fragment.spv
│   │   │   ├── cubemap-shader-cubemap_sampling_test_vertex.spv
│   │   │   ├── cull-shader-compute_copy_depth_to_pyramid.spv
│   │   │   ├── cull-shader-compute_copy_depth_to_pyramid_multisampled.spv
│   │   │   ├── cull-shader-compute_culling.spv
│   │   │   ├── cull-shader-compute_downsample_depth_pyramid.spv
│   │   │   ├── debug-shader-debug_overlay_fragment.spv
│   │   │   ├── debug-shader-debug_overlay_vertex.spv
│   │   │   ├── light-shader-light_tiling_bin_lights.spv
│   │   │   ├── light-shader-light_tiling_clear_tiles.spv
│   │   │   ├── light-shader-light_tiling_compute_tile_min_and_max_depth.spv
│   │   │   ├── light-shader-light_tiling_compute_tile_min_and_max_depth_multisampled.spv
│   │   │   ├── light-shader-light_tiling_depth_pre_pass.spv
│   │   │   ├── light-shader-shadow_mapping_fragment.spv
│   │   │   ├── light-shader-shadow_mapping_vertex.spv
│   │   │   ├── manifest.json
│   │   │   ├── pbr-ibl-shader-di_convolution_fragment.spv
│   │   │   ├── primitive-shader-primitive_fragment.spv
│   │   │   ├── primitive-shader-primitive_vertex.spv
│   │   │   ├── skybox-shader-skybox_cubemap_fragment.spv
│   │   │   ├── skybox-shader-skybox_cubemap_vertex.spv
│   │   │   ├── skybox-shader-skybox_equirectangular_fragment.spv
│   │   │   ├── skybox-shader-skybox_vertex.spv
│   │   │   ├── tonemapping-tonemapping_fragment.spv
│   │   │   ├── tonemapping-tonemapping_vertex.spv
│   │   │   ├── tutorial-implicit_isosceles_vertex.spv
│   │   │   ├── tutorial-passthru_fragment.spv
│   │   │   ├── tutorial-slabbed_renderlet.spv
│   │   │   ├── tutorial-slabbed_vertices.spv
│   │   │   ├── tutorial-slabbed_vertices_no_instance.spv
│   │   │   ├── ui_slab-shader-ui_fragment.spv
│   │   │   └── ui_slab-shader-ui_vertex.spv
│   │   ├── src/
│   │   │   ├── atlas/
│   │   │   │   ├── atlas_image.rs
│   │   │   │   ├── cpu.rs
│   │   │   │   └── shader.rs
│   │   │   ├── atlas.rs
│   │   │   ├── bindgroup.rs
│   │   │   ├── bloom/
│   │   │   │   ├── cpu.rs
│   │   │   │   └── shader.rs
│   │   │   ├── bloom.rs
│   │   │   ├── build.rs
│   │   │   ├── bvol.rs
│   │   │   ├── camera/
│   │   │   │   ├── cpu.rs
│   │   │   │   └── shader.rs
│   │   │   ├── camera.rs
│   │   │   ├── color.rs
│   │   │   ├── compositor/
│   │   │   │   └── cpu.rs
│   │   │   ├── compositor.rs
│   │   │   ├── context.rs
│   │   │   ├── convolution.rs
│   │   │   ├── cubemap/
│   │   │   │   ├── cpu.rs
│   │   │   │   └── shader.rs
│   │   │   ├── cubemap.rs
│   │   │   ├── cull/
│   │   │   │   ├── cpu.rs
│   │   │   │   └── shader.rs
│   │   │   ├── cull.rs
│   │   │   ├── debug/
│   │   │   │   └── cpu.rs
│   │   │   ├── debug.rs
│   │   │   ├── draw/
│   │   │   │   └── cpu.rs
│   │   │   ├── draw.rs
│   │   │   ├── geometry/
│   │   │   │   ├── cpu.rs
│   │   │   │   └── shader.rs
│   │   │   ├── geometry.rs
│   │   │   ├── gltf/
│   │   │   │   └── anime.rs
│   │   │   ├── gltf.rs
│   │   │   ├── internal/
│   │   │   │   └── cpu.rs
│   │   │   ├── internal.rs
│   │   │   ├── lib.rs
│   │   │   ├── light/
│   │   │   │   ├── cpu/
│   │   │   │   │   └── test.rs
│   │   │   │   ├── cpu.rs
│   │   │   │   ├── shader.rs
│   │   │   │   ├── shadow_map.rs
│   │   │   │   └── tiling.rs
│   │   │   ├── light.rs
│   │   │   ├── linkage/
│   │   │   │   ├── atlas_blit_fragment.rs
│   │   │   │   ├── atlas_blit_vertex.rs
│   │   │   │   ├── bloom_downsample_fragment.rs
│   │   │   │   ├── bloom_mix_fragment.rs
│   │   │   │   ├── bloom_upsample_fragment.rs
│   │   │   │   ├── bloom_vertex.rs
│   │   │   │   ├── brdf_lut_convolution_fragment.rs
│   │   │   │   ├── brdf_lut_convolution_vertex.rs
│   │   │   │   ├── compositor_fragment.rs
│   │   │   │   ├── compositor_vertex.rs
│   │   │   │   ├── compute_copy_depth_to_pyramid.rs
│   │   │   │   ├── compute_copy_depth_to_pyramid_multisampled.rs
│   │   │   │   ├── compute_culling.rs
│   │   │   │   ├── compute_downsample_depth_pyramid.rs
│   │   │   │   ├── cubemap_sampling_test_fragment.rs
│   │   │   │   ├── cubemap_sampling_test_vertex.rs
│   │   │   │   ├── debug_overlay_fragment.rs
│   │   │   │   ├── debug_overlay_vertex.rs
│   │   │   │   ├── di_convolution_fragment.rs
│   │   │   │   ├── generate_mipmap_fragment.rs
│   │   │   │   ├── generate_mipmap_vertex.rs
│   │   │   │   ├── implicit_isosceles_vertex.rs
│   │   │   │   ├── light_tiling_bin_lights.rs
│   │   │   │   ├── light_tiling_clear_tiles.rs
│   │   │   │   ├── light_tiling_compute_tile_min_and_max_depth.rs
│   │   │   │   ├── light_tiling_compute_tile_min_and_max_depth_multisampled.rs
│   │   │   │   ├── light_tiling_depth_pre_pass.rs
│   │   │   │   ├── passthru_fragment.rs
│   │   │   │   ├── prefilter_environment_cubemap_fragment.rs
│   │   │   │   ├── prefilter_environment_cubemap_vertex.rs
│   │   │   │   ├── primitive_fragment.rs
│   │   │   │   ├── primitive_vertex.rs
│   │   │   │   ├── shadow_mapping_fragment.rs
│   │   │   │   ├── shadow_mapping_vertex.rs
│   │   │   │   ├── skybox_cubemap_fragment.rs
│   │   │   │   ├── skybox_cubemap_vertex.rs
│   │   │   │   ├── skybox_equirectangular_fragment.rs
│   │   │   │   ├── skybox_vertex.rs
│   │   │   │   ├── slabbed_renderlet.rs
│   │   │   │   ├── slabbed_vertices.rs
│   │   │   │   ├── slabbed_vertices_no_instance.rs
│   │   │   │   ├── tonemapping_fragment.rs
│   │   │   │   ├── tonemapping_vertex.rs
│   │   │   │   ├── ui_fragment.rs
│   │   │   │   └── ui_vertex.rs
│   │   │   ├── linkage.rs
│   │   │   ├── material/
│   │   │   │   └── cpu.rs
│   │   │   ├── material.rs
│   │   │   ├── math.rs
│   │   │   ├── mesh.rs
│   │   │   ├── pbr/
│   │   │   │   ├── brdf/
│   │   │   │   │   ├── cpu.rs
│   │   │   │   │   └── shader.rs
│   │   │   │   ├── brdf.rs
│   │   │   │   ├── debug.rs
│   │   │   │   ├── ibl/
│   │   │   │   │   ├── cpu.rs
│   │   │   │   │   ├── diffuse_irradiance.rs
│   │   │   │   │   └── shader.rs
│   │   │   │   ├── ibl.rs
│   │   │   │   └── shader.rs
│   │   │   ├── pbr.rs
│   │   │   ├── primitive/
│   │   │   │   ├── cpu.rs
│   │   │   │   └── shader.rs
│   │   │   ├── primitive.rs
│   │   │   ├── sdf.rs
│   │   │   ├── skybox/
│   │   │   │   ├── cpu.rs
│   │   │   │   └── shader.rs
│   │   │   ├── skybox.rs
│   │   │   ├── stage/
│   │   │   │   └── cpu.rs
│   │   │   ├── stage.rs
│   │   │   ├── sync.rs
│   │   │   ├── texture/
│   │   │   │   └── mips.rs
│   │   │   ├── texture.rs
│   │   │   ├── tonemapping/
│   │   │   │   └── cpu.rs
│   │   │   ├── tonemapping.rs
│   │   │   ├── transform/
│   │   │   │   └── cpu.rs
│   │   │   ├── transform.rs
│   │   │   ├── tutorial/
│   │   │   │   ├── implicit_isosceles_vertex.wgsl
│   │   │   │   └── passthru.wgsl
│   │   │   ├── tutorial.rs
│   │   │   ├── types.rs
│   │   │   └── ui_slab/
│   │   │       ├── mod.rs
│   │   │       └── shader.rs
│   │   ├── tests/
│   │   │   └── wasm.rs
│   │   └── webdriver.json
│   ├── renderling-build/
│   │   ├── Cargo.toml
│   │   └── src/
│   │       └── lib.rs
│   ├── renderling-ui/
│   │   ├── Cargo.toml
│   │   └── src/
│   │       ├── lib.rs
│   │       ├── renderer.rs
│   │       └── test.rs
│   ├── sandbox/
│   │   ├── Cargo.toml
│   │   └── src/
│   │       └── main.rs
│   ├── wire-types/
│   │   ├── Cargo.toml
│   │   └── src/
│   │       └── lib.rs
│   └── xtask/
│       ├── Cargo.toml
│       └── src/
│           ├── deps.rs
│           ├── main.rs
│           └── server.rs
├── fonts/
│   └── Font Awesome 6 Free-Regular-400.otf
├── gltf/
│   ├── DamagedHelmet.glb
│   ├── EmissiveStrengthTest.glb
│   ├── Fox.glb
│   ├── SimpleSkin.gltf
│   ├── animated_triangle.gltf
│   ├── box_animated.glb
│   ├── cheetah_cone.glb
│   ├── cube.glb
│   ├── four_spotlights.glb
│   ├── gltfTutorial_003_MinimalGltfFile.gltf
│   ├── gltfTutorial_008_SimpleMeshes.gltf
│   ├── gltfTutorial_013_SimpleTexture.gltf
│   ├── gltfTutorial_017_SimpleMorphTarget.gltf
│   ├── gltfTutorial_019_SimpleSkin.gltf
│   ├── light_tiling_test.glb
│   ├── marble_bust_1k.glb
│   ├── normal_mapping_brick_sphere.glb
│   ├── pedestal.glb
│   ├── shadow_mapping_only_cuboid.gltf
│   ├── shadow_mapping_only_cuboid_red_and_blue.gltf
│   ├── shadow_mapping_point.glb
│   ├── shadow_mapping_points.glb
│   ├── shadow_mapping_sanity.glb
│   ├── shadow_mapping_sanity.gltf
│   ├── shadow_mapping_sanity_camera.gltf
│   ├── shadow_mapping_spots.glb
│   ├── simple_morph_triangle.gltf
│   ├── spot_lights.glb
│   └── spot_one.glb
├── img/
│   └── hdr/
│       ├── helipad.hdr
│       ├── night.hdr
│       └── resting_place.hdr
├── manual/
│   ├── .gitignore
│   ├── book.toml
│   └── src/
│       ├── SUMMARY.md
│       ├── context.md
│       ├── gltf.md
│       ├── lighting/
│       │   ├── analytical.md
│       │   └── ibl.md
│       ├── lighting.md
│       ├── reflinks.md
│       ├── setup.md
│       ├── skybox.md
│       ├── stage.md
│       └── welcome.md
└── rustfmt.toml

================================================
FILE CONTENTS
================================================

================================================
FILE: .cargo/config.toml
================================================
[alias]
xtask = "run --package xtask --"
shaders = "xtask compile-shaders"
linkage = "xtask generate-linkage"
test-wasm = "xtask test-wasm"

[build]
rustflags = ["--cfg=web_sys_unstable_apis"]
rustdocflags = ["--cfg=web_sys_unstable_apis"]

[env]
CARGO_WORKSPACE_DIR = { value = "", relative = true }


================================================
FILE: .gitattributes
================================================
*.txt text
*.rs text
*.md text
*.yaml text
*.spv binary
*.wgsl linguist-generated=true binary
* text=auto eol=lf


================================================
FILE: .github/FUNDING.yml
================================================
# These are supported funding model platforms

github: [schell] # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2]
patreon: # Replace with a single Patreon username
open_collective: # Replace with a single Open Collective username
ko_fi: # Replace with a single Ko-fi username
tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel
community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry
liberapay: # Replace with a single Liberapay username
issuehunt: # Replace with a single IssueHunt username
otechie: # Replace with a single Otechie username
lfx_crowdfunding: # Replace with a single LFX Crowdfunding project-name e.g., cloud-foundry
custom: # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2']


================================================
FILE: .github/workflows/push.yaml
================================================
# Happens on push to main, and all PRs
name: push

on: 
  push:
    branches: 
      - main
  pull_request:

env:
  # For setup-rust, see https://github.com/moonrepo/setup-rust/issues/22
  GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
  CARGO_GPU_COMMITSH: 31153a8edd3bc626d4b9fb0cd7bdb7a8b30797d3

jobs:
  # Installs cargo deps and sets the cache directory for subsequent jobs
  install-cargo-gpu:
    strategy:
      matrix: 
        os: [ubuntu-24.04, macos-latest]
    runs-on: ${{ matrix.os }}
    defaults:
      run: 
        shell: bash
    env:
      RUST_LOG: debug  
      RUNNER_OS: ${{ matrix.os }}
    outputs:
      cachepath-macOS: ${{ steps.cachepathstep.outputs.cachepath-macOS }}
      cachepath-Linux: ${{ steps.cachepathstep.outputs.cachepath-Linux }}
      cachepath-Windows: ${{ steps.cachepathstep.outputs.cachepath-Windows }}
    steps:
      - uses: actions/checkout@v2
      - uses: actions/cache@v4
        with:
          path: ~/.cargo
          # THIS KEY MUST MATCH BELOW
          key: cargo-cache-2-${{ env.CARGO_GPU_COMMITSH }}-${{ matrix.os }}
      - uses: moonrepo/setup-rust@v1
        with:
          cache: false
      - run: rustup default stable 
      - run: rustup update
      - run: | 
          cargo install --git https://github.com/rust-gpu/cargo-gpu --rev $CARGO_GPU_COMMITSH cargo-gpu 
      - run: cargo gpu show commitsh
      - id: cachepathstep
        run: |
          CACHE_PATH=`cargo gpu show cache-directory`
          echo $CACHE_PATH
          echo "cachepath-$RUNNER_OS=$CACHE_PATH" >> "$GITHUB_OUTPUT"

  # Builds the shaders and ensures there is no git diff
  renderling-build-shaders:
    needs: install-cargo-gpu
    strategy:
      fail-fast: false
      matrix: 
        # temporarily skip windows, revisit after a fix for this error is found:
        # https://github.com/rust-lang/cc-rs/issues/1331
        os: [ubuntu-24.04, macos-latest] #, windows-latest]
    runs-on: ${{ matrix.os }}
    defaults:
      run: 
        shell: bash
    env:
      RUST_LOG: debug
    steps:
      - uses: actions/checkout@v2
      # Disable moonrepo's built-in cache so it doesn't overwrite ~/.cargo
      # (which contains the cargo-gpu binary from the install-cargo-gpu job).
      - uses: moonrepo/setup-rust@v1
        with:
          cache: false
      - uses: actions/cache@v4
        with:
          path: ~/.cargo
          # THIS KEY MUST MATCH ABOVE
          key: cargo-cache-2-${{ env.CARGO_GPU_COMMITSH }}-${{ matrix.os }}
      - uses: actions/cache@v4
        with:
          path: | 
              ${{ needs.install-cargo-gpu.outputs.cachepath-macOS }}
              ${{ needs.install-cargo-gpu.outputs.cachepath-Linux }}
              ${{ needs.install-cargo-gpu.outputs.cachepath-Windows }}
          key: rust-gpu-cache-1-${{ matrix.os }}
      - run: rustup install nightly
      - run: rustup component add --toolchain nightly rustfmt
      - run: cargo gpu show commitsh
      - run: rm -rf crates/renderling/src/linkage/* crates/renderling/shaders
      - run: cargo shaders
      - run: cargo linkage
      - run: cargo build -p renderling
      - run: git diff --exit-code --no-ext-diff

  # Ensures code is properly formatted with nightly rustfmt
  renderling-fmt:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v2
      - uses: actions/cache@v4
        with:
          path: ~/.cargo
          # THIS KEY MUST MATCH ABOVE
          key: renderling-fmt-${{ env.CARGO_GPU_COMMITSH }}-ubuntu-24.04
      - run: mkdir -p $HOME/.cargo/bin
      - uses: moonrepo/setup-rust@v1
      - run: rustup install nightly
      - run: rustup component add --toolchain nightly rustfmt
      - run: cargo +nightly fmt -- --check

  # BAU clippy lints
  renderling-clippy:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v2
      - uses: moonrepo/setup-rust@v1
      - run: cargo clippy

  # Ensures the example glTF viewer compiles
  renderling-build-example:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v2 
      - uses: moonrepo/setup-rust@v1
      - run: cargo build -p example
      
  # BAU tests
  renderling-test:
    strategy:
      matrix: 
        os: [ubuntu-latest, macos-latest]
    runs-on: ${{ matrix.os }}
    steps:
      - uses: actions/checkout@v2
      - uses: moonrepo/setup-rust@v1
      - uses: actions/cache@v4
        with:
          path: ~/.cargo
          key: ${{ matrix.os }}-test-cargo-${{ hashFiles('**/Cargo.lock') }}
          restore-keys: ${{ matrix.os }}-cargo-

      - name: Install linux deps 
        if: runner.os == 'Linux'
        run: |
            sudo apt-get -y update
            sudo apt-get -y install mesa-vulkan-drivers libvulkan1 vulkan-tools vulkan-validationlayers

      - name: Install cargo-nextest
        run: cargo install --locked cargo-nextest || true

      - name: Test 
        run: cargo nextest run -j 1
        env: 
          RUST_BACKTRACE: 1

      - uses: actions/upload-artifact@v4
        if: always()
        with:
          name: test-output-${{ matrix.os }}
          path: test_output  

  ## WASM tests, commented out until we can get a proper headless browser on CI
  # renderling-wasm-test:
  #   # strategy:
  #   #   matrix: 
  #   #     # empty string means ff, --chrome is chrome
  #   #     browser: ["", "--chrome"]
  #   runs-on: ubuntu-latest
  #   steps:
  #     - uses: actions/checkout@v2
  #     - uses: moonrepo/setup-rust@v1
  #     - uses: actions/cache@v4
  #       with:
  #         path: ~/.cargo
  #         key: ${{ runner.os }}-test-cargo-${{ hashFiles('**/Cargo.lock') }}
  #         restore-keys: ${{ runner.os }}-cargo-        
  #     - name: Install linux deps 
  #       if: runner.os == 'Linux'
  #       run: |
  #           sudo apt-get -y update
  #           sudo apt-get -y install mesa-vulkan-drivers libvulkan1 vulkan-tools vulkan-validationlayers
  #     - name: Install wasm-pack
  #       run: cargo install --locked wasm-pack || true
  #     - name: Test WASM
  #       env:
  #         RUST_LOG: info
  #       run: cargo test-wasm --chrome #${{ matrix.browser }}
  #     - uses: actions/upload-artifact@v4
  #       if: always()
  #       with:
  #         name: test-output-${{ runner.os }}-wasm
  #         path: test_output


================================================
FILE: .gitignore
================================================
# macOS
*.DS_Store
# will have compiled files and executables
/target/
shaders/target
shaders/shader-crate/target
**/spirv-manifest.json

# Remove Cargo.lock from gitignore if creating an executable, leave it for libraries
# More information here https://doc.rust-lang.org/cargo/guide/cargo-toml-vs-cargo-lock.html
# Cargo.lock

# These are backup files generated by rustfmt
**/*.rs.bk

*~undo-tree~

test_output
cmy_triangle_renderer.svg
.aider*
flamegraph.svg
**/*.blend1

# WGSL is generated by `cargo xtask generate-linkage --wgsl`
# or by `cargo build -p renderling`, but we don't want to track
# the changes in them.
crates/renderling/shaders/*.wgsl


================================================
FILE: .helix/snippets/rust.json
================================================
{
  "anchor-snippet": {
    "prefix": "anchor",
    "body": [
      "// ANCHOR: ${1:anchor}",
  	  "$2",
  	  "// ANCHOR_END: $1"
    ],
    "description": "insert a video tag"
  }
}


================================================
FILE: AGENTS.md
================================================
# AGENTS.md

## Build & Test
- Build: `cargo build -p renderling`
- Test all: `cargo nextest run -j 1` or `cargo test`
- Single test: `cargo test <test_name>` or `cargo nextest run <test_name>`
- Lint: `cargo clippy`
- Shaders: `cargo shaders` (compile), `cargo linkage` (generate WGSL)

## Code Style
- Max line width: 100 chars
- Imports: group by crate (`imports_granularity = "crate"`), std → external → internal
- Error handling: use `snafu` crate with `#[derive(Debug, Snafu)]`
- Naming: `snake_case` functions/modules, `PascalCase` types, `with_*` builder methods
- Tests: inline `#[cfg(test)] mod test { ... }` within modules
- CPU-only code: wrap with `#[cfg(cpu)]`

Always format with `cargo +nightly fmt`.

## Disallowed Methods (clippy.toml)
Avoid: `Vec{2,3,4}::normalize_or_zero`, `Mat4::to_scale_rotation_translation`, `f32::signum`


================================================
FILE: CONTRIBUTING.md
================================================
# Contributing to Renderling

Thank you for your interest in contributing to Renderling!

## Code of Conduct

This project follows the [Zcash Code of Conduct](https://github.com/zcash/zcash/blob/master/code_of_conduct.md).
We are committed to providing a welcoming and harassment-free experience for everyone.

## NLnet Generative AI Policy

This is an NLnet-funded project. We adhere to the [NLnet Generative AI Policy](https://nlnet.nl/foundation/policies/generativeAI/).
If you use generative AI tools like LLMs, code assistants, etc. in your contributions, you must:
- Disclose any substantive use
- Maintain a prompt provenance log for material contributions
- Ensure outputs can be legally published under FLOS licenses
- Not present AI-generated content as your own human-authored work

### When it comes to AI - use your best judgment

I use AI to help plan a strategy and then make changes by hand.

What I want to avoid is a situation where copyrighted material from an LLM's training corpus
makes it into the codebase.

So please disclose if the _outputs_ of generative AI is what is being committed.

## Getting Started

1. Fork and clone the repository
2. Install Rust via [rustup](https://rustup.rs)
3. Install `cargo-gpu`: `cargo install --git https://github.com/rust-gpu/cargo-gpu cargo-gpu`
4. Optionally install `cargo-nextest`: `cargo install cargo-nextest`

## Development Workflow

1. Create a branch for your changes
2. Follow the code style guidelines in [AGENTS.md](AGENTS.md)
3. Run tests: `cargo nextest run -j 1` or `cargo test`
4. Run lints: `cargo clippy`
5. If modifying shaders: `cargo shaders && cargo linkage`
6. Ensure there are no unexpected diffs: `git diff`
7. Submit a pull request

## Discussions

Questions, ideas, and general discussion should happen on
[GitHub Discussions](https://github.com/schell/renderling/discussions).

## Testing

Tests render images in headless mode and compare against reference images in `test_img/`.
New visual features should include image comparison tests where applicable.

## License

By contributing, you agree that your contributions will be dual-licensed under
the MIT and Apache 2.0 licenses. See [LICENSE](LICENSE) for details.


================================================
FILE: Cargo.toml
================================================
[workspace]
members = [ 
    "crates/example", 
    "crates/examples",
    "crates/example-culling",
    #"crates/example-wasm",
    "crates/loading-bytes",
    "crates/renderling", 
    "crates/renderling-build",
    "crates/renderling-ui",
    "crates/wire-types",
    # "crates/sandbox",
    "crates/xtask"
]

exclude = ["./shaders"]

resolver = "2"

[workspace.dependencies]
assert_approx_eq = "1.1.0"
async-channel = "1.8"
axum = "0.8.4"
bytemuck = { version = "1.19.0", features = ["derive"] }
cfg_aliases = "0.2"
clap = { version = "4.5.23", features = ["derive"] }
console_log = "1.0.0"
craballoc = { version = "0.3.1" } 
crabslab = { version = "0.6.6", default-features = false }
plotters = "0.3.7"
ctor = "0.2.2"
dagga = "0.2.1"
env_logger = "0.10.0"
futures-lite = "1.13"
futures-util = "0.3.31"
glam = { version = "0.30", default-features = false }
gltf = { version = "1.4,1", features = ["KHR_lights_punctual", "KHR_materials_unlit", "KHR_materials_emissive_strength", "extras", "extensions"] }
glyph_brush = "0.7.8"
image = "0.25"
log = "0.4"
loading-bytes = { path = "crates/loading-bytes", version = "0.1.1" }
lyon = "1.0.1"
naga = { version = "26.0", features = ["spv-in", "wgsl-out", "wgsl-in", "msl-out"] }
new_mime_guess = "4.0.4"
pretty_assertions = "1.4.0"
proc-macro2 = { version = "1.0", features = ["span-locations"] }
quote = "1.0"
reqwest = "0.12.23"
rustc-hash = "1.1"
serde = {version = "1.0", features = ["derive"]}
serde_json = "1.0.117"
send_wrapper = "0.6.0"
similarity = "0.2.0"
snafu = "0.8"
spirv-std = { git = "https://github.com/rust-gpu/rust-gpu.git", rev = "05b34493ce661dccd6694cf58afc13e3c8f7a7e0" }
spirv-std-macros = { git = "https://github.com/rust-gpu/rust-gpu.git", rev = "05b34493ce661dccd6694cf58afc13e3c8f7a7e0" }
syn = { version = "2.0.49", features = ["full", "extra-traits", "parsing"] }
tokio = "1.47.1"
tracing = "0.1.41"
wasm-bindgen = "0.2"
wasm-bindgen-futures = "0.4"
wasm-bindgen-test = "0.3"
web-sys = "0.3"
winit = { version = "0.30.12" }
wgpu = { version = "26.0" }
wgpu-core = { version = "26.0" }
metal = "0.32"

[profile.dev]
opt-level = 1

[profile.dev.package.image]
opt-level = 3

[profile.dev.package.gltf]
opt-level = 3

[patch.crates-io]
spirv-std = { git = "https://github.com/rust-gpu/rust-gpu.git", rev = "05b34493ce661dccd6694cf58afc13e3c8f7a7e0" }

[workspace.lints.rust]
unexpected_cfgs = { level = "allow", check-cfg = [ 'cfg(spirv)' ] }


================================================
FILE: LICENSE
================================================
Rendeling is dual-licensed under either

* MIT License (docs/LICENSE-MIT or http://opensource.org/licenses/MIT)
* Apache License, Version 2.0 (docs/LICENSE-APACHE or http://www.apache.org/licenses/LICENSE-2.0)

at your option.

================================================
FILE: NOTES.md
================================================
# Notes

Just pro-cons on tech choices and little things I don't want to forget whil implementing `renderling`.

# gltf

* why are there repeats of nodes in document.nodes?

# rust-gpu

## pros

* sharing code on CPU and GPU
  - sanity testing GPU code on CPU using regular tests
  - ability to run shaders on either CPU or GPU and profile
* it's Rust
  - using cargo and Rust module system
  - expressions!
  - type checking!
  - traits!
  - editor tooling!

## cons / limititions / gotchas

* Can't use an array as a slice, it causes this error:
  ```
  error: cannot cast between pointer types
         from `*[u32; 3]`
           to `*[u32]`  
  ```
  See the ticket I opened <https://github.com/Rust-GPU/rust-gpu/issues/465>
* ~~can't use enums (but you can't in glsl or hlsl or msl or wgsl either)~~ you _can_ but they must be simple (like `#[repr(u32)]`)
* ~~struct layout size/alignment errors can be really tricky~~ solved by using a slab
* rust code must be no-std
* don't use `while let` or `while` loops
* for loops are hit or miss, sometimes they work and sometimes they don't
  - see [this rust-gpu issue](https://github.com/EmbarkStudios/rust-gpu/issues/739)
  - see [conversation with eddyb on discord](https://discord.com/channels/750717012564770887/750717499737243679/threads/1092283362217046066)
* can't use `.max` or `.min` on integers
* meh, but no support for dynamically sized arrays (how would that work in no-std?)
  - see [conversation on discord](https://discord.com/channels/750717012564770887/750717499737243679/1091813590400516106)
* can't use bitwise rotate_left or rotate_right
  - see [the issue on github](https://github.com/EmbarkStudios/rust-gpu/issues/1062)
* sometimes things like indexing are just funky-joe-monkey:
  - see [this comment on discord](https://discord.com/channels/750717012564770887/750717499737243679/1131395331368693770)
  - see [this comment on matrix](https://matrix.to/#/!XFRnMvAfptAHthwBCx:matrix.org/$f4RmQGzq4Ulmmd4bEFOvP0LzLZei8lrHCF--s71Zcxs?via=matrix.org&via=mozilla.org&via=kyju.org)
* cannot use shader entry point functions nested within each other
  - see [the discussion on `rust-gpu` discord](https://discord.com/channels/750717012564770887/750717499737243679/1198813817975603251)
* if your shader crate is just a library and has no entry points it **cannot** have the
  `crate-type = ["rlib", "dylib"]` Cargo.toml annotation or you will get "Undefined symbols" errors
* no recursion! you must convert your recursive algos into ones with manually managed stacks
* `usize` is `u32` on `target_arch = "spirv"`! Watch out for silent shader panics caused by wrapping
  arithmetic operations.

# wgpu

## pros

* works on all platforms with the same API
* much more configurable than OpenGL
* much better error messages than OpenGL
* less verbose than Vulkan
* the team is very responsive

## cons

* no support for arrays of textures on web, yet
* atomics are not supported in the Naga SPIRV frontend, which limits the capabilities of compute
  - see [the related Naga issue](https://github.com/gfx-rs/naga/issues/2301)

# glam

## pros

* lots of other graphics libs use it
* speed

## cons

* different types on SIMD, with different structures - like Vec4 is a struct on my macos
  but it's a tuple on SIMD linux.

# more things to figure out

* bindless - wth exactly is it

# tips, gotchas, links and further reading

* `location[...] is provided by the previous stage output but is not consumed as input by this stage.`
  - rust-gpu has optimized away the shader input, you must use the input parameter in your downstream shader
  - sometimes the optimization is pretty agressive, so you really gotta _use_ the input
* [Forward+ shading (as opposed to deferred)](https://takahiroharada.files.wordpress.com/2015/04/forward_plus.pdf)
  **tl;dr**
  In a compute shader before the vertex pass:
  * break up the frame into tiles
  * for each tile compute which lights' contribution to the pixels in the tile
  * during shading, iterate over _only_ the lights for each pixel according to its tile
* [**Help inspecting buffers in Xcode** ](https://developer.apple.com/documentation/xcode/inspecting-buffers?changes=__9)
* command that includes some vulkan debugging stuff
  - VK_LOADER_LAYERS_ENABLE='*validation' VK_LAYER_ENABLES=VK_VALIDATION_FEATURE_ENABLE_DEBUG_PRINTF_EXT DEBUG_PRINTF_TO_STDOUT=1
* When generating mipmaps I ran into a problem where sampling the original texture was always coming up [0.0, 0.0 0.0, 0.0]. It turns out that the sampler was trying to read from the mipmap at level 1, and of course it didn't exist yet as that was the one I was trying to generate. The fix was to sample a different texture - one without slots for the mipmaps, then throw away that texture.

## PBR reference implementations
* [khronos sample viewer](https://github.khronos.org/glTF-Sample-Viewer-Release/)
  - [vertex shader code](https://github.com/KhronosGroup/glTF-Sample-Viewer/blob/main/source/Renderer/shaders/primitive.vert)
  - [fragment shader code](https://github.com/KhronosGroup/glTF-Sample-Viewer/blob/main/source/Renderer/shaders/pbr.frag)
* [babylonjs](https://sandbox.babylonjs.com/)
  - [vertex shader code](https://github.com/BabylonJS/Babylon.js/blob/master/packages/dev/core/src/Shaders/pbr.vertex.fx)
  - [fragment shader code](https://github.com/BabylonJS/Babylon.js/blob/master/packages/dev/core/src/Shaders/pbr.fragment.fx)

# contributions made during the course of this project
* wrote an NLNet grant proposal to add atomics to `naga`'s spv frontend
  - roughly to complete this PR https://github.com/gfx-rs/naga/pull/2304
* fixing `wgpu`'s vulkan backend selection on macOS
  - https://github.com/gfx-rs/wgpu/pull/3958
  - https://github.com/gfx-rs/wgpu/pull/3962


================================================
FILE: README.md
================================================
# <img style="image-rendering: pixelated; image-rendering: -moz-crisp-edges; image-rendering: crisp-edges;" src="https://github.com/user-attachments/assets/83eafc47-287c-4b5b-8fd7-2063e56b2338" /> renderling

Renderling is an innovative, GPU-driven renderer designed for efficient scene rendering with a focus on leveraging 
GPU capabilities for nearly all rendering operations. 
Utilizing Rust for shader development, it ensures memory safety and cross-platform compatibility, including web platforms. 
The project, currently in the alpha stage, aims for rapid loading of GLTF files, handling large scenes, and supporting numerous lights. 
Development emphasizes performance, ergonomics, observability and the use of modern rendering techniques like forward+ rendering and 
physically based shading.

Read the [manual](https://renderling.xyz/manual/index.html) to get started.

Read the [docs](https://renderling.xyz/docs/renderling/index.html) for more info.

Visit <https://renderling.xyz> to read the development blog.

<img width="912" alt="ibl_environment_test" src="https://github.com/schell/renderling/assets/24942/297d6150-64b2-45b8-9760-12b27dc8cc3e">

This project is funded through [NGI Zero Commons](https://nlnet.nl/commonsfund/), a fund established by [NLnet](https://nlnet.nl) 
with financial support from the European Commission's [Next Generation Internet](https://ngi.eu) program. 
Learn more at the [2025 NLnet project page](https://nlnet.nl/project/Renderling-Ecosystem/) and the 
[2024 NLnet project page](https://nlnet.nl/project/Renderling).

[<img src="https://nlnet.nl/logo/banner.png" alt="NLnet foundation logo" width="20%" />](https://nlnet.nl)

[<img src="https://nlnet.nl/image/logos/NGI0_tag.svg" alt="NGI Zero Logo" width="20%" />](https://nlnet.nl/core)


## Warning

This is very much a work in progress.

## What

`renderling` holds entire scenes of geometry, textures, materials, lighting, even the scene graph itself - in GPU buffers.
All but a few of the rendering operations happen on the GPU.
The CPU is used to interact with the filesystem to marshall data to the GPU and to update transforms.

Shaders are written in Rust, via `rust-gpu`.

## Why should I use `renderling`

* Data is easily staged on the GPU using an automatically reference counted slab allocator that 
  provides access from the CPU.

  Your scene geometry, materials, animations - all of it - live on the GPU, while the CPU has easy access
  to read and modify that data, without borrowing - allowing you to send your data through threads to anything 
  that needs it. 

* Having everything on the GPU makes `renderling` very effective at rendering certain types of scenes.
  
  Specifically `renderling` aims to be good at rendering scenes with a moderate level of unique geometry,
  (possibly a large amount of repeated geometry), with a small number of large textures (or large number of small textures),
  and lots of lighting effects.

* Tight integration with GLTF:
  - Loading scenes, nodes, animations etc
  - Includes tools for controlling animations
  - Supported extensions:
    * KHR_lights_punctual
    * KHR_materials_unlit
    * KHR_materials_emissive_strength
* Image based lighting + analytical lighting

* Good documentation

## API Features

* simple structs represent nodes, meshes, materials and lights
* seamless GPU / CPU syncronization
* headless rendering support
  - rendering to texture and saving via `image` crate
* text and user interface rendering support
* nested nodes with local transforms
* tight integration with glTF (cargo feature `gltf` - on by default)

Shaders are written in Rust via `rust-gpu` where possible, falling back to `wgsl` where needed.

## Rendering Features / Roadmap

Renderling takes a [forward+](https://takahiroharada.files.wordpress.com/2015/04/forward_plus.pdf) approach to rendering.

By default it uses a single uber-shader for rendering.

- [x] texture atlas
  - [x] automatic resource management (Arc/drop based)
  - [ ] [BCn compression](https://www.reedbeta.com/blog/understanding-bcn-texture-compression-formats/)

- [x] GPU slab allocator
  - [x] automatic resource management (Arc/drop based)

- [x] frustum culling
- [ ] occlusion culling - in progress

- [x] Built-in support for common lighting/material workflows
  - [x] physically based shading
  - [x] unlit
- [x] forward+ style light tiling/light culling
- [x] shadow mapping
  - [ ] shadow mapping from image-based lighting
- [ ] ssao

- [x] msaa
- [x] bloom "physically based" up+downsampling blur
- [ ] depth of field
- [x] high dynamic range
- [x] skybox

- [x] image based lighting
  - [x] diffuse
  - [x] specular
     
- [ ] order independent transparency

- [ ] entirely too much raymarching 

- gltf support
  - [x] scenes
  - [x] nodes
  - [x] cameras
  - [x] meshes
  - materials
    - [x] pbr metallic roughness (factors + textures)
    - [x] normal mapping
    - [x] occlusion textures
    - [ ] pbr specular glossiness
    - [ ] parallax mapping
  - [x] textures, images, samplers
  - animation
    - [x] interpolation
    - [x] skinning
    - [x] morph targets 

- 2d (renderling-ui)
  - [x] text
  - [x] stroked and filled paths
    - [x] circles
    - [x] rectangles
    - [x] cubic beziers
    - [x] quadratic beziers
    - [x] arbitrary polygons
    - [x] fill w/ image

## Definition
**renderling** noun

A small beast that looks cute up close, ready to do your graphics bidding.

## Haiku

> Ghost in the machine,
> lighting your scene with magic.
> Cute technology.

## Project Organization

* crates/renderling

  Main library crate.
  Contains CPU Rust code for creating pipelines and managing resources, making render passes, etc.
  Contains GPU Rust code of the shader operations themselves.
  Contains tests, some using image comparison of actual frame renders for consistency and backwards compatibility.

* crates/renderling/shaders

  Contains **.spv** and **.wgsl** files generated by [`cargo-gpu`](https://github.com/rust-gpu/cargo-gpu).

* crates/renderling/src/linkage*

  Contains autogenerated `wgpu` linkage for the generated shaders.

* img

  Image assets for tests (textures, etc.)

* test_img

  Reference images to use for testing.

* crates/example

  Contains an example of using the `renderling` crate to make an application.

## Tests

Tests use `renderling` in headless mode and generate images that are compared to expected output.

### Running tests

```
cargo test
```

## Building the shaders

The `crates/renderling/shaders/` folder contains the generated SPIR-V files.

To regenerate the shaders, run:

```
cargo shaders
```

And to explicitly re-generate `wgpu` linkage, you can run: 

```
cargo linkage
```

...but the `build.rs` script will do this for you, so it's not strictly necessary.

## Building on WASM

```
RUSTFLAGS=--cfg=web_sys_unstable_apis trunk build crates/example-wasm/index.html && basic-http-server -a 127.0.0.1:8080 crates/example-wasm/dist
```

## 🫶 Sponsor this!

This work will always be free and open source. 
If you use it (outright or for inspiration), please consider donating.

[💰 Sponsor 💝](https://github.com/sponsors/schell)

### Special thanks 

- James Harton ([@jimsynz](https://github.com/jimsynz/)) for donating multiple linux CI runners with 
  physical GPUs!

### Related work & spin-off projects 

Many projects were born from first solving a need within `renderling`. 
Some of these solutions were then spun off into their own projects.

- [`wgsl-rs`](https://github.com/schell/wgsl-rs)
  Write WGSL shaders using a subset of Rust and run them on CPU _and_ GPU
- [`crabslab` and `craballoc`](https://github.com/schell/crabslab)
  A slab allocator for working across CPU/GPU boundaries.
- [`loading-bytes`](crates/loading-bytes)
  A cross-platform (including the web) and comedically tiny way of loading files to bytes.
- [`moongraph`](https://github.com/schell/moongraph)
  A DAG and resource graph runner.
- Contributions to [`naga`](https://github.com/gfx-rs/wgpu/issues/4489)
  * Adding atomics support to the SPIR-V frontend
- Contributions to [`gltf`](https://github.com/gltf-rs/gltf/pull/419)
- [`cargo-gpu`](https://githu.com/rust-gpu/cargo-gpu)
  A shader compilation cli tool.

Sponsoring this project contributes to the ecosystem. 

## License
Renderling is free and open source. All code in this repository is dual-licensed under either:

    MIT License (LICENSE-MIT or http://opensource.org/licenses/MIT)
    Apache License, Version 2.0 (LICENSE-APACHE or http://www.apache.org/licenses/LICENSE-2.0)

at your option. This means you can select the license you prefer! This dual-licensing approach
is the de-facto standard in the Rust ecosystem and there are very good reasons to include both.

Unless you explicitly state otherwise, any contribution intentionally submitted for inclusion
in the work by you, as defined in the Apache-2.0 license, shall be dual licensed as above,
without any additional terms or conditions.

## Notes & Devlog
I keep a list of (un)organized notes about this project [here](NOTES.md).
I keep a devlog at the official website <https://renderling.xyz/devlog/index.html>.


================================================
FILE: clippy.toml
================================================
disallowed-methods = [
    "glam::Vec2::normalize_or_zero",
    "glam::Vec3::normalize_or_zero",
    "glam::Vec4::normalize_or_zero",
    "glam::Mat4::to_scale_rotation_translation",
    "f32::signum"
]


================================================
FILE: crates/example/Cargo.toml
================================================
[package]
name = "example"
version = "0.1.0"
edition = "2021"

# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html

[lib]

[[bin]]
name = "example"

[dependencies]
clap = { version = "^4.3", features = ["derive"] }
craballoc.workspace = true
#console-subscriber = "0.4.0"
env_logger = {workspace=true}
futures-lite = {workspace=true}
gltf = { workspace = true }
icosahedron = "0.1"
lazy_static = "1.4.0"
loading-bytes = { path = "../loading-bytes" }
log = { workspace = true }
renderling = { path = "../renderling" }
renderling-ui = { path = "../renderling-ui", features = ["text", "path"] }
wasm-bindgen = { workspace = true }
wasm-bindgen-futures = "^0.4"
web-sys = { workspace = true, features = ["Performance", "Window"] }
winit = { workspace = true }
wgpu = { workspace = true }

[dev-dependencies]
image = { workspace = true }
img-diff = { path = "../img-diff" }


================================================
FILE: crates/example/src/camera.rs
================================================
//! Camera control.
use std::str::FromStr;

use renderling::{
    bvol::Aabb,
    camera::Camera,
    glam::{Mat4, Quat, UVec2, Vec2, Vec3},
};
use winit::{event::KeyEvent, keyboard::Key};

const RADIUS_SCROLL_DAMPENING: f32 = 0.001;
const DX_DY_DRAG_DAMPENING: f32 = 0.01;

#[derive(Clone, Copy, Debug, Default)]
pub enum CameraControl {
    #[default]
    Turntable,
    WasdMouse,
}

impl FromStr for CameraControl {
    type Err = String;

    fn from_str(s: &str) -> Result<Self, Self::Err> {
        match s {
            "turntable" => Ok(CameraControl::Turntable),
            "wasdmouse" => Ok(CameraControl::WasdMouse),
            _ => Err("must be 'turntable' or 'wasdmouse'".to_owned()),
        }
    }
}

pub struct TurntableCameraController {
    /// look at
    pub center: Vec3,
    /// distance from the origin
    pub radius: f32,
    /// Determines the distance between the camera's near and far planes
    depth: f32,
    /// anglular position on a circle `radius` away from the origin on x,z
    pub phi: f32,
    /// angular distance from y axis
    pub theta: f32,
    /// is the left mouse button down
    left_mb_down: bool,
}

impl Default for TurntableCameraController {
    fn default() -> Self {
        Self {
            center: Vec3::ZERO,
            radius: 6.0,
            depth: 12.0,
            phi: 0.0,
            theta: std::f32::consts::FRAC_PI_4,
            left_mb_down: false,
        }
    }
}

impl CameraController for TurntableCameraController {
    fn tick(&mut self) {}

    fn reset(&mut self, bounds: Aabb) {
        log::debug!("resetting turntable bounds to {bounds:?}");
        let diagonal_length = bounds.diagonal_length();
        self.radius = diagonal_length * 1.25;
        self.depth = 2.0 * diagonal_length;
        self.center = bounds.center();
        self.left_mb_down = false;
    }

    fn update_camera(&self, UVec2 { x: w, y: h }: UVec2, current_camera: &Camera) {
        let camera_position = Self::camera_position(self.radius, self.phi, self.theta);
        let znear = self.depth / 1000.0;
        current_camera.set_projection_and_view(
            Mat4::perspective_rh(
                std::f32::consts::FRAC_PI_4,
                w as f32 / h as f32,
                znear,
                self.depth,
            ),
            Mat4::look_at_rh(camera_position, self.center, Vec3::Y),
        );
    }

    fn mouse_scroll(&mut self, delta: f32) {
        self.zoom(delta);
    }

    fn mouse_moved(&mut self, _position: Vec2) {}

    fn mouse_motion(&mut self, delta: Vec2) {
        self.pan(delta);
    }

    fn mouse_button(&mut self, is_pressed: bool, is_left_button: bool) {
        self.left_mb_down = is_left_button && is_pressed;
    }

    fn key(&mut self, _event: KeyEvent) {}
}

impl TurntableCameraController {
    fn camera_position(radius: f32, phi: f32, theta: f32) -> Vec3 {
        // convert from spherical to cartesian
        let x = radius * theta.sin() * phi.cos();
        let y = radius * theta.sin() * phi.sin();
        let z = radius * theta.cos();
        // in renderling Y is up so switch the y and z axis
        Vec3::new(x, z, y)
    }

    fn zoom(&mut self, delta: f32) {
        self.radius = (self.radius - (delta * RADIUS_SCROLL_DAMPENING)).max(0.0);
    }

    fn pan(&mut self, delta: Vec2) {
        if self.left_mb_down {
            self.phi += delta.x * DX_DY_DRAG_DAMPENING;

            let next_theta = self.theta - delta.y * DX_DY_DRAG_DAMPENING;
            self.theta = next_theta.clamp(0.0001, std::f32::consts::PI);
        }
    }
}

#[derive(Default)]
pub struct WasdMouseCameraController {
    position: Vec3,
    theta: f32,
    phi: f32,
    forward_is_down: bool,
    backward_is_down: bool,
    left_is_down: bool,
    right_is_down: bool,
    up_is_down: bool,
    down_is_down: bool,
    speed: f32,
    last_tick: Option<f64>,
}

impl CameraController for WasdMouseCameraController {
    fn tick(&mut self) {
        let now = super::now();
        if let Some(prev) = self.last_tick.replace(now) {
            let dt = now - prev;

            // We want the direction to be based solely on self.theta, because we
            // don't want the camera to move in Y.
            let forward_direction = Vec3::NEG_Z;
            let left_direction = Vec3::NEG_X;
            let up_direction = Vec3::Y;
            let rotation = Quat::from_rotation_y(-self.theta);

            if self.forward_is_down {
                let direction = rotation * forward_direction;
                let velocity = dt as f32 * self.speed * direction;
                self.position += velocity;
            }
            if self.backward_is_down {
                let direction = rotation * forward_direction;
                let velocity = dt as f32 * self.speed * direction;
                self.position -= velocity;
            }
            if self.left_is_down {
                let direction = rotation * left_direction;
                let velocity = dt as f32 * self.speed * direction;
                self.position += velocity;
            }
            if self.right_is_down {
                let direction = rotation * left_direction;
                let velocity = dt as f32 * self.speed * direction;
                self.position -= velocity;
            }
            if self.up_is_down {
                let velocity = dt as f32 * self.speed * up_direction;
                self.position += velocity;
            }
            if self.down_is_down {
                let velocity = dt as f32 * self.speed * up_direction;
                self.position -= velocity;
            }
        }
    }

    fn update_camera(&self, UVec2 { x: w, y: h }: UVec2, camera: &Camera) {
        let camera_rotation =
            Quat::from_euler(renderling::glam::EulerRot::XYZ, self.phi, self.theta, 0.0);
        let projection =
            Mat4::perspective_infinite_rh(std::f32::consts::FRAC_PI_4, w as f32 / h as f32, 0.01);
        let view = Mat4::from_quat(camera_rotation) * Mat4::from_translation(-self.position);
        camera.set_projection_and_view(projection, view);
    }

    fn reset(&mut self, _bounds: Aabb) {
        self.position = Vec3::ZERO; //center_max_z + (center_max_z - center_min_z) * 0.5;
        self.theta = 0.0;
        self.phi = 0.0;
        self.speed = 2.0;
        log::info!("resetting to position: {}", self.position);
    }

    fn mouse_scroll(&mut self, _delta: f32) {}

    fn mouse_moved(&mut self, _position: Vec2) {}

    fn mouse_motion(&mut self, delta: Vec2) {
        const DAMPER: f32 = 0.005;
        self.theta = (self.theta + DAMPER * delta.x).rem_euclid(2.0 * std::f32::consts::PI);
        self.phi = (self.phi + DAMPER * delta.y).clamp(-1.2, 1.2);
    }

    fn mouse_button(&mut self, _is_pressed: bool, _is_left_button: bool) {}

    fn key(
        &mut self,
        KeyEvent {
            logical_key, state, ..
        }: KeyEvent,
    ) {
        match logical_key {
            Key::Character(c) => match c.as_str() {
                "p" => self.forward_is_down = state.is_pressed(),
                "k" => self.backward_is_down = state.is_pressed(),
                "i" => self.right_is_down = state.is_pressed(),
                "y" => self.left_is_down = state.is_pressed(),
                "u" => {
                    self.down_is_down = false;
                    self.up_is_down = state.is_pressed();
                }
                "U" => {
                    self.up_is_down = false;
                    self.down_is_down = state.is_pressed();
                }
                s => log::info!("unused key char '{s}'"),
            },

            k => log::info!("key: {k:#?}"),
        }
    }
}

pub trait CameraController {
    fn reset(&mut self, bounds: Aabb);
    fn tick(&mut self);
    fn update_camera(&self, size: UVec2, camera: &Camera);
    fn mouse_scroll(&mut self, delta: f32);
    fn mouse_moved(&mut self, position: Vec2);
    fn mouse_motion(&mut self, delta: Vec2);
    fn mouse_button(&mut self, is_pressed: bool, is_left_button: bool);
    fn key(&mut self, event: KeyEvent);

    fn window_event(&mut self, event: winit::event::WindowEvent) {
        match event {
            winit::event::WindowEvent::MouseWheel { delta, .. } => {
                let delta = match delta {
                    winit::event::MouseScrollDelta::LineDelta(_, up) => up,
                    winit::event::MouseScrollDelta::PixelDelta(pos) => pos.y as f32,
                };

                self.mouse_scroll(delta);
            }
            winit::event::WindowEvent::CursorMoved { position, .. } => {
                self.mouse_moved(Vec2::new(position.x as f32, position.y as f32));
            }
            winit::event::WindowEvent::MouseInput { state, button, .. } => {
                let is_pressed = matches!(state, winit::event::ElementState::Pressed);
                let is_left_button = matches!(button, winit::event::MouseButton::Left);
                self.mouse_button(is_pressed, is_left_button);
            }
            winit::event::WindowEvent::KeyboardInput {
                device_id: _,
                event,
                is_synthetic: _,
            } => {
                self.key(event);
            }

            _ => {}
        }
    }

    fn device_event(&mut self, event: winit::event::DeviceEvent) {
        if let winit::event::DeviceEvent::MouseMotion { delta } = event {
            self.mouse_motion(Vec2::new(delta.0 as f32, delta.1 as f32))
        }
    }
}


================================================
FILE: crates/example/src/lib.rs
================================================
//! Runs through all the gltf sample models to test and show-off renderling's
//! gltf capabilities.
use std::{
    collections::{HashMap, HashSet},
    sync::{Arc, Mutex},
};

use glam::{Mat4, UVec2, Vec2, Vec3, Vec4};
use renderling::{
    atlas::AtlasImage,
    bvol::{Aabb, BoundingSphere},
    camera::Camera,
    context::Context,
    geometry::Vertex,
    glam,
    gltf::{Animator, GltfDocument},
    light::{AnalyticalLight, Lux},
    primitive::Primitive,
    skybox::Skybox,
    stage::Stage,
};
use renderling_ui::{FontArc, Section, Text, UiRect, UiRenderer, UiText};

pub mod camera;
use camera::{
    CameraControl, CameraController, TurntableCameraController, WasdMouseCameraController,
};

pub mod time;
use time::FPSCounter;

pub mod utils;

const FONT_BYTES: &[u8] =
    include_bytes!("../../../fonts/Recursive Mn Lnr St Med Nerd Font Complete.ttf");

const DARK_BLUE_BG_COLOR: Vec4 = Vec4::new(
    0x30 as f32 / 255.0,
    0x35 as f32 / 255.0,
    0x42 as f32 / 255.0,
    1.0,
);

pub enum SupportedFileType {
    Gltf,
    Hdr,
}

pub fn is_file_supported(file: impl AsRef<std::path::Path>) -> Option<SupportedFileType> {
    let ext = file.as_ref().extension()?;
    Some(match ext.to_str()? {
        "hdr" => SupportedFileType::Hdr,
        _ => SupportedFileType::Gltf,
    })
}

#[cfg(not(target_arch = "wasm32"))]
lazy_static::lazy_static! {
    static ref START: std::time::Instant = std::time::Instant::now();
}

fn now() -> f64 {
    #[cfg(target_arch = "wasm32")]
    {
        let doc = web_sys::window().unwrap();
        let perf = doc.performance().unwrap();
        perf.now()
    }
    #[cfg(not(target_arch = "wasm32"))]
    {
        let now = std::time::Instant::now();
        let duration = now.duration_since(*START);
        duration.as_secs_f64()
    }
}

struct AppUi {
    ui: UiRenderer,
    fps_text: UiText,
    fps_counter: FPSCounter,
    fps_background: UiRect,
    last_fps_display: f64,
}

impl AppUi {
    fn make_fps_widget(fps_counter: &FPSCounter, ui: &mut UiRenderer) -> (UiText, UiRect) {
        let offset = Vec2::new(2.0, 2.0);
        let text = format!("{}fps", fps_counter.current_fps_string());
        let fps_text = ui.add_text(
            Section::default()
                .add_text(
                    Text::new(&text)
                        .with_scale(32.0)
                        .with_color([0.0, 0.0, 0.0, 1.0]),
                )
                .with_screen_position((offset.x, offset.y)),
        );
        let (min, max) = fps_text.bounds();
        let size = max - min;
        let background = ui
            .add_rect()
            .with_position(min)
            .with_size(size)
            .with_fill_color(Vec4::ONE)
            .with_z(-0.9);
        (fps_text, background)
    }

    fn tick(&mut self) {
        self.fps_counter.next_frame();
        let now = now();
        if now - self.last_fps_display >= 1.0 {
            // Remove old text and background before recreating.
            self.ui.remove_text(&self.fps_text);
            self.ui.remove_rect(&self.fps_background);
            let (fps_text, background) = Self::make_fps_widget(&self.fps_counter, &mut self.ui);
            self.fps_text = fps_text;
            self.fps_background = background;
            self.last_fps_display = now;
        }
    }
}

pub enum Model {
    Gltf(Box<GltfDocument>),
    Default(Primitive),
    None,
}

pub struct App {
    last_frame_instant: f64,
    skybox_image_bytes: Option<Vec<u8>>,
    loads: Arc<Mutex<HashMap<std::path::PathBuf, Vec<u8>>>>,
    pub stage: Stage,
    camera: Camera,
    _lighting: AnalyticalLight,
    model: Model,
    animators: Option<Vec<Animator>>,
    animations_conflict: bool,
    pub camera_controller: Box<dyn CameraController + 'static>,
    ui: AppUi,
}

impl App {
    pub fn new(ctx: &Context, camera_control: CameraControl) -> Self {
        let stage = ctx
            .new_stage()
            .with_background_color(DARK_BLUE_BG_COLOR)
            .with_bloom_mix_strength(0.5)
            .with_bloom_filter_radius(4.0)
            .with_msaa_sample_count(4);
        let size = ctx.get_size();
        let (proj, view) = renderling::camera::default_perspective(size.x as f32, size.y as f32);
        let camera = stage.new_camera().with_projection_and_view(proj, view);

        let sunlight = stage
            .new_directional_light()
            .with_direction(Vec3::NEG_Y)
            .with_color(renderling::math::hex_to_vec4(0xFDFBD3FF))
            .with_intensity(Lux::OUTDOOR_SUNSET);

        stage
            .set_atlas_size(wgpu::Extent3d {
                width: 2048,
                height: 2048,
                depth_or_array_layers: 32,
            })
            .unwrap();

        let mut ui = UiRenderer::new(ctx);
        let _ = ui.add_font(FontArc::try_from_slice(FONT_BYTES).unwrap());
        let fps_counter = FPSCounter::default();
        let (fps_text, fps_background) = AppUi::make_fps_widget(&fps_counter, &mut ui);

        Self {
            ui: AppUi {
                ui,
                fps_text,
                fps_counter,
                fps_background,
                last_fps_display: now(),
            },
            stage,
            camera,
            _lighting: sunlight.into_generic(),
            model: Model::None,
            animators: None,
            animations_conflict: false,

            skybox_image_bytes: None,
            loads: Arc::new(Mutex::new(HashMap::default())),
            last_frame_instant: now(),

            camera_controller: match camera_control {
                CameraControl::Turntable => Box::new(TurntableCameraController::default()),
                CameraControl::WasdMouse => Box::new(WasdMouseCameraController::default()),
            },
        }
    }

    pub fn tick(&mut self) {
        self.camera_controller.tick();
        self.tick_loads();
        self.update_view();
        self.animate();
        self.ui.tick();
    }

    pub fn render(&mut self, ctx: &Context) {
        log::info!("render");
        let frame = ctx.get_next_frame().unwrap();
        self.stage.render(&frame.view());
        self.ui.ui.render(&frame.view());
        frame.present();
        log::info!("render done");
    }

    pub fn update_view(&mut self) {
        self.camera_controller
            .update_camera(self.stage.get_size(), &self.camera);
    }

    pub fn load_hdr_skybox(&mut self, bytes: Vec<u8>) {
        log::info!("loading skybox");
        let img = AtlasImage::from_hdr_bytes(&bytes).unwrap();
        let skybox = Skybox::new(self.stage.runtime(), img);
        self.skybox_image_bytes = Some(bytes);
        self.stage.use_skybox(&skybox);
        let ibl = self.stage.new_ibl(&skybox);
        self.stage.use_ibl(&ibl);
    }

    fn set_model(&mut self, model: Model) {
        match std::mem::replace(&mut self.model, model) {
            Model::Gltf(gltf_document) => {
                // Remove all the things that was loaded by the document
                for prim in gltf_document.primitives.values().flatten() {
                    self.stage.remove_primitive(prim);
                }
                for light in gltf_document.lights.iter() {
                    self.stage.remove_light(light);
                }
            }
            Model::Default(primitive) => {
                self.stage.remove_primitive(&primitive);
            }
            Model::None => {}
        }
    }

    pub fn load_default_model(&mut self) {
        log::info!("loading default model");
        let mut min = Vec3::splat(f32::INFINITY);
        let mut max = Vec3::splat(f32::NEG_INFINITY);

        self.last_frame_instant = now();
        let vertices = self
            .stage
            .new_vertices(renderling::math::unit_cube().into_iter().map(|(p, n)| {
                let p = p * 2.0;
                min = min.min(p);
                max = max.max(p);
                Vertex::default()
                    .with_position(p)
                    .with_normal(n)
                    .with_color(Vec4::new(1.0, 0.0, 0.0, 1.0))
            }));
        let primitive = self
            .stage
            .new_primitive()
            .with_vertices(vertices)
            .with_bounds({
                log::info!("default model bounds: {min} {max}");
                BoundingSphere::from((min, max))
            });

        self.set_model(Model::Default(primitive));

        self.camera_controller.reset(Aabb::new(min, max));
        self.camera_controller
            .update_camera(self.stage.get_size(), &self.camera);
    }

    fn load_gltf_model(&mut self, path: impl AsRef<std::path::Path>, bytes: &[u8]) {
        log::info!("loading gltf");
        self.camera_controller
            .reset(Aabb::new(Vec3::NEG_ONE, Vec3::ONE));
        self.stage.clear_images().unwrap();
        let doc = match self.stage.load_gltf_document_from_bytes(bytes) {
            Err(e) => {
                log::error!("gltf loading error: {e}");
                if cfg!(not(target_arch = "wasm32")) {
                    log::info!("attempting to load by filesystem");
                    match self.stage.load_gltf_document_from_path(path) {
                        Ok(doc) => doc,
                        Err(e) => {
                            log::error!("gltf loading error: {e}");
                            return;
                        }
                    }
                } else {
                    return;
                }
            }
            Ok(doc) => doc,
        };

        // find the bounding box of the model so we can display it correctly
        let mut min = Vec3::splat(f32::INFINITY);
        let mut max = Vec3::splat(f32::NEG_INFINITY);

        let scene = doc.default_scene.unwrap_or(0);
        log::info!("Displaying scene {scene}");

        let nodes = doc.recursive_nodes_in_scene(scene);
        log::trace!("  nodes:");
        for node in nodes {
            let tfrm = Mat4::from(node.global_transform());
            if let Some(mesh_index) = node.mesh {
                // UNWRAP: safe because we know the node exists
                for primitive in doc.meshes.get(mesh_index).unwrap().primitives.iter() {
                    let bbmin = tfrm.transform_point3(primitive.bounding_box.0);
                    let bbmax = tfrm.transform_point3(primitive.bounding_box.1);
                    min = min.min(bbmin);
                    max = max.max(bbmax);
                }
            }
        }

        log::trace!("Bounding box: {min} {max}");
        let bounding_box = Aabb::new(min, max);
        self.camera_controller.reset(bounding_box);
        self.camera_controller
            .update_camera(self.stage.get_size(), &self.camera);

        self.last_frame_instant = now();

        if doc.animations.is_empty() {
            log::trace!("  animations: none");
        } else {
            log::trace!("  animations:");
        }
        let mut animated_nodes = HashSet::default();
        let mut has_conflicting_animations = false;
        self.animators = Some(
            doc.animations
                .iter()
                .enumerate()
                .map(|(i, a)| {
                    let target_nodes = a.target_node_indices().collect::<HashSet<_>>();
                    has_conflicting_animations =
                        has_conflicting_animations || !animated_nodes.is_disjoint(&target_nodes);
                    animated_nodes.extend(target_nodes);

                    log::trace!("    {i} {:?} {}s", a.name, a.length_in_seconds());
                    // for (t, tween) in a.tweens.iter().enumerate() {
                    //     log::trace!(
                    //         "      tween {t} targets node {} {}",
                    //         tween.target_node_index,
                    //         tween.properties.description()
                    //     );
                    // }
                    Animator::new(doc.nodes.iter(), a.clone())
                })
                .collect(),
        );
        if has_conflicting_animations {
            log::trace!("  and some animations conflict");
        }
        self.animations_conflict = has_conflicting_animations;

        // // Update lights and shadows
        // for light in doc.lights.iter() {
        //     if let Some(dir) = light.details.as_directional() {
        //         log::info!("found a directional light to use for shadows");
        //         {
        //             let (p, j) = dir.get().shadow_mapping_projection_and_view(
        //                 &light.node_transform.get_global_transform().into(),
        //                 &self.camera.get(),
        //             );
        //             let mut guard = self.lighting.shadow_map.descriptor_lock();
        //             guard.light_space_transform = p * j;
        //         }

        //         self.lighting
        //             .shadow_map
        //             .update(&self.lighting.lighting,
        // doc.primitives.values().flatten());         self.lighting.light =
        // light.light.clone();         self.lighting.light_details =
        // dir.clone();     }
        // }

        self.set_model(Model::Gltf(Box::new(doc)));
    }

    pub fn tick_loads(&mut self) {
        let loaded = std::mem::take(&mut *self.loads.lock().unwrap());
        for (path, bytes) in loaded.into_iter() {
            log::info!("loaded {}bytes from {}", bytes.len(), path.display());
            match is_file_supported(&path) {
                Some(SupportedFileType::Gltf) => self.load_gltf_model(path, &bytes),
                Some(SupportedFileType::Hdr) => self.load_hdr_skybox(bytes),
                None => {}
            }
        }
    }

    /// Queues a load operation.
    pub fn load(&mut self, path: &str) {
        let path = std::path::PathBuf::from(path);
        let loads = self.loads.clone();

        #[cfg(target_arch = "wasm32")]
        {
            wasm_bindgen_futures::spawn_local(async move {
                let path_str = format!("{}", path.display());
                let bytes = loading_bytes::load(&path_str).await.unwrap();
                let mut loads = loads.lock().unwrap();
                loads.insert(path, bytes);
                log::debug!("loaded {path_str}");
            });
        }
        #[cfg(not(target_arch = "wasm32"))]
        {
            let _ = std::thread::spawn(move || {
                let bytes = std::fs::read(&path)
                    .unwrap_or_else(|e| panic!("could not load '{}': {e}", path.display()));
                let mut loads = loads.lock().unwrap();
                loads.insert(path, bytes);
            });
        }
    }

    pub fn set_size(&mut self, size: UVec2) {
        self.stage.set_size(size);
    }

    pub fn animate(&mut self) {
        let now = now();
        let dt_seconds = now - self.last_frame_instant;
        self.last_frame_instant = now;
        self.camera_controller.tick();
        if let Some(animators) = self.animators.as_mut() {
            if self.animations_conflict {
                if let Some(animator) = animators.first_mut() {
                    animator.progress(dt_seconds as f32).unwrap();
                }
            } else {
                for animator in animators.iter_mut() {
                    animator.progress(dt_seconds as f32).unwrap();
                }
            }
        }
    }
}

// /// Sets up the demo for a given model
// pub async fn demo(
//     ctx: &Context,
//     model: Option<impl AsRef<str>>,
//     skybox: Option<impl AsRef<str>>,
// ) -> impl FnMut(&mut Context, Option<&winit::event::WindowEvent>) {
//     let mut app = App::new(ctx).await;
//     if let Some(file) = model {
//         app.load(file.as_ref());
//     }
//     if let Some(file) = skybox {
//         app.load(file.as_ref());
//     }
//     let mut event_state = renderling_gpui::EventState::default();
//     move |r, ev: Option<&winit::event::WindowEvent>| {
//         if let Some(ev) = ev {
//             match ev {
//                 winit::event::WindowEvent::MouseWheel { delta, .. } => {
//                     let delta = match delta {
//                         winit::event::MouseScrollDelta::LineDelta(_, up) =>
// *up,                         winit::event::MouseScrollDelta::PixelDelta(pos)
// => pos.y as f32,                     };

//                     app.zoom(r, delta);
//                 }
//                 winit::event::WindowEvent::CursorMoved { position, .. } => {
//                     app.pan(r, *position);
//                 }
//                 winit::event::WindowEvent::MouseInput { state, button, .. }
// => {                     app.mouse_button(*state, *button);
//                 }
//                 winit::event::WindowEvent::KeyboardInput { input, .. } => {
//                     app.key_input(r, *input);
//                 }
//                 winit::event::WindowEvent::Resized(size) => {
//                     log::trace!("resizing to {size:?}");
//                     app.resize(r, size.width, size.height);
//                     let _ = app
//                         .gpui
//                         .0
//                         .graph
//                         .get_resource::<ScreenSize>()
//                         .unwrap()
//                         .unwrap();
//                 }
//                 winit::event::WindowEvent::DroppedFile(path) => {
//                     log::trace!("got dropped file event: {}",
// path.display());                     let path = format!("{}",
// path.display());                     app.load(&path);
//                 }
//                 _ => {}
//             }

//             if let Some(ev) = event_state.event_from_winit(ev) {
//                 let scene =
// r.graph.get_resource_mut::<Scene>().unwrap().unwrap();                 let
// channel = scene.get_debug_channel();                 let mut
// set_debug_channel = |mode| {                     log::debug!("setting debug
// mode to {mode:?}");                     if channel != mode {
//                         scene.set_debug_channel(mode);
//                     } else {
//                         scene.set_debug_channel(DebugChannel::None);
//                     }
//                 };

//                 match app.ui.event(ev) {
//                     None => {}
//                     Some(ev) => match ev {
//                         UiEvent::ToggleDebugChannel(channel) =>
// set_debug_channel(channel),                     },
//                 }
//             }
//         } else {
//             app.tick_loads(r);
//             app.update_camera_view(r);
//             app.animate(r);
//             app.gpui.layout(&mut app.ui);
//             app.gpui.render(&mut app.ui);
//             r.render().unwrap();
//         }
//     }
// }


================================================
FILE: crates/example/src/main.rs
================================================
//! Main entry point for the gltf viewer.
use std::sync::Arc;

use clap::Parser;
use example::{camera::CameraControl, App};
use renderling::{
    context::Context,
    glam::{UVec2, Vec2},
};
use winit::{application::ApplicationHandler, event::WindowEvent, window::WindowAttributes};

#[derive(Debug, Parser)]
#[command(author, version, about)]
struct Cli {
    /// Optional gltf model to load at startup
    #[arg(short, long)]
    model: Option<String>,

    /// Optional HDR image to use as skybox at startup
    #[arg(short, long)]
    skybox: Option<String>,

    /// Camera control scheme
    #[arg(short, long, default_value = "turntable")]
    camera_control: CameraControl,
    // /// Optional number of repeat instances of the same model
    // #[arg(short, long)]
    // repeat_n: Option<u32>,
}

struct InnerApp {
    ctx: Context,
    app: App,
}

impl InnerApp {
    fn tick(&mut self) {
        self.app.tick();
    }

    fn event(&mut self, event: WindowEvent) -> bool {
        match event {
            winit::event::WindowEvent::DroppedFile(path) => {
                log::trace!("got dropped file event: {}", path.display());
                let path = format!("{}", path.display());
                self.app.load(&path);
            }

            winit::event::WindowEvent::CloseRequested
            | winit::event::WindowEvent::KeyboardInput {
                event:
                    winit::event::KeyEvent {
                        logical_key: winit::keyboard::Key::Named(winit::keyboard::NamedKey::Escape),
                        ..
                    },
                ..
            } => return true,
            winit::event::WindowEvent::Resized(size) => {
                let size = UVec2::new(size.width, size.height);
                self.ctx.set_size(size);
                self.app.set_size(size);
            }
            winit::event::WindowEvent::RedrawRequested => {
                self.ctx.get_device().poll(wgpu::PollType::Wait).unwrap();
            }
            e => self.app.camera_controller.window_event(e),
        }
        false
    }
}

struct OuterApp {
    cli: Cli,
    inner: Option<InnerApp>,
}

impl ApplicationHandler for OuterApp {
    fn resumed(&mut self, event_loop: &winit::event_loop::ActiveEventLoop) {
        let window_size = winit::dpi::LogicalSize {
            width: 800,
            height: 600,
        };
        let attributes = WindowAttributes::default()
            .with_inner_size(window_size)
            .with_title("renderling gltf viewer");
        let window = Arc::new(event_loop.create_window(attributes).unwrap());

        // Set up a new renderling context
        let ctx = futures_lite::future::block_on(Context::from_winit_window(None, window.clone()));
        let mut app = App::new(&ctx, self.cli.camera_control);
        if let Some(file) = self.cli.model.as_ref() {
            log::info!("loading model '{file}'");
            app.load(file.as_ref());
        } else {
            log::info!("loading default model");
            app.load_default_model();
        }
        if let Some(file) = self.cli.skybox.as_ref() {
            log::info!("loading skybox '{file}'");
            app.load(file.as_ref());
        }
        self.inner = Some(InnerApp { ctx, app });
    }

    fn about_to_wait(&mut self, _event_loop: &winit::event_loop::ActiveEventLoop) {
        if let Some(inner) = self.inner.as_mut() {
            inner.tick();
            inner.app.render(&inner.ctx);
        }
    }

    fn window_event(
        &mut self,
        event_loop: &winit::event_loop::ActiveEventLoop,
        _window_id: winit::window::WindowId,
        event: winit::event::WindowEvent,
    ) {
        if let Some(inner) = self.inner.as_mut() {
            if inner.event(event) {
                event_loop.exit();
            }
        }
    }

    fn device_event(
        &mut self,
        _event_loop: &winit::event_loop::ActiveEventLoop,
        _device_id: winit::event::DeviceId,
        event: winit::event::DeviceEvent,
    ) {
        if let Some(inner) = self.inner.as_mut() {
            if let winit::event::DeviceEvent::MouseMotion { delta } = event {
                inner
                    .app
                    .camera_controller
                    .mouse_motion(Vec2::new(delta.0 as f32, delta.1 as f32))
            }
        }
    }
}

fn main() {
    let cli = Cli::parse();
    env_logger::builder().init();
    log::info!("starting up with options: {cli:#?}");

    let event_loop = winit::event_loop::EventLoop::new().unwrap();
    event_loop.set_control_flow(winit::event_loop::ControlFlow::Poll);
    let mut outer_app = OuterApp { cli, inner: None };
    event_loop.run_app(&mut outer_app).unwrap();
}


================================================
FILE: crates/example/src/time.rs
================================================
//! Time constructs.
//!
//! Because Instant::now() doesn't work on arch = wasm32.
pub use std::time::Duration;
#[cfg(not(target_arch = "wasm32"))]
pub use std::time::Instant;
#[cfg(target_arch = "wasm32")]
use web_sys::window;

#[derive(Clone, Copy, Debug)]
pub struct Time {
    #[cfg(target_arch = "wasm32")]
    time: u32,

    #[cfg(not(target_arch = "wasm32"))]
    time: Instant,
}

impl Time {
    #[cfg(not(target_arch = "wasm32"))]
    pub fn now() -> Self {
        Time {
            time: Instant::now(),
        }
    }

    #[cfg(target_arch = "wasm32")]
    pub fn now() -> Self {
        Time {
            time: window().unwrap().performance().unwrap().now() as u32,
        }
    }

    #[cfg(not(target_arch = "wasm32"))]
    pub fn millis_since(&self, then: Time) -> u32 {
        self.time.duration_since(then.time).as_millis() as u32
    }

    #[cfg(target_arch = "wasm32")]
    pub fn millis_since(&self, then: Time) -> u32 {
        self.time - then.time
    }
}

pub const FPS_COUNTER_BUFFER_SIZE: usize = 60;

pub struct CounterBuffer<T> {
    buffer: [T; FPS_COUNTER_BUFFER_SIZE],
    index: usize,
}

impl CounterBuffer<f32> {
    pub fn new(init: f32) -> Self {
        CounterBuffer {
            buffer: [init; FPS_COUNTER_BUFFER_SIZE],
            index: 0,
        }
    }

    pub fn write(&mut self, val: f32) {
        self.buffer[self.index] = val;
        self.index = (self.index + 1) % self.buffer.len();
    }

    pub fn average(&self) -> f32 {
        self.buffer.iter().fold(0.0, |sum, dt| sum + dt) / self.buffer.len() as f32
    }

    pub fn current(&self) -> f32 {
        let last_index = if self.index == 0 {
            self.buffer.len() - 1
        } else {
            self.index - 1
        };
        self.buffer[last_index]
    }

    pub fn frames(&self) -> &[f32; FPS_COUNTER_BUFFER_SIZE] {
        &self.buffer
    }
}

pub struct FPSCounter {
    counter: CounterBuffer<f32>,
    last_instant: Time,
    last_dt: f32,
    averages: CounterBuffer<f32>,
}

impl Default for FPSCounter {
    fn default() -> Self {
        FPSCounter::new()
    }
}

impl FPSCounter {
    pub fn new() -> FPSCounter {
        FPSCounter {
            counter: CounterBuffer::new(0.0),
            last_instant: Time::now(),
            last_dt: 0.0,
            averages: CounterBuffer::new(0.0),
        }
    }

    pub fn restart(&mut self) {
        self.last_instant = Time::now();
    }

    pub fn next_frame(&mut self) -> f32 {
        let this_instant = Time::now();
        let delta = this_instant.millis_since(self.last_instant);
        let dt_seconds = delta as f32 / 1000.0;
        self.last_dt = dt_seconds;
        self.last_instant = this_instant;
        self.counter.write(dt_seconds);
        if self.counter.index + 1 == FPS_COUNTER_BUFFER_SIZE {
            let avg = self.counter.average();
            self.averages.write(avg);
        }
        dt_seconds
    }

    pub fn avg_frame_delta(&self) -> f32 {
        self.counter.average()
    }

    pub fn current_fps(&self) -> f32 {
        1.0 / self.avg_frame_delta()
    }

    pub fn current_fps_string(&self) -> String {
        let avg = self.averages.current();
        format!("{:.1}", 1.0 / avg)
    }

    /// Return the last frame's delta in seconds.
    pub fn last_delta(&self) -> f32 {
        self.last_dt
    }

    pub fn second_averages(&self) -> &[f32; FPS_COUNTER_BUFFER_SIZE] {
        self.averages.frames()
    }
}


================================================
FILE: crates/example/src/utils.rs
================================================
//! Example app utilities.

use std::sync::Arc;

use renderling::context::Context;
use winit::monitor::MonitorHandle;

pub trait TestAppHandler: winit::application::ApplicationHandler {
    fn new(
        event_loop: &winit::event_loop::ActiveEventLoop,
        window: Arc<winit::window::Window>,
        ctx: &Context,
    ) -> Self;
    fn render(&mut self, ctx: &Context);
}

pub(crate) struct InnerTestApp<T> {
    ctx: Context,
    app: T,
}

pub struct TestApp<T> {
    size: winit::dpi::Size,
    inner: Option<InnerTestApp<T>>,
}

impl<T: TestAppHandler> winit::application::ApplicationHandler for TestApp<T> {
    fn resumed(&mut self, event_loop: &winit::event_loop::ActiveEventLoop) {
        // I have my editor on the high monitor, and I read on the low one,
        // so set the position of the opened window to the lowest monitor so
        // it doesn't constantly switch away from my editor...
        let maybe_lowest_monitor =
            event_loop
                .available_monitors()
                .fold(None::<MonitorHandle>, |mut acc, monitor| {
                    let position = monitor.position();
                    let name = monitor.name().unwrap_or("unknown".to_owned());
                    log::info!("found monitor: {name} {position:?}");
                    if let Some(current_monitor) = acc.as_mut() {
                        // greater than here because y increases downward
                        if monitor.position().y > current_monitor.position().y {
                            acc = Some(monitor);
                        }
                    } else {
                        acc = Some(monitor);
                    }
                    acc
                });

        let window = Arc::new(
            event_loop
                .create_window(
                    winit::window::WindowAttributes::default()
                        .with_inner_size(self.size)
                        .with_title("test app")
                        .with_fullscreen(
                            maybe_lowest_monitor
                                .map(|m| winit::window::Fullscreen::Borderless(Some(m))),
                        ),
                )
                .unwrap(),
        );
        let ctx = futures_lite::future::block_on(Context::from_winit_window(None, window.clone()));
        let mut app = T::new(event_loop, window, &ctx);
        app.resumed(event_loop);
        self.inner = Some(InnerTestApp { app, ctx });
    }

    fn window_event(
        &mut self,
        event_loop: &winit::event_loop::ActiveEventLoop,
        window_id: winit::window::WindowId,
        event: winit::event::WindowEvent,
    ) {
        if let Some(inner) = self.inner.as_mut() {
            match event {
                // winit::event::WindowEvent::RedrawRequested => {
                //     inner.ctx.get_device().poll(wgpu::Maintain::Wait);
                // }
                winit::event::WindowEvent::CloseRequested
                | winit::event::WindowEvent::KeyboardInput {
                    event:
                        winit::event::KeyEvent {
                            logical_key:
                                winit::keyboard::Key::Named(winit::keyboard::NamedKey::Escape),
                            ..
                        },
                    ..
                } => {
                    event_loop.exit();
                }
                _ => {
                    inner.app.window_event(event_loop, window_id, event);
                }
            }
        }
    }

    fn device_event(
        &mut self,
        event_loop: &winit::event_loop::ActiveEventLoop,
        device_id: winit::event::DeviceId,
        event: winit::event::DeviceEvent,
    ) {
        if let Some(inner) = self.inner.as_mut() {
            inner.app.device_event(event_loop, device_id, event);
        }
    }

    fn about_to_wait(&mut self, event_loop: &winit::event_loop::ActiveEventLoop) {
        if let Some(inner) = self.inner.as_mut() {
            inner.app.about_to_wait(event_loop);
            inner.app.render(&inner.ctx);
        }
    }
}

impl<T: TestAppHandler> TestApp<T> {
    pub fn new(size: impl Into<winit::dpi::Size>) -> Self {
        TestApp {
            size: size.into(),
            inner: None,
        }
    }

    pub fn run(&mut self) {
        let event_loop = winit::event_loop::EventLoop::new().unwrap();
        event_loop.set_control_flow(winit::event_loop::ControlFlow::Poll);
        event_loop.run_app(self).unwrap();
    }
}


================================================
FILE: crates/example-culling/Cargo.toml
================================================
[package]
name = "example-culling"
version = "0.1.0"
edition = "2021"

[dependencies]
env_logger.workspace = true
example = { path = "../example" }
fastrand = "2.1.1"
log.workspace = true
renderling = { path = "../renderling" }
winit.workspace = true


================================================
FILE: crates/example-culling/src/main.rs
================================================
//! An example app showing (and verifying) how frustum culling works in
//! `renderling`.
use std::sync::Arc;

use example::{camera::CameraController, utils::*};
use renderling::{
    bvol::{Aabb, BoundingSphere},
    camera::{shader::CameraDescriptor, Camera},
    context::Context,
    geometry::Vertex,
    glam::{EulerRot, Mat4, Quat, UVec2, Vec3, Vec4},
    light::{AnalyticalLight, DirectionalLight, Lux},
    material::Material,
    math::hex_to_vec4,
    primitive::Primitive,
    stage::Stage,
    tonemapping::srgba_to_linear,
};
use winit::{
    application::ApplicationHandler,
    event::{ElementState, KeyEvent},
    event_loop::ActiveEventLoop,
    keyboard::Key,
};

const MIN_SIZE: f32 = 0.5;
const MAX_SIZE: f32 = 4.0;
const MAX_DIST: f32 = 40.0;
const BOUNDS: Aabb = Aabb {
    min: Vec3::new(-MAX_DIST, -MAX_DIST, -MAX_DIST),
    max: Vec3::new(MAX_DIST, MAX_DIST, MAX_DIST),
};

struct AppCamera(Camera);
struct FrustumCamera(CameraDescriptor);

type Type = Primitive;

#[allow(dead_code)]
struct CullingExample {
    app_camera: AppCamera,
    controller: example::camera::TurntableCameraController,
    stage: Stage,
    dlights: [AnalyticalLight<DirectionalLight>; 2],
    frustum_camera: FrustumCamera,
    frustum_primitive: Primitive,
    material_aabb_outside: Material,
    material_aabb_overlapping: Material,
    primitives: Vec<Type>,
    next_k: u64,
}

impl CullingExample {
    fn make_aabb(center: Vec3, half_size: Vec3) -> Aabb {
        let min = center - half_size;
        let max = center + half_size;
        Aabb::new(min, max)
    }

    fn make_aabbs(
        seed: u64,
        stage: &Stage,
        frustum_camera: &FrustumCamera,
        material_outside: &Material,
        material_overlapping: &Material,
    ) -> Vec<Primitive> {
        log::info!("generating aabbs with seed {seed}");
        fastrand::seed(seed);
        (0..25u32)
            .map(|i| {
                log::info!("aabb {i}");
                let x = fastrand::f32() * MAX_DIST - MAX_DIST / 2.0;
                let y = fastrand::f32() * MAX_DIST - MAX_DIST / 2.0;
                let z = fastrand::f32() * MAX_DIST - MAX_DIST / 2.0;
                let w = fastrand::f32() * (MAX_SIZE - MIN_SIZE) + MIN_SIZE;
                let h = fastrand::f32() * (MAX_SIZE - MIN_SIZE) + MIN_SIZE;
                let l = fastrand::f32() * (MAX_SIZE - MIN_SIZE) + MIN_SIZE;

                let rx = std::f32::consts::PI * fastrand::f32();
                let ry = std::f32::consts::PI * fastrand::f32();
                let rz = std::f32::consts::PI * fastrand::f32();

                let rotation = Quat::from_euler(EulerRot::XYZ, rx, ry, rz);

                let center = Vec3::new(x, y, z);
                let half_size = Vec3::new(w, h, l);
                let aabb = Self::make_aabb(Vec3::ZERO, half_size);

                let transform = stage
                    .new_transform()
                    .with_translation(center)
                    .with_rotation(rotation);
                stage
                    .new_primitive()
                    .with_vertices(stage.new_vertices(aabb.get_mesh().into_iter().map(
                        |(position, normal)| Vertex {
                            position,
                            normal,
                            ..Default::default()
                        },
                    )))
                    .with_material(
                        if BoundingSphere::from(aabb)
                            .is_inside_camera_view(&frustum_camera.0, transform.descriptor())
                            .0
                        {
                            material_overlapping
                        } else {
                            material_outside
                        },
                    )
                    .with_transform(transform)
            })
            .collect::<Vec<_>>()
    }
}

impl ApplicationHandler for CullingExample {
    fn resumed(&mut self, _event_loop: &winit::event_loop::ActiveEventLoop) {
        log::info!("culling-example resumed");
    }

    fn window_event(
        &mut self,
        _event_loop: &winit::event_loop::ActiveEventLoop,
        _window_id: winit::window::WindowId,
        event: winit::event::WindowEvent,
    ) {
        match event {
            winit::event::WindowEvent::KeyboardInput {
                event:
                    KeyEvent {
                        logical_key: Key::Character(c),
                        state: ElementState::Pressed,
                        ..
                    },
                ..
            } => {
                if c.as_str() == "r" {
                    // remove all the primitives, dropping their resources
                    for primitive in self.primitives.drain(..) {
                        self.stage.remove_primitive(&primitive);
                    }

                    let _ = self.stage.commit();
                    self.primitives.extend(Self::make_aabbs(
                        self.next_k,
                        &self.stage,
                        &self.frustum_camera,
                        &self.material_aabb_outside,
                        &self.material_aabb_overlapping,
                    ));
                    self.next_k += 1;
                }
            }
            winit::event::WindowEvent::Resized(physical_size) => {
                log::info!("window resized to {physical_size:?}");
                let size = UVec2 {
                    x: physical_size.width,
                    y: physical_size.height,
                };
                self.stage.set_size(size);
                self.controller.update_camera(size, &self.app_camera.0);
            }
            event => self.controller.window_event(event),
        }
    }

    fn device_event(
        &mut self,
        _event_loop: &winit::event_loop::ActiveEventLoop,
        _device_id: winit::event::DeviceId,
        event: winit::event::DeviceEvent,
    ) {
        self.controller.device_event(event);
    }
}

impl TestAppHandler for CullingExample {
    fn new(
        _event_loop: &ActiveEventLoop,
        _window: Arc<winit::window::Window>,
        ctx: &Context,
    ) -> Self {
        let mut seed = 46;
        let mut prims = vec![];
        let stage = ctx.new_stage().with_lighting(true);
        let sunlight_a = stage
            .new_directional_light()
            .with_direction(Vec3::new(-0.8, -1.0, 0.5).normalize())
            .with_color(Vec4::ONE)
            .with_intensity(Lux::OUTDOOR_SUNSET);
        let sunlight_b = stage
            .new_directional_light()
            .with_direction(Vec3::new(1.0, 1.0, -0.1).normalize())
            .with_color(Vec4::ONE)
            .with_intensity(Lux::OUTDOOR_TWILIGHT);

        let dlights = [sunlight_a, sunlight_b];

        let frustum_camera = FrustumCamera({
            let aspect = 1.0;
            let fovy = core::f32::consts::FRAC_PI_4;
            let znear = 4.0;
            let zfar = 1000.0;
            let projection = Mat4::perspective_rh(fovy, aspect, znear, zfar);
            let eye = Vec3::new(0.0, 0.0, 10.0);
            let target = Vec3::ZERO;
            let up = Vec3::Y;
            let view = Mat4::look_at_rh(eye, target, up);
            // let projection = Mat4::orthographic_rh(-10.0, 10.0, -10.0, 10.0, -10.0,
            // 10.0); let view = Mat4::IDENTITY;
            CameraDescriptor::new(projection, view)
        });

        let frustum = frustum_camera.0.frustum();
        log::info!("frustum.planes: {:#?}", frustum.planes);

        let blue_color = srgba_to_linear(hex_to_vec4(0x7EACB5FF));
        let red_color = srgba_to_linear(hex_to_vec4(0xC96868FF));
        let yellow_color = srgba_to_linear(hex_to_vec4(0xFADFA1FF));

        let material_aabb_overlapping = stage.new_material().with_albedo_factor(blue_color);
        let material_aabb_outside = stage.new_material().with_albedo_factor(red_color);
        let material_frustum = stage.new_material().with_albedo_factor(yellow_color);
        let app_camera = AppCamera(stage.new_camera());
        prims.extend(Self::make_aabbs(
            seed,
            &stage,
            &frustum_camera,
            &material_aabb_outside,
            &material_aabb_overlapping,
        ));
        seed += 1;

        let frustum_vertices =
            stage.new_vertices(frustum_camera.0.frustum().get_mesh().into_iter().map(
                |(position, normal)| Vertex {
                    position,
                    normal,
                    ..Default::default()
                },
            ));
        let frustum_prim = stage
            .new_primitive()
            .with_vertices(&frustum_vertices)
            .with_material(&material_frustum);
        stage.add_primitive(&frustum_prim);

        Self {
            next_k: seed,
            app_camera,
            frustum_camera,
            dlights,
            controller: {
                let mut c = example::camera::TurntableCameraController::default();
                c.reset(BOUNDS);
                c.phi = 0.5;
                c
            },
            stage,
            material_aabb_overlapping,
            material_aabb_outside,
            frustum_primitive: frustum_prim,
            primitives: prims,
        }
    }

    fn render(&mut self, ctx: &Context) {
        let size = self.stage.get_size();
        self.controller.update_camera(size, &self.app_camera.0);

        let frame = ctx.get_next_frame().unwrap();
        self.stage.render(&frame.view());
        frame.present();
    }
}

fn main() {
    env_logger::builder().init();
    log::info!("starting example-culling");
    TestApp::<CullingExample>::new(winit::dpi::LogicalSize::new(800, 600)).run();
}


================================================
FILE: crates/example-wasm/.gitignore
================================================
dist


================================================
FILE: crates/example-wasm/Cargo.toml
================================================
[package]
name = "example-wasm"
version = "0.1.0"
edition = "2021"

# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html

[lib]
crate-type = ["cdylib", "rlib"]

[dependencies]
console_log = "^0.2"
console_error_panic_hook = "^0.1"
example = { path = "../example" }
fern = "0.6"
futures-lite.workspace = true
gltf.workspace = true
log.workspace = true
renderling = { path = "../renderling", features = ["wasm", "winit"] }
wasm-bindgen.workspace = true
wasm-bindgen-futures.workspace = true
wasm-bindgen-test = "^0.3"
web-sys = { workspace = true, features = ["Document", "Element", "Event", "HtmlCanvasElement", "HtmlElement", "Window"] }
wgpu.workspace = true
winit.workspace = true


================================================
FILE: crates/example-wasm/Trunk.toml
================================================
target = "index.html"


================================================
FILE: crates/example-wasm/index.html
================================================
<!doctype html>
<html>
  <head>
    <style>
      /* A sane CSS reset from 
			 * https://www.digitalocean.com/community/tutorials/css-minimal-css-reset
			*/
      html {
        box-sizing: border-box;
        font-size: 16px;
      }

      *,
      *:before,
      *:after {
        box-sizing: inherit;
      }

      body,
      h1,
      h2,
      h3,
      h4,
      h5,
      h6,
      p,
      ol,
      ul {
        margin: 0;
        padding: 0;
        font-weight: normal;
      }

      ol,
      ul {
        list-style: none;
      }

      img {
        max-width: 100%;
        height: auto;
      }

      main {
        margin: 0;
        width: 100%;
        height: 100%;
        background-color: #7f7f7f;
      }

      main canvas {
        margin: 0;
        padding: 0;
        position: absolute;
        left: 0;
        right: 0;
        top: 0;
        bottom: 0;
        background-color: #ffffff;
      }

      section fieldset {
        margin: 0 0.25em 0.5em 0.25em;
      }
    </style>

    <link data-trunk rel="copy-file" href="../../gltf/Fox.glb" />
    <link data-trunk rel="copy-file" href="../../img/hdr/helipad.hdr" />
    <link data-trunk rel="copy-file" href="../../fonts/Recursive Mn Lnr St Med Nerd Font Complete.ttf" />
  </head>
  <body>
    <main>
      <canvas></canvas>
    </main>
  </body>
</html>


================================================
FILE: crates/example-wasm/src/event.rs
================================================
//! A light abstraction over UI event callbacks.
//!
//! This uses [`futures-lite::Stream`] to send events to downstream listeners.
use std::{
    pin::Pin,
    sync::{Arc, Mutex},
    task::Waker,
};

use futures_lite::Stream;
use wasm_bindgen::{prelude::Closure, JsCast, JsValue};
use web_sys::EventTarget;

type MaybeCallback = Option<Arc<Closure<dyn FnMut(JsValue)>>>;

struct WebCallback {
    target: EventTarget,
    name: String,
    closure: MaybeCallback,
    waker: Arc<Mutex<Option<Waker>>>,
    event: Arc<Mutex<Option<web_sys::Event>>>,
}

impl Drop for WebCallback {
    fn drop(&mut self) {
        if let Some(arc) = self.closure.take() {
            if let Ok(closure) = Arc::try_unwrap(arc) {
                self.target
                    .remove_event_listener_with_callback(
                        self.name.as_str(),
                        closure.as_ref().unchecked_ref(),
                    )
                    .unwrap();
            }
        }
    }
}

impl Stream for WebCallback {
    type Item = web_sys::Event;

    fn poll_next(
        self: Pin<&mut Self>,
        cx: &mut std::task::Context<'_>,
    ) -> std::task::Poll<Option<Self::Item>> {
        let data = self.get_mut();
        *data.waker.lock().unwrap() = Some(cx.waker().clone());

        if let Some(event) = data.event.lock().unwrap().take() {
            std::task::Poll::Ready(Some(event))
        } else {
            std::task::Poll::Pending
        }
    }
}

/// Listen for events of the given name on the given target.
/// All events will be sent downstream until the stream is
/// dropped.
pub fn event_stream(
    ev_name: &str,
    target: &web_sys::EventTarget,
) -> impl Stream<Item = web_sys::Event> {
    let waker: Arc<Mutex<Option<Waker>>> = Default::default();
    let waker_here = waker.clone();

    let event: Arc<Mutex<Option<web_sys::Event>>> = Default::default();
    let event_here = event.clone();

    let closure = Closure::wrap(Box::new(move |val: JsValue| {
        let ev = val.unchecked_into();
        *event.lock().unwrap() = Some(ev);
        if let Some(waker) = waker.lock().unwrap().take() {
            waker.wake()
        }
    }) as Box<dyn FnMut(JsValue)>);

    target
        .add_event_listener_with_callback(ev_name, closure.as_ref().unchecked_ref())
        .unwrap();

    WebCallback {
        target: target.clone(),
        name: ev_name.to_string(),
        closure: Some(closure.into()),
        event: event_here,
        waker: waker_here,
    }
}


================================================
FILE: crates/example-wasm/src/lib.rs
================================================
#![allow(dead_code)]
use glam::{Vec2, Vec4};
use renderling::{camera::Camera, gltf::GltfDocument, stage::Stage, ui::prelude::*};
use wasm_bindgen::prelude::*;
use web_sys::HtmlCanvasElement;

mod event;
mod req_animation_frame;

const HDR_IMAGE_BYTES: &[u8] = include_bytes!("../../../img/hdr/helipad.hdr");
const GLTF_FOX_BYTES: &[u8] = include_bytes!("../../../gltf/Fox.glb");

fn surface_from_canvas(_canvas: HtmlCanvasElement) -> Option<wgpu::SurfaceTarget<'static>> {
    #[cfg(target_arch = "wasm32")]
    {
        Some(wgpu::SurfaceTarget::Canvas(_canvas))
    }
    #[cfg(not(target_arch = "wasm32"))]
    {
        None
    }
}

pub struct App {
    ctx: Context,
    ui: Ui,
    path: UiPath,
    stage: Stage,
    doc: GltfDocument,
    camera: Camera,
    text: UiText,
}

impl App {
    fn tick(&self) {
        let frame = self.ctx.get_next_frame().unwrap();
        self.ui.render(&frame.view());
        self.stage.render(&frame.view());
        frame.present();
    }
}

#[wasm_bindgen(start)]
pub async fn main() {
    std::panic::set_hook(Box::new(console_error_panic_hook::hook));
    fern::Dispatch::new()
        .level(log::LevelFilter::Info)
        .level_for("wgpu", log::LevelFilter::Warn)
        .level_for("naga", log::LevelFilter::Trace)
        .level_for("renderling::draw", log::LevelFilter::Trace)
        .chain(fern::Output::call(console_log::log))
        .apply()
        .unwrap();

    log::info!("Starting example-wasm");

    let dom_window = web_sys::window().unwrap();
    let dom_doc = dom_window.document().unwrap();

    let canvas = dom_doc
        .query_selector("main canvas")
        .unwrap()
        .unwrap()
        .dyn_into::<HtmlCanvasElement>()
        .unwrap();
    canvas.set_width(800);
    canvas.set_height(600);

    let surface = surface_from_canvas(canvas.clone()).unwrap();
    let ctx = Context::try_new_with_surface(800, 600, None, surface)
        .await
        .unwrap();

    let ui = ctx.new_ui();
    let path = ui
        .path_builder()
        .with_circle(Vec2::splat(100.0), 20.0)
        .with_fill_color(Vec4::new(1.0, 1.0, 0.0, 1.0))
        .fill();
    let _ = ui
        .load_font("Recursive Mn Lnr St Med Nerd Font Complete.ttf")
        .await
        .expect_throw("Could not load font");
    let text = ui
        .text_builder()
        .with_color(
            // white
            Vec4::ONE,
        )
        .with_section(Section::default().add_text(Text::new("WASM example").with_scale(24.0)))
        .build();

    let stage = ctx
        .new_stage()
        .with_background_color(
            // black
            // Vec3::ZERO.extend(1.0),
            Vec4::new(1.0, 0.0, 0.0, 1.0),
        )
        .with_lighting(false);

    let skybox = stage.new_skybox_from_bytes(HDR_IMAGE_BYTES).unwrap();
    stage.set_skybox(skybox);

    let fox = stage.load_gltf_document_from_bytes(GLTF_FOX_BYTES).unwrap();
    log::info!("fox aabb: {:?}", fox.bounding_volume());

    let (p, v) = renderling::camera::default_perspective(800.0, 600.0);
    let camera = stage.new_camera().with_projection_and_view(p, v);

    let app = App {
        ctx,
        ui,
        path,
        stage,
        doc: fox,
        camera,
        text,
    };
    app.tick();

    loop {
        let _ = req_animation_frame::next_animation_frame().await;
        app.tick();
    }
}


================================================
FILE: crates/example-wasm/src/req_animation_frame.rs
================================================
//! Request animation frame helpers, taken from [mogwai](https://crates.io/crates/mogwai).
use std::{cell::RefCell, rc::Rc};

use wasm_bindgen::{prelude::Closure, JsCast, JsValue, UnwrapThrowExt};

fn req_animation_frame(f: &Closure<dyn FnMut(JsValue)>) {
    web_sys::window()
        .expect_throw("could not get window")
        .request_animation_frame(f.as_ref().unchecked_ref())
        .expect_throw("should register `requestAnimationFrame` OK");
}

/// Sets a static rust closure to be called with `window.requestAnimationFrame`.
///
/// The static rust closure takes one parameter which is
/// a timestamp representing the number of milliseconds since the application's
/// load. See <https://developer.mozilla.org/en-US/docs/Web/API/DOMHighResTimeStamp>
/// for more info.
fn request_animation_frame(mut f: impl FnMut(JsValue) + 'static) {
    let wrapper = Rc::new(RefCell::new(None));
    let callback = Box::new({
        let wrapper = wrapper.clone();
        move |jsval| {
            f(jsval);
            wrapper.borrow_mut().take();
        }
    }) as Box<dyn FnMut(JsValue)>;
    let closure: Closure<dyn FnMut(JsValue)> = Closure::wrap(callback);
    *wrapper.borrow_mut() = Some(closure);
    req_animation_frame(wrapper.borrow().as_ref().unwrap_throw());
}

#[derive(Clone, Default)]
#[expect(clippy::type_complexity, reason = "not too complex")]
struct NextFrame {
    closure: Rc<RefCell<Option<Closure<dyn FnMut(JsValue)>>>>,
    ts: Rc<RefCell<Option<f64>>>,
    waker: Rc<RefCell<Option<std::task::Waker>>>,
}

impl std::future::Future for NextFrame {
    type Output = f64;

    fn poll(
        self: std::pin::Pin<&mut Self>,
        cx: &mut std::task::Context<'_>,
    ) -> std::task::Poll<Self::Output> {
        if let Some(ts) = self.ts.borrow_mut().take() {
            std::task::Poll::Ready(ts)
        } else {
            *self.waker.borrow_mut() = Some(cx.waker().clone());
            std::task::Poll::Pending
        }
    }
}

/// Creates a future that will resolve on the next animation frame.
///
/// The future's output is a timestamp representing the number of
/// milliseconds since the application's load.
/// See <https://developer.mozilla.org/en-US/docs/Web/API/DOMHighResTimeStamp>
/// for more info.
pub fn next_animation_frame() -> impl std::future::Future<Output = f64> {
    // https://rustwasm.github.io/wasm-bindgen/examples/request-animation-frame.html#srclibrs
    let frame = NextFrame::default();

    *frame.closure.borrow_mut() = Some(Closure::wrap(Box::new({
        let frame = frame.clone();
        move |ts_val: JsValue| {
            *frame.ts.borrow_mut() = Some(ts_val.as_f64().unwrap_or(0.0));
            if let Some(waker) = frame.waker.borrow_mut().take() {
                waker.wake();
            }
        }
    }) as Box<dyn FnMut(JsValue)>));

    req_animation_frame(frame.closure.borrow().as_ref().unwrap_throw());

    frame
}


================================================
FILE: crates/examples/Cargo.toml
================================================
[package]
name = "examples"
version = "0.1.0"
edition = "2024"

[dependencies]
doc-comment = "0.3"
env_logger.workspace = true
futures-lite.workspace = true
renderling = { path = "../renderling", features = ["test-utils"] }
renderling_build = { path = "../renderling-build" }
tokio = { workspace = true, features = ["full"] }
wgpu.workspace = true


================================================
FILE: crates/examples/src/context.rs
================================================
//! Context manual page.

#[tokio::test]
async fn context_page() {
    // ANCHOR: create
    use renderling::context::Context;

    let ctx = Context::headless(256, 256).await;
    // ANCHOR_END: create

    // ANCHOR: frame
    let frame = ctx.get_next_frame().unwrap();
    // ...do some rendering
    //
    // Then capture the frame into an image, if you like
    let _image_capture = frame.read_image().await.unwrap();
    frame.present();
    // ANCHOR_END: frame
}


================================================
FILE: crates/examples/src/gltf.rs
================================================
//! GLTF manual page.

use crate::workspace_dir;

#[tokio::test]
async fn manual_gltf() {
    // ANCHOR: setup
    use renderling::{
        camera::Camera,
        context::Context,
        glam::{Mat4, Vec3, Vec4},
        stage::Stage,
    };

    let ctx = Context::headless(256, 256).await;
    let stage: Stage = ctx
        .new_stage()
        .with_background_color(Vec4::new(0.25, 0.25, 0.25, 1.0));

    let _camera: Camera = {
        let aspect = 1.0;
        let fovy = core::f32::consts::PI / 4.0;
        let znear = 0.1;
        let zfar = 10.0;
        let projection = Mat4::perspective_rh(fovy, aspect, znear, zfar);
        let eye = Vec3::new(0.5, 0.5, 0.8);
        let target = Vec3::new(0.0, 0.3, 0.0);
        let up = Vec3::Y;
        let view = Mat4::look_at_rh(eye, target, up);

        stage
            .new_camera()
            .with_projection_and_view(projection, view)
    };
    // ANCHOR_END: setup

    // ANCHOR: load
    use renderling::{gltf::GltfDocument, types::GpuOnlyArray};
    let model: GltfDocument<GpuOnlyArray> = stage
        .load_gltf_document_from_path(workspace_dir().join("gltf/marble_bust_1k.glb"))
        .unwrap()
        .into_gpu_only();
    println!("bounds: {:?}", model.bounding_volume());
    // ANCHOR_END: load

    super::cwd_to_manual_assets_dir();

    // ANCHOR: render_1
    let frame = ctx.get_next_frame().unwrap();
    stage.render(&frame.view());
    let img = frame.read_image().await.unwrap();
    img.save("gltf-example-shadow.png").unwrap();
    frame.present();
    // ANCHOR_END: render_1

    // ANCHOR: no_lights
    stage.set_has_lighting(false);
    // ANCHOR_END: no_lights

    let frame = ctx.get_next_frame().unwrap();
    stage.render(&frame.view());
    let img = frame.read_image().await.unwrap();
    img.save("gltf-example-unlit.png").unwrap();
    frame.present();
}


================================================
FILE: crates/examples/src/lib.rs
================================================
//! # Examples for the manual
//!
//! This crate contains examples and snippets that get pulled into the manual
//! via mdbook links. It also contains tests.

#[cfg(test)]
mod context;

#[cfg(test)]
mod stage;

#[cfg(test)]
mod gltf;

#[cfg(test)]
mod skybox;

#[cfg(test)]
mod lighting;

pub fn cwd_to_manual_assets_dir() -> std::path::PathBuf {
    let current_dir =
        std::path::PathBuf::from(std::env!("CARGO_WORKSPACE_DIR")).join("manual/src/assets");
    let current_dir = current_dir.canonicalize().unwrap();
    std::env::set_current_dir(&current_dir).unwrap();
    println!("current dir: {:?}", std::env::current_dir());
    current_dir
}

pub fn workspace_dir() -> std::path::PathBuf {
    renderling_build::workspace_dir().canonicalize().unwrap()
}

pub fn test_output_dir() -> std::path::PathBuf {
    let dir = renderling_build::test_output_dir();
    std::fs::create_dir_all(&dir).unwrap();
    dir.canonicalize().unwrap()
}

pub fn cwd_to_cargo_workspace() -> std::path::PathBuf {
    let current_dir = workspace_dir();
    std::env::set_current_dir(&current_dir).unwrap();
    println!("current dir: {:?}", std::env::current_dir());
    current_dir
}

doc_comment::doctest!("../../../manual/src/stage.md", stage_md);

#[test]
fn can_test() {
    assert_eq!(1, 1);
}


================================================
FILE: crates/examples/src/lighting.rs
================================================
//! Lighting examples.

use crate::{cwd_to_manual_assets_dir, workspace_dir};

#[tokio::test]
async fn manual_lighting() {
    // ANCHOR: setup
    use renderling::{
        camera::Camera,
        context::Context,
        glam::{Mat4, Vec3, Vec4},
        gltf::GltfDocument,
        stage::Stage,
        types::GpuOnlyArray,
    };

    let ctx = Context::headless(256, 256).await;
    let stage: Stage = ctx
        .new_stage()
        .with_background_color(Vec4::new(0.25, 0.25, 0.25, 1.0))
        .with_lighting(false);

    let _camera: Camera = {
        let aspect = 1.0;
        let fovy = core::f32::consts::PI / 4.0;
        let znear = 0.1;
        let zfar = 10.0;
        let projection = Mat4::perspective_rh(fovy, aspect, znear, zfar);
        let eye = Vec3::new(0.5, 0.5, 0.8);
        let target = Vec3::new(0.0, 0.3, 0.0);
        let up = Vec3::Y;
        let view = Mat4::look_at_rh(eye, target, up);

        stage
            .new_camera()
            .with_projection_and_view(projection, view)
    };

    let model: GltfDocument<GpuOnlyArray> = stage
        .load_gltf_document_from_path(workspace_dir().join("gltf/marble_bust_1k.glb"))
        .unwrap()
        .into_gpu_only();
    println!("bounds: {:?}", model.bounding_volume());

    let skybox = stage
        .new_skybox_from_path(workspace_dir().join("img/hdr/helipad.hdr"))
        .unwrap();
    stage.use_skybox(&skybox);

    let frame = ctx.get_next_frame().unwrap();
    stage.render(&frame.view());
    frame.present();
    // ANCHOR_END: setup

    cwd_to_manual_assets_dir();

    // ANCHOR: lighting_on
    stage.set_has_lighting(true);

    let frame = ctx.get_next_frame().unwrap();
    stage.render(&frame.view());
    let img = frame.read_image().await.unwrap();
    img.save("lighting/no-lights.png").unwrap();
    frame.present();
    // ANCHOR_END: lighting_on

    // ANCHOR: directional
    use renderling::{
        color::css_srgb_color_to_linear,
        light::{AnalyticalLight, DirectionalLight, Lux},
    };

    let sunset_amber_sunlight_color = css_srgb_color_to_linear(250, 198, 104);

    let directional: AnalyticalLight<DirectionalLight> = stage
        .new_directional_light()
        .with_direction(Vec3::new(-0.5, -0.5, 0.0))
        .with_color(sunset_amber_sunlight_color)
        .with_intensity(Lux::OUTDOOR_OVERCAST_HIGH);

    let frame = ctx.get_next_frame().unwrap();
    stage.render(&frame.view());
    let img = frame.read_image().await.unwrap();
    img.save("lighting/directional.png").unwrap();
    frame.present();
    // ANCHOR_END: directional

    // ANCHOR: remove_directional
    stage.remove_light(&directional);
    drop(directional);
    // ANCHOR_END: remove_directional

    // ANCHOR: point
    use renderling::light::{Candela, PointLight};

    let point: AnalyticalLight<PointLight> = stage
        .new_point_light()
        .with_position({
            let bust_aabb = model.bounding_volume().unwrap();
            bust_aabb.max
        })
        .with_color(sunset_amber_sunlight_color)
        .with_intensity(Candela(100.0));

    let frame = ctx.get_next_frame().unwrap();
    stage.render(&frame.view());
    let img = frame.read_image().await.unwrap();
    img.save("lighting/point.png").unwrap();
    frame.present();
    // ANCHOR_END: point

    // ANCHOR: remove_point
    stage.remove_light(&point);
    drop(point);
    // ANCHOR_END: remove_point

    // ANCHOR: spot
    use renderling::light::SpotLight;

    let camera_eye = Vec3::new(0.5, 0.5, 0.8);
    let camera_target = Vec3::new(0.0, 0.3, 0.0);
    let position = camera_eye;
    let direction = camera_target - camera_eye;
    let spot: AnalyticalLight<SpotLight> = stage
        .new_spot_light()
        .with_position(position)
        .with_direction(direction)
        // the cutoff values determine the angle of the cone
        .with_inner_cutoff(0.15)
        .with_outer_cutoff(0.2)
        .with_color(sunset_amber_sunlight_color)
        .with_intensity(Candela(12_000.0));

    let frame = ctx.get_next_frame().unwrap();
    stage.render(&frame.view());
    let img = frame.read_image().await.unwrap();
    img.save("lighting/spot.png").unwrap();
    frame.present();
    // ANCHOR_END: spot

    // ANCHOR: remove_spot
    stage.remove_light(&spot);
    drop(spot);
    // ANCHOR_END: remove_spot
}

#[tokio::test]
async fn manual_lighting_ibl() {
    let _ = env_logger::builder().try_init();

    cwd_to_manual_assets_dir();

    // ANCHOR: ibl_setup
    use renderling::{
        camera::Camera,
        context::Context,
        glam::{Mat4, Vec3, Vec4},
        gltf::GltfDocument,
        stage::Stage,
        types::GpuOnlyArray,
    };

    let ctx = Context::headless(512, 512).await;
    let stage: Stage = ctx
        .new_stage()
        .with_background_color(Vec4::new(0.25, 0.25, 0.25, 1.0));

    let _camera: Camera = {
        let aspect = 1.0;
        let fovy = core::f32::consts::PI / 4.0;
        let znear = 0.1;
        let zfar = 10.0;
        let projection = Mat4::perspective_rh(fovy, aspect, znear, zfar);
        let eye = Vec3::new(0.5, 0.5, 0.8);
        let target = Vec3::new(0.0, 0.3, 0.0);
        let up = Vec3::Y;
        let view = Mat4::look_at_rh(eye, target, up);

        stage
            .new_camera()
            .with_projection_and_view(projection, view)
    };

    let model: GltfDocument<GpuOnlyArray> = stage
        .load_gltf_document_from_path(workspace_dir().join("gltf/marble_bust_1k.glb"))
        .unwrap()
        .into_gpu_only();

    let skybox = stage
        .new_skybox_from_path(workspace_dir().join("img/hdr/helipad.hdr"))
        .unwrap();
    stage.use_skybox(&skybox);
    // ANCHOR_END: ibl_setup

    // ANCHOR: ibl
    use renderling::pbr::ibl::Ibl;

    let ibl: Ibl = stage.new_ibl(&skybox);
    stage.use_ibl(&ibl);

    let frame = ctx.get_next_frame().unwrap();
    stage.render(&frame.view());
    let img = frame.read_image().await.unwrap();
    img.save("lighting/ibl.png").unwrap();
    frame.present();
    // ANCHOR_END: ibl

    // ANCHOR: mix
    use renderling::{color::css_srgb_color_to_linear, light::Candela};

    let sunset_amber_sunlight_color = css_srgb_color_to_linear(250, 198, 104);
    let _point = stage
        .new_point_light()
        .with_position({
            let bust_aabb = model.bounding_volume().unwrap();
            bust_aabb.max
        })
        .with_color(sunset_amber_sunlight_color)
        .with_intensity(Candela(100.0));

    let frame = ctx.get_next_frame().unwrap();
    stage.render(&frame.view());
    let img = frame.read_image().await.unwrap();
    img.save("lighting/ibl-analytical-mixed.png").unwrap();
    frame.present();
    // ANCHOR_END: mix
}


================================================
FILE: crates/examples/src/skybox.rs
================================================
//! Skybox manual page.

use crate::{cwd_to_manual_assets_dir, workspace_dir};

#[tokio::test]
pub async fn manual_skybox() {
    // ANCHOR: setup
    use renderling::{
        camera::Camera,
        context::Context,
        glam::{Mat4, Vec3, Vec4},
        stage::Stage,
    };

    let ctx = Context::headless(256, 256).await;
    let stage: Stage = ctx
        .new_stage()
        .with_background_color(Vec4::new(0.25, 0.25, 0.25, 1.0))
        .with_lighting(false);

    let _camera: Camera = {
        let aspect = 1.0;
        let fovy = core::f32::consts::PI / 4.0;
        let znear = 0.1;
        let zfar = 10.0;
        let projection = Mat4::perspective_rh(fovy, aspect, znear, zfar);
        let eye = Vec3::new(0.5, 0.5, 0.8);
        let target = Vec3::new(0.0, 0.3, 0.0);
        let up = Vec3::Y;
        let view = Mat4::look_at_rh(eye, target, up);

        stage
            .new_camera()
            .with_projection_and_view(projection, view)
    };

    use renderling::{gltf::GltfDocument, types::GpuOnlyArray};
    let model: GltfDocument<GpuOnlyArray> = stage
        .load_gltf_document_from_path(workspace_dir().join("gltf/marble_bust_1k.glb"))
        .unwrap()
        .into_gpu_only();
    println!("bounds: {:?}", model.bounding_volume());

    let frame = ctx.get_next_frame().unwrap();
    stage.render(&frame.view());
    frame.present();
    // ANCHOR_END: setup

    // ANCHOR: skybox
    let skybox = stage
        .new_skybox_from_path(workspace_dir().join("img/hdr/helipad.hdr"))
        .unwrap();
    stage.use_skybox(&skybox);
    // ANCHOR_END: skybox

    cwd_to_manual_assets_dir();

    // ANCHOR: render_skybox
    let frame = ctx.get_next_frame().unwrap();
    stage.render(&frame.view());
    let image = frame.read_image().await.unwrap();
    image.save("skybox.png").unwrap();
    frame.present();
    // ANCHOR_END: render_skybox
}


================================================
FILE: crates/examples/src/stage.rs
================================================
//! Stage manual page.

#[tokio::test]
async fn manual_stage() {
    let _ = env_logger::builder().try_init();

    // ANCHOR: creation
    use renderling::{context::Context, glam::Vec4, stage::Stage};

    let ctx = Context::headless(256, 256).await;
    let stage: Stage = ctx
        .new_stage()
        .with_background_color(Vec4::new(0.5, 0.5, 0.5, 1.0));
    // ANCHOR_END: creation

    // ANCHOR: camera
    use renderling::{
        camera::Camera,
        glam::{Mat4, Vec3},
    };

    let camera: Camera = stage
        .new_camera()
        .with_default_perspective(256.0, 256.0)
        .with_view(Mat4::look_at_rh(Vec3::splat(1.5), Vec3::ZERO, Vec3::Y));
    // This is technically not necessary because Stage always "uses" the first
    // camera created, but we do it here for demonstration.
    stage.use_camera(&camera);
    // ANCHOR_END: camera

    // ANCHOR: unit_cube_vertices
    use renderling::geometry::{Vertex, Vertices};

    let vertices: Vertices = stage.new_vertices(renderling::math::unit_cube().into_iter().map(
        |(position, normal)| {
            Vertex::default()
                .with_position(position)
                .with_normal(normal)
                .with_color({
                    // The color can vary from vertex to vertex
                    //
                    // X axis is green
                    let g: f32 = position.x + 0.5;
                    // Y axis is blue
                    let b: f32 = position.y + 0.5;
                    // Z is red
                    let r: f32 = position.z + 0.5;
                    Vec4::new(r, g, b, 1.0)
                })
        },
    ));
    // ANCHOR_END: unit_cube_vertices

    // ANCHOR: unload_vertices
    use renderling::types::GpuOnlyArray;

    let vertices: Vertices<GpuOnlyArray> = vertices.into_gpu_only();
    // ANCHOR_END: unload_vertices

    // ANCHOR: material
    let material = stage
        .new_material()
        .with_albedo_factor(Vec4::ONE)
        .with_has_lighting(false);
    // ANCHOR_END: material

    // ANCHOR: prim
    let prim = stage
        .new_primitive()
        .with_vertices(&vertices)
        .with_material(&material);
    // ANCHOR_END: prim

    // Excluded from the manual because it's off-topic
    let current_dir =
        std::path::PathBuf::from(std::env!("CARGO_WORKSPACE_DIR")).join("manual/src/assets");
    let current_dir = current_dir.canonicalize().unwrap();
    std::env::set_current_dir(current_dir).unwrap();

    // ANCHOR: render
    let frame = ctx.get_next_frame().unwrap();
    stage.render(&frame.view());

    let img = frame.read_image().await.unwrap();
    img.save("stage-example.png").unwrap();
    frame.present();
    // ANCHOR_END: render

    // ANCHOR: committed_size_bytes
    let bytes_committed = stage.used_gpu_buffer_byte_size();
    println!("bytes_committed: {bytes_committed}");
    // ANCHOR_END: committed_size_bytes

    // ANCHOR: removal
    let staged_prim_count = stage.remove_primitive(&prim);
    assert_eq!(0, staged_prim_count);
    drop(vertices);
    drop(material);
    drop(prim);

    let frame = ctx.get_next_frame().unwrap();
    stage.render(&frame.view());
    let img = frame.read_image().await.unwrap();
    img.save("stage-example-gone.png").unwrap();
    frame.present();
    // ANCHOR_END: removal
}


================================================
FILE: crates/img-diff/Cargo.toml
================================================
[package]
name = "img-diff"
version = "0.1.0"
edition = "2021"

# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html

[dependencies]
glam = { workspace = true, features = ["std"] }
image.workspace = true
log.workspace = true
renderling_build = { path = "../renderling-build" }
snafu = "^0.7"


================================================
FILE: crates/img-diff/src/lib.rs
================================================
//! Provides image diffing for testing.
use glam::{Vec3, Vec4, Vec4Swizzles};
use image::{DynamicImage, Luma, Rgb, Rgb32FImage, Rgba32FImage};
use renderling_build::{test_img_dir, test_output_dir};
use snafu::prelude::*;

const PIXEL_MAGNITUDE_THRESHOLD: f32 = 0.1;
pub const LOW_PIXEL_THRESHOLD: f32 = 0.02;
const IMAGE_DIFF_THRESHOLD: f32 = 0.05;

fn checkerboard_background_color(x: u32, y: u32) -> Vec3 {
    let size = 16;
    let x_square_index = x / size;
    let x_grey = x_square_index.is_multiple_of(2);
    let y_square_index = y / size;
    let y_grey = y_square_index.is_multiple_of(2);
    if (x_grey && y_grey) || (!x_grey && !y_grey) {
        Vec3::from([0.5, 0.5, 0.5])
    } else {
        Vec3::from([1.0, 1.0, 1.0])
    }
}

#[derive(Debug, Snafu)]
enum ImgDiffError {
    #[snafu(display("Images are different sizes. Expected {lhs:?}, saw {rhs:?}"))]
    ImageSize { lhs: (u32, u32), rhs: (u32, u32) },
}

pub struct DiffCfg {
    /// The threshold for a pixel to be considered different.
    ///
    /// Difference is measured as the magnitude of vector subtraction
    /// between the two pixels.
    pub pixel_threshold: f32,
    /// The percentage of "different" pixels (as determined using
    /// `pixel_threshold`) to "correct" pixels that the image must contain
    /// before it is considered an error.
    pub image_threshold: f32,
    /// The name of the test.
    pub test_name: Option<&'static str>,
    /// The output directory to store comparisons in.
    pub output_dir: std::path::PathBuf,
}

impl Default for DiffCfg {
    fn default() -> Self {
        Self {
            pixel_threshold: PIXEL_MAGNITUDE_THRESHOLD,
            image_threshold: IMAGE_DIFF_THRESHOLD,
            test_name: None,
            output_dir: test_output_dir(),
        }
    }
}

pub struct DiffResults {
    num_pixels: usize,
    diff_image: Rgb32FImage,
    mask_image: DynamicImage,
    max_delta_length: f32,
    avg_delta_length: f32,
}

fn get_results(
    left_image: &Rgba32FImage,
    right_image: &Rgba32FImage,
    threshold: f32,
) -> Result<Option<DiffResults>, ImgDiffError> {
    let lid @ (width, height) = left_image.dimensions();
    let rid = right_image.dimensions();
    snafu::ensure!(lid == rid, ImageSizeSnafu { lhs: lid, rhs: rid });

    let results = left_image
        .enumerate_pixels()
        .flat_map(|(x, y, left_pixel)| {
            let right_pixel = right_image.get_pixel(x, y);
            if left_pixel == right_pixel {
                None
            } else {
                // pre-multiply alpha
                let left_pixel = Vec4::from(left_pixel.0);
                let left_pixel = (left_pixel * left_pixel.w).xyz();
                let right_pixel = Vec4::from(right_pixel.0);
                let right_pixel = (right_pixel * right_pixel.w).xyz();
                let delta = (left_pixel - right_pixel).abs();
                if delta.length() > threshold {
                    Some((x, y, delta))
                } else {
                    None
                }
            }
        })
        .collect::<Vec<_>>();

    let mut max_delta_length: f32 = 0.0;
    let mut sum_delta_length: f32 = 0.0;
    let diffs: usize = results.len();
    if diffs == 0 {
        Ok(None)
    } else {
        let mut mask_image = image::ImageBuffer::from_pixel(width, height, Luma([255u8]));
        let mut output_image = image::ImageBuffer::from_pixel(width, height, Rgb([0.0, 0.0, 0.0]));

        for x in 0..width {
            for y in 0..height {
                output_image.put_pixel(x, y, Rgb(checkerboard_background_color(x, y).into()));
            }
        }

        for (x, y, delta) in results {
            let length = delta.length();
            sum_delta_length += length;
            max_delta_length = length.max(max_delta_length);
            mask_image.put_pixel(x, y, Luma([0]));
            output_image.put_pixel(x, y, Rgb(delta.into()));
        }
        Ok(Some(DiffResults {
            num_pixels: diffs,
            diff_image: output_image,
            mask_image: mask_image.into(),
            max_delta_length,
            avg_delta_length: sum_delta_length / diffs as f32,
        }))
    }
}

pub fn save_to(
    dir: impl AsRef<std::path::Path>,
    filename: impl AsRef<std::path::Path>,
    seen: impl Into<DynamicImage>,
) -> Result<(), String> {
    let path = dir.as_ref().join(filename);
    std::fs::create_dir_all(path.parent().unwrap()).unwrap();
    let img: DynamicImage = seen.into();
    let img_buffer = img.into_rgba8();
    let img = DynamicImage::from(img_buffer);
    img.save(path).map_err(|e| e.to_string())
}

pub fn save(filename: impl AsRef<std::path::Path>, seen: impl Into<DynamicImage>) {
    save_to(test_output_dir(), filename, seen).unwrap()
}

pub fn assert_eq_cfg(
    filename: &str,
    lhs: impl Into<DynamicImage>,
    rhs: impl Into<DynamicImage>,
    cfg: DiffCfg,
) -> Result<(), String> {
    let lhs = lhs.into();
    let lhs = lhs.into_rgba32f();
    let rhs = rhs.into().into_rgba32f();
    let DiffCfg {
        pixel_threshold,
        image_threshold,
        test_name,
        output_dir,
    } = cfg;
    let results = match get_results(&lhs, &rhs, pixel_threshold) {
        Ok(maybe_diff) => maybe_diff,
        Err(e) => return Err(format!("Asserting {filename} failed: {e}")),
    };
    if let Some(DiffResults {
        num_pixels: diffs,
        diff_image,
        mask_image,
        max_delta_length,
        avg_delta_length,
    }) = results
    {
        println!("{filename} has {diffs} pixel differences (threshold={pixel_threshold})");
        println!("  max_delta_length: {max_delta_length}");
        println!(
            "  avg_delta_length: {avg_delta_length} (average of deltas of pixels past the \
             threshold)"
        );
        let percent_diff = diffs as f32 / (lhs.width() * lhs.height()) as f32;
        println!("{filename}'s image is {percent_diff} different (threshold={image_threshold})");
        if percent_diff < image_threshold {
            return Ok(());
        }

        let mut dir = output_dir.join(test_name.unwrap_or(filename));
        dir.set_extension("");
        std::fs::create_dir_all(&dir).expect("cannot create test output dir");
        let expected = dir.join("expected.png");
        let seen = dir.join("seen.png");
        let diff = dir.join("diff.png");
        let mask = dir.join("mask.png");
        let lhs = DynamicImage::from(lhs).into_rgba8();
        let rhs = DynamicImage::from(rhs).into_rgba8();
        lhs.save_with_format(&expected, image::ImageFormat::Png)
            .expect("can't save expected");
        rhs.save_with_format(&seen, image::ImageFormat::Png)
            .expect("can't save seen");
        let diff_image = DynamicImage::from(diff_image).into_rgba8();
        diff_image
            .save_with_format(&diff, image::ImageFormat::Png)
            .expect("can't save diff");
        let mask_image = mask_image.into_rgba8();
        mask_image
            .save_with_format(&mask, image::ImageFormat::Png)
            .expect("can't save diff mask");
        Err(format!(
            "{} has >= {} differences above the threshold\nexpected: {}\nseen: {}\ndiff: {}",
            filename,
            diffs,
            expected.display(),
            seen.display(),
            diff.display()
        ))
    } else {
        Ok(())
    }
}

pub fn assert_eq(filename: &str, lhs: impl Into<DynamicImage>, rhs: impl Into<DynamicImage>) {
    assert_eq_cfg(filename, lhs, rhs, DiffCfg::default()).unwrap()
}

pub fn assert_img_eq_cfg_result(
    filename: &str,
    seen: impl Into<DynamicImage>,
    cfg: DiffCfg,
) -> Result<(), String> {
    let path = test_img_dir().join(filename);
    let lhs = image::open(&path)
        .unwrap_or_else(|e| panic!("can't open expected image '{}': {e}", path.display(),));
    assert_eq_cfg(filename, lhs, seen, cfg)
}

pub fn assert_img_eq_cfg(filename: &str, seen: impl Into<DynamicImage>, cfg: DiffCfg) {
    assert_img_eq_cfg_result(filename, seen, cfg).unwrap()
}

pub fn assert_img_eq(filename: &str, seen: impl Into<DynamicImage>) {
    assert_img_eq_cfg(filename, seen, DiffCfg::default())
}

/// Normalize the depth image to make it easier to see.
///
/// ## Warning
/// This is only normalization, not linearization.
pub fn normalize_gray_img(seen: &mut image::GrayImage) {
    let mut max = 0u8;
    let mut min = u8::MAX;
    seen.pixels().for_each(|Luma([c])| {
        max = max.max(*c);
        min = min.min(*c);
    });
    let total = (max - min) as f32;
    seen.pixels_mut().for_each(|c| {
        let comps = c.0.map(|u| {
            let percent = (u as f32 - min as f32) / total;
            let float = percent * 255.0;
            float as u8
        });
        c.0 = comps;
    });
    log::info!("normalize_gray_img-min: {min}");
    log::info!("normalize_gray_img-max: {max}");
}

#[cfg(test)]
mod test {
    use crate::assert_img_eq;

    #[test]
    fn can_compare_images_sanity() {
        let img = image::open("../../test_img/jolt.png").unwrap();
        assert_img_eq("jolt.png", img);
    }
}


================================================
FILE: crates/loading-bytes/Cargo.toml
================================================
[package]
name = "loading-bytes"
version = "0.1.1"
edition = "2021"
description = "Load bytes from paths on native and WASM"
repository = "https://github.com/schell/renderling"
license = "MIT OR Apache-2.0"
keywords = ["image", "font", "binary", "loading", "bytes"]
categories = ["webassembly", "filesystem", "asynchronous"]
readme = "README.md"

# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html

[dependencies]
async-fs = "1.6"
js-sys = "0.3"
send_wrapper = {workspace=true}
serde.workspace = true
serde_json.workspace = true
snafu = {workspace = true}
wasm-bindgen = {workspace = true}
wasm-bindgen-futures = {workspace = true}
web-sys = { workspace = true, features = ["Request", "RequestInit", "Response", "Window"] }


================================================
FILE: crates/loading-bytes/README.md
================================================
# loading-bytes

Sometimes loading things really bites. 

Let's say you just want to compile your program on native and web, but you've 
done a bit of loading in your call tree... 

You know what I'm talking about, I mean reading from the filesystem. Easy stuff - right? 

## WRONG!

Well, not really - it's still pretty easy - but it's a hassle, hoff!

## NOT ANYMORE.

That's right folks! Step right up and use this here little library to load things from 
the filesystem on native and through a web request on WASM! 

What you get back totally bytes!

## BUT WAIT, THERE'S MORE!

Act now and I'll throw in some "nice" enumerated error handling.

OK. You got me. That's free!

In fact, the whole thing is free! Take it or leave it, folks, the choice is yours.

...

`loading-bytes` - from the same company that brought you `streaming-nibbles`🐟... 

...Just kidding! 

Happy hacking :) ☕☕☕


================================================
FILE: crates/loading-bytes/src/lib.rs
================================================
//! Abstraction over loading bytes on WASM or other.
use snafu::prelude::*;
use wasm_bindgen::UnwrapThrowExt;

#[derive(Debug, Snafu)]
pub enum WasmError {
    #[snafu(display("Could not create request to load '{path}': {msg:#?}"))]
    CreateRequest {
        path: String,
        msg: send_wrapper::SendWrapper<wasm_bindgen::JsValue>,
    },

    #[snafu(display("Fetch failed to load '{path}' by WASM error: {msg:#?}"))]
    Fetch {
        path: String,
        msg: send_wrapper::SendWrapper<wasm_bindgen::JsValue>,
    },

    #[snafu(display("Fetching '{path}' returned something that was not a Response: {msg:#?}"))]
    NotAResponse {
        path: String,
        msg: send_wrapper::SendWrapper<wasm_bindgen::JsValue>,
    },

    #[snafu(display("Could not get response array buffer '{path}': {msg:#?}"))]
    Array {
        path: String,
        msg: send_wrapper::SendWrapper<wasm_bindgen::JsValue>,
    },

    #[snafu(display("Could not get buffer from array '{path}': {msg:#?}"))]
    Buffer {
        path: String,
        msg: send_wrapper::SendWrapper<wasm_bindgen::JsValue>,
    },

    #[snafu(display("{other}"))]
    Other { other: String },
}

/// Enumeration of all errors this library may result in.
#[derive(Debug, Snafu)]
pub enum LoadingBytesError {
    #[snafu(display("{source}"))]
    Wasm { source: WasmError },

    #[snafu(display("loading '{path}' by filesystem from CWD '{}' error: {source}", cwd.display()))]
    Fs {
        path: String,
        cwd: std::path::PathBuf,
        source: std::io::Error,
    },
}

impl From<WasmError> for LoadingBytesError {
    fn from(value: WasmError) -> Self {
        LoadingBytesError::Wasm { source: value }
    }
}

pub async fn load_wasm(path: &str) -> Result<Vec<u8>, WasmError> {
    use wasm_bindgen::JsCast;

    let path = path.to_string();
    let opts = web_sys::RequestInit::new();
    opts.set_method("GET");
    let request = web_sys::Request::new_with_str_and_init(&path, &opts).map_err(|msg| {
        CreateRequestSnafu {
            path: path.clone(),
            msg: send_wrapper::SendWrapper::new(msg),
        }
        .build()
    })?;
    let window = web_sys::window().unwrap();
    let resp_value = wasm_bindgen_futures::JsFuture::from(window.fetch_with_request(&request))
        .await
        .map_err(|msg| {
            FetchSnafu {
                path: path.clone(),
                msg: send_wrapper::SendWrapper::new(msg),
            }
            .build()
        })?;
    let resp: web_sys::Response = resp_value.dyn_into().map_err(|msg| {
        NotAResponseSnafu {
            path: path.clone(),
            msg: send_wrapper::SendWrapper::new(msg),
        }
        .build()
    })?;
    let array_promise = resp.array_buffer().map_err(|msg| {
        ArraySnafu {
            path: path.clone(),
            msg: send_wrapper::SendWrapper::new(msg),
        }
        .build()
    })?;
    let buffer = wasm_bindgen_futures::JsFuture::from(array_promise)
        .await
        .map_err(|msg| {
            BufferSnafu {
                path: path.clone(),
                msg: send_wrapper::SendWrapper::new(msg),
            }
            .build()
        })?;
    assert!(buffer.is_instance_of::<js_sys::ArrayBuffer>());
    let array: js_sys::Uint8Array = js_sys::Uint8Array::new(&buffer);
    let mut bytes: Vec<u8> = vec![0; array.length() as usize];
    array.copy_to(&mut bytes);
    Ok(bytes)
}

pub async fn post_json_wasm<T: serde::de::DeserializeOwned>(
    path: &str,
    data: &str,
) -> Result<T, WasmError> {
    use js_sys::JsString;
    use wasm_bindgen::JsCast;

    let path = path.to_string();
    let opts = web_sys::RequestInit::new();
    opts.set_method("POST");
    let headers = js_sys::Object::new();
    js_sys::Reflect::set(
        &headers,
        &JsString::from("content-type"),
        &JsString::from("application/json"),
    )
    .unwrap();
    opts.set_headers(&headers);
    let body = js_sys::JsString::from(data);
    opts.set_body(&body.into());
    let request = web_sys::Request::new_with_str_and_init(&path, &opts).map_err(|msg| {
        CreateRequestSnafu {
            path: path.clone(),
            msg: send_wrapper::SendWrapper::new(msg),
        }
        .build()
    })?;
    let window = web_sys::window().unwrap();
    let resp_value = wasm_bindgen_futures::JsFuture::from(window.fetch_with_request(&request))
        .await
        .map_err(|msg| {
            FetchSnafu {
                path: path.clone(),
                msg: send_wrapper::SendWrapper::new(msg),
            }
            .build()
        })?;
    let resp: web_sys::Response = resp_value.dyn_into().map_err(|msg| {
        NotAResponseSnafu {
            path: path.clone(),
            msg: send_wrapper::SendWrapper::new(msg),
        }
        .build()
    })?;

    snafu::ensure!(
        resp.ok(),
        OtherSnafu {
            other: wasm_bindgen_futures::JsFuture::from(resp.text().unwrap())
                .await
                .unwrap()
                .as_string()
                .unwrap()
        }
    );

    let value = wasm_bindgen_futures::JsFuture::from(resp.text().unwrap_throw())
        .await
        .unwrap_throw();
    let s = value.as_string().expect_throw(&format!("{value:#?}"));
    let t = serde_json::from_str::<T>(&s).unwrap_throw();
    Ok(t)
}

// TODO: deduplicate post_bin_wasm and post_json_wasm
pub async fn post_bin_wasm<T: serde::de::DeserializeOwned>(
    path: &str,
    data: &[u8],
) -> Result<T, WasmError> {
    use js_sys::JsString;
    use wasm_bindgen::JsCast;

    let path = path.to_string();
    let opts = web_sys::RequestInit::new();
    opts.set_method("POST");
    let headers = js_sys::Object::new();
    js_sys::Reflect::set(
        &headers,
        &JsString::from("content-type"),
        &JsString::from("application/octet-stream"),
    )
    .unwrap();
    opts.set_headers(&headers);
    let body = js_sys::Uint8Array::from(data);
    opts.set_body(&body.into());
    let request = web_sys::Request::new_with_str_and_init(&path, &opts).map_err(|msg| {
        CreateRequestSnafu {
            path: path.clone(),
            msg: send_wrapper::SendWrapper::new(msg),
        }
        .build()
    })?;
    let window = web_sys::window().unwrap();
    let resp_value = wasm_bindgen_futures::JsFuture::from(window.fetch_with_request(&request))
        .await
        .map_err(|msg| {
            FetchSnafu {
                path: path.clone(),
                msg: send_wrapper::SendWrapper::new(msg),
            }
            .build()
        })?;
    let resp: web_sys::Response = resp_value.dyn_into().map_err(|msg| {
        NotAResponseSnafu {
            path: path.clone(),
            msg: send_wrapper::SendWrapper::new(msg),
        }
        .build()
    })?;

    snafu::ensure!(
        resp.ok(),
        OtherSnafu {
            other: wasm_bindgen_futures::JsFuture::from(resp.text().unwrap())
                .await
                .unwrap()
                .as_string()
                .unwrap()
        }
    );

    let value = wasm_bindgen_futures::JsFuture::from(resp.text().unwrap_throw())
        .await
        .unwrap_throw();
    let s = value.as_string().expect_throw(&format!("{value:#?}"));
    let t = serde_json::from_str::<T>(&s).unwrap_throw();
    Ok(t)
}

/// Load the file at the given url fragment or path and return it as a vector of
/// bytes, if possible.
pub async fn load(path: &str) -> Result<Vec<u8>, LoadingBytesError> {
    #[cfg(target_arch = "wasm32")]
    {
        let bytes = load_wasm(path).await?;
        Ok(bytes)
    }
    #[cfg(not(target_arch = "wasm32"))]
    {
        let bytes: Vec<u8> = async_fs::read(path).await.with_context(|_| FsSnafu {
            path: path.to_string(),
            cwd: std::env::current_dir().unwrap(),
        })?;
        Ok(bytes)
    }
}


================================================
FILE: crates/renderling/Cargo.toml
================================================
[package]
name = "renderling"
version = "0.6.0"
edition = "2021"
description = "User-friendly real-time rendering. 🍖"
repository = "https://github.com/schell/renderling"
license = "MIT OR Apache-2.0"
keywords = ["game", "graphics", "shader", "rendering"]
categories = ["rendering", "game-development", "graphics"]
readme = "../../README.md"
build = "src/build.rs"

[package.metadata.rust-gpu.install]
auto-install-rust-toolchain = true

[package.metadata.rust-gpu.build]
output-dir = "shaders" 
multimodule = true

# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html

[lib]
crate-type = ["rlib", "cdylib"]

[features]
default = ["gltf", "winit"]
gltf = ["dep:gltf", "dep:serde_json"]
test_i8_i16_extraction = []
test-utils = ["dep:metal", "dep:wgpu-core", "dep:futures-lite"]
wasm = ["wgpu/fragile-send-sync-non-atomic-wasm"]
debug-slab = []
light-tiling-stats = [ "dep:plotters" ]

[build-dependencies]
cfg_aliases.workspace = true
naga.workspace = true
pathdiff = "0.2.2"
quote.workspace = true
renderling_build = { path = "../renderling-build", version = "0.1.0" }
serde.workspace = true
serde_json.workspace = true
similarity.workspace = true

# dependencies for CPU and GPU code
[dependencies]
spirv-std.workspace = true

# dependencies for GPU code
[target.'cfg(target_arch = "spirv")'.dependencies]
crabslab = { workspace = true, features = ["glam"]  }
glam = { workspace = true, default-features = false, features = ["libm"] }

# dependencies for CPU code
[target.'cfg(not(target_arch = "spirv"))'.dependencies]
async-channel = {workspace = true}
bytemuck = {workspace = true}
craballoc.workspace = true
crabslab = { workspace = true, features = ["default"] }
crunch = "0.5"
dagga = {workspace=true}
futures-lite = { workspace = true, optional = true }
glam = { workspace = true, features = ["std"] }
gltf = {workspace = true, optional = true}
half = "2.3"
image = {workspace = true, features = ["hdr"]}
log = {workspace = true}
plotters = { workspace = true, optional = true }
pretty_assertions.workspace = true
rustc-hash = {workspace = true}
serde_json = {workspace = true, optional = true}
snafu = {workspace = true}
wgpu = { workspace = true, features = ["spirv"] }
winit = { workspace = true, optional = true }

# dependencies for WASM CPU code
[target.'cfg(target_arch = "wasm32")'.dependencies]
wasm-bindgen.workspace = true

[dev-dependencies]
assert_approx_eq.workspace = true
console_log.workspace = true
ctor = "0.2.2"
env_logger.workspace = true
example = { path = "../example" }
fastrand = "2.1.1"
futures-lite.workspace = true
human-repr = "1.1.0"
icosahedron = "0.1"
img-diff = { path = "../img-diff" }
loading-bytes = { workspace = true }
naga.workspace = true
renderling_build = { path = "../renderling-build" }
ttf-parser = "0.20.0"
wasm-bindgen-test.workspace = true
winit.workspace = true
wire-types = { path = "../wire-types" }

[target.'cfg(not(target_arch = "spirv"))'.dev-dependencies]
glam = { workspace = true, features = ["std", "debug-glam-assert"] }

[target.'cfg(target_os = "macos")'.dependencies]
metal = { workspace = true, optional = true }
wgpu-core = { workspace = true, optional = true }

[target.'cfg(target_os = "macos")'.dev-dependencies]
metal.workspace = true
wgpu-core.workspace = true

[dev-dependencies.web-sys]
workspace = true
features = [
  "Navigator",
  "Window"
]

[lints]
workspace = true


================================================
FILE: crates/renderling/shaders/manifest.json
================================================
[
  {
    "source_path": "shaders/atlas-shader-atlas_blit_fragment.spv",
    "entry_point": "atlas::shader::atlas_blit_fragment",
    "wgsl_entry_point": "atlasshaderatlas_blit_fragment"
  },
  {
    "source_path": "shaders/atlas-shader-atlas_blit_vertex.spv",
    "entry_point": "atlas::shader::atlas_blit_vertex",
    "wgsl_entry_point": "atlasshaderatlas_blit_vertex"
  },
  {
    "source_path": "shaders/bloom-shader-bloom_downsample_fragment.spv",
    "entry_point": "bloom::shader::bloom_downsample_fragment",
    "wgsl_entry_point": "bloomshaderbloom_downsample_fragment"
  },
  {
    "source_path": "shaders/bloom-shader-bloom_mix_fragment.spv",
    "entry_point": "bloom::shader::bloom_mix_fragment",
    "wgsl_entry_point": "bloomshaderbloom_mix_fragment"
  },
  {
    "source_path": "shaders/bloom-shader-bloom_upsample_fragment.spv",
    "entry_point": "bloom::shader::bloom_upsample_fragment",
    "wgsl_entry_point": "bloomshaderbloom_upsample_fragment"
  },
  {
    "source_path": "shaders/bloom-shader-bloom_vertex.spv",
    "entry_point": "bloom::shader::bloom_vertex",
    "wgsl_entry_point": "bloomshaderbloom_vertex"
  },
  {
    "source_path": "shaders/compositor-compositor_fragment.spv",
    "entry_point": "compositor::compositor_fragment",
    "wgsl_entry_point": "compositorcompositor_fragment"
  },
  {
    "source_path": "shaders/compositor-compositor_vertex.spv",
    "entry_point": "compositor::compositor_vertex",
    "wgsl_entry_point": "compositorcompositor_vertex"
  },
  {
    "source_path": "shaders/convolution-shader-brdf_lut_convolution_fragment.spv",
    "entry_point": "convolution::shader::brdf_lut_convolution_fragment",
    "wgsl_entry_point": "convolutionshaderbrdf_lut_convolution_fragment"
  },
  {
    "source_path": "shaders/convolution-shader-brdf_lut_convolution_vertex.spv",
    "entry_point": "convolution::shader::brdf_lut_convolution_vertex",
    "wgsl_entry_point": "convolutionshaderbrdf_lut_convolution_vertex"
  },
  {
    "source_path": "shaders/convolution-shader-generate_mipmap_fragment.spv",
    "entry_point": "convolution::shader::generate_mipmap_fragment",
    "wgsl_entry_point": "convolutionshadergenerate_mipmap_fragment"
  },
  {
    "source_path": "shaders/convolution-shader-generate_mipmap_vertex.spv",
    "entry_point": "convolution::shader::generate_mipmap_vertex",
    "wgsl_entry_point": "convolutionshadergenerate_mipmap_vertex"
  },
  {
    "source_path": "shaders/convolution-shader-prefilter_environment_cubemap_fragment.spv",
    "entry_point": "convolution::shader::prefilter_environment_cubemap_fragment",
    "wgsl_entry_point": "convolutionshaderprefilter_environment_cubemap_fragment"
  },
  {
    "source_path": "shaders/convolution-shader-prefilter_environment_cubemap_vertex.spv",
    "entry_point": "convolution::shader::prefilter_environment_cubemap_vertex",
    "wgsl_entry_point": "convolutionshaderprefilter_environment_cubemap_vertex"
  },
  {
    "source_path": "shaders/cubemap-shader-cubemap_sampling_test_fragment.spv",
    "entry_point": "cubemap::shader::cubemap_sampling_test_fragment",
    "wgsl_entry_point": "cubemapshadercubemap_sampling_test_fragment"
  },
  {
    "source_path": "shaders/cubemap-shader-cubemap_sampling_test_vertex.spv",
    "entry_point": "cubemap::shader::cubemap_sampling_test_vertex",
    "wgsl_entry_point": "cubemapshadercubemap_sampling_test_vertex"
  },
  {
    "source_path": "shaders/cull-shader-compute_copy_depth_to_pyramid.spv",
    "entry_point": "cull::shader::compute_copy_depth_to_pyramid",
    "wgsl_entry_point": "cullshadercompute_copy_depth_to_pyramid"
  },
  {
    "source_path": "shaders/cull-shader-compute_copy_depth_to_pyramid_multisampled.spv",
    "entry_point": "cull::shader::compute_copy_depth_to_pyramid_multisampled",
    "wgsl_entry_point": "cullshadercompute_copy_depth_to_pyramid_multisampled"
  },
  {
    "source_path": "shaders/cull-shader-compute_culling.spv",
    "entry_point": "cull::shader::compute_culling",
    "wgsl_entry_point": "cullshadercompute_culling"
  },
  {
    "source_path": "shaders/cull-shader-compute_downsample_depth_pyramid.spv",
    "entry_point": "cull::shader::compute_downsample_depth_pyramid",
    "wgsl_entry_point": "cullshadercompute_downsample_depth_pyramid"
  },
  {
    "source_path": "shaders/debug-shader-debug_overlay_fragment.spv",
    "entry_point": "debug::shader::debug_overlay_fragment",
    "wgsl_entry_point": "debugshaderdebug_overlay_fragment"
  },
  {
    "source_path": "shaders/debug-shader-debug_overlay_vertex.spv",
    "entry_point": "debug::shader::debug_overlay_vertex",
    "wgsl_entry_point": "debugshaderdebug_overlay_vertex"
  },
  {
    "source_path": "shaders/light-shader-light_tiling_bin_lights.spv",
    "entry_point": "light::shader::light_tiling_bin_lights",
    "wgsl_entry_point": "lightshaderlight_tiling_bin_lights"
  },
  {
    "source_path": "shaders/light-shader-light_tiling_clear_tiles.spv",
    "entry_point": "light::shader::light_tiling_clear_tiles",
    "wgsl_entry_point": "lightshaderlight_tiling_clear_tiles"
  },
  {
    "source_path": "shaders/light-shader-light_tiling_compute_tile_min_and_max_depth.spv",
    "entry_point": "light::shader::light_tiling_compute_tile_min_and_max_depth",
    "wgsl_entry_point": "lightshaderlight_tiling_compute_tile_min_and_max_depth"
  },
  {
    "source_path": "shaders/light-shader-light_tiling_compute_tile_min_and_max_depth_multisampled.spv",
    "entry_point": "light::shader::light_tiling_compute_tile_min_and_max_depth_multisampled",
    "wgsl_entry_point": "lightshaderlight_tiling_compute_tile_min_and_max_depth_multisampled"
  },
  {
    "source_path": "shaders/light-shader-light_tiling_depth_pre_pass.spv",
    "entry_point": "light::shader::light_tiling_depth_pre_pass",
    "wgsl_entry_point": "lightshaderlight_tiling_depth_pre_pass"
  },
  {
    "source_path": "shaders/light-shader-shadow_mapping_fragment.spv",
    "entry_point": "light::shader::shadow_mapping_fragment",
    "wgsl_entry_point": "lightshadershadow_mapping_fragment"
  },
  {
    "source_path": "shaders/light-shader-shadow_mapping_vertex.spv",
    "entry_point": "light::shader::shadow_mapping_vertex",
    "wgsl_entry_point": "lightshadershadow_mapping_vertex"
  },
  {
    "source_path": "shaders/pbr-ibl-shader-di_convolution_fragment.spv",
    "entry_point": "pbr::ibl::shader::di_convolution_fragment",
    "wgsl_entry_point": "pbriblshaderdi_convolution_fragment"
  },
  {
    "source_path": "shaders/primitive-shader-primitive_fragment.spv",
    "entry_point": "primitive::shader::primitive_fragment",
    "wgsl_entry_point": "primitiveshaderprimitive_fragment"
  },
  {
    "source_path": "shaders/primitive-shader-primitive_vertex.spv",
    "entry_point": "primitive::shader::primitive_vertex",
    "wgsl_entry_point": "primitiveshaderprimitive_vertex"
  },
  {
    "source_path": "shaders/skybox-shader-skybox_cubemap_fragment.spv",
    "entry_point": "skybox::shader::skybox_cubemap_fragment",
    "wgsl_entry_point": "skyboxshaderskybox_cubemap_fragment"
  },
  {
    "source_path": "shaders/skybox-shader-skybox_cubemap_vertex.spv",
    "entry_point": "skybox::shader::skybox_cubemap_vertex",
    "wgsl_entry_point": "skyboxshaderskybox_cubemap_vertex"
  },
  {
    "source_path": "shaders/skybox-shader-skybox_equirectangular_fragment.spv",
    "entry_point": "skybox::shader::skybox_equirectangular_fragment",
    "wgsl_entry_point": "skyboxshaderskybox_equirectangular_fragment"
  },
  {
    "source_path": "shaders/skybox-shader-skybox_vertex.spv",
    "entry_point": "skybox::shader::skybox_vertex",
    "wgsl_entry_point": "skyboxshaderskybox_vertex"
  },
  {
    "source_path": "shaders/tonemapping-tonemapping_fragment.spv",
    "entry_point": "tonemapping::tonemapping_fragment",
    "wgsl_entry_point": "tonemappingtonemapping_fragment"
  },
  {
    "source_path": "shaders/tonemapping-tonemapping_vertex.spv",
    "entry_point": "tonemapping::tonemapping_vertex",
    "wgsl_entry_point": "tonemappingtonemapping_vertex"
  },
  {
    "source_path": "shaders/tutorial-implicit_isosceles_vertex.spv",
    "entry_point": "tutorial::implicit_isosceles_vertex",
    "wgsl_entry_point": "tutorialimplicit_isosceles_vertex"
  },
  {
    "source_path": "shaders/tutorial-passthru_fragment.spv",
    "entry_point": "tutorial::passthru_fragment",
    "wgsl_entry_point": "tutorialpassthru_fragment"
  },
  {
    "source_path": "shaders/tutorial-slabbed_renderlet.spv",
    "entry_point": "tutorial::slabbed_renderlet",
    "wgsl_entry_point": "tutorialslabbed_renderlet"
  },
  {
    "source_path": "shaders/tutorial-slabbed_vertices.spv",
    "entry_point": "tutorial::slabbed_vertices",
    "wgsl_entry_point": "tutorialslabbed_vertices"
  },
  {
    "source_path": "shaders/tutorial-slabbed_vertices_no_instance.spv",
    "entry_point": "tutorial::slabbed_vertices_no_instance",
    "wgsl_entry_point": "tutorialslabbed_vertices_no_instance"
  },
  {
    "source_path": "shaders/ui_slab-shader-ui_fragment.spv",
    "entry_point": "ui_slab::shader::ui_fragment",
    "wgsl_entry_point": "ui_slabshaderui_fragment"
  },
  {
    "source_path": "shaders/ui_slab-shader-ui_vertex.spv",
    "entry_point": "ui_slab::shader::ui_vertex",
    "wgsl_entry_point": "ui_slabshaderui_vertex"
  }
]

================================================
FILE: crates/renderling/src/atlas/atlas_image.rs
================================================
//! Images and texture formats.
//!
//! Used to represent textures before they are sent to the GPU.
use glam::UVec2;
use image::EncodableLayout;
use snafu::prelude::*;

fn cwd() -> Option<String> {
    #[cfg(target_arch = "wasm32")]
    {
        Some("localhost".to_string())
    }
    #[cfg(not(target_arch = "wasm32"))]
    {
        let cwd = std::env::current_dir().ok()?;
        Some(format!("{}", cwd.display()))
    }
}

#[derive(Debug, Snafu)]
pub enum AtlasImageError {
    #[snafu(display("Cannot load image '{}' from cwd '{:?}': {source}", path.display(), cwd()))]
    CannotLoad {
        source: std::io::Error,
        path: std::path::PathBuf,
    },

    #[snafu(display("Image error: {source}\nCurrent dir: {:?}", cwd()))]
    Image { source: image::error::ImageError },
}

#[derive(Clone, Copy, Debug)]
pub enum AtlasImageFormat {
    R8,
    R8G8,
    R8G8B8,
    R8G8B8A8,
    R16,
    R16G16,
    R16G16B16,
    R16G16B16A16,
    R16G16B16A16FLOAT,
    R32FLOAT,
    R32G32B32FLOAT,
    R32G32B32A32FLOAT,
    D32FLOAT,
}

impl From<AtlasImageFormat> for wgpu::TextureFormat {
    fn from(value: AtlasImageFormat) -> Self {
        match value {
            AtlasImageFormat::R8 => wgpu::TextureFormat::R8Unorm,
            AtlasImageFormat::R8G8 => wgpu::TextureFormat::Rg8Unorm,
            AtlasImageFormat::R8G8B8 => wgpu::TextureFormat::Rgba8Unorm, /* No direct 3-channel */
            // format, using
            // 4-channel
            AtlasImageFormat::R8G8B8A8 => wgpu::TextureFormat::Rgba8Unorm,
            AtlasImageFormat::R16 => wgpu::TextureFormat::R16Unorm,
            AtlasImageFormat::R16G16 => wgpu::TextureFormat::Rg16Unorm,
            AtlasImageFormat::R16G16B16 => wgpu::TextureFormat::Rgba16Unorm, /* No direct 3-channel format, using 4-channel */
            AtlasImageFormat::R16G16B16A16 => wgpu::TextureFormat::Rgba16Unorm,
            AtlasImageFormat::R16G16B16A16FLOAT => wgpu::TextureFormat::Rgba16Float,
            AtlasImageFormat::R32FLOAT => wgpu::TextureFormat::R32Float,
            AtlasImageFormat::R32G32B32FLOAT => wgpu::TextureFormat::Rgba32Float, /* No direct 3-channel format, using 4-channel */
            AtlasImageFormat::R32G32B32A32FLOAT => wgpu::TextureFormat::Rgba32Float,
            AtlasImageFormat::D32FLOAT => wgpu::TextureFormat::Depth32Float,
        }
    }
}

impl AtlasImageFormat {
    pub fn from_wgpu_texture_format(value: wgpu::TextureFormat) -> Option<Self> {
        match value {
            wgpu::TextureFormat::R8Uint => Some(AtlasImageFormat::R8),
            wgpu::TextureFormat::R16Uint => Some(AtlasImageFormat::R16),
            wgpu::TextureFormat::R32Float => Some(AtlasImageFormat::R32FLOAT),
            wgpu::TextureFormat::Rg8Uint => Some(AtlasImageFormat::R8G8),
            wgpu::TextureFormat::Rg16Uint => Some(AtlasImageFormat::R16G16),
            wgpu::TextureFormat::Rgba16Float => Some(AtlasImageFormat::R16G16B16A16FLOAT),
            wgpu::TextureFormat::Depth32Float => Some(AtlasImageFormat::D32FLOAT),
            _ => None,
        }
    }

    pub fn zero_pixel(&self) -> &[u8] {
        match self {
            AtlasImageFormat::R8 => &[0],
            AtlasImageFormat::R8G8 => &[0, 0],
            AtlasImageFormat::R8G8B8 => &[0, 0, 0],
            AtlasImageFormat::R8G8B8A8 => &[0, 0, 0, 0],
            AtlasImageFormat::R16 => &[0, 0],
            AtlasImageFormat::R16G16 => &[0, 0, 0, 0],
            AtlasImageFormat::R16G16B16 => &[0, 0, 0, 0, 0, 0],
            AtlasImageFormat::R16G16B16A16 => &[0, 0, 0, 0, 0, 0, 0, 0],
            AtlasImageFormat::R16G16B16A16FLOAT => &[0, 0, 0, 0, 0, 0, 0, 0],
            AtlasImageFormat::R32FLOAT | AtlasImageFormat::D32FLOAT => &[0, 0, 0, 0],
            AtlasImageFormat::R32G32B32FLOAT => &[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
            AtlasImageFormat::R32G32B32A32FLOAT => {
                &[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
            }
        }
    }
}

/// Image data in transit from CPU to GPU.
#[derive(Clone, Debug)]
pub struct AtlasImage {
    pub pixels: Vec<u8>,
    pub size: UVec2,
    pub format: AtlasImageFormat,
    // Whether or not to convert from sRGB color space into linear color space.
    pub apply_linear_transfer: bool,
}

#[cfg(feature = "gltf")]
impl From<gltf::image::Data> for AtlasImage {
    fn from(value: gltf::image::Data) -> Self {
        let pixels = value.pixels;
        let size = UVec2::new(value.width, value.height);
        let format = match value.format {
            gltf::image::Format::R8 => AtlasImageFormat::R8,
            gltf::image::Format::R8G8 => AtlasImageFormat::R8G8,
            gltf::image::Format::R8G8B8 => AtlasImageFormat::R8G8B8,
            gltf::image::Format::R8G8B8A8 => AtlasImageFormat::R8G8B8A8,
            gltf::image::Format::R16 => AtlasImageFormat::R16,
            gltf::image::Format::R16G16 => AtlasImageFormat::R16G16,
            gltf::image::Format::R16G16B16 => AtlasImageFormat::R16G16B16,
            gltf::image::Format::R16G16B16A16 => AtlasImageFormat::R16G16B16A16,
            gltf::image::Format::R32G32B32FLOAT => AtlasImageFormat::R32G32B32FLOAT,
            gltf::image::Format::R32G32B32A32FLOAT => AtlasImageFormat::R32G32B32A32FLOAT,
        };

        AtlasImage {
            size,
            pixels,
            format,
            // Determining this gets deferred until material construction
            apply_linear_transfer: false,
        }
    }
}

impl From<image::DynamicImage> for AtlasImage {
    fn from(value: image::DynamicImage) -> Self {
        let width = value.width();
        let height = value.height();

        use AtlasImageFormat::*;
        let (pixels, format) = match value {
            image::DynamicImage::ImageLuma8(img) => (img.into_vec(), R8),
            i @ image::DynamicImage::ImageLumaA8(_) => (i.into_rgba8().into_vec(), R8G8B8A8),
            image::DynamicImage::ImageRgb8(img) => (img.into_vec(), R8G8B8),
            image::DynamicImage::ImageRgba8(img) => (img.into_vec(), R8G8B8A8),
            image::DynamicImage::ImageLuma16(img) => (img.as_bytes().to_vec(), R16),
            i @ image::DynamicImage::ImageLumaA16(_) => {
                (i.into_rgba16().as_bytes().to_vec(), R16G16B16A16)
            }
            i @ image::DynamicImage::ImageRgb16(_) => (i.as_bytes().to_vec(), R16G16B16),
            i @ image::DynamicImage::ImageRgba16(_) => (i.as_bytes().to_vec(), R16G16B16A16),
            i @ image::DynamicImage::ImageRgb32F(_) => (i.as_bytes().to_vec(), R32G32B32FLOAT),
            i @ image::DynamicImage::ImageRgba32F(_) => (i.as_bytes().to_vec(), R32G32B32A32FLOAT),
            _ => todo!(),
        };
        AtlasImage {
            pixels,
            format,
            // Most of the time when people are using `image` to load images, those images
            // have color data that was authored in sRGB space.
            apply_linear_transfer: true,
            size: UVec2::new(width, height),
        }
    }
}

impl TryFrom<std::path::PathBuf> for AtlasImage {
    type Error = AtlasImageError;

    fn try_from(value: std::path::PathBuf) -> Result<Self, Self::Error> {
        let img = image::open(value).context(ImageSnafu)?;
        Ok(img.into())
    }
}

impl AtlasImage {
    pub fn from_hdr_path(p: impl AsRef<std::path::Path>) -> Result<Self, AtlasImageError> {
        let bytes = std::fs::read(p.as_ref()).with_context(|_| CannotLoadSnafu {
            path: std::path::PathBuf::from(p.as_ref()),
        })?;
        Self::from_hdr_bytes(&bytes)
    }

    pub fn from_hdr_bytes(bytes: &[u8]) -> Result<Self, AtlasImageError> {
        // Decode HDR data.
        let decoder = image::codecs::hdr::HdrDecoder::new(bytes).context(ImageSnafu)?;
        let width = decoder.metadata().width;
        let height = decoder.metadata().height;
        let img = image::DynamicImage::from_decoder(decoder).unwrap();
        let pixels = img.into_rgb32f();

        // Add alpha data.
        let mut pixel_data: Vec<f32> = Vec::new();
        for pixel in pixels.pixels() {
            pixel_data.push(pixel[0]);
            pixel_data.push(pixel[1]);
            pixel_data.push(pixel[2]);
            pixel_data.push(1.0);
        }
        let mut pixels = vec![];
        pixels.extend_from_slice(bytemuck::cast_slice(pixel_data.as_slice()));

        Ok(Self {
            pixels,
            size: UVec2::new(width, height),
            format: AtlasImageFormat::R32G32B32A32FLOAT,
            apply_linear_transfer: false,
        })
    }

    pub fn from_path(p: impl AsRef<std::path::Path>) -> Result<Self, AtlasImageError> {
        Self::try_from(p.as_ref().to_path_buf())
    }

    pub fn into_rgba8(self) -> Option<image::RgbaImage> {
        let pixels = convert_pixels(
            self.pixels,
            self.format,
            wgpu::TextureFormat::Rgba8Unorm,
            self.apply_linear_transfer,
        );
        image::RgbaImage::from_vec(self.size.x, self.size.y, pixels)
    }

    /// Returns a new [`AtlasImage`] with zeroed data.
    pub fn new(size: UVec2, format: AtlasImageFormat) -> Self {
        Self {
            pixels: std::iter::repeat_n(format.zero_pixel(), (size.x * size.y) as usize)
                .flatten()
                .copied()
                .collect(),
            size,
            format,
            apply_linear_transfer: false,
        }
    }
}

fn apply_linear_xfer(bytes: &mut [u8], format: AtlasImageFormat) {
    use crate::color::*;
    match format {
        AtlasImageFormat::R8
        | AtlasImageFormat::R8G8
        | AtlasImageFormat::R8G8B8
        | AtlasImageFormat::R8G8B8A8 => {
            bytes.iter_mut().for_each(linear_xfer_u8);
        }
        AtlasImageFormat::R16
        | AtlasImageFormat::R16G16
        | AtlasImageFormat::R16G16B16
        | AtlasImageFormat::R16G16B16A16 => {
            let bytes: &mut [u16] = bytemuck::cast_slice_mut(bytes);
            bytes.iter_mut().for_each(linear_xfer_u16);
        }
        AtlasImageFormat::R16G16B16A16FLOAT => {
            let bytes: &mut [u16] = bytemuck::cast_slice_mut(bytes);
            bytes.iter_mut().for_each(linear_xfer_f16);
        }
        AtlasImageFormat::R32G32B32FLOAT
        | AtlasImageFormat::R32G32B32A32FLOAT
        | AtlasImageFormat::D32FLOAT
        | AtlasImageFormat::R32FLOAT => {
            let bytes: &mut [f32] = bytemuck::cast_slice_mut(bytes);
            bytes.iter_mut().for_each(linear_xfer_f32);
        }
    }
}

/// Interpret/convert the `AtlasImage` pixel data into `wgpu::TextureFormat`
/// pixels, if possible.
///
/// This applies the linear transfer function if `apply_linear_transfer` is
/// `true`.
pub fn convert_pixels(
    bytes: impl IntoIterator<Item = u8>,
    from_format: AtlasImageFormat,
    to_format: wgpu::TextureFormat,
    apply_linear_transfer: bool,
) -> Vec<u8> {
    use crate::color::*;
    let mut bytes = bytes.into_iter().collect::<Vec<_>>();
    log::trace!("converting image of format {from_format:?}");
    // Convert using linear transfer, if needed
    if apply_linear_transfer {
        log::trace!("  converting to linear color space (from sRGB)");
        apply_linear_xfer(&mut bytes, from_format);
    }

    // Hamfisted conversion to `to_format`
    match (from_format, to_format) {
        (AtlasImageFormat::R8, wgpu::TextureFormat::Rgba8Unorm) => {
            bytes.into_iter().flat_map(|r| [r, 0, 0, 255]).collect()
        }
        (AtlasImageFormat::R8G8, wgpu::TextureFormat::Rgba8Unorm) => bytes
            .chunks_exact(2)
            .flat_map(|p| {
                if let [r, g] = p {
                    [*r, *g, 0, 255]
                } else {
                    unreachable!()
                }
            })
            .collect(),
        (AtlasImageFormat::R8G8B8, wgpu::TextureFormat::Rgba8Unorm) => bytes
            .chunks_exact(3)
            .flat_map(|p| {
                if let [r, g, b] = p {
                    [*r, *g, *b, 255]
                } else {
                    unreachable!()
                }
            })
            .collect(),
        (AtlasImageFormat::R8G8B8A8, wgpu::TextureFormat::Rgba8Unorm) => bytes,
        (AtlasImageFormat::R16, wgpu::TextureFormat::Rgba8Unorm) => {
            bytemuck::cast_slice::<u8, u16>(&bytes)
                .iter()
                .flat_map(|r| [u16_to_u8(*r), 0, 0, 255])
                .collect()
        }
        (AtlasImageFormat::R16G16, wgpu::TextureFormat::Rgba8Unorm) => {
            bytemuck::cast_slice::<u8, u16>(&bytes)
                .chunks_exact(2)
                .flat_map(|p| {
                    if let [r, g] = p {
                        [u16_to_u8(*r), u16_to_u8(*g), 0, 255]
                    } else {
                        unreachable!()
                    }
                })
                .collect()
        }
        (AtlasImageFormat::R16G16B16, wgpu::TextureFormat::Rgba8Unorm) => {
            bytemuck::cast_slice::<u8, u16>(&bytes)
                .chunks_exact(3)
                .flat_map(|p| {
                    if let [r, g, b] = p {
                        [u16_to_u8(*r), u16_to_u8(*g), u16_to_u8(*b), 255]
                    } else {
                        unreachable!()
                    }
                })
                .collect()
        }

        (AtlasImageFormat::R16G16B16A16, wgpu::TextureFormat::Rgba8Unorm) => {
            bytemuck::cast_slice::<u8, u16>(&bytes)
                .iter()
                .copied()
                .map(u16_to_u8)
                .collect()
        }
        (AtlasImageFormat::R16G16B16A16FLOAT, wgpu::TextureFormat::Rgba8Unorm) => {
            bytemuck::cast_slice::<u8, u16>(&bytes)
                .iter()
                .map(|bits| half::f16::from_bits(*bits).to_f32())
                .collect::<Vec<_>>()
                .chunks_exact(4)
                .flat_map(|p| {
                    if let [r, g, b, a] = p {
                        [f32_to_u8(*r), f32_to_u8(*g), f32_to_u8(*b), f32_to_u8(*a)]
                    } else {
                        unreachable!()
                    }
                })
                .collect()
        }
        (AtlasImageFormat::R32G32B32FLOAT, wgpu::TextureFormat::Rgba8Unorm) => {
            bytemuck::cast_slice::<u8, f32>(&bytes)
                .chunks_exact(3)
                .flat_map(|p| {
                    if let [r, g, b] = p {
                        [f32_to_u8(*r), f32_to_u8(*g), f32_to_u8(*b), 255]
                    } else {
                        unreachable!()
                    }
                })
                .collect()
        }
        (AtlasImageFormat::R32G32B32A32FLOAT, wgpu::TextureFormat::Rgba8Unorm)
        | (AtlasImageFormat::R32FLOAT, wgpu::TextureFormat::Rgba8Unorm)
        | (AtlasImageFormat::D32FLOAT, wgpu::TextureFormat::Rgba8Unorm) => {
            bytemuck::cast_slice::<u8, f32>(&bytes)
                .iter()
                .copied()
                .map(f32_to_u8)
                .collect()
        }
        (AtlasImageFormat::R32FLOAT, wgpu::TextureFormat::R32Float) => bytes,
        // TODO: add more atlas format conversions
        (from, to) => panic!("cannot convert from atlas format {from:?} to {to:?}"),
    }
}


================================================
FILE: crates/renderling/src/atlas/cpu.rs
================================================
use core::{ops::Deref, sync::atomic::AtomicUsize};
use std::sync::{Arc, Mutex, RwLock};

use craballoc::{
    prelude::{Hybrid, SlabAllocator, WeakHybrid},
    runtime::WgpuRuntime,
    slab::SlabBuffer,
};
use crabslab::Id;
use glam::{UVec2, UVec3};
use image::RgbaImage;
use snafu::{prelude::*, OptionExt};

use crate::{
    atlas::{
        shader::{AtlasBlittingDescriptor, AtlasDescriptor, AtlasTextureDescriptor},
        TextureModes,
    },
    bindgroup::ManagedBindGroup,
    texture::{self, CopiedTextureBuffer, Texture},
};

use super::atlas_image::{convert_pixels, AtlasImage};

pub(crate) const ATLAS_SUGGESTED_SIZE: u32 = 2048;
pub(crate) const ATLAS_SUGGESTED_LAYERS: u32 = 8;

#[derive(Debug, Snafu)]
pub enum AtlasError {
    #[snafu(display("Cannot pack textures.\natlas_size: {size:#?}"))]
    CannotPackTextures { size: wgpu::Extent3d },

    #[snafu(display("Missing layer {index}"))]
    MissingLayer { index: u32, images: Vec<AtlasImage> },

    #[snafu(display("Atlas size is invalid: {size:?}"))]
    Size { size: wgpu::Extent3d },

    #[snafu(display("Missing slab during staging"))]
    StagingMissingSlab,

    #[snafu(display("Missing bindgroup {layer}"))]
    MissingBindgroup { layer: u32 },

    #[snafu(display("{source}"))]
    Texture {
        source: crate::texture::TextureError,
    },
}

/// A staged texture in the texture atlas.
///
/// An [`AtlasTexture`] can be acquired through:
///
/// * [`Atlas::add_image`]
/// * [`Atlas::add_images`].
/// * [`Atlas::set_images`]
///
/// Clones of this type all point to the same underlying data.
///
/// Dropping all clones of this type will cause it to be unloaded from the GPU.
///
/// If a value of this type has been given to another staged resource,
/// like [`Material`](crate::material::Material), this will prevent the
/// `AtlasTexture` from being dropped and unloaded.
///
/// Internally an `AtlasTexture` holds a reference to its descriptor,
/// [`AtlasTextureDescriptor`].
#[derive(Clone)]
pub struct AtlasTexture {
    pub(crate) descriptor: Hybrid<AtlasTextureDescriptor>,
}

impl AtlasTexture {
    /// Get the GPU slab identifier of the underlying descriptor.
    ///
    /// This is for internal use.
    pub fn id(&self) -> Id<AtlasTextureDescriptor> {
        self.descriptor.id()
    }

    /// Return a copy of the underlying descriptor.
    pub fn descriptor(&self) -> AtlasTextureDescriptor {
        self.descriptor.get()
    }

    /// Return the texture modes of the underlying descriptor.
    pub fn modes(&self) -> TextureModes {
        self.descriptor.get().modes
    }

    /// Sets the texture modes of the underlying descriptor.
    ///
    /// ## Warning
    ///
    /// This also sets the modes for all clones of this value.
    pub fn set_modes(&self, modes: TextureModes) {
        self.descriptor.modify(|d| d.modes = modes);
    }
}

/// Used to track textures internally.
///
/// We need a separate struct for tracking textures because the atlas
/// reorganizes the layout (the packing) of textures each time a new
/// texture is added.
///
/// This means the textures must be updated on the GPU, but we don't
/// want these internal representations to keep unreferenced textures
/// from dropping, so we have to maintain a separate representation
/// here.
#[derive(Clone, Debug)]
struct InternalAtlasTexture {
    /// Cached value.
    cache: AtlasTextureDescriptor,
    weak: WeakHybrid<AtlasTextureDescriptor>,
}

impl InternalAtlasTexture {
    fn from_hybrid(hat: &Hybrid<AtlasTextureDescriptor>) -> Self {
        InternalAtlasTexture {
            cache: hat.get(),
            weak: WeakHybrid::from_hybrid(hat),
        }
    }

    fn has_external_references(&self) -> bool {
        self.weak.has_external_references()
    }

    fn set(&mut self, at: AtlasTextureDescriptor) {
        self.cache = at;
        if let Some(hy) = self.weak.upgrade() {
            hy.set(at);
        } else if let Some(gpu) = self.weak.weak_gpu().upgrade() {
            gpu.set(at)
        } else {
            log::warn!("could not set atlas texture, lost");
        }
    }
}

pub(crate) fn check_size(size: wgpu::Extent3d) {
    let conditions = size.depth_or_array_layers >= 2
        && size.width == size.height
        && (size.width & (size.width - 1)) == 0;
    if !conditions {
        log::error!("{}", AtlasError::Size { size });
    }
}

fn fan_split_n<T>(n: usize, input: impl IntoIterator<Item = T>) -> Vec<Vec<T>> {
    if n == 0 {
        return vec![];
    }
    let mut output = vec![];
    for _ in 0..n {
        output.push(vec![]);
    }
    let mut i = 0;
    for item in input.into_iter() {
        // UNWRAP: safe because i % n
        output
            .get_mut(i)
            .unwrap_or_else(|| panic!("could not unwrap i:{i} n:{n}"))
            .push(item);
        i = (i + 1) % n;
    }
    output
}

#[derive(Clone)]
enum AnotherPacking<'a> {
    Img {
        original_index: usize,
        image: &'a AtlasImage,
    },
    Internal(InternalAtlasTexture),
}

impl AnotherPacking<'_> {
    fn size(&self) -> UVec2 {
        match self {
            AnotherPacking::Img {
                original_index: _,
                image,
            } => image.size,
            AnotherPacking::Internal(tex) => tex.cache.size_px,
        }
    }
}

#[derive(Clone, Default, Debug)]
pub struct Layer {
    frames: Vec<InternalAtlasTexture>,
}

/// A texture atlas, used to store all the textures in a scene.
///
/// Clones of `Atlas` all point to the same internal data.
#[derive(Clone)]
pub struct Atlas {
    pub(crate) slab: SlabAllocator<WgpuRuntime>,
    texture_array: Arc<RwLock<Texture>>,
    layers: Arc<RwLock<Vec<Layer>>>,
    label: Option<String>,
    descriptor: Hybrid<AtlasDescriptor>,
    /// Used for user updates into the atlas by blit images into specific
    /// frames.
    blitter: AtlasBlitter,
}

impl Atlas {
    const LABEL: Option<&str> = Some("atlas-texture");

    pub fn device(&self) -> &wgpu::Device {
        self.slab.device()
    }

    /// Create the initial texture to use.
    fn create_texture(
        runtime: impl AsRef<WgpuRuntime>,
        size: wgpu::Extent3d,
        format: Option<wgpu::TextureFormat>,
        label: Option<&str>,
        usage: Option<wgpu::TextureUsages>,
    ) -> Texture {
        let device = &runtime.as_ref().device;
        let queue = &runtime.as_ref().queue;
        check_size(size);
        let usage = usage.unwrap_or(wgpu::TextureUsages::empty());
        let texture = device.create_texture(&wgpu::TextureDescriptor {
            label: Some(label.unwrap_or(Self::LABEL.unwrap())),
            size,
            mip_level_count: 1,
            sample_count: 1,
            dimension: wgpu::TextureDimension::D2,
            format: format.unwrap_or(wgpu::TextureFormat::Rgba8Unorm),
            usage: usage
                | wgpu::TextureUsages::TEXTURE_BINDING
                | wgpu::TextureUsages::COPY_DST
                | wgpu::TextureUsages::COPY_SRC,
            view_formats: &[],
        });

        let mut encoder = device.create_command_encoder(&wgpu::CommandEncoderDescriptor {
            label: Some("create atlas texture array"),
        });
        if device.features().contains(wgpu::Features::CLEAR_TEXTURE) {
            encoder.clear_texture(
                &texture,
                &wgpu::ImageSubresourceRange {
                    aspect: wgpu::TextureAspect::All,
                    base_mip_level: 0,
                    mip_level_count: None,
                    base_array_layer: 0,
                    array_layer_count: None,
                },
            );
        }
        queue.submit(Some(encoder.finish()));

        let sampler_desc = wgpu::SamplerDescriptor {
            address_mode_u: wgpu::AddressMode::ClampToEdge,
            address_mode_v: wgpu::AddressMode::ClampToEdge,
            address_mode_w: wgpu::AddressMode::ClampToEdge,
            mag_filter: wgpu::FilterMode::Nearest,
            min_filter: wgpu::FilterMode::Nearest,
            mipmap_filter: wgpu::FilterMode::Nearest,
            ..Default::default()
        };

        Texture::from_wgpu_tex(device, texture, Some(sampler_desc), None)
    }

    /// Create a new atlas.
    ///
    /// Size _must_ be a power of two.
    ///
    /// ## Panics
    /// Panics if `size` is not a power of two.
    pub fn new(
        slab: &SlabAllocator<WgpuRuntime>,
        size: wgpu::Extent3d,
        format: Option<wgpu::TextureFormat>,
        label: Option<&str>,
        usage: Option<wgpu::TextureUsages>,
    ) -> Self {
        let texture = Self::create_texture(slab.runtime(), size, format, label, usage);
        let num_layers = texture.texture.size().depth_or_array_layers as usize;
        let layers = vec![Layer::default(); num_layers];
        log::trace!("creating new atlas with dimensions {size:?}, {num_layers} layers");
        let descriptor = slab.new_value(AtlasDescriptor {
            size: UVec3::new(size.width, size.height, size.depth_or_array_layers),
        });
        let label = label.map(|s| s.to_owned());
        let blitter = AtlasBlitter::new(
            slab.device(),
            texture.texture.format(),
            wgpu::FilterMode::Linear,
        );
        Atlas {
            slab: slab.clone(),
            layers: Arc::new(RwLock::new(layers)),
            descriptor,
            label,
            blitter,
            texture_array: Arc::new(RwLock::new(texture)),
        }
    }

    pub fn descriptor_id(&self) -> Id<AtlasDescriptor> {
        self.descriptor.id()
    }

    pub fn len(&self) -> usize {
        // UNWRAP: panic on purpose
        let layers = self.layers.read().expect("atlas layers read");
        layers.iter().map(|layer| layer.frames.len()).sum::<usize>()
    }

    pub fn is_empty(&self) -> bool {
        // UNWRAP: panic on purpose
        self.len() == 0
    }

    /// Returns a reference to the current atlas texture array.
    pub fn get_texture(&self) -> impl Deref<Target = Texture> + '_ {
        // UNWRAP: panic on purpose
        self.texture_array.read().expect("atlas texture_array read")
    }

    pub fn get_layers(&self) -> impl Deref<Target = Vec<Layer>> + '_ {
        // UNWRAP: panic on purpose
        self.layers.read().expect("atlas layers read")
    }

    /// Reset this atlas with all new images.
    ///
    /// Any existing `Hybrid<AtlasTexture>`s will be invalidated.
    pub fn set_images(&self, images: &[AtlasImage]) -> Result<Vec<AtlasTexture>, AtlasError> {
        log::debug!("setting images");
        {
            // UNWRAP: panic on purpose
            let texture = self.texture_array.read().expect("atlas texture_array read");
            let mut guard = self.layers.write().expect("atlas layers write");
            let layers: &mut Vec<_> = guard.as_mut();
            let new_layers =
                vec![Layer::default(); texture.texture.size().depth_or_array_layers as usize];
            let _old_layers = std::mem::replace(layers, new_layers);
        }
        self.add_images(images)
    }

    pub fn get_size(&self) -> wgpu::Extent3d {
        // UNWRAP: POP
        self.texture_array
            .read()
            .expect("atlas texture_array read")
            .texture
            .size()
    }

    /// Add the given images
    pub fn add_images<'a>(
        &self,
        images: impl IntoIterator<Item = &'a AtlasImage>,
    ) -> Result<Vec<AtlasTexture>, AtlasError> {
        // UNWRAP: POP
        let mut layers = self.layers.write().expect("atlas layers write");
        let mut texture_array = self
            .texture_array
            .write()
            .expect("atlas texture_array write");
        let extent = texture_array.texture.size();

        let newly_packed_layers = pack_images(&layers, images, extent)
            .context(CannotPackTexturesSnafu { size: extent })?;

        let mut staged = StagedResources::try_staging(
            self.slab.runtime(),
            extent,
            newly_packed_layers,
            Some(&self.slab),
            &texture_array,
            self.label.as_deref(),
        )?;

        // Commit our newly staged values, now that everything is done.
        *texture_array = staged.texture;
        *layers = staged.layers;

        staged.image_additions.sort_by_key(|a| a.0);
        Ok(staged
            .image_additions
            .into_iter()
            .map(|a| AtlasTexture { descriptor: a.1 })
            .collect())
    }

    /// Add one image.
    ///
    /// If you have more than one image, you should use [`Atlas::add_images`],
    /// as every change in images causes a repacking, which might be
    /// expensive.
    pub fn add_image(&self, image: &AtlasImage) -> Result<AtlasTexture, AtlasError> {
        // UNWRAP: safe because we know there's at least one image
        Ok(self.add_images(Some(image))?.pop().unwrap())
    }

    /// Resize the atlas.
    ///
    /// This also distributes the images by size among all layers in an effort
    /// to reduce the likelyhood that packing the atlas may fail.
    ///
    /// ## Errors
    /// Errors if `size` has a width or height that is not a power of two, or
    /// are unequal
    pub fn resize(
        &self,
        runtime: impl AsRef<WgpuRuntime>,
        extent: wgpu::Extent3d,
    ) -> Result<(), AtlasError> {
        let mut layers = self.layers.write().expect("atlas layers write");
        let mut texture_array = self
            .texture_array
            .write()
            .expect("atlas texture_array write");

        let newly_packed_layers =
            pack_images(&layers, &[], extent).context(CannotPackTexturesSnafu { size: extent })?;

        let staged = StagedResources::try_staging(
            runtime,
            extent,
            newly_packed_layers,
            None::<&SlabAllocator<WgpuRuntime>>,
            &texture_array,
            self.label.as_deref(),
        )?;

        // Commit our newly staged values, now that everything is done.
        *texture_array = staged.texture;
        *layers = staged.layers;

        Ok(())
    }

    /// Perform upkeep on the atlas.
    ///
    /// This removes any `TextureFrame`s that have no references and repacks the
    /// atlas if any were removed.
    ///
    /// Returns `true` if the atlas texture was recreated.
    #[must_use]
    pub fn upkeep(&self, runtime: impl AsRef<WgpuRuntime>) -> bool {
        let mut total_dropped = 0;
        {
            let mut layers = self.layers.write().expect("atlas layers write");
            for (i, layer) in layers.iter_mut().enumerate() {
                let mut dropped = 0;
                layer.frames.retain(|entry| {
                    if entry.has_external_references() {
                        true
                    } else {
                        dropped += 1;
                        false
                    }
                });
                total_dropped += dropped;
                if dropped > 0 {
                    log::trace!("removed {dropped} frames from layer {i}");
                }
            }

            layers.len()
        };

        if total_dropped > 0 {
            log::trace!("repacking after dropping {total_dropped} frames from the atlas");
            // UNWRAP: safe because we can only remove frames from the atlas, which should
            // only make it easier to pack.
            self.resize(runtime.as_ref(), self.get_size()).unwrap();
            true
        } else {
            false
        }
    }

    /// Read the atlas image from the GPU into a [`CopiedTextureBuffer`].
    ///
    /// This is primarily for testing.
    ///
    /// ## Panics
    /// Panics if the pixels read from the GPU cannot be read.
    pub fn atlas_img_buffer(
        &self,
        runtime: impl AsRef<WgpuRuntime>,
        layer: u32,
    ) -> CopiedTextureBuffer {
        let runtime = runtime.as_ref();
        let tex = self.get_texture();
        let size = tex.texture.size();
        let (channels, subpixel_bytes) =
            crate::texture::wgpu_texture_format_channels_and_subpixel_bytes_todo(
                tex.texture.format(),
            );
        log::info!("atlas_texture_format: {:#?}", tex.texture.format());
        log::info!("atlas_texture_channels: {channels:#?}");
        log::info!("atlas_texture_subpixel_bytes: {subpixel_bytes:#?}");
        CopiedTextureBuffer::read_from(
            runtime,
            &tex.texture,
            size.width as usize,
            size.height as usize,
            channels as usize,
            subpixel_bytes as usize,
            0,
            Some(wgpu::Origin3d {
                x: 0,
                y: 0,
                z: layer,
            }),
        )
    }

    /// Read the atlas image from the GPU.
    ///
    /// This is primarily for testing.
    ///
    /// The resulting image will be in a **linear** color space.
    ///
    /// ## Panics
    /// Panics if the pixels read from the GPU cannot be converted into an
    /// `RgbaImage`.
    pub async fn atlas_img(&self, runtime: impl AsRef<WgpuRuntime>, layer: u32) -> RgbaImage {
        let runtime = runtime.as_ref();
        let buffer = self.atlas_img_buffer(runtime, layer);
        buffer.into_linear_rgba(&runtime.device).await.unwrap()
    }

    // It's ok to hold this lock because this is just for testing.
    #[allow(clippy::await_holding_lock)]
    pub async fn read_images(&self, runtime: impl AsRef<WgpuRuntime>) -> Vec<RgbaImage> {
        let mut images = vec![];
        for i in 0..self.layers.read().expect("atlas layers read").len() {
            images.push(self.atlas_img(runtime.as_ref(), i as u32).await);
        }
        images
    }

    /// Update the given [`AtlasTexture`] with a
    /// [`Texture`](crate::texture::Texture).
    ///
    /// This will blit the `Texture` into the frame of the [`Atlas`] pointed to
    /// by the `AtlasTexture`.
    ///
    /// Returns a submission index that can be polled with
    /// [`wgpu::Device::poll`].
    pub fn update_texture(
        &self,
        atlas_texture: &AtlasTexture,
        source_texture: &texture::Texture,
    ) -> Result<wgpu::SubmissionIndex, AtlasError> {
        self.update_textures(Some((atlas_texture, source_texture)))
    }

    /// Update the given [`AtlasTexture`]s with
    /// [`Texture`](crate::texture::Texture)s.
    ///
    /// This will blit the `Texture` into the frame of the [`Atlas`] pointed to
    /// by the `AtlasTexture`.
    ///
    /// Returns a submission index that can be polled with
    /// [`wgpu::Device::poll`].
    pub fn update_textures<'a>(
        &self,
        updates: impl IntoIterator<Item = (&'a AtlasTexture, &'a texture::Texture)>,
    ) -> Result<wgpu::SubmissionIndex, AtlasError> {
        let updates = updates.into_iter().collect::<Vec<_>>();
        let op = AtlasBlittingOperation::new(&self.blitter, self, updates.len());
        let runtime = self.slab.runtime();
        let mut encoder = runtime
            .device
            .create_command_encoder(&wgpu::CommandEncoderDescriptor {
                label: Some("Atlas::update_texture"),
            });
        for (i, (atlas_texture, source_texture)) in updates.into_iter().enumerate() {
            op.run(
                runtime,
                &mut encoder,
                source_texture,
                i as u32,
                self,
                atlas_texture,
            )?;
        }
        Ok(runtime.queue.submit(Some(encoder.finish())))
    }

    /// Update the given [`AtlasTexture`]s with new data.
    ///
    /// This will blit the image data into the frame of the [`Atlas`] pointed to
    /// by the `AtlasTexture`.
    ///
    /// Returns a submission index that can be polled with
    /// [`wgpu::Device::poll`].
    pub fn update_images<'a>(
        &self,
        updates: impl IntoIterator<Item = (&'a AtlasTexture, impl Into<AtlasImage>)>,
    ) -> Result<wgpu::SubmissionIndex, AtlasError> {
        let (atlas_textures, images): (Vec<_>, Vec<_>) = updates.into_iter().unzip();
        let mut textures = vec![];
        for image in images.into_iter() {
            let image: AtlasImage = image.into();
            let atlas_format = self.get_texture().texture.format();
            let bytes = super::atlas_image::convert_pixels(
                image.pixels,
                image.format,
                atlas_format,
                image.apply_linear_transfer,
            );
            let (channels, subpixel_bytes) =
                texture::wgpu_texture_format_channels_and_subpixel_bytes(atlas_format)
                    .context(TextureSnafu)?;
            let texture = texture::Texture::new_with(
                self.slab.runtime(),
                Some("atlas-image-update"),
                Some(wgpu::TextureUsages::TEXTURE_BINDING | wgpu::TextureUsages::COPY_DST),
                None,
                atlas_format,
                channels,
                subpixel_bytes,
                image.size.x,
                image.size.y,
                1,
                &bytes,
            );
            textures.push(texture);
        }
        self.update_textures(atlas_textures.into_iter().zip(textures.iter()))
    }

    /// Update the given [`AtlasTexture`]s with new data.
    ///
    /// This will blit the image data into the frame of the [`Atlas`] pointed to
    /// by the `AtlasTexture`.
    ///
    /// Returns a submission index that can be polled with
    /// [`wgpu::Device::poll`].
    pub fn update_image(
        &self,
        atlas_texture: &AtlasTexture,
        source_image: impl Into<AtlasImage>,
    ) -> Result<wgpu::SubmissionIndex, AtlasError> {
        self.update_images(Some((atlas_texture, source_image)))
    }
}

fn pack_images<'a>(
    layers: &[Layer],
    images: impl IntoIterator<Item = &'a AtlasImage>,
    extent: wgpu::Extent3d,
) -> Option<Vec<crunch::PackedItems<AnotherPacking<'a>>>> {
    let mut new_packing: Vec<AnotherPacking> = {
        let layers: Vec<_> = layers.to_vec();
        layers
            .into_iter()
            .flat_map(|layer| layer.frames)
            // Filter out any textures that have been completely dropped
            // by the user.
            .filter_map(|tex| {
                if tex.has_external_references() {
                    Some(AnotherPacking::Internal(tex))
                } else {
                    None
                }
            })
            .chain(
                images
                    .into_iter()
                    .enumerate()
                    .map(|(i, image)| AnotherPacking::Img {
                        original_index: i,
                        image,
                    }),
            )
            .collect()
    };
    new_packing.sort_by_key(|a| a.size().length_squared());
    let total_images = new_packing.len();
    let new_packing_layers: Vec<Vec<AnotherPacking>> =
        fan_split_n(extent.depth_or_array_layers as usize, new_packing);
    log::trace!(
        "packing {total_images} textures into {} layers",
        new_packing_layers.len()
    );
    let mut newly_packed_layers: Vec<crunch::PackedItems<_>> = vec![];
    for (i, new_layer) in new_packing_layers.into_iter().enumerate() {
        log::trace!("  packing layer {i} into power of 2 {}", extent.width);
        let packed = crunch::pack_into_po2(
            extent.width as usize,
            new_layer.into_iter().map(|p| {
                let size = p.size();
                crunch::Item::new(p, size.x as usize, size.y as usize, crunch::Rotation::None)
            }),
        )
        .ok()?;
        log::trace!("  layer {i} packed with {} textures", packed.items.len());
        newly_packed_layers.push(packed);
    }
    Some(newly_packed_layers)
}

/// Internal atlas resources.
struct StagedResources {
    texture: Texture,
    image_additions: Vec<(usize, Hybrid<AtlasTextureDescriptor>)>,
    layers: Vec<Layer>,
}

impl StagedResources {
    /// Stage the packed images, copying them to the next texture.
    fn try_staging(
        runtime: impl AsRef<WgpuRuntime>,
        extent: wgpu::Extent3d,
        newly_packed_layers: Vec<crunch::PackedItems<AnotherPacking>>,
        slab: Option<&SlabAllocator<WgpuRuntime>>,
        old_texture_array: &Texture,
        label: Option<&str>,
    ) -> Result<Self, AtlasError> {
        let runtime = runtime.as_ref();
        let new_texture_array = Atlas::create_texture(
            runtime,
            extent,
            Some(old_texture_array.texture.format()),
            label,
            Some(old_texture_array.texture.usage()),
        );
        let mut output = vec![];
        let mut encoder = runtime
            .device
            .create_command_encoder(&wgpu::CommandEncoderDescriptor {
                label: Some("atlas staging"),
            });
        let mut temporary_layers = vec![Layer::default(); extent.depth_or_array_layers as usize];
        for (layer_index, packed_items) in newly_packed_layers.into_iter().enumerate() {
            if packed_items.items.is_empty() {
                continue;
            }
            // UNWRAP: safe because we know this index exists because we created it above
            let layer = temporary_layers.get_mut(layer_index).unwrap();
            for (frame_index, crunch::PackedItem { data: item, rect }) in
                packed_items.items.into_iter().enumerate()
            {
                let offset_px = UVec2::new(rect.x as u32, rect.y as u32);
                let size_px = UVec2::new(rect.w as u32, rect.h as u32);

                match item {
                    AnotherPacking::Img {
                        original_index,
                        image,
                    } => {
                        let atlas_texture = AtlasTextureDescriptor {
                            offset_px,
                            size_px,
                            frame_index: frame_index as u32,
                            layer_index: layer_index as u32,
                            ..Default::default()
                        };
                        let texture = slab
                            .context(StagingMissingSlabSnafu)?
                            .new_value(atlas_texture);
                        layer
                            .frames
                            .push(InternalAtlasTexture::from_hybrid(&texture));
                        output.push((original_index, texture));

                        let bytes = convert_pixels(
                            image.pixels.clone(),
                            image.format,
                            old_texture_array.texture.format(),
                            image.apply_linear_transfer,
                        );

                        let origin = wgpu::Origin3d {
                            x: offset_px.x,
                            y: offset_px.y,
                            z: layer_index as u32,
                        };
                        let size = wgpu::Extent3d {
                            width: size_px.x,
                            height: size_px.y,
                            depth_or_array_layers: 1,
                        };
                        log::trace!(
                            "  writing image data to frame {frame_index} in layer {layer_index}"
                        );
                        log::trace!("    frame: {atlas_texture:?}");
                        log::trace!("    origin: {origin:?}");
                        log::trace!("    size: {size:?}");

                        // write the new image from the CPU to the new texture
                        runtime.queue.write_texture(
                            wgpu::TexelCopyTextureInfo {
                                texture: &new_texture_array.texture,
                                mip_level: 0,
                                origin,
                                aspect: wgpu::TextureAspect::All,
                            },
                            &bytes,
                            wgpu::TexelCopyBufferLayout {
                                offset: 0,
                                bytes_per_row: Some(4 * size_px.x),
                                rows_per_image: Some(size_px.y),
                            },
                            size,
                        );
                    }
                    AnotherPacking::Internal(mut texture) => {
                        let prev_t = texture.cache;
                        let mut t = texture.cache;
                        debug_assert_eq!(t.size_px, size_px);
                        // copy the frame from the old texture to the new texture
                        // in a new destination
                        encoder.copy_texture_to_texture(
                            wgpu::TexelCopyTextureInfo {
                                texture: &old_texture_array.texture,
                                mip_level: 0,
                                origin: t.origin(),
                                aspect: wgpu::TextureAspect::All,
                            },
                            wgpu::TexelCopyTextureInfo {
                                texture: &new_texture_array.texture,
                                mip_level: 0,
                                origin: wgpu::Origin3d {
                                    x: offset_px.x,
                                    y: offset_px.y,
                                    z: layer_index as u32,
                                },
                                aspect: wgpu::TextureAspect::All,
                            },
                            t.size_as_extent(),
                        );

                        t.layer_index = layer_index as u32;
                        t.frame_index = frame_index as u32;
                        t.offset_px = offset_px;

                        log::trace!(
                            "  copied previous frame {}",
                            pretty_assertions::Comparison::new(&prev_t, &t)
                        );

                        texture.set(t);
                        layer.frames.push(texture);
                    }
                }
            }
        }
        runtime.queue.submit(Some(encoder.finish()));

        Ok(Self {
            texture: new_texture_array,
            image_additions: output,
            layers: temporary_layers,
        })
    }
}

/// A reusable blitting operation that copies a source texture into a specific
/// frame of an [`Atlas`].
#[derive(Clone)]
pub struct AtlasBlittingOperation {
    atlas_slab_buffer: Arc<Mutex<SlabBuffer<wgpu::Buffer>>>,
    pipeline: Arc<wgpu::RenderPipeline>,
    bindgroups: Arc<Vec<ManagedBindGroup>>,
    bindgroup_layout: Arc<wgpu::BindGroupLayout>,
    sampler: Arc<wgpu::Sampler>,
    from_texture_id: Arc<AtomicUsize>,
    pub(crate) desc: Hybrid<AtlasBlittingDescriptor>,
}

impl AtlasBlittingOperation {
    pub fn new(
        blitter: &AtlasBlitter,
        into_atlas: &Atlas,
        source_layers: usize,
    ) -> AtlasBlittingOperation {
        AtlasBlittingOperation {
            desc: into_atlas
                .slab
                .new_value(AtlasBlittingDescriptor::default()),
            atlas_slab_buffer: Arc::new(Mutex::new(into_atlas.slab.commit())),
            bindgroups: {
                let mut bgs = vec![];
                for _ in 0..source_layers {
                    bgs.push(ManagedBindGroup::default());
                }
                Arc::new(bgs)
            },
            pipeline: blitter.pipeline.clone(),
            sampler: blitter.sampler.clone(),
            bindgroup_layout: blitter.bind_group_layout.clone(),
            from_texture_id: Default::default(),
        }
    }

    /// Copies the data from texture this [`AtlasBlittingOperation`] was created
    /// with into the atlas.
    ///
    /// The original items used to create the inner bind group are required
    /// here, to determine whether or not the bind group needs to be
    /// invalidated.
    pub fn run(
        &self,
        runtime: impl AsRef<WgpuRuntime>,
        encoder: &mut wgpu::CommandEncoder,
        from_texture: &crate::texture::Texture,
        from_layer: u32,
        to_atlas: &Atlas,
        atlas_texture: &AtlasTexture,
    ) -> Result<(), AtlasError> {
        let runtime = runtime.as_ref();

        // update the descriptor
        self.desc.set(AtlasBlittingDescriptor {
            atlas_texture_id: atlas_texture.id(),
            atlas_desc_id: to_atlas.descriptor_id(),
        });
        // sync the update
        let _ = to_atlas.slab.commit();

        let to_atlas_texture = to_atlas.get_texture();
        let mut atlas_slab_buffer = self
            .atlas_slab_buffer
            .lock()
            .expect("atlas slab buffer lock");
        let atlas_slab_invalid = atlas_slab_buffer.update_if_invalid();
        let from_texture_has_been_replaced = {
            let prev_id = self
                .from_texture_id
                .swap(from_texture.id(), std::sync::atomic::Ordering::Relaxed);
            from_texture.id() != prev_id
        };
        let should_invalidate = atlas_slab_invalid || from_texture_has_been_replaced;
        let view = from_texture
            .texture
            .create_view(&wgpu::TextureViewDescriptor {
                label: Some("atlas-blitting"),
                base_array_layer: from_layer,
                array_layer_count: Some(1),
                dimension: Some(wgpu::TextureViewDimension::D2),
                ..Default::default()
            });
        let bindgroup = self
            .bindgroups
            .get(from_layer as usize)
            .context(MissingBindgroupSnafu { layer: from_layer })?
            .get(should_invalidate, || {
                runtime
                    .device
                    .create_bind_group(&wgpu::BindGroupDescriptor {
                        label: Some("atlas-blitting"),
                        layout: &self.bindgroup_layout,
                        entries: &[
                            wgpu::BindGroupEntry {
                                binding: 0,
                                resource: wgpu::BindingResource::Buffer(
                                    atlas_slab_buffer.deref().as_entire_buffer_binding(),
                                ),
                            },
                            wgpu::BindGroupEntry {
                                binding: 1,
                                resource: wgpu::BindingResource::TextureView(&view),
                            },
                            wgpu::BindGroupEntry {
                                binding: 2,
                                resource: wgpu::BindingResource::Sampler(&self.sampler),
                            },
                        ],
                    })
            });

        let atlas_texture = atlas_texture.descriptor();
        let atlas_view = to_atlas_texture
            .texture
            .create_view(&wgpu::TextureViewDescriptor {
                label: Some("atlas-blitting"),
                format: None,
                dimension: Some(wgpu::TextureViewDimension::D2),
                usage: None,
                aspect: wgpu::TextureAspect::All,
                base_mip_level: 0,
                mip_level_count: None,
                base_array_layer: atlas_texture.layer_index,
                array_layer_count: Some(1),
            });
        let mut pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
            label: Some("atlas-blitter"),
            color_attachments: &[Some(wgpu::RenderPassColorAttachment {
                view: &atlas_view,
                resolve_target: None,
                ops: wgpu::Operations {
                    load: wgpu::LoadOp::Load,
                    store: wgpu::StoreOp::Store,
                },
                depth_slice: None,
            })],
            depth_stencil_attachment: None,
            timestamp_writes: None,
            occlusion_query_set: None,
        });
        pass.set_pipeline(&self.pipeline);
        pass.set_bind_group(0, Some(bindgroup.as_ref()), &[]);
        let id = self.desc.id();
        pass.draw(0..6, id.inner()..id.inner() + 1);
        Ok(())
    }
}

/// A texture blitting utility.
///
/// [`AtlasBlitter`] copies textures to specific frames within the texture
/// atlas.
#[derive(Clone)]
pub struct AtlasBlitter {
    pipeline: Arc<wgpu::RenderPipeline>,
    bind_group_layout: Arc<wgpu::BindGroupLayout>,
    sampler: Arc<wgpu::Sampler>,
}

impl AtlasBlitter {
    /// Creates a new [`AtlasBlitter`].
    ///
    /// # Arguments
    /// - `device` - A [`wgpu::Device`]
    /// - `format` - The [`wgpu::TextureFormat`] of the atlas being updated.
    /// - `mag_filter` - The filtering algorithm to use when magnifying. This is
    ///   used when the input source is smaller than the destination.
    pub fn new(
        device: &wgpu::Device,
        format: wgpu::TextureFormat,
        mag_filter: wgpu::FilterMode,
    ) -> Self {
        let sampler = device.create_sampler(&wgpu::SamplerDescriptor {
            label: Some("atlas-blitter"),
            address_mode_u: wgpu::AddressMode::ClampToEdge,
            address_mode_v: wgpu::AddressMode::ClampToEdge,
            address_mode_w: wgpu::AddressMode::ClampToEdge,
            mag_filter,
            ..Default::default()
        });

        let bind_group_layout = device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
            label: Some("atlas-blitter"),
            entries: &[
                wgpu::BindGroupLayoutEntry {
                    binding: 0,
                    visibility: wgpu::ShaderStages::VERTEX,
                    ty: wgpu::BindingType::Buffer {
                        ty: wgpu::BufferBindingType::Storage { read_only: true },
                        has_dynamic_offset: false,
                        min_binding_size: None,
                    },
                    count: None,
                },
                wgpu::BindGroupLayoutEntry {
                    binding: 1,
                    visibility: wgpu::ShaderStages::FRAGMENT,
                    ty: wgpu::BindingType::Texture {
                        sample_type: wgpu::TextureSampleType::Float {
                            filterable: mag_filter == wgpu::FilterMode::Linear,
                        },
                        view_dimension: wgpu::TextureViewDimension::D2,
                        multisampled: false,
                    },
                    count: None,
                },
                wgpu::BindGroupLayoutEntry {
                    binding: 2,
                    visibility: wgpu::ShaderStages::FRAGMENT,
                    ty: wgpu::BindingType::Sampler(
                        if mag_filter == wgpu::FilterMode::Linear {
                            wgpu::SamplerBindingType::Filtering
                        } else {
                            wgpu::SamplerBindingType::NonFiltering
                        },
                    ),
                    count: None,
                },
            ],
        });

        let pipeline_layout = device.create_pipeline
Download .txt
gitextract_l0v6lfp5/

├── .cargo/
│   └── config.toml
├── .gitattributes
├── .github/
│   ├── FUNDING.yml
│   └── workflows/
│       └── push.yaml
├── .gitignore
├── .helix/
│   └── snippets/
│       └── rust.json
├── AGENTS.md
├── CONTRIBUTING.md
├── Cargo.toml
├── LICENSE
├── NOTES.md
├── README.md
├── blender/
│   ├── cheetah_cone.blend
│   ├── normal_mapping_brick_sphere.blend
│   ├── pedestal.blend
│   ├── shadow_mapping_point.blend
│   ├── shadow_mapping_points.blend
│   ├── shadow_mapping_sanity_spot.blend
│   ├── shadow_mapping_spots.blend
│   ├── spot_lights.blend
│   └── spot_one.blend
├── clippy.toml
├── crates/
│   ├── example/
│   │   ├── Cargo.toml
│   │   └── src/
│   │       ├── camera.rs
│   │       ├── lib.rs
│   │       ├── main.rs
│   │       ├── time.rs
│   │       └── utils.rs
│   ├── example-culling/
│   │   ├── Cargo.toml
│   │   └── src/
│   │       └── main.rs
│   ├── example-wasm/
│   │   ├── .gitignore
│   │   ├── Cargo.toml
│   │   ├── Trunk.toml
│   │   ├── index.html
│   │   └── src/
│   │       ├── event.rs
│   │       ├── lib.rs
│   │       └── req_animation_frame.rs
│   ├── examples/
│   │   ├── Cargo.toml
│   │   └── src/
│   │       ├── context.rs
│   │       ├── gltf.rs
│   │       ├── lib.rs
│   │       ├── lighting.rs
│   │       ├── skybox.rs
│   │       └── stage.rs
│   ├── img-diff/
│   │   ├── Cargo.toml
│   │   └── src/
│   │       └── lib.rs
│   ├── loading-bytes/
│   │   ├── Cargo.toml
│   │   ├── README.md
│   │   └── src/
│   │       └── lib.rs
│   ├── renderling/
│   │   ├── Cargo.toml
│   │   ├── shaders/
│   │   │   ├── atlas-shader-atlas_blit_fragment.spv
│   │   │   ├── atlas-shader-atlas_blit_vertex.spv
│   │   │   ├── bloom-shader-bloom_downsample_fragment.spv
│   │   │   ├── bloom-shader-bloom_mix_fragment.spv
│   │   │   ├── bloom-shader-bloom_upsample_fragment.spv
│   │   │   ├── bloom-shader-bloom_vertex.spv
│   │   │   ├── compositor-compositor_fragment.spv
│   │   │   ├── compositor-compositor_vertex.spv
│   │   │   ├── convolution-shader-brdf_lut_convolution_fragment.spv
│   │   │   ├── convolution-shader-brdf_lut_convolution_vertex.spv
│   │   │   ├── convolution-shader-generate_mipmap_fragment.spv
│   │   │   ├── convolution-shader-generate_mipmap_vertex.spv
│   │   │   ├── convolution-shader-prefilter_environment_cubemap_fragment.spv
│   │   │   ├── convolution-shader-prefilter_environment_cubemap_vertex.spv
│   │   │   ├── cubemap-shader-cubemap_sampling_test_fragment.spv
│   │   │   ├── cubemap-shader-cubemap_sampling_test_vertex.spv
│   │   │   ├── cull-shader-compute_copy_depth_to_pyramid.spv
│   │   │   ├── cull-shader-compute_copy_depth_to_pyramid_multisampled.spv
│   │   │   ├── cull-shader-compute_culling.spv
│   │   │   ├── cull-shader-compute_downsample_depth_pyramid.spv
│   │   │   ├── debug-shader-debug_overlay_fragment.spv
│   │   │   ├── debug-shader-debug_overlay_vertex.spv
│   │   │   ├── light-shader-light_tiling_bin_lights.spv
│   │   │   ├── light-shader-light_tiling_clear_tiles.spv
│   │   │   ├── light-shader-light_tiling_compute_tile_min_and_max_depth.spv
│   │   │   ├── light-shader-light_tiling_compute_tile_min_and_max_depth_multisampled.spv
│   │   │   ├── light-shader-light_tiling_depth_pre_pass.spv
│   │   │   ├── light-shader-shadow_mapping_fragment.spv
│   │   │   ├── light-shader-shadow_mapping_vertex.spv
│   │   │   ├── manifest.json
│   │   │   ├── pbr-ibl-shader-di_convolution_fragment.spv
│   │   │   ├── primitive-shader-primitive_fragment.spv
│   │   │   ├── primitive-shader-primitive_vertex.spv
│   │   │   ├── skybox-shader-skybox_cubemap_fragment.spv
│   │   │   ├── skybox-shader-skybox_cubemap_vertex.spv
│   │   │   ├── skybox-shader-skybox_equirectangular_fragment.spv
│   │   │   ├── skybox-shader-skybox_vertex.spv
│   │   │   ├── tonemapping-tonemapping_fragment.spv
│   │   │   ├── tonemapping-tonemapping_vertex.spv
│   │   │   ├── tutorial-implicit_isosceles_vertex.spv
│   │   │   ├── tutorial-passthru_fragment.spv
│   │   │   ├── tutorial-slabbed_renderlet.spv
│   │   │   ├── tutorial-slabbed_vertices.spv
│   │   │   ├── tutorial-slabbed_vertices_no_instance.spv
│   │   │   ├── ui_slab-shader-ui_fragment.spv
│   │   │   └── ui_slab-shader-ui_vertex.spv
│   │   ├── src/
│   │   │   ├── atlas/
│   │   │   │   ├── atlas_image.rs
│   │   │   │   ├── cpu.rs
│   │   │   │   └── shader.rs
│   │   │   ├── atlas.rs
│   │   │   ├── bindgroup.rs
│   │   │   ├── bloom/
│   │   │   │   ├── cpu.rs
│   │   │   │   └── shader.rs
│   │   │   ├── bloom.rs
│   │   │   ├── build.rs
│   │   │   ├── bvol.rs
│   │   │   ├── camera/
│   │   │   │   ├── cpu.rs
│   │   │   │   └── shader.rs
│   │   │   ├── camera.rs
│   │   │   ├── color.rs
│   │   │   ├── compositor/
│   │   │   │   └── cpu.rs
│   │   │   ├── compositor.rs
│   │   │   ├── context.rs
│   │   │   ├── convolution.rs
│   │   │   ├── cubemap/
│   │   │   │   ├── cpu.rs
│   │   │   │   └── shader.rs
│   │   │   ├── cubemap.rs
│   │   │   ├── cull/
│   │   │   │   ├── cpu.rs
│   │   │   │   └── shader.rs
│   │   │   ├── cull.rs
│   │   │   ├── debug/
│   │   │   │   └── cpu.rs
│   │   │   ├── debug.rs
│   │   │   ├── draw/
│   │   │   │   └── cpu.rs
│   │   │   ├── draw.rs
│   │   │   ├── geometry/
│   │   │   │   ├── cpu.rs
│   │   │   │   └── shader.rs
│   │   │   ├── geometry.rs
│   │   │   ├── gltf/
│   │   │   │   └── anime.rs
│   │   │   ├── gltf.rs
│   │   │   ├── internal/
│   │   │   │   └── cpu.rs
│   │   │   ├── internal.rs
│   │   │   ├── lib.rs
│   │   │   ├── light/
│   │   │   │   ├── cpu/
│   │   │   │   │   └── test.rs
│   │   │   │   ├── cpu.rs
│   │   │   │   ├── shader.rs
│   │   │   │   ├── shadow_map.rs
│   │   │   │   └── tiling.rs
│   │   │   ├── light.rs
│   │   │   ├── linkage/
│   │   │   │   ├── atlas_blit_fragment.rs
│   │   │   │   ├── atlas_blit_vertex.rs
│   │   │   │   ├── bloom_downsample_fragment.rs
│   │   │   │   ├── bloom_mix_fragment.rs
│   │   │   │   ├── bloom_upsample_fragment.rs
│   │   │   │   ├── bloom_vertex.rs
│   │   │   │   ├── brdf_lut_convolution_fragment.rs
│   │   │   │   ├── brdf_lut_convolution_vertex.rs
│   │   │   │   ├── compositor_fragment.rs
│   │   │   │   ├── compositor_vertex.rs
│   │   │   │   ├── compute_copy_depth_to_pyramid.rs
│   │   │   │   ├── compute_copy_depth_to_pyramid_multisampled.rs
│   │   │   │   ├── compute_culling.rs
│   │   │   │   ├── compute_downsample_depth_pyramid.rs
│   │   │   │   ├── cubemap_sampling_test_fragment.rs
│   │   │   │   ├── cubemap_sampling_test_vertex.rs
│   │   │   │   ├── debug_overlay_fragment.rs
│   │   │   │   ├── debug_overlay_vertex.rs
│   │   │   │   ├── di_convolution_fragment.rs
│   │   │   │   ├── generate_mipmap_fragment.rs
│   │   │   │   ├── generate_mipmap_vertex.rs
│   │   │   │   ├── implicit_isosceles_vertex.rs
│   │   │   │   ├── light_tiling_bin_lights.rs
│   │   │   │   ├── light_tiling_clear_tiles.rs
│   │   │   │   ├── light_tiling_compute_tile_min_and_max_depth.rs
│   │   │   │   ├── light_tiling_compute_tile_min_and_max_depth_multisampled.rs
│   │   │   │   ├── light_tiling_depth_pre_pass.rs
│   │   │   │   ├── passthru_fragment.rs
│   │   │   │   ├── prefilter_environment_cubemap_fragment.rs
│   │   │   │   ├── prefilter_environment_cubemap_vertex.rs
│   │   │   │   ├── primitive_fragment.rs
│   │   │   │   ├── primitive_vertex.rs
│   │   │   │   ├── shadow_mapping_fragment.rs
│   │   │   │   ├── shadow_mapping_vertex.rs
│   │   │   │   ├── skybox_cubemap_fragment.rs
│   │   │   │   ├── skybox_cubemap_vertex.rs
│   │   │   │   ├── skybox_equirectangular_fragment.rs
│   │   │   │   ├── skybox_vertex.rs
│   │   │   │   ├── slabbed_renderlet.rs
│   │   │   │   ├── slabbed_vertices.rs
│   │   │   │   ├── slabbed_vertices_no_instance.rs
│   │   │   │   ├── tonemapping_fragment.rs
│   │   │   │   ├── tonemapping_vertex.rs
│   │   │   │   ├── ui_fragment.rs
│   │   │   │   └── ui_vertex.rs
│   │   │   ├── linkage.rs
│   │   │   ├── material/
│   │   │   │   └── cpu.rs
│   │   │   ├── material.rs
│   │   │   ├── math.rs
│   │   │   ├── mesh.rs
│   │   │   ├── pbr/
│   │   │   │   ├── brdf/
│   │   │   │   │   ├── cpu.rs
│   │   │   │   │   └── shader.rs
│   │   │   │   ├── brdf.rs
│   │   │   │   ├── debug.rs
│   │   │   │   ├── ibl/
│   │   │   │   │   ├── cpu.rs
│   │   │   │   │   ├── diffuse_irradiance.rs
│   │   │   │   │   └── shader.rs
│   │   │   │   ├── ibl.rs
│   │   │   │   └── shader.rs
│   │   │   ├── pbr.rs
│   │   │   ├── primitive/
│   │   │   │   ├── cpu.rs
│   │   │   │   └── shader.rs
│   │   │   ├── primitive.rs
│   │   │   ├── sdf.rs
│   │   │   ├── skybox/
│   │   │   │   ├── cpu.rs
│   │   │   │   └── shader.rs
│   │   │   ├── skybox.rs
│   │   │   ├── stage/
│   │   │   │   └── cpu.rs
│   │   │   ├── stage.rs
│   │   │   ├── sync.rs
│   │   │   ├── texture/
│   │   │   │   └── mips.rs
│   │   │   ├── texture.rs
│   │   │   ├── tonemapping/
│   │   │   │   └── cpu.rs
│   │   │   ├── tonemapping.rs
│   │   │   ├── transform/
│   │   │   │   └── cpu.rs
│   │   │   ├── transform.rs
│   │   │   ├── tutorial/
│   │   │   │   ├── implicit_isosceles_vertex.wgsl
│   │   │   │   └── passthru.wgsl
│   │   │   ├── tutorial.rs
│   │   │   ├── types.rs
│   │   │   └── ui_slab/
│   │   │       ├── mod.rs
│   │   │       └── shader.rs
│   │   ├── tests/
│   │   │   └── wasm.rs
│   │   └── webdriver.json
│   ├── renderling-build/
│   │   ├── Cargo.toml
│   │   └── src/
│   │       └── lib.rs
│   ├── renderling-ui/
│   │   ├── Cargo.toml
│   │   └── src/
│   │       ├── lib.rs
│   │       ├── renderer.rs
│   │       └── test.rs
│   ├── sandbox/
│   │   ├── Cargo.toml
│   │   └── src/
│   │       └── main.rs
│   ├── wire-types/
│   │   ├── Cargo.toml
│   │   └── src/
│   │       └── lib.rs
│   └── xtask/
│       ├── Cargo.toml
│       └── src/
│           ├── deps.rs
│           ├── main.rs
│           └── server.rs
├── fonts/
│   └── Font Awesome 6 Free-Regular-400.otf
├── gltf/
│   ├── DamagedHelmet.glb
│   ├── EmissiveStrengthTest.glb
│   ├── Fox.glb
│   ├── SimpleSkin.gltf
│   ├── animated_triangle.gltf
│   ├── box_animated.glb
│   ├── cheetah_cone.glb
│   ├── cube.glb
│   ├── four_spotlights.glb
│   ├── gltfTutorial_003_MinimalGltfFile.gltf
│   ├── gltfTutorial_008_SimpleMeshes.gltf
│   ├── gltfTutorial_013_SimpleTexture.gltf
│   ├── gltfTutorial_017_SimpleMorphTarget.gltf
│   ├── gltfTutorial_019_SimpleSkin.gltf
│   ├── light_tiling_test.glb
│   ├── marble_bust_1k.glb
│   ├── normal_mapping_brick_sphere.glb
│   ├── pedestal.glb
│   ├── shadow_mapping_only_cuboid.gltf
│   ├── shadow_mapping_only_cuboid_red_and_blue.gltf
│   ├── shadow_mapping_point.glb
│   ├── shadow_mapping_points.glb
│   ├── shadow_mapping_sanity.glb
│   ├── shadow_mapping_sanity.gltf
│   ├── shadow_mapping_sanity_camera.gltf
│   ├── shadow_mapping_spots.glb
│   ├── simple_morph_triangle.gltf
│   ├── spot_lights.glb
│   └── spot_one.glb
├── img/
│   └── hdr/
│       ├── helipad.hdr
│       ├── night.hdr
│       └── resting_place.hdr
├── manual/
│   ├── .gitignore
│   ├── book.toml
│   └── src/
│       ├── SUMMARY.md
│       ├── context.md
│       ├── gltf.md
│       ├── lighting/
│       │   ├── analytical.md
│       │   └── ibl.md
│       ├── lighting.md
│       ├── reflinks.md
│       ├── setup.md
│       ├── skybox.md
│       ├── stage.md
│       └── welcome.md
└── rustfmt.toml
Download .txt
SYMBOL INDEX (2182 symbols across 140 files)

FILE: crates/example-culling/src/main.rs
  constant MIN_SIZE (line 26) | const MIN_SIZE: f32 = 0.5;
  constant MAX_SIZE (line 27) | const MAX_SIZE: f32 = 4.0;
  constant MAX_DIST (line 28) | const MAX_DIST: f32 = 40.0;
  constant BOUNDS (line 29) | const BOUNDS: Aabb = Aabb {
  type AppCamera (line 34) | struct AppCamera(Camera);
  type FrustumCamera (line 35) | struct FrustumCamera(CameraDescriptor);
  type Type (line 37) | type Type = Primitive;
  type CullingExample (line 40) | struct CullingExample {
    method make_aabb (line 54) | fn make_aabb(center: Vec3, half_size: Vec3) -> Aabb {
    method make_aabbs (line 60) | fn make_aabbs(
  method resumed (line 119) | fn resumed(&mut self, _event_loop: &winit::event_loop::ActiveEventLoop) {
  method window_event (line 123) | fn window_event(
  method device_event (line 169) | fn device_event(
  method new (line 180) | fn new(
  method render (line 269) | fn render(&mut self, ctx: &Context) {
  function main (line 279) | fn main() {

FILE: crates/example-wasm/src/event.rs
  type MaybeCallback (line 14) | type MaybeCallback = Option<Arc<Closure<dyn FnMut(JsValue)>>>;
  type WebCallback (line 16) | struct WebCallback {
  method drop (line 25) | fn drop(&mut self) {
  type Item (line 40) | type Item = web_sys::Event;
  method poll_next (line 42) | fn poll_next(
  function event_stream (line 60) | pub fn event_stream(

FILE: crates/example-wasm/src/lib.rs
  constant HDR_IMAGE_BYTES (line 10) | const HDR_IMAGE_BYTES: &[u8] = include_bytes!("../../../img/hdr/helipad....
  constant GLTF_FOX_BYTES (line 11) | const GLTF_FOX_BYTES: &[u8] = include_bytes!("../../../gltf/Fox.glb");
  function surface_from_canvas (line 13) | fn surface_from_canvas(_canvas: HtmlCanvasElement) -> Option<wgpu::Surfa...
  type App (line 24) | pub struct App {
    method tick (line 35) | fn tick(&self) {
  function main (line 44) | pub async fn main() {

FILE: crates/example-wasm/src/req_animation_frame.rs
  function req_animation_frame (line 6) | fn req_animation_frame(f: &Closure<dyn FnMut(JsValue)>) {
  function request_animation_frame (line 19) | fn request_animation_frame(mut f: impl FnMut(JsValue) + 'static) {
  type NextFrame (line 35) | struct NextFrame {
    type Output (line 42) | type Output = f64;
    method poll (line 44) | fn poll(
  function next_animation_frame (line 63) | pub fn next_animation_frame() -> impl std::future::Future<Output = f64> {

FILE: crates/example/src/camera.rs
  constant RADIUS_SCROLL_DAMPENING (line 11) | const RADIUS_SCROLL_DAMPENING: f32 = 0.001;
  constant DX_DY_DRAG_DAMPENING (line 12) | const DX_DY_DRAG_DAMPENING: f32 = 0.01;
  type CameraControl (line 15) | pub enum CameraControl {
  type Err (line 22) | type Err = String;
  method from_str (line 24) | fn from_str(s: &str) -> Result<Self, Self::Err> {
  type TurntableCameraController (line 33) | pub struct TurntableCameraController {
    method camera_position (line 105) | fn camera_position(radius: f32, phi: f32, theta: f32) -> Vec3 {
    method zoom (line 114) | fn zoom(&mut self, delta: f32) {
    method pan (line 118) | fn pan(&mut self, delta: Vec2) {
  method default (line 49) | fn default() -> Self {
  type WasdMouseCameraController (line 129) | pub struct WasdMouseCameraController {
  type CameraController (line 244) | pub trait CameraController {
    method tick (line 62) | fn tick(&mut self) {}
    method reset (line 64) | fn reset(&mut self, bounds: Aabb) {
    method update_camera (line 73) | fn update_camera(&self, UVec2 { x: w, y: h }: UVec2, current_camera: &...
    method mouse_scroll (line 87) | fn mouse_scroll(&mut self, delta: f32) {
    method mouse_moved (line 91) | fn mouse_moved(&mut self, _position: Vec2) {}
    method mouse_motion (line 93) | fn mouse_motion(&mut self, delta: Vec2) {
    method mouse_button (line 97) | fn mouse_button(&mut self, is_pressed: bool, is_left_button: bool) {
    method key (line 101) | fn key(&mut self, _event: KeyEvent) {}
    method tick (line 144) | fn tick(&mut self) {
    method update_camera (line 187) | fn update_camera(&self, UVec2 { x: w, y: h }: UVec2, camera: &Camera) {
    method reset (line 196) | fn reset(&mut self, _bounds: Aabb) {
    method mouse_scroll (line 204) | fn mouse_scroll(&mut self, _delta: f32) {}
    method mouse_moved (line 206) | fn mouse_moved(&mut self, _position: Vec2) {}
    method mouse_motion (line 208) | fn mouse_motion(&mut self, delta: Vec2) {
    method mouse_button (line 214) | fn mouse_button(&mut self, _is_pressed: bool, _is_left_button: bool) {}
    method key (line 216) | fn key(
    method reset (line 245) | fn reset(&mut self, bounds: Aabb);
    method tick (line 246) | fn tick(&mut self);
    method update_camera (line 247) | fn update_camera(&self, size: UVec2, camera: &Camera);
    method mouse_scroll (line 248) | fn mouse_scroll(&mut self, delta: f32);
    method mouse_moved (line 249) | fn mouse_moved(&mut self, position: Vec2);
    method mouse_motion (line 250) | fn mouse_motion(&mut self, delta: Vec2);
    method mouse_button (line 251) | fn mouse_button(&mut self, is_pressed: bool, is_left_button: bool);
    method key (line 252) | fn key(&mut self, event: KeyEvent);
    method window_event (line 254) | fn window_event(&mut self, event: winit::event::WindowEvent) {
    method device_event (line 284) | fn device_event(&mut self, event: winit::event::DeviceEvent) {

FILE: crates/example/src/lib.rs
  constant FONT_BYTES (line 34) | const FONT_BYTES: &[u8] =
  constant DARK_BLUE_BG_COLOR (line 37) | const DARK_BLUE_BG_COLOR: Vec4 = Vec4::new(
  type SupportedFileType (line 44) | pub enum SupportedFileType {
  function is_file_supported (line 49) | pub fn is_file_supported(file: impl AsRef<std::path::Path>) -> Option<Su...
  function now (line 62) | fn now() -> f64 {
  type AppUi (line 77) | struct AppUi {
    method make_fps_widget (line 86) | fn make_fps_widget(fps_counter: &FPSCounter, ui: &mut UiRenderer) -> (...
    method tick (line 109) | fn tick(&mut self) {
  type Model (line 124) | pub enum Model {
  type App (line 130) | pub struct App {
    method new (line 145) | pub fn new(ctx: &Context, camera_control: CameraControl) -> Self {
    method tick (line 201) | pub fn tick(&mut self) {
    method render (line 209) | pub fn render(&mut self, ctx: &Context) {
    method update_view (line 218) | pub fn update_view(&mut self) {
    method load_hdr_skybox (line 223) | pub fn load_hdr_skybox(&mut self, bytes: Vec<u8>) {
    method set_model (line 233) | fn set_model(&mut self, model: Model) {
    method load_default_model (line 251) | pub fn load_default_model(&mut self) {
    method load_gltf_model (line 284) | fn load_gltf_model(&mut self, path: impl AsRef<std::path::Path>, bytes...
    method tick_loads (line 396) | pub fn tick_loads(&mut self) {
    method load (line 409) | pub fn load(&mut self, path: &str) {
    method set_size (line 434) | pub fn set_size(&mut self, size: UVec2) {
    method animate (line 438) | pub fn animate(&mut self) {

FILE: crates/example/src/main.rs
  type Cli (line 14) | struct Cli {
  type InnerApp (line 31) | struct InnerApp {
    method tick (line 37) | fn tick(&mut self) {
    method event (line 41) | fn event(&mut self, event: WindowEvent) -> bool {
  type OuterApp (line 72) | struct OuterApp {
  method resumed (line 78) | fn resumed(&mut self, event_loop: &winit::event_loop::ActiveEventLoop) {
  method about_to_wait (line 105) | fn about_to_wait(&mut self, _event_loop: &winit::event_loop::ActiveEvent...
  method window_event (line 112) | fn window_event(
  method device_event (line 125) | fn device_event(
  function main (line 142) | fn main() {

FILE: crates/example/src/time.rs
  type Time (line 11) | pub struct Time {
    method now (line 21) | pub fn now() -> Self {
    method now (line 28) | pub fn now() -> Self {
    method millis_since (line 35) | pub fn millis_since(&self, then: Time) -> u32 {
    method millis_since (line 40) | pub fn millis_since(&self, then: Time) -> u32 {
  constant FPS_COUNTER_BUFFER_SIZE (line 45) | pub const FPS_COUNTER_BUFFER_SIZE: usize = 60;
  type CounterBuffer (line 47) | pub struct CounterBuffer<T> {
  function new (line 53) | pub fn new(init: f32) -> Self {
  function write (line 60) | pub fn write(&mut self, val: f32) {
  function average (line 65) | pub fn average(&self) -> f32 {
  function current (line 69) | pub fn current(&self) -> f32 {
  function frames (line 78) | pub fn frames(&self) -> &[f32; FPS_COUNTER_BUFFER_SIZE] {
  type FPSCounter (line 83) | pub struct FPSCounter {
    method new (line 97) | pub fn new() -> FPSCounter {
    method restart (line 106) | pub fn restart(&mut self) {
    method next_frame (line 110) | pub fn next_frame(&mut self) -> f32 {
    method avg_frame_delta (line 124) | pub fn avg_frame_delta(&self) -> f32 {
    method current_fps (line 128) | pub fn current_fps(&self) -> f32 {
    method current_fps_string (line 132) | pub fn current_fps_string(&self) -> String {
    method last_delta (line 138) | pub fn last_delta(&self) -> f32 {
    method second_averages (line 142) | pub fn second_averages(&self) -> &[f32; FPS_COUNTER_BUFFER_SIZE] {
  method default (line 91) | fn default() -> Self {

FILE: crates/example/src/utils.rs
  type TestAppHandler (line 8) | pub trait TestAppHandler: winit::application::ApplicationHandler {
    method new (line 9) | fn new(
    method render (line 14) | fn render(&mut self, ctx: &Context);
  type InnerTestApp (line 17) | pub(crate) struct InnerTestApp<T> {
  type TestApp (line 22) | pub struct TestApp<T> {
  function resumed (line 28) | fn resumed(&mut self, event_loop: &winit::event_loop::ActiveEventLoop) {
  function window_event (line 69) | fn window_event(
  function device_event (line 99) | fn device_event(
  function about_to_wait (line 110) | fn about_to_wait(&mut self, event_loop: &winit::event_loop::ActiveEventL...
  function new (line 119) | pub fn new(size: impl Into<winit::dpi::Size>) -> Self {
  function run (line 126) | pub fn run(&mut self) {

FILE: crates/examples/src/context.rs
  function context_page (line 4) | async fn context_page() {

FILE: crates/examples/src/gltf.rs
  function manual_gltf (line 6) | async fn manual_gltf() {

FILE: crates/examples/src/lib.rs
  function cwd_to_manual_assets_dir (line 21) | pub fn cwd_to_manual_assets_dir() -> std::path::PathBuf {
  function workspace_dir (line 30) | pub fn workspace_dir() -> std::path::PathBuf {
  function test_output_dir (line 34) | pub fn test_output_dir() -> std::path::PathBuf {
  function cwd_to_cargo_workspace (line 40) | pub fn cwd_to_cargo_workspace() -> std::path::PathBuf {
  function can_test (line 50) | fn can_test() {

FILE: crates/examples/src/lighting.rs
  function manual_lighting (line 6) | async fn manual_lighting() {
  function manual_lighting_ibl (line 148) | async fn manual_lighting_ibl() {

FILE: crates/examples/src/skybox.rs
  function manual_skybox (line 6) | pub async fn manual_skybox() {

FILE: crates/examples/src/stage.rs
  function manual_stage (line 4) | async fn manual_stage() {

FILE: crates/img-diff/src/lib.rs
  constant PIXEL_MAGNITUDE_THRESHOLD (line 7) | const PIXEL_MAGNITUDE_THRESHOLD: f32 = 0.1;
  constant LOW_PIXEL_THRESHOLD (line 8) | pub const LOW_PIXEL_THRESHOLD: f32 = 0.02;
  constant IMAGE_DIFF_THRESHOLD (line 9) | const IMAGE_DIFF_THRESHOLD: f32 = 0.05;
  function checkerboard_background_color (line 11) | fn checkerboard_background_color(x: u32, y: u32) -> Vec3 {
  type ImgDiffError (line 25) | enum ImgDiffError {
  type DiffCfg (line 30) | pub struct DiffCfg {
  method default (line 47) | fn default() -> Self {
  type DiffResults (line 57) | pub struct DiffResults {
  function get_results (line 65) | fn get_results(
  function save_to (line 128) | pub fn save_to(
  function save (line 141) | pub fn save(filename: impl AsRef<std::path::Path>, seen: impl Into<Dynam...
  function assert_eq_cfg (line 145) | pub fn assert_eq_cfg(
  function assert_eq (line 218) | pub fn assert_eq(filename: &str, lhs: impl Into<DynamicImage>, rhs: impl...
  function assert_img_eq_cfg_result (line 222) | pub fn assert_img_eq_cfg_result(
  function assert_img_eq_cfg (line 233) | pub fn assert_img_eq_cfg(filename: &str, seen: impl Into<DynamicImage>, ...
  function assert_img_eq (line 237) | pub fn assert_img_eq(filename: &str, seen: impl Into<DynamicImage>) {
  function normalize_gray_img (line 245) | pub fn normalize_gray_img(seen: &mut image::GrayImage) {
  function can_compare_images_sanity (line 270) | fn can_compare_images_sanity() {

FILE: crates/loading-bytes/src/lib.rs
  type WasmError (line 6) | pub enum WasmError {
  type LoadingBytesError (line 43) | pub enum LoadingBytesError {
    method from (line 56) | fn from(value: WasmError) -> Self {
  function load_wasm (line 61) | pub async fn load_wasm(path: &str) -> Result<Vec<u8>, WasmError> {
  function post_json_wasm (line 114) | pub async fn post_json_wasm<T: serde::de::DeserializeOwned>(
  function post_bin_wasm (line 179) | pub async fn post_bin_wasm<T: serde::de::DeserializeOwned>(
  function load (line 245) | pub async fn load(path: &str) -> Result<Vec<u8>, LoadingBytesError> {

FILE: crates/renderling-build/src/lib.rs
  type Linkage (line 9) | struct Linkage {
    method fmt (line 16) | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
    method fn_name (line 84) | pub fn fn_name(&self) -> &str {
  function wgsl (line 89) | fn wgsl(spv_filepath: impl AsRef<std::path::Path>, destination: impl AsR...
  function workspace_dir (line 144) | pub fn workspace_dir() -> std::path::PathBuf {
  function test_img_dir (line 152) | pub fn test_img_dir() -> std::path::PathBuf {
  function test_output_dir (line 160) | pub fn test_output_dir() -> std::path::PathBuf {
  function wasm_test_output_dir (line 168) | pub fn wasm_test_output_dir() -> std::path::PathBuf {
  type RenderlingPaths (line 173) | pub struct RenderlingPaths {
    method new (line 194) | pub fn new() -> Option<Self> {
    method generate_linkage (line 221) | pub fn generate_linkage(

FILE: crates/renderling-ui/src/renderer.rs
  type UiRect (line 46) | pub struct UiRect {
    method id (line 52) | pub fn id(&self) -> Id<UiDrawCallDescriptor> {
    method descriptor (line 57) | pub fn descriptor(&self) -> UiDrawCallDescriptor {
    method set_position (line 62) | pub fn set_position(&self, position: Vec2) -> &Self {
    method with_position (line 68) | pub fn with_position(self, position: Vec2) -> Self {
    method set_size (line 74) | pub fn set_size(&self, size: Vec2) -> &Self {
    method with_size (line 80) | pub fn with_size(self, size: Vec2) -> Self {
    method set_fill_color (line 86) | pub fn set_fill_color(&self, color: Vec4) -> &Self {
    method with_fill_color (line 92) | pub fn with_fill_color(self, color: Vec4) -> Self {
    method set_corner_radii (line 99) | pub fn set_corner_radii(&self, radii: Vec4) -> &Self {
    method with_corner_radii (line 105) | pub fn with_corner_radii(self, radii: Vec4) -> Self {
    method set_border (line 111) | pub fn set_border(&self, width: f32, color: Vec4) -> &Self {
    method with_border (line 120) | pub fn with_border(self, width: f32, color: Vec4) -> Self {
    method set_gradient (line 126) | pub fn set_gradient(&self, gradient: Option<GradientDescriptor>) -> &S...
    method with_gradient (line 133) | pub fn with_gradient(self, gradient: Option<GradientDescriptor>) -> Se...
    method set_opacity (line 139) | pub fn set_opacity(&self, opacity: f32) -> &Self {
    method with_opacity (line 145) | pub fn with_opacity(self, opacity: f32) -> Self {
    method set_z (line 151) | pub fn set_z(&self, z: f32) -> &Self {
    method with_z (line 157) | pub fn with_z(self, z: f32) -> Self {
    method position (line 165) | pub fn position(&self) -> Vec2 {
    method size (line 170) | pub fn size(&self) -> Vec2 {
    method fill_color (line 175) | pub fn fill_color(&self) -> Vec4 {
    method corner_radii (line 180) | pub fn corner_radii(&self) -> Vec4 {
    method border_width (line 185) | pub fn border_width(&self) -> f32 {
    method border_color (line 190) | pub fn border_color(&self) -> Vec4 {
    method gradient (line 195) | pub fn gradient(&self) -> GradientDescriptor {
    method opacity (line 200) | pub fn opacity(&self) -> f32 {
    method z (line 205) | pub fn z(&self) -> f32 {
  type UiCircle (line 214) | pub struct UiCircle {
    method id (line 220) | pub fn id(&self) -> Id<UiDrawCallDescriptor> {
    method descriptor (line 225) | pub fn descriptor(&self) -> UiDrawCallDescriptor {
    method set_center (line 230) | pub fn set_center(&self, center: Vec2) -> &Self {
    method with_center (line 239) | pub fn with_center(self, center: Vec2) -> Self {
    method set_radius (line 245) | pub fn set_radius(&self, radius: f32) -> &Self {
    method with_radius (line 255) | pub fn with_radius(self, radius: f32) -> Self {
    method set_fill_color (line 261) | pub fn set_fill_color(&self, color: Vec4) -> &Self {
    method with_fill_color (line 267) | pub fn with_fill_color(self, color: Vec4) -> Self {
    method set_border (line 273) | pub fn set_border(&self, width: f32, color: Vec4) -> &Self {
    method with_border (line 282) | pub fn with_border(self, width: f32, color: Vec4) -> Self {
    method set_gradient (line 288) | pub fn set_gradient(&self, gradient: Option<GradientDescriptor>) -> &S...
    method with_gradient (line 295) | pub fn with_gradient(self, gradient: Option<GradientDescriptor>) -> Se...
    method set_opacity (line 301) | pub fn set_opacity(&self, opacity: f32) -> &Self {
    method with_opacity (line 307) | pub fn with_opacity(self, opacity: f32) -> Self {
    method set_z (line 313) | pub fn set_z(&self, z: f32) -> &Self {
    method with_z (line 319) | pub fn with_z(self, z: f32) -> Self {
    method center (line 327) | pub fn center(&self) -> Vec2 {
    method radius (line 333) | pub fn radius(&self) -> f32 {
    method fill_color (line 338) | pub fn fill_color(&self) -> Vec4 {
    method border_width (line 343) | pub fn border_width(&self) -> f32 {
    method border_color (line 348) | pub fn border_color(&self) -> Vec4 {
    method gradient (line 353) | pub fn gradient(&self) -> GradientDescriptor {
    method opacity (line 358) | pub fn opacity(&self) -> f32 {
    method z (line 363) | pub fn z(&self) -> f32 {
  type UiEllipse (line 372) | pub struct UiEllipse {
    method id (line 378) | pub fn id(&self) -> Id<UiDrawCallDescriptor> {
    method descriptor (line 383) | pub fn descriptor(&self) -> UiDrawCallDescriptor {
    method set_center (line 388) | pub fn set_center(&self, center: Vec2) -> &Self {
    method with_center (line 397) | pub fn with_center(self, center: Vec2) -> Self {
    method set_radii (line 403) | pub fn set_radii(&self, radii: Vec2) -> &Self {
    method with_radii (line 413) | pub fn with_radii(self, radii: Vec2) -> Self {
    method set_fill_color (line 419) | pub fn set_fill_color(&self, color: Vec4) -> &Self {
    method with_fill_color (line 425) | pub fn with_fill_color(self, color: Vec4) -> Self {
    method set_border (line 431) | pub fn set_border(&self, width: f32, color: Vec4) -> &Self {
    method with_border (line 440) | pub fn with_border(self, width: f32, color: Vec4) -> Self {
    method set_gradient (line 446) | pub fn set_gradient(&self, gradient: Option<GradientDescriptor>) -> &S...
    method with_gradient (line 453) | pub fn with_gradient(self, gradient: Option<GradientDescriptor>) -> Se...
    method set_opacity (line 459) | pub fn set_opacity(&self, opacity: f32) -> &Self {
    method with_opacity (line 465) | pub fn with_opacity(self, opacity: f32) -> Self {
    method set_z (line 471) | pub fn set_z(&self, z: f32) -> &Self {
    method with_z (line 477) | pub fn with_z(self, z: f32) -> Self {
    method center (line 485) | pub fn center(&self) -> Vec2 {
    method radii (line 491) | pub fn radii(&self) -> Vec2 {
    method fill_color (line 496) | pub fn fill_color(&self) -> Vec4 {
    method border_width (line 501) | pub fn border_width(&self) -> f32 {
    method border_color (line 506) | pub fn border_color(&self) -> Vec4 {
    method gradient (line 511) | pub fn gradient(&self) -> GradientDescriptor {
    method opacity (line 516) | pub fn opacity(&self) -> f32 {
    method z (line 521) | pub fn z(&self) -> f32 {
  type UiImage (line 530) | pub struct UiImage {
    method fmt (line 538) | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
    method id (line 547) | pub fn id(&self) -> Id<UiDrawCallDescriptor> {
    method descriptor (line 552) | pub fn descriptor(&self) -> UiDrawCallDescriptor {
    method set_position (line 557) | pub fn set_position(&self, position: Vec2) -> &Self {
    method with_position (line 563) | pub fn with_position(self, position: Vec2) -> Self {
    method set_size (line 569) | pub fn set_size(&self, size: Vec2) -> &Self {
    method with_size (line 575) | pub fn with_size(self, size: Vec2) -> Self {
    method set_tint (line 582) | pub fn set_tint(&self, color: Vec4) -> &Self {
    method with_tint (line 588) | pub fn with_tint(self, color: Vec4) -> Self {
    method set_opacity (line 594) | pub fn set_opacity(&self, opacity: f32) -> &Self {
    method with_opacity (line 600) | pub fn with_opacity(self, opacity: f32) -> Self {
    method set_z (line 606) | pub fn set_z(&self, z: f32) -> &Self {
    method with_z (line 612) | pub fn with_z(self, z: f32) -> Self {
    method position (line 620) | pub fn position(&self) -> Vec2 {
    method size (line 625) | pub fn size(&self) -> Vec2 {
    method tint (line 630) | pub fn tint(&self) -> Vec4 {
    method opacity (line 635) | pub fn opacity(&self) -> f32 {
    method z (line 640) | pub fn z(&self) -> f32 {
  function vec2_to_point (line 664) | fn vec2_to_point(v: impl Into<Vec2>) -> geom::Point<f32> {
  function vec2_to_vec (line 669) | fn vec2_to_vec(v: impl Into<Vec2>) -> geom::Vector<f32> {
  constant NUM_ATTRIBUTES (line 675) | const NUM_ATTRIBUTES: usize = 8;
  type PathAttributes (line 679) | struct PathAttributes {
    method to_array (line 685) | fn to_array(self) -> [f32; NUM_ATTRIBUTES] {
    method from_slice (line 691) | fn from_slice(s: &[f32]) -> Self {
  type StrokeConfig (line 700) | pub struct StrokeConfig {
  method default (line 710) | fn default() -> Self {
  type UiPathBuilder (line 735) | pub struct UiPathBuilder {
    method new (line 744) | pub(crate) fn new() -> Self {
    method set_fill_color (line 759) | pub fn set_fill_color(&mut self, color: impl Into<Vec4>) -> &mut Self {
    method with_fill_color (line 765) | pub fn with_fill_color(mut self, color: impl Into<Vec4>) -> Self {
    method set_stroke_color (line 771) | pub fn set_stroke_color(&mut self, color: impl Into<Vec4>) -> &mut Self {
    method with_stroke_color (line 777) | pub fn with_stroke_color(mut self, color: impl Into<Vec4>) -> Self {
    method set_stroke_config (line 783) | pub fn set_stroke_config(&mut self, config: StrokeConfig) -> &mut Self {
    method with_stroke_config (line 789) | pub fn with_stroke_config(mut self, config: StrokeConfig) -> Self {
    method set_fill_image (line 802) | pub fn set_fill_image(&mut self, texture: &AtlasTexture) -> &mut Self {
    method with_fill_image (line 808) | pub fn with_fill_image(mut self, texture: &AtlasTexture) -> Self {
    method begin (line 816) | pub fn begin(&mut self, at: impl Into<Vec2>) -> &mut Self {
    method with_begin (line 822) | pub fn with_begin(mut self, at: impl Into<Vec2>) -> Self {
    method end (line 828) | pub fn end(&mut self, close: bool) -> &mut Self {
    method with_end (line 834) | pub fn with_end(mut self, close: bool) -> Self {
    method line_to (line 840) | pub fn line_to(&mut self, to: impl Into<Vec2>) -> &mut Self {
    method with_line_to (line 848) | pub fn with_line_to(mut self, to: impl Into<Vec2>) -> Self {
    method quadratic_bezier_to (line 854) | pub fn quadratic_bezier_to(
    method with_quadratic_bezier_to (line 868) | pub fn with_quadratic_bezier_to(
    method cubic_bezier_to (line 878) | pub fn cubic_bezier_to(
    method with_cubic_bezier_to (line 894) | pub fn with_cubic_bezier_to(
    method add_rectangle (line 907) | pub fn add_rectangle(&mut self, min: impl Into<Vec2>, max: impl Into<V...
    method with_rectangle (line 917) | pub fn with_rectangle(mut self, min: impl Into<Vec2>, max: impl Into<V...
    method add_rounded_rectangle (line 923) | pub fn add_rounded_rectangle(
    method with_rounded_rectangle (line 951) | pub fn with_rounded_rectangle(
    method add_circle (line 965) | pub fn add_circle(&mut self, center: impl Into<Vec2>, radius: f32) -> ...
    method with_circle (line 976) | pub fn with_circle(mut self, center: impl Into<Vec2>, radius: f32) -> ...
    method add_ellipse (line 982) | pub fn add_ellipse(
    method with_ellipse (line 1000) | pub fn with_ellipse(
    method add_polygon (line 1011) | pub fn add_polygon(&mut self, points: &[Vec2]) -> &mut Self {
    method with_polygon (line 1024) | pub fn with_polygon(mut self, points: &[Vec2]) -> Self {
    method fill (line 1033) | pub fn fill(self, renderer: &mut UiRenderer) -> UiPath {
    method stroke (line 1065) | pub fn stroke(self, renderer: &mut UiRenderer) -> UiPath {
    method compute_bounding_box_uvs (line 1104) | fn compute_bounding_box_uvs(geometry: &mut VertexBuffers<UiVertex, u32...
    method upload (line 1126) | fn upload(
  type UiPath (line 1162) | pub struct UiPath {
    method fmt (line 1169) | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
    method id (line 1178) | pub fn id(&self) -> Id<UiDrawCallDescriptor> {
    method descriptor (line 1183) | pub fn descriptor(&self) -> UiDrawCallDescriptor {
    method set_z (line 1188) | pub fn set_z(&self, z: f32) -> &Self {
    method with_z (line 1194) | pub fn with_z(self, z: f32) -> Self {
    method set_opacity (line 1200) | pub fn set_opacity(&self, opacity: f32) -> &Self {
    method with_opacity (line 1206) | pub fn with_opacity(self, opacity: f32) -> Self {
    method opacity (line 1214) | pub fn opacity(&self) -> f32 {
    method z (line 1219) | pub fn z(&self) -> f32 {
  type GlyphCache (line 1246) | pub(crate) struct GlyphCache {
    method new (line 1272) | pub fn new(fonts: Vec<FontArc>) -> Self {
    method rebuild_with_fonts (line 1285) | pub fn rebuild_with_fonts(&mut self, fonts: Vec<FontArc>) {
    method queue (line 1295) | pub fn queue<'a>(&mut self, section: impl Into<std::borrow::Cow<'a, Se...
    method glyph_bounds (line 1300) | pub fn glyph_bounds<'a>(
    method process (line 1312) | pub fn process(&mut self) -> Option<Vec<GlyphQuad>> {
    method take_image (line 1429) | pub fn take_image(&mut self) -> Option<image::RgbaImage> {
  type GlyphQuad (line 1257) | pub(crate) struct GlyphQuad {
  type UiText (line 1452) | pub struct UiText {
    method bounds (line 1469) | pub fn bounds(&self) -> (Vec2, Vec2) {
    method set_z (line 1474) | pub fn set_z(&self, z: f32) -> &Self {
    method with_z (line 1482) | pub fn with_z(self, z: f32) -> Self {
    method set_opacity (line 1488) | pub fn set_opacity(&self, opacity: f32) -> &Self {
    method with_opacity (line 1496) | pub fn with_opacity(self, opacity: f32) -> Self {
    method opacity (line 1505) | pub fn opacity(&self) -> f32 {
    method z (line 1514) | pub fn z(&self) -> f32 {
  type DrawCall (line 1533) | struct DrawCall {
  type UiRenderer (line 1554) | pub struct UiRenderer {
    constant LABEL (line 1604) | const LABEL: Option<&'static str> = Some("renderling-ui");
    constant DEFAULT_ATLAS_SIZE (line 1607) | const DEFAULT_ATLAS_SIZE: wgpu::Extent3d = wgpu::Extent3d {
    method new (line 1614) | pub fn new(ctx: &Context) -> Self {
    method set_background_color (line 1683) | pub fn set_background_color(&mut self, color: Option<Vec4>) -> &mut Se...
    method with_background_color (line 1689) | pub fn with_background_color(mut self, color: Vec4) -> Self {
    method with_msaa_sample_count (line 1698) | pub fn with_msaa_sample_count(mut self, count: u32) -> Self {
    method set_size (line 1722) | pub fn set_size(&mut self, size: UVec2) {
    method add_rect (line 1759) | pub fn add_rect(&mut self) -> UiRect {
    method add_circle (line 1777) | pub fn add_circle(&mut self) -> UiCircle {
    method add_ellipse (line 1795) | pub fn add_ellipse(&mut self) -> UiEllipse {
    method add_image (line 1819) | pub fn add_image(&mut self, image: impl Into<AtlasImage>) -> UiImage {
    method upload_image (line 1865) | pub fn upload_image(&mut self, image: impl Into<AtlasImage>) -> AtlasT...
    method add_font (line 1892) | pub fn add_font(&mut self, font: FontArc) -> FontId {
    method add_text (line 1918) | pub fn add_text<'a>(
    method remove_rect (line 2008) | pub fn remove_rect(&mut self, element: &UiRect) {
    method remove_circle (line 2013) | pub fn remove_circle(&mut self, element: &UiCircle) {
    method remove_ellipse (line 2018) | pub fn remove_ellipse(&mut self, element: &UiEllipse) {
    method remove_image (line 2023) | pub fn remove_image(&mut self, element: &UiImage) {
    method remove_text (line 2029) | pub fn remove_text(&mut self, element: &UiText) {
    method path_builder (line 2047) | pub fn path_builder(&self) -> UiPathBuilder {
    method remove_path (line 2053) | pub fn remove_path(&mut self, element: &UiPath) {
    method clear (line 2058) | pub fn clear(&mut self) {
    method render (line 2065) | pub fn render(&mut self, view: &wgpu::TextureView) {
    method ortho2d (line 2209) | fn ortho2d(width: f32, height: f32) -> Mat4 {
    method default_descriptor (line 2222) | fn default_descriptor(&self, element_type: UiElementType) -> UiDrawCal...
    method create_bindgroup_layout (line 2245) | fn create_bindgroup_layout(device: &wgpu::Device) -> wgpu::BindGroupLa...
    method create_pipeline (line 2282) | fn create_pipeline(
    method create_msaa_texture (line 2336) | fn create_msaa_texture(
    method create_overlay_texture (line 2365) | fn create_overlay_texture(
    method create_bindgroup (line 2389) | fn create_bindgroup(
    method remove_by_id (line 2417) | fn remove_by_id(&mut self, id: Id<UiDrawCallDescriptor>) {

FILE: crates/renderling-ui/src/test.rs
  function init_logging (line 10) | fn init_logging() {
  function save_and_assert (line 17) | fn save_and_assert(name: &str, img: image::RgbaImage) {
  function can_render_rect (line 33) | fn can_render_rect() {
  function can_render_rounded_rect (line 51) | fn can_render_rounded_rect() {
  function can_render_circle (line 70) | fn can_render_circle() {
  function can_render_bordered_rect (line 88) | fn can_render_bordered_rect() {
  function can_render_multiple_shapes (line 108) | fn can_render_multiple_shapes() {
  function can_render_image (line 146) | fn can_render_image() {
  function can_render_image_with_tint (line 172) | fn can_render_image_with_tint() {
  function can_render_gradient_rect (line 195) | fn can_render_gradient_rect() {
  function can_render_text (line 222) | fn can_render_text() {
  function can_render_filled_path (line 252) | fn can_render_filled_path() {
  function can_render_stroked_path (line 275) | fn can_render_stroked_path() {
  function can_render_path_shapes (line 295) | fn can_render_path_shapes() {
  function can_render_text_with_shapes (line 333) | fn can_render_text_with_shapes() {
  function star_points (line 377) | fn star_points(num_points: usize, outer_radius: f32, inner_radius: f32) ...
  function can_render_path_with_image_fill (line 394) | fn can_render_path_with_image_fill() {

FILE: crates/renderling/src/atlas.rs
  type TextureModes (line 31) | pub struct TextureModes {
  function repeat (line 39) | pub fn repeat(mut input: f32) -> f32 {
  function clamp (line 50) | pub fn clamp(input: f32) -> f32 {
  type TextureAddressMode (line 65) | pub enum TextureAddressMode {
    method fmt (line 73) | fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
    method wrap (line 85) | pub fn wrap(&self, input: f32) -> f32 {
  function can_repeat (line 136) | fn can_repeat() {
  function constrain_clip_coords_sanity (line 145) | fn constrain_clip_coords_sanity() {

FILE: crates/renderling/src/atlas/atlas_image.rs
  function cwd (line 8) | fn cwd() -> Option<String> {
  type AtlasImageError (line 21) | pub enum AtlasImageError {
  type AtlasImageFormat (line 33) | pub enum AtlasImageFormat {
    method from_wgpu_texture_format (line 72) | pub fn from_wgpu_texture_format(value: wgpu::TextureFormat) -> Option<...
    method zero_pixel (line 85) | pub fn zero_pixel(&self) -> &[u8] {
  function from (line 50) | fn from(value: AtlasImageFormat) -> Self {
  type AtlasImage (line 107) | pub struct AtlasImage {
    method from (line 117) | fn from(value: gltf::image::Data) -> Self {
    method from (line 144) | fn from(value: image::DynamicImage) -> Self {
    type Error (line 176) | type Error = AtlasImageError;
    method try_from (line 178) | fn try_from(value: std::path::PathBuf) -> Result<Self, Self::Error> {
    method from_hdr_path (line 185) | pub fn from_hdr_path(p: impl AsRef<std::path::Path>) -> Result<Self, A...
    method from_hdr_bytes (line 192) | pub fn from_hdr_bytes(bytes: &[u8]) -> Result<Self, AtlasImageError> {
    method from_path (line 219) | pub fn from_path(p: impl AsRef<std::path::Path>) -> Result<Self, Atlas...
    method into_rgba8 (line 223) | pub fn into_rgba8(self) -> Option<image::RgbaImage> {
    method new (line 234) | pub fn new(size: UVec2, format: AtlasImageFormat) -> Self {
  function apply_linear_xfer (line 247) | fn apply_linear_xfer(bytes: &mut [u8], format: AtlasImageFormat) {
  function convert_pixels (line 282) | pub fn convert_pixels(

FILE: crates/renderling/src/atlas/cpu.rs
  constant ATLAS_SUGGESTED_SIZE (line 25) | pub(crate) const ATLAS_SUGGESTED_SIZE: u32 = 2048;
  constant ATLAS_SUGGESTED_LAYERS (line 26) | pub(crate) const ATLAS_SUGGESTED_LAYERS: u32 = 8;
  type AtlasError (line 29) | pub enum AtlasError {
  type AtlasTexture (line 70) | pub struct AtlasTexture {
    method id (line 78) | pub fn id(&self) -> Id<AtlasTextureDescriptor> {
    method descriptor (line 83) | pub fn descriptor(&self) -> AtlasTextureDescriptor {
    method modes (line 88) | pub fn modes(&self) -> TextureModes {
    method set_modes (line 97) | pub fn set_modes(&self, modes: TextureModes) {
  type InternalAtlasTexture (line 113) | struct InternalAtlasTexture {
    method from_hybrid (line 120) | fn from_hybrid(hat: &Hybrid<AtlasTextureDescriptor>) -> Self {
    method has_external_references (line 127) | fn has_external_references(&self) -> bool {
    method set (line 131) | fn set(&mut self, at: AtlasTextureDescriptor) {
  function check_size (line 143) | pub(crate) fn check_size(size: wgpu::Extent3d) {
  function fan_split_n (line 152) | fn fan_split_n<T>(n: usize, input: impl IntoIterator<Item = T>) -> Vec<V...
  type AnotherPacking (line 173) | enum AnotherPacking<'a> {
  function size (line 182) | fn size(&self) -> UVec2 {
  type Layer (line 194) | pub struct Layer {
  type Atlas (line 202) | pub struct Atlas {
    constant LABEL (line 214) | const LABEL: Option<&str> = Some("atlas-texture");
    method device (line 216) | pub fn device(&self) -> &wgpu::Device {
    method create_texture (line 221) | fn create_texture(
    method new (line 282) | pub fn new(
    method descriptor_id (line 312) | pub fn descriptor_id(&self) -> Id<AtlasDescriptor> {
    method len (line 316) | pub fn len(&self) -> usize {
    method is_empty (line 322) | pub fn is_empty(&self) -> bool {
    method get_texture (line 328) | pub fn get_texture(&self) -> impl Deref<Target = Texture> + '_ {
    method get_layers (line 333) | pub fn get_layers(&self) -> impl Deref<Target = Vec<Layer>> + '_ {
    method set_images (line 341) | pub fn set_images(&self, images: &[AtlasImage]) -> Result<Vec<AtlasTex...
    method get_size (line 355) | pub fn get_size(&self) -> wgpu::Extent3d {
    method add_images (line 365) | pub fn add_images<'a>(
    method add_image (line 406) | pub fn add_image(&self, image: &AtlasImage) -> Result<AtlasTexture, At...
    method resize (line 419) | pub fn resize(
    method upkeep (line 456) | pub fn upkeep(&self, runtime: impl AsRef<WgpuRuntime>) -> bool {
    method atlas_img_buffer (line 496) | pub fn atlas_img_buffer(
    method atlas_img (line 536) | pub async fn atlas_img(&self, runtime: impl AsRef<WgpuRuntime>, layer:...
    method read_images (line 544) | pub async fn read_images(&self, runtime: impl AsRef<WgpuRuntime>) -> V...
    method update_texture (line 560) | pub fn update_texture(
    method update_textures (line 576) | pub fn update_textures<'a>(
    method update_images (line 608) | pub fn update_images<'a>(
    method update_image (line 651) | pub fn update_image(
  function pack_images (line 660) | fn pack_images<'a>(
  type StagedResources (line 716) | struct StagedResources {
    method try_staging (line 724) | fn try_staging(
  type AtlasBlittingOperation (line 874) | pub struct AtlasBlittingOperation {
    method new (line 885) | pub fn new(
    method run (line 915) | pub fn run(
  type AtlasBlitter (line 1027) | pub struct AtlasBlitter {
    method new (line 1041) | pub fn new(
  function atlas_uv_mapping (line 1160) | fn atlas_uv_mapping() {
  function uv_wrapping (line 1216) | fn uv_wrapping() {
  function negative_uv_wrapping (line 1310) | fn negative_uv_wrapping() {
  function transform_uvs_for_atlas (line 1407) | fn transform_uvs_for_atlas() {
  function can_load_and_read_atlas_texture_array (line 1424) | fn can_load_and_read_atlas_texture_array() {
  function upkeep_trims_the_atlas (line 1445) | fn upkeep_trims_the_atlas() {

FILE: crates/renderling/src/atlas/shader.rs
  type AtlasDescriptor (line 7) | pub struct AtlasDescriptor {
  type AtlasTextureDescriptor (line 13) | pub struct AtlasTextureDescriptor {
    method uv (line 30) | pub fn uv(&self, mut uv: Vec2, atlas_size: UVec2) -> Vec3 {
    method constrain_clip_coords_to_texture_space (line 53) | pub fn constrain_clip_coords_to_texture_space(
    method constrain_clip_coords (line 66) | pub fn constrain_clip_coords(&self, clip_pos: Vec2, atlas_size: UVec2)...
    method origin (line 74) | pub fn origin(&self) -> wgpu::Origin3d {
    method size_as_extent (line 84) | pub fn size_as_extent(&self) -> wgpu::Extent3d {
  type AtlasBlittingDescriptor (line 94) | pub struct AtlasBlittingDescriptor {
  function atlas_blit_vertex (line 105) | pub fn atlas_blit_vertex(
  function atlas_blit_fragment (line 133) | pub fn atlas_blit_fragment(

FILE: crates/renderling/src/bindgroup.rs
  type ManagedBindGroup (line 11) | pub struct ManagedBindGroup {
    method from (line 22) | fn from(value: wgpu::BindGroup) -> Self {
    method new (line 31) | pub fn new() -> Self {
    method get (line 37) | pub fn get(
    method invalidate (line 62) | pub fn invalidate(&self) {
  method default (line 16) | fn default() -> Self {

FILE: crates/renderling/src/bloom/cpu.rs
  function create_bindgroup_layout (line 15) | fn create_bindgroup_layout(device: &wgpu::Device, label: Option<&str>) -...
  function create_bloom_downsample_pipeline (line 49) | fn create_bloom_downsample_pipeline(device: &wgpu::Device) -> wgpu::Rend...
  function create_bloom_upsample_pipeline (line 94) | fn create_bloom_upsample_pipeline(device: &wgpu::Device) -> wgpu::Render...
  function config_resolutions (line 139) | fn config_resolutions(resolution: UVec2) -> impl Iterator<Item = UVec2> {
  function create_texture (line 144) | fn create_texture(
  function create_textures (line 184) | fn create_textures(runtime: impl AsRef<WgpuRuntime>, resolution: UVec2) ...
  function create_bindgroup (line 213) | fn create_bindgroup(
  function create_bindgroups (line 240) | fn create_bindgroups(
  function create_mix_pipeline (line 253) | fn create_mix_pipeline(device: &wgpu::Device) -> wgpu::RenderPipeline {
  function create_mix_bindgroup (line 344) | fn create_mix_bindgroup(
  type Bloom (line 386) | pub struct Bloom {
    method new (line 407) | pub fn new(runtime: impl AsRef<WgpuRuntime>, hdr_texture: &Texture) ->...
    method slab_allocator (line 473) | pub(crate) fn slab_allocator(&self) -> &SlabAllocator<WgpuRuntime> {
    method set_mix_strength (line 477) | pub fn set_mix_strength(&self, strength: f32) {
    method get_mix_strength (line 481) | pub fn get_mix_strength(&self) -> f32 {
    method set_filter_radius (line 486) | pub fn set_filter_radius(&self, filter_radius: f32) {
    method get_filter_radius (line 492) | pub fn get_filter_radius(&self) -> Vec2 {
    method get_size (line 496) | pub fn get_size(&self) -> UVec2 {
    method set_hdr_texture (line 502) | pub fn set_hdr_texture(&self, runtime: impl AsRef<WgpuRuntime>, hdr_te...
    method get_mix_texture (line 548) | pub fn get_mix_texture(&self) -> Texture {
    method render_downsamples (line 556) | pub(crate) fn render_downsamples(&self, device: &wgpu::Device, queue: ...
    method render_upsamples (line 625) | fn render_upsamples(&self, device: &wgpu::Device, queue: &wgpu::Queue) {
    method render_mix (line 672) | fn render_mix(&self, device: &wgpu::Device, queue: &wgpu::Queue) {
    method bloom (line 703) | pub fn bloom(&self, device: &wgpu::Device, queue: &wgpu::Queue) {
  function bloom_texture_sizes_sanity (line 724) | fn bloom_texture_sizes_sanity() {
  function bloom_sanity (line 762) | fn bloom_sanity() {

FILE: crates/renderling/src/bloom/shader.rs
  function bloom_vertex (line 9) | pub fn bloom_vertex(
  function bloom_downsample_fragment (line 31) | pub fn bloom_downsample_fragment(
  function bloom_upsample_fragment (line 101) | pub fn bloom_upsample_fragment(
  function bloom_mix_fragment (line 167) | pub fn bloom_mix_fragment(

FILE: crates/renderling/src/build.rs
  function main (line 3) | fn main() {

FILE: crates/renderling/src/bvol.rs
  function normalize_plane (line 22) | pub fn normalize_plane(mut plane: Vec4) -> Vec4 {
  function intersect_planes (line 37) | pub fn intersect_planes(p0: &Vec4, p1: &Vec4, p2: &Vec4) -> Vec3 {
  function dist_bpp (line 46) | pub fn dist_bpp(plane: &Vec4, point: Vec3) -> f32 {
  function mi_vertex (line 51) | pub fn mi_vertex(plane: &Vec4, aabb: &Aabb) -> Vec3 {
  function mo_vertex (line 72) | pub fn mo_vertex(plane: &Vec4, aabb: &Aabb) -> Vec3 {
  type Aabb (line 94) | pub struct Aabb {
    method from (line 100) | fn from((a, b): (Vec3, Vec3)) -> Self {
    method new (line 106) | pub fn new(a: Vec3, b: Vec3) -> Self {
    method width (line 114) | pub fn width(&self) -> f32 {
    method height (line 119) | pub fn height(&self) -> f32 {
    method depth (line 124) | pub fn depth(&self) -> f32 {
    method center (line 128) | pub fn center(&self) -> Vec3 {
    method extents (line 132) | pub fn extents(&self) -> Vec3 {
    method diagonal_length (line 136) | pub fn diagonal_length(&self) -> f32 {
    method is_zero (line 140) | pub fn is_zero(&self) -> bool {
    method union (line 145) | pub fn union(a: Self, b: Self) -> Self {
    method is_outside_camera_view (line 154) | pub fn is_outside_camera_view(
    method get_mesh (line 178) | pub fn get_mesh(&self) -> Vec<(Vec3, Vec3)> {
    method intersects_aabb (line 204) | pub fn intersects_aabb(&self, other: &Aabb) -> bool {
  type Frustum (line 216) | pub struct Frustum {
    method from_camera (line 229) | pub fn from_camera(camera: &CameraDescriptor) -> Self {
    method get_mesh (line 291) | pub fn get_mesh(&self) -> Vec<(Vec3, Vec3)> {
    method test_against_aabb (line 313) | pub fn test_against_aabb(&self, aabb: &Aabb) -> bool {
    method depth (line 338) | pub fn depth(&self) -> f32 {
  type BoundingBox (line 350) | pub struct BoundingBox {
    method from_min_max (line 356) | pub fn from_min_max(min: Vec3, max: Vec3) -> Self {
    method distance (line 365) | pub fn distance(&self, point: Vec3) -> f32 {
    method get_mesh (line 389) | pub fn get_mesh(&self) -> [(Vec3, Vec3); 36] {
    method contains_point (line 423) | pub fn contains_point(&self, point: Vec3) -> bool {
  type BoundingSphere (line 432) | pub struct BoundingSphere {
    method from (line 438) | fn from((min, max): (Vec3, Vec3)) -> Self {
    method from (line 446) | fn from(value: Aabb) -> Self {
    method new (line 453) | pub fn new(center: impl Into<Vec3>, radius: f32) -> BoundingSphere {
    method is_inside_camera_view (line 462) | pub fn is_inside_camera_view(
    method project_by (line 477) | pub fn project_by(&self, view_projection: &Mat4) -> Self {
    method project_onto_viewport (line 492) | pub fn project_onto_viewport(&self, camera: &CameraDescriptor, viewpor...
  type BVol (line 533) | pub trait BVol {
    method get_aabb (line 520) | fn get_aabb(&self) -> Aabb {
    method culls_this_plane (line 527) | fn culls_this_plane(&self, plane: &Vec4) -> bool {
    method get_aabb (line 535) | fn get_aabb(&self) -> Aabb;
    method culls_this_plane (line 540) | fn culls_this_plane(&self, plane: &Vec4) -> bool;
    method is_inside_frustum (line 542) | fn is_inside_frustum(&self, frustum: Frustum) -> bool {
    method coherent_test_is_volume_outside_frustum (line 560) | fn coherent_test_is_volume_outside_frustum(
    method get_aabb (line 584) | fn get_aabb(&self) -> Aabb {
    method culls_this_plane (line 588) | fn culls_this_plane(&self, plane: &Vec4) -> bool {
  function bvol_frustum_is_in_world_space_sanity (line 602) | fn bvol_frustum_is_in_world_space_sanity() {
  function frustum_culling_debug_corner_case (line 619) | fn frustum_culling_debug_corner_case() {
  function bounding_box_from_min_max (line 650) | fn bounding_box_from_min_max() {
  function orthographic_projection_lighting (line 723) | fn orthographic_projection_lighting() {
  function aabb_intersection (line 779) | fn aabb_intersection() {
  function aabb_union (line 787) | fn aabb_union() {

FILE: crates/renderling/src/camera.rs
  function default_perspective (line 35) | pub fn default_perspective(width: f32, height: f32) -> (Mat4, Mat4) {
  function perspective (line 44) | pub fn perspective(width: f32, height: f32) -> Mat4 {
  function ortho (line 58) | pub fn ortho(width: f32, height: f32) -> Mat4 {
  function look_at (line 68) | pub fn look_at(eye: impl Into<Vec3>, target: impl Into<Vec3>, up: impl I...
  function default_ortho2d (line 79) | pub fn default_ortho2d(width: f32, height: f32) -> (Mat4, Mat4) {
  function forward (line 99) | fn forward() {

FILE: crates/renderling/src/camera/cpu.rs
  type Camera (line 21) | pub struct Camera {
    method as_ref (line 26) | fn as_ref(&self) -> &Camera {
    method new (line 33) | pub fn new(slab: &SlabAllocator<impl IsRuntime>) -> Self {
    method id (line 40) | pub fn id(&self) -> Id<CameraDescriptor> {
    method descriptor (line 45) | pub fn descriptor(&self) -> CameraDescriptor {
    method set_default_perspective (line 73) | pub fn set_default_perspective(&self, width: f32, height: f32) -> &Self {
    method with_default_perspective (line 103) | pub fn with_default_perspective(self, width: f32, height: f32) -> Self {
    method set_default_ortho2d (line 110) | pub fn set_default_ortho2d(&self, width: f32, height: f32) -> &Self {
    method with_default_ortho2d (line 118) | pub fn with_default_ortho2d(self, width: f32, height: f32) -> Self {
    method set_projection_and_view (line 124) | pub fn set_projection_and_view(
    method with_projection_and_view (line 135) | pub fn with_projection_and_view(
    method projection_and_view (line 145) | pub fn projection_and_view(&self) -> (Mat4, Mat4) {
    method set_projection (line 151) | pub fn set_projection(&self, projection: impl Into<Mat4>) -> &Self {
    method with_projection (line 157) | pub fn with_projection(self, projection: impl Into<Mat4>) -> Self {
    method projection (line 163) | pub fn projection(&self) -> Mat4 {
    method set_view (line 168) | pub fn set_view(&self, view: impl Into<Mat4>) -> &Self {
    method with_view (line 174) | pub fn with_view(self, view: impl Into<Mat4>) -> Self {
    method view (line 180) | pub fn view(&self) -> Mat4 {
  function camera_position_sanity (line 192) | fn camera_position_sanity() {

FILE: crates/renderling/src/camera/shader.rs
  type CameraDescriptor (line 14) | pub struct CameraDescriptor {
    method new (line 26) | pub fn new(projection: Mat4, view: Mat4) -> Self {
    method default_perspective (line 30) | pub fn default_perspective(width: f32, height: f32) -> Self {
    method default_ortho2d (line 35) | pub fn default_ortho2d(width: f32, height: f32) -> Self {
    method projection (line 40) | pub fn projection(&self) -> Mat4 {
    method set_projection_and_view (line 54) | pub fn set_projection_and_view(&mut self, projection: Mat4, view: Mat4) {
    method with_projection_and_view (line 82) | pub fn with_projection_and_view(mut self, projection: Mat4, view: Mat4...
    method set_projection (line 87) | pub fn set_projection(&mut self, projection: Mat4) {
    method with_projection (line 91) | pub fn with_projection(mut self, projection: Mat4) -> Self {
    method view (line 96) | pub fn view(&self) -> Mat4 {
    method set_view (line 100) | pub fn set_view(&mut self, view: Mat4) {
    method with_view (line 104) | pub fn with_view(mut self, view: Mat4) -> Self {
    method position (line 109) | pub fn position(&self) -> Vec3 {
    method frustum (line 113) | pub fn frustum(&self) -> Frustum {
    method view_projection (line 117) | pub fn view_projection(&self) -> Mat4 {
    method near_plane (line 121) | pub fn near_plane(&self) -> Vec4 {
    method far_plane (line 125) | pub fn far_plane(&self) -> Vec4 {
    method z_near (line 130) | pub fn z_near(&self) -> f32 {
    method z_far (line 134) | pub fn z_far(&self) -> f32 {
    method depth (line 138) | pub fn depth(&self) -> f32 {
    method forward (line 144) | pub fn forward(&self) -> Vec3 {
    method frustum_near_point (line 148) | pub fn frustum_near_point(&self) -> Vec3 {
    method frustum_far_point (line 152) | pub fn frustum_far_point(&self) -> Vec3 {

FILE: crates/renderling/src/color.rs
  function linear_xfer_u8 (line 14) | pub fn linear_xfer_u8(c: &mut u8) {
  function opto_xfer_u8 (line 25) | pub fn opto_xfer_u8(c: &mut u8) {
  function linear_xfer_u16 (line 36) | pub fn linear_xfer_u16(c: &mut u16) {
  function linear_xfer_f16 (line 52) | pub fn linear_xfer_f16(c: &mut u16) {
  function u16_to_u8 (line 58) | pub fn u16_to_u8(c: u16) -> u8 {
  function f32_to_u8 (line 62) | pub fn f32_to_u8(c: f32) -> u8 {
  function u8_to_f32 (line 66) | pub fn u8_to_f32(c: u8) -> f32 {
  function css_srgb_color_to_linear (line 74) | pub fn css_srgb_color_to_linear(r: u8, g: u8, b: u8) -> Vec4 {
  function linear_xfer_f32 (line 91) | pub fn linear_xfer_f32(c: &mut f32) {
  function linear_xfer_vec4 (line 101) | pub fn linear_xfer_vec4(v: &mut Vec4) {
  function rgb_hex_color (line 114) | pub fn rgb_hex_color(hex: u32) -> Vec4 {
  function can_rgb_hex_color (line 126) | fn can_rgb_hex_color() {

FILE: crates/renderling/src/compositor.rs
  function compositor_vertex (line 14) | pub fn compositor_vertex(
  function compositor_fragment (line 29) | pub fn compositor_fragment(

FILE: crates/renderling/src/compositor/cpu.rs
  type Compositor (line 15) | pub struct Compositor {
    method new (line 23) | pub fn new(device: &wgpu::Device, format: wgpu::TextureFormat) -> Self {
    method composite (line 111) | pub fn composite(

FILE: crates/renderling/src/context.rs
  type RenderTargetInner (line 23) | pub(crate) enum RenderTargetInner {
  type RenderTarget (line 37) | pub struct RenderTarget(pub(crate) RenderTargetInner);
    method from (line 41) | fn from(value: wgpu::Texture) -> Self {
    method resize (line 51) | pub fn resize(&mut self, width: u32, height: u32, device: &wgpu::Devic...
    method format (line 84) | pub fn format(&self) -> wgpu::TextureFormat {
    method is_headless (line 92) | pub fn is_headless(&self) -> bool {
    method as_texture (line 100) | pub fn as_texture(&self) -> Option<&wgpu::Texture> {
    method get_size (line 108) | pub fn get_size(&self) -> UVec2 {
  type ContextError (line 125) | pub enum ContextError {
  type FrameTextureView (line 143) | pub struct FrameTextureView {
  type Target (line 149) | type Target = wgpu::TextureView;
  method deref (line 151) | fn deref(&self) -> &Self::Target {
  type FrameSurface (line 158) | pub(crate) enum FrameSurface {
  type Frame (line 166) | pub struct Frame {
    method texture (line 173) | pub fn texture(&self) -> &wgpu::Texture {
    method view (line 181) | pub fn view(&self) -> wgpu::TextureView {
    method copy_to_buffer (line 192) | pub fn copy_to_buffer(&self, width: u32, height: u32) -> CopiedTexture...
    method get_size (line 235) | pub fn get_size(&self) -> UVec2 {
    method read_image (line 249) | pub async fn read_image(&self) -> Result<image::RgbaImage, TextureErro...
    method read_srgb_image (line 268) | pub async fn read_srgb_image(&self) -> Result<image::RgbaImage, Textur...
    method read_linear_image (line 281) | pub async fn read_linear_image(&self) -> Result<image::RgbaImage, Text...
    method present (line 289) | pub fn present(self) {
  type GlobalStageConfig (line 299) | pub(crate) struct GlobalStageConfig {
  type Context (line 315) | pub struct Context {
    method as_ref (line 323) | fn as_ref(&self) -> &WgpuRuntime {
    method new (line 331) | pub fn new(
    method try_new_headless (line 371) | pub async fn try_new_headless(
    method try_new_with_surface (line 385) | pub async fn try_new_with_surface(
    method from_winit_window (line 403) | pub async fn from_winit_window(
    method headless (line 420) | pub async fn headless(width: u32, height: u32) -> Self {
    method get_size (line 433) | pub fn get_size(&self) -> UVec2 {
    method set_size (line 438) | pub fn set_size(&mut self, size: UVec2) {
    method create_texture (line 444) | pub fn create_texture<P>(
    method texture_from_wgpu_tex (line 464) | pub fn texture_from_wgpu_tex(
    method runtime (line 473) | pub fn runtime(&self) -> &WgpuRuntime {
    method get_device (line 478) | pub fn get_device(&self) -> &wgpu::Device {
    method get_queue (line 483) | pub fn get_queue(&self) -> &wgpu::Queue {
    method get_adapter (line 488) | pub fn get_adapter(&self) -> &wgpu::Adapter {
    method get_adapter_owned (line 493) | pub fn get_adapter_owned(&self) -> Arc<wgpu::Adapter> {
    method get_render_target (line 498) | pub fn get_render_target(&self) -> &RenderTarget {
    method get_next_frame (line 513) | pub fn get_next_frame(&self) -> Result<Frame, ContextError> {
    method set_default_atlas_texture_size (line 533) | pub fn set_default_atlas_texture_size(&self, size: impl Into<UVec3>) -...
    method with_default_atlas_texture_size (line 556) | pub fn with_default_atlas_texture_size(self, size: impl Into<UVec3>) -...
    method set_shadow_mapping_atlas_texture_size (line 566) | pub fn set_shadow_mapping_atlas_texture_size(&self, size: impl Into<UV...
    method with_shadow_mapping_atlas_texture_size (line 589) | pub fn with_shadow_mapping_atlas_texture_size(self, size: impl Into<UV...
    method set_use_direct_draw (line 600) | pub fn set_use_direct_draw(&self, use_direct_drawing: bool) {
    method with_use_direct_draw (line 613) | pub fn with_use_direct_draw(self, use_direct_drawing: bool) -> Self {
    method get_use_direct_draw (line 619) | pub fn get_use_direct_draw(&self) -> bool {
    method new_stage (line 628) | pub fn new_stage(&self) -> Stage {

FILE: crates/renderling/src/convolution.rs
  function radical_inverse_vdc (line 22) | fn radical_inverse_vdc(mut bits: u32) -> f32 {
  function hammersley (line 31) | fn hammersley(i: u32, n: u32) -> Vec2 {
  function importance_sample_ggx (line 35) | fn importance_sample_ggx(xi: Vec2, n: Vec3, roughness: f32) -> Vec3 {
  function geometry_schlick_ggx (line 58) | fn geometry_schlick_ggx(n_dot_v: f32, roughness: f32) -> f32 {
  function geometry_smith (line 72) | fn geometry_smith(normal: Vec3, view_dir: Vec3, light_dir: Vec3, roughne...
  constant SAMPLE_COUNT (line 81) | const SAMPLE_COUNT: u32 = 1024;
  function integrate_brdf (line 83) | pub fn integrate_brdf(mut n_dot_v: f32, roughness: f32) -> Vec2 {
  function integrate_brdf_doesnt_work (line 119) | pub fn integrate_brdf_doesnt_work(mut n_dot_v: f32, roughness: f32) -> V...
  type VertexPrefilterEnvironmentCubemapIds (line 160) | pub struct VertexPrefilterEnvironmentCubemapIds {
  function prefilter_environment_cubemap_vertex (line 168) | pub fn prefilter_environment_cubemap_vertex(
  function prefilter_environment_cubemap_fragment (line 188) | pub fn prefilter_environment_cubemap_fragment(
  function calc_lod_old (line 228) | pub fn calc_lod_old(n: Vec3, v: Vec3, h: Vec3, roughness: f32) -> f32 {
  function calc_lod (line 242) | pub fn calc_lod(n_dot_l: f32) -> f32 {
  function generate_mipmap_vertex (line 250) | pub fn generate_mipmap_vertex(
  function generate_mipmap_fragment (line 262) | pub fn generate_mipmap_fragment(
  type Vert (line 273) | struct Vert {
  constant BRDF_VERTS (line 279) | const BRDF_VERTS: [Vert; 6] = {
  function brdf_lut_convolution_vertex (line 302) | pub fn brdf_lut_convolution_vertex(
  function brdf_lut_convolution_fragment (line 314) | pub fn brdf_lut_convolution_fragment(in_uv: glam::Vec2, out_color: &mut ...
  function integrate_brdf_sanity (line 324) | fn integrate_brdf_sanity() {

FILE: crates/renderling/src/cubemap.rs
  function cubemap_right (line 22) | fn cubemap_right() {
  function cubemap_face_index (line 37) | fn cubemap_face_index() {

FILE: crates/renderling/src/cubemap/cpu.rs
  function cpu_sample_cubemap (line 14) | pub fn cpu_sample_cubemap(cubemap: &[image::DynamicImage; 6], coord: Vec...
  type SceneCubemap (line 41) | pub struct SceneCubemap {
    method new (line 49) | pub fn new(
    method run (line 88) | pub fn run(&self, stage: &Stage) {
  type EquirectangularImageToCubemapBlitter (line 148) | pub struct EquirectangularImageToCubemapBlitter(pub wgpu::RenderPipeline);
    method create_bindgroup_layout (line 151) | pub fn create_bindgroup_layout(device: &wgpu::Device) -> wgpu::BindGro...
    method create_bindgroup (line 185) | pub fn create_bindgroup(
    method new (line 214) | pub fn new(device: &wgpu::Device, format: wgpu::TextureFormat) -> Self {
  function hand_rolled_cubemap_sampling (line 283) | fn hand_rolled_cubemap_sampling() {

FILE: crates/renderling/src/cubemap/shader.rs
  function cubemap_sampling_test_vertex (line 12) | pub fn cubemap_sampling_test_vertex(
  function cubemap_sampling_test_fragment (line 25) | pub fn cubemap_sampling_test_fragment(
  type CubemapFaceDirection (line 39) | pub struct CubemapFaceDirection {
    constant X (line 49) | pub const X: Self = Self {
    constant NEG_X (line 54) | pub const NEG_X: Self = Self {
    constant Y (line 60) | pub const Y: Self = Self {
    constant NEG_Y (line 65) | pub const NEG_Y: Self = Self {
    constant Z (line 71) | pub const Z: Self = Self {
    constant NEG_Z (line 76) | pub const NEG_Z: Self = Self {
    constant FACES (line 82) | pub const FACES: [Self; 6] = [
    method right (line 91) | pub fn right(&self) -> Vec3 {
    method view (line 96) | pub fn view(&self) -> Mat4 {
  type CubemapDescriptor (line 101) | pub struct CubemapDescriptor {
    method get_face_index_and_uv (line 109) | pub fn get_face_index_and_uv(coord: Vec3) -> (usize, Vec2) {
    method sample (line 136) | pub fn sample<A, S>(&self, coord: Vec3, slab: &[u32], atlas: &A, sampl...
  function cubemap_right (line 159) | fn cubemap_right() {
  function cubemap_face_index (line 174) | fn cubemap_face_index() {

FILE: crates/renderling/src/cull/cpu.rs
  type CullingError (line 17) | pub enum CullingError {
    method from (line 35) | fn from(source: SlabAllocatorError) -> Self {
  type ComputeCulling (line 41) | pub struct ComputeCulling {
    constant LABEL (line 55) | const LABEL: Option<&'static str> = Some("compute-culling");
    method new_bindgroup (line 57) | fn new_bindgroup(
    method new (line 90) | pub fn new(
    method runtime (line 171) | fn runtime(&self) -> &WgpuRuntime {
    method run (line 175) | pub fn run(&mut self, indirect_draw_count: u32, depth_texture: &Textur...
  type DepthPyramid (line 218) | pub struct DepthPyramid {
    constant LABEL (line 226) | const LABEL: &str = "depth-pyramid";
    method allocate (line 228) | fn allocate(
    method new (line 251) | pub fn new(runtime: impl AsRef<WgpuRuntime>, size: UVec2) -> Self {
    method resize (line 264) | pub fn resize(&mut self, size: UVec2) {
    method size (line 284) | pub fn size(&self) -> UVec2 {
    method read_images (line 288) | pub async fn read_images(&self) -> Result<Vec<image::GrayImage>, Culli...
  type ComputeCopyDepth (line 319) | struct ComputeCopyDepth {
    constant LABEL (line 328) | const LABEL: Option<&'static str> = Some("compute-occlusion-copy-depth...
    method create_bindgroup_layout (line 330) | fn create_bindgroup_layout(device: &wgpu::Device, sample_count: u32) -...
    method create_pipeline (line 371) | fn create_pipeline(
    method create_bindgroup (line 402) | fn create_bindgroup(
    method new (line 426) | pub fn new(depth_pyramid: &DepthPyramid, depth_texture: &Texture) -> S...
    method run (line 447) | pub fn run(&mut self, pyramid: &mut DepthPyramid, depth_texture: &Text...
  type ComputeDownsampleDepth (line 509) | struct ComputeDownsampleDepth {
    constant LABEL (line 517) | const LABEL: Option<&'static str> = Some("compute-occlusion-downsample...
    method create_bindgroup_layout (line 519) | fn create_bindgroup_layout(device: &wgpu::Device) -> wgpu::BindGroupLa...
    method create_pipeline (line 538) | fn create_pipeline(
    method create_bindgroup (line 559) | fn create_bindgroup(
    method new (line 576) | pub fn new(pyramid: &DepthPyramid) -> Self {
    method run (line 590) | pub fn run(&mut self, pyramid: &DepthPyramid) {
  type ComputeDepthPyramid (line 634) | pub struct ComputeDepthPyramid {
    constant _LABEL (line 641) | const _LABEL: Option<&'static str> = Some("compute-depth-pyramid");
    method new (line 643) | pub fn new(runtime: impl AsRef<WgpuRuntime>, depth_texture: &Texture) ...
    method run (line 657) | pub fn run(&mut self, depth_texture: &Texture) {
  function occlusion_culling_sanity (line 690) | fn occlusion_culling_sanity() {
  function depth_pyramid_descriptor_sanity (line 739) | fn depth_pyramid_descriptor_sanity() {
  function occlusion_culling_debugging (line 785) | fn occlusion_culling_debugging() {

FILE: crates/renderling/src/cull/shader.rs
  function compute_culling (line 17) | pub fn compute_culling(
  type DepthPyramidDescriptor (line 122) | pub struct DepthPyramidDescriptor {
    method should_skip_invocation (line 138) | fn should_skip_invocation(&self, global_invocation: UVec3) -> bool {
    method size_at (line 144) | pub fn size_at(&self, mip_level: u32) -> UVec2 {
    method id_of_depth (line 149) | pub fn id_of_depth(&self, mip_level: u32, coord: UVec2, slab: &[u32]) ...
  type DepthImage2d (line 157) | pub type DepthImage2d = Image!(2D, type=f32, sampled, depth);
  type DepthImage2dMultisampled (line 158) | pub type DepthImage2dMultisampled = Image!(2D, type=f32, sampled, depth,...
  function compute_copy_depth_to_pyramid (line 165) | pub fn compute_copy_depth_to_pyramid(
  function compute_copy_depth_to_pyramid_multisampled (line 187) | pub fn compute_copy_depth_to_pyramid_multisampled(
  function compute_downsample_depth_pyramid (line 215) | pub fn compute_downsample_depth_pyramid(

FILE: crates/renderling/src/debug.rs
  function debug_overlay_vertex (line 18) | pub fn debug_overlay_vertex(
  function debug_overlay_fragment (line 29) | pub fn debug_overlay_fragment(

FILE: crates/renderling/src/debug/cpu.rs
  type DebugOverlay (line 6) | pub struct DebugOverlay {
    constant LABEL (line 13) | const LABEL: Option<&'static str> = Some("debug-overlay");
    method create_bindgroup_layout (line 15) | fn create_bindgroup_layout(device: &wgpu::Device) -> wgpu::BindGroupLa...
    method create_pipeline_layout (line 45) | fn create_pipeline_layout(
    method create_pipeline (line 56) | fn create_pipeline(
    method create_bindgroup (line 99) | pub fn create_bindgroup(
    method new (line 123) | pub fn new(device: &wgpu::Device, format: wgpu::TextureFormat) -> Self {
    method render (line 132) | pub fn render(

FILE: crates/renderling/src/draw.rs
  type DrawIndirectArgs (line 18) | pub struct DrawIndirectArgs {

FILE: crates/renderling/src/draw/cpu.rs
  type IndirectDraws (line 21) | pub struct IndirectDraws {
    method new (line 28) | fn new(
    method slab_allocator (line 48) | pub(crate) fn slab_allocator(&self) -> &SlabAllocator<WgpuRuntime> {
    method invalidate (line 52) | fn invalidate(&mut self) {
    method read_hzb_images (line 63) | pub async fn read_hzb_images(&self) -> Result<Vec<image::GrayImage>, C...
  method from (line 73) | fn from(id: Id<PrimitiveDescriptor>) -> Self {
  type DrawingStrategy (line 93) | pub(crate) struct DrawingStrategy {
    method as_indirect (line 98) | pub(crate) fn as_indirect(&self) -> Option<&IndirectDraws> {
  type DrawCalls (line 105) | pub struct DrawCalls {
    method new (line 120) | pub fn new(
    method drawing_strategy (line 152) | pub(crate) fn drawing_strategy(&self) -> &DrawingStrategy {
    method get_compute_culling_available (line 157) | pub fn get_compute_culling_available(&self) -> bool {
    method add_primitive (line 167) | pub fn add_primitive(&mut self, renderlet: &Primitive) -> usize {
    method remove_primitive (line 180) | pub fn remove_primitive(&mut self, renderlet: &Primitive) -> usize {
    method sort_primitives (line 192) | pub fn sort_primitives(&mut self, f: impl Fn(&Primitive, &Primitive) -...
    method draw_count (line 201) | pub fn draw_count(&self) -> usize {
    method pre_draw (line 208) | pub fn pre_draw(
    method draw_direct (line 257) | pub fn draw_direct(&self, render_pass: &mut wgpu::RenderPass) {
    method draw (line 275) | pub fn draw(&self, render_pass: &mut wgpu::RenderPass) {

FILE: crates/renderling/src/geometry.rs
  type MorphTarget (line 20) | pub struct MorphTarget {
  type Vertex (line 30) | pub struct Vertex {
    method with_position (line 59) | pub fn with_position(mut self, p: impl Into<Vec3>) -> Self {
    method with_color (line 64) | pub fn with_color(mut self, c: impl Into<Vec4>) -> Self {
    method with_uv0 (line 69) | pub fn with_uv0(mut self, uv: impl Into<Vec2>) -> Self {
    method with_uv1 (line 74) | pub fn with_uv1(mut self, uv: impl Into<Vec2>) -> Self {
    method with_normal (line 79) | pub fn with_normal(mut self, n: impl Into<Vec3>) -> Self {
    method with_tangent (line 84) | pub fn with_tangent(mut self, t: impl Into<Vec4>) -> Self {
    method generate_normal (line 89) | pub fn generate_normal(a: Vec3, b: Vec3, c: Vec3) -> Vec3 {
    method generate_tangent (line 95) | pub fn generate_tangent(a: Vec3, a_uv: Vec2, b: Vec3, b_uv: Vec2, c: V...
    method cube_mesh (line 123) | pub fn cube_mesh() -> [Vertex; 36] {
  method default (line 44) | fn default() -> Self {

FILE: crates/renderling/src/geometry/cpu.rs
  type Vertices (line 38) | pub struct Vertices<Ct: IsContainer = GpuCpuArray> {
    method new (line 89) | pub(crate) fn new(
    method into_gpu_only (line 102) | pub fn into_gpu_only(self) -> Vertices<GpuOnlyArray> {
    method get_vertex (line 109) | pub fn get_vertex(&self, index: usize) -> Option<Vertex> {
    method get_vec (line 114) | pub fn get_vec(&self) -> Vec<Vertex> {
    method modify_vertex (line 119) | pub fn modify_vertex<T: 'static>(
  method clone (line 47) | fn clone(&self) -> Self {
  function fmt (line 58) | fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
  function from (line 66) | fn from(value: Vertices) -> Self {
  function from (line 72) | fn from(value: &Vertices) -> Self {
  function from (line 78) | fn from(value: &Vertices<GpuOnlyArray>) -> Self {
  function array (line 133) | pub fn array(&self) -> Array<Vertex> {
  function set_vertex (line 141) | pub fn set_vertex(&self, index: usize, value: &Vertex) {
  type Indices (line 149) | pub struct Indices<Ct: IsContainer = GpuCpuArray> {
    method new (line 210) | pub fn new(geometry: &Geometry, indices: impl IntoIterator<Item = u32>...
    method into_gpu_only (line 217) | pub fn into_gpu_only(self) -> Indices<GpuOnlyArray> {
  function fmt (line 157) | fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
  method clone (line 169) | fn clone(&self) -> Self {
  function from (line 177) | fn from(value: Indices) -> Self {
  function from (line 183) | fn from(value: &Indices) -> Self {
  function from (line 189) | fn from(value: &Indices<GpuOnlyArray>) -> Self {
  function array (line 199) | pub fn array(&self) -> Array<u32> {
  type MorphTargets (line 226) | pub struct MorphTargets {
    method from (line 233) | fn from(value: &MorphTargets) -> Self {
    method fmt (line 239) | fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
    method new (line 248) | pub(crate) fn new(
    method array (line 263) | pub fn array(&self) -> Array<Array<MorphTarget>> {
  type MorphTargetWeights (line 270) | pub struct MorphTargetWeights {
    method from (line 275) | fn from(value: &MorphTargetWeights) -> Self {
    method new (line 281) | pub(crate) fn new(
    method array (line 292) | pub fn array(&self) -> Array<f32> {
    method get_item (line 297) | pub fn get_item(&self, index: usize) -> Option<f32> {
    method set_item (line 302) | pub fn set_item(&self, index: usize, weight: f32) {
  type Geometry (line 309) | pub struct Geometry {
    method as_ref (line 322) | fn as_ref(&self) -> &WgpuRuntime {
    method as_ref (line 328) | fn as_ref(&self) -> &SlabAllocator<WgpuRuntime> {
    method new (line 334) | pub fn new(runtime: impl AsRef<WgpuRuntime>, resolution: UVec2, atlas_...
    method runtime (line 359) | pub fn runtime(&self) -> &WgpuRuntime {
    method slab_allocator (line 363) | pub fn slab_allocator(&self) -> &SlabAllocator<WgpuRuntime> {
    method descriptor (line 367) | pub fn descriptor(&self) -> &Hybrid<GeometryDescriptor> {
    method default_vertices (line 372) | pub fn default_vertices(&self) -> &Vertices {
    method commit (line 377) | pub fn commit(&self) -> SlabBuffer<wgpu::Buffer> {
    method new_camera (line 384) | pub fn new_camera(&self) -> Camera {
    method use_camera (line 393) | pub fn use_camera(&self, camera: &Camera) {
    method new_transform (line 402) | pub fn new_transform(&self) -> Transform {
    method new_vertices (line 407) | pub fn new_vertices(&self, vertices: impl IntoIterator<Item = Vertex>)...
    method new_indices (line 412) | pub fn new_indices(&self, indices: impl IntoIterator<Item = u32>) -> I...
    method new_morph_targets (line 417) | pub fn new_morph_targets(
    method new_morph_target_weights (line 425) | pub fn new_morph_target_weights(
    method new_matrices (line 433) | pub fn new_matrices(&self, data: impl IntoIterator<Item = Mat4>) -> Hy...
    method new_skin (line 437) | pub fn new_skin(
  type Skin (line 451) | pub struct Skin {
    method from (line 464) | fn from(value: &Skin) -> Self {
    method fmt (line 470) | fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
    method new (line 482) | pub fn new(
    method id (line 510) | pub fn id(&self) -> Id<SkinDescriptor> {
    method descriptor (line 515) | pub fn descriptor(&self) -> SkinDescriptor {
  type SkinJoint (line 528) | pub struct SkinJoint(pub(crate) Transform);
    method from (line 531) | fn from(transform: Transform) -> Self {
    method from (line 537) | fn from(transform: &Transform) -> Self {
    method from (line 543) | fn from(value: NestedTransform) -> Self {
    method from (line 549) | fn from(value: &NestedTransform) -> Self {

FILE: crates/renderling/src/geometry/shader.rs
  type SkinDescriptor (line 13) | pub struct SkinDescriptor {
    method get_joint_matrix (line 24) | pub fn get_joint_matrix(&self, i: usize, vertex: Vertex, slab: &[u32])...
    method get_skinning_matrix (line 35) | pub fn get_skinning_matrix(&self, vertex: Vertex, slab: &[u32]) -> Mat4 {
  type GeometryDescriptor (line 58) | pub struct GeometryDescriptor {
  method default (line 70) | fn default() -> Self {

FILE: crates/renderling/src/gltf.rs
  type StageGltfError (line 36) | pub enum StageGltfError {
    method from (line 107) | fn from(source: AtlasError) -> Self {
  method from (line 113) | fn from(transform: gltf::scene::Transform) -> Self {
  function from_gltf_light_kind (line 123) | pub fn from_gltf_light_kind(kind: gltf::khr_lights_punctual::Kind) -> Li...
  function gltf_light_intensity_units (line 131) | pub fn gltf_light_intensity_units(kind: gltf::khr_lights_punctual::Kind)...
  method from_gltf (line 140) | fn from_gltf(mode: gltf::texture::WrappingMode) -> TextureAddressMode {
  function get_vertex_count (line 149) | pub fn get_vertex_count(primitive: &gltf::Primitive<'_>) -> u32 {
  method preprocess_images (line 165) | pub fn preprocess_images(
  method from_gltf (line 211) | pub fn from_gltf(
  type GltfPrimitive (line 287) | pub struct GltfPrimitive<Ct: IsContainer = GpuCpuArray> {
    method from_gltf (line 312) | pub fn from_gltf(
    method into_gpu_only (line 560) | pub fn into_gpu_only(self) -> GltfPrimitive<GpuOnlyArray> {
  function fmt (line 300) | fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
  type GltfMesh (line 578) | pub struct GltfMesh<Ct: IsContainer = GpuCpuArray> {
    method from_gltf (line 599) | fn from_gltf(stage: &Stage, buffer_data: &[gltf::buffer::Data], mesh: ...
    method into_gpu_only (line 613) | pub fn into_gpu_only(self) -> GltfMesh<GpuOnlyArray> {
  function fmt (line 590) | fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
  type GltfCamera (line 630) | pub struct GltfCamera {
    method as_ref (line 638) | fn as_ref(&self) -> &Camera {
    method new (line 644) | fn new(stage: &Stage, gltf_camera: gltf::Camera<'_>, transform: &Neste...
  type GltfNode (line 690) | pub struct GltfNode {
    method global_transform (line 714) | pub fn global_transform(&self) -> TransformDescriptor {
  type GltfSkin (line 720) | pub struct GltfSkin {
    method from_gltf (line 733) | pub fn from_gltf(
  type GltfDocument (line 793) | pub struct GltfDocument<Ct: IsContainer = GpuCpuArray> {
    method from_gltf (line 812) | pub fn from_gltf(
    method into_gpu_only (line 1193) | pub fn into_gpu_only(self) -> GltfDocument<GpuOnlyArray> {
  function renderlets_iter (line 1236) | pub fn renderlets_iter(&self) -> impl Iterator<Item = &Primitive> {
  function collect_nodes_recursive (line 1240) | fn collect_nodes_recursive<'a>(&'a self, node_index: usize, nodes: &mut ...
  function root_nodes_in_scene (line 1257) | pub fn root_nodes_in_scene(&self, scene_index: usize) -> impl Iterator<I...
  function recursive_nodes_in_scene (line 1275) | pub fn recursive_nodes_in_scene(&self, scene_index: usize) -> impl Itera...
  function bounding_volume (line 1290) | pub fn bounding_volume(&self) -> Option<Aabb> {
  function get_vertex_count_primitive_sanity (line 1317) | fn get_vertex_count_primitive_sanity() {
  function stage_gltf_simple_meshes (line 1337) | fn stage_gltf_simple_meshes() {
  function minimal_mesh (line 1362) | fn minimal_mesh() {
  function gltf_images (line 1391) | fn gltf_images() {
  function simple_texture (line 1443) | fn simple_texture() {
  function normal_mapping_brick_sphere (line 1473) | fn normal_mapping_brick_sphere() {
  function rigged_fox (line 1491) | fn rigged_fox() {
  function camera_position_sanity (line 1573) | fn camera_position_sanity() {

FILE: crates/renderling/src/gltf/anime.rs
  type InterpolationError (line 8) | pub enum InterpolationError {
  type Interpolation (line 32) | pub enum Interpolation {
    method fmt (line 39) | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
    method from (line 49) | fn from(value: gltf::animation::Interpolation) -> Self {
    method is_cubic_spline (line 59) | fn is_cubic_spline(&self) -> bool {
  type Keyframe (line 65) | pub struct Keyframe(pub f32);
  type TweenProperty (line 68) | pub enum TweenProperty {
    method as_translation (line 76) | fn as_translation(&self) -> Option<&Vec3> {
    method as_rotation (line 83) | fn as_rotation(&self) -> Option<&Quat> {
    method as_scale (line 90) | fn as_scale(&self) -> Option<&Vec3> {
    method as_morph_target_weights (line 97) | fn as_morph_target_weights(&self) -> Option<&Vec<f32>> {
    method description (line 104) | pub fn description(&self) -> &'static str {
  type TweenProperties (line 116) | pub enum TweenProperties {
    method get (line 124) | pub fn get(&self, index: usize) -> Option<TweenProperty> {
    method get_cubic (line 141) | pub fn get_cubic(&self, index: usize) -> Option<[TweenProperty; 3]> {
    method description (line 192) | pub fn description(&self) -> &'static str {
  type Tween (line 203) | pub struct Tween {
    method interpolate (line 221) | pub fn interpolate(&self, time: f32) -> Result<Option<TweenProperty>, ...
    method interpolate_wrap (line 237) | pub fn interpolate_wrap(&self, time: f32) -> Result<Option<TweenProper...
    method get_previous_keyframe (line 243) | fn get_previous_keyframe(
    method get_next_keyframe (line 255) | fn get_next_keyframe(
    method interpolate_step (line 267) | fn interpolate_step(&self, time: f32) -> Result<Option<TweenProperty>,...
    method interpolate_cubic (line 280) | fn interpolate_cubic(&self, time: f32) -> Result<Option<TweenProperty>...
    method interpolate_linear (line 404) | fn interpolate_linear(&self, time: f32) -> Result<Option<TweenProperty...
    method length_in_seconds (line 462) | pub fn length_in_seconds(&self) -> f32 {
    method get_first_keyframe_property (line 471) | pub fn get_first_keyframe_property(&self) -> Option<TweenProperty> {
    method get_last_keyframe_property (line 504) | pub fn get_last_keyframe_property(&self) -> Option<TweenProperty> {
  type AnimationNode (line 545) | pub struct AnimationNode {
  function from (line 551) | fn from(node: &GltfNode) -> Self {
  type AnimationError (line 563) | pub enum AnimationError {
  type Animation (line 572) | pub struct Animation {
    method from_gltf (line 579) | pub fn from_gltf(
    method length_in_seconds (line 642) | pub fn length_in_seconds(&self) -> f32 {
    method get_properties_at_time (line 650) | pub fn get_properties_at_time(
    method into_animator (line 669) | pub fn into_animator(
    method target_node_indices (line 676) | pub fn target_node_indices(&self) -> impl Iterator<Item = usize> + '_ {
  type Animator (line 689) | pub struct Animator {
    method new (line 701) | pub fn new(
    method progress (line 716) | pub fn progress(&mut self, dt_seconds: f32) -> Result<(), Interpolatio...
  function gltf_simple_animation (line 773) | fn gltf_simple_animation() {

FILE: crates/renderling/src/internal/cpu.rs
  function adapter (line 12) | pub async fn adapter(
  function device (line 45) | pub async fn device(
  function new_instance (line 80) | pub fn new_instance(backends: Option<wgpu::Backends>) -> wgpu::Instance {
  function new_windowed_adapter_device_queue (line 105) | pub async fn new_windowed_adapter_device_queue(
  function new_headless_device_queue_and_target (line 152) | pub async fn new_headless_device_queue_and_target(

FILE: crates/renderling/src/lib.rs
  function capture_gpu_frame (line 258) | pub fn capture_gpu_frame<T>(
  type BlockOnFuture (line 314) | pub trait BlockOnFuture {
    method block (line 318) | fn block(self) -> Self::Output;
    type Output (line 323) | type Output = <Self as std::future::Future>::Output;
    method block (line 325) | fn block(self) -> Self::Output {
  function init_logging (line 347) | fn init_logging() {
  function capture_gpu_frame (line 353) | pub fn capture_gpu_frame<T>(
  function make_two_directional_light_setup (line 362) | pub fn make_two_directional_light_setup(stage: &Stage) -> (AnalyticalLig...
  function sanity_transmute (line 377) | fn sanity_transmute() {
  function right_tri_vertices (line 394) | pub fn right_tri_vertices() -> Vec<Vertex> {
  function cmy_triangle_sanity (line 410) | fn cmy_triangle_sanity() {
  function cmy_triangle_backface (line 449) | fn cmy_triangle_backface() {
  function cmy_triangle_update_transform (line 479) | fn cmy_triangle_update_transform() {
  function pyramid_points (line 510) | fn pyramid_points() -> [Vec3; 5] {
  function pyramid_indices (line 519) | fn pyramid_indices() -> [u16; 18] {
  function cmy_gpu_vertex (line 526) | fn cmy_gpu_vertex(p: Vec3) -> Vertex {
  function gpu_cube_vertices (line 535) | pub fn gpu_cube_vertices() -> Vec<Vertex> {
  function cmy_cube_sanity (line 544) | fn cmy_cube_sanity() {
  function cmy_cube_indices (line 570) | fn cmy_cube_indices() {
  function cmy_cube_visible (line 606) | fn cmy_cube_visible() {
  function cmy_cube_remesh (line 667) | fn cmy_cube_remesh() {
  function gpu_uv_unit_cube (line 710) | fn gpu_uv_unit_cube() -> Vec<Vertex> {
  function unlit_textured_cube_material (line 759) | fn unlit_textured_cube_material() {
  function multi_node_scene (line 815) | fn multi_node_scene() {
  function scene_cube_directional (line 877) | fn scene_cube_directional() {
  function square_scale_norm_check (line 935) | fn square_scale_norm_check() {
  function scene_parent_sanity (line 966) | fn scene_parent_sanity() {
  function camera_position_from_view_matrix (line 1045) | fn camera_position_from_view_matrix() {
  function can_resize_context_and_stage (line 1053) | fn can_resize_context_and_stage() {
  function can_direct_draw_cube (line 1094) | fn can_direct_draw_cube() {

FILE: crates/renderling/src/light.rs
  function position_direction_sanity (line 113) | fn position_direction_sanity() {
  function clip_space_bounds_sanity (line 155) | fn clip_space_bounds_sanity() {
  function finding_orthogonal_vectors_sanity (line 179) | fn finding_orthogonal_vectors_sanity() {
  function next_light_sanity (line 204) | fn next_light_sanity() {
  function frag_coord_to_tile_index (line 253) | fn frag_coord_to_tile_index() {

FILE: crates/renderling/src/light/cpu.rs
  type LightingError (line 33) | pub enum LightingError {
    method from (line 42) | fn from(source: AtlasError) -> Self {
  type IsLight (line 48) | pub trait IsLight: Clone {
    method style (line 50) | fn style(&self) -> LightStyle;
    method light_space_transforms (line 52) | fn light_space_transforms(
    method style (line 77) | fn style(&self) -> LightStyle {
    method light_space_transforms (line 81) | fn light_space_transforms(
    method style (line 189) | fn style(&self) -> LightStyle {
    method light_space_transforms (line 193) | fn light_space_transforms(
    method style (line 304) | fn style(&self) -> LightStyle {
    method light_space_transforms (line 308) | fn light_space_transforms(
    method style (line 508) | fn style(&self) -> LightStyle {
    method light_space_transforms (line 516) | fn light_space_transforms(
    method style (line 583) | fn style(&self) -> LightStyle {
    method light_space_transforms (line 587) | fn light_space_transforms(
  type DirectionalLight (line 72) | pub struct DirectionalLight {
    method id (line 99) | pub fn id(&self) -> Id<DirectionalLightDescriptor> {
    method descriptor (line 104) | pub fn descriptor(&self) -> DirectionalLightDescriptor {
  function set_direction (line 114) | pub fn set_direction(&self, direction: Vec3) -> &Self {
  function with_direction (line 120) | pub fn with_direction(self, direction: Vec3) -> Self {
  function modify_direction (line 126) | pub fn modify_direction<T: 'static>(&self, f: impl FnOnce(&mut Vec3) -> ...
  function direction (line 131) | pub fn direction(&self) -> Vec3 {
  function set_color (line 136) | pub fn set_color(&self, color: Vec4) -> &Self {
  function with_color (line 142) | pub fn with_color(self, color: Vec4) -> Self {
  function modify_color (line 148) | pub fn modify_color<T: 'static>(&self, f: impl FnOnce(&mut Vec4) -> T) -...
  function color (line 153) | pub fn color(&self) -> Vec4 {
  function set_intensity (line 158) | pub fn set_intensity(&self, intensity: Lux) -> &Self {
  function with_intensity (line 164) | pub fn with_intensity(self, intensity: Lux) -> Self {
  function modify_intensity (line 170) | pub fn modify_intensity<T: 'static>(&self, f: impl FnOnce(&mut Lux) -> T...
  function intensity (line 175) | pub fn intensity(&self) -> Lux {
  type PointLight (line 184) | pub struct PointLight {
    method id (line 213) | pub fn id(&self) -> Id<PointLightDescriptor> {
    method descriptor (line 218) | pub fn descriptor(&self) -> PointLightDescriptor {
  function set_position (line 229) | pub fn set_position(&self, position: Vec3) -> &Self {
  function with_position (line 235) | pub fn with_position(self, position: Vec3) -> Self {
  function modify_position (line 241) | pub fn modify_position<T: 'static>(&self, f: impl FnOnce(&mut Vec3) -> T...
  function position (line 246) | pub fn position(&self) -> Vec3 {
  function set_color (line 251) | pub fn set_color(&self, color: Vec4) -> &Self {
  function with_color (line 257) | pub fn with_color(self, color: Vec4) -> Self {
  function modify_color (line 263) | pub fn modify_color<T: 'static>(&self, f: impl FnOnce(&mut Vec4) -> T) -...
  function color (line 268) | pub fn color(&self) -> Vec4 {
  function set_intensity (line 273) | pub fn set_intensity(&self, intensity: Candela) -> &Self {
  function with_intensity (line 279) | pub fn with_intensity(self, intensity: Candela) -> Self {
  function modify_intensity (line 285) | pub fn modify_intensity<T: 'static>(&self, f: impl FnOnce(&mut Candela) ...
  function intensity (line 290) | pub fn intensity(&self) -> Candela {
  type SpotLight (line 299) | pub struct SpotLight {
    method id (line 330) | pub fn id(&self) -> Id<SpotLightDescriptor> {
    method descriptor (line 335) | pub fn descriptor(&self) -> SpotLightDescriptor {
  function set_position (line 346) | pub fn set_position(&self, position: Vec3) -> &Self {
  function with_position (line 352) | pub fn with_position(self, position: Vec3) -> Self {
  function modify_position (line 358) | pub fn modify_position<T: 'static>(&self, f: impl FnOnce(&mut Vec3) -> T...
  function position (line 363) | pub fn position(&self) -> Vec3 {
  function set_direction (line 368) | pub fn set_direction(&self, direction: Vec3) -> &Self {
  function with_direction (line 374) | pub fn with_direction(self, direction: Vec3) -> Self {
  function modify_direction (line 380) | pub fn modify_direction<T: 'static>(&self, f: impl FnOnce(&mut Vec3) -> ...
  function direction (line 385) | pub fn direction(&self) -> Vec3 {
  function set_inner_cutoff (line 390) | pub fn set_inner_cutoff(&self, inner_cutoff: f32) -> &Self {
  function with_inner_cutoff (line 398) | pub fn with_inner_cutoff(self, inner_cutoff: f32) -> Self {
  function modify_inner_cutoff (line 404) | pub fn modify_inner_cutoff<T: 'static>(&self, f: impl FnOnce(&mut f32) -...
  function inner_cutoff (line 409) | pub fn inner_cutoff(&self) -> f32 {
  function set_outer_cutoff (line 414) | pub fn set_outer_cutoff(&self, outer_cutoff: f32) -> &Self {
  function with_outer_cutoff (line 422) | pub fn with_outer_cutoff(self, outer_cutoff: f32) -> Self {
  function modify_outer_cutoff (line 428) | pub fn modify_outer_cutoff<T: 'static>(&self, f: impl FnOnce(&mut f32) -...
  function outer_cutoff (line 433) | pub fn outer_cutoff(&self) -> f32 {
  function set_color (line 438) | pub fn set_color(&self, color: Vec4) -> &Self {
  function with_color (line 444) | pub fn with_color(self, color: Vec4) -> Self {
  function modify_color (line 450) | pub fn modify_color<T: 'static>(&self, f: impl FnOnce(&mut Vec4) -> T) -...
  function color (line 455) | pub fn color(&self) -> Vec4 {
  function set_intensity (line 460) | pub fn set_intensity(&self, intensity: Candela) -> &Self {
  function with_intensity (line 466) | pub fn with_intensity(self, intensity: Candela) -> Self {
  function modify_intensity (line 472) | pub fn modify_intensity<T: 'static>(&self, f: impl FnOnce(&mut Candela) ...
  function intensity (line 477) | pub fn intensity(&self) -> Candela {
  type Light (line 483) | pub enum Light {
    method from (line 490) | fn from(light: DirectionalLight) -> Self {
    method from (line 496) | fn from(light: PointLight) -> Self {
    method from (line 502) | fn from(light: SpotLight) -> Self {
  type AnalyticalLight (line 549) | pub struct AnalyticalLight<T = Light> {
    method as_directional (line 609) | pub fn as_directional(&self) -> Option<&DirectionalLight> {
    method as_point (line 618) | pub fn as_point(&self) -> Option<&PointLight> {
    method as_spot (line 627) | pub fn as_spot(&self) -> Option<&SpotLight> {
  function fmt (line 568) | fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
  function id (line 637) | pub fn id(&self) -> Id<LightDescriptor> {
  function descriptor (line 642) | pub fn descriptor(&self) -> LightDescriptor {
  function link_node_transform (line 647) | pub fn link_node_transform(&self, transform: &NestedTransform) {
  function inner (line 655) | pub fn inner(&self) -> &T {
  function transform (line 668) | pub fn transform(&self) -> &Transform {
  function linked_node_transform (line 678) | pub fn linked_node_transform(&self) -> Option<NestedTransform> {
  function into_generic (line 690) | pub fn into_generic(self) -> AnalyticalLight
  type Lighting (line 712) | pub struct Lighting {
    method create_shadow_map_atlas (line 767) | fn create_shadow_map_atlas(
    method new (line 784) | pub fn new(atlas_size: wgpu::Extent3d, geometry: &Geometry) -> Self {
    method slab_allocator (line 813) | pub fn slab_allocator(&self) -> &SlabAllocator<WgpuRuntime> {
    method add_light (line 827) | pub fn add_light<T>(&self, bundle: &AnalyticalLight<T>)
    method remove_light (line 855) | pub fn remove_light<T: IsLight>(&self, bundle: &AnalyticalLight<T>) {
    method lights (line 876) | pub fn lights(&self) -> Vec<AnalyticalLight> {
    method new_directional_light (line 886) | pub fn new_directional_light(&self) -> AnalyticalLight<DirectionalLigh...
    method new_point_light (line 911) | pub fn new_point_light(&self) -> AnalyticalLight<PointLight> {
    method new_spot_light (line 934) | pub fn new_spot_light(&self) -> AnalyticalLight<SpotLight> {
    method set_ambient_color (line 961) | pub fn set_ambient_color(&self, color: Vec4) {
    method ambient_color (line 966) | pub fn ambient_color(&self) -> Vec4 {
    method new_shadow_map (line 972) | pub fn new_shadow_map<T>(
    method commit (line 994) | pub fn commit(&self) -> SlabBuffer<wgpu::Buffer> {
  type LightingBindGroupLayoutEntries (line 726) | pub struct LightingBindGroupLayoutEntries {
    method new (line 733) | pub fn new(starting_binding: u32) -> Self {

FILE: crates/renderling/src/light/cpu/test.rs
  function spot_one_calc (line 26) | fn spot_one_calc() {
  function spot_one_frame (line 86) | fn spot_one_frame() {
  function spot_lights (line 116) | fn spot_lights() {
  function light_tiling_light_bounds (line 149) | fn light_tiling_light_bounds() {
  function gen_vec3 (line 223) | fn gen_vec3(prng: &mut GpuRng) -> Vec3 {
  type GeneratedLight (line 230) | struct GeneratedLight {
  function gen_light (line 236) | fn gen_light(stage: &Stage, prng: &mut GpuRng, bounding_boxes: &[Boundin...
  function size (line 292) | fn size() -> UVec2 {
  function make_camera (line 299) | fn make_camera(stage: &Stage) -> Camera {
  function clear_tiles_sanity (line 317) | fn clear_tiles_sanity() {
  function min_max_depth_sanity (line 390) | fn min_max_depth_sanity() {
  function light_bins_sanity (line 443) | fn light_bins_sanity() {
  function light_bins_point (line 516) | fn light_bins_point() {
  function tiling_e2e_sanity_with (line 572) | fn tiling_e2e_sanity_with(
  function tiling_e2e_sanity (line 728) | fn tiling_e2e_sanity() {
  function snapshot (line 760) | fn snapshot(ctx: &crate::context::Context, stage: &Stage, path: &str, sa...
  function ambient_light (line 778) | fn ambient_light() {
  constant MAX_LIGHTS (line 834) | const MAX_LIGHTS: usize = 2usize.pow(10);
  type LightTilingStatsRun (line 848) | pub struct LightTilingStatsRun {
    method avg_frame_time (line 854) | fn avg_frame_time(&self, with_tiling: bool) -> f32 {
  type LightTilingStats (line 871) | pub struct LightTilingStats {
  function plot (line 875) | pub fn plot(stats: LightTilingStats, filename: &str) {
  function pedestal (line 986) | fn pedestal() {

FILE: crates/renderling/src/light/shader.rs
  type LightingDescriptor (line 40) | pub struct LightingDescriptor {
  type ShadowMapDescriptor (line 66) | pub struct ShadowMapDescriptor {
  method default (line 84) | fn default() -> Self {
  type ShadowMappingVertexInfo (line 99) | pub struct ShadowMappingVertexInfo {
  function shadow_mapping_vertex (line 125) | pub fn shadow_mapping_vertex(
  function shadow_mapping_fragment (line 176) | pub fn shadow_mapping_fragment(clip_pos: Vec4, frag_color: &mut Vec4) {
  type SpotLightCalculation (line 186) | pub struct SpotLightCalculation {
    method new (line 232) | pub fn new(
  type SpotLightDescriptor (line 286) | pub struct SpotLightDescriptor {
    method shadow_mapping_projection_and_view (line 318) | pub fn shadow_mapping_projection_and_view(
  method default (line 298) | fn default() -> Self {
  type Candela (line 347) | pub struct Candela(pub f32);
    method fmt (line 350) | fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
    constant COMMON_WAX_CANDLE (line 361) | pub const COMMON_WAX_CANDLE: Self = Candela(1.0);
  type Lux (line 374) | pub struct Lux(pub f32);
    method fmt (line 377) | fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
    constant OUTDOOR_TWILIGHT (line 384) | pub const OUTDOOR_TWILIGHT: Self = Lux(1.0);
    constant OUTDOOR_STREET_LIGHT_MIN (line 385) | pub const OUTDOOR_STREET_LIGHT_MIN: Self = Lux(5.0);
    constant OUTDOOR_SUNSET (line 386) | pub const OUTDOOR_SUNSET: Self = Lux(10.0);
    constant INDOOR_LOUNGE (line 387) | pub const INDOOR_LOUNGE: Self = Lux(50.0);
    constant INDOOR_HALLWAY (line 388) | pub const INDOOR_HALLWAY: Self = Lux(80.0);
    constant OUTDOOR_OVERCAST_LOW (line 389) | pub const OUTDOOR_OVERCAST_LOW: Self = Lux(100.0);
    constant INDOOR_OFFICE_LOW (line 390) | pub const INDOOR_OFFICE_LOW: Self = Lux(320.0);
    constant OUTDOOR_SUNRISE_OR_SUNSET (line 391) | pub const OUTDOOR_SUNRISE_OR_SUNSET: Self = Lux(400.0);
    constant INDOOR_OFFICE_HIGH (line 392) | pub const INDOOR_OFFICE_HIGH: Self = Lux(500.0);
    constant OUTDOOR_OVERCAST_HIGH (line 393) | pub const OUTDOOR_OVERCAST_HIGH: Self = Lux(1000.0);
    constant OUTDOOR_FOXS_WEDDING (line 394) | pub const OUTDOOR_FOXS_WEDDING: Self = Lux(3000.0);
    constant OUTDOOR_FULL_DAYLIGHT_LOW (line 395) | pub const OUTDOOR_FULL_DAYLIGHT_LOW: Self = Lux(10_000.0);
    constant OUTDOOR_FULL_DAYLIGHT_HIGH (line 396) | pub const OUTDOOR_FULL_DAYLIGHT_HIGH: Self = Lux(25_000.0);
    constant OUTDOOR_DIRECT_SUNLIGHT_LOW (line 397) | pub const OUTDOOR_DIRECT_SUNLIGHT_LOW: Self = Lux(32_000.0);
    constant OUTDOOR_DIRECT_SUNLIGHT_HIGH (line 398) | pub const OUTDOOR_DIRECT_SUNLIGHT_HIGH: Self = Lux(130_000.0);
  type DirectionalLightDescriptor (line 403) | pub struct DirectionalLightDescriptor {
    method shadow_mapping_projection_and_view (line 425) | pub fn shadow_mapping_projection_and_view(
  method default (line 411) | fn default() -> Self {
  type PointLightDescriptor (line 456) | pub struct PointLightDescriptor {
    method shadow_mapping_view_matrix (line 479) | pub fn shadow_mapping_view_matrix(
    method shadow_mapping_projection_matrix (line 490) | pub fn shadow_mapping_projection_matrix(z_near: f32, z_far: f32) -> Ma...
    method shadow_mapping_projection_and_view_matrices (line 494) | pub fn shadow_mapping_projection_and_view_matrices(
  method default (line 466) | fn default() -> Self {
  function radius_of_illumination (line 522) | pub fn radius_of_illumination(intensity_candelas: f32, minimum_illuminan...
  type LightStyle (line 528) | pub enum LightStyle {
    method fmt (line 535) | fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
  constant SLAB_SIZE (line 545) | const SLAB_SIZE: usize = { 1 };
  method read_slab (line 547) | fn read_slab(index: usize, slab: &[u32]) -> Self {
  method write_slab (line 557) | fn write_slab(&self, index: usize, slab: &mut [u32]) -> usize {
  type LightDescriptor (line 567) | pub struct LightDescriptor {
    method from (line 597) | fn from(id: Id<DirectionalLightDescriptor>) -> Self {
    method from (line 608) | fn from(id: Id<SpotLightDescriptor>) -> Self {
    method from (line 619) | fn from(id: Id<PointLightDescriptor>) -> Self {
    method into_directional_id (line 630) | pub fn into_directional_id(self) -> Id<DirectionalLightDescriptor> {
    method into_spot_id (line 634) | pub fn into_spot_id(self) -> Id<SpotLightDescriptor> {
    method into_point_id (line 638) | pub fn into_point_id(self) -> Id<PointLightDescriptor> {
  method default (line 586) | fn default() -> Self {
  type ShadowCalculation (line 646) | pub struct ShadowCalculation {
    method new (line 660) | pub fn new(
    method get_atlas_texture_at (line 689) | fn get_atlas_texture_at(&self, light_slab: &[u32], index: usize) -> At...
    method get_frag_pos_in_light_space (line 695) | fn get_frag_pos_in_light_space(&self, light_slab: &[u32], index: usize...
    method run_directional_or_spot (line 705) | pub fn run_directional_or_spot<T, S>(
    constant POINT_SAMPLE_OFFSET_DIRECTIONS (line 782) | pub const POINT_SAMPLE_OFFSET_DIRECTIONS: [Vec3; 21] = [
    method run_point (line 809) | pub fn run_point<T, S>(
  function light_tiling_depth_pre_pass (line 877) | pub fn light_tiling_depth_pre_pass(
  type DepthImage2d (line 903) | pub type DepthImage2d = Image!(2D, type=f32, sampled, depth);
  type DepthImage2dMultisampled (line 905) | pub type DepthImage2dMultisampled = Image!(2D, type=f32, sampled, depth,...
  type LightTile (line 910) | pub struct LightTile {
    method fmt (line 924) | fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
  type LightTilingDescriptor (line 937) | pub struct LightTilingDescriptor {
    method tile_grid_size (line 963) | pub fn tile_grid_size(&self) -> UVec2 {
    method tile_coord_for_fragment (line 968) | pub fn tile_coord_for_fragment(&self, frag_coord: Vec2) -> UVec2 {
    method tile_index_for_fragment (line 973) | pub fn tile_index_for_fragment(&self, frag_coord: Vec2) -> usize {
  method default (line 951) | fn default() -> Self {
  function quantize_depth_f32_to_u32 (line 981) | pub fn quantize_depth_f32_to_u32(depth: f32) -> u32 {
  function dequantize_depth_u32_to_f32 (line 986) | pub fn dequantize_depth_u32_to_f32(depth: u32) -> f32 {
  type NextLightIndex (line 992) | pub(crate) struct NextLightIndex {
    method new (line 1014) | pub fn new(
    method next_index (line 1027) | pub fn next_index(&self) -> usize {
  type Item (line 1000) | type Item = Id<Id<LightDescriptor>>;
  method next (line 1002) | fn next(&mut self) -> Option<Self::Item> {
  type LightTilingInvocation (line 1037) | struct LightTilingInvocation {
    method new (line 1043) | fn new(global_id: UVec3, descriptor: LightTilingDescriptor) -> Self {
    method frag_pos (line 1053) | fn frag_pos(&self) -> UVec2 {
    method tile_grid_size (line 1058) | fn tile_grid_size(&self) -> UVec2 {
    method tile_coord (line 1065) | fn tile_coord(&self) -> UVec2 {
    method tile_index (line 1070) | fn tile_index(&self) -> usize {
    method tile_ndc_midpoint (line 1077) | fn tile_ndc_midpoint(&self) -> Vec2 {
    method compute_min_and_max_depth (line 1087) | fn compute_min_and_max_depth(
    method should_invoke (line 1126) | fn should_invoke(&self) -> bool {
    method clear_tile (line 1135) | fn clear_tile(&self, lighting_slab: &mut [u32]) {
    method compute_light_lists (line 1156) | fn compute_light_lists(&self, geometry_slab: &[u32], lighting_slab: &m...
  function light_tiling_clear_tiles (line 1272) | pub fn light_tiling_clear_tiles(
  function light_tiling_compute_tile_min_and_max_depth (line 1287) | pub fn light_tiling_compute_tile_min_and_max_depth(
  function light_tiling_compute_tile_min_and_max_depth_multisampled (line 1303) | pub fn light_tiling_compute_tile_min_and_max_depth_multisampled(
  function light_tiling_bin_lights (line 1316) | pub fn light_tiling_bin_lights(

FILE: crates/renderling/src/light/shadow_map.rs
  type ShadowMap (line 35) | pub struct ShadowMap {
    constant LABEL (line 57) | const LABEL: Option<&str> = Some("shadow-map");
    method create_update_bindgroup_layout (line 59) | pub fn create_update_bindgroup_layout(device: &wgpu::Device) -> wgpu::...
    method create_update_pipeline (line 87) | pub fn create_update_pipeline(
    method create_update_bindgroup (line 131) | fn create_update_bindgroup(
    method descriptor_id (line 158) | pub fn descriptor_id(&self) -> Id<ShadowMapDescriptor> {
    method descriptor_lock (line 165) | pub fn descriptor_lock(&self) -> HybridWriteGuard<'_, ShadowMapDescrip...
    method new (line 171) | pub fn new<T>(
    method update (line 268) | pub fn update<'a>(
  function shadow_mapping_just_cuboid (line 401) | fn shadow_mapping_just_cuboid() {
  function shadow_mapping_just_cuboid_red_and_blue (line 456) | fn shadow_mapping_just_cuboid_red_and_blue() {
  function shadow_mapping_sanity (line 509) | fn shadow_mapping_sanity() {
  function shadow_mapping_spot_lights (line 597) | fn shadow_mapping_spot_lights() {
  function shadow_mapping_point_lights (line 663) | fn shadow_mapping_point_lights() {

FILE: crates/renderling/src/light/tiling.rs
  type LightTiling (line 43) | pub struct LightTiling<Ct: IsContainer = GpuArrayContainer> {
    method new (line 512) | pub fn new(
  constant LABEL (line 62) | const LABEL: Option<&'static str> = Some("light-tiling");
  function create_bindgroup_layout (line 65) | fn create_bindgroup_layout(device: &wgpu::Device, multisampled: bool) ->...
  function create_clear_tiles_pipeline (line 106) | fn create_clear_tiles_pipeline(
  function create_compute_min_max_depth_pipeline (line 123) | fn create_compute_min_max_depth_pipeline(
  function create_compute_bins_pipeline (line 140) | fn create_compute_bins_pipeline(
  function create_layouts (line 158) | fn create_layouts(
  function prepare (line 171) | pub(crate) fn prepare(&self, lighting: &Lighting, depth_texture_size: UV...
  function clear_tiles (line 180) | pub(crate) fn clear_tiles(
  constant WORKGROUP_SIZE (line 202) | const WORKGROUP_SIZE: UVec3 = UVec3::new(16, 16, 1);
  function compute_min_max_depth (line 204) | pub(crate) fn compute_min_max_depth(
  function compute_bins (line 223) | pub(crate) fn compute_bins(
  function get_bindgroup (line 244) | pub fn get_bindgroup(
  function set_minimum_illuminance (line 288) | pub fn set_minimum_illuminance(&self, minimum_illuminance_lux: f32) {
  function run (line 295) | pub fn run(&self, stage: &Stage) {
  function tiles (line 323) | pub fn tiles(&self) -> &Ct::Container<LightTile> {
  function read_tiles (line 329) | pub(crate) async fn read_tiles(&self, lighting: &Lighting) -> Vec<LightT...
  function read_tile (line 339) | pub(crate) fn read_tile(&self, lighting: &Lighting, tile_coord: UVec2) -...
  function read_images (line 349) | pub(crate) async fn read_images(
  type LightTilingConfig (line 404) | pub struct LightTilingConfig {
  method default (line 432) | fn default() -> Self {
  function new_hybrid (line 443) | pub(crate) fn new_hybrid(

FILE: crates/renderling/src/linkage.rs
  type ShaderLinkage (line 60) | pub struct ShaderLinkage {
  function validate_shaders (line 73) | fn validate_shaders() {

FILE: crates/renderling/src/linkage/atlas_blit_fragment.rs
  constant ENTRY_POINT (line 6) | pub const ENTRY_POINT: &str = "atlas::shader::atlas_blit_fragment";
  function descriptor (line 7) | pub fn descriptor() -> wgpu::ShaderModuleDescriptor<'static> {
  function linkage (line 10) | pub fn linkage(device: &wgpu::Device) -> super::ShaderLinkage {
  constant ENTRY_POINT (line 20) | pub const ENTRY_POINT: &str = "atlasshaderatlas_blit_fragment";
  function descriptor (line 21) | pub fn descriptor() -> wgpu::ShaderModuleDescriptor<'static> {
  function linkage (line 24) | pub fn linkage(device: &wgpu::Device) -> super::ShaderLinkage {
  function linkage (line 32) | pub fn linkage(device: &wgpu::Device) -> ShaderLinkage {

FILE: crates/renderling/src/linkage/atlas_blit_vertex.rs
  constant ENTRY_POINT (line 6) | pub const ENTRY_POINT: &str = "atlas::shader::atlas_blit_vertex";
  function descriptor (line 7) | pub fn descriptor() -> wgpu::ShaderModuleDescriptor<'static> {
  function linkage (line 10) | pub fn linkage(device: &wgpu::Device) -> super::ShaderLinkage {
  constant ENTRY_POINT (line 20) | pub const ENTRY_POINT: &str = "atlasshaderatlas_blit_vertex";
  function descriptor (line 21) | pub fn descriptor() -> wgpu::ShaderModuleDescriptor<'static> {
  function linkage (line 24) | pub fn linkage(device: &wgpu::Device) -> super::ShaderLinkage {
  function linkage (line 32) | pub fn linkage(device: &wgpu::Device) -> ShaderLinkage {

FILE: crates/renderling/src/linkage/bloom_downsample_fragment.rs
  constant ENTRY_POINT (line 6) | pub const ENTRY_POINT: &str = "bloom::shader::bloom_downsample_fragment";
  function descriptor (line 7) | pub fn descriptor() -> wgpu::ShaderModuleDescriptor<'static> {
  function linkage (line 10) | pub fn linkage(device: &wgpu::Device) -> super::ShaderLinkage {
  constant ENTRY_POINT (line 23) | pub const ENTRY_POINT: &str = "bloomshaderbloom_downsample_fragment";
  function descriptor (line 24) | pub fn descriptor() -> wgpu::ShaderModuleDescriptor<'static> {
  function linkage (line 27) | pub fn linkage(device: &wgpu::Device) -> super::ShaderLinkage {
  function linkage (line 35) | pub fn linkage(device: &wgpu::Device) -> ShaderLinkage {

FILE: crates/renderling/src/linkage/bloom_mix_fragment.rs
  constant ENTRY_POINT (line 6) | pub const ENTRY_POINT: &str = "bloom::shader::bloom_mix_fragment";
  function descriptor (line 7) | pub fn descriptor() -> wgpu::ShaderModuleDescriptor<'static> {
  function linkage (line 10) | pub fn linkage(device: &wgpu::Device) -> super::ShaderLinkage {
  constant ENTRY_POINT (line 20) | pub const ENTRY_POINT: &str = "bloomshaderbloom_mix_fragment";
  function descriptor (line 21) | pub fn descriptor() -> wgpu::ShaderModuleDescriptor<'static> {
  function linkage (line 24) | pub fn linkage(device: &wgpu::Device) -> super::ShaderLinkage {
  function linkage (line 32) | pub fn linkage(device: &wgpu::Device) -> ShaderLinkage {

FILE: crates/renderling/src/linkage/bloom_upsample_fragment.rs
  constant ENTRY_POINT (line 6) | pub const ENTRY_POINT: &str = "bloom::shader::bloom_upsample_fragment";
  function descriptor (line 7) | pub fn descriptor() -> wgpu::ShaderModuleDescriptor<'static> {
  function linkage (line 10) | pub fn linkage(device: &wgpu::Device) -> super::ShaderLinkage {
  constant ENTRY_POINT (line 20) | pub const ENTRY_POINT: &str = "bloomshaderbloom_upsample_fragment";
  function descriptor (line 21) | pub fn descriptor() -> wgpu::ShaderModuleDescriptor<'static> {
  function linkage (line 24) | pub fn linkage(device: &wgpu::Device) -> super::ShaderLinkage {
  function linkage (line 32) | pub fn linkage(device: &wgpu::Device) -> ShaderLinkage {

FILE: crates/renderling/src/linkage/bloom_vertex.rs
  constant ENTRY_POINT (line 6) | pub const ENTRY_POINT: &str = "bloom::shader::bloom_vertex";
  function descriptor (line 7) | pub fn descriptor() -> wgpu::ShaderModuleDescriptor<'static> {
  function linkage (line 10) | pub fn linkage(device: &wgpu::Device) -> super::ShaderLinkage {
  constant ENTRY_POINT (line 20) | pub const ENTRY_POINT: &str = "bloomshaderbloom_vertex";
  function descriptor (line 21) | pub fn descriptor() -> wgpu::ShaderModuleDescriptor<'static> {
  function linkage (line 24) | pub fn linkage(device: &wgpu::Device) -> super::ShaderLinkage {
  function linkage (line 32) | pub fn linkage(device: &wgpu::Device) -> ShaderLinkage {

FILE: crates/renderling/src/linkage/brdf_lut_convolution_fragment.rs
  constant ENTRY_POINT (line 6) | pub const ENTRY_POINT: &str = "convolution::shader::brdf_lut_convolution...
  function descriptor (line 7) | pub fn descriptor() -> wgpu::ShaderModuleDescriptor<'static> {
  function linkage (line 10) | pub fn linkage(device: &wgpu::Device) -> super::ShaderLinkage {
  constant ENTRY_POINT (line 23) | pub const ENTRY_POINT: &str = "convolutionshaderbrdf_lut_convolution_fra...
  function descriptor (line 24) | pub fn descriptor() -> wgpu::ShaderModuleDescriptor<'static> {
  function linkage (line 27) | pub fn linkage(device: &wgpu::Device) -> super::ShaderLinkage {
  function linkage (line 38) | pub fn linkage(device: &wgpu::Device) -> ShaderLinkage {

FILE: crates/renderling/src/linkage/brdf_lut_convolution_vertex.rs
  constant ENTRY_POINT (line 6) | pub const ENTRY_POINT: &str = "convolution::shader::brdf_lut_convolution...
  function descriptor (line 7) | pub fn descriptor() -> wgpu::ShaderModuleDescriptor<'static> {
  function linkage (line 10) | pub fn linkage(device: &wgpu::Device) -> super::ShaderLinkage {
  constant ENTRY_POINT (line 23) | pub const ENTRY_POINT: &str = "convolutionshaderbrdf_lut_convolution_ver...
  function descriptor (line 24) | pub fn descriptor() -> wgpu::ShaderModuleDescriptor<'static> {
  function linkage (line 27) | pub fn linkage(device: &wgpu::Device) -> super::ShaderLinkage {
  function linkage (line 35) | pub fn linkage(device: &wgpu::Device) -> ShaderLinkage {

FILE: crates/renderling/src/linkage/compositor_fragment.rs
  constant ENTRY_POINT (line 6) | pub const ENTRY_POINT: &str = "compositor::compositor_fragment";
  function descriptor (line 7) | pub fn descriptor() -> wgpu::ShaderModuleDescriptor<'static> {
  function linkage (line 10) | pub fn linkage(device: &wgpu::Device) -> super::ShaderLinkage {
  constant ENTRY_POINT (line 20) | pub const ENTRY_POINT: &str = "compositorcompositor_fragment";
  function descriptor (line 21) | pub fn descriptor() -> wgpu::ShaderModuleDescriptor<'static> {
  function linkage (line 24) | pub fn linkage(device: &wgpu::Device) -> super::ShaderLinkage {
  function linkage (line 32) | pub fn linkage(device: &wgpu::Device) -> ShaderLinkage {

FILE: crates/renderling/src/linkage/compositor_vertex.rs
  constant ENTRY_POINT (line 6) | pub const ENTRY_POINT: &str = "compositor::compositor_vertex";
  function descriptor (line 7) | pub fn descriptor() -> wgpu::ShaderModuleDescriptor<'static> {
  function linkage (line 10) | pub fn linkage(device: &wgpu::Device) -> super::ShaderLinkage {
  constant ENTRY_POINT (line 20) | pub const ENTRY_POINT: &str = "compositorcompositor_vertex";
  function descriptor (line 21) | pub fn descriptor() -> wgpu::ShaderModuleDescriptor<'static> {
  function linkage (line 24) | pub fn linkage(device: &wgpu::Device) -> super::ShaderLinkage {
  function linkage (line 32) | pub fn linkage(device: &wgpu::Device) -> ShaderLinkage {

FILE: crates/renderling/src/linkage/compute_copy_depth_to_pyramid.rs
  constant ENTRY_POINT (line 6) | pub const ENTRY_POINT: &str = "cull::shader::compute_copy_depth_to_pyram...
  function descriptor (line 7) | pub fn descriptor() -> wgpu::ShaderModuleDescriptor<'static> {
  function linkage (line 10) | pub fn linkage(device: &wgpu::Device) -> super::ShaderLinkage {
  constant ENTRY_POINT (line 23) | pub const ENTRY_POINT: &str = "cullshadercompute_copy_depth_to_pyramid";
  function descriptor (line 24) | pub fn descriptor() -> wgpu::ShaderModuleDescriptor<'static> {
  function linkage (line 27) | pub fn linkage(device: &wgpu::Device) -> super::ShaderLinkage {
  function linkage (line 38) | pub fn linkage(device: &wgpu::Device) -> ShaderLinkage {

FILE: crates/renderling/src/linkage/compute_copy_depth_to_pyramid_multisampled.rs
  constant ENTRY_POINT (line 6) | pub const ENTRY_POINT: &str = "cull::shader::compute_copy_depth_to_pyram...
  function descriptor (line 7) | pub fn descriptor() -> wgpu::ShaderModuleDescriptor<'static> {
  function linkage (line 12) | pub fn linkage(device: &wgpu::Device) -> super::ShaderLinkage {
  constant ENTRY_POINT (line 25) | pub const ENTRY_POINT: &str = "cullshadercompute_copy_depth_to_pyramid_m...
  function descriptor (line 26) | pub fn descriptor() -> wgpu::ShaderModuleDescriptor<'static> {
  function linkage (line 31) | pub fn linkage(device: &wgpu::Device) -> super::ShaderLinkage {
  function linkage (line 42) | pub fn linkage(device: &wgpu::Device) -> ShaderLinkage {

FILE: crates/renderling/src/linkage/compute_culling.rs
  constant ENTRY_POINT (line 6) | pub const ENTRY_POINT: &str = "cull::shader::compute_culling";
  function descriptor (line 7) | pub fn descriptor() -> wgpu::ShaderModuleDescriptor<'static> {
  function linkage (line 10) | pub fn linkage(device: &wgpu::Device) -> super::ShaderLinkage {
  constant ENTRY_POINT (line 20) | pub const ENTRY_POINT: &str = "cullshadercompute_culling";
  function descriptor (line 21) | pub fn descriptor() -> wgpu::ShaderModuleDescriptor<'static> {
  function linkage (line 24) | pub fn linkage(device: &wgpu::Device) -> super::ShaderLinkage {
  function linkage (line 32) | pub fn linkage(device: &wgpu::Device) -> ShaderLinkage {

FILE: crates/renderling/src/linkage/compute_downsample_depth_pyramid.rs
  constant ENTRY_POINT (line 6) | pub const ENTRY_POINT: &str = "cull::shader::compute_downsample_depth_py...
  function descriptor (line 7) | pub fn descriptor() -> wgpu::ShaderModuleDescriptor<'static> {
  function linkage (line 10) | pub fn linkage(device: &wgpu::Device) -> super::ShaderLinkage {
  constant ENTRY_POINT (line 23) | pub const ENTRY_POINT: &str = "cullshadercompute_downsample_depth_pyramid";
  function descriptor (line 24) | pub fn descriptor() -> wgpu::ShaderModuleDescriptor<'static> {
  function linkage (line 27) | pub fn linkage(device: &wgpu::Device) -> super::ShaderLinkage {
  function linkage (line 38) | pub fn linkage(device: &wgpu::Device) -> ShaderLinkage {

FILE: crates/renderling/src/linkage/cubemap_sampling_test_fragment.rs
  constant ENTRY_POINT (line 6) | pub const ENTRY_POINT: &str = "cubemap::shader::cubemap_sampling_test_fr...
  function descriptor (line 7) | pub fn descriptor() -> wgpu::ShaderModuleDescriptor<'static> {
  function linkage (line 10) | pub fn linkage(device: &wgpu::Device) -> super::ShaderLinkage {
  constant ENTRY_POINT (line 23) | pub const ENTRY_POINT: &str = "cubemapshadercubemap_sampling_test_fragme...
  function descriptor (line 24) | pub fn descriptor() -> wgpu::ShaderModuleDescriptor<'static> {
  function linkage (line 27) | pub fn linkage(device: &wgpu::Device) -> super::ShaderLinkage {
  function linkage (line 38) | pub fn linkage(device: &wgpu::Device) -> ShaderLinkage {

FILE: crates/renderling/src/linkage/cubemap_sampling_test_vertex.rs
  constant ENTRY_POINT (line 6) | pub const ENTRY_POINT: &str = "cubemap::shader::cubemap_sampling_test_ve...
  function descriptor (line 7) | pub fn descriptor() -> wgpu::ShaderModuleDescriptor<'static> {
  function linkage (line 10) | pub fn linkage(device: &wgpu::Device) -> super::ShaderLinkage {
  constant ENTRY_POINT (line 23) | pub const ENTRY_POINT: &str = "cubemapshadercubemap_sampling_test_vertex";
  function descriptor (line 24) | pub fn descriptor() -> wgpu::ShaderModuleDescriptor<'static> {
  function linkage (line 27) | pub fn linkage(device: &wgpu::Device) -> super::ShaderLinkage {
  function linkage (line 38) | pub fn linkage(device: &wgpu::Device) -> ShaderLinkage {

FILE: crates/renderling/src/linkage/debug_overlay_fragment.rs
  constant ENTRY_POINT (line 6) | pub const ENTRY_POINT: &str = "debug::shader::debug_overlay_fragment";
  function descriptor (line 7) | pub fn descriptor() -> wgpu::ShaderModuleDescriptor<'static> {
  function linkage (line 10) | pub fn linkage(device: &wgpu::Device) -> super::ShaderLinkage {
  constant ENTRY_POINT (line 20) | pub const ENTRY_POINT: &str = "debugshaderdebug_overlay_fragment";
  function descriptor (line 21) | pub fn descriptor() -> wgpu::ShaderModuleDescriptor<'static> {
  function linkage (line 24) | pub fn linkage(device: &wgpu::Device) -> super::ShaderLinkage {
  function linkage (line 32) | pub fn linkage(device: &wgpu::Device) -> ShaderLinkage {

FILE: crates/renderling/src/linkage/debug_overlay_vertex.rs
  constant ENTRY_POINT (line 6) | pub const ENTRY_POINT: &str = "debug::shader::debug_overlay_vertex";
  function descriptor (line 7) | pub fn descriptor() -> wgpu::ShaderModuleDescriptor<'static> {
  function linkage (line 10) | pub fn linkage(device: &wgpu::Device) -> super::ShaderLinkage {
  constant ENTRY_POINT (line 20) | pub const ENTRY_POINT: &str = "debugshaderdebug_overlay_vertex";
  function descriptor (line 21) | pub fn descriptor() -> wgpu::ShaderModuleDescriptor<'static> {
  function linkage (line 24) | pub fn linkage(device: &wgpu::Device) -> super::ShaderLinkage {
  function linkage (line 32) | pub fn linkage(device: &wgpu::Device) -> ShaderLinkage {

FILE: crates/renderling/src/linkage/di_convolution_fragment.rs
  constant ENTRY_POINT (line 6) | pub const ENTRY_POINT: &str = "pbr::ibl::shader::di_convolution_fragment";
  function descriptor (line 7) | pub fn descriptor() -> wgpu::ShaderModuleDescriptor<'static> {
  function linkage (line 10) | pub fn linkage(device: &wgpu::Device) -> super::ShaderLinkage {
  constant ENTRY_POINT (line 20) | pub const ENTRY_POINT: &str = "pbriblshaderdi_convolution_fragment";
  function descriptor (line 21) | pub fn descriptor() -> wgpu::ShaderModuleDescriptor<'static> {
  function linkage (line 24) | pub fn linkage(device: &wgpu::Device) -> super::ShaderLinkage {
  function linkage (line 32) | pub fn linkage(device: &wgpu::Device) -> ShaderLinkage {

FILE: crates/renderling/src/linkage/generate_mipmap_fragment.rs
  constant ENTRY_POINT (line 6) | pub const ENTRY_POINT: &str = "convolution::shader::generate_mipmap_frag...
  function descriptor (line 7) | pub fn descriptor() -> wgpu::ShaderModuleDescriptor<'static> {
  function linkage (line 10) | pub fn linkage(device: &wgpu::Device) -> super::ShaderLinkage {
  constant ENTRY_POINT (line 20) | pub const ENTRY_POINT: &str = "convolutionshadergenerate_mipmap_fragment";
  function descriptor (line 21) | pub fn descriptor() -> wgpu::ShaderModuleDescriptor<'static> {
  function linkage (line 24) | pub fn linkage(device: &wgpu::Device) -> super::ShaderLinkage {
  function linkage (line 32) | pub fn linkage(device: &wgpu::Device) -> ShaderLinkage {

FILE: crates/renderling/src/linkage/generate_mipmap_vertex.rs
  constant ENTRY_POINT (line 6) | pub const ENTRY_POINT: &str = "convolution::shader::generate_mipmap_vert...
  function descriptor (line 7) | pub fn descriptor() -> wgpu::ShaderModuleDescriptor<'static> {
  function linkage (line 10) | pub fn linkage(device: &wgpu::Device) -> super::ShaderLinkage {
  constant ENTRY_POINT (line 20) | pub const ENTRY_POINT: &str = "convolutionshadergenerate_mipmap_vertex";
  function descriptor (line 21) | pub fn descriptor() -> wgpu::ShaderModuleDescriptor<'static> {
  function linkage (line 24) | pub fn linkage(device: &wgpu::Device) -> super::ShaderLinkage {
  function linkage (line 32) | pub fn linkage(device: &wgpu::Device) -> ShaderLinkage {

FILE: crates/renderling/src/linkage/implicit_isosceles_vertex.rs
  constant ENTRY_POINT (line 6) | pub const ENTRY_POINT: &str = "tutorial::implicit_isosceles_vertex";
  function descriptor (line 7) | pub fn descriptor() -> wgpu::ShaderModuleDescriptor<'static> {
  function linkage (line 10) | pub fn linkage(device: &wgpu::Device) -> super::ShaderLinkage {
  constant ENTRY_POINT (line 23) | pub const ENTRY_POINT: &str = "tutorialimplicit_isosceles_vertex";
  function descriptor (line 24) | pub fn descriptor() -> wgpu::ShaderModuleDescriptor<'static> {
  function linkage (line 27) | pub fn linkage(device: &wgpu::Device) -> super::ShaderLinkage {
  function linkage (line 35) | pub fn linkage(device: &wgpu::Device) -> ShaderLinkage {

FILE: crates/renderling/src/linkage/light_tiling_bin_lights.rs
  constant ENTRY_POINT (line 6) | pub const ENTRY_POINT: &str = "light::shader::light_tiling_bin_lights";
  function descriptor (line 7) | pub fn descriptor() -> wgpu::ShaderModuleDescriptor<'static> {
  function linkage (line 10) | pub fn linkage(device: &wgpu::Device) -> super::ShaderLinkage {
  constant ENTRY_POINT (line 20) | pub const ENTRY_POINT: &str = "lightshaderlight_tiling_bin_lights";
  function descriptor (line 21) | pub fn descriptor() -> wgpu::ShaderModuleDescriptor<'static> {
  function linkage (line 24) | pub fn linkage(device: &wgpu::Device) -> super::ShaderLinkage {
  function linkage (line 32) | pub fn linkage(device: &wgpu::Device) -> ShaderLinkage {

FILE: crates/renderling/src/linkage/light_tiling_clear_tiles.rs
  constant ENTRY_POINT (line 6) | pub const ENTRY_POINT: &str = "light::shader::light_tiling_clear_tiles";
  function descriptor (line 7) | pub fn descriptor() -> wgpu::ShaderModuleDescriptor<'static> {
  function linkage (line 10) | pub fn linkage(device: &wgpu::Device) -> super::ShaderLinkage {
  constant ENTRY_POINT (line 20) | pub const ENTRY_POINT: &str = "lightshaderlight_tiling_clear_tiles";
  function descriptor (line 21) | pub fn descriptor() -> wgpu::ShaderModuleDescriptor<'static> {
  function linkage (line 24) | pub fn linkage(device: &wgpu::Device) -> super::ShaderLinkage {
  function linkage (line 32) | pub fn linkage(device: &wgpu::Device) -> ShaderLinkage {

FILE: crates/renderling/src/linkage/light_tiling_compute_tile_min_and_max_depth.rs
  constant ENTRY_POINT (line 6) | pub const ENTRY_POINT: &str = "light::shader::light_tiling_compute_tile_...
  function descriptor (line 7) | pub fn descriptor() -> wgpu::ShaderModuleDescriptor<'static> {
  function linkage (line 12) | pub fn linkage(device: &wgpu::Device) -> super::ShaderLinkage {
  constant ENTRY_POINT (line 25) | pub const ENTRY_POINT: &str = "lightshaderlight_tiling_compute_tile_min_...
  function descriptor (line 26) | pub fn descriptor() -> wgpu::ShaderModuleDescriptor<'static> {
  function linkage (line 31) | pub fn linkage(device: &wgpu::Device) -> super::ShaderLinkage {
  function linkage (line 42) | pub fn linkage(device: &wgpu::Device) -> ShaderLinkage {

FILE: crates/renderling/src/linkage/light_tiling_compute_tile_min_and_max_depth_multisampled.rs
  constant ENTRY_POINT (line 6) | pub const ENTRY_POINT: &str =
  function descriptor (line 8) | pub fn descriptor() -> wgpu::ShaderModuleDescriptor<'static> {
  function linkage (line 14) | pub fn linkage(device: &wgpu::Device) -> super::ShaderLinkage {
  constant ENTRY_POINT (line 27) | pub const ENTRY_POINT: &str =
  function descriptor (line 29) | pub fn descriptor() -> wgpu::ShaderModuleDescriptor<'static> {
  function linkage (line 35) | pub fn linkage(device: &wgpu::Device) -> super::ShaderLinkage {
  function linkage (line 46) | pub fn linkage(device: &wgpu::Device) -> ShaderLinkage {

FILE: crates/renderling/src/linkage/light_tiling_depth_pre_pass.rs
  constant ENTRY_POINT (line 6) | pub const ENTRY_POINT: &str = "light::shader::light_tiling_depth_pre_pass";
  function descriptor (line 7) | pub fn descriptor() -> wgpu::ShaderModuleDescriptor<'static> {
  function linkage (line 10) | pub fn linkage(device: &wgpu::Device) -> super::ShaderLinkage {
  constant ENTRY_POINT (line 23) | pub const ENTRY_POINT: &str = "lightshaderlight_tiling_depth_pre_pass";
  function descriptor (line 24) | pub fn descriptor() -> wgpu::ShaderModuleDescriptor<'static> {
  function linkage (line 27) | pub fn linkage(device: &wgpu::Device) -> super::ShaderLinkage {
  function linkage (line 35) | pub fn linkage(device: &wgpu::Device) -> ShaderLinkage {

FILE: crates/renderling/src/linkage/passthru_fragment.rs
  constant ENTRY_POINT (line 6) | pub const ENTRY_POINT: &str = "tutorial::passthru_fragment";
  function descriptor (line 7) | pub fn descriptor() -> wgpu::ShaderModuleDescriptor<'static> {
  function linkage (line 10) | pub fn linkage(device: &wgpu::Device) -> super::ShaderLinkage {
  constant ENTRY_POINT (line 20) | pub const ENTRY_POINT: &str = "tutorialpassthru_fragment";
  function descriptor (line 21) | pub fn descriptor() -> wgpu::ShaderModuleDescriptor<'static> {
  function linkage (line 24) | pub fn linkage(device: &wgpu::Device) -> super::ShaderLinkage {
  function linkage (line 32) | pub fn linkage(device: &wgpu::Device) -> ShaderLinkage {

FILE: crates/renderling/src/linkage/prefilter_environment_cubemap_fragment.rs
  constant ENTRY_POINT (line 6) | pub const ENTRY_POINT: &str = "convolution::shader::prefilter_environmen...
  function descriptor (line 7) | pub fn descriptor() -> wgpu::ShaderModuleDescriptor<'static> {
  function linkage (line 12) | pub fn linkage(device: &wgpu::Device) -> super::ShaderLinkage {
  constant ENTRY_POINT (line 25) | pub const ENTRY_POINT: &str = "convolutionshaderprefilter_environment_cu...
  function descriptor (line 26) | pub fn descriptor() -> wgpu::ShaderModuleDescriptor<'static> {
  function linkage (line 31) | pub fn linkage(device: &wgpu::Device) -> super::ShaderLinkage {
  function linkage (line 42) | pub fn linkage(device: &wgpu::Device) -> ShaderLinkage {

FILE: crates/renderling/src/linkage/prefilter_environment_cubemap_vertex.rs
  constant ENTRY_POINT (line 6) | pub const ENTRY_POINT: &str = "convolution::shader::prefilter_environmen...
  function descriptor (line 7) | pub fn descriptor() -> wgpu::ShaderModuleDescriptor<'static> {
  function linkage (line 12) | pub fn linkage(device: &wgpu::Device) -> super::ShaderLinkage {
  constant ENTRY_POINT (line 25) | pub const ENTRY_POINT: &str = "convolutionshaderprefilter_environment_cu...
  function descriptor (line 26) | pub fn descriptor() -> wgpu::ShaderModuleDescriptor<'static> {
  function linkage (line 31) | pub fn linkage(device: &wgpu::Device) -> super::ShaderLinkage {
  function linkage (line 42) | pub fn linkage(device: &wgpu::Device) -> ShaderLinkage {

FILE: crates/renderling/src/linkage/primitive_fragment.rs
  constant ENTRY_POINT (line 6) | pub const ENTRY_POINT: &str = "primitive::shader::primitive_fragment";
  function descriptor (line 7) | pub fn descriptor() -> wgpu::ShaderModuleDescriptor<'static> {
  function linkage (line 10) | pub fn linkage(device: &wgpu::Device) -> super::ShaderLinkage {
  constant ENTRY_POINT (line 20) | pub const ENTRY_POINT: &str = "primitiveshaderprimitive_fragment";
  function descriptor (line 21) | pub fn descriptor() -> wgpu::ShaderModuleDescriptor<'static> {
  function linkage (line 24) | pub fn linkage(device: &wgpu::Device) -> super::ShaderLinkage {
  function linkage (line 32) | pub fn linkage(device: &wgpu::Device) -> ShaderLinkage {

FILE: crates/renderling/src/linkage/primitive_vertex.rs
  constant ENTRY_POINT (line 6) | pub const ENTRY_POINT: &str = "primitive::shader::primitive_vertex";
  function descriptor (line 7) | pub fn descriptor() -> wgpu::ShaderModuleDescriptor<'static> {
  function linkage (line 10) | pub fn linkage(device: &wgpu::Device) -> super::ShaderLinkage {
  constant ENTRY_POINT (line 20) | pub const ENTRY_POINT: &str = "primitiveshaderprimitive_vertex";
  function descriptor (line 21) | pub fn descriptor() -> wgpu::ShaderModuleDescriptor<'static> {
  function linkage (line 24) | pub fn linkage(device: &wgpu::Device) -> super::ShaderLinkage {
  function linkage (line 32) | pub fn linkage(device: &wgpu::Device) -> ShaderLinkage {

FILE: crates/renderling/src/linkage/shadow_mapping_fragment.rs
  constant ENTRY_POINT (line 6) | pub const ENTRY_POINT: &str = "light::shader::shadow_mapping_fragment";
  function descriptor (line 7) | pub fn descriptor() -> wgpu::ShaderModuleDescriptor<'static> {
  function linkage (line 10) | pub fn linkage(device: &wgpu::Device) -> super::ShaderLinkage {
  constant ENTRY_POINT (line 20) | pub const ENTRY_POINT: &str = "lightshadershadow_mapping_fragment";
  function descriptor (line 21) | pub fn descriptor() -> wgpu::ShaderModuleDescriptor<'static> {
  function linkage (line 24) | pub fn linkage(device: &wgpu::Device) -> super::ShaderLinkage {
  function linkage (line 32) | pub fn linkage(device: &wgpu::Device) -> ShaderLinkage {

FILE: crates/renderling/src/linkage/shadow_mapping_vertex.rs
  constant ENTRY_POINT (line 6) | pub const ENTRY_POINT: &str = "light::shader::shadow_mapping_vertex";
  function descriptor (line 7) | pub fn descriptor() -> wgpu::ShaderModuleDescriptor<'static> {
  function linkage (line 10) | pub fn linkage(device: &wgpu::Device) -> super::ShaderLinkage {
  constant ENTRY_POINT (line 20) | pub const ENTRY_POINT: &str = "lightshadershadow_mapping_vertex";
  function descriptor (line 21) | pub fn descriptor() -> wgpu::ShaderModuleDescriptor<'static> {
  function linkage (line 24) | pub fn linkage(device: &wgpu::Device) -> super::ShaderLinkage {
  function linkage (line 32) | pub fn linkage(device: &wgpu::Device) -> ShaderLinkage {

FILE: crates/renderling/src/linkage/skybox_cubemap_fragment.rs
  constant ENTRY_POINT (line 6) | pub const ENTRY_POINT: &str = "skybox::shader::skybox_cubemap_fragment";
  function descriptor (line 7) | pub fn descriptor() -> wgpu::ShaderModuleDescriptor<'static> {
  function linkage (line 10) | pub fn linkage(device: &wgpu::Device) -> super::ShaderLinkage {
  constant ENTRY_POINT (line 20) | pub const ENTRY_POINT: &str = "skyboxshaderskybox_cubemap_fragment";
  function descriptor (line 21) | pub fn descriptor() -> wgpu::ShaderModuleDescriptor<'static> {
  function linkage (line 24) | pub fn linkage(device: &wgpu::Device) -> super::ShaderLinkage {
  function linkage (line 32) | pub fn linkage(device: &wgpu::Device) -> ShaderLinkage {

FILE: crates/renderling/src/linkage/skybox_cubemap_vertex.rs
  constant ENTRY_POINT (line 6) | pub const ENTRY_POINT: &str = "skybox::shader::skybox_cubemap_vertex";
  function descriptor (line 7) | pub fn descriptor() -> wgpu::ShaderModuleDescriptor<'static> {
  function linkage (line 10) | pub fn linkage(device: &wgpu::Device) -> super::ShaderLinkage {
  constant ENTRY_POINT (line 20) | pub const ENTRY_POINT: &str = "skyboxshaderskybox_cubemap_vertex";
  function descriptor (line 21) | pub fn descriptor() -> wgpu::ShaderModuleDescriptor<'static> {
  function linkage (line 24) | pub fn linkage(device: &wgpu::Device) -> super::ShaderLinkage {
  function linkage (line 32) | pub fn linkage(device: &wgpu::Device) -> ShaderLinkage {

FILE: crates/renderling/src/linkage/skybox_equirectangular_fragment.rs
  constant ENTRY_POINT (line 6) | pub const ENTRY_POINT: &str = "skybox::shader::skybox_equirectangular_fr...
  function descriptor (line 7) | pub fn descriptor() -> wgpu::ShaderModuleDescriptor<'static> {
  function linkage (line 10) | pub fn linkage(device: &wgpu::Device) -> super::ShaderLinkage {
  constant ENTRY_POINT (line 23) | pub const ENTRY_POINT: &str = "skyboxshaderskybox_equirectangular_fragme...
  function descriptor (line 24) | pub fn descriptor() -> wgpu::ShaderModuleDescriptor<'static> {
  function linkage (line 27) | pub fn linkage(device: &wgpu::Device) -> super::ShaderLinkage {
  function linkage (line 38) | pub fn linkage(device: &wgpu::Device) -> ShaderLinkage {

FILE: crates/renderling/src/linkage/skybox_vertex.rs
  constant ENTRY_POINT (line 6) | pub const ENTRY_POINT: &str = "skybox::shader::skybox_vertex";
  function descriptor (line 7) | pub fn descriptor() -> wgpu::ShaderModuleDescriptor<'static> {
  function linkage (line 10) | pub fn linkage(device: &wgpu::Device) -> super::ShaderLinkage {
  constant ENTRY_POINT (line 20) | pub const ENTRY_POINT: &str = "skyboxshaderskybox_vertex";
  function descriptor (line 21) | pub fn descriptor() -> wgpu::ShaderModuleDescriptor<'static> {
  function linkage (line 24) | pub fn linkage(device: &wgpu::Device) -> super::ShaderLinkage {
  function linkage (line 32) | pub fn linkage(device: &wgpu::Device) -> ShaderLinkage {

FILE: crates/renderling/src/linkage/slabbed_renderlet.rs
  constant ENTRY_POINT (line 6) | pub const ENTRY_POINT: &str = "tutorial::slabbed_renderlet";
  function descriptor (line 7) | pub fn descriptor() -> wgpu::ShaderModuleDescriptor<'static> {
  function linkage (line 10) | pub fn linkage(device: &wgpu::Device) -> super::ShaderLinkage {
  constant ENTRY_POINT (line 20) | pub const ENTRY_POINT: &str = "tutorialslabbed_renderlet";
  function descriptor (line 21) | pub fn descriptor() -> wgpu::ShaderModuleDescriptor<'static> {
  function linkage (line 24) | pub fn linkage(device: &wgpu::Device) -> super::ShaderLinkage {
  function linkage (line 32) | pub fn linkage(device: &wgpu::Device) -> ShaderLinkage {

FILE: crates/renderling/src/linkage/slabbed_vertices.rs
  constant ENTRY_POINT (line 6) | pub const ENTRY_POINT: &str = "tutorial::slabbed_vertices";
  function descriptor (line 7) | pub fn descriptor() -> wgpu::ShaderModuleDescriptor<'static> {
  function linkage (line 10) | pub fn linkage(device: &wgpu::Device) -> super::ShaderLinkage {
  constant ENTRY_POINT (line 20) | pub const ENTRY_POINT: &str = "tutorialslabbed_vertices";
  function descriptor (line 21) | pub fn descriptor() -> wgpu::ShaderModuleDescriptor<'static> {
  function linkage (line 24) | pub fn linkage(device: &wgpu::Device) -> super::ShaderLinkage {
  function linkage (line 32) | pub fn linkage(device: &wgpu::Device) -> ShaderLinkage {

FILE: crates/renderling/src/linkage/slabbed_vertices_no_instance.rs
  constant ENTRY_POINT (line 6) | pub const ENTRY_POINT: &str = "tutorial::slabbed_vertices_no_instance";
  function descriptor (line 7) | pub fn descriptor() -> wgpu::ShaderModuleDescriptor<'static> {
  function linkage (line 10) | pub fn linkage(device: &wgpu::Device) -> super::ShaderLinkage {
  constant ENTRY_POINT (line 23) | pub const ENTRY_POINT: &str = "tutorialslabbed_vertices_no_instance";
  function descriptor (line 24) | pub fn descriptor() -> wgpu::ShaderModuleDescriptor<'static> {
  function linkage (line 27) | pub fn linkage(device: &wgpu::Device) -> super::ShaderLinkage {
  function linkage (line 38) | pub fn linkage(device: &wgpu::Device) -> ShaderLinkage {

FILE: crates/renderling/src/linkage/tonemapping_fragment.rs
  constant ENTRY_POINT (line 6) | pub const ENTRY_POINT: &str = "tonemapping::tonemapping_fragment";
  function descriptor (line 7) | pub fn descriptor() -> wgpu::ShaderModuleDescriptor<'static> {
  function linkage (line 10) | pub fn linkage(device: &wgpu::Device) -> super::ShaderLinkage {
  constant ENTRY_POINT (line 20) | pub const ENTRY_POINT: &str = "tonemappingtonemapping_fragment";
  function descriptor (line 21) | pub fn descriptor() -> wgpu::ShaderModuleDescriptor<'static> {
  function linkage (line 24) | pub fn linkage(device: &wgpu::Device) -> super::ShaderLinkage {
  function linkage (line 32) | pub fn linkage(device: &wgpu::Device) -> ShaderLinkage {

FILE: crates/renderling/src/linkage/tonemapping_vertex.rs
  constant ENTRY_POINT (line 6) | pub const ENTRY_POINT: &str = "tonemapping::tonemapping_vertex";
  function descriptor (line 7) | pub fn descriptor() -> wgpu::ShaderModuleDescriptor<'static> {
  function linkage (line 10) | pub fn linkage(device: &wgpu::Device) -> super::ShaderLinkage {
  constant ENTRY_POINT (line 20) | pub const ENTRY_POINT: &str = "tonemappingtonemapping_vertex";
  function descriptor (line 21) | pub fn descriptor() -> wgpu::ShaderModuleDescriptor<'static> {
  function linkage (line 24) | pub fn linkage(device: &wgpu::Device) -> super::ShaderLinkage {
  function linkage (line 32) | pub fn linkage(device: &wgpu::Device) -> ShaderLinkage {

FILE: crates/renderling/src/linkage/ui_fragment.rs
  constant ENTRY_POINT (line 6) | pub const ENTRY_POINT: &str = "ui_slab::shader::ui_fragment";
  function descriptor (line 7) | pub fn descriptor() -> wgpu::ShaderModuleDescriptor<'static> {
  function linkage (line 10) | pub fn linkage(device: &wgpu::Device) -> super::ShaderLinkage {
  constant ENTRY_POINT (line 20) | pub const ENTRY_POINT: &str = "ui_slabshaderui_fragment";
  function descriptor (line 21) | pub fn descriptor() -> wgpu::ShaderModuleDescriptor<'static> {
  function linkage (line 24) | pub fn linkage(device: &wgpu::Device) -> super::ShaderLinkage {
  function linkage (line 32) | pub fn linkage(device: &wgpu::Device) -> ShaderLinkage {

FILE: crates/renderling/src/linkage/ui_vertex.rs
  constant ENTRY_POINT (line 6) | pub const ENTRY_POINT: &str = "ui_slab::shader::ui_vertex";
  function descriptor (line 7) | pub fn descriptor() -> wgpu::ShaderModuleDescriptor<'static> {
  function linkage (line 10) | pub fn linkage(device: &wgpu::Device) -> super::ShaderLinkage {
  constant ENTRY_POINT (line 20) | pub const ENTRY_POINT: &str = "ui_slabshaderui_vertex";
  function descriptor (line 21) | pub fn descriptor() -> wgpu::ShaderModuleDescriptor<'static> {
  function linkage (line 24) | pub fn linkage(device: &wgpu::Device) -> super::ShaderLinkage {
  function linkage (line 32) | pub fn linkage(device: &wgpu::Device) -> ShaderLinkage {

FILE: crates/renderling/src/material.rs
  type MaterialDescriptor (line 19) | pub struct MaterialDescriptor {
  method default (line 43) | fn default() -> Self {

FILE: crates/renderling/src/material/cpu.rs
  type Materials (line 21) | pub struct Materials {
    method as_ref (line 28) | fn as_ref(&self) -> &WgpuRuntime {
    method new (line 41) | pub fn new(runtime: impl AsRef<WgpuRuntime>, atlas_size: wgpu::Extent3...
    method runtime (line 60) | pub fn runtime(&self) -> &WgpuRuntime {
    method slab_allocator (line 65) | pub fn slab_allocator(&self) -> &SlabAllocator<WgpuRuntime> {
    method atlas (line 70) | pub fn atlas(&self) -> &Atlas {
    method default_material (line 75) | pub fn default_material(&self) -> &Material {
    method commit (line 83) | pub fn commit(&self) -> (bool, SlabBuffer<wgpu::Buffer>) {
    method new_material (line 89) | pub fn new_material(&self) -> Material {
  type Material (line 119) | pub struct Material {
    method from (line 130) | fn from(value: &Material) -> Self {
    method id (line 137) | pub fn id(&self) -> Id<MaterialDescriptor> {
    method descriptor (line 142) | pub fn descriptor(&self) -> MaterialDescriptor {
    method set_emissive_factor (line 151) | pub fn set_emissive_factor(&self, param: Vec3) -> &Self {
    method with_emissive_factor (line 160) | pub fn with_emissive_factor(self, param: Vec3) -> Self {
    method set_emissive_strength_multiplier (line 169) | pub fn set_emissive_strength_multiplier(&self, param: f32) -> &Self {
    method with_emissive_strength_multiplier (line 179) | pub fn with_emissive_strength_multiplier(self, param: f32) -> Self {
    method set_albedo_factor (line 188) | pub fn set_albedo_factor(&self, param: Vec4) -> &Self {
    method with_albedo_factor (line 197) | pub fn with_albedo_factor(self, param: Vec4) -> Self {
    method set_metallic_factor (line 206) | pub fn set_metallic_factor(&self, param: f32) -> &Self {
    method with_metallic_factor (line 215) | pub fn with_metallic_factor(self, param: f32) -> Self {
    method set_roughness_factor (line 224) | pub fn set_roughness_factor(&self, param: f32) -> &Self {
    method with_roughness_factor (line 233) | pub fn with_roughness_factor(self, param: f32) -> Self {
    method set_albedo_tex_coord (line 242) | pub fn set_albedo_tex_coord(&self, param: u32) -> &Self {
    method with_albedo_tex_coord (line 251) | pub fn with_albedo_tex_coord(self, param: u32) -> Self {
    method set_metallic_roughness_tex_coord (line 260) | pub fn set_metallic_roughness_tex_coord(&self, param: u32) -> &Self {
    method with_metallic_roughness_tex_coord (line 270) | pub fn with_metallic_roughness_tex_coord(self, param: u32) -> Self {
    method set_normal_tex_coord (line 279) | pub fn set_normal_tex_coord(&self, param: u32) -> &Self {
    method with_normal_tex_coord (line 288) | pub fn with_normal_tex_coord(self, param: u32) -> Self {
    method set_ambient_occlusion_tex_coord (line 297) | pub fn set_ambient_occlusion_tex_coord(&self, param: u32) -> &Self {
    method with_ambient_occlusion_tex_coord (line 306) | pub fn with_ambient_occlusion_tex_coord(self, param: u32) -> Self {
    method set_emissive_tex_coord (line 315) | pub fn set_emissive_tex_coord(&self, param: u32) -> &Self {
    method with_emissive_tex_coord (line 324) | pub fn with_emissive_tex_coord(self, param: u32) -> Self {
    method set_has_lighting (line 333) | pub fn set_has_lighting(&self, param: bool) -> &Self {
    method with_has_lighting (line 342) | pub fn with_has_lighting(self, param: bool) -> Self {
    method set_ambient_occlusion_strength (line 351) | pub fn set_ambient_occlusion_strength(&self, param: f32) -> &Self {
    method with_ambient_occlusion_strength (line 360) | pub fn with_ambient_occlusion_strength(self, param: f32) -> Self {
    method remove_albedo_texture (line 369) | pub fn remove_albedo_texture(&self) {
    method set_albedo_texture (line 378) | pub fn set_albedo_texture(&self, texture: &AtlasTexture) -> &Self {
    method with_albedo_texture (line 386) | pub fn with_albedo_texture(self, texture: &AtlasTexture) -> Self {
    method remove_metallic_roughness_texture (line 397) | pub fn remove_metallic_roughness_texture(&self) {
    method set_metallic_roughness_texture (line 411) | pub fn set_metallic_roughness_texture(&self, texture: &AtlasTexture) -...
    method with_metallic_roughness_texture (line 426) | pub fn with_metallic_roughness_texture(self, texture: &AtlasTexture) -...
    method remove_normal_texture (line 435) | pub fn remove_normal_texture(&self) {
    method set_normal_texture (line 448) | pub fn set_normal_texture(&self, texture: &AtlasTexture) -> &Self {
    method with_normal_texture (line 463) | pub fn with_normal_texture(self, texture: &AtlasTexture) -> Self {
    method remove_ambient_occlusion_texture (line 472) | pub fn remove_ambient_occlusion_texture(&self) {
    method set_ambient_occlusion_texture (line 482) | pub fn set_ambient_occlusion_texture(&self, texture: &AtlasTexture) ->...
    method with_ambient_occlusion_texture (line 493) | pub fn with_ambient_occlusion_texture(self, texture: &AtlasTexture) ->...
    method remove_emissive_texture (line 502) | pub fn remove_emissive_texture(&self) {
    method set_emissive_texture (line 515) | pub fn set_emissive_texture(&self, texture: &AtlasTexture) -> &Self {
    method with_emissive_texture (line 527) | pub fn with_emissive_texture(self, texture: &AtlasTexture) -> Self {

FILE: crates/renderling/src/math.rs
  type Fetch (line 17) | pub trait Fetch<Coords> {
    method fetch (line 20) | fn fetch(&self, coords: Coords) -> Self::Output;
  type Output (line 24) | type Output = Vec4;
  function fetch (line 26) | fn fetch(&self, coords: UVec2) -> Self::Output {
  type Output (line 32) | type Output = Vec4;
  function fetch (line 34) | fn fetch(&self, coords: UVec2) -> Self::Output {
  type IsSampler (line 42) | pub trait IsSampler: Copy + Clone {}
  type Sample2d (line 48) | pub trait Sample2d {
    method sample_by_lod (line 51) | fn sample_by_lod(&self, sampler: Self::Sampler, uv: glam::Vec2, lod: f...
    type Sampler (line 55) | type Sampler = Sampler;
    method sample_by_lod (line 57) | fn sample_by_lod(&self, sampler: Self::Sampler, uv: glam::Vec2, lod: f...
    type Sampler (line 63) | type Sampler = Sampler;
    method sample_by_lod (line 65) | fn sample_by_lod(&self, sampler: Self::Sampler, uv: glam::Vec2, lod: f...
    type Sampler (line 124) | type Sampler = ();
    method sample_by_lod (line 126) | fn sample_by_lod(&self, _sampler: Self::Sampler, _uv: glam::Vec2, _lod...
    type Sampler (line 188) | type Sampler = ();
    method sample_by_lod (line 190) | fn sample_by_lod(&self, _sampler: Self::Sampler, uv: glam::Vec2, _lod:...
  type Sample2dArray (line 70) | pub trait Sample2dArray {
    method sample_by_lod (line 73) | fn sample_by_lod(&self, sampler: Self::Sampler, uv: glam::Vec3, lod: f...
    type Sampler (line 77) | type Sampler = Sampler;
    method sample_by_lod (line 79) | fn sample_by_lod(&self, sampler: Self::Sampler, uv: glam::Vec3, lod: f...
    type Sampler (line 85) | type Sampler = Sampler;
    method sample_by_lod (line 87) | fn sample_by_lod(&self, sampler: Self::Sampler, uv: glam::Vec3, lod: f...
    type Sampler (line 132) | type Sampler = ();
    method sample_by_lod (line 134) | fn sample_by_lod(&self, _sampler: Self::Sampler, _uv: glam::Vec3, _lod...
    type Sampler (line 219) | type Sampler = ();
    method sample_by_lod (line 222) | fn sample_by_lod(&self, _sampler: Self::Sampler, uv: glam::Vec3, _lod:...
  type SampleCube (line 92) | pub trait SampleCube {
    method sample_by_lod (line 95) | fn sample_by_lod(&self, sampler: Self::Sampler, uv: Vec3, lod: f32) ->...
    type Sampler (line 99) | type Sampler = Sampler;
    method sample_by_lod (line 101) | fn sample_by_lod(&self, sampler: Self::Sampler, uv: Vec3, lod: f32) ->...
    type Sampler (line 140) | type Sampler = ();
    method sample_by_lod (line 142) | fn sample_by_lod(&self, _sampler: Self::Sampler, _uv: Vec3, _lod: f32)...
    type Sampler (line 243) | type Sampler = ();
    method sample_by_lod (line 245) | fn sample_by_lod(
  type ConstTexture (line 113) | pub struct ConstTexture(Vec4);
    type Output (line 116) | type Output = Vec4;
    method fetch (line 118) | fn fetch(&self, _coords: UVec2) -> Self::Output {
    method new (line 148) | pub fn new(value: Vec4) -> Self {
  type CpuTexture2d (line 154) | pub struct CpuTexture2d<P: image::Pixel, Container> {
  function from_image (line 160) | pub fn from_image(
  type Output (line 173) | type Output = Vec4;
  function fetch (line 175) | fn fetch(&self, coords: UVec2) -> Self::Output {
  type CpuTexture2dArray (line 199) | pub struct CpuTexture2dArray<P: image::Pixel, Container> {
  function from_images (line 205) | pub fn from_images(
  type CpuCubemap (line 238) | pub struct CpuCubemap {
  function scaled_u8_to_f32 (line 256) | pub fn scaled_u8_to_f32(u: u8) -> f32 {
  function luma_u8_to_vec4 (line 260) | pub fn luma_u8_to_vec4(p: &image::Luma<u8>) -> Vec4 {
  function scaled_f32_to_u8 (line 266) | pub fn scaled_f32_to_u8(f: f32) -> u8 {
  function scaled_u32_to_u8 (line 271) | pub fn scaled_u32_to_u8(u: u32) -> u8 {
  type IsVector (line 286) | pub trait IsVector {
    method alt_norm_or_zero (line 291) | fn alt_norm_or_zero(&self) -> Self;
    method signum_or_zero (line 294) | fn signum_or_zero(&self) -> Self;
    method dot2 (line 298) | fn dot2(&self) -> f32;
    method orthonormal_vectors (line 301) | fn orthonormal_vectors(&self) -> Self::OrthogonalVectors;
    type OrthogonalVectors (line 305) | type OrthogonalVectors = Vec2;
    method alt_norm_or_zero (line 307) | fn alt_norm_or_zero(&self) -> Self {
    method signum_or_zero (line 315) | fn signum_or_zero(&self) -> Self {
    method dot2 (line 319) | fn dot2(&self) -> f32 {
    method orthonormal_vectors (line 323) | fn orthonormal_vectors(&self) -> Self::OrthogonalVectors {
    type OrthogonalVectors (line 329) | type OrthogonalVectors = [Vec3; 2];
    method alt_norm_or_zero (line 331) | fn alt_norm_or_zero(&self) -> Self {
    method signum_or_zero (line 339) | fn signum_or_zero(&self) -> Self {
    method dot2 (line 347) | fn dot2(&self) -> f32 {
    method orthonormal_vectors (line 351) | fn orthonormal_vectors(&self) -> Self::OrthogonalVectors {
  function distance_to_line (line 367) | pub fn distance_to_line(p: Vec3, a: Vec3, b: Vec3) -> f32 {
  type IsMatrix (line 385) | pub trait IsMatrix {
    method to_scale_rotation_translation_or_id (line 401) | fn to_scale_rotation_translation_or_id(&self) -> (glam::Vec3, glam::Qu...
    method to_scale_rotation_translation_or_id (line 472) | fn to_scale_rotation_translation_or_id(&self) -> (glam::Vec3, glam::Qu...
  function from_rotation_axes (line 408) | fn from_rotation_axes(x_axis: glam::Vec3, y_axis: glam::Vec3, z_axis: gl...
  function srt_id (line 466) | const fn srt_id() -> (Vec3, Quat, Vec3) {
  function signum_or_zero (line 509) | pub fn signum_or_zero(n: f32) -> f32 {
  function step_ge (line 516) | pub fn step_ge(value: f32, edge: f32) -> f32 {
  function step_le (line 523) | pub fn step_le(value: f32, edge: f32) -> f32 {
  function smoothstep (line 527) | pub fn smoothstep(edge_in: f32, edge_out: f32, mut x: f32) -> f32 {
  function triangle_face_normal (line 533) | pub fn triangle_face_normal(p1: Vec3, p2: Vec3, p3: Vec3) -> Vec3 {
  function hex_to_vec4 (line 547) | pub fn hex_to_vec4(color: u32) -> Vec4 {
  constant UNIT_QUAD_CCW (line 556) | pub const UNIT_QUAD_CCW: [Vec3; 6] = {
  constant CLIP_QUAD_CCW (line 564) | pub const CLIP_QUAD_CCW: [Vec3; 6] = {
  constant CLIP_SPACE_COORD_QUAD_CCW_TL (line 572) | pub const CLIP_SPACE_COORD_QUAD_CCW_TL: Vec4 = Vec4::new(-1.0, 1.0, 0.5,...
  constant CLIP_SPACE_COORD_QUAD_CCW_BL (line 573) | pub const CLIP_SPACE_COORD_QUAD_CCW_BL: Vec4 = Vec4::new(-1.0, -1.0, 0.5...
  constant CLIP_SPACE_COORD_QUAD_CCW_TR (line 574) | pub const CLIP_SPACE_COORD_QUAD_CCW_TR: Vec4 = Vec4::new(1.0, 1.0, 0.5, ...
  constant CLIP_SPACE_COORD_QUAD_CCW_BR (line 575) | pub const CLIP_SPACE_COORD_QUAD_CCW_BR: Vec4 = Vec4::new(1.0, -1.0, 0.5,...
  constant CLIP_SPACE_COORD_QUAD_CCW (line 577) | pub const CLIP_SPACE_COORD_QUAD_CCW: [Vec4; 6] = {
  constant UV_COORD_QUAD_CCW (line 588) | pub const UV_COORD_QUAD_CCW: [Vec2; 6] = {
  constant POINTS_2D_TEX_QUAD (line 596) | pub const POINTS_2D_TEX_QUAD: [Vec2; 6] = {
  constant UNIT_POINTS (line 611) | pub const UNIT_POINTS: [Vec3; 8] = {
  constant UNIT_INDICES (line 626) | pub const UNIT_INDICES: [usize; 36] = [
  function unit_cube (line 636) | pub fn unit_cube() -> Vec<(Vec3, Vec3)> {
  constant CUBE (line 658) | pub const CUBE: [Vec3; 36] = {
  function reflect (line 670) | pub fn reflect(i: Vec3, n: Vec3) -> Vec3 {
  function is_inside_clip_space (line 678) | pub fn is_inside_clip_space(p: Vec3) -> bool {
  function convex_mesh (line 682) | pub const fn convex_mesh([p0, p1, p2, p3, p4, p5, p6, p7]: [Vec3; 8]) ->...
  type GpuRng (line 702) | pub struct GpuRng(pub u32);
    method new (line 705) | pub fn new(state: u32) -> GpuRng {
    method gen (line 709) | pub fn gen(&mut self) -> u32 {
    method gen_u32 (line 725) | pub fn gen_u32(&mut self, min: u32, max: u32) -> u32 {
    method gen_f32 (line 731) | pub fn gen_f32(&mut self, min: f32, max: f32) -> f32 {
    method gen_vec3 (line 738) | pub fn gen_vec3(&mut self, min: Vec3, max: Vec3) -> Vec3 {
    method gen_vec2 (line 745) | pub fn gen_vec2(&mut self, min: Vec2, max: Vec2) -> Vec2 {
  function convert_pixel_to_ndc (line 755) | pub fn convert_pixel_to_ndc(pixel_coord: Vec2, viewport_size: UVec2) -> ...
  function step_sanity (line 769) | fn step_sanity() {
  function nan_sanity (line 777) | fn nan_sanity() {
  function signum_sanity (line 785) | fn signum_sanity() {

FILE: crates/renderling/src/mesh.rs
  type Mesh (line 5) | pub struct Mesh {
    method new (line 12) | pub fn new<V: bytemuck::Pod>(
    method from_vertices (line 43) | pub fn from_vertices<V: bytemuck::Pod>(
    method draw (line 51) | pub fn draw<'a>(&'a self, render_pass: &mut wgpu::RenderPass<'a>) {
    method update (line 64) | pub fn update<V: bytemuck::Pod>(

FILE: crates/renderling/src/pbr.rs
  function pbr_metallic_roughness_spheres (line 29) | fn pbr_metallic_roughness_spheres() {

FILE: crates/renderling/src/pbr/brdf/cpu.rs
  type BrdfLut (line 8) | pub struct BrdfLut {
    method new (line 14) | pub fn new(runtime: impl AsRef<WgpuRuntime>) -> Self {
    method texture (line 105) | pub fn texture(&self) -> &texture::Texture {
  function precomputed_brdf (line 115) | fn precomputed_brdf() {

FILE: crates/renderling/src/pbr/brdf/shader.rs
  function sample_brdf (line 7) | pub fn sample_brdf<T: Sample2d<Sampler = S>, S: IsSampler>(

FILE: crates/renderling/src/pbr/debug.rs
  type DebugChannel (line 8) | pub enum DebugChannel {

FILE: crates/renderling/src/pbr/ibl/cpu.rs
  type Ibl (line 17) | pub struct Ibl {
    method new (line 28) | pub fn new(runtime: impl AsRef<WgpuRuntime>, skybox: &Skybox) -> Self {
    method is_empty (line 113) | pub fn is_empty(&self) -> bool {
  function create_irradiance_map (line 118) | fn create_irradiance_map(
  function create_prefiltered_environment_pipeline_and_bindgroup (line 155) | pub(crate) fn create_prefiltered_environment_pipeline_and_bindgroup(
  function create_prefiltered_environment_map (line 262) | fn create_prefiltered_environment_map(
  function diffuse_irradiance_convolution_bindgroup_layout (line 348) | pub fn diffuse_irradiance_convolution_bindgroup_layout(
  function diffuse_irradiance_convolution_bindgroup (line 384) | pub fn diffuse_irradiance_convolution_bindgroup(
  type DiffuseIrradianceConvolutionRenderPipeline (line 411) | pub struct DiffuseIrradianceConvolutionRenderPipeline(pub wgpu::RenderPi...
    method new (line 415) | pub fn new(device: &wgpu::Device, format: wgpu::TextureFormat) -> Self {
  function creates_valid_cubemaps (line 481) | fn creates_valid_cubemaps() {
  function mirror_cube_is_lit_by_environment (line 555) | fn mirror_cube_is_lit_by_environment() {

FILE: crates/renderling/src/pbr/ibl/shader.rs
  function di_convolution_fragment (line 11) | pub fn di_convolution_fragment(

FILE: crates/renderling/src/pbr/shader.rs
  function normal_distribution_ggx (line 27) | pub fn normal_distribution_ggx(n: Vec3, h: Vec3, roughness: f32) -> f32 {
  function geometry_schlick_ggx (line 39) | fn geometry_schlick_ggx(ndot_v: f32, roughness: f32) -> f32 {
  function geometry_smith (line 51) | fn geometry_smith(n: Vec3, v: Vec3, l: Vec3, roughness: f32) -> f32 {
  function fresnel_schlick (line 68) | fn fresnel_schlick(
  function fresnel_schlick_roughness (line 78) | fn fresnel_schlick_roughness(cos_theta: f32, f0: Vec3, roughness: f32) -...
  function outgoing_radiance (line 83) | pub fn outgoing_radiance(
  function sample_irradiance (line 133) | pub fn sample_irradiance<T: SampleCube<Sampler = S>, S: IsSampler>(
  function sample_specular_reflection (line 142) | pub fn sample_specular_reflection<T: SampleCube<Sampler = S>, S: IsSampl...
  function get_material (line 161) | pub fn get_material(
  function texture_color (line 180) | pub fn texture_color<A: Sample2dArray<Sampler = S>, S: IsSampler>(
  function fragment_impl (line 201) | pub fn fragment_impl<A, T, DtA, C, S>(
  function shade_fragment (line 477) | pub fn shade_fragment<S, T>(

FILE: crates/renderling/src/primitive/cpu.rs
  type Primitive (line 22) | pub struct Primitive {
    method new (line 40) | pub fn new(stage: &Stage) -> Self {
    method set_vertices (line 77) | pub fn set_vertices(&self, vertices: impl Into<Vertices<GpuOnlyArray>>...
    method with_vertices (line 86) | pub fn with_vertices(self, vertices: impl Into<Vertices<GpuOnlyArray>>...
    method set_indices (line 95) | pub fn set_indices(&self, indices: impl Into<Indices<GpuOnlyArray>>) -...
    method with_indices (line 104) | pub fn with_indices(self, indices: impl Into<Indices<GpuOnlyArray>>) -...
    method remove_indices (line 110) | pub fn remove_indices(&self) -> &Self {
    method id (line 120) | pub fn id(&self) -> Id<PrimitiveDescriptor> {
    method descriptor (line 125) | pub fn descriptor(&self) -> PrimitiveDescriptor {
    method set_bounds (line 130) | pub fn set_bounds(&self, bounds: BoundingSphere) -> &Self {
    method with_bounds (line 136) | pub fn with_bounds(self, bounds: BoundingSphere) -> Self {
    method bounds (line 144) | pub fn bounds(&self) -> BoundingSphere {
    method modify_bounds (line 154) | pub fn modify_bounds<T: 'static>(&self, f: impl FnOnce(&mut BoundingSp...
    method set_visible (line 159) | pub fn set_visible(&self, visible: bool) -> &Self {
    method with_visible (line 165) | pub fn with_visible(self, visible: bool) -> Self {
    method visible (line 171) | pub fn visible(&self) -> bool {
    method modify_visible (line 181) | pub fn modify_visible<T: 'static>(&self, f: impl FnOnce(&mut bool) -> ...
    method set_transform (line 193) | pub fn set_transform(&self, transform: impl Into<Transform>) -> &Self {
    method with_transform (line 205) | pub fn with_transform(self, transform: impl Into<Transform>) -> Self {
    method transform (line 213) | pub fn transform(&self) -> impl Deref<Target = Option<Transform>> + '_ {
    method remove_transform (line 220) | pub fn remove_transform(&self) -> &Self {
    method set_material (line 230) | pub fn set_material(&self, material: impl Into<Material>) -> &Self {
    method with_material (line 238) | pub fn with_material(self, material: impl Into<Material>) -> Self {
    method material (line 246) | pub fn material(&self) -> impl Deref<Target = Option<Material>> + '_ {
    method remove_material (line 251) | pub fn remove_material(&self) -> &Self {
    method set_skin (line 261) | pub fn set_skin(&self, skin: impl Into<Skin>) -> &Self {
    method with_skin (line 269) | pub fn with_skin(self, skin: impl Into<Skin>) -> Self {
    method skin (line 277) | pub fn skin(&self) -> impl Deref<Target = Option<Skin>> + '_ {
    method remove_skin (line 282) | pub fn remove_skin(&self) -> &Self {
    method set_morph_targets (line 292) | pub fn set_morph_targets(
    method with_morph_targets (line 309) | pub fn with_morph_targets(
    method morph_targets (line 322) | pub fn morph_targets(
    method remove_morph_targets (line 329) | pub fn remove_morph_targets(&self) -> &Self {
  method clone (line 61) | fn clone(&self) -> Self {

FILE: crates/renderling/src/primitive/shader.rs
  type VertexInfo (line 31) | pub struct VertexInfo {
  type PrimitiveDescriptor (line 41) | pub struct PrimitiveDescriptor {
    method get_vertex_info (line 76) | pub fn get_vertex_info(&self, vertex_index: u32, geometry_slab: &[u32]...
    method get_transform (line 91) | pub fn get_transform(&self, vertex: Vertex, slab: &[u32]) -> Transform...
    method get_vertex (line 103) | pub fn get_vertex(&self, vertex_index: u32, slab: &[u32]) -> Vertex {
    method get_vertex_count (line 122) | pub fn get_vertex_count(&self) -> u32 {
  method default (line 56) | fn default() -> Self {
  type PrimitivePbrVertexInfo (line 135) | pub struct PrimitivePbrVertexInfo {
  function primitive_vertex (line 156) | pub fn primitive_vertex(
  function primitive_fragment (line 244) | pub fn primitive_fragment(
  function test_i8_i16_extraction (line 305) | pub fn test_i8_i16_extraction(

FILE: crates/renderling/src/sdf.rs
  type CircleDescriptor (line 16) | pub struct CircleDescriptor {
    method distance (line 22) | pub fn distance(&self, point: Vec2) -> f32 {
  type Box (line 29) | pub struct Box {
    method distance (line 35) | pub fn distance(&self, point: Vec2) -> f32 {
  function sdf_circle_sanity (line 52) | fn sdf_circle_sanity() {
  function sdf_box_sanity (line 72) | fn sdf_box_sanity() {

FILE: crates/renderling/src/skybox/cpu.rs
  type SkyboxRenderPipeline (line 16) | pub struct SkyboxRenderPipeline {
    method msaa_sample_count (line 22) | pub fn msaa_sample_count(&self) -> u32 {
  function skybox_bindgroup_layout (line 27) | fn skybox_bindgroup_layout(device: &wgpu::Device) -> wgpu::BindGroupLayo...
  function create_skybox_bindgroup (line 61) | pub(crate) fn create_skybox_bindgroup(
  function create_skybox_render_pipeline (line 87) | pub(crate) fn create_skybox_render_pipeline(
  type Skybox (line 160) | pub struct Skybox {
    method empty (line 168) | pub fn empty(runtime: impl AsRef<WgpuRuntime>) -> Self {
    method new (line 183) | pub fn new(runtime: impl AsRef<WgpuRuntime>, hdr_img: AtlasImage) -> S...
    method environment_cubemap_texture (line 247) | pub fn environment_cubemap_texture(&self) -> &texture::Texture {
    method hdr_texture_from_atlas_image (line 252) | pub fn hdr_texture_from_atlas_image(
    method create_hdr_texture (line 278) | pub fn create_hdr_texture(runtime: impl AsRef<WgpuRuntime>, hdr_data: ...
    method create_environment_map_from_hdr (line 284) | fn create_environment_map_from_hdr(
    method is_empty (line 326) | pub fn is_empty(&self) -> bool {
  function hdr_skybox_scene (line 338) | fn hdr_skybox_scene() {

FILE: crates/renderling/src/skybox/shader.rs
  constant INV_ATAN (line 18) | const INV_ATAN: Vec2 = Vec2::new(0.1591, core::f32::consts::FRAC_1_PI);
  function direction_to_equirectangular_uv (line 22) | pub fn direction_to_equirectangular_uv(dir: Vec3) -> Vec2 {
  function skybox_vertex (line 31) | pub fn skybox_vertex(
  function skybox_cubemap_fragment (line 50) | pub fn skybox_cubemap_fragment(
  function skybox_cubemap_vertex (line 67) | pub fn skybox_cubemap_vertex(
  function skybox_equirectangular_fragment (line 82) | pub fn skybox_equirectangular_fragment(

FILE: crates/renderling/src/stage.rs
  function matrix_hierarchy_sanity (line 22) | fn matrix_hierarchy_sanity() {
  function nested_transform_fox_rigging (line 39) | fn nested_transform_fox_rigging() {

FILE: crates/renderling/src/stage/cpu.rs
  type StageError (line 40) | pub enum StageError {
    method from (line 53) | fn from(source: AtlasError) -> Self {
    method from (line 59) | fn from(source: LightingError) -> Self {
    method from (line 66) | fn from(source: crate::gltf::StageGltfError) -> Self {
  function create_msaa_textureview (line 71) | fn create_msaa_textureview(
  type StageCommitResult (line 97) | pub struct StageCommitResult {
    method latest_creation_time (line 105) | pub(crate) fn latest_creation_time(&self) -> usize {
    method should_invalidate (line 119) | pub(crate) fn should_invalidate(&self, previous_creation_time: usize) ...
  type PrimitiveBindGroup (line 149) | struct PrimitiveBindGroup<'a> {
  function create (line 168) | pub fn create(self) -> wgpu::BindGroup {
  type StageRendering (line 231) | pub(crate) struct StageRendering<'a> {
  function run (line 244) | pub fn run(self) -> (wgpu::SubmissionIndex, Option<SlabBuffer<wgpu::Buff...
  type Stage (line 396) | pub struct Stage {
    method as_ref (line 440) | fn as_ref(&self) -> &WgpuRuntime {
    method as_ref (line 446) | fn as_ref(&self) -> &Geometry {
    method as_ref (line 452) | fn as_ref(&self) -> &Materials {
    method as_ref (line 458) | fn as_ref(&self) -> &Lighting {
    method load_gltf_document_from_path (line 466) | pub fn load_gltf_document_from_path(
    method load_gltf_document_from_bytes (line 479) | pub fn load_gltf_document_from_bytes(
    method default_vertices (line 495) | pub fn default_vertices(&self) -> &Vertices {
    method new_camera (line 503) | pub fn new_camera(&self) -> Camera {
    method use_camera (line 508) | pub fn use_camera(&self, camera: impl AsRef<Camera>) {
    method used_camera_id (line 513) | pub fn used_camera_id(&self) -> Id<CameraDescriptor> {
    method use_camera_id (line 518) | pub fn use_camera_id(&self, camera_id: Id<CameraDescriptor>) {
    method new_transform (line 525) | pub fn new_transform(&self) -> Transform {
    method new_vertices (line 530) | pub fn new_vertices(&self, data: impl IntoIterator<Item = Vertex>) -> ...
    method new_indices (line 535) | pub fn new_indices(&self, data: impl IntoIterator<Item = u32>) -> Indi...
    method new_morph_targets (line 540) | pub fn new_morph_targets(
    method new_morph_target_weights (line 548) | pub fn new_morph_target_weights(
    method new_skin (line 556) | pub fn new_skin(
    method new_primitive (line 576) | pub fn new_primitive(&self) -> Primitive {
    method geometry_descriptor (line 582) | pub fn geometry_descriptor(&self) -> &Hybrid<GeometryDescriptor> {
    method default_material (line 592) | pub fn default_material(&self) -> &Material {
    method new_material (line 599) | pub fn new_material(&self) -> Material {
    method set_atlas_size (line 606) | pub fn set_atlas_size(&self, size: wgpu::Extent3d) -> Result<(), Stage...
    method add_images (line 623) | pub fn add_images(
    method clear_images (line 644) | pub fn clear_images(&self) -> Result<(), StageError> {
    method set_images (line 657) | pub fn set_images(
    method new_directional_light (line 678) | pub fn new_directional_light(&self) -> AnalyticalLight<DirectionalLigh...
    method new_point_light (line 683) | pub fn new_point_light(&self) -> AnalyticalLight<PointLight> {
    method new_spot_light (line 688) | pub fn new_spot_light(&self) -> AnalyticalLight<SpotLight> {
    method add_light (line 698) | pub fn add_light<T>(&self, bundle: &AnalyticalLight<T>)
    method remove_light (line 712) | pub fn remove_light<T: IsLight>(&self, bundle: &AnalyticalLight<T>) {
    method set_ambient_color (line 723) | pub fn set_ambient_color(&self, color: Vec4) -> &Self {
    method with_ambient_color (line 729) | pub fn with_ambient_color(self, color: Vec4) -> Self {
    method ambient_color (line 735) | pub fn ambient_color(&self) -> Vec4 {
    method new_shadow_map (line 754) | pub fn new_shadow_map<T>(
    method new_light_tiling (line 778) | pub fn new_light_tiling(&self, config: LightTilingConfig) -> LightTili...
    method get_skybox_pipeline_and_bindgroup (line 794) | fn get_skybox_pipeline_and_bindgroup(
    method use_skybox (line 841) | pub fn use_skybox(&self, skybox: &Skybox) -> &Self {
    method remove_skybox (line 861) | pub fn remove_skybox(&self) -> Option<Skybox> {
    method new_skybox_from_path (line 885) | pub fn new_skybox_from_path(
    method new_skybox_from_bytes (line 896) | pub fn new_skybox_from_bytes(&self, bytes: &[u8]) -> Result<Skybox, At...
    method new_ibl (line 905) | pub fn new_ibl(&self, skybox: &Skybox) -> Ibl {
    method use_ibl (line 912) | pub fn use_ibl(&self, ibl: &Ibl) -> &Self {
    method remove_ibl (line 921) | pub fn remove_ibl(&self) -> Option<Ibl> {
    method runtime (line 937) | pub fn runtime(&self) -> &WgpuRuntime {
    method device (line 941) | pub fn device(&self) -> &wgpu::Device {
    method queue (line 945) | pub fn queue(&self) -> &wgpu::Queue {
    method brdf_lut (line 952) | pub fn brdf_lut(&self) -> &BrdfLut {
    method used_gpu_buffer_byte_size (line 963) | pub fn used_gpu_buffer_byte_size(&self) -> usize {
    method hdr_texture (line 980) | pub fn hdr_texture(&self) -> impl Deref<Target = crate::texture::Textu...
    method commit (line 991) | pub fn commit(&self) -> StageCommitResult {
    method primitive_pipeline_bindgroup_layout (line 1005) | fn primitive_pipeline_bindgroup_layout(device: &wgpu::Device) -> wgpu:...
    method create_primitive_pipeline (line 1113) | pub fn create_primitive_pipeline(
    method new (line 1176) | pub fn new(ctx: &crate::context::Context) -> Self {
    method set_background_color (line 1257) | pub fn set_background_color(&self, color: impl Into<Vec4>) {
    method with_background_color (line 1270) | pub fn with_background_color(self, color: impl Into<Vec4>) -> Self {
    method get_msaa_sample_count (line 1276) | pub fn get_msaa_sample_count(&self) -> u32 {
    method set_msaa_sample_count (line 1285) | pub fn set_msaa_sample_count(&self, multisample_count: u32) {
    method with_msaa_sample_count (line 1349) | pub fn with_msaa_sample_count(self, multisample_count: u32) -> Self {
    method set_clear_color_attachments (line 1355) | pub fn set_clear_color_attachments(&self, should_clear: bool) {
    method with_clear_color_attachments (line 1361) | pub fn with_clear_color_attachments(self, should_clear: bool) -> Self {
    method set_clear_depth_attachments (line 1367) | pub fn set_clear_depth_attachments(&self, should_clear: bool) {
    method with_clear_depth_attachments (line 1373) | pub fn with_clear_depth_attachments(self, should_clear: bool) -> Self {
    method set_debug_mode (line 1379) | pub fn set_debug_mode(&self, debug_mode: DebugChannel) {
    method with_debug_mode (line 1386) | pub fn with_debug_mode(self, debug_mode: DebugChannel) -> Self {
    method set_use_debug_overlay (line 1392) | pub fn set_use_debug_overlay(&self, use_debug_overlay: bool) {
    method with_debug_overlay (line 1398) | pub fn with_debug_overlay(self, use_debug_overlay: bool) -> Self {
    method set_use_frustum_culling (line 1406) | pub fn set_use_frustum_culling(&self, use_frustum_culling: bool) {
    method with_frustum_culling (line 1413) | pub fn with_frustum_culling(self, use_frustum_culling: bool) -> Self {
    method set_use_occlusion_culling (line 1425) | pub fn set_use_occlusion_culling(&self, use_occlusion_culling: bool) {
    method with_occlusion_culling (line 1432) | pub fn with_occlusion_culling(self, use_occlusion_culling: bool) -> Se...
    method set_has_lighting (line 1438) | pub fn set_has_lighting(&self, use_lighting: bool) {
    method with_lighting (line 1445) | pub fn with_lighting(self, use_lighting: bool) -> Self {
    method set_has_vertex_skinning (line 1451) | pub fn set_has_vertex_skinning(&self, use_skinning: bool) {
    method with_vertex_skinning (line 1458) | pub fn with_vertex_skinning(self, use_skinning: bool) -> Self {
    method get_size (line 1463) | pub fn get_size(&self) -> UVec2 {
    method set_size (line 1471) | pub fn set_size(&self, size: UVec2) {
    method with_size (line 1521) | pub fn with_size(self, size: UVec2) -> Self {
    method set_has_bloom (line 1527) | pub fn set_has_bloom(&self, has_bloom: bool) {
    method with_bloom (line 1533) | pub fn with_bloom(self, has_bloom: bool) -> Self {
    method set_bloom_mix_strength (line 1541) | pub fn set_bloom_mix_strength(&self, strength: f32) {
    method with_bloom_mix_strength (line 1545) | pub fn with_bloom_mix_strength(self, strength: f32) -> Self {
    method set_bloom_filter_radius (line 1553) | pub fn set_bloom_filter_radius(&self, filter_radius: f32) {
    method with_bloom_filter_radius (line 1560) | pub fn with_bloom_filter_radius(self, filter_radius: f32) -> Self {
    method add_primitive (line 1573) | pub fn add_primitive(&self, primitive: &Primitive) -> usize {
    method remove_primitive (line 1583) | pub fn remove_primitive(&self, primitive: &Primitive) -> usize {
    method sort_primitive (line 1591) | pub fn sort_primitive(&self, f: impl Fn(&Primitive, &Primitive) -> std...
    method get_depth_texture (line 1598) | pub fn get_depth_texture(&self) -> DepthTexture {
    method new_nested_transform (line 1611) | pub fn new_nested_transform(&self) -> NestedTransform {
    method render (line 1616) | pub fn render(&self, view: &wgpu::TextureView) {
  function vertex_slab_roundtrip (line 1745) | fn vertex_slab_roundtrip() {
  function matrix_subtraction_sanity (line 1768) | fn matrix_subtraction_sanity() {
  function can_global_transform_calculation (line 1774) | fn can_global_transform_calculation() {
  function can_msaa (line 1802) | fn can_msaa() {
  function stage_geometry_desc_sanity (line 1860) | fn stage_geometry_desc_sanity() {
  function slabbed_vertices_native (line 1875) | fn slabbed_vertices_native() {

FILE: crates/renderling/src/sync.rs
  function atomic_i_increment (line 10) | pub fn atomic_i_increment<const SCOPE: u32, const SEMANTICS: u32>(
  function atomic_u_min (line 31) | pub fn atomic_u_min<const SCOPE: u32, const SEMANTICS: u32>(
  function atomic_u_max (line 54) | pub fn atomic_u_max<const SCOPE: u32, const SEMANTICS: u32>(

FILE: crates/renderling/src/texture.rs
  type TextureError (line 26) | pub enum TextureError {
  type Result (line 55) | type Result<T, E = TextureError> = std::result::Result<T, E>;
  function wgpu_texture_format_channels_and_subpixel_bytes (line 57) | pub fn wgpu_texture_format_channels_and_subpixel_bytes(
  function wgpu_texture_format_channels_and_subpixel_bytes_todo (line 74) | pub fn wgpu_texture_format_channels_and_subpixel_bytes_todo(
  function get_next_texture_id (line 82) | pub(crate) fn get_next_texture_id() -> usize {
  type Texture (line 88) | pub struct Texture {
    method id (line 105) | pub fn id(&self) -> usize {
    method width (line 109) | pub fn width(&self) -> u32 {
    method height (line 113) | pub fn height(&self) -> u32 {
    method size (line 117) | pub fn size(&self) -> UVec2 {
    method new_cubemap_texture (line 122) | pub fn new_cubemap_texture(
    method new_with (line 213) | pub fn new_with(
    method new (line 292) | pub fn new(
    method from_image_bytes (line 317) | pub fn from_image_bytes(
    method from_dynamic_image (line 343) | pub fn from_dynamic_image(
    method from_image_buffer (line 405) | pub fn from_image_buffer<P>(
    method from_wgpu_tex (line 472) | pub fn from_wgpu_tex(
    constant DEPTH_FORMAT (line 502) | pub const DEPTH_FORMAT: wgpu::TextureFormat = wgpu::TextureFormat::Dep...
    method create_depth_texture (line 504) | pub fn create_depth_texture(
    method create_depth_texture_for_shadow_map (line 552) | pub fn create_depth_texture_for_shadow_map(
    method read (line 608) | pub fn read(
    method read_hdr_image (line 628) | pub async fn read_hdr_image(
    method generate_mips (line 660) | pub fn generate_mips(
    constant HDR_TEXTURE_FORMAT (line 672) | pub const HDR_TEXTURE_FORMAT: wgpu::TextureFormat = wgpu::TextureForma...
    method create_hdr_texture (line 675) | pub fn create_hdr_texture(
    method render_cubemap (line 722) | pub(crate) fn render_cubemap(
  function read_depth_texture_to_image (line 810) | pub async fn read_depth_texture_to_image(
  function read_depth_texture_f32 (line 834) | pub async fn read_depth_texture_f32(
  type DepthTexture (line 851) | pub struct DepthTexture {
    method new (line 865) | pub fn new(runtime: impl AsRef<WgpuRuntime>, texture: impl Into<Arc<wg...
    method try_new_from (line 872) | pub fn try_new_from(
    method read_image (line 894) | pub async fn read_image(&self) -> Result<Option<image::GrayImage>> {
  type Target (line 857) | type Target = wgpu::Texture;
  method deref (line 859) | fn deref(&self) -> &Self::Target {
  type BufferDimensions (line 907) | pub struct BufferDimensions {
    method new (line 915) | pub fn new(channels: usize, subpixel_bytes: usize, width: usize, heigh...
  type MappedBuffer (line 933) | pub struct MappedBuffer<'a> {
  type Output (line 941) | type Output = Result<Vec<u8>, wgpu::BufferAsyncError>;
  function poll (line 943) | fn poll(
  type CopiedTextureBuffer (line 969) | pub struct CopiedTextureBuffer {
    method get_mapped_buffer (line 977) | fn get_mapped_buffer(&self) -> MappedBuffer<'_> {
    method pixels (line 1003) | pub async fn pixels(&self, device: &wgpu::Device) -> Result<Vec<u8>> {
    method convert_to_rgba (line 1010) | pub async fn convert_to_rgba(self) -> Result<image::RgbaImage, Texture...
    method into_image (line 1038) | pub async fn into_image<Sp, P>(
    method into_atlas_image (line 1059) | pub async fn into_atlas_image(self, device: &wgpu::Device) -> Result<A...
    method into_rgba (line 1080) | pub async fn into_rgba(
    method into_linear_rgba (line 1124) | pub async fn into_linear_rgba(
    method into_srgba (line 1154) | pub async fn into_srgba(self, device: &wgpu::Device) -> Result<image::...
    method read_from (line 1180) | pub fn read_from(
    method new (line 1239) | pub fn new(runtime: impl AsRef<WgpuRuntime>, texture: &wgpu::Texture) ...
  function generate_mipmaps (line 1262) | fn generate_mipmaps() {

FILE: crates/renderling/src/texture/mips.rs
  constant LABEL (line 9) | const LABEL: Option<&str> = Some("mip-map-generator");
  type MipMapError (line 12) | pub enum MipMapError {
  function create_pipeline (line 20) | fn create_pipeline(
  type MipMapGenerator (line 59) | pub struct MipMapGenerator {
    method new (line 66) | pub fn new(device: &wgpu::Device, format: wgpu::TextureFormat) -> Self {
    method generate (line 105) | pub fn generate(

FILE: crates/renderling/src/tonemapping.rs
  constant GAMMA (line 16) | const GAMMA: f32 = 2.2;
  constant INV_GAMMA (line 17) | const INV_GAMMA: f32 = 1.0 / GAMMA;
  constant ACESINPUT_MAT (line 20) | const ACESINPUT_MAT: Mat3 = mat3(
  constant ACESOUTPUT_MAT (line 27) | const ACESOUTPUT_MAT: Mat3 = mat3(
  function linear_to_srgb (line 35) | pub fn linear_to_srgb(color: Vec3) -> Vec3 {
  function srgb_to_linear (line 41) | pub fn srgb_to_linear(srgb_in: Vec3) -> Vec3 {
  function srgba_to_linear (line 47) | pub fn srgba_to_linear(srgb_in: Vec4) -> Vec4 {
  function tone_map_aces_narkowicz (line 53) | pub fn tone_map_aces_narkowicz(color: Vec3) -> Vec3 {
  function rrt_and_odtfit (line 65) | fn rrt_and_odtfit(color: Vec3) -> Vec3 {
  function tone_map_aces_hill (line 71) | pub fn tone_map_aces_hill(mut color: Vec3) -> Vec3 {
  function tone_map_reinhard (line 82) | pub fn tone_map_reinhard(color: Vec3) -> Vec3 {
  type Tonemap (line 88) | pub struct Tonemap(u32);
    constant NONE (line 91) | pub const NONE: Self = Tonemap(0);
    constant ACES_NARKOWICZ (line 92) | pub const ACES_NARKOWICZ: Self = Tonemap(1);
    constant ACES_HILL (line 93) | pub const ACES_HILL: Self = Tonemap(2);
    constant ACES_HILL_EXPOSURE_BOOST (line 94) | pub const ACES_HILL_EXPOSURE_BOOST: Self = Tonemap(3);
    constant REINHARD (line 95) | pub const REINHARD: Self = Tonemap(4);
  type TonemapConstants (line 100) | pub struct TonemapConstants {
  method default (line 106) | fn default() -> Self {
  function tonemap (line 114) | pub fn tonemap(mut color: Vec4, slab: &[u32]) -> Vec4 {
  constant QUAD_2D_POINTS (line 135) | const QUAD_2D_POINTS: [(Vec2, Vec2); 6] = {
  function tonemapping_vertex (line 144) | pub fn tonemapping_vertex(
  function tonemapping_fragment (line 155) | pub fn tonemapping_fragment(

FILE: crates/renderling/src/tonemapping/cpu.rs
  function bindgroup_layout (line 13) | pub fn bindgroup_layout(device: &wgpu::Device, label: Option<&str>) -> w...
  function create_bindgroup (line 50) | pub fn create_bindgroup(
  type Tonemapping (line 89) | pub struct Tonemapping {
    method new (line 98) | pub fn new(
    method slab_allocator (line 168) | pub(crate) fn slab_allocator(&self) -> &SlabAllocator<WgpuRuntime> {
    method set_hdr_texture (line 172) | pub fn set_hdr_texture(&self, device: &wgpu::Device, hdr_texture: &Tex...
    method get_tonemapping_config (line 185) | pub fn get_tonemapping_config(&self) -> TonemapConstants {
    method set_tonemapping_config (line 189) | pub fn set_tonemapping_config(&self, config: TonemapConstants) {
    method render (line 193) | pub fn render(&self, device: &wgpu::Device, queue: &wgpu::Queue, view:...

FILE: crates/renderling/src/transform.rs
  type TransformDescriptor (line 18) | pub struct TransformDescriptor {
    method from (line 31) | fn from(value: Mat4) -> Self {
    constant IDENTITY (line 54) | pub const IDENTITY: Self = TransformDescriptor {
  method default (line 25) | fn default() -> Self {
  method from (line 42) | fn from(
  function transform_roundtrip (line 70) | fn transform_roundtrip() {

FILE: crates/renderling/src/transform/cpu.rs
  type Transform (line 13) | pub struct Transform {
    method from (line 18) | fn from(value: &Transform) -> Self {
    method new (line 25) | pub(crate) fn new(slab: &SlabAllocator<impl IsRuntime>) -> Self {
    method id (line 31) | pub fn id(&self) -> Id<TransformDescriptor> {
    method descriptor (line 36) | pub fn descriptor(&self) -> TransformDescriptor {
    method set_descriptor (line 41) | pub fn set_descriptor(&self, descriptor: TransformDescriptor) -> &Self {
    method with_descriptor (line 47) | pub fn with_descriptor(self, descriptor: TransformDescriptor) -> Self {
    method as_mat4 (line 53) | pub fn as_mat4(&self) -> Mat4 {
    method translation (line 58) | pub fn translation(&self) -> Vec3 {
    method modify_translation (line 68) | pub fn modify_translation<T: 'static>(&self, f: impl FnOnce(&mut Vec3)...
    method set_translation (line 77) | pub fn set_translation(&self, translation: impl Into<Vec3>) -> &Self {
    method with_translation (line 88) | pub fn with_translation(self, translation: impl Into<Vec3>) -> Self {
    method rotation (line 94) | pub fn rotation(&self) -> Quat {
    method modify_rotation (line 104) | pub fn modify_rotation<T: 'static>(&self, f: impl FnOnce(&mut Quat) ->...
    method set_rotation (line 113) | pub fn set_rotation(&self, rotation: impl Into<Quat>) -> &Self {
    method with_rotation (line 123) | pub fn with_rotation(self, rotation: impl Into<Quat>) -> Self {
    method scale (line 129) | pub fn scale(&self) -> Vec3 {
    method modify_scale (line 139) | pub fn modify_scale<T: 'static>(&self, f: impl FnOnce(&mut Vec3) -> T)...
    method set_scale (line 148) | pub fn set_scale(&self, scale: impl Into<Vec3>) -> &Self {
    method with_scale (line 158) | pub fn with_scale(self, scale: impl Into<Vec3>) -> Self {
    method from (line 202) | fn from(value: &NestedTransform) -> Self {
    method from (line 208) | fn from(value: NestedTransform) -> Self {
  type NestedTransform (line 171) | pub struct NestedTransform {
    method fmt (line 179) | fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
    method new (line 215) | pub(crate) fn new(slab: &SlabAllocator<impl IsRuntime>) -> Self {
    method local_translation (line 227) | pub fn local_translation(&self) -> Vec3 {
    method modify_local_translation (line 240) | pub fn modify_local_translation<T>(&self, f: impl FnOnce(&mut Vec3) ->...
    method set_local_translation (line 254) | pub fn set_local_translation(&self, translation: impl Into<Vec3>) -> &...
    method with_local_translation (line 268) | pub fn with_local_translation(self, translation: impl Into<Vec3>) -> S...
    method local_rotation (line 274) | pub fn local_rotation(&self) -> Quat {
    method modify_local_rotation (line 287) | pub fn modify_local_rotation<T>(&self, f: impl FnOnce(&mut Quat) -> T)...
    method set_local_rotation (line 301) | pub fn set_local_rotation(&self, rotation: impl Into<Quat>) -> &Self {
    method with_local_rotation (line 315) | pub fn with_local_rotation(self, rotation: impl Into<Quat>) -> Self {
    method local_scale (line 321) | pub fn local_scale(&self) -> Vec3 {
    method modify_local_scale (line 334) | pub fn modify_local_scale<T>(&self, f: impl FnOnce(&mut Vec3) -> T) ->...
    method set_local_scale (line 348) | pub fn set_local_scale(&self, scale: impl Into<Vec3>) -> &Self {
    method with_local_scale (line 362) | pub fn with_local_scale(self, scale: impl Into<Vec3>) -> Self {
    method global_id (line 370) | pub fn global_id(&self) -> Id<TransformDescriptor> {
    method global_descriptor (line 377) | pub fn global_descriptor(&self) -> TransformDescriptor {
    method local_descriptor (line 388) | pub fn local_descriptor(&self) -> TransformDescriptor {
    method mark_dirty (line 392) | fn mark_dirty(&self) {
    method hierarchy (line 404) | pub fn hierarchy(&self) -> Vec<TransformDescriptor> {
    method add_child (line 413) | pub fn add_child(&self, node: &NestedTransform) {
    method remove_child (line 422) | pub fn remove_child(&self, node: &NestedTransform) {
    method parent (line 438) | pub fn parent(&self) -> Option<NestedTransform> {

FILE: crates/renderling/src/tutorial.rs
  function passthru_fragment (line 17) | pub fn passthru_fragment(in_color: Vec4, output: &mut Vec4) {
  function implicit_isosceles_vertex (line 26) | pub fn implicit_isosceles_vertex(
  function slabbed_vertices_no_instance (line 46) | pub fn slabbed_vertices_no_instance(
  function slabbed_vertices (line 65) | pub fn slabbed_vertices(
  function slabbed_renderlet (line 93) | pub fn slabbed_renderlet(

FILE: crates/renderling/src/types.rs
  type GpuOnly (line 7) | pub type GpuOnly = GpuContainer;
  type GpuOnlyArray (line 11) | pub type GpuOnlyArray = GpuArrayContainer;
  type GpuCpu (line 17) | pub type GpuCpu = HybridContainer;
  type GpuCpuArray (line 24) | pub type GpuCpuArray = HybridArrayContainer;

FILE: crates/renderling/src/ui_slab/mod.rs
  type UiElementType (line 20) | pub enum UiElementType {
    method from_u32 (line 37) | pub fn from_u32(v: u32) -> Self {
  type GradientType (line 53) | pub enum GradientType {
    method from_u32 (line 64) | pub fn from_u32(v: u32) -> Self {
  type GradientDescriptor (line 78) | pub struct GradientDescriptor {
  type UiVertex (line 100) | pub struct UiVertex {
    method with_position (line 110) | pub fn with_position(mut self, position: impl Into<Vec2>) -> Self {
    method with_uv (line 115) | pub fn with_uv(mut self, uv: impl Into<Vec2>) -> Self {
    method with_color (line 120) | pub fn with_color(mut self, color: impl Into<Vec4>) -> Self {
  type UiDrawCallDescriptor (line 133) | pub struct UiDrawCallDescriptor {
  type UiViewport (line 183) | pub struct UiViewport {

FILE: crates/renderling/src/ui_slab/shader.rs
  function sdf_rounded_rect (line 19) | fn sdf_rounded_rect(p: Vec2, half_ext: Vec2, radii: Vec4) -> f32 {
  function sdf_circle (line 43) | fn sdf_circle(p: Vec2, radius: f32) -> f32 {
  function sdf_ellipse (line 48) | fn sdf_ellipse(p: Vec2, radii: Vec2) -> f32 {
  function eval_gradient (line 56) | fn eval_gradient(
  function ui_vertex (line 101) | pub fn ui_vertex(
  function ui_fragment (line 165) | pub fn ui_fragment(

FILE: crates/renderling/tests/wasm.rs
  function can_write_system_info_artifact (line 25) | async fn can_write_system_info_artifact() {
  function can_create_headless_ctx (line 50) | async fn can_create_headless_ctx() {
  function stage_creation (line 57) | async fn stage_creation() {
  function image_from_bytes (line 64) | fn image_from_bytes(bytes: &[u8]) -> image::DynamicImage {
  function load_test_img (line 72) | async fn load_test_img(path: &str) -> image::DynamicImage {
  function image_to_wire (line 81) | fn image_to_wire(seen: impl Into<DynamicImage>) -> wire_types::Image {
  function assert_img_eq (line 98) | async fn assert_img_eq(filename: &str, seen: impl Into<image::DynamicIma...
  function save (line 113) | async fn save(filename: &str, seen: impl Into<image::DynamicImage>) {
  function can_load_image (line 129) | async fn can_load_image() {
  function can_img_diff (line 134) | async fn can_img_diff() {
  function can_clear_background_sanity (line 146) | async fn can_clear_background_sanity() {
  function implicit_isosceles_triangle (line 183) | async fn implicit_isosceles_triangle() {
  function slabbed_vertices_no_instance (line 297) | async fn slabbed_vertices_no_instance() {
  function slabbed_isosceles_triangle (line 452) | async fn slabbed_isosceles_triangle() {
  function can_clear_background (line 987) | async fn can_clear_background() {
  function right_tri_vertices (line 1008) | fn right_tri_vertices() -> Vec<Vertex> {
  function can_render_hello_triangle (line 1023) | async fn can_render_hello_triangle() {

FILE: crates/sandbox/src/main.rs
  type Example (line 14) | struct Example {
    method event (line 21) | fn event(&mut self, event: winit::event::WindowEvent) -> bool {
  type App (line 50) | struct App {
    method resumed (line 55) | fn resumed(&mut self, event_loop: &winit::event_loop::ActiveEventLoop) {
    method window_event (line 68) | fn window_event(
    method about_to_wait (line 81) | fn about_to_wait(&mut self, _event_loop: &winit::event_loop::ActiveEve...
  function main (line 90) | fn main() {

FILE: crates/wire-types/src/lib.rs
  type PixelType (line 3) | pub enum PixelType {
  type Image (line 10) | pub struct Image {
  type Error (line 18) | pub struct Error {
    method from (line 23) | fn from(description: String) -> Self {

FILE: crates/xtask/src/deps.rs
  function has_binary (line 5) | pub async fn has_binary(name: impl AsRef<str>) -> bool {
  function cargo_install (line 15) | pub async fn cargo_install(name: impl AsRef<str>) {

FILE: crates/xtask/src/main.rs
  type Command (line 8) | enum Command {
  type Manual (line 43) | pub struct Manual {
    method install_deps (line 62) | async fn install_deps() {
    method build_docs (line 71) | async fn build_docs() {
    method test (line 83) | async fn test() {
  type Cli (line 98) | struct Cli {
  function main (line 105) | async fn main() {

FILE: crates/xtask/src/server.rs
  function serve (line 19) | pub async fn serve() {
  function accept (line 37) | async fn accept(request: Request) -> Response {
  function static_file_inner (line 49) | async fn static_file_inner(
  function static_file (line 89) | async fn static_file(Path(path): Path<String>) -> Result<Response, Statu...
  function image_from_wire (line 94) | fn image_from_wire(img: wire_types::Image) -> Result<image::DynamicImage...
  function assert_img_eq_inner (line 110) | async fn assert_img_eq_inner(
  function assert_img_eq (line 130) | async fn assert_img_eq(
  function save_inner (line 150) | async fn save_inner(filename: &str, img: wire_types::Image) -> Result<()...
  function save (line 156) | async fn save(
  function artifact_inner (line 175) | async fn artifact_inner(filename: impl AsRef<std::path::Path>, body: Bod...
  function artifact (line 199) | async fn artifact(Path(parts): Path<Vec<String>>, body: Body) -> Response {
  function serve_docs (line 213) | pub async fn serve_docs() {
  function docs (line 223) | async fn docs(Path(path): Path<String>) -> impl IntoResponse {
Condensed preview — 283 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (1,497K chars).
[
  {
    "path": ".cargo/config.toml",
    "chars": 301,
    "preview": "[alias]\nxtask = \"run --package xtask --\"\nshaders = \"xtask compile-shaders\"\nlinkage = \"xtask generate-linkage\"\ntest-wasm "
  },
  {
    "path": ".gitattributes",
    "chars": 113,
    "preview": "*.txt text\n*.rs text\n*.md text\n*.yaml text\n*.spv binary\n*.wgsl linguist-generated=true binary\n* text=auto eol=lf\n"
  },
  {
    "path": ".github/FUNDING.yml",
    "chars": 812,
    "preview": "# These are supported funding model platforms\n\ngithub: [schell] # Replace with up to 4 GitHub Sponsors-enabled usernames"
  },
  {
    "path": ".github/workflows/push.yaml",
    "chars": 6283,
    "preview": "# Happens on push to main, and all PRs\nname: push\n\non: \n  push:\n    branches: \n      - main\n  pull_request:\n\nenv:\n  # Fo"
  },
  {
    "path": ".gitignore",
    "chars": 656,
    "preview": "# macOS\n*.DS_Store\n# will have compiled files and executables\n/target/\nshaders/target\nshaders/shader-crate/target\n**/spi"
  },
  {
    "path": ".helix/snippets/rust.json",
    "chars": 183,
    "preview": "{\n  \"anchor-snippet\": {\n    \"prefix\": \"anchor\",\n    \"body\": [\n      \"// ANCHOR: ${1:anchor}\",\n  \t  \"$2\",\n  \t  \"// ANCHOR"
  },
  {
    "path": "AGENTS.md",
    "chars": 847,
    "preview": "# AGENTS.md\n\n## Build & Test\n- Build: `cargo build -p renderling`\n- Test all: `cargo nextest run -j 1` or `cargo test`\n-"
  },
  {
    "path": "CONTRIBUTING.md",
    "chars": 2207,
    "preview": "# Contributing to Renderling\n\nThank you for your interest in contributing to Renderling!\n\n## Code of Conduct\n\nThis proje"
  },
  {
    "path": "Cargo.toml",
    "chars": 2417,
    "preview": "[workspace]\nmembers = [ \n    \"crates/example\", \n    \"crates/examples\",\n    \"crates/example-culling\",\n    #\"crates/exampl"
  },
  {
    "path": "LICENSE",
    "chars": 226,
    "preview": "Rendeling is dual-licensed under either\n\n* MIT License (docs/LICENSE-MIT or http://opensource.org/licenses/MIT)\n* Apache"
  },
  {
    "path": "NOTES.md",
    "chars": 5760,
    "preview": "# Notes\n\nJust pro-cons on tech choices and little things I don't want to forget whil implementing `renderling`.\n\n# gltf\n"
  },
  {
    "path": "README.md",
    "chars": 9148,
    "preview": "# <img style=\"image-rendering: pixelated; image-rendering: -moz-crisp-edges; image-rendering: crisp-edges;\" src=\"https:/"
  },
  {
    "path": "clippy.toml",
    "chars": 203,
    "preview": "disallowed-methods = [\n    \"glam::Vec2::normalize_or_zero\",\n    \"glam::Vec3::normalize_or_zero\",\n    \"glam::Vec4::normal"
  },
  {
    "path": "crates/example/Cargo.toml",
    "chars": 914,
    "preview": "[package]\nname = \"example\"\nversion = \"0.1.0\"\nedition = \"2021\"\n\n# See more keys and their definitions at https://doc.rust"
  },
  {
    "path": "crates/example/src/camera.rs",
    "chars": 9523,
    "preview": "//! Camera control.\nuse std::str::FromStr;\n\nuse renderling::{\n    bvol::Aabb,\n    camera::Camera,\n    glam::{Mat4, Quat,"
  },
  {
    "path": "crates/example/src/lib.rs",
    "chars": 18865,
    "preview": "//! Runs through all the gltf sample models to test and show-off renderling's\n//! gltf capabilities.\nuse std::{\n    coll"
  },
  {
    "path": "crates/example/src/main.rs",
    "chars": 4741,
    "preview": "//! Main entry point for the gltf viewer.\nuse std::sync::Arc;\n\nuse clap::Parser;\nuse example::{camera::CameraControl, Ap"
  },
  {
    "path": "crates/example/src/time.rs",
    "chars": 3457,
    "preview": "//! Time constructs.\n//!\n//! Because Instant::now() doesn't work on arch = wasm32.\npub use std::time::Duration;\n#[cfg(no"
  },
  {
    "path": "crates/example/src/utils.rs",
    "chars": 4530,
    "preview": "//! Example app utilities.\n\nuse std::sync::Arc;\n\nuse renderling::context::Context;\nuse winit::monitor::MonitorHandle;\n\np"
  },
  {
    "path": "crates/example-culling/Cargo.toml",
    "chars": 251,
    "preview": "[package]\nname = \"example-culling\"\nversion = \"0.1.0\"\nedition = \"2021\"\n\n[dependencies]\nenv_logger.workspace = true\nexampl"
  },
  {
    "path": "crates/example-culling/src/main.rs",
    "chars": 9750,
    "preview": "//! An example app showing (and verifying) how frustum culling works in\n//! `renderling`.\nuse std::sync::Arc;\n\nuse examp"
  },
  {
    "path": "crates/example-wasm/.gitignore",
    "chars": 5,
    "preview": "dist\n"
  },
  {
    "path": "crates/example-wasm/Cargo.toml",
    "chars": 727,
    "preview": "[package]\nname = \"example-wasm\"\nversion = \"0.1.0\"\nedition = \"2021\"\n\n# See more keys and their definitions at https://doc"
  },
  {
    "path": "crates/example-wasm/Trunk.toml",
    "chars": 22,
    "preview": "target = \"index.html\"\n"
  },
  {
    "path": "crates/example-wasm/index.html",
    "chars": 1355,
    "preview": "<!doctype html>\n<html>\n  <head>\n    <style>\n      /* A sane CSS reset from \n\t\t\t * https://www.digitalocean.com/community"
  },
  {
    "path": "crates/example-wasm/src/event.rs",
    "chars": 2509,
    "preview": "//! A light abstraction over UI event callbacks.\n//!\n//! This uses [`futures-lite::Stream`] to send events to downstream"
  },
  {
    "path": "crates/example-wasm/src/lib.rs",
    "chars": 3363,
    "preview": "#![allow(dead_code)]\nuse glam::{Vec2, Vec4};\nuse renderling::{camera::Camera, gltf::GltfDocument, stage::Stage, ui::prel"
  },
  {
    "path": "crates/example-wasm/src/req_animation_frame.rs",
    "chars": 2915,
    "preview": "//! Request animation frame helpers, taken from [mogwai](https://crates.io/crates/mogwai).\nuse std::{cell::RefCell, rc::"
  },
  {
    "path": "crates/examples/Cargo.toml",
    "chars": 348,
    "preview": "[package]\nname = \"examples\"\nversion = \"0.1.0\"\nedition = \"2024\"\n\n[dependencies]\ndoc-comment = \"0.3\"\nenv_logger.workspace "
  },
  {
    "path": "crates/examples/src/context.rs",
    "chars": 472,
    "preview": "//! Context manual page.\n\n#[tokio::test]\nasync fn context_page() {\n    // ANCHOR: create\n    use renderling::context::Co"
  },
  {
    "path": "crates/examples/src/gltf.rs",
    "chars": 1866,
    "preview": "//! GLTF manual page.\n\nuse crate::workspace_dir;\n\n#[tokio::test]\nasync fn manual_gltf() {\n    // ANCHOR: setup\n    use r"
  },
  {
    "path": "crates/examples/src/lib.rs",
    "chars": 1288,
    "preview": "//! # Examples for the manual\n//!\n//! This crate contains examples and snippets that get pulled into the manual\n//! via "
  },
  {
    "path": "crates/examples/src/lighting.rs",
    "chars": 6729,
    "preview": "//! Lighting examples.\n\nuse crate::{cwd_to_manual_assets_dir, workspace_dir};\n\n#[tokio::test]\nasync fn manual_lighting()"
  },
  {
    "path": "crates/examples/src/skybox.rs",
    "chars": 1891,
    "preview": "//! Skybox manual page.\n\nuse crate::{cwd_to_manual_assets_dir, workspace_dir};\n\n#[tokio::test]\npub async fn manual_skybo"
  },
  {
    "path": "crates/examples/src/stage.rs",
    "chars": 3327,
    "preview": "//! Stage manual page.\n\n#[tokio::test]\nasync fn manual_stage() {\n    let _ = env_logger::builder().try_init();\n\n    // A"
  },
  {
    "path": "crates/img-diff/Cargo.toml",
    "chars": 336,
    "preview": "[package]\nname = \"img-diff\"\nversion = \"0.1.0\"\nedition = \"2021\"\n\n# See more keys and their definitions at https://doc.rus"
  },
  {
    "path": "crates/img-diff/src/lib.rs",
    "chars": 9125,
    "preview": "//! Provides image diffing for testing.\nuse glam::{Vec3, Vec4, Vec4Swizzles};\nuse image::{DynamicImage, Luma, Rgb, Rgb32"
  },
  {
    "path": "crates/loading-bytes/Cargo.toml",
    "chars": 770,
    "preview": "[package]\nname = \"loading-bytes\"\nversion = \"0.1.1\"\nedition = \"2021\"\ndescription = \"Load bytes from paths on native and W"
  },
  {
    "path": "crates/loading-bytes/README.md",
    "chars": 891,
    "preview": "# loading-bytes\n\nSometimes loading things really bites. \n\nLet's say you just want to compile your program on native and "
  },
  {
    "path": "crates/loading-bytes/src/lib.rs",
    "chars": 7865,
    "preview": "//! Abstraction over loading bytes on WASM or other.\nuse snafu::prelude::*;\nuse wasm_bindgen::UnwrapThrowExt;\n\n#[derive("
  },
  {
    "path": "crates/renderling/Cargo.toml",
    "chars": 3395,
    "preview": "[package]\nname = \"renderling\"\nversion = \"0.6.0\"\nedition = \"2021\"\ndescription = \"User-friendly real-time rendering. 🍖\"\nre"
  },
  {
    "path": "crates/renderling/shaders/manifest.json",
    "chars": 9272,
    "preview": "[\n  {\n    \"source_path\": \"shaders/atlas-shader-atlas_blit_fragment.spv\",\n    \"entry_point\": \"atlas::shader::atlas_blit_f"
  },
  {
    "path": "crates/renderling/src/atlas/atlas_image.rs",
    "chars": 15271,
    "preview": "//! Images and texture formats.\n//!\n//! Used to represent textures before they are sent to the GPU.\nuse glam::UVec2;\nuse"
  },
  {
    "path": "crates/renderling/src/atlas/cpu.rs",
    "chars": 53559,
    "preview": "use core::{ops::Deref, sync::atomic::AtomicUsize};\nuse std::sync::{Arc, Mutex, RwLock};\n\nuse craballoc::{\n    prelude::{"
  },
  {
    "path": "crates/renderling/src/atlas/shader.rs",
    "chars": 5285,
    "preview": "use crabslab::{Id, Slab, SlabItem};\nuse glam::{UVec2, UVec3, Vec2, Vec3, Vec3Swizzles, Vec4, Vec4Swizzles};\nuse spirv_st"
  },
  {
    "path": "crates/renderling/src/atlas.rs",
    "chars": 6319,
    "preview": "//! Texture atlas.\n//!\n//! All images are packed into an atlas at staging time.\n//! Texture descriptors describe where i"
  },
  {
    "path": "crates/renderling/src/bindgroup.rs",
    "chars": 1731,
    "preview": "//! A helper wrapper around `Arc<RwLock<Arc<wgpu::BindGroup>>>` that provides\n//! invalidation.\n\nuse std::sync::{Arc, Rw"
  },
  {
    "path": "crates/renderling/src/bloom/cpu.rs",
    "chars": 29536,
    "preview": "//! Bloom.\nuse core::ops::Deref;\nuse std::sync::{Arc, RwLock};\n\nuse craballoc::{\n    prelude::{Hybrid, HybridArray, Slab"
  },
  {
    "path": "crates/renderling/src/bloom/shader.rs",
    "chars": 6865,
    "preview": "use crabslab::{Id, Slab};\nuse glam::{Vec2, Vec4, Vec4Swizzles};\nuse spirv_std::{image::Image2d, spirv, Sampler};\n\n/// Bl"
  },
  {
    "path": "crates/renderling/src/bloom.rs",
    "chars": 283,
    "preview": "//! Physically based bloom.\n//!\n//! As described in [learnopengl.com's Physically Based Bloom\n//! article](https://learn"
  },
  {
    "path": "crates/renderling/src/build.rs",
    "chars": 476,
    "preview": "//! Generates linkage for shaders and sets up cfg aliases.\n\nfn main() {\n    if std::env::var(\"CARGO_CFG_TARGET_ARCH\").as"
  },
  {
    "path": "crates/renderling/src/bvol.rs",
    "chars": 27136,
    "preview": "//! Bounding volumes and culling primitives.\n//!\n//! The initial implementation here was gleaned from `treeculler`, whic"
  },
  {
    "path": "crates/renderling/src/camera/cpu.rs",
    "chars": 6119,
    "preview": "//! CPU side of [crate::camera].\n\nuse craballoc::{runtime::IsRuntime, slab::SlabAllocator, value::Hybrid};\nuse crabslab:"
  },
  {
    "path": "crates/renderling/src/camera/shader.rs",
    "chars": 5031,
    "preview": "//! [`CameraDescriptor`] and camera shader utilities.\n\nuse crabslab::SlabItem;\nuse glam::{Mat4, Vec2, Vec3, Vec4};\n\nuse "
  },
  {
    "path": "crates/renderling/src/camera.rs",
    "chars": 3840,
    "preview": "//! Camera projection, view and utilities.\nuse glam::{Mat4, Vec3};\n\n#[cfg(cpu)]\nmod cpu;\n#[cfg(cpu)]\npub use cpu::*;\n\npu"
  },
  {
    "path": "crates/renderling/src/color.rs",
    "chars": 3907,
    "preview": "//! Color utils.\n\nuse glam::Vec4;\n#[cfg(target_arch = \"spirv\")]\nuse spirv_std::num_traits::Float;\n\n/// Applies a linear "
  },
  {
    "path": "crates/renderling/src/compositor/cpu.rs",
    "chars": 6152,
    "preview": "//! CPU-side compositor for alpha-blending a source texture onto a target.\n\n/// Alpha-blends a source texture onto a tar"
  },
  {
    "path": "crates/renderling/src/compositor.rs",
    "chars": 1371,
    "preview": "//! Compositor for alpha-blending a source texture onto a target framebuffer.\n//!\n//! This is used by the `renderling-ui"
  },
  {
    "path": "crates/renderling/src/context.rs",
    "chars": 21460,
    "preview": "//! Rendering context initialization\n//!\n//! This module contains [`Context`] initialization and frame management.\n//! T"
  },
  {
    "path": "crates/renderling/src/convolution.rs",
    "chars": 11423,
    "preview": "//! Convolution shaders.\n//!\n//! These shaders convolve various functions to produce cached maps.\n\npub mod shader {\n    "
  },
  {
    "path": "crates/renderling/src/cubemap/cpu.rs",
    "chars": 24476,
    "preview": "//! CPU side of the cubemap module.\nuse std::sync::Arc;\n\nuse glam::{Mat4, UVec2, Vec3, Vec4};\nuse image::GenericImageVie"
  },
  {
    "path": "crates/renderling/src/cubemap/shader.rs",
    "chars": 5732,
    "preview": "use crabslab::{Array, Id, Slab};\nuse glam::{Mat4, Vec2, Vec3, Vec3Swizzles, Vec4};\nuse spirv_std::{num_traits::Zero, spi"
  },
  {
    "path": "crates/renderling/src/cubemap.rs",
    "chars": 1623,
    "preview": "//! Cubemap utilities.\n//!\n//! Shaders, render pipelines and layouts for creating and sampling cubemaps.\n//!\n//! For mor"
  },
  {
    "path": "crates/renderling/src/cull/cpu.rs",
    "chars": 37684,
    "preview": "//! CPU side of compute culling.\n\nuse craballoc::{\n    prelude::{GpuArray, Hybrid, SlabAllocator, SlabAllocatorError},\n "
  },
  {
    "path": "crates/renderling/src/cull/shader.rs",
    "chars": 9088,
    "preview": "use crabslab::{Array, Id, Slab, SlabItem};\nuse glam::{UVec2, UVec3, Vec2, Vec3Swizzles};\n#[allow(unused_imports)]\nuse sp"
  },
  {
    "path": "crates/renderling/src/cull.rs",
    "chars": 257,
    "preview": "//! Compute based culling.\n//!\n//! Frustum culling as explained in\n//! [the vulkan guide](https://vkguide.dev/docs/gpudr"
  },
  {
    "path": "crates/renderling/src/debug/cpu.rs",
    "chars": 6330,
    "preview": "//! CPU side of drawing debugging overlays.\n\nuse std::sync::{Arc, Mutex};\n\n#[derive(Clone)]\npub struct DebugOverlay {\n  "
  },
  {
    "path": "crates/renderling/src/debug.rs",
    "chars": 3327,
    "preview": "//! Debug overlay.\n#[cfg(cpu)]\nmod cpu;\n#[cfg(cpu)]\npub use cpu::*;\n\npub mod shader {\n    use crabslab::{Id, Slab};\n    "
  },
  {
    "path": "crates/renderling/src/draw/cpu.rs",
    "chars": 10640,
    "preview": "//! CPU-only side of renderling/draw.rs\n\nuse craballoc::{\n    prelude::{Gpu, SlabAllocator, WgpuRuntime},\n    slab::Slab"
  },
  {
    "path": "crates/renderling/src/draw.rs",
    "chars": 698,
    "preview": "//! Handles queueing draw calls.\n//!\n//! [`DrawCalls`] is used to maintain the list of all staged\n//! [`PrimitiveDescrip"
  },
  {
    "path": "crates/renderling/src/geometry/cpu.rs",
    "chars": 16039,
    "preview": "//! CPU side of the [super::geometry](geometry) module.\nuse std::sync::{Arc, Mutex};\n\nuse craballoc::{\n    runtime::{IsR"
  },
  {
    "path": "crates/renderling/src/geometry/shader.rs",
    "chars": 2984,
    "preview": "use crabslab::{Array, Id, Slab, SlabItem};\nuse glam::Mat4;\n\nuse crate::{\n    camera::shader::CameraDescriptor, geometry:"
  },
  {
    "path": "crates/renderling/src/geometry.rs",
    "chars": 3760,
    "preview": "//! Types and functions for staging geometry.\nuse crate::math::IsVector;\nuse crabslab::SlabItem;\n\n#[cfg(cpu)]\nmod cpu;\n#"
  },
  {
    "path": "crates/renderling/src/gltf/anime.rs",
    "chars": 29802,
    "preview": "//! Animation helpers for gltf.\nuse glam::{Quat, Vec3};\nuse snafu::prelude::*;\n\nuse crate::{geometry::MorphTargetWeights"
  },
  {
    "path": "crates/renderling/src/gltf.rs",
    "chars": 60119,
    "preview": "//! GLTF support.\n//!\n//! # Loading GLTF files\n//!\n//! Loading GLTF files is accomplished through\n//! [`Stage::load_gltf"
  },
  {
    "path": "crates/renderling/src/internal/cpu.rs",
    "chars": 6166,
    "preview": "//! Internal CPU utilities and stuff.\nuse std::sync::Arc;\n\nuse snafu::{OptionExt, ResultExt};\n\nuse crate::context::{\n   "
  },
  {
    "path": "crates/renderling/src/internal.rs",
    "chars": 388,
    "preview": "//! Internal types and functions.\n//!\n//! ## Note\n//! The types and functions exposed by this module are used internally"
  },
  {
    "path": "crates/renderling/src/lib.rs",
    "chars": 42134,
    "preview": "//! <div style=\"float: right; padding: 1em;\">\n//!    <img\n//!       style=\"image-rendering: pixelated; image-rendering: "
  },
  {
    "path": "crates/renderling/src/light/cpu/test.rs",
    "chars": 39588,
    "preview": "//! Tests of the lighting system.\n\nuse glam::{Vec3, Vec4, Vec4Swizzles};\n\nuse spirv_std::num_traits::Zero;\n\nuse crate::{"
  },
  {
    "path": "crates/renderling/src/light/cpu.rs",
    "chars": 34629,
    "preview": "//! CPU-only lighting and shadows.\nuse std::sync::{Arc, RwLock};\n\n#[cfg(doc)]\nuse crate::stage::Stage;\nuse craballoc::{\n"
  },
  {
    "path": "crates/renderling/src/light/shader.rs",
    "chars": 50947,
    "preview": "//! Shader functions for the lighting system.\n//!\n//! Directional lights are in lux, spot and point lights are in\n//! ca"
  },
  {
    "path": "crates/renderling/src/light/shadow_map.rs",
    "chars": 28425,
    "preview": "//! Shadow mapping.\n\nuse core::{ops::Deref, sync::atomic::AtomicUsize};\nuse std::sync::Arc;\n\nuse craballoc::{\n    prelud"
  },
  {
    "path": "crates/renderling/src/light/tiling.rs",
    "chars": 20944,
    "preview": "//! This module implements light tiling, a technique used in rendering to\n//! efficiently manage and apply lighting effe"
  },
  {
    "path": "crates/renderling/src/light.rs",
    "chars": 11294,
    "preview": "//! Lighting effects.\n//!\n//! This module includes support for various types of lights such as\n//! directional, point, a"
  },
  {
    "path": "crates/renderling/src/linkage/atlas_blit_fragment.rs",
    "chars": 1347,
    "preview": "#![allow(dead_code)]\n//! Automatically generated by Renderling's `build.rs`.\nuse crate::linkage::ShaderLinkage;\n#[cfg(no"
  },
  {
    "path": "crates/renderling/src/linkage/atlas_blit_vertex.rs",
    "chars": 1335,
    "preview": "#![allow(dead_code)]\n//! Automatically generated by Renderling's `build.rs`.\nuse crate::linkage::ShaderLinkage;\n#[cfg(no"
  },
  {
    "path": "crates/renderling/src/linkage/bloom_downsample_fragment.rs",
    "chars": 1417,
    "preview": "#![allow(dead_code)]\n//! Automatically generated by Renderling's `build.rs`.\nuse crate::linkage::ShaderLinkage;\n#[cfg(no"
  },
  {
    "path": "crates/renderling/src/linkage/bloom_mix_fragment.rs",
    "chars": 1341,
    "preview": "#![allow(dead_code)]\n//! Automatically generated by Renderling's `build.rs`.\nuse crate::linkage::ShaderLinkage;\n#[cfg(no"
  },
  {
    "path": "crates/renderling/src/linkage/bloom_upsample_fragment.rs",
    "chars": 1371,
    "preview": "#![allow(dead_code)]\n//! Automatically generated by Renderling's `build.rs`.\nuse crate::linkage::ShaderLinkage;\n#[cfg(no"
  },
  {
    "path": "crates/renderling/src/linkage/bloom_vertex.rs",
    "chars": 1305,
    "preview": "#![allow(dead_code)]\n//! Automatically generated by Renderling's `build.rs`.\nuse crate::linkage::ShaderLinkage;\n#[cfg(no"
  },
  {
    "path": "crates/renderling/src/linkage/brdf_lut_convolution_fragment.rs",
    "chars": 1499,
    "preview": "#![allow(dead_code)]\n//! Automatically generated by Renderling's `build.rs`.\nuse crate::linkage::ShaderLinkage;\n#[cfg(no"
  },
  {
    "path": "crates/renderling/src/linkage/brdf_lut_convolution_vertex.rs",
    "chars": 1453,
    "preview": "#![allow(dead_code)]\n//! Automatically generated by Renderling's `build.rs`.\nuse crate::linkage::ShaderLinkage;\n#[cfg(no"
  },
  {
    "path": "crates/renderling/src/linkage/compositor_fragment.rs",
    "chars": 1339,
    "preview": "#![allow(dead_code)]\n//! Automatically generated by Renderling's `build.rs`.\nuse crate::linkage::ShaderLinkage;\n#[cfg(no"
  },
  {
    "path": "crates/renderling/src/linkage/compositor_vertex.rs",
    "chars": 1327,
    "preview": "#![allow(dead_code)]\n//! Automatically generated by Renderling's `build.rs`.\nuse crate::linkage::ShaderLinkage;\n#[cfg(no"
  },
  {
    "path": "crates/renderling/src/linkage/compute_copy_depth_to_pyramid.rs",
    "chars": 1471,
    "preview": "#![allow(dead_code)]\n//! Automatically generated by Renderling's `build.rs`.\nuse crate::linkage::ShaderLinkage;\n#[cfg(no"
  },
  {
    "path": "crates/renderling/src/linkage/compute_copy_depth_to_pyramid_multisampled.rs",
    "chars": 1593,
    "preview": "#![allow(dead_code)]\n//! Automatically generated by Renderling's `build.rs`.\nuse crate::linkage::ShaderLinkage;\n#[cfg(no"
  },
  {
    "path": "crates/renderling/src/linkage/compute_culling.rs",
    "chars": 1319,
    "preview": "#![allow(dead_code)]\n//! Automatically generated by Renderling's `build.rs`.\nuse crate::linkage::ShaderLinkage;\n#[cfg(no"
  },
  {
    "path": "crates/renderling/src/linkage/compute_downsample_depth_pyramid.rs",
    "chars": 1489,
    "preview": "#![allow(dead_code)]\n//! Automatically generated by Renderling's `build.rs`.\nuse crate::linkage::ShaderLinkage;\n#[cfg(no"
  },
  {
    "path": "crates/renderling/src/linkage/cubemap_sampling_test_fragment.rs",
    "chars": 1489,
    "preview": "#![allow(dead_code)]\n//! Automatically generated by Renderling's `build.rs`.\nuse crate::linkage::ShaderLinkage;\n#[cfg(no"
  },
  {
    "path": "crates/renderling/src/linkage/cubemap_sampling_test_vertex.rs",
    "chars": 1477,
    "preview": "#![allow(dead_code)]\n//! Automatically generated by Renderling's `build.rs`.\nuse crate::linkage::ShaderLinkage;\n#[cfg(no"
  },
  {
    "path": "crates/renderling/src/linkage/debug_overlay_fragment.rs",
    "chars": 1365,
    "preview": "#![allow(dead_code)]\n//! Automatically generated by Renderling's `build.rs`.\nuse crate::linkage::ShaderLinkage;\n#[cfg(no"
  },
  {
    "path": "crates/renderling/src/linkage/debug_overlay_vertex.rs",
    "chars": 1353,
    "preview": "#![allow(dead_code)]\n//! Automatically generated by Renderling's `build.rs`.\nuse crate::linkage::ShaderLinkage;\n#[cfg(no"
  },
  {
    "path": "crates/renderling/src/linkage/di_convolution_fragment.rs",
    "chars": 1379,
    "preview": "#![allow(dead_code)]\n//! Automatically generated by Renderling's `build.rs`.\nuse crate::linkage::ShaderLinkage;\n#[cfg(no"
  },
  {
    "path": "crates/renderling/src/linkage/generate_mipmap_fragment.rs",
    "chars": 1401,
    "preview": "#![allow(dead_code)]\n//! Automatically generated by Renderling's `build.rs`.\nuse crate::linkage::ShaderLinkage;\n#[cfg(no"
  },
  {
    "path": "crates/renderling/src/linkage/generate_mipmap_vertex.rs",
    "chars": 1389,
    "preview": "#![allow(dead_code)]\n//! Automatically generated by Renderling's `build.rs`.\nuse crate::linkage::ShaderLinkage;\n#[cfg(no"
  },
  {
    "path": "crates/renderling/src/linkage/implicit_isosceles_vertex.rs",
    "chars": 1401,
    "preview": "#![allow(dead_code)]\n//! Automatically generated by Renderling's `build.rs`.\nuse crate::linkage::ShaderLinkage;\n#[cfg(no"
  },
  {
    "path": "crates/renderling/src/linkage/light_tiling_bin_lights.rs",
    "chars": 1371,
    "preview": "#![allow(dead_code)]\n//! Automatically generated by Renderling's `build.rs`.\nuse crate::linkage::ShaderLinkage;\n#[cfg(no"
  },
  {
    "path": "crates/renderling/src/linkage/light_tiling_clear_tiles.rs",
    "chars": 1377,
    "preview": "#![allow(dead_code)]\n//! Automatically generated by Renderling's `build.rs`.\nuse crate::linkage::ShaderLinkage;\n#[cfg(no"
  },
  {
    "path": "crates/renderling/src/linkage/light_tiling_compute_tile_min_and_max_depth.rs",
    "chars": 1603,
    "preview": "#![allow(dead_code)]\n//! Automatically generated by Renderling's `build.rs`.\nuse crate::linkage::ShaderLinkage;\n#[cfg(no"
  },
  {
    "path": "crates/renderling/src/linkage/light_tiling_compute_tile_min_and_max_depth_multisampled.rs",
    "chars": 1727,
    "preview": "#![allow(dead_code)]\n//! Automatically generated by Renderling's `build.rs`.\nuse crate::linkage::ShaderLinkage;\n#[cfg(no"
  },
  {
    "path": "crates/renderling/src/linkage/light_tiling_depth_pre_pass.rs",
    "chars": 1429,
    "preview": "#![allow(dead_code)]\n//! Automatically generated by Renderling's `build.rs`.\nuse crate::linkage::ShaderLinkage;\n#[cfg(no"
  },
  {
    "path": "crates/renderling/src/linkage/passthru_fragment.rs",
    "chars": 1319,
    "preview": "#![allow(dead_code)]\n//! Automatically generated by Renderling's `build.rs`.\nuse crate::linkage::ShaderLinkage;\n#[cfg(no"
  },
  {
    "path": "crates/renderling/src/linkage/prefilter_environment_cubemap_fragment.rs",
    "chars": 1597,
    "preview": "#![allow(dead_code)]\n//! Automatically generated by Renderling's `build.rs`.\nuse crate::linkage::ShaderLinkage;\n#[cfg(no"
  },
  {
    "path": "crates/renderling/src/linkage/prefilter_environment_cubemap_vertex.rs",
    "chars": 1585,
    "preview": "#![allow(dead_code)]\n//! Automatically generated by Renderling's `build.rs`.\nuse crate::linkage::ShaderLinkage;\n#[cfg(no"
  },
  {
    "path": "crates/renderling/src/linkage/primitive_fragment.rs",
    "chars": 1357,
    "preview": "#![allow(dead_code)]\n//! Automatically generated by Renderling's `build.rs`.\nuse crate::linkage::ShaderLinkage;\n#[cfg(no"
  },
  {
    "path": "crates/renderling/src/linkage/primitive_vertex.rs",
    "chars": 1345,
    "preview": "#![allow(dead_code)]\n//! Automatically generated by Renderling's `build.rs`.\nuse crate::linkage::ShaderLinkage;\n#[cfg(no"
  },
  {
    "path": "crates/renderling/src/linkage/shadow_mapping_fragment.rs",
    "chars": 1371,
    "preview": "#![allow(dead_code)]\n//! Automatically generated by Renderling's `build.rs`.\nuse crate::linkage::ShaderLinkage;\n#[cfg(no"
  },
  {
    "path": "crates/renderling/src/linkage/shadow_mapping_vertex.rs",
    "chars": 1359,
    "preview": "#![allow(dead_code)]\n//! Automatically generated by Renderling's `build.rs`.\nuse crate::linkage::ShaderLinkage;\n#[cfg(no"
  },
  {
    "path": "crates/renderling/src/linkage/skybox_cubemap_fragment.rs",
    "chars": 1375,
    "preview": "#![allow(dead_code)]\n//! Automatically generated by Renderling's `build.rs`.\nuse crate::linkage::ShaderLinkage;\n#[cfg(no"
  },
  {
    "path": "crates/renderling/src/linkage/skybox_cubemap_vertex.rs",
    "chars": 1363,
    "preview": "#![allow(dead_code)]\n//! Automatically generated by Renderling's `build.rs`.\nuse crate::linkage::ShaderLinkage;\n#[cfg(no"
  },
  {
    "path": "crates/renderling/src/linkage/skybox_equirectangular_fragment.rs",
    "chars": 1491,
    "preview": "#![allow(dead_code)]\n//! Automatically generated by Renderling's `build.rs`.\nuse crate::linkage::ShaderLinkage;\n#[cfg(no"
  },
  {
    "path": "crates/renderling/src/linkage/skybox_vertex.rs",
    "chars": 1315,
    "preview": "#![allow(dead_code)]\n//! Automatically generated by Renderling's `build.rs`.\nuse crate::linkage::ShaderLinkage;\n#[cfg(no"
  },
  {
    "path": "crates/renderling/src/linkage/slabbed_renderlet.rs",
    "chars": 1319,
    "preview": "#![allow(dead_code)]\n//! Automatically generated by Renderling's `build.rs`.\nuse crate::linkage::ShaderLinkage;\n#[cfg(no"
  },
  {
    "path": "crates/renderling/src/linkage/slabbed_vertices.rs",
    "chars": 1313,
    "preview": "#![allow(dead_code)]\n//! Automatically generated by Renderling's `build.rs`.\nuse crate::linkage::ShaderLinkage;\n#[cfg(no"
  },
  {
    "path": "crates/renderling/src/linkage/slabbed_vertices_no_instance.rs",
    "chars": 1453,
    "preview": "#![allow(dead_code)]\n//! Automatically generated by Renderling's `build.rs`.\nuse crate::linkage::ShaderLinkage;\n#[cfg(no"
  },
  {
    "path": "crates/renderling/src/linkage/tonemapping_fragment.rs",
    "chars": 1349,
    "preview": "#![allow(dead_code)]\n//! Automatically generated by Renderling's `build.rs`.\nuse crate::linkage::ShaderLinkage;\n#[cfg(no"
  },
  {
    "path": "crates/renderling/src/linkage/tonemapping_vertex.rs",
    "chars": 1337,
    "preview": "#![allow(dead_code)]\n//! Automatically generated by Renderling's `build.rs`.\nuse crate::linkage::ShaderLinkage;\n#[cfg(no"
  },
  {
    "path": "crates/renderling/src/linkage/ui_fragment.rs",
    "chars": 1307,
    "preview": "#![allow(dead_code)]\n//! Automatically generated by Renderling's `build.rs`.\nuse crate::linkage::ShaderLinkage;\n#[cfg(no"
  },
  {
    "path": "crates/renderling/src/linkage/ui_vertex.rs",
    "chars": 1295,
    "preview": "#![allow(dead_code)]\n//! Automatically generated by Renderling's `build.rs`.\nuse crate::linkage::ShaderLinkage;\n#[cfg(no"
  },
  {
    "path": "crates/renderling/src/linkage.rs",
    "chars": 6596,
    "preview": "//! Provides convenient wrappers around renderling shader linkage.\n//!\n//! For internal use.\n// # Warning!\n// Please don"
  },
  {
    "path": "crates/renderling/src/material/cpu.rs",
    "chars": 16976,
    "preview": "//! CPU side of materials.\nuse std::sync::{Arc, Mutex};\n\nuse craballoc::{\n    // Craballoc is used for memory allocation"
  },
  {
    "path": "crates/renderling/src/material.rs",
    "chars": 2013,
    "preview": "//! Atlas images, used for materials. CPU and GPU.\n\n#[cfg(cpu)]\nmod cpu;\n#[cfg(cpu)]\npub use cpu::*;\n\npub mod shader {\n "
  },
  {
    "path": "crates/renderling/src/math.rs",
    "chars": 24141,
    "preview": "//! Mathematical helper types and functions.\n//!\n//! Primarily this module adds some traits to help using `glam` types o"
  },
  {
    "path": "crates/renderling/src/mesh.rs",
    "chars": 3173,
    "preview": "//! Sometimes you just need a mesh.\nuse wgpu::util::DeviceExt;\n\n/// A vertex buffer.\npub struct Mesh {\n    vertex_buffer"
  },
  {
    "path": "crates/renderling/src/pbr/brdf/cpu.rs",
    "chars": 5297,
    "preview": "//! CPU side of BRDF stuff.\nuse craballoc::runtime::WgpuRuntime;\n\nuse crate::texture;\n\n/// Pre-computed texture of the b"
  },
  {
    "path": "crates/renderling/src/pbr/brdf/shader.rs",
    "chars": 556,
    "preview": "//! Shader side of BRDF stuff.\n\nuse glam::{Vec2, Vec3, Vec4Swizzles};\n\nuse crate::math::{IsSampler, IsVector, Sample2d};"
  },
  {
    "path": "crates/renderling/src/pbr/brdf.rs",
    "chars": 193,
    "preview": "//! BRDF computation.\n//!\n//! Helpers for computing (and holding onto) a Bidirectional Reflectance\n//! Distribution Func"
  },
  {
    "path": "crates/renderling/src/pbr/debug.rs",
    "chars": 1934,
    "preview": "//! Debugging helpers.\nuse crabslab::SlabItem;\n\n/// Used to debug shaders by early exiting the shader and attempting to "
  },
  {
    "path": "crates/renderling/src/pbr/ibl/cpu.rs",
    "chars": 21944,
    "preview": "//! CPU side of IBL\n\nuse core::sync::atomic::AtomicBool;\nuse std::sync::Arc;\n\nuse craballoc::{runtime::WgpuRuntime, slab"
  },
  {
    "path": "crates/renderling/src/pbr/ibl/diffuse_irradiance.rs",
    "chars": 312,
    "preview": "//! Diffuse irradiance convolution.\n\nuse glam::{Vec3, Vec4, Vec4Swizzles};\n#[cfg(target_arch = \"spirv\")]\nuse spirv_std::"
  },
  {
    "path": "crates/renderling/src/pbr/ibl/shader.rs",
    "chars": 1762,
    "preview": "//! Shader side of IBL\nuse glam::{Vec3, Vec4, Vec4Swizzles};\n#[cfg(gpu)]\nuse spirv_std::num_traits::Float;\nuse spirv_std"
  },
  {
    "path": "crates/renderling/src/pbr/ibl.rs",
    "chars": 197,
    "preview": "//! Image based lighting\n//!\n//! For more info on image based lighting, see <https://learnopengl.com/PBR/IBL/Diffuse-irr"
  },
  {
    "path": "crates/renderling/src/pbr/shader.rs",
    "chars": 22119,
    "preview": "//! Physically based shader code.\nuse crabslab::{Id, Slab};\nuse glam::{Mat4, Vec2, Vec3, Vec4, Vec4Swizzles};\n\n#[allow(u"
  },
  {
    "path": "crates/renderling/src/pbr.rs",
    "chars": 3945,
    "preview": "//! \"Physically based\" types and functions.\n//!\n//! ## References\n//! * <https://learnopengl.com/PBR/Theory>\n//! * <http"
  },
  {
    "path": "crates/renderling/src/primitive/cpu.rs",
    "chars": 10601,
    "preview": "//! Mesh primitives.\n\nuse core::ops::Deref;\nuse std::sync::{Arc, Mutex};\n\nuse craballoc::value::Hybrid;\nuse crabslab::{A"
  },
  {
    "path": "crates/renderling/src/primitive/shader.rs",
    "chars": 10686,
    "preview": "//! Shader support for rendering primitives.\nuse crabslab::{Array, Id, Slab, SlabItem};\nuse glam::{Mat4, Vec2, Vec3, Vec"
  },
  {
    "path": "crates/renderling/src/primitive.rs",
    "chars": 87,
    "preview": "//! Mesh primitives\n\n#[cfg(cpu)]\nmod cpu;\n#[cfg(cpu)]\npub use cpu::*;\n\npub mod shader;\n"
  },
  {
    "path": "crates/renderling/src/sdf.rs",
    "chars": 2383,
    "preview": "//! SDF functions for use in shaders.\n//!\n//! For more info, see these great articles:\n//! - <https://iquilezles.org/art"
  },
  {
    "path": "crates/renderling/src/skybox/cpu.rs",
    "chars": 12074,
    "preview": "//! CPU-side code for skybox rendering.\nuse core::sync::atomic::AtomicBool;\nuse std::sync::Arc;\n\nuse craballoc::{prelude"
  },
  {
    "path": "crates/renderling/src/skybox/shader.rs",
    "chars": 3000,
    "preview": "//! Skybox shaders.\n\nuse crabslab::{Id, Slab};\nuse glam::{Mat3, Mat4, Vec2, Vec3, Vec4, Vec4Swizzles};\nuse spirv_std::{\n"
  },
  {
    "path": "crates/renderling/src/skybox.rs",
    "chars": 158,
    "preview": "//! Rendering skylines at infinite distances.\n#[cfg(not(target_arch = \"spirv\"))]\nmod cpu;\n#[cfg(not(target_arch = \"spirv"
  },
  {
    "path": "crates/renderling/src/stage/cpu.rs",
    "chars": 73676,
    "preview": "//! GPU staging area.\nuse core::{\n    ops::Deref,\n    sync::atomic::{AtomicU32, AtomicUsize, Ordering},\n};\nuse craballoc"
  },
  {
    "path": "crates/renderling/src/stage.rs",
    "chars": 3012,
    "preview": "//! Scene staging.\n//!\n//! The [`Stage`] is the entrypoint for staging data on the GPU and\n//! interacting with lighting"
  },
  {
    "path": "crates/renderling/src/sync.rs",
    "chars": 1668,
    "preview": "//! GPU locks and atomics.\n\nuse crabslab::Id;\n\n/// Perform an [atomic_i_increment](spirv_std::arch::atomic_i_increment)\n"
  },
  {
    "path": "crates/renderling/src/texture/mips.rs",
    "chars": 7185,
    "preview": "//! Mip-map generation.\n\nuse crate::texture::Texture;\nuse craballoc::runtime::WgpuRuntime;\nuse snafu::Snafu;\n\nuse super:"
  },
  {
    "path": "crates/renderling/src/texture.rs",
    "chars": 45204,
    "preview": "//! Wrapper around [`wgpu::Texture`].\nuse core::sync::atomic::AtomicUsize;\nuse std::{\n    ops::Deref,\n    sync::{Arc, La"
  },
  {
    "path": "crates/renderling/src/tonemapping/cpu.rs",
    "chars": 8056,
    "preview": "//! Tonemapping.\nuse core::ops::Deref;\nuse craballoc::{\n    prelude::{Hybrid, SlabAllocator},\n    runtime::WgpuRuntime,\n"
  },
  {
    "path": "crates/renderling/src/tonemapping.rs",
    "chars": 5215,
    "preview": "//! Tonemapping from an HDR texture to SDR.\n//!\n//! ## References\n//! * <https://github.com/KhronosGroup/glTF-Sample-Vie"
  },
  {
    "path": "crates/renderling/src/transform/cpu.rs",
    "chars": 13353,
    "preview": "//! CPU side of transform.\n\nuse std::sync::{Arc, RwLock};\n\nuse craballoc::{runtime::IsRuntime, slab::SlabAllocator, valu"
  },
  {
    "path": "crates/renderling/src/transform.rs",
    "chars": 2263,
    "preview": "//! Decomposed 3d transforms and hierarchies.\n\n#[cfg(cpu)]\nmod cpu;\n#[cfg(cpu)]\npub use cpu::*;\n\npub mod shader {\n    us"
  },
  {
    "path": "crates/renderling/src/tutorial/implicit_isosceles_vertex.wgsl",
    "chars": 403,
    "preview": "struct VertexOutput {\n    @location(0) color: vec4<f32>,\n    @builtin(position) clip_pos: vec4<f32>,\n}\n@vertex \nfn main("
  },
  {
    "path": "crates/renderling/src/tutorial/passthru.wgsl",
    "chars": 157,
    "preview": "// Pass-through fragment shader that copies in color to out.\n@fragment\nfn main(@location(0) color:vec4<f32>) -> @locatio"
  },
  {
    "path": "crates/renderling/src/tutorial.rs",
    "chars": 4199,
    "preview": "//! Shaders used in the contributor intro tutorial and in WASM tests.\n\nuse crabslab::{Array, Id, Slab, SlabItem};\nuse gl"
  },
  {
    "path": "crates/renderling/src/types.rs",
    "chars": 939,
    "preview": "//! Type level machinery.\n\nuse craballoc::value::{GpuArrayContainer, GpuContainer, HybridArrayContainer, HybridContainer"
  },
  {
    "path": "crates/renderling/src/ui_slab/mod.rs",
    "chars": 6105,
    "preview": "//! Shared types for the 2D/UI rendering pipeline.\n//!\n//! These types are used by both the CPU (renderling-ui crate) an"
  },
  {
    "path": "crates/renderling/src/ui_slab/shader.rs",
    "chars": 10981,
    "preview": "//! GPU shader entry points for the 2D/UI rendering pipeline.\n//!\n//! These shaders are compiled via rust-gpu and used b"
  },
  {
    "path": "crates/renderling/tests/wasm.rs",
    "chars": 38360,
    "preview": "//! WASM tests.\n#![allow(dead_code)]\n\nuse craballoc::{\n    runtime::WgpuRuntime,\n    slab::{SlabAllocator, SlabBuffer},\n"
  },
  {
    "path": "crates/renderling/webdriver.json",
    "chars": 186,
    "preview": "{\n  \"moz:firefoxOptions\": {\n    \"prefs\": {\n      \"dom.webgpu.enabled\": true\n    },\n    \"args\": []\n  },\n  \"goog:chromeOpt"
  },
  {
    "path": "crates/renderling-build/Cargo.toml",
    "chars": 465,
    "preview": "[package]\nname = \"renderling_build\"\nversion = \"0.1.0\"\nedition = \"2021\"\ndescription = \"Builds shader linkage for the Rend"
  },
  {
    "path": "crates/renderling-build/src/lib.rs",
    "chars": 10464,
    "preview": "#![allow(unexpected_cfgs)]\nuse naga::{\n    back::wgsl::WriterFlags,\n    valid::{ValidationFlags, Validator},\n};\nuse quot"
  },
  {
    "path": "crates/renderling-ui/Cargo.toml",
    "chars": 1256,
    "preview": "[package]\nname = \"renderling-ui\"\nversion = \"0.1.0\"\nedition = \"2021\"\ndescription = \"Lightweight 2D/UI renderer for render"
  },
  {
    "path": "crates/renderling-ui/src/lib.rs",
    "chars": 1964,
    "preview": "//! Lightweight 2D/UI renderer for renderling.\n//!\n//! This crate provides a dedicated 2D rendering pipeline that is sep"
  },
  {
    "path": "crates/renderling-ui/src/renderer.rs",
    "chars": 81444,
    "preview": "//! Core `UiRenderer` implementation.\n//!\n//! This module contains the GPU pipeline setup, element management,\n//! and r"
  },
  {
    "path": "crates/renderling-ui/src/test.rs",
    "chars": 15896,
    "preview": "//! Tests for the 2D/UI renderer.\n\n#[cfg(test)]\nmod tests {\n    use glam::{Vec2, Vec4};\n\n    use crate::{GradientDescrip"
  },
  {
    "path": "crates/sandbox/Cargo.toml",
    "chars": 273,
    "preview": "[package]\nname = \"sandbox\"\nversion = \"0.1.0\"\nedition = \"2021\"\n\n# See more keys and their definitions at https://doc.rust"
  },
  {
    "path": "crates/sandbox/src/main.rs",
    "chars": 2861,
    "preview": "//! This is a sandbox.\n//!\n//! This program will change on a whim and does not contain anything all that\n//! useful.\nuse"
  },
  {
    "path": "crates/wire-types/Cargo.toml",
    "chars": 104,
    "preview": "[package]\nname = \"wire-types\"\nversion = \"0.1.0\"\nedition = \"2021\"\n\n[dependencies]\nserde.workspace = true\n"
  },
  {
    "path": "crates/wire-types/src/lib.rs",
    "chars": 565,
    "preview": "/// Supported pixel type.\n#[derive(Clone, Debug, serde::Deserialize, serde::Serialize)]\npub enum PixelType {\n    Rgb8,\n "
  },
  {
    "path": "crates/xtask/Cargo.toml",
    "chars": 486,
    "preview": "[package]\nname = \"xtask\"\nversion = \"0.1.0\"\nedition = \"2021\"\n\n[dependencies]\naxum.workspace = true\nclap.workspace = true\n"
  },
  {
    "path": "crates/xtask/src/deps.rs",
    "chars": 715,
    "preview": "//! Xtask dependency helpers.\n//!\n//! This module helps installing xtask's required dependencies.\n\npub async fn has_bina"
  },
  {
    "path": "crates/xtask/src/main.rs",
    "chars": 6540,
    "preview": "//! A build helper for the `renderling` project.\nuse clap::{Parser, Subcommand};\n\nmod deps;\nmod server;\n\n#[derive(Subcom"
  },
  {
    "path": "crates/xtask/src/server.rs",
    "chars": 7663,
    "preview": "//! Axum web server for running the webdriver proxy.\n//!\n//! This proxy server allows the WASM tests to request static a"
  },
  {
    "path": "gltf/SimpleSkin.gltf",
    "chars": 3566,
    "preview": "{\n  \"scene\" : 0,\n  \"scenes\" : [ {\n    \"nodes\" : [ 0, 1 ]\n  } ],\n  \n  \"nodes\" : [ {\n    \"skin\" : 0,\n    \"mesh\" : 0\n  }, {"
  },
  {
    "path": "gltf/animated_triangle.gltf",
    "chars": 2170,
    "preview": "{\n  \"scene\": 0,\n  \"scenes\" : [\n    {\n      \"nodes\" : [ 0 ]\n    }\n  ],\n\n  \"nodes\" : [\n    {\n      \"mesh\" : 0,\n      \"rota"
  },
  {
    "path": "gltf/gltfTutorial_003_MinimalGltfFile.gltf",
    "chars": 1114,
    "preview": "{\n  \"scene\": 0,\n  \"scenes\" : [\n    {\n      \"nodes\" : [ 0 ]\n    }\n  ],\n\n  \"nodes\" : [\n    {\n      \"mesh\" : 0\n    }\n  ],\n\n"
  },
  {
    "path": "gltf/gltfTutorial_008_SimpleMeshes.gltf",
    "chars": 1457,
    "preview": "{\n  \"scene\": 0,\n  \"scenes\" : [\n    {\n      \"nodes\" : [ 0, 1]\n    }\n  ],\n  \"nodes\" : [\n    {\n      \"mesh\" : 0\n    },\n    "
  },
  {
    "path": "gltf/gltfTutorial_013_SimpleTexture.gltf",
    "chars": 1729,
    "preview": "{\n  \"scene\": 0,\n  \"scenes\" : [ {\n    \"nodes\" : [ 0 ]\n  } ],\n  \"nodes\" : [ {\n    \"mesh\" : 0\n  } ],\n  \"meshes\" : [ {\n    \""
  },
  {
    "path": "gltf/gltfTutorial_017_SimpleMorphTarget.gltf",
    "chars": 2994,
    "preview": "{\n  \"scene\": 0,\n  \"scenes\":[\n    {\n      \"nodes\":[\n        0\n      ]\n    }\n  ],\n  \"nodes\":[\n    {\n      \"mesh\":0\n    }\n "
  },
  {
    "path": "gltf/gltfTutorial_019_SimpleSkin.gltf",
    "chars": 3555,
    "preview": "{\n  \"scene\" : 0,\n  \"scenes\" : [ {\n    \"nodes\" : [ 0, 1 ]\n  } ],\n\n  \"nodes\" : [ {\n    \"skin\" : 0,\n    \"mesh\" : 0\n  }, {\n "
  },
  {
    "path": "gltf/shadow_mapping_only_cuboid.gltf",
    "chars": 3977,
    "preview": "{\n\t\"asset\":{\n\t\t\"generator\":\"Khronos glTF Blender I/O v4.3.47\",\n\t\t\"version\":\"2.0\"\n\t},\n\t\"extensionsUsed\":[\n\t\t\"KHR_lights_p"
  },
  {
    "path": "gltf/shadow_mapping_only_cuboid_red_and_blue.gltf",
    "chars": 4497,
    "preview": "{\n\t\"asset\":{\n\t\t\"generator\":\"Khronos glTF Blender I/O v4.3.47\",\n\t\t\"version\":\"2.0\"\n\t},\n\t\"extensionsUsed\":[\n\t\t\"KHR_lights_p"
  },
  {
    "path": "gltf/shadow_mapping_sanity.gltf",
    "chars": 9722,
    "preview": "{\n\t\"asset\":{\n\t\t\"generator\":\"Khronos glTF Blender I/O v4.3.47\",\n\t\t\"version\":\"2.0\"\n\t},\n\t\"extensionsUsed\":[\n\t\t\"KHR_material"
  },
  {
    "path": "gltf/shadow_mapping_sanity_camera.gltf",
    "chars": 5517,
    "preview": "{\n\t\"asset\":{\n\t\t\"generator\":\"Khronos glTF Blender I/O v4.3.47\",\n\t\t\"version\":\"2.0\"\n\t},\n\t\"extensionsUsed\":[\n\t\t\"KHR_lights_p"
  },
  {
    "path": "gltf/simple_morph_triangle.gltf",
    "chars": 2994,
    "preview": "{\n  \"scene\": 0,\n  \"scenes\":[\n    {\n      \"nodes\":[\n        0\n      ]\n    }\n  ],\n  \"nodes\":[\n    {\n      \"mesh\":0\n    }\n "
  },
  {
    "path": "manual/.gitignore",
    "chars": 5,
    "preview": "book\n"
  },
  {
    "path": "manual/book.toml",
    "chars": 684,
    "preview": "[book]\nauthors = [\"Schell Carl Scivally\"]\nlanguage = \"en\"\nsrc = \"src\"\ntitle = \"The Renderling Manual\"\ndescription = \"Ope"
  },
  {
    "path": "manual/src/SUMMARY.md",
    "chars": 337,
    "preview": "# Summary\n\n- [Welcome](./welcome.md)  \n- [Project setup](./setup.md)\n- [Context creation](./context.md)\n- [Staging resou"
  },
  {
    "path": "manual/src/context.md",
    "chars": 2295,
    "preview": "# Context\n\nThe first step of any `renderling` program starts with [`renderling::context::Context`][Context].\n\nThe `Conte"
  },
  {
    "path": "manual/src/gltf.md",
    "chars": 3146,
    "preview": "# Loading GLTF files 📂\n\n`renderling`'s built-in model format is [GLTF](https://www.khronos.org/gltf/), a\nversatile and e"
  },
  {
    "path": "manual/src/lighting/analytical.md",
    "chars": 3193,
    "preview": "# Analytical lights\n\nAnalytical lighting in real-time rendering refers to the use of mathematical\nmodels to simulate the"
  }
]

// ... and 83 more files (download for full content)

About this extraction

This page contains the full source code of the schell/renderling GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 283 files (1.4 MB), approximately 360.3k tokens, and a symbol index with 2182 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.

Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.

Copied to clipboard!